Skip to main content

Verilog RTL Design – Best Practices

· loading · loading · · ·
HDL Verilog HDL
Axolot Logic
Author
Axolot Logic
Digital Design Engineer
Table of Contents
Verilog HDL Series - This article is part of a series.
Part 23: This Article

🛠️ Verilog RTL Design – Writing Rules & Best Practices
#

When writing RTL modules in Verilog, adhering to consistent structure and syntax is crucial for readability, synthesizability, and scalability. Here are the golden rules to follow:


📌 1. Module Definition
#

Start every design block with a module and end with endmodule.

In the image below, you can see an abstract representation of this module. It includes only the module declaration and port definitions. At this stage, it can now be connected to another module or instantiated inside another module.

module module_name(
  input  wire clk,
  input  wire rst,
  input  wire a,
  output wire y
);
// Internal logic here
endmodule

📌 2. Use Meaningful Port Names
#

Avoid single-letter names in real designs — use clk, rst_n, data_in, valid, etc.


📌 3. Port Direction & Type Declaration
#

Use input, output, and optionally inout. Ports should be typed as wire by default. Use reg only for values driven inside always blocks.

input  wire clk;
output reg  valid_out;  // Driven inside always block

📌 4. Consistent Formatting
#

Keep clean indentation and consistent naming. Example:

assign y = a & b;

📌 5. Combinational Logic → assign or always @(*)
#

Use assign for continuous assignments (preferred for simple logic):

assign y = a & b;

Or, for more complex logic:

always @(*) begin
  case (sel)
    2'b00: out = in0;
    2'b01: out = in1;
    ...
  endcase
end

📌 6. Sequential Logic → always @(posedge clk)
#

Use reg type and non-blocking assignment (<=) for sequential logic.

always @(posedge clk or posedge rst) begin
  if (rst)
    count <= 0;
  else
    count <= count + 1;
end

📌 7. Don’t Mix Blocking and Non-blocking
#

In the same always block, use only one type:

  • = for combinational logic (always @(*))
  • <= for sequential logic (always @(posedge clk))

📌 8. Reset Logic
#

Always define reset behavior if required by synthesis/initialization.

if (rst) begin
  state <= IDLE;
end

📌 9. Avoid Delay # in RTL
#

Delays like #10 are for testbenches only. They are not synthesizable and should never appear in RTL.


📌 10. Avoid initial in RTL
#

Use initial only in testbenches or simulation-only code.


✅ Summary Table
#

Rule Do This Avoid This
Use module and endmodule module mymod(...); ❌ Unnamed or inline code
Use assign or always assign y = a & b; always y = a & b;
Use <= for seq logic count <= count + 1; count = count + 1;
Use reg inside always reg valid; always @(...) wire assigned in always
Avoid delays in RTL assign #no_delay #10, #5 in logic
Clear reset logic if (rst) q <= 0; ❌ No reset, or floating states

🧪 Verilog Testbench – Writing Rules & Best Practices
#

A testbench is a simulation-only Verilog module used to stimulate and observe the behavior of your RTL design. It is not synthesizable and is essential for verifying correctness before hardware implementation.


📌 1. No Ports in Testbench Module
#

Testbenches are not connected to external pins, so no input/output.

module tb_my_design;

📌 2. Declare Signals to Connect to DUT
#

Define reg for inputs and wire for outputs of your DUT.

reg clk, rst, a, b;
wire y;

📌 3. Instantiate the DUT (Design Under Test)
#

Connect declared signals to your RTL module using named mapping.

my_design dut (
  .clk(clk),
  .rst(rst),
  .a(a),
  .b(b),
  .y(y)
);

📌 4. Generate Clock Signal
#

Use an always block to toggle clock.

initial clk = 0;
always #5 clk = ~clk;  // 10ns clock period

📌 5. Apply Stimulus Using initial Block
#

Use initial to define your test sequence.

initial begin
  rst = 1; a = 0; b = 0;
  #10 rst = 0;
  #10 a = 1;
  #10 b = 1;
  #20 $finish;
end

📌 6. Monitor Outputs with $monitor or $display
#

Use these for observing signal values in the terminal or waveform.

initial begin
  $monitor("Time=%0t : a=%b b=%b y=%b", $time, a, b, y);
end

📌 7. Use $dumpfile and $dumpvars for Waveform (GTKWave etc.)
#

Enable waveform generation for viewing in tools like GTKWave.

initial begin
  $dumpfile("wave.vcd");
  $dumpvars(0, tb_my_design);
end

✅ Sample Testbench Template
#

module tb_and_gate;

  reg a, b;
  wire y;

  // Instantiate DUT
  and_gate dut (
    .a(a),
    .b(b),
    .y(y)
  );

  initial begin
    $dumpfile("wave.vcd");
    $dumpvars(0, tb_and_gate);
    $monitor("Time: %0t | a=%b b=%b y=%b", $time, a, b, y);

    // Stimulus
    a = 0; b = 0; #10;
    a = 1; b = 0; #10;
    a = 0; b = 1; #10;
    a = 1; b = 1; #10;
    $finish;
  end

endmodule

🎯 Summary Table
#

Rule Do This Avoid This
No ports in testbench module tb_my_module; ❌ Using input/output
Clock generation always #5 clk = ~clk; ❌ Manual toggling inside initial
Use initial for stimulus a = 1; #10; b = 0; ❌ Using always with delays
Monitor output $monitor or $display ❌ No visibility into values
Dump waveform $dumpfile("wave.vcd") ❌ No waveform = no debug

📚 Recommended Verilog Coding Style Guide #

When writing Verilog code—whether for FPGA prototyping, ASIC design, or formal verification—following a clear and consistent coding style is crucial. It not only improves readability and maintainability but also helps teams collaborate more effectively.

For this reason, I highly recommend referring to the lowRISC Verilog Coding Style Guide as a baseline for best practices.

🔗 Guide Link: 👉

Why follow this guide?
#

  • ✅ Clear conventions for signal naming and module hierarchy
  • ✅ Guidelines for formatting, spacing, and indentation
  • ✅ Recommendations for synthesizable vs non-synthesizable constructs
  • ✅ Practices for simulation clarity and debugging ease

Whether you’re just getting started with Verilog or you’re building a large-scale design project, adopting this guide can save time and prevent costly misunderstandings.


Verilog HDL Series - This article is part of a series.
Part 23: This Article

Related

Command-Line Input
· loading · loading
HDL Verilog HDL
Module Definition, Usage, and Constructs
· loading · loading
HDL Verilog HDL
Synthesis
· loading · loading
HDL Verilog HDL
Blocking vs Non-Blocking Assignments
· loading · loading
HDL Verilog HDL
Compiler Directives & Macros
· loading · loading
HDL Verilog HDL
Control Flow
· loading · loading
HDL Verilog HDL

comments powered by Disqus