Skip to content

Session 5: RTL Design & Verification


Homework Given

  • 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

## Verilog for My Project's Core Module

Based on the block diagram for my digital password lock project, The main components of the system are:

  1. The 4-bit entered password from the user switches
  2. The 4-bit stored password defined in the comparator
  3. A debounced button input used to submit the password
  4. A 4-bit register that stores the entered password
  5. A password comparator that checks whether the entered password matches the stored password
  6. A Finite State Machine (FSM) that controls the sequence of system operations
  7. A fail counter that tracks incorrect password attempts
  8. LED output signals that indicate access granted, denied, or system lock

The core logic of the system is to check whether the entered password matches the stored password.

To begin, I examined the module structure and reviewed the example files provided. With this initial understanding, I started developing the Verilog code for my design.

My project had some similarities with the fortune teller example, so I found it useful to refer to that Verilog implementation while structuring my own module.

I decided to start with a simple core module that performs only the password comparison. My approach was to separate the design into two parts:

  1. A core module that implements the main logic, and

  2. A top-level wrapper module

The core module acts as a password comparator and is shown below:

`timescale 1ns/1ps

module password_core (

    input  [3:0] entered_pw,    // The user entered password using button
    output match 
);
parameter stored_pw = 4'b1011; //stored password

assign match = (entered_pw == stored_pw);

endmodule

At this stage, I chose to focus only on the comparator logic.

Currently, the system does not store any values dynamically. As a result, the current implementation consists purely of combinational logic.


Top level Wrapper

Before I worked on the top level wrapper, I wanted to understand what exactly I am looking at. These are the websites I referred to learn more about this concept.

Analog Circuit Design Verilog module

With this I understood, the top level warpper is the module which is at the highest-level in the design hierarchy. It represents the overall system or top-level view of the digital circuit. It connects external components, instantiates internal module and wires everything together.

From the digikey website, under the top-level module provided an example. Based on that example, I worked on my own top-level module which includes parameters,ports,and the button debouncing module which is from the library and an instantiation of my core module.

module digital_password_lock #(

    // Parameters

  parameter CLK_FREQ = 50_000_000,  // Your board's clock speed (Hz)
  parameter BAUD     = 115200       // Serial communication speed
)(

    // Ports

    input  wire clk,    
    input  wire rst_n, 
    input  wire btn,   
    input reg [3:0] pw_switch,  
    output wire green_led,    // if password matches
    output wire red_led       // if password does not match
);

// Button Debouncing, Debounce module from the library


    wire btn_pressed;  // Clean, debounced button signal
    wire match;

    debounce #(
         .CLK_FREQ(CLK_FREQ)  // Pass our clock freq to the  debouncer
    ) debounce_inst (
        .clk(clk),
        .rst_n(rst_n),
        .btn_raw(btn),           // Raw button input
        .btn_pressed(btn_pressed) // Clean output
    );

//Instantiate core module 

password_core core ( 
    .entered_pw(pw_switch),
    .match(match)
  );

  //LED logic

 assign green_led = match;
 assign red_led = ~match;

 endmodule
In this design, the top level module integrates the debouncing module and the password comparator module. The switches provide the user input for the entered password, and the comparator determines whether the entered password matches the stored password. The LEDs then provides the result of this comparison.


Simulation and Waveform

Before I improve my code further, I wanted to try simulating it and view the waveform.

To verify the functionality of the digital password lock design, a testbench was created. When I first ran the Hello World simulation, I worked with a testbench, which gave me a basic understanding of how testbenches function in Verilog simulation.

A testbench is a Verilog module used to simulate and validate the behavior of a design before implementing it on actual hardware. It provides input signals to the design and observes the resulting outputs in a controlled simulation environment.

Unlike the regular design modules, a testbench does not have input or output ports. Instead, it internally generates signals such as the clock, reset, and input values that are connected to the Device Under Test (DUT).

`timescale 1ns/1ps

module digital_password_lock_tb;

    reg  clk;      
    reg  rst_n;    
    reg  btn;      
    reg [3:0] pw_switch;      

    wire green_led;      
    wire red_led;


    // Device Under Test (DUT) Instantiation

    // We instantiate the top-level module

    digital_password_lock #(
        .CLK_FREQ(1_000_000),   // 1 MHz clock for fast simulation
        .BAUD(100_000)          // 100 kbaud for easy timing math
    ) dut (
        .clk(clk),
        .rst_n(rst_n),
        .btn(btn),
        .pw_switch(pw_switch),
        .green_led(green_led),
        .red_led(red_led)
    );

    always begin
        #500;           // Wait 500 ns
        clk = ~clk;     // Toggle clock (0->1 or 1->0)
    end

    initial begin

        // Waveform Dumping
        // viewed in GTKWave to see all signal transitions.

        $dumpfile("password_lock_tb.vcd");  // Output filename
        $dumpvars(0, digital_password_lock_tb);  // Dump all signals in this module

        // Initialize Signals
        // At time 0, set initial values for all inputs

        clk   = 0;   // Clock starts low
        rst_n = 1;  
        btn = 0;

   // Test 1: wrong password
        pw_switch = 4'b0000;
        btn  = 1;   // Button pressed
        #15000000;

        if (green_led && !red_led)
            $display("Password %b: ACCESS GRANTED",pw_switch);
        else if(!green_led && red_led)
            $display("Password %b: ACCESS DENIED",pw_switch);
        else
            $display("ERROR: Invalid LED State");
        #3000000;
  // Test 2: Correct password
    pw_switch = 4'b1011;
        btn  = 1;   // Button pressed
     #15000000;
        if (green_led && !red_led)
            $display("Password%b: ACCESS GRANTED",pw_switch);
        else if(!green_led && red_led)
            $display("Password%b: ACCESS DENIED",pw_switch);
        else
            $display("ERROR: Invalid LED State");
          #3000000;
        $finish;

    end

endmodule

In this project, the testbench instantiates the digital_password_lock module as the Device Under Test (DUT). Since my current system only has a debounce and comparator,it does not depend on a stored state and an initialization.

For this simple test bench, I impemented 2 test cases. In the first test, the switch input was set to 0000, which does not match the stored password. The expected result is that access is denied, with the red LED ON and the green LED OFF. In the second test, the switch input was set to 1011, which matches the stored password. In this case, access should be granted, turning the green LED ON and the red LED OFF.


Simulation Output

Now I wanted to do the simulation to verify my outputs. I ran the following command:

iverilog -o digital_password_lock.v digital_password_lock_tb.v 
Which gave me the following error: I relaized I used the wrong command and it was only running the testbench file. One thing I didn't inlcude was the debounce file when running the command and I got the error: unknown module type for debounce.

Then I changed the command to :

iverilog -o sim  digital_password_lock.v digital_password_lock_tb.v debounce.v

With this I got another set of errors, which were mostly syntax errors

I fixed the errors and did the simulation again and it worked out. I got sim file and I ran the following command:

vvp sim
With this I got the following output:

I ran the following command to view the waveform:

gtkwave password_lock_tb.vcd

The simulation waveform verifies the functionality of the digital password lock. Initially, the entered password is set to 0000, which does not match the stored password 1011, resulting in the red LED being asserted and the green LED remaining low. When the input password changes to 1011, matching the stored password, the green LED transitions to logic high while the red LED transitions to logic low, indicating successful authentication.

Final Verilog and Testbench

After completing the initial development and simulation, I gained a clearer understanding of the design workflow. With this understanding, I proceeded to integrate the remaining components required for the digital password lock system and finalize the design.

`timescale 1ns/1ps

// ============================================================================
// Password Comparator
// ============================================================================
module password_core (
    input wire [3:0] entered_pw,
    output wire match
);

    parameter stored_pw = 4'b1011;

    assign match = (entered_pw == stored_pw);

endmodule


// ============================================================================
// 4-bit Register
// Stores the switch value when load is asserted
// ============================================================================
module register4 (
    input             clk,
    input             rst_n,
    input             load,
    input      [3:0]  d,
    output reg [3:0]  q
);

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            q <= 4'b0000;
        else if (load)
            q <= d;
    end

endmodule


// ============================================================================
// Top Module with FSM
// States:
//   IDLE: waiting
//   CHECK: compare entered password
//   ACCESS_OK: green LED on
//   ACCESS_FAIL: red LED on
// ============================================================================

module digital_password_lock #(
    parameter CLK_FREQ = 50_000_000,
    parameter BAUD     = 115200
)(
    input  wire       clk,
    input  wire       rst_n,
    input  wire       btn,
    input  wire [3:0] pw_switch,
    output wire       green_led,
    output wire       red_led,
    output wire [2:0] state_out     // for waveform viewing
);

    // ------------------------------------------------------------------------
    // Internal signals
    // ------------------------------------------------------------------------
    wire btn_pressed;
    wire match;
    wire [3:0] entered_pw;

    reg [2:0] state, next_state;

    //failed attempts
    reg[1:0] fail_count;

    //blink
    reg[23:0] blink_counter;
    reg blink;


    localparam IDLE        = 3'b000;
    localparam LOAD        = 3'b001;
    localparam CHECK       = 3'b010;
    localparam ACCESS_OK   = 3'b011;
    localparam ACCESS_FAIL = 3'b100;
    localparam LOCKED      = 3'b101;

    // ------------------------------------------------------------------------
    // Debounce
    // ------------------------------------------------------------------------

    debounce #(
        .CLK_FREQ(CLK_FREQ)
    ) debounce_inst (
        .clk(clk),
        .rst_n(rst_n),
        .btn_raw(btn),
        .btn_pressed(btn_pressed)
    );

    // ------------------------------------------------------------------------
    // Register
    // Capture password when debounced button is pressed
    // ------------------------------------------------------------------------

    register4 pw_register (
        .clk(clk),
        .rst_n(rst_n),
        .load(btn_pressed),
        .d(pw_switch),
        .q(entered_pw)
    );

    // ------------------------------------------------------------------------
    // Comparator
    // ------------------------------------------------------------------------
    password_core core (
        .entered_pw(entered_pw),
        .match(match)
    );

    // ------------------------------------------------------------------------
    // FSM state register
    // ------------------------------------------------------------------------
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            state <= IDLE;
        else
            state <= next_state;
    end

    // ------------------------------------------------------------------------
    // Fail counter
    // ------------------------------------------------------------------------
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            fail_count <= 0;

        else if (state == ACCESS_OK)
            fail_count <= 0;

        else if (state == CHECK && !match)
            fail_count <= fail_count + 1;
    end

    // ------------------------------------------------------------------------
    // Blink generator
    // ------------------------------------------------------------------------
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            blink_counter <= 0;
            blink <= 0;
        end
        else begin
            blink_counter <= blink_counter + 1;
            blink <= blink_counter[10];
        end
    end

    // ------------------------------------------------------------------------
    // FSM next-state logic
    // ------------------------------------------------------------------------
    always @(*) begin
        next_state = state;

        case (state)
            IDLE: begin
                if (btn_pressed)
                    next_state = LOAD;
            end

            LOAD: begin
                    next_state = CHECK;
            end

            CHECK: begin
                if (match)
                    next_state = ACCESS_OK;
                else if (fail_count == 2)
                    next_state = LOCKED;
                else
                    next_state = ACCESS_FAIL;
            end

            ACCESS_OK: begin
                if (btn_pressed)
                    next_state = LOAD;
            end

            ACCESS_FAIL: begin
                if (btn_pressed)
                    next_state = LOAD;
            end

            LOCKED: begin
                next_state = LOCKED;
            end

            default: begin
            next_state = IDLE;
            end
        endcase
    end

    // ------------------------------------------------------------------------
    // Outputs
    // ------------------------------------------------------------------------
    assign green_led = (state == ACCESS_OK);
    assign red_led   = 
        (state == ACCESS_FAIL) ? 1'b1:
        (state == LOCKED)      ? blink :
                 0;                 

    assign state_out = state;

endmodule

For the finalized version of my Digital Password Lock Design (digital_password_lock.v), since I already worked on my core module and had an idea of how to write the top level wrapper, I worked on the 4-bit register using D flip-flop which would store the entered password when the button is pressed. The flip-flop pattern was already given in the class webpage. So it was easy to figure it out.

It was now time to figure out the finite state machine for my project, I planned on doing the following states for my use i.e, idle, load, check,access_ok, access_fail and locked. I also created fail counter and a blink generator for the locked state, so if the password is wrong, the counter increments and after 3 wrong password, the system gets locked and causes Red LED to blink as a warning.

TestBench

`timescale 1ns/1ps

module digital_password_lock_tb;

    reg clk;
    reg rst_n;
    reg btn;
    reg [3:0] pw_switch;

    wire green_led;
    wire red_led;
    wire [2:0] state_out;

    // ASCII messages for GTKWave
    reg [8*40-1:0] message;
    reg [8*24-1:0] state_name;

    // ------------------------------------------------------------------------
    // DUT
    // ------------------------------------------------------------------------
    digital_password_lock #(
        .CLK_FREQ(1_000_000),
        .BAUD(100_000)
    ) dut (
        .clk(clk),
        .rst_n(rst_n),
        .btn(btn),
        .pw_switch(pw_switch),
        .green_led(green_led),
        .red_led(red_led),
        .state_out(state_out)
    );

    // ------------------------------------------------------------------------
    // Clock generation: 1 MHz clock
    // ------------------------------------------------------------------------
    always #500 clk = ~clk;

    // ------------------------------------------------------------------------
    // Decode state for GTKWave ASCII viewing
    // ------------------------------------------------------------------------
    always @(*) begin
        case (state_out)
            3'b000: state_name = "IDLE";
            3'b001: state_name = "LOAD";
            3'b010: state_name = "CHECK";
            3'b011: state_name = "ACCESS_GRANTED";
            3'b100: state_name = "ACCESS_DENIED";
            3'b101: state_name = "LOCKED";
            default: state_name ="UNKNOWN";
        endcase
    end

    // ------------------------------------------------------------------------
    // Button press task
    // ------------------------------------------------------------------------
    task press_button;
    begin
        btn = 1;
        #15000000;
        btn = 0;
    end
    endtask

    // ------------------------------------------------------------------------
    // Simulation
    // ------------------------------------------------------------------------
    initial begin
        $dumpfile("password_lock_tb.vcd");
        $dumpvars(0, digital_password_lock_tb);

        clk       = 0;
        rst_n     = 0;
        btn       = 0;
        pw_switch = 4'b0000;
        message   = "SYSTEM RESET";

        #3000;
        rst_n   = 1;
        message = "USER ENTERS PASSWORD";

        $display("\nUser enters password...");
        #3000000;

        // ------------------------------------------------------------
        // Attempt 1 Wrong password attempt
        // ------------------------------------------------------------

        pw_switch = 4'b0000;
        press_button();

        message = "CHECKING PASSWORD";
         $display("\nChecking Password..");

         #20000000;

         if(state_out == 3'b100) begin
            message = "WRONG PASSWORD";
                $display("\nAttempt 1: Wrong Password");
         end

        #50000000;

    // ------------------------------------------------------------
        // Attempt 2 Wrong password attempt
        // ------------------------------------------------------------
        message = "USER ENTERS PASSWORD AGAIN";
        $display("\nUser enters password again...");

        pw_switch = 4'b0011;
        press_button();
    #50000000;
        message = "CHECKING PASSWORD";
         $display("\nChecking Password..");

         #50000000;

          if(state_out == 3'b100) begin
            message = "WRONG PASSWORD";
                $display("\nAttempt 2: Wrong Password");
          end
        #4000000;

        // ------------------------------------------------------------
        // Attempt 3 Wrong password attempt to locked
        // ------------------------------------------------------------
        message = "USER ENTERS PASSWORD AGAIN";
        $display("\nUser enters password again...");

        pw_switch = 4'b0101;
        press_button();

        message = "CHECKING PASSWORD";
        $display("\nChecking Password..");

         #50000000;

         if(state_out == 3'b101)begin
            message = "SYSTEM LOCKED- RED LED BLINKING";
            $display("\nAttempt 3 : System is Locked, Red LED Blinking_-_-_-");
         end

        // observe blinking
        #100000000;

        // ------------------------------------------------------------
        // System Reset
        // ------------------------------------------------------------
        message = "SYSTEM RESET";
        $display("\nSystem Reseting...");

    rst_n = 0;
    #5000;
    rst_n= 1;

        #20000000;

        // ------------------------------------------------------------
        // Correct Password
        // ------------------------------------------------------------
        message = "USER ENTERS PASSWORD AGAIN";
        $display("\nUser enters password again...");

        #3000000;

        pw_switch = 4'b1011;
        press_button();

        message = "CHECKING PASSWORD";
         $display("\nChecking Password...");

        #20000000;

        if (state_out == 3'b011) begin
            message = "ACCESS GRANTED";
            $display("\n ACCESS GRANTED!");
            $display("\n WELCOME");
        end
        else begin
            message = "ERROR";
            $display("ERROR: Expected green LED");
        end

        #3000000;

        message = "SIMULATION DONE";
        $display("\nSimulation finished.\n");
        $finish;
    end

endmodule

Now onto my testbench verilog, I wanted my simulation output to show user interaction and also be able to view that in the waveform. So I created a task called press_button to mimic presssing the enter button. I also added state name and message, which are ASCII messages I can view in the waveform. For the finalized test bench, I created 4 test, 3 of which shows the wrong password attempt and in the 3rd state the system gets locked. After system resets, the 4th test shows the correct password output display.

Simulation & Waveform

For the simulation output, I was happy to see the output came just as I expected.

The output waveform also showed the whole simulation as expected. It start at the idle state, then user enters password, the register stores the entered password. system compares it to the correct password. If password is correct, it causes green led to glow and displays Access Granted. For wrong password, red led glows and display Access Denied.The user is given 3 attemps to put the correct password, if it exceeds 3 times, it causes system to get locked and eventually it returns to idle state.

Run Linter

The final thing I did was to run the linter for the code:

verilator --lint-only -Wall fatal digital_password_lock.v digital_password_lock_tb.v
I got the following warning which were mostly unused signals and paramater, which are low severity warnings.

I used the as most of the unused signals appear in the testbench, where they are declared for waveform observation and debugging purposes.

/* verilator lint_off */ 
My file digital_password_lock.v contains multiple modules, which gives the warning filename 'digital_password_lock' does not match MODULE name, 'password_core'. Since this was intentional I decided to use the verilator for this as well.

The withexpand warning was in the debounce file, I decided to ignore that as well becuase it is a library code.

I remove the baud rate becuse I dont have UART communication.

When I ran the verilator again, it did not have any warnings.