DEV Community

Cover image for Verilog Overview: How to Write Verilog Code
Semiwave Technologies
Semiwave Technologies

Posted on

Verilog Overview: How to Write Verilog Code

How to Write Verilog Code: A Beginner’s Guide for RTL Design Engineers

Hey there! If you’re diving into the world of ASIC design or just brushing up on your hardware description language (HDL) skills, Verilog is likely your go-to tool. As an RTL design engineer, I’ve spent countless hours crafting Verilog code for everything from simple counters to complex state machines. In this post, I’ll walk you through the essentials of writing clean, functional Verilog code, with practical tips and snippets to get you started. Whether you’re a newbie or a seasoned verification engineer looking to understand the RTL side better, this guide’s got you covered!

Why Verilog?

Verilog is one of the most popular HDLs used in ASIC and FPGA design. It’s concise, widely supported, and strikes a balance between abstraction and control. Here’s why it’s a staple in VLSI:

  • Industry Standard: Used in countless EDA tools like Synopsys, Cadence, and Questa.
  • Simulation-Friendly: Perfect for both design and verification workflows.
  • Scalable: Works for simple modules or massive SoCs.

Let’s jump into the nuts and bolts of writing Verilog code.

Verilog Basics: Structure of a Module

Every Verilog design starts with a module, the building block of your hardware. Think of it as a function in software, but it describes physical hardware behavior. Here’s the basic syntax:

module module_name (
    input wire clk,    // Clock input
    input wire reset,  // Reset input
    output reg out     // Output
);
    // Your logic goes here
endmodule
Enter fullscreen mode Exit fullscreen mode
  • Module Declaration: Defines the module name and its ports (inputs/outputs).
  • Port Types: Use wire for combinational signals and reg for sequential (clocked) outputs.
  • endmodule: Closes the module. No semicolons here!

Pro Tip: Always name your module descriptively (e.g., counter_4bit instead of mod1). It saves headaches during debugging!

Writing Your First Verilog Module: A 4-Bit Counter

Let’s create a simple 4-bit counter that increments on every clock cycle and resets to zero when reset is high. This is a classic example for RTL beginners.

module counter_4bit (
    input wire clk,    // Clock input
    input wire reset,  // Synchronous reset
    output reg [3:0] count  // 4-bit counter output
);
    always @(posedge clk) begin
        if (reset)
            count <= 4'b0000;  // Reset to 0
        else
            count <= count + 1;  // Increment count
    end
endmodule
Enter fullscreen mode Exit fullscreen mode

Breaking It Down

  • Inputs/Outputs: clk and reset are inputs, count is a 4-bit output ([3:0] means 4 bits, from 0 to 15).
  • always @(posedge clk): This block triggers on the rising edge of the clock, making it synchronous.
  • Non-Blocking Assignments (<=): Used in sequential logic to ensure proper register updates.
  • Reset Logic: When reset is high, count is set to 0; otherwise, it increments.

Verification Note: If you’re a verification engineer, you’d write a testbench to simulate this. Want a testbench example? Let me know in the comments!

Best Practices for Clean Verilog Code

Writing Verilog is more than just getting it to work—it’s about making it readable, synthesizable, and maintainable. Here are my go-to tips:

  • Use Meaningful Names: clk over c, data_out over d.
  • Comment Liberally: Explain complex logic. Future you will thank you.
  // Increment counter only if enable is high
  if (enable) count <= count + 1;
Enter fullscreen mode Exit fullscreen mode
  • Stick to Non-Blocking for Sequential Logic: Use <= in always @(posedge clk) blocks to avoid race conditions.
  • Avoid Latches: Ensure all paths in combinational always blocks assign every output to prevent unintended latches.
  always @(*) begin
      if (sel)
          out = in1;  // Assign for all cases
      else
          out = in2;
  end
Enter fullscreen mode Exit fullscreen mode
  • Keep It Modular: Break complex designs into smaller modules for reusability and easier debugging.

Common Pitfalls to Avoid

  • Blocking vs. Non-Blocking: Using = instead of <= in sequential logic can cause simulation-synthesis mismatches.
  • Missing Sensitivity Lists: For combinational logic, use @(*) to automatically include all inputs.
  • Overcomplicating Logic: Simplify your design before coding. Sketch state machines or logic flows on paper first.

Testing Your Code

As an RTL designer, you’ll work closely with verification engineers. A basic testbench for our counter might look like this:

module tb_counter_4bit;
    reg clk, reset;
    wire [3:0] count;

    counter_4bit uut (
        .clk(clk),
        .reset(reset),
        .count(count)
    );

    initial begin
        clk = 0;
        reset = 1;
        #10 reset = 0;  // Release reset after 10ns
        #100 $finish;   // End simulation
    end

    always #5 clk = ~clk;  // 100MHz clock (10ns period)
endmodule
Enter fullscreen mode Exit fullscreen mode

This testbench generates a clock, applies a reset, and lets the counter run. Run it in your favorite simulator (e.g., ModelSim) to see the counter increment!

Why This Matters for VLSI Enthusiasts

Writing clean Verilog code isn’t just about functionality—it’s about ensuring your design is synthesizable, testable, and scalable. As ASIC designs grow more complex, good coding practices save time in synthesis, place-and-route, and verification. Plus, they make you a hero in the eyes of your verification team!

What’s Next?

  • Try It Yourself: Code a 4-bit counter in your favorite EDA tool and simulate it.
  • Explore Further: Add an enable signal or make the counter up/down configurable.
  • Deep Dive: Check out my next post on “Writing Testbenches for Verilog” for verification tips!

Got questions or want to share your Verilog tips? Drop them in the comments below! Let’s keep the VLSI community buzzing.

Top comments (0)