It is hard to give a "best practices" for HDL mainly because of the difference between actual good practices, and tool limitations. For example, it has long been advised to avoid multi-dimensional arrays on module ports even though there is nothing logically wrong with it. A lot of the ASIC-world "best-practices" involve assuming that global optimizations cannot be done. Likewise, best practices differ for FPGA vs ASIC in some cases like resets. For FPGAs, it is advised to avoid resets or use synchronous resets more often (Xilinx). For ASICs, it is assumed that there will be a reset on almost everything.
For composition of a design into modules, I do have some opinions. The main ones are:
1.) Avoid making tightly coupled logic modules for the sole purpose of making a large file into multiple smaller files. This is hard to really express, as it makes sense to divide a design into smaller functions which may end up being tightly-coupled with their intended instantiation. An example of an inappropriate module would be removing a state machine from the logic feeding it and the logic it feeds when there is no intention to provide a standard interface that might allow it to be exchanged with a different state machine.
2.) Avoid mesh-like block diagrams. Basically, if you have five modules, each module should ideally only interface to two other modules (an input and an output), forming a simple data path. There are clearly times when this won't work. The point is to attempt to consolidate interfaces. This allows designs to be modified more easily because logic changes are largely tied to single files. An example would be adding a pipeline register to a design where half of the signals from A to C go to module B. If all signals from A to C went first to B, and only some were modified on the route to C, then in adding the pipeline stage in module B it would be clear that some additional signals would need to be pipelined.
3.) Document interfaces. For the same reasons as above. It is very easy to mislead people with port names, like "en". This example name doesn't provide any indication on if "en" can be '1' every cycle, or must be high every other cycle, or etc...
4.) it makes sense to make modules for common code. If you find yourself solving the same problem over and over again, it is possible you want to start making a few modules and then reusing them later.
Of course #1/2 have a lot of exceptions. It is common to have fascade modules which are just a grouping of instances with a little glue logic. Clearly an exception for #2 would be made for things like a centralized control bus. #4 also requires some caution as it is possible to create "overly-generic" code -- functions that you've made very customizable, but are rarely used and have functionality that is never used and never tested.