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:
- The 4-bit entered password from the user switches
- The 4-bit stored password defined in the comparator
- A debounced button input used to submit the password
- A 4-bit register that stores the entered password
- A password comparator that checks whether the entered password matches the stored password
- A Finite State Machine (FSM) that controls the sequence of system operations
- A fail counter that tracks incorrect password attempts
- 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:
-
A core module that implements the main logic, and
-
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
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
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

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 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 */
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.
