Skip to content

Session 5 - RTL Design & Verification

Course material

Summary

RTL = Registers + Combinational Logic + Clock Use wire + assign for combinational Use reg + always @(posedge clk) for sequential FSMs organize sequential behavior Testbenches verify your design before synthesis

Homework

  • Write Verilog for your project’s core module (aim for 10-30 lines to start) Integrate with any provided library modules (e.g., debounce, UART, PWM) — create a top-level wrapper

  • Simulate with a testbench and examine waveforms in GTKWave Run linter (verilator –lint-only) and fix any warnings

Class memo

What is a RTL level?

We have touched to transistor level and gate level so far. In this class we learned about RTL level design.

Verilog basic

In Verilog, number is shown as bellow. - 8’b10101010 // 8-bit binary: 170 - 8’hAA // 8-bit hex: 170 - 8’d170 // 8-bit decimal: 170 - 8’o252 // 8-bit octal: 170

  • 4’b1010 // 4-bit value: 10
  • 4’bxxxx // 4-bit unknown
  • 4’bzzzz // 4-bit high-impedance

Difference of Assign and Always

  • “assign” seems connection
assign out = in1 & in2;
  • “always” seems proceedure. It work following to the CLK.
always @(posedge clk) begin
    out <= in;
end

Assignment 1

Write Verilog for your project’s core module (aim for 10-30 lines to start)

I tried to design a code that generate 261.63Hz square wave

  • Following to web search, I set 10MHz for system CLK.
    • Then 10,000,000Hz / 261.63 Hz → 38,222 count

  • I asked Gemini to …
    • “Design Verilog program that generate 261.63Hz square wave from 10MHz clock wave.”
    • Then I got the code bellow.

note_c_generator.v

module note_c_generator (
    input  wire clk,      // System clock input 10MHz
    input  wire rst_n,    // Active-low asynchronous reset
    input  wire enable,   // Enable signal (High to output sound)
    output reg  sq_wave   // Square wave output (Note C)
);


    localparam CLK_COUNT_MAX = 19111;

    // 15-bit counter to reach 19111
    reg [14:0] counter;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            // Asynchronous reset to initial state
            counter <= 15'd0;
            sq_wave <= 1'b0;
        end else if (enable) begin
            if (counter >= CLK_COUNT_MAX - 1) begin
                // Reset counter and toggle the output state
                counter <= 15'd0;
                sq_wave <= ~sq_wave;
            end else begin
                // Increment counter each clock cycle
                counter <= counter + 1'b1;
            end
        end else begin
            // Reset state when enable is low (Silence)
            counter <= 15'd0;
            sq_wave <= 1'b0;
        end
    end

endmodule

Assignment 2

Integrate with any provided library modules (e.g., debounce, UART, PWM) — create a top-level wrapper

I updated my system diagram. - I asked Gemini to … - “Design Verilog program that integrate button input and speaker output.”

Then I got 2 following code.

1. Top-level Wrapper (top.v)

module top (
    input  wire clk,      // 10MHz System Clock
    input  wire rst_n,    // Reset
    input  wire btn_in,   // Physical Button input
    output wire spk_out   // To Speaker/Buzzer
);

    wire clean_enable;

    // 1. Instantiate Debounce module
    // To filter out mechanical noise from the button
    debounce u_debounce (
        .clk(clk),
        .rst_n(rst_n),
        .noisy_in(btn_in),
        .clean_out(clean_enable)
    );

    // 2. Instantiate your Note C Generator
    // Generates ~261.63Hz when clean_enable is High
    note_c_generator u_note_c (
        .clk(clk),
        .rst_n(rst_n),
        .enable(clean_enable),
        .sq_wave(spk_out)
    );

endmodule

2. Simple Debounce Module (debounce.v)

module debounce (
    input  wire clk,
    input  wire rst_n,
    input  wire noisy_in,
    output reg  clean_out
);
    // 10MHz clock, 1ms delay = 10,000 cycles
    reg [13:0] count;
    reg sync_0, sync_1;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            sync_0 <= 1'b0;
            sync_1 <= 1'b0;
            count  <= 14'd0;
            clean_out <= 1'b0;
        end else begin
            sync_0 <= noisy_in; // Synchronize to clk domain
            sync_1 <= sync_0;

            if (sync_1 == clean_out) begin
                count <= 14'd0;
            end else if (count >= 14'd10000) begin
                clean_out <= sync_1;
                count <= 14'd0;
            end else begin
                count <= count + 1'b1;
            end
        end
    end
endmodule

Assignment 3

Simulate with a testbench and examine waveforms in GTKWave

  • I asked Gemini to …
    • “Make test bench file”

Then I got following code.

Testbench code (top_tb.v)

timescale 1ns / 1ps

module top_tb();

    // Inputs
    reg clk;
    reg rst_n;
    reg btn_in;

    // Output
    wire spk_out;

    // Instantiate the Top-level module
    top uut (
        .clk(clk),
        .rst_n(rst_n),
        .btn_in(btn_in),
        .spk_out(spk_out)
    );

    // Clock generation: 10MHz (Period = 100ns)
    always #50 clk = ~clk;

    initial begin
        // --- Step 1: Initialize signals ---
        clk = 0;
        rst_n = 0;
        btn_in = 0;

        // --- Step 2: Release Reset ---
        #200 rst_n = 1;
        $display("Reset released at %t", $time);

        // --- Step 3: Simulate Button Chattering ---
        // Debounce logic waits for 1ms stability.
        #1000;
        btn_in = 1; #50000;  // High for 50us
        btn_in = 0; #50000;  // Low for 50us
        btn_in = 1; #50000;  // High for 50us
        $display("Button chattering finished at %t", $time);

        // --- Step 4: Hold Button (Stable) ---
        // Now keep it High for more than 1ms to trigger the sound
        btn_in = 1;
        $display("Button held stable from %t", $time);

        // Wait for sound to start and observe a few cycles
        // 1ms (debounce) + 10ms (approx 2.5 cycles of Note C)
        #11000000; 

        // --- Step 5: Release Button ---
        btn_in = 0;
        $display("Button released at %t", $time);

        #2000000; // Observe silence

        $display("Simulation finished.");
        $finish;
    end

    // Create a waveform file for GTKWave
    initial begin
        $dumpfile("top_tb.vcd");
        $dumpvars(0, top_tb);
    end

endmodule

I prepared following files.

Compile

Then I compiled them with following command.

iverilog -o sim.out top.v debounce.v note_c_generator.v top_tb.v

Run simulation

vvp sim.out

vvp is a runtime engine for Icarus Verilog.

Show result

gtkwave top_tb.vcd

Assignment 4

Run linter (verilator –lint-only) and fix any warnings

verilator --lint-only -Wall top.v debounce.v note_c_generator.v

After hitting above command, I got the result

- V e r i l a t i o n   R e p o r t: Verilator 5.044 2026-01-01 rev v5.044
- Verilator: Built from 0.061 MB sources in 4 modules, into 0.010 MB in 3 C++ files needing 0.000 MB
- Verilator: Walltime 0.007 s (elab=0.000, cvt=0.002, bld=0.000); cpu 0.006 s on 1 threads; alloced 30.348 MB

This message means these 4 modules I created ran in 0.007s(Walltime) without any erro/wornings.

At last, I tried to make mistake.

I changed the value of signal widths line 14 to 10.

collect: 
reg [14:0] counter;
↓
wrong: 
reg [10:0] counter;

The result of simulation is already wrong…

Then I got following wornings by linting.

%Warning-WIDTHTRUNC: note_c_generator.v:17:21: Operator ASSIGNDLY expects 11 bits on the Assign RHS, but Assign RHS's CONST '15'h0' generates 15 bits.
                                             : ... note: In instance 'top.u_note_c'
   17 |             counter <= 15'd0;
      |                     ^~
                     ... For warning description see https://verilator.org/warn/WIDTHTRUNC?v=5.044
                     ... Use "/* verilator lint_off WIDTHTRUNC */" and lint_on around source to disable this message.
%Warning-WIDTHTRUNC: note_c_generator.v:22:25: Operator ASSIGNDLY expects 11 bits on the Assign RHS, but Assign RHS's CONST '15'h0' generates 15 bits.
                                             : ... note: In instance 'top.u_note_c'
   22 |                 counter <= 15'd0;
      |                         ^~
%Warning-WIDTHEXPAND: note_c_generator.v:20:25: Operator GTE expects 32 or 15 bits on the LHS, but LHS's VARREF 'counter' generates 11 bits.
                                              : ... note: In instance 'top.u_note_c'
   20 |             if (counter >= CLK_COUNT_MAX - 1) begin
      |                         ^~
                      ... For warning description see https://verilator.org/warn/WIDTHEXPAND?v=5.044
                      ... Use "/* verilator lint_off WIDTHEXPAND */" and lint_on around source to disable this message.
%Warning-WIDTHTRUNC: note_c_generator.v:30:21: Operator ASSIGNDLY expects 11 bits on the Assign RHS, but Assign RHS's CONST '15'h0' generates 15 bits.
                                             : ... note: In instance 'top.u_note_c'
   30 |             counter <= 15'd0;
      |                     ^~
%Error: Exiting due to 4 warning(s)

I could see the warning by chaning the error inside of the Verilog code.