RTL Design: 3-Bit Rules
After learning digital systems design and moving toward using HDL to describe the logic, I learned after making many mistakes that I should first understand the language features and how synthesis tools use the language constructs to synthesize the circuit. Based on this, I always practice certain rules while writing RTL and sharing that with anyone who is just getting started. I call them 3-bit rules to RTL design.
3-Bit Rules:
3'b000: Blocking vs non-blocking assignments
Use non-blocking (<=) whenever the intent is to infer a register (flip-flop) for a signal. Also, note that use always_ff with a clock edge (posedge/negedge) to correctly describe a register in the design.
For combinational logic, use the blocking assignments. This is to be done inside the always_comb blocks. Alternatively, assign statements can be used to do the same.
3'b001: Never mix blocking (=) and non-blocking (<=) assignments within procedural blocks
This is a mistake I made a lot the first time I learned the language. Never do this. Follow the first rule and never mix the assignments within the same always blocks. It is broken if your test case works because you have mixed assignments inside your design.
3'b010: Never use #delays in the design
Initially a few years back when I first learned Verilog HDL, I saw a lot of online design examples where #delays were used in the design. Never do this!! They don't work as you might expect and will cause a lot of bugs, mismatches, and hardware will never work. Use them in testbenches, no problem there :-)
3'b011: Separate multiple sequential or combinational logic in different always blocks
This is mainly for those who are new to the language if your design has multiple sequential or combinational circuit elements. Then it is good to start with having separate logic blocks for each part of the circuit. Tremendously helps in debugging. Later on, when you get more practice you can do it in the same procedural blocks.
3'b100: Follow a good naming convention from the beginning
Yes, it is not just good practice but essential for any good design and debugging. Later on, you will be instantiating multiple blocks and declaring a lot of internal signals. If proper naming convention is not followed trust me you will get lost while debugging.
3'b101: Start with a basic testbench that can at the very least generate basic signals and can drive some direct input to your design.
This is mostly my preference, even before I start with the design I first create a testbench capable of driving my module interface signals. This includes the clock generator, reset condition, and some directed input signal values based on the design. It helps a lot when I start the RTL design and can quickly run the testbench to see if my signals are correctly getting initialized on reset. Is my circuit able to drive the output based on some input?
Later, as I make more progress with my design, I enhance my testbench along with it. Don't leave the testbench as the last part, design and verification in my opinion goes hand-in-hand.
3'b110: Be the synthesis tool! Whenever possible, draw the circuit synthesis will create.
Anytime you write any RTL, think and try to hand draw what circuit it will generate. This will make you a better designer and also help you understand the design better. If you know what circuit is getting synthesized you can understand the timing and debug faster.
3'b111: Think parallel!.
Hardware design is a parallel approach. Don't think of it as software programming. Software follows sequential semantics but this is not the case with hardware languages. All the signals that you declare have a physical meaning. They translate to logic gates with wires and things happen in parallel i.e. at the same time instant multiple parts of the circuit are active and driving different signals.