Lab 3: Keypad Scanner

Author

Christian Wu

Published

September 16, 2025

Introduction

The goal of this lab is to read a 4 x 4 matrix keypad and display the last two digits on a dual 7-segment display, with the most recent entry appearing on the right. Each key press should only register once, and additional key inputs when a button is held down should be ignored.

FPGA Design

Objective

The objective of this lab is to build upon the work done in Lab 2, by adding the addition of a keypad for inputs. This brings an additional layer of complexity, by making sure that we synchronize asynchronous outputs, eliminate switch bouncing, and making sure that buttons are only registered once.

Design

Synchronizing asynchronous inputs

Metastability occurs with this keypad when key inputs occur too close to a rising edge of the clock, causing asynchrony. To fix this, we can use a two stage synchronizer, which involves using two flip flops to synchronize our keypad input and have a stable input going into the FSM. This is done in this lab by synchronizing my column inputs, with the synchronizer module.

Accounting for switch bouncing

All mechanical switches have a phenomenom called switch bouncing, where the input signals by the switch oscillate quickly between on and off as the button is getting clicked. This occurs until the button is fully clicked and the input signal becomes stable. To account for this and not read mistake one key press for multiple key presses, I used SystemVerilog code to add a delay between reading inputs in my FSM. This will prevent a single key input being mistaken as multiple inputs.

Block Diagram

My approach to this lab consists of eight different modules. It was designed to have many modules to make testing the functionality of each function of this lab easily. An overarching top level module called lab3_cw, which will be programmed to the board, and seven submodules synchronizer, keypadFSM, keypad, sevenSegDigits, timeMultiplexer, sevenSegMux and sevenSegDisplay. The synchronizer submodule will synchronize my keypad inputs using a simple two stage synchronizer with two flip flops. The keypadFSM submodule is an FSM that controls how to detect keypad presses and what to do with it. It also implements switch debouncing. The keypad submodule helps decode what number corresponds to each row and column on the 4 x 4 keypad. The sevenSegDigits submodule is responsible for shifting the digits on the dual 7-segment display based on what the FSM tells it to do. The timeMultiplexer submodule is the same as in lab 2, and is responsible for controlling which common anode gets turned on in the dual 7-segment display. The sevenSegMux submodule is the same as in lab 2, and is a mux that controls which four bit input will be fed into sevenSegmentDisplay based on the enable, signal, which is given by timeMultiplexer. The sevenSegmentDisplay submodule is the same one that was used in Lab 1 and 2, which will control the outputs on the 7-segment display, displaying the hexadecimal value provided by the dip switches, s1[3:0] and s2[3:0]. It was designed to switch at 500 Hz, which should be high enough to not be noticeable to the human eye. It will also output a signal to guide which input should be used by the sevenSegmentDisplay module, which is controlled by sevenSegMux. The top level lab3_cw calls upon all these submodules as well as the HSOSC submodule, which takes advantage of the onboard 48 MHz clock on the FPGA. Below is the block diagram I used to create this project:

Lab 3 Block Diagram

synchronizer Design

The synchronizer submodule was a very simple design. It consisted of a two stage synchronizer by utilizing two flip flops to synchronize our asynchronous inputs.

Below is the code block that was used for this submodule:

// Christian Wu
// chrwu@g.hmc.edu
// 09/13/25

// This module is a simple two-stage synchronizer to safely bring an asynchronous input signal
// into the clock domain of the provided clock signal. This helps to prevent metastability issues.

module synchronizer (
    input logic clk,
    input logic reset,
    input logic [3:0] in,
    output logic [3:0] out);

    logic [3:0] mid;

    always_ff @(posedge clk) begin
        if (reset) begin
            mid <= 4'b0000;
            out <= 4'b0000;
        end else begin
        mid <= in;
        out <= mid;
    end
    end
endmodule

Keypad FSM Design

The FSM was designed to have 12 states. In order to detect key presses, I would rotate between scanning each row, one at a time until detecting a key input. Then it would move into the debounce state where it would have a 50 ms delay for debouncing, and then go to the hold state if the input is stable.

Some mechanical switches only bounce for 10 - 20 ms, so my debouncing method accounts for all of it and makes sure that keys are debounced. However, tradeoffs for this is that responses might be slow due to the long debounce time and the slow time between moving state to state, which goes at 1 kHz. But for the purposes of this lab, it seems pretty reasonable to go at 50 ms, since we cannot really exploit the long debounce times as it is not noticeable to the human eye and reaction speed. Another limitation is the need for an extra four states for debouncing. Right now, I am utilizing 4 bits of state, while only using 12 states, so I am wasting 4 states. If I did not have 4 bits of debouncing state, I could use just 3 bits of state and fully utilize it.

Here is my FSM State Transition Diagram:

FSM State Transition Diagram

Here is a state transition table for switching states:

Current State Condition Next State
S0 buttonPressed && oneButtonPressed S4
S0 !(buttonPressed && oneButtonPressed) S1
S1 buttonPressed && oneButtonPressed S6
S1 !(buttonPressed && oneButtonPressed) S2
S2 buttonPressed && oneButtonPressed S8
S2 !(buttonPressed && oneButtonPressed) S3
S3 buttonPressed && oneButtonPressed S10
S3 !(buttonPressed && oneButtonPressed) S0
S4 !buttonPressed || !oneButtonPressed S1
S4 counter >= 50 S5
S4 buttonPressed && oneButtonPressed && counter < 50 S4
S5 !originalStillPressed S1
S5 originalStillPressed S5
S6 !buttonPressed || !oneButtonPressed S2
S6 counter >= 50 S7
S6 buttonPressed && oneButtonPressed && counter < 50 S6
S7 !originalStillPressed S2
S7 originalStillPressed S7
S8 !buttonPressed || !oneButtonPressed S3
S8 counter >= 50 S9
S8 buttonPressed && oneButtonPressed && counter < 50 S8
S9 !originalStillPressed S3
S9 originalStillPressed S9
S10 !buttonPressed || !oneButtonPressed S0
S10 counter >= 50 S11
S10 buttonPressed && oneButtonPressed && counter < 50 S10
S11 !originalStillPressed S0
S11 originalStillPressed S11

Here is a state transition table for my outputs:

State Group row Output en Signal rc Output
S0, S4, S5 4’b0001 1 when S4→S5 {rowPressed, col}
S1, S6, S7 4’b0010 1 when S6→S7 {rowPressed, col}
S2, S8, S9 4’b0100 1 when S8→S9 {rowPressed, col}
S3, S10, S11 4’b1000 1 when S10→S11 {rowPressed, col}

Here is the code for this submodule:

// Christian Wu
// chrwu@g.hmc.edu
// 09/15/25

// This module implements a finite state machine (FSM) to scan a 4x4 keypad.
// It drives the rows and reads the columns to detect key presses, with
// debouncing and single key press detection. The FSM operates at 1kHz,
// with a debounce period of 50ms. The design ensures that only one key
// press is registered at a time, and it waits for the key to be released
// before allowing another key press to be detected.

module keypadFSM (
    input  logic clk,
    input  logic reset,
    input  logic [3:0] col,
    output logic [3:0] row,
    output logic [7:0] rc,
    output logic en);

    typedef enum logic [3:0] {
        S0,       // Row0 scan
        S1,       // Row1 scan
        S2,       // Row2 scan
        S3,       // Row3 scan
        S4,       // Row0 debounce
        S5,       // Row0 hold
        S6,       // Row1 debounce
        S7,       // Row1 hold
        S8,       // Row2 debounce
        S9,       // Row2 hold
        S10,      // Row3 debounce
        S11       // Row3 hold
    } state_t;

    state_t state, nextState;
    logic [3:0] activeCol;
    logic buttonPressed, oneButtonPressed;
    logic [3:0] rowPressed;
    logic [3:0] originalButton; 
    logic originalStillPressed; 
    logic [7:0] counter;
    logic [18:0] fsm_counter;
    logic fsm_tick;

    parameter FSM_TICK_COUNT = 19'd48_000;    
    parameter DEBOUNCE_COUNT = 8'd50;        

    // 1kHz tick generator for FSM state transitions
    always_ff @(posedge clk) begin
        if (reset) begin
            fsm_counter <= 0;
            fsm_tick <= 0;
        end else begin
            if (fsm_counter >= FSM_TICK_COUNT - 1) begin
                fsm_counter <= 0;
                fsm_tick <= 1;
            end else begin
                fsm_counter <= fsm_counter + 1;
                fsm_tick <= 0;
            end
        end
    end

    always_ff @(posedge clk) begin
        if (reset) begin
            state <= S0;
            counter <= 0;
            originalButton <= 4'b0000;
        end else if (fsm_tick) begin
            state <= nextState;
            
            // Store original button when entering debounce states
            if ((state inside {S0,S1,S2,S3}) && (nextState inside {S4,S6,S8,S10}) && oneButtonPressed) begin
                originalButton <= activeCol;
            end
            
            // Clear original button when returning to scanning
            if (nextState inside {S0,S1,S2,S3}) begin
                originalButton <= 4'b0000;
            end
            
            // Debounce counter (only in debounce states)
            if (state inside {S4,S6,S8,S10}) begin
                if (buttonPressed && counter < DEBOUNCE_COUNT)
                    counter <= counter + 1;
                else if (!buttonPressed)
                    counter <= 0;
            end else begin
                counter <= 0;
            end
        end
    end

    always_comb begin
        case (state)
            S0, S4, S5:     row = 4'b0001; // Row0 active
            S1, S6, S7:     row = 4'b0010; // Row1 active
            S2, S8, S9:     row = 4'b0100; // Row2 active
            S3, S10, S11:   row = 4'b1000; // Row3 active
            default:        row = 4'b0001; // Default to Row0
        endcase
    end

    assign activeCol = col & {4{row[0] | row[1] | row[2] | row[3]}};

    assign buttonPressed    = |activeCol;
    assign oneButtonPressed = (activeCol != 0) &&
                              ((activeCol & (activeCol - 1)) == 0);
    
    // Check if the original button is still pressed
    assign originalStillPressed = (originalButton != 4'b0000) && 
                                  ((activeCol & originalButton) == originalButton);

    assign rowPressed[0] = (state inside {S0, S4, S5});
    assign rowPressed[1] = (state inside {S1, S6, S7});
    assign rowPressed[2] = (state inside {S2, S8, S9});
    assign rowPressed[3] = (state inside {S3, S10, S11});

    assign en = fsm_tick && (
                (nextState == S5 && state == S4) ||
                (nextState == S7 && state == S6) ||
                (nextState == S9 && state == S8) ||
                (nextState == S11 && state == S10)
              );

    always_comb begin
        case (state)
            S0:  nextState = buttonPressed && oneButtonPressed ? S4 : S1;
            S1:  nextState = buttonPressed && oneButtonPressed ? S6 : S2;
            S2:  nextState = buttonPressed && oneButtonPressed ? S8 : S3;
            S3:  nextState = buttonPressed && oneButtonPressed ? S10 : S0;

            S4:  nextState = (!buttonPressed || !oneButtonPressed) ? S1 :
                            (counter >= DEBOUNCE_COUNT ? S5 : S4);
            S5:  nextState = !originalStillPressed ? S1 : S5; 

            S6:  nextState = (!buttonPressed || !oneButtonPressed) ? S2 :
                            (counter >= DEBOUNCE_COUNT ? S7 : S6);
            S7:  nextState = !originalStillPressed ? S2 : S7; 

            S8:  nextState = (!buttonPressed || !oneButtonPressed) ? S3 :
                            (counter >= DEBOUNCE_COUNT ? S9 : S8);
            S9:  nextState = !originalStillPressed ? S3 : S9; 

            S10: nextState = (!buttonPressed || !oneButtonPressed) ? S0 :
                            (counter >= DEBOUNCE_COUNT ? S11 : S10);
            S11: nextState = !originalStillPressed ? S0 : S11;

            default: nextState = S0;
        endcase
    end

    assign rc = {rowPressed, col};
endmodule

Keypad Design

This submodule decodes a row and column input into a 4 bit number for all our hexadecimal digits. This was designed for an active high inputs from our keypad.

Below is the code block that was used for this submodule:

// Christian Wu
// chrwu@g.hmc.edu
// 09/13/25

// This module takes in a combination of row and column inputs from the keypad,
// and outputs a 4-bit binary representation of the key pressed. If no key is pressed,
// the output is 4'b1111.

module keypad (
    input  logic [3:0] row,
    input  logic [3:0] col,
    output logic [3:0] key
);

    always_comb begin
        case ({row, col})
            8'b0001_0010: key = 4'b0000; // 0
            8'b1000_0001: key = 4'b0001; // 1
            8'b1000_0010: key = 4'b0010; // 2
            8'b1000_0100: key = 4'b0011; // 3
            8'b0100_0001: key = 4'b0100; // 4
            8'b0100_0010: key = 4'b0101; // 5
            8'b0100_0100: key = 4'b0110; // 6
            8'b0010_0001: key = 4'b0111; // 7
            8'b0010_0010: key = 4'b1000; // 8
            8'b0010_0100: key = 4'b1001; // 9
            8'b0001_0001: key = 4'b1010; // A
            8'b0001_0100: key = 4'b1011; // B
            8'b1000_1000: key = 4'b1100; // C
            8'b0100_1000: key = 4'b1101; // D
            8'b0010_1000: key = 4'b1110; // E
            8'b0001_1000: key = 4'b1111; // F
            default:      key = 4'b1111; // No key pressed
        endcase
    end
endmodule

Seven Segment Digits Design

This submodule stores the digits displayed on our dual 7-segment display and will add new numbers on the right and shift the old values to the left when instructed by the keypad FSM.

Below is the code block that was used for this submodule:

// Christian Wu
// chrwu@g.hmc.edu 
// 09/13/25

// This module stores the last two hexadecimal digits pressed on the keypad, and outputs them to be displayed
// on a dual seven segment display. When a new key is pressed, the previous key is shifted to the left digit,
// and the new key is displayed on the right digit.

module sevenSegDigits (
    input  logic clk,
    input  logic reset,
    input  logic en,
    input  logic [3:0] key,
    output logic [3:0] s1,
    output logic [3:0] s2);

    logic en_prev;
    logic en_edge;
    
    // Detect rising edge of enable signal
    always_ff @(posedge clk or posedge reset) begin
        if (reset) begin
            en_prev <= 1'b0;
        end else begin
            en_prev <= en;
        end
    end
    
    assign en_edge = en & ~en_prev;
    
    // Update digits on enable edge
    always_ff @(posedge clk) begin
        if (reset) begin
            s1 <= 4'b0000; // Reset left digit to 0
            s2 <= 4'b0000; // Reset right digit to 0
        end else if (en_edge) begin
            // When a new key is detected, shift digits
            s1 <= s2;   // Shift the previous right digit to the left
            s2 <= key;  // Update the right digit with the new key
        end
    end

endmodule

Time Multiplexer Design

This submodule is taken from Lab 2. The main purpose of the timeMultiplexer submodule is to switch between each common anode on our dual 7-segment display, so that we only need one sevenSegmentDisplay submodule to control both digits. Thus, we are turning on one side of the dual 7-segment display on at a time, but if we do it fast enough, the human eye cannot notice each digit turning on and off. To switch between the two common anodes fast enough, I chose to switch between each display at 500 Hz. This is done by using the FPGA’s onboard 48 MHz clock, and using a counter. 500 Hz is equivalent to 0.002 seconds per cycle, and assuming a 50% duty cycle, we must switch from one common anode to the other every 0.001 seconds. For a 48 MHz clock, this would equate to 48000 ticks. Thus, we can use a counter to turn each side of the dual 7-segment display on or off, every 48000 ticks. At the same time, we can also output, signal, to tell the sevenSegMux submodule, which input to display on the corresponding side of the dual 7-segment display

Below is the code for the timeMultiplexer submodule:

//  timeMultiplexer.sv
//  Christian Wu
//  chrwu@g.hmc.edu
//  09/06/25

// This module takes in two four bit inputs, s1 and s2, and switches between them to drive a dual seven
// segment display, to utilize only one sevenSegmentDisplay module. The switching is done at a rate fast enough
// such that the human eye cannot detect the switching, and it appears that both displays are on at the same time.

module timeMultiplexer (
    input clk,
    output logic an1, an2,
    output logic signal);

    logic [24:0] counter = 0;

    always_ff @(posedge clk) begin
        counter <= counter + 1;
        if (counter == 48000) begin
            counter <= 0;
            signal <= ~signal; 
            if (~signal) begin
                an1 <= 1; // turn off an1
                an2 <= 0; // turn on an2
            end else begin
                an1 <= 0; // turn on an1
                an2 <= 1; // turn off an2
            end

    end
    end
endmodule

Seven Segment Mux Design

The design for the sevenSegMux submodule is quite simple. This submodule depicts a simple mux with an enable on it. This submodule is used to output the correct 4 bit input into the sevenSegmentDisplay submodule. When the enable is on, we set the ouput to be s2, and when it is off, we set the output to be s1.

Below is the code for the sevenSegMux submodule:

// Christian Wu
// chrwu@g.hmc.edu
// 09/08/25

// This module takes in two 4-bit inputs, s1 and s2, and an enable, and outputs one 
// of the inputs to a 4-bit output based on the enable signal for the seven-segment display

module sevenSegMux (
    input logic [3:0] s1, s2,
    input logic enable,
    output logic [3:0] out);

    always_comb begin
        if (enable) begin
            out = s2;
        end else begin
            out = s1;
        end
    end
endmodule

Seven Segment Display Design

This submodule is copied from Lab 1 and uses the exact same code. Designing the sevenSegmentDisplay submodule was fairly simple. It involved using a case statement to write out the outputs, seg[6:0] of the 7-segment display for all 16 possible inputs of s[3:0]. While writing the code, it is worth noting that turning on each segment of the 7-segment display was with a 0 output as the cathode is connected to each output pin.

Below is a labeled image of each segment of the 7-segment display and the code for this submodule, where s[0] is segment A and s[6] is segment G of the 7-segment display:

7-segment display segment mapping
// sevenSegmentDisplay.sv
// Christian Wu
// chrwu@g.hmc.edu
// 09/06/25

// This module takes in a 4-bit input 's', using the dip switches on the motherboard,
// and controls the seven-segment display output seg[6:0].
module lab1_sevenSegmentDisplay (
    input logic [3:0] s,
    output logic [6:0] seg);
 // 7-Segment Display Logic
    always_comb begin
        case (s)
            4'h0: seg = 7'b1000000; // 0
            4'h1: seg = 7'b1111001; // 1
            4'h2: seg = 7'b0100100; // 2
            4'h3: seg = 7'b0110000; // 3
            4'h4: seg = 7'b0011001; // 4
            4'h5: seg = 7'b0010010; // 5
            4'h6: seg = 7'b0000010; // 6
            4'h7: seg = 7'b1111000; // 7
            4'h8: seg = 7'b0000000; // 8
            4'h9: seg = 7'b0010000; // 9
            4'hA: seg = 7'b0001000; // A
            4'hB: seg = 7'b0000011; // b
            4'hC: seg = 7'b1000110; // C
            4'hD: seg = 7'b0100001; // d
            4'hE: seg = 7'b0000110; // E
            4'hF: seg = 7'b0001110; // F
            default: seg = 7'b1111111; // Off
        endcase
    end
endmodule

Top Level Module

Given that this lab project was split into one top level module and seven submodules, the top level module is quite simple. It is a module that calls upon the seven submodules synchronizer, keypadFSM, keypad, sevenSegDigits, timeMultiplexer, sevenSegMux and sevenSegDisplay. It also calls upon the HSOSC module to take advantage of the onboard 48 MHz clock on the FPGA. This top level module, lab2_cw, will then be programmed onto the FPGA.

Below is the code for the top level module, lab3_cw:

// Christian Wu
// chrwu@g.hmc.edu
// 09/13/25

// This is the top level module for Lab 3. This module will display the last two hexadecimal digits pressed
// on the seven segment display. Simplified to use single clock with proper timing.

module lab3_cw (
    input logic resetInv, 
    input logic [3:0] col,
    output logic [3:0] row,
    output logic [6:0] seg,
    output logic an1, an2);

    logic clk;         
    logic reset;
    logic [7:0] rc;
    logic [3:0] col_sync;
    logic [3:0] key;
    logic en;
    logic [3:0] s1, s2;
    logic signal;
    logic [3:0] current_seg;       
    
    assign reset = ~resetInv;

    HSOSC hf_osc (.CLKHFPU(1'b1), .CLKHFEN(1'b1), .CLKHF(clk));
    synchronizer sync (.clk(clk), .reset(reset), .in(col), .out(col_sync));
    keypadFSM fsm (.clk(clk), .reset(reset), .col(col_sync), .row(row), .rc(rc), .en(en));
    keypad keypad_decode (.row(rc[7:4]), .col(rc[3:0]), .key(key));
    sevenSegDigits digits (.clk(clk), .reset(reset), .en(en), .key(key), .s1(s1), .s2(s2));
    timeMultiplexer timeMux (.clk(clk), .reset(reset), .an1(an1), .an2(an2), .signal(signal));
    sevenSegMux segMux (.s1(s1), .s2(s2), .enable(signal), .out(current_seg));
    sevenSegmentDisplay segDisplay (.s(current_seg), .seg(seg));
endmodule

Testbench Simulation and Testing

Testbench Design and Results

To test my code and see whether it would work as expected, I created a testbench to test before moving on to using physical hardware. I created a testbench for each of my eight modules, testing a different functionality of this lab with east testbench. For the synchronizer submodule, I tested the ability to synchronize and provide a stable output from a asynchronous input. For the keypadFSM submodule, I tested the state transitions and ensured that based on the right inputs, the right outputs would be given. For the keypad submodule, I tested the decoding from a row and column input to a 4 bit number. For the sevenSegDigits submodule, I tested the functionality of switching numbers on the display, based on the inputs given. For the timeMultiplexer submodule, I tested the sequential logic that occurs in this submodule to check that the submodule switches between turning on each common anode on the dual 7-segment display, and correctly outputs the right signal for the sevenSegMux to use. For the sevenSegMux submodule, I tested that the mux would output the right 4 bit value from the two 4 bit inputs, based on whether the enable was on or off. For the sevenSegmentDisplay submodule, I tested all 16 possible 4 bit inputs and made sure that the 7 bit output was correct. For the top level lab3_cw module, it was used to test that the HSOSC module worked, by sampling a few different test cases and making sure that the dual 7-segment display would show both of the values by switching between outputting each one.

Each of the testbenches were done by using the stim/assert method, to ensure functionality automatically, without having to analyze the waveforms carefully to see if it worked or not.

synchronizer_testbench results:

This testbench tested the ability to synchronize an asynchronous input and provide a stable output. Below is the code used for my testbench:

// Christian Wu
// chrwu@g.hmc.edu
// 09/17/25

// testbench for synchronizer 

`timescale 1ns/1ps

module synchronizer_testbench;

    logic clk;
    logic reset;
    logic [3:0] in;
    logic [3:0] out;

    // DUT instantiation
    synchronizer dut (
        .clk   (clk),
        .reset (reset),
        .in    (in),
        .out   (out)
    );

    // Clock generation
    initial clk = 0;
    always #5 clk = ~clk;

    initial begin
        // Initialize
        reset = 1;
        in = 4'b0000;
        #12;
        reset = 0;

        // --- Step 1 ---
        @(posedge clk); in = 4'b1010;
        @(posedge clk);        
        @(posedge clk); 
        @(posedge clk);  
        assert(out === 4'b1010) else $error("Step 1 failed: out=%b", out);

        // --- Step 2 ---
        @(posedge clk); in = 4'b1111;
        @(posedge clk);
        @(posedge clk);
        @(posedge clk);
        assert(out === 4'b1111) else $error("Step 2 failed: out=%b", out);

        // --- Step 3 ---
        @(posedge clk); in = 4'b0101;
        @(posedge clk);
        @(posedge clk);
        @(posedge clk);
        assert(out === 4'b0101) else $error("Step 3 failed: out=%b", out);

        $display("All steps completed successfully");
        $finish;
    end

endmodule

Below are the Questa simulation results:

synchronizer Waveforms

synchronizer Transcript
keypadFSM_testbench results:

This testbench tested the enable and output logic of my FSM, making sure that state transitions worked adequately and gave the right results. Below if the code used for my testbench:

// Christian Wu
// chrwu@g.hmc.edu
// 09/17/25

// Testbench for keypadFSM module

`timescale 1ns/1ps

module keypadFSM_testbench();

    // Signals
    logic clk;
    logic reset;
    logic [3:0] col;
    logic [3:0] row;
    logic [7:0] rc;
    logic en;

    // Instantiate DUT
    keypadFSM dut (
        .clk(clk),
        .reset(reset),
        .col(col),
        .row(row),
        .rc(rc),
        .en(en)
    );

    // Clock: 50 MHz
    initial clk = 0;
    always #10 clk = ~clk;

    integer i;
    logic en_seen;
    logic [3:0] expected_col;

    initial begin
        reset = 1;
        col = 4'b0000;
        repeat (5) @(posedge clk);
        reset = 0;

        // Wait a few FSM ticks
        for (i = 0; i < 2*48_000; i = i + 1) @(posedge clk);

        // --- Test Case 1: Row0, Col0 ---
        col = 4'b0001;
        expected_col = 4'b0001;
        en_seen = 0;

        // Hold key long enough (> debounce)
        for (i = 0; i < 150*48_000; i = i + 1) begin
            @(posedge clk);
            if (en) begin
                en_seen = 1;
                // Check only column bits
                if (rc[3:0] !== expected_col)
                    $error("Error: column mismatch in Test Case 1! Expected %b, got %b", expected_col, rc[3:0]);
                // Check row is one-hot
                if (^rc[7:4] !== 1'b1)
                    $error("Error: row not one-hot in Test Case 1! rc[7:4]=%b", rc[7:4]);
            end
        end
        col = 4'b0000;
        if (!en_seen) $error("Error: en not asserted in Test Case 1");

        // --- Test Case 2: Row1, Col2 ---
        col = 4'b0100;
        expected_col = 4'b0100;
        en_seen = 0;

        for (i = 0; i < 150*48_000; i = i + 1) begin
            @(posedge clk);
            if (en) begin
                en_seen = 1;
                if (rc[3:0] !== expected_col)
                    $error("Error: column mismatch in Test Case 2! Expected %b, got %b", expected_col, rc[3:0]);
                if (^rc[7:4] !== 1'b1)
                    $error("Error: row not one-hot in Test Case 2! rc[7:4]=%b", rc[7:4]);
            end
        end
        col = 4'b0000;
        if (!en_seen) $error("Error: en not asserted in Test Case 2");

        // --- Test Case 3: Multiple keys (ignored) ---
        col = 4'b0011;
        en_seen = 0;

        for (i = 0; i < 50*48_000; i = i + 1) begin
            @(posedge clk);
            if (en) en_seen = 1;
        end
        if (en_seen)
            $error("Error: en asserted with multiple keys in Test Case 3");
        col = 4'b0000;

        $display("All tests completed successfully.");
        $stop;
    end

endmodule

Below are the Questa simulation results:

keypadFSM Waveforms

keypadFSM Transcript
keypad_testbench results:

This testbench tested the decoding of a row and column on a keypad into a 4 bit number. Below if the code used for my testbench:

// Christian Wu
// chrwu@g.hmc.edu
// 09/13/25

// This module is a testbench for the keypad decoder module. It tests all possible key presses
// and verifies that the correct key code is output. It also includes edge case tests to ensure
// robustness of the keypad decoder.

`timescale 1ns/1ps

module keypad_testbench();
    
    // Testbench signals
    logic [3:0] row;
    logic [3:0] col;
    logic [3:0] key;
    
    // Instantiate the keypad decoder
    keypad dut (
        .row(row),
        .col(col),
        .key(key)
    );
    
    // Main stimulus and test sequence
    initial begin
        
        // Test 1: Key '1' - Row 0 (0001), Col 0 (0001) -> 0001_0001
        row = 4'b1000; col = 4'b0001; #10;
        assert(key == 4'b0001) else $error("Key '1' failed: expected 4'b0001, got %b", key);
        
        // Test 2: Key '2' - Row 0 (1000), Col 1 (0010) -> 1000_0010
        row = 4'b1000; col = 4'b0010; #10;
        assert(key == 4'b0010) else $error("Key '2' failed: expected 4'b0010, got %b", key);
        
        // Test 3: Key '3' - Row 0 (1000), Col 2 (0100) -> 1000_0100
        row = 4'b1000; col = 4'b0100; #10;
        assert(key == 4'b0011) else $error("Key '3' failed: expected 4'b0011, got %b", key);
        
        // Test 4: Key 'C' - Row 0 (1000), Col 3 (1000) -> 1000_1000
        row = 4'b1000; col = 4'b1000; #10;
        assert(key == 4'b1100) else $error("Key 'C' failed: expected 4'b1100, got %b", key);
        
        // Test 5: Key '4' - Row 1 (0100), Col 0 (0001) -> 0100_0001
        row = 4'b0100; col = 4'b0001; #10;
        assert(key == 4'b0100) else $error("Key '4' failed: expected 4'b0100, got %b", key);
        
        // Test 6: Key '5' - Row 1 (0100), Col 1 (0010) -> 0100_0010
        row = 4'b0100; col = 4'b0010; #10;
        assert(key == 4'b0101) else $error("Key '5' failed: expected 4'b0101, got %b", key);
        
        // Test 7: Key '6' - Row 1 (0100), Col 2 (0100) -> 0100_0100
        row = 4'b0100; col = 4'b0100; #10;
        assert(key == 4'b0110) else $error("Key '6' failed: expected 4'b0110, got %b", key);
        
        // Test 8: Key 'D' - Row 1 (0100), Col 3 (1000) -> 0100_1000
        row = 4'b0100; col = 4'b1000; #10;
        assert(key == 4'b1101) else $error("Key 'D' failed: expected 4'b1101, got %b", key);
        
        // Test 9: Key '7' - Row 2 (0010), Col 0 (0001) -> 0010_0001
        row = 4'b0010; col = 4'b0001; #10;
        assert(key == 4'b0111) else $error("Key '7' failed: expected 4'b0111, got %b", key);
        
        // Test 10: Key '8' - Row 2 (0010), Col 1 (0010) -> 0010_0010
        row = 4'b0010; col = 4'b0010; #10;
        assert(key == 4'b1000) else $error("Key '8' failed: expected 4'b1000, got %b", key);
        
        // Test 11: Key '9' - Row 2 (0010), Col 2 (0100) -> 0010_0100
        row = 4'b0010; col = 4'b0100; #10;
        assert(key == 4'b1001) else $error("Key '9' failed: expected 4'b1001, got %b", key);
        
        // Test 12: Key 'E' - Row 2 (0010), Col 3 (1000) -> 0010_1000
        row = 4'b0010; col = 4'b1000; #10;
        assert(key == 4'b1110) else $error("Key 'E' failed: expected 4'b1110, got %b", key);
        
        // Test 13: Key 'A' - Row 3 (0001), Col 0 (0001) -> 0001_0001
        row = 4'b0001; col = 4'b0001; #10;
        assert(key == 4'b1010) else $error("Key 'A' failed: expected 4'b1010, got %b", key);
        
        // Test 14: Key '0' - Row 3 (0001), Col 1 (0010) -> 0001_0010
        row = 4'b0001; col = 4'b0010; #10;
        assert(key == 4'b0000) else $error("Key '0' failed: expected 4'b0000, got %b", key);
        
        // Test 15: Key 'B' - Row 3 (0001), Col 2 (0100) -> 0001_0100
        row = 4'b0001; col = 4'b0100; #10;
        assert(key == 4'b1011) else $error("Key 'B' failed: expected 4'b1011, got %b", key);
        
        // Test 16: Key 'F' - Row 3 (0001), Col 3 (1000) -> 0001_1000
        row = 4'b0001; col = 4'b1000; #10;
        assert(key == 4'b1111) else $error("Key 'F' failed: expected 4'b1111, got %b", key);
        
        // Edge Case Tests
        // Test 17: No key pressed
        row = 4'b0000; col = 4'b0000; #10;
        assert(key == 4'b1111) else $error("No key pressed failed: expected 4'b1111, got %b", key);
        
        // Test 18: Multiple rows active (invalid)
        row = 4'b0011; col = 4'b0001; #10;
        assert(key == 4'b1111) else $error("Multiple rows test failed: expected 4'b1111, got %b", key);
        
        // Test 19: Multiple columns active (invalid)
        row = 4'b0001; col = 4'b0011; #10;
        assert(key == 4'b1111) else $error("Multiple columns test failed: expected 4'b1111, got %b", key);
        
        // Test 20: Both multiple rows and columns (invalid)
        row = 4'b0011; col = 4'b0011; #10;
        assert(key == 4'b1111) else $error("Multiple rows and columns failed: expected 4'b1111, got %b", key);
        
        // Test 21: All ones (invalid)
        row = 4'b1111; col = 4'b1111; #10;
        assert(key == 4'b1111) else $error("All ones test failed: expected 4'b1111, got %b", key);

        // Test 22: Only one row high, no column high
        row = 4'b0001; col = 4'b0000; #10;
        assert(key == 4'b1111) else $error("Row high only failed: expected 4'b1111, got %b", key);
        
        // Test 23: Only one column high, no row high  
        row = 4'b0000; col = 4'b0001; #10;
        assert(key == 4'b1111) else $error("Column high only failed: expected 4'b1111, got %b", key);
        
        $display("\nAll tests successfully completed.");
        $stop;
    end
endmodule

Below are the Questa simulation results:

keypad Waveforms

keypad Transcript
sevenSegDigits_testbench results:

This testbench tested the proper display of numbers on the dual 7-segment display, ensuring the correct shifting of numbers. Below if the code used for my testbench:

// Christian Wu
// chrwu@g.hmc.edu
// 09/15/25

// This module is a testbench for the sevenSegDigits module. It tests the digit storage
// and shifting functionality with proper timing for synchronous reset and enable signals.

`timescale 1ns / 1ps

module sevenSegDigits_testbench();
    
    logic clk;
    logic reset;
    logic en;
    logic [3:0] key;
    logic [3:0] s1, s2;
    
    int test_count = 0;
    logic [3:0] s1_before, s2_before; // keep these at module scope
    
    initial clk = 0;
    always #10 clk = ~clk;
    
    sevenSegDigits dut (
        .clk(clk),
        .reset(reset),
        .en(en),
        .key(key),
        .s1(s1),
        .s2(s2)
    );
    
    initial begin
        
        reset = 1;
        en = 0;
        key = 4'h0;
        
        @(posedge clk);
        @(posedge clk); 
        
        // Test 1: Reset behavior
        test_count++;
        assert (s1 == 4'h0 && s2 == 4'h0) else
            $error("Test %0d FAILED: Reset should set both digits to 0. Got s1=%h, s2=%h", test_count, s1, s2);
        
        // Release reset and wait a posedge for stable operation
        reset = 0;
        @(posedge clk);
        
        // Test 2: First key press
        test_count++;
        key = 4'h5; en = 1;
        @(posedge clk); en = 0;      
        @(posedge clk);             
        assert (s1 == 4'h0 && s2 == 4'h5) else
            $error("Test %0d FAILED: First key should be s1=0, s2=5. Got s1=%h, s2=%h", test_count, s1, s2);
        
        // Test 3: Second key press
        test_count++;
        key = 4'hA; en = 1;
        @(posedge clk); en = 0;
        @(posedge clk);
        assert (s1 == 4'h5 && s2 == 4'hA) else
            $error("Test %0d FAILED: Second key should be s1=5, s2=A. Got s1=%h, s2=%h", test_count, s1, s2);
        
        // Test 4: Third key press
        test_count++;
        key = 4'hF; en = 1;
        @(posedge clk); en = 0;
        @(posedge clk);
        assert (s1 == 4'hA && s2 == 4'hF) else
            $error("Test %0d FAILED: Third key should be s1=A, s2=F. Got s1=%h, s2=%h", test_count, s1, s2);
        
        // Test 5: Enable held high (keep key stable while en is high)
        test_count++;
        key = 4'h7;
        en = 1;
        @(posedge clk);
        @(posedge clk); 
        @(posedge clk); 
        en = 0;
        @(posedge clk);
        assert (s1 == 4'hF && s2 == 4'h7) else
            $error("Test %0d FAILED: Enable high should only update once. Got s1=%h, s2=%h", test_count, s1, s2);
        
        // Test 6: No enable (change key while en==0)
        test_count++;
        s1_before = s1;
        s2_before = s2;
        key = 4'h9;   
        @(posedge clk);    
        @(posedge clk);
        assert (s1 == s1_before && s2 == s2_before) else
            $error("Test %0d FAILED: No enable should not update. Got s1=%h, s2=%h, expected s1=%h, s2=%h", 
                   test_count, s1, s2, s1_before, s2_before);
        
        // Test 7: All hex digits
        test_count++;
        for (int i = 0; i < 16; i++) begin
            key = i[3:0]; en = 1;
            @(posedge clk); en = 0; @(posedge clk); 
            assert (s2 == i[3:0]) else
                $error("Hex test FAILED: Key %h not stored correctly. Got s2=%h", i[3:0], s2);
        end
        
        $display("All %0d tests completed successfully", test_count);
        $stop;
    end
    
    // Continuous assertions - only check when not in reset and outputs are not X
    always @(posedge clk) begin
        if (!reset && s1 !== 4'bxxxx && s2 !== 4'bxxxx) begin
            assert (s1 <= 4'hF && s2 <= 4'hF) else
                $error("ASSERTION: Outputs should be valid hex (0-F). Got s1=%h, s2=%h", s1, s2);
        end
    end
    
    // Safety timeout
    initial begin
        #50000; // 50us timeout
        $display("Testbench timeout");
        $finish;
    end
    
endmodule

Below are the Questa simulation results:

sevenSegDigits Waveforms

sevenSegDigits Transcript
timeMultiplexer_testbench results:

This testbench tested the sequential logic of switching between turning on each common anode, and outputting the right signal for the sevenSegmentMux submodule. Below is the code for my testbench:

// Christian Wu
// chrwu@g.hmc.edu
// 09/08/25

// This module tests the timeMultiplexer module by simulating clock cycles and checking if the an1, an2, and signal outputs
// toggle correctly based on the internal counter.

// timeMultiplexer_simple_tb.sv
// Simple testbench focusing only on an1 and an2 switching behavior

`timescale 1ns / 1ps

module timeMultiplexer_testbench();
    logic clk;
    logic an1, an2;
    logic signal;
    
    timeMultiplexer dut (.clk(clk), .an1(an1), .an2(an2), .signal(signal));
    
    initial begin
        clk = 0;
        forever #5 clk = ~clk; 
    end
    
    // Test sequence
    initial begin
        repeat(10) begin
            @(posedge signal or negedge signal);
            #1;
        end
        
        $display("Test completed successfully.");
        $stop;
    end
    
    // Assert statements for anode behavior
    always @(*) begin
        // Assert anodes are always opposite (mutually exclusive)
        assert (an1 !== an2)
        else $error("Anodes not mutually exclusive: an1=%b, an2=%b", an1, an2);
        
        // Assert correct anode control based on signal
        assert (signal ? (an1 == 1 && an2 == 0) : (an1 == 0 && an2 == 1))
        else $error("Incorrect anode control: signal=%b, an1=%b, an2=%b", signal, an1, an2);
        
        // Assert only one display is active at a time (one anode is 0)
        assert ((an1 == 0) ^ (an2 == 0))
        else $error("Neither or both displays active: an1=%b, an2=%b", an1, an2);
    end
    
endmodule

I also used an oscilloscope to confirm that the switching between the two sides of the dual 7-segment display is at 500 Hz:

Oscilloscope trace switching displays at 500 Hz

Below are the Questa simulation results:

timeMultiplexer Waveforms

timeMultiplexer Transcript
sevenSegMux_testbench results:

This testbench tested that the mux worked as expected, testing all 512 cases. Below is the code for my testbench:

// Christian Wu
// chrwu@g.hmc.edu
// 09/08/25

// This module tests the sevenSegMux module by inputing all combinations of s1, s2, and enable signals
// and checking if the output is as expected.

`timescale 1ns/1ps

module sevenSegMux_testbench();
    logic [3:0] s1, s2;
    logic enable;
    logic [3:0] out;
    sevenSegMux dut (.s1(s1), .s2(s2), .enable(enable), .out(out));

    integer i, j;
    integer errors = 0;
    integer test_cases = 0;
    logic [3:0] expected_out;

    initial begin
        // Test all combinations of 4-bit inputs and enable signal
        for (i = 0; i < 16; i++) begin
            for (j = 0; j < 16; j++) begin
                s1 = i[3:0];
                s2 = j[3:0];

                // Test with enable = 0
                enable = 0;
                #10; 
                test_cases++;
                expected_out = s1;
                assert (out === expected_out)
                    else $error("ASSERTION FAILED: s1=%0d, s2=%0d, enable=0, expected out=%0d, got out=%0d", s1, s2, expected_out, out);

                // Test with enable = 1
                enable = 1;
                #10; 
                test_cases++;
                expected_out = s2;
                assert (out === expected_out)
                    else $error("ASSERTION FAILED: s1=%0d, s2=%0d, enable=1, expected out=%0d, got out=%0d", s1, s2, expected_out, out);
            end
        end

        $display("All %0d test cases successfully completed.", test_cases);
        $stop;
    end
    endmodule

Below are the Questa simulation results:

sevenSegMux Waveforms

sevenSegMux Transcript
sevenSegmentDisplay_testbench results:

This testbench tested the combinational logic of the sevenSegmentDisplay submodule, testing all 16 cases. Below is the code used for my testbench:

// sevenSegmentDisplay.sv
// Christian Wu
// chrwu@g.hmc.edu
// 09/06/25

// This module takes in a 4-bit input 's', using the dip switches on the motherboard,
// and controls the seven-segment display output seg[6:0]. This module is copy and pasted from lab 1
module sevenSegmentDisplay (
    input logic [3:0] s,
    output logic [6:0] seg);
 // 7-Segment Display Logic
    always_comb begin
        case (s)
            4'h0: seg = 7'b1000000; // 0
            4'h1: seg = 7'b1111001; // 1
            4'h2: seg = 7'b0100100; // 2
            4'h3: seg = 7'b0110000; // 3
            4'h4: seg = 7'b0011001; // 4
            4'h5: seg = 7'b0010010; // 5
            4'h6: seg = 7'b0000010; // 6
            4'h7: seg = 7'b1111000; // 7
            4'h8: seg = 7'b0000000; // 8
            4'h9: seg = 7'b0010000; // 9
            4'hA: seg = 7'b0001000; // A
            4'hB: seg = 7'b0000011; // b
            4'hC: seg = 7'b1000110; // C
            4'hD: seg = 7'b0100001; // d
            4'hE: seg = 7'b0000110; // E
            4'hF: seg = 7'b0001110; // F
            default: seg = 7'b1111111; // Off
        endcase
    end
endmodule

Below are the results of the Questa simulation of this testbench:

sevenSegmentDisplay Waveforms

sevenSegmentDisplay Transcript
lab3_cw_testbench results:

This testbench tested the HSOSC module and the overall functionality of this lab by using a few sample test cases. Below is the code for my testbench:

// Christian Wu
// chrwu@g.hmc.edu
// 09/17/25

// Testbench for lab3_cw

`timescale 1ns/1ps

module lab3_cw_testbench(); 
    logic clk;
    logic resetInv;  
    logic [3:0] col;
    logic [3:0] row;
    logic an1, an2;
    logic [6:0] seg;

    lab3_cw dut(.resetInv(resetInv), .col(col), .row(row), .seg(seg), .an1(an1), .an2(an2));

    always begin
        clk = 0; #10.42;  // ~48MHz clock period
        clk = 1; #10.42;
    end

    initial begin
        resetInv = 0; #100;  
        resetInv = 1;    
    end
    
    initial begin
        col = 4'b0000;
        
        wait(resetInv == 1);
        #1_000_000;  // 1ms delay
        
        $display("Starting keypad test sequence...");
        
        // Test Key 1: Row 0 (4'b0001), Col 0 (4'b0001)
        wait(row == 4'b0001);  
        #1000;
        col = 4'b0001;
        $display("Time %0t: Pressing key 1 - Row: %b, Col: %b", $time, row, col);
        #60_000_000;  
        col = 4'b0000;
        $display("Time %0t: Released key 1", $time);
        #10_000_000;
        
        // Test Key 2: Row 0 (4'b0001), Col 1 (4'b0010)
        wait(row == 4'b0001);  
        #1000;
        col = 4'b0010;
        $display("Time %0t: Pressing key 2 - Row: %b, Col: %b", $time, row, col);
        #60_000_000; 
        col = 4'b0000;
        $display("Time %0t: Released key 2", $time);
        #10_000_000;
        
        // Test Key 3: Row 0 (4'b0001), Col 2 (4'b0100)
        wait(row == 4'b0001);  
        #1000;
        col = 4'b0100;
        $display("Time %0t: Pressing key 3 - Row: %b, Col: %b", $time, row, col);
        #60_000_000;
        col = 4'b0000;
        $display("Time %0t: Released key 3", $time);
        #10_000_000;
        
        // Test Key A: Row 3 (4'b1000), Col 0 (4'b0001)
        wait(row == 4'b1000);  
        #1000;
        col = 4'b0001;
        $display("Time %0t: Pressing key A - Row: %b, Col: %b", $time, row, col);
        #60_000_000;  
        col = 4'b0000;
        $display("Time %0t: Released key A", $time);
        #10_000_000;
        
        #100_000_000;
        
        $display("Tests completed");
        $finish;
    end

endmodule

Below are my Questa simulation results:

lab3_cw Waveforms

Thus, all testbenches and testing for this lab was successful!

Hardware

Design and Schematic

After finishing with testing, I can now build my hardware and program my FPGA. Below, is a table of all of my pin assignments, as well as my schematic:

Pin assignments for Lab 3 inputs and outputs
Name Pin
reset 43
row[0] 37
row[1] 31
row[2] 35
row[3] 32
col[0] 44
col[1] 3
col[2] 45
col[3] 46
an1 10
an2 2
seg[0] 21
seg[1] 19
seg[2] 11
seg[3] 6
seg[4] 4
seg[5] 48
seg[6] 47

Lab 3 Schematic

Dual 7-segment Display Resistor Values

For the 7-segment display, resistors were added at each of the seven segment pins to prevent the display from drawing too much current. After referencing the HDSP-521A Seven Segment Display Datasheet, I found that the forward voltage for the display is 1.7 V. Given that we are using a 3.3 V source voltage from the FPGA, the voltage across the resistor is 1.6 V. We need to keep the current under 8 mA, which is the maximum for our ice40, so to keep it clean, I used 220 Ohm Resistors, giving a current of 7.3 mA.

2N3906 Transistor Base Resistor Value

We are supplying 3.3 V to our 2N3906 PNP Transistor. We know from the data sheetthat the Base Emitter Voltage is around 0.7, so we have 2.6 V that will be dropped through the resistor that connects the base to the GPIO Pin. From the iice40 Datasheet, we know from Table 4.17 that I_OL = 8 mA. Thus, we know that we want the current to be between 1 and 8 mA. I chose to use a 1 k Ohm resistor, which gives a current of 2.6 mA, giving ample wiggle room on both ends. Below is a picture that models this math:

Transistor Resistor Value Math

Results

After finishing with the design, I was able to wire my board according to the schematic and programming my top level module, lab3_cw, onto my board. I was able to physically verify that everything worked as expected.

Below is a video demonstrating this lab working successfully:

Successful Keypad Decoder

As a result, my design and project has met all the requirements for this lab.

AI Prototype

Prompt 1

To test how AI would do for this lab, I entered the following prompt to Claude AI:

Target device: Lattice iCE40 UP5K FPGA with internal high-speed oscillator (~20 MHz).
Write synthesizable SystemVerilog to scan a 4x4 matrix keypad and display the last two hex keys pressed on a dual 7‑segment display. Implement:
* A clock divider that derives a scan clock on the order of 100–200 Hz from the internal oscillator.
* A keypad scanning controller that iterates one active‑low column at a time and samples active‑low rows, registering at most one key per press (debounce‑by‑design), ignoring additional presses while any key is held, and allowing a new registration only after release.
* A top level that updates two hex digits (older and most recent) when a new key is registered and drives a time‑multiplexed two‑digit 7‑segment display without visible flicker and with balanced brightness.
Use idiomatic SystemVerilog (e.g., logic, always_ff, enumerated states for FSMs). Provide clean module boundaries and keep all state synchronous. Include brief comments explaining the design choices.

AI Code

As a result, it returned the following code:

// Top-level module for 4x4 keypad scanner with dual 7-segment display
// Targets Lattice iCE40 UP5K FPGA with ~20 MHz internal oscillator
module keypad_display_top (
    input  logic       clk,           // ~20 MHz internal oscillator
    input  logic       rst_n,         // Active-low reset
    
    // Keypad interface (active-low)
    output logic [3:0] col_n,         // Column outputs (active-low)
    input  logic [3:0] row_n,         // Row inputs (active-low, pulled up)
    
    // 7-segment display interface
    output logic [6:0] seg_n,         // 7-segment outputs (active-low)
    output logic [1:0] dig_sel_n      // Digit select (active-low)
);

    // Internal signals
    logic scan_clk;                   // ~150 Hz scan clock
    logic display_clk;                // ~1 kHz display multiplex clock
    logic [3:0] key_pressed;          // Current key value (0-F)
    logic key_valid;                  // Key press event pulse
    
    // Instantiate clock divider
    clock_divider clk_div (
        .clk(clk),
        .rst_n(rst_n),
        .scan_clk(scan_clk),
        .display_clk(display_clk)
    );
    
    // Instantiate keypad scanner
    keypad_scanner scanner (
        .clk(clk),
        .rst_n(rst_n),
        .scan_clk(scan_clk),
        .col_n(col_n),
        .row_n(row_n),
        .key_pressed(key_pressed),
        .key_valid(key_valid)
    );
    
    // Instantiate display controller
    display_controller display (
        .clk(clk),
        .rst_n(rst_n),
        .display_clk(display_clk),
        .key_pressed(key_pressed),
        .key_valid(key_valid),
        .seg_n(seg_n),
        .dig_sel_n(dig_sel_n)
    );

endmodule

// Clock divider module
// Generates scan clock (~150 Hz) and display multiplex clock (~1 kHz)
module clock_divider (
    input  logic clk,           // ~20 MHz input clock
    input  logic rst_n,
    output logic scan_clk,      // ~150 Hz for keypad scanning
    output logic display_clk    // ~1 kHz for display multiplexing
);

    // Clock divider counters
    // For 20 MHz input: scan_clk = 20M / (2^17) ≈ 153 Hz
    logic [16:0] scan_counter;
    // For display_clk = 20M / (2^14) ≈ 1.2 kHz
    logic [13:0] display_counter;
    
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            scan_counter <= '0;
            display_counter <= '0;
        end else begin
            scan_counter <= scan_counter + 1;
            display_counter <= display_counter + 1;
        end
    end
    
    assign scan_clk = scan_counter[16];
    assign display_clk = display_counter[13];

endmodule

// Keypad scanner with debouncing
// Scans 4x4 matrix keypad, registers one key per press with debounce-by-design
module keypad_scanner (
    input  logic       clk,
    input  logic       rst_n,
    input  logic       scan_clk,
    output logic [3:0] col_n,         // Active-low column outputs
    input  logic [3:0] row_n,         // Active-low row inputs
    output logic [3:0] key_pressed,   // Registered key value
    output logic       key_valid      // Key press event (single cycle pulse)
);

    // FSM states
    typedef enum logic [1:0] {
        IDLE,           // No key pressed, ready to scan
        KEY_DETECTED,   // Key detected, wait for stable reading
        KEY_HELD        // Key registered, wait for release
    } state_t;
    
    state_t state, next_state;
    
    // Scan control
    logic [1:0] col_select;           // Current column being scanned (0-3)
    logic [3:0] col_decoded;          // One-hot column select
    logic [3:0] row_sync;             // Synchronized row inputs
    
    // Key detection
    logic key_detected;
    logic [3:0] detected_key;
    
    // Column decoder: converts 2-bit select to one-hot (active-low)
    always_comb begin
        col_decoded = 4'b1111;  // Default all inactive
        col_decoded[col_select] = 1'b0;  // Activate selected column
    end
    
    assign col_n = col_decoded;
    
    // Synchronize row inputs (2 FF synchronizer)
    logic [3:0] row_sync1;
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            row_sync1 <= 4'b1111;
            row_sync <= 4'b1111;
        end else begin
            row_sync1 <= row_n;
            row_sync <= row_sync1;
        end
    end
    
    // Key detection logic
    always_comb begin
        key_detected = 1'b0;
        detected_key = 4'h0;
        
        // Check for active row in current column
        for (int i = 0; i < 4; i++) begin
            if (!row_sync[i]) begin  // Active-low row detected
                key_detected = 1'b1;
                // Calculate key value: row * 4 + col
                detected_key = {i[1:0], col_select};
                break;  // Only register first detected key
            end
        end
    end
    
    // Column scanning counter (advances on scan_clk)
    logic scan_clk_prev;
    logic scan_edge;
    
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            scan_clk_prev <= 1'b0;
        end else begin
            scan_clk_prev <= scan_clk;
        end
    end
    
    assign scan_edge = scan_clk && !scan_clk_prev;
    
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            col_select <= 2'b00;
        end else if (scan_edge) begin
            col_select <= col_select + 1;  // Cycles through 0-3
        end
    end
    
    // Main FSM
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state <= IDLE;
        end else begin
            state <= next_state;
        end
    end
    
    always_comb begin
        next_state = state;
        
        case (state)
            IDLE: begin
                if (key_detected) begin
                    next_state = KEY_DETECTED;
                end
            end
            
            KEY_DETECTED: begin
                if (!key_detected) begin
                    next_state = IDLE;  // False detection, go back
                end else if (scan_edge) begin
                    next_state = KEY_HELD;  // Stable for full scan cycle
                end
            end
            
            KEY_HELD: begin
                if (!key_detected) begin
                    next_state = IDLE;  // Key released
                end
            end
        endcase
    end
    
    // Output registration
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            key_pressed <= 4'h0;
            key_valid <= 1'b0;
        end else begin
            key_valid <= 1'b0;  // Default to no pulse
            
            if (state == KEY_DETECTED && next_state == KEY_HELD) begin
                key_pressed <= detected_key;
                key_valid <= 1'b1;  // Generate single-cycle pulse
            end
        end
    end

endmodule

// Display controller with time multiplexing
// Maintains two hex digits and drives 7-segment display without flicker
module display_controller (
    input  logic       clk,
    input  logic       rst_n,
    input  logic       display_clk,
    input  logic [3:0] key_pressed,
    input  logic       key_valid,
    output logic [6:0] seg_n,         // 7-segment outputs (active-low)
    output logic [1:0] dig_sel_n      // Digit select (active-low)
);

    // Stored digits (older and newer)
    logic [3:0] digit_old, digit_new;
    
    // Display multiplexing
    logic display_select;             // 0 = digit_old, 1 = digit_new
    logic [3:0] current_digit;
    
    // Update stored digits on new key press
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            digit_old <= 4'h0;
            digit_new <= 4'h0;
        end else if (key_valid) begin
            digit_old <= digit_new;     // Shift: new becomes old
            digit_new <= key_pressed;  // Store new key
        end
    end
    
    // Display multiplexer (toggles on display_clk edge)
    logic display_clk_prev;
    logic display_edge;
    
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            display_clk_prev <= 1'b0;
        end else begin
            display_clk_prev <= display_clk;
        end
    end
    
    assign display_edge = display_clk && !display_clk_prev;
    
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            display_select <= 1'b0;
        end else if (display_edge) begin
            display_select <= ~display_select;
        end
    end
    
    // Select current digit and digit enable
    always_comb begin
        if (display_select) begin
            current_digit = digit_new;
            dig_sel_n = 2'b01;  // Enable right digit (newer)
        end else begin
            current_digit = digit_old;
            dig_sel_n = 2'b10;  // Enable left digit (older)
        end
    end
    
    // 7-segment decoder (hex to 7-segment, active-low outputs)
    always_comb begin
        case (current_digit)
            4'h0: seg_n = 7'b1000000;  // 0
            4'h1: seg_n = 7'b1111001;  // 1
            4'h2: seg_n = 7'b0100100;  // 2
            4'h3: seg_n = 7'b0110000;  // 3
            4'h4: seg_n = 7'b0011001;  // 4
            4'h5: seg_n = 7'b0010010;  // 5
            4'h6: seg_n = 7'b0000010;  // 6
            4'h7: seg_n = 7'b1111000;  // 7
            4'h8: seg_n = 7'b0000000;  // 8
            4'h9: seg_n = 7'b0010000;  // 9
            4'hA: seg_n = 7'b0001000;  // A
            4'hB: seg_n = 7'b0000011;  // b
            4'hC: seg_n = 7'b1000110;  // C
            4'hD: seg_n = 7'b0100001;  // d
            4'hE: seg_n = 7'b0000110;  // E
            4'hF: seg_n = 7'b0001110;  // F
        endcase
    end

endmodule

Test in Radiant

I then started a new project in Radiant and attempted to synthesize the code. Claude was able to synthesize properly and it seems like it would actually work quite well, which is really impressive. I would need to add the HSOSC module to be able to utilize the FPGA’s onboard clock to run this. But overall, it worked quite well

Modular Prompts

I then ran the second prompt by using modular prompts. I used the following prompts:

Target device: Lattice iCE40 UP5K FPGA.

Overall Goal: Write SystemVerilog to scan a 4x4 matrix keypad and display the last two hex keys pressed on a dual 7 segment display.

Current Goal: Write a synthesizable SystemVerilog module that produces a one‑shot registration signal for the keypad system. Behavior:

When a key press is first detected, capture the current key code and assert a single‑cycle “new key” pulse.
While any key remains pressed, do not accept additional keys.
Only after keys are released should a subsequent press be recognized.
This should handle debouncing of the keys.
Implement as a small synchronous FSM with enumerated states and glitch‑free outputs. Keep names and interfaces reasonable; do not assume any hidden modules beyond what you define here
Target device: Lattice iCE40 UP5K FPGA.

Write a synthesizable SystemVerilog module that cycles through keypad columns (active‑low, one at a time) and samples rows (active‑low) to detect a single key at a time. Behavior:

Iterate columns at a suitable scan rate derived from the divided clock and sample rows.
When a key is detected, report a stable key code consistent with a standard 4x4 keypad layout and maintain it while the key remains pressed.
Provide a boolean signal indicating whether any key is currently pressed.
Use clean state encoding and synchronous logic; avoid combinational feedback and latches.
Target device: Lattice iCE40 UP5K FPGA with internal oscillator as the root clock source.

Write a top‑level SystemVerilog module that instantiates the scanner and one‑shot modules, shifts the last two keys (older ← most recent; most recent ← new), and drives a multiplexed two‑digit seven‑segment display. Requirements:

Update the displayed digits only when a new key is registered.
Ensure equal apparent brightness and no visible flicker.
Keep all logic synthesizable and synchronous; use idiomatic SystemVerilog constructs.
Provide any small clock‑enable or divider logic you need. You can also assume that a sevenSegment module exists that takes a 4 bit input and outputs the 7 segments.

AI Code Result

Claude gave me the following code.

module keypad_oneshot (
    input  logic        clk,
    input  logic        rst_n,
    input  logic        key_valid,     // From keypad scanner - high when any key detected
    input  logic [3:0]  key_code,      // From keypad scanner - current key code
    
    output logic        new_key_pulse, // Single-cycle pulse when new key registered
    output logic [3:0]  captured_key   // Stable key code output
);

    // FSM States
    typedef enum logic [1:0] {
        IDLE        = 2'b00,  // No key pressed, ready for new press
        DEBOUNCE    = 2'b01,  // Key detected, debouncing
        KEY_HELD    = 2'b10,  // Key validated and held, waiting for release
        RELEASE_DEB = 2'b11   // Key released, debouncing release
    } state_t;
    
    state_t current_state, next_state;
    
    // Debounce counter - adjust DEBOUNCE_COUNT based on your clock frequency
    // For 12MHz clock, 240 cycles = ~20us debounce time
    localparam int DEBOUNCE_COUNT = 240;
    localparam int COUNTER_WIDTH = $clog2(DEBOUNCE_COUNT + 1);
    
    logic [COUNTER_WIDTH-1:0] debounce_counter;
    logic debounce_done;
    logic [3:0] key_code_reg;
    
    // Debounce counter logic
    assign debounce_done = (debounce_counter == DEBOUNCE_COUNT);
    
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            debounce_counter <= '0;
        end else begin
            case (current_state)
                IDLE: begin
                    if (key_valid) begin
                        debounce_counter <= 1;  // Start counting
                    end else begin
                        debounce_counter <= '0;
                    end
                end
                
                DEBOUNCE: begin
                    if (key_valid && !debounce_done) begin
                        debounce_counter <= debounce_counter + 1;
                    end else begin
                        debounce_counter <= '0;
                    end
                end
                
                KEY_HELD: begin
                    if (!key_valid) begin
                        debounce_counter <= 1;  // Start release debounce
                    end else begin
                        debounce_counter <= '0;
                    end
                end
                
                RELEASE_DEB: begin
                    if (!key_valid && !debounce_done) begin
                        debounce_counter <= debounce_counter + 1;
                    end else begin
                        debounce_counter <= '0;
                    end
                end
                
                default: debounce_counter <= '0;
            endcase
        end
    end
    
    // State register
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            current_state <= IDLE;
        end else begin
            current_state <= next_state;
        end
    end
    
    // Next state logic
    always_comb begin
        next_state = current_state;  // Default: stay in current state
        
        case (current_state)
            IDLE: begin
                if (key_valid) begin
                    next_state = DEBOUNCE;
                end
            end
            
            DEBOUNCE: begin
                if (!key_valid) begin
                    next_state = IDLE;  // Glitch - go back to idle
                end else if (debounce_done) begin
                    next_state = KEY_HELD;  // Valid press confirmed
                end
            end
            
            KEY_HELD: begin
                if (!key_valid) begin
                    next_state = RELEASE_DEB;
                end
            end
            
            RELEASE_DEB: begin
                if (key_valid) begin
                    next_state = KEY_HELD;  // Key pressed again before full release
                end else if (debounce_done) begin
                    next_state = IDLE;  // Clean release confirmed
                end
            end
            
            default: next_state = IDLE;
        endcase
    end
    
    // Key code capture register
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            key_code_reg <= 4'h0;
        end else if (current_state == DEBOUNCE && debounce_done && key_valid) begin
            // Capture key code when transitioning from DEBOUNCE to KEY_HELD
            key_code_reg <= key_code;
        end
    end
    
    // Output assignments
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            new_key_pulse <= 1'b0;
        end else begin
            // Generate single-cycle pulse when transitioning to KEY_HELD
            new_key_pulse <= (current_state == DEBOUNCE) && 
                           (next_state == KEY_HELD) && 
                           debounce_done;
        end
    end
    
    // Captured key output is stable once registered
    assign captured_key = key_code_reg;

endmodule

For prompt 2, I got the following code:

module keypad_scanner (
    input  logic        clk,
    input  logic        rst_n,
    input  logic [3:0]  keypad_rows,    // Active-low row inputs
    
    output logic [3:0]  keypad_cols,    // Active-low column outputs  
    output logic        key_valid,      // High when any key is pressed
    output logic [3:0]  key_code        // 4-bit key code (0-F hex)
);

    // Keypad layout for reference:
    // Cols: 0   1   2   3
    // Row 0: 1   2   3   A
    // Row 1: 4   5   6   B  
    // Row 2: 7   8   9   C
    // Row 3: *   0   #   D
    
    // Standard hex encoding:
    // 1=0x1, 2=0x2, 3=0x3, A=0xA
    // 4=0x4, 5=0x5, 6=0x6, B=0xB  
    // 7=0x7, 8=0x8, 9=0x9, C=0xC
    // *=0xE, 0=0x0, #=0xF, D=0xD

    // Scan timing - divide main clock for column scanning
    // For 12MHz clock, divide by 3000 gives ~4kHz scan rate (1ms per column, 4ms full cycle)
    localparam int SCAN_DIVIDER = 3000;
    localparam int SCAN_COUNTER_WIDTH = $clog2(SCAN_DIVIDER);
    
    logic [SCAN_COUNTER_WIDTH-1:0] scan_counter;
    logic scan_tick;
    
    // Column state counter (2 bits for 4 columns)
    logic [1:0] col_select;
    
    // Key detection and encoding
    logic [3:0] current_key_code;
    logic current_key_valid;
    logic [3:0] stable_key_code;
    logic stable_key_valid;
    
    // Generate scan tick - divides clock for column scanning
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            scan_counter <= '0;
            scan_tick <= 1'b0;
        end else begin
            if (scan_counter == SCAN_DIVIDER - 1) begin
                scan_counter <= '0;
                scan_tick <= 1'b1;
            end else begin
                scan_counter <= scan_counter + 1;
                scan_tick <= 1'b0;
            end
        end
    end
    
    // Column selection counter - advances on scan_tick
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            col_select <= 2'b00;
        end else if (scan_tick) begin
            col_select <= col_select + 1;  // Auto-wraps at 4
        end
    end
    
    // Generate column outputs - active low, one-hot
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            keypad_cols <= 4'b1111;  // All high (inactive)
        end else begin
            case (col_select)
                2'b00: keypad_cols <= 4'b1110;  // Column 0 active
                2'b01: keypad_cols <= 4'b1101;  // Column 1 active  
                2'b10: keypad_cols <= 4'b1011;  // Column 2 active
                2'b11: keypad_cols <= 4'b0111;  // Column 3 active
                default: keypad_cols <= 4'b1111;
            endcase
        end
    end
    
    // Key detection and encoding logic
    always_comb begin
        current_key_valid = 1'b0;
        current_key_code = 4'h0;
        
        // Check for key press in current column
        // Rows are active-low, so look for 0 bits
        case (col_select)
            2'b00: begin // Column 0: 1, 4, 7, *
                case (keypad_rows)
                    4'b1110: begin current_key_valid = 1'b1; current_key_code = 4'h1; end // Row 0: '1'
                    4'b1101: begin current_key_valid = 1'b1; current_key_code = 4'h4; end // Row 1: '4'  
                    4'b1011: begin current_key_valid = 1'b1; current_key_code = 4'h7; end // Row 2: '7'
                    4'b0111: begin current_key_valid = 1'b1; current_key_code = 4'hE; end // Row 3: '*'
                    default: begin current_key_valid = 1'b0; current_key_code = 4'h0; end
                endcase
            end
            
            2'b01: begin // Column 1: 2, 5, 8, 0
                case (keypad_rows)
                    4'b1110: begin current_key_valid = 1'b1; current_key_code = 4'h2; end // Row 0: '2'
                    4'b1101: begin current_key_valid = 1'b1; current_key_code = 4'h5; end // Row 1: '5'
                    4'b1011: begin current_key_valid = 1'b1; current_key_code = 4'h8; end // Row 2: '8' 
                    4'b0111: begin current_key_valid = 1'b1; current_key_code = 4'h0; end // Row 3: '0'
                    default: begin current_key_valid = 1'b0; current_key_code = 4'h0; end
                endcase
            end
            
            2'b10: begin // Column 2: 3, 6, 9, #
                case (keypad_rows)
                    4'b1110: begin current_key_valid = 1'b1; current_key_code = 4'h3; end // Row 0: '3'
                    4'b1101: begin current_key_valid = 1'b1; current_key_code = 4'h6; end // Row 1: '6'
                    4'b1011: begin current_key_valid = 1'b1; current_key_code = 4'h9; end // Row 2: '9'
                    4'b0111: begin current_key_valid = 1'b1; current_key_code = 4'hF; end // Row 3: '#' 
                    default: begin current_key_valid = 1'b0; current_key_code = 4'h0; end
                endcase
            end
            
            2'b11: begin // Column 3: A, B, C, D
                case (keypad_rows)
                    4'b1110: begin current_key_valid = 1'b1; current_key_code = 4'hA; end // Row 0: 'A'
                    4'b1101: begin current_key_valid = 1'b1; current_key_code = 4'hB; end // Row 1: 'B'
                    4'b1011: begin current_key_valid = 1'b1; current_key_code = 4'hC; end // Row 2: 'C' 
                    4'b0111: begin current_key_valid = 1'b1; current_key_code = 4'hD; end // Row 3: 'D'
                    default: begin current_key_valid = 1'b0; current_key_code = 4'h0; end
                endcase
            end
            
            default: begin
                current_key_valid = 1'b0;
                current_key_code = 4'h0;
            end
        endcase
    end
    
    // Stable key output registers - hold value while key is pressed
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            stable_key_valid <= 1'b0;
            stable_key_code <= 4'h0;
        end else begin
            if (current_key_valid) begin
                // Key detected - capture and hold
                stable_key_valid <= 1'b1;
                stable_key_code <= current_key_code;
            end else if (!current_key_valid && scan_tick && col_select == 2'b11) begin
                // End of scan cycle with no key detected - clear outputs
                stable_key_valid <= 1'b0;
                stable_key_code <= 4'h0;
            end
            // Otherwise maintain previous values
        end
    end
    
    // Output assignments
    assign key_valid = stable_key_valid;
    assign key_code = stable_key_code;

endmodule

For the top module, it gave me the following code:

module keypad_top (
    // External connections
    input  logic [3:0]  keypad_rows,    // Active-low row inputs from keypad
    output logic [3:0]  keypad_cols,    // Active-low column drives to keypad
    output logic [6:0]  seg_cathodes,   // 7-segment cathodes (a-g, active-low)
    output logic [1:0]  seg_anodes      // 7-segment anodes (digit select, active-low)
);

    // Internal oscillator instance for iCE40 UP5K (48MHz nominal)
    logic clk_48mhz;
    
    SB_HFOSC #(
        .CLKHF_DIV("0b10")  // Divide by 4: 48MHz/4 = 12MHz
    ) u_hfosc (
        .CLKHFPU(1'b1),     // Power up the oscillator
        .CLKHFEN(1'b1),     // Enable the oscillator
        .CLKHF(clk_48mhz)   // 48MHz output (before divider)
    );
    
    // Main system clock (12MHz after internal divider)
    logic clk;
    assign clk = clk_48mhz;
    
    // Generate reset from power-on
    logic [3:0] reset_counter = 4'h0;
    logic rst_n;
    
    always_ff @(posedge clk) begin
        if (reset_counter != 4'hF) begin
            reset_counter <= reset_counter + 1;
            rst_n <= 1'b0;
        end else begin
            rst_n <= 1'b1;
        end
    end
    
    // Inter-module signals
    logic        key_valid;
    logic [3:0]  key_code;
    logic        new_key_pulse;
    logic [3:0]  captured_key;
    
    // Key history registers - shift register for last two keys
    logic [3:0]  recent_key;     // Most recent key (rightmost digit)
    logic [3:0]  older_key;      // Older key (leftmost digit)
    
    // Seven-segment display multiplexing
    logic [3:0]  mux_counter;
    logic        digit_select;   // 0 = older digit, 1 = recent digit
    logic [3:0]  current_digit;
    logic [6:0]  seg_data;
    
    // Display refresh rate: 12MHz / 4096 ≈ 2.93kHz per digit, 1.46kHz refresh rate
    // This gives good brightness without visible flicker
    localparam int MUX_DIVIDER = 4096;
    localparam int MUX_COUNTER_WIDTH = $clog2(MUX_DIVIDER);
    
    logic [MUX_COUNTER_WIDTH-1:0] mux_div_counter;
    logic mux_tick;
    
    //==========================================================================
    // Clock divider for display multiplexing
    //==========================================================================
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            mux_div_counter <= '0;
            mux_tick <= 1'b0;
        end else begin
            if (mux_div_counter == MUX_DIVIDER - 1) begin
                mux_div_counter <= '0;
                mux_tick <= 1'b1;
            end else begin
                mux_div_counter <= mux_div_counter + 1;
                mux_tick <= 1'b0;
            end
        end
    end
    
    //==========================================================================
    // Display multiplexing logic
    //==========================================================================
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            digit_select <= 1'b0;
        end else if (mux_tick) begin
            digit_select <= ~digit_select;  // Toggle between digits
        end
    end
    
    // Select current digit data
    always_comb begin
        case (digit_select)
            1'b0: current_digit = older_key;   // Left digit (older key)
            1'b1: current_digit = recent_key;  // Right digit (recent key)
            default: current_digit = 4'h0;
        endcase
    end
    
    // Generate anode signals (active-low digit select)
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            seg_anodes <= 2'b11;  // Both digits off
        end else begin
            case (digit_select)
                1'b0: seg_anodes <= 2'b10;  // Left digit on, right digit off
                1'b1: seg_anodes <= 2'b01;  // Left digit off, right digit on
                default: seg_anodes <= 2'b11;
            endcase
        end
    end
    
    //==========================================================================
    // Key history management
    //==========================================================================
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            recent_key <= 4'h0;
            older_key <= 4'h0;
        end else if (new_key_pulse) begin
            // Shift keys: older ← recent, recent ← new
            older_key <= recent_key;
            recent_key <= captured_key;
        end
    end
    
    //==========================================================================
    // Module instantiations
    //==========================================================================
    
    // Keypad scanner
    keypad_scanner u_scanner (
        .clk         (clk),
        .rst_n       (rst_n),
        .keypad_rows (keypad_rows),
        .keypad_cols (keypad_cols),
        .key_valid   (key_valid),
        .key_code    (key_code)
    );
    
    // One-shot key registration
    keypad_oneshot u_oneshot (
        .clk           (clk),
        .rst_n         (rst_n),
        .key_valid     (key_valid),
        .key_code      (key_code),
        .new_key_pulse (new_key_pulse),
        .captured_key  (captured_key)
    );
    
    // Seven-segment decoder for current digit
    sevenSegment u_seven_seg (
        .hex_digit (current_digit),
        .segments  (seg_data)
    );
    
    // Register seven-segment outputs for clean timing
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            seg_cathodes <= 7'b1111111;  // All segments off (active-low)
        end else begin
            seg_cathodes <= seg_data;
        end
    end

endmodule

//==========================================================================
// Seven-segment decoder module (as referenced in the instantiation)
//==========================================================================
module sevenSegment (
    input  logic [3:0] hex_digit,
    output logic [6:0] segments    // {g,f,e,d,c,b,a} - active low
);

    always_comb begin
        case (hex_digit)
            4'h0: segments = 7'b1000000; // '0'  
            4'h1: segments = 7'b1111001; // '1'
            4'h2: segments = 7'b0100100; // '2'
            4'h3: segments = 7'b0110000; // '3'
            4'h4: segments = 7'b0011001; // '4'
            4'h5: segments = 7'b0010010; // '5'
            4'h6: segments = 7'b0000010; // '6'
            4'h7: segments = 7'b1111000; // '7'
            4'h8: segments = 7'b0000000; // '8'
            4'h9: segments = 7'b0010000; // '9'
            4'hA: segments = 7'b0001000; // 'A'
            4'hB: segments = 7'b0000011; // 'b'
            4'hC: segments = 7'b1000110; // 'C'
            4'hD: segments = 7'b0100001; // 'd'
            4'hE: segments = 7'b0000110; // 'E'
            4'hF: segments = 7'b0001110; // 'F'
            default: segments = 7'b1111111; // All off
        endcase
    end

endmodule

Radiant Test

I created a new project in Radiant and it could not synthesize properly due to the error:

Error   35901063    Synthesis   ERROR <35901063> - c:/users/chris/onedrive/desktop/wu-e155-lab3/e155-lab3/lab3_aiprototype2/source/impl_1/keypad_top.sv(18): instantiating unknown module SB_HFOSC. VERI-1063 [keypad_top.sv:18]    

I then changed this from SB_HFOSC to HSOSC and it was able to synthesize

Reflection

I am overall very impressed with how AI performed. I learned that Claude is able to write much cleaner code than me, avoiding redundancies and having everything fit in just one module. While this is helpful here, it might not be helpful for testbenching purposes, but I think if I gave AI the right prompts, it would modify my code to make it more testbench friendly and also write the testbenches for me.

However, I think that using a modular structure is much better, being able to outline the way you want your code to run and giving the AI more specific prompts can help it give you code that works well. Using a one module approach is difficult for testing and debugging, and splitting it up makes it both easier for the user and for the AI, so be able to systematically work part by part.

What I learned here, is how important it is to be very precise with prompting AI. The prompts that are given to us to give to AI are very precise in terminology and what we want the AI to do, leaving no room for unnecessary misinterpretation that causes the code to not work. Thus, by giving well written prompts, we are able to get AI to give us exactly what we want and make it work.

Once again, I am very impressed with Claude and I can see how useful it will be for the final project.

Hours Spent

I spent 42 hours on this lab.