Lab 3: Keypad Scanner

Introduction

In this lab I created code to read a keypad and display the the recent two inputs on the dual seven segment display.

Technical Documentation

The source code for this project can be found in this github repository.

Block Diagram

Figure 1: Block diagram of the Verilog design.

The block diagram in Figure 1 demonstrates the overall architecture of the design.

Schematic

Figure 2: Schematic of the physical circuit.

Figure 3 shows how the project was wired on my breadboard.

State Transition

Figure 3: State transition diagram.

Figure 3 shows the FSM that I used in this project.

Results and Discussion

I was able to get the design working and I think I spent around 40 hours on this project. Probably would have been less if I had my brain working properly and was able to focus but I did get sick during this project so that is why it is not up to standards.

In this project I decided to power rows and take inputs of columns because this was the easiest way for me to understand. Another choice I made was using a bunch of enable signals that the entire system could talk to one another. The alternative is to include all possible states from the seperate modules together which would double the amount of states we need to use. The greater the amount of states the more output logic we have to keep track of and finding a bug in the states would have been way worse.

AI Prototype

For this AI prototype I wanted to try an AI that was not ChatGPT because in previous weeks ChatGPT hasn’t been giving the greatest code. So this weeek I used the recommended Claude. Here is the prompt I gave it: “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.”

Here is the code it gave me:

// 4x4 Matrix Keypad Scanner with Dual 7-Segment Display
// Target: Lattice iCE40 UP5K with ~20 MHz internal oscillator
// Author: Claude
// Date: 2025-09-29

//==============================================================================
// Top-level module
//==============================================================================
module keypad_display_top (
    input  logic       clk_in,      // ~20 MHz internal oscillator
    input  logic       rst_n,       // Active-low reset
    
    // Keypad interface (active-low)
    output logic [3:0] col_out,     // Column drives (one low at a time)
    input  logic [3:0] row_in,      // Row sense inputs
    
    // 7-segment display (common cathode, assuming multiplexed)
    output logic [6:0] seg,         // Segments a-g (active high)
    output logic [1:0] digit_sel    // Digit select (active high)
);

    // Internal signals
    logic        scan_clk;          // ~100-200 Hz scanning clock
    logic        display_clk;       // ~1 kHz display multiplexing clock
    logic [3:0]  key_code;          // Current key pressed (0-F)
    logic        key_valid;         // Pulse when new key registered
    logic [3:0]  digit0, digit1;    // Two most recent keys (digit1 = newest)
    
    //--------------------------------------------------------------------------
    // Clock dividers
    //--------------------------------------------------------------------------
    // Derive ~150 Hz scan clock (20 MHz / 133333 ≈ 150 Hz)
    // and ~1 kHz display refresh (20 MHz / 20000 = 1 kHz)
    clock_divider #(
        .SCAN_DIV(133333),      // 150 Hz for keypad scanning
        .DISPLAY_DIV(10000)     // 2 kHz for display (1 kHz per digit)
    ) clk_div_inst (
        .clk_in      (clk_in),
        .rst_n       (rst_n),
        .scan_clk    (scan_clk),
        .display_clk (display_clk)
    );
    
    //--------------------------------------------------------------------------
    // Keypad scanner with debounce-by-design
    //--------------------------------------------------------------------------
    keypad_scanner scanner_inst (
        .clk         (clk_in),
        .rst_n       (rst_n),
        .scan_clk    (scan_clk),
        .row_in      (row_in),
        .col_out     (col_out),
        .key_code    (key_code),
        .key_valid   (key_valid)
    );
    
    //--------------------------------------------------------------------------
    // Key history register
    // Shifts in new keys: digit1 gets new key, digit0 gets old digit1
    //--------------------------------------------------------------------------
    always_ff @(posedge clk_in or negedge rst_n) begin
        if (!rst_n) begin
            digit0 <= 4'h0;
            digit1 <= 4'h0;
        end else if (key_valid) begin
            digit0 <= digit1;       // Shift older key
            digit1 <= key_code;     // Store newest key
        end
    end
    
    //--------------------------------------------------------------------------
    // Time-multiplexed 7-segment display driver
    //--------------------------------------------------------------------------
    display_driver display_inst (
        .clk         (clk_in),
        .rst_n       (rst_n),
        .display_clk (display_clk),
        .digit0      (digit0),
        .digit1      (digit1),
        .seg         (seg),
        .digit_sel   (digit_sel)
    );

endmodule


//==============================================================================
// Clock divider module
// Generates slow clocks from the high-speed oscillator
//==============================================================================
module clock_divider #(
    parameter int SCAN_DIV    = 133333,  // Divider for scan clock (~150 Hz)
    parameter int DISPLAY_DIV = 10000    // Divider for display clock (~2 kHz)
)(
    input  logic clk_in,
    input  logic rst_n,
    output logic scan_clk,
    output logic display_clk
);

    logic [$clog2(SCAN_DIV)-1:0]    scan_counter;
    logic [$clog2(DISPLAY_DIV)-1:0] display_counter;
    
    // Scan clock generation
    always_ff @(posedge clk_in or negedge rst_n) begin
        if (!rst_n) begin
            scan_counter <= '0;
            scan_clk     <= 1'b0;
        end else begin
            if (scan_counter == SCAN_DIV - 1) begin
                scan_counter <= '0;
                scan_clk     <= 1'b1;  // Single-cycle pulse
            end else begin
                scan_counter <= scan_counter + 1;
                scan_clk     <= 1'b0;
            end
        end
    end
    
    // Display multiplex clock generation
    always_ff @(posedge clk_in or negedge rst_n) begin
        if (!rst_n) begin
            display_counter <= '0;
            display_clk     <= 1'b0;
        end else begin
            if (display_counter == DISPLAY_DIV - 1) begin
                display_counter <= '0;
                display_clk     <= 1'b1;  // Single-cycle pulse
            end else begin
                display_counter <= display_counter + 1;
                display_clk     <= 1'b0;
            end
        end
    end

endmodule


//==============================================================================
// Keypad scanner with debounce-by-design
// Scans columns sequentially, samples rows, and registers one key per press
//==============================================================================
module keypad_scanner (
    input  logic       clk,         // System clock
    input  logic       rst_n,       // Active-low reset
    input  logic       scan_clk,    // Scan tick (~150 Hz)
    input  logic [3:0] row_in,      // Row inputs (active-low)
    output logic [3:0] col_out,     // Column outputs (active-low, one at a time)
    output logic [3:0] key_code,    // Detected key code (0-F)
    output logic       key_valid    // Pulse when new key registered
);

    // FSM states
    typedef enum logic [1:0] {
        IDLE,           // No key pressed, scanning
        KEY_DETECTED,   // Key detected, register and wait for release
        WAIT_RELEASE    // Waiting for all keys to be released
    } state_t;
    
    state_t state, state_next;
    
    // Column scanning (one-hot active-low)
    logic [1:0] col_index;          // Which column is active (0-3)
    logic [3:0] col_out_reg;
    
    // Key detection
    logic       any_key_pressed;    // True if any row is low (key pressed)
    logic [3:0] key_code_reg;
    logic       key_valid_reg;
    
    // Combinational: check if any key is currently pressed
    assign any_key_pressed = (row_in != 4'b1111);
    
    //--------------------------------------------------------------------------
    // Column scanner: cycles through columns on each scan_clk
    //--------------------------------------------------------------------------
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            col_index <= 2'b00;
        end else if (scan_clk && state == IDLE) begin
            col_index <= col_index + 1;  // Wrap around 0-3
        end
    end
    
    // Generate active-low column output (one-hot)
    always_comb begin
        col_out_reg = 4'b1111;  // All high (inactive)
        col_out_reg[col_index] = 1'b0;  // Drive selected column low
    end
    
    assign col_out = col_out_reg;
    
    //--------------------------------------------------------------------------
    // Key code calculation from row and column
    //--------------------------------------------------------------------------
    function automatic logic [3:0] calc_key_code(logic [1:0] col, logic [3:0] row);
        logic [3:0] code;
        // Standard 4x4 keypad layout (hex):
        // Row 0: 1 2 3 A
        // Row 1: 4 5 6 B
        // Row 2: 7 8 9 C
        // Row 3: * 0 # D  (mapping * to E, # to F)
        case ({col, row})
            // Column 0
            6'b00_1110: code = 4'h1;  // Row 0, Col 0
            6'b00_1101: code = 4'h4;  // Row 1, Col 0
            6'b00_1011: code = 4'h7;  // Row 2, Col 0
            6'b00_0111: code = 4'hE;  // Row 3, Col 0 (*)
            
            // Column 1
            6'b01_1110: code = 4'h2;  // Row 0, Col 1
            6'b01_1101: code = 4'h5;  // Row 1, Col 1
            6'b01_1011: code = 4'h8;  // Row 2, Col 1
            6'b01_0111: code = 4'h0;  // Row 3, Col 1
            
            // Column 2
            6'b10_1110: code = 4'h3;  // Row 0, Col 2
            6'b10_1101: code = 4'h6;  // Row 1, Col 2
            6'b10_1011: code = 4'h9;  // Row 2, Col 2
            6'b10_0111: code = 4'hF;  // Row 3, Col 2 (#)
            
            // Column 3
            6'b11_1110: code = 4'hA;  // Row 0, Col 3
            6'b11_1101: code = 4'hB;  // Row 1, Col 3
            6'b11_1011: code = 4'hC;  // Row 2, Col 3
            6'b11_0111: code = 4'hD;  // Row 3, Col 3
            
            default:    code = 4'h0;  // No valid key
        endcase
        return code;
    endfunction
    
    //--------------------------------------------------------------------------
    // FSM: debounce-by-design state machine
    //--------------------------------------------------------------------------
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state <= IDLE;
        end else begin
            state <= state_next;
        end
    end
    
    always_comb begin
        state_next = state;
        
        case (state)
            IDLE: begin
                // Scanning columns, looking for a key press
                if (scan_clk && any_key_pressed) begin
                    state_next = KEY_DETECTED;
                end
            end
            
            KEY_DETECTED: begin
                // Key registered, now wait for release
                state_next = WAIT_RELEASE;
            end
            
            WAIT_RELEASE: begin
                // Wait until all keys released before accepting new press
                if (scan_clk && !any_key_pressed) begin
                    state_next = IDLE;
                end
            end
            
            default: state_next = IDLE;
        endcase
    end
    
    //--------------------------------------------------------------------------
    // Output registration
    // Generate key_valid pulse and latch key_code when transitioning to KEY_DETECTED
    //--------------------------------------------------------------------------
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            key_code_reg  <= 4'h0;
            key_valid_reg <= 1'b0;
        end else begin
            if (state == IDLE && state_next == KEY_DETECTED) begin
                // Capture key code at the moment of detection
                key_code_reg  <= calc_key_code(col_index, row_in);
                key_valid_reg <= 1'b1;  // Pulse for one cycle
            end else begin
                key_valid_reg <= 1'b0;
            end
        end
    end
    
    assign key_code  = key_code_reg;
    assign key_valid = key_valid_reg;

endmodule


//==============================================================================
// 7-segment display driver with time multiplexing
// Alternates between two digits at ~1 kHz rate for flicker-free display
//==============================================================================
module display_driver (
    input  logic       clk,
    input  logic       rst_n,
    input  logic       display_clk,  // Multiplex clock (~2 kHz)
    input  logic [3:0] digit0,       // Older digit (left)
    input  logic [3:0] digit1,       // Newer digit (right)
    output logic [6:0] seg,          // 7-segment outputs a-g
    output logic [1:0] digit_sel     // Digit selection (one-hot)
);

    logic       current_digit;      // Which digit to display (0 or 1)
    logic [3:0] hex_value;
    
    // Alternate between digits on each display_clk tick
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            current_digit <= 1'b0;
        end else if (display_clk) begin
            current_digit <= ~current_digit;
        end
    end
    
    // Select which hex value to display
    assign hex_value = current_digit ? digit1 : digit0;
    
    // Generate digit select signals (one-hot active high)
    assign digit_sel = current_digit ? 2'b10 : 2'b01;
    
    // Hex to 7-segment decoder
    // Segments: {g, f, e, d, c, b, a} - active high
    always_comb begin
        case (hex_value)
            4'h0: seg = 7'b0111111;  // 0
            4'h1: seg = 7'b0000110;  // 1
            4'h2: seg = 7'b1011011;  // 2
            4'h3: seg = 7'b1001111;  // 3
            4'h4: seg = 7'b1100110;  // 4
            4'h5: seg = 7'b1101101;  // 5
            4'h6: seg = 7'b1111101;  // 6
            4'h7: seg = 7'b0000111;  // 7
            4'h8: seg = 7'b1111111;  // 8
            4'h9: seg = 7'b1101111;  // 9
            4'hA: seg = 7'b1110111;  // A
            4'hB: seg = 7'b1111100;  // b
            4'hC: seg = 7'b0111001;  // C
            4'hD: seg = 7'b1011110;  // d
            4'hE: seg = 7'b1111001;  // E
            4'hF: seg = 7'b1110001;  // F
            default: seg = 7'b0000000;  // Blank
        endcase
    end

endmodule

One thing I found interesting between it and my code is that it only used 3 states in their FSM. This is what I had originally planned on but couldn’t get it to work. When I did try to run the code I got some errors that made it unable to synthesize but overall it seems similar to the code I have written. I woner if it could be used to bebug my code well since I found forums weren’t as helpful when I was debugging.