內容目錄

Key-Comb Locker

1. keypad_scan.sv

test keypad, with debouncing.

I got two kind of 4×4 keypad, one needs to swap row & column pin sequence, one needn’t.

Validation Checklist

Feature Status Notes
Tri-state row control ✅ Implemented Uses generate block for clean row drive logic
Keypad decoding ✅ Implemented Covers full 4×4 matrix with layout inversion
Debounce (1ms sampling) ✅ Implemented stable_cnt logic at 50MHz
Debounce (3-cycle stable) ✅ Implemented key_valid_buf shift register
Valid signal extension ✅ Implemented io_valid_shift prolongs io[12]
Debug output via LEDs ✅ Implemented LEDR[9:5] = io[12:8] shows keycode and valid flag

keypad_scan.sv

module keypad_scan (
    input  logic        clk,        // System clock input
    input  logic        n_rst,      // Active-low asynchronous reset
    inout  tri [3:0]    row_out,    // Row outputs to keypad (tri-state controlled)
    input  logic [3:0]  col,        // Column inputs from keypad
    output logic [12:8] io,         // Output: io[11:8] = keycode, io[12] = key_valid
    output logic [9:0]  LEDR        // Debug LEDs
);

    // === Internal Signals ===
    logic [1:0] row_idx;            // Current row index for scanning (0–3)
    logic [3:0] row_drive;          // Active-low row drive control
    logic [3:0] keycode;            // Raw decoded key value from keypad
    logic       key_valid;          // Indicates a keypress was detected

    // === Debounce Buffers ===
    logic [2:0] key_valid_buf;      // Shift register for key_valid debounce
    logic [3:0] keycode_buf;        // Stable keycode output after debounce

    // === Output Valid Extension ===
    logic [4:0] io_valid_shift;     // Extends io[12] signal for multiple cycles
    assign io[12] = |io_valid_shift; // Valid if any bit in shift register is high
    assign io[11:8] = keycode_buf;   // Output stable keycode
    assign LEDR[9:5] = io[12:8];     // Debug: show keycode and valid flag

    // === Tri-State Row Output Control ===
    // Drive one row low at a time, others remain high-Z
    assign row_out[0] = (row_drive[0]) ? 1'bz : 1'b0;
    assign row_out[1] = (row_drive[1]) ? 1'bz : 1'b0;
    assign row_out[2] = (row_drive[2]) ? 1'bz : 1'b0;
    assign row_out[3] = (row_drive[3]) ? 1'bz : 1'b0;

    // === Row Scanning Logic ===
    // Rotates row index every 1ms to scan keypad
    always_ff @(posedge clk or negedge n_rst) begin
        if (!n_rst)
            row_idx <= 2'd3; // Start from row 3
        else if (is_stable)
            row_idx <= (row_idx == 0) ? 2'd3 : row_idx - 1; // Rotate rows
    end

    // === Row Drive Control ===
    // Only one row is actively driven low at a time
    always_comb begin
        row_drive = 4'b1111;         // Default: all rows high-Z
        row_drive[row_idx] = 1'b0;   // Drive current row low
    end

    // === Key Detection and Decoding ===
    // Detects keypress and maps row/column pair to keycode
    always_ff @(posedge clk or negedge n_rst) begin
        if (!n_rst) begin
            keycode   <= 4'h0;
            key_valid <= 1'b0;
        end else if (is_stable) begin
            key_valid <= 1'b0;
            for (int j = 3; j >= 0; j--) begin
                if (!col[j]) begin // Active-low column detected
                    // Decode row/column pair into keycode
                    case ({row_idx, j[1:0]})
                        4'b1111: keycode <= 4'h1;
                        4'b1110: keycode <= 4'h2;
                        4'b1101: keycode <= 4'h3;
                        4'b1100: keycode <= 4'hA;
                        4'b1011: keycode <= 4'h4;
                        4'b1010: keycode <= 4'h5;
                        4'b1001: keycode <= 4'h6;
                        4'b1000: keycode <= 4'hB;
                        4'b0111: keycode <= 4'h7;
                        4'b0110: keycode <= 4'h8;
                        4'b0101: keycode <= 4'h9;
                        4'b0100: keycode <= 4'hC;
                        4'b0011: keycode <= 4'hE;
                        4'b0010: keycode <= 4'h0;
                        4'b0001: keycode <= 4'hF;
                        4'b0000: keycode <= 4'hD;
                        default: keycode <= 4'h0;
                    endcase
                    key_valid <= 1'b1; // Mark key as valid
                end
            end
        end
    end

    // === Debounce Timer ===
    // Waits 1ms before sampling key state (50MHz clock → 50,000 cycles)
    logic [15:0] stable_cnt;
    logic        is_stable;

    always_ff @(posedge clk or negedge n_rst) begin
        if (!n_rst) begin
            stable_cnt <= 16'd0;
            is_stable  <= 1'b0;
        end else if (stable_cnt == 16'd49999) begin
            is_stable  <= 1'b1;  // 1ms elapsed
            stable_cnt <= 16'd0;
        end else begin
            stable_cnt <= stable_cnt + 1;
            is_stable  <= 1'b0;
        end
    end

    // === Debounce and Output Latching ===
    // Latches keycode only if key_valid is high for 3 consecutive samples
    always_ff @(posedge clk or negedge n_rst) begin
        if (!n_rst) begin
            key_valid_buf  <= 3'b000;
            keycode_buf    <= 4'h0;
            io_valid_shift <= 5'b00000;
        end else begin
            // Shift in current key_valid
            key_valid_buf <= {key_valid_buf[1:0], key_valid};

            // If key_valid is stable for 3 cycles, latch keycode
            if (key_valid_buf == 3'b111)
                keycode_buf <= keycode;

            // Extend io[12] signal for visibility (5-cycle pulse)
            io_valid_shift <= {io_valid_shift[3:0], (key_valid_buf == 3'b111)};
        end
    end
endmodule

keypad_scan.qsf

########## FPGA INFO
set_global_assignment -name FAMILY "MAX 10"
set_global_assignment -name DEVICE 10M50DAF484C7G

########## top module
set_global_assignment -name TOP_LEVEL_ENTITY keypad_scan

########## CLOCK 10MHz ADC, and 2 50MHz clock source
#set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to clk_adc
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to clk
#set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to clk2
#set_location_assignment PIN_N5 -to clk_adc
set_location_assignment PIN_P11 -to clk
set_location_assignment PIN_N14 -to clk2

########## RESET button & unused button (n_rst, KEY1)
set_instance_assignment -name IO_STANDARD "3.3 V SCHMITT TRIGGER" -to n_rst
set_instance_assignment -name IO_STANDARD "3.3 V SCHMITT TRIGGER" -to key1
set_location_assignment PIN_B8 -to n_rst
set_location_assignment PIN_A7 -to key1

#===========================================================================
################### Keypad USed Pins ###########
### keypad scan row: row[3:0], tri-state output as OC
# row scan signal out, row[3:0](tri-state io, set 'z' as OC cut-off)
set_location_assignment PIN_AA12 -to row_out[3]
set_location_assignment PIN_AA11 -to row_out[2]
set_location_assignment PIN_Y10 -to row_out[1]
set_location_assignment PIN_AB9 -to row_out[0]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to row_out[3]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to row_out[2]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to row_out[1]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to row_out[0]

# keypad scan column: col[3:0](input with internal pull-high)
set_location_assignment PIN_AB8 -to col[3]
set_location_assignment PIN_AB7 -to col[2]
set_location_assignment PIN_AB6 -to col[1]
set_location_assignment PIN_AB5 -to col[0]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to col[3]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to col[2]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to col[1]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to col[0]
set_instance_assignment -name WEAK_PULL_UP_RESISTOR ON -to col[3]
set_instance_assignment -name WEAK_PULL_UP_RESISTOR ON -to col[2]
set_instance_assignment -name WEAK_PULL_UP_RESISTOR ON -to col[1]
set_instance_assignment -name WEAK_PULL_UP_RESISTOR ON -to col[0]

#### key value output for keyboard
# io[8]–io[11] as keycode(4-bit)
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to io[8]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to io[9]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to io[10]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to io[11]
set_location_assignment PIN_A11 -to io[8]
set_location_assignment PIN_B11 -to io[9]
set_location_assignment PIN_D13 -to io[10]
set_location_assignment PIN_C13 -to io[11]

# io[12] as key_valid
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to io[12]
set_location_assignment PIN_E14 -to io[12]

===========================================================================
### debug LEDS
# LEDR[0]–LEDR[9] IO_STANDARD & PIN assignment
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to LEDR[0]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to LEDR[1]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to LEDR[2]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to LEDR[3]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to LEDR[4]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to LEDR[5]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to LEDR[6]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to LEDR[7]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to LEDR[8]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to LEDR[9]
set_location_assignment PIN_A8 -to LEDR[0]
set_location_assignment PIN_A9 -to LEDR[1]
set_location_assignment PIN_A10 -to LEDR[2]
set_location_assignment PIN_B10 -to LEDR[3]
set_location_assignment PIN_D13 -to LEDR[4]
set_location_assignment PIN_C13 -to LEDR[5]
set_location_assignment PIN_E14 -to LEDR[6]
set_location_assignment PIN_D14 -to LEDR[7]
set_location_assignment PIN_A11 -to LEDR[8]
set_location_assignment PIN_B11 -to LEDR[9]

keypad_scan.qpf

QUARTUS_VERSION = "24.1"
DATE = "13:15:49  October 11, 2025"

# Revisions

PROJECT_REVISION = "keypad_scan"

keypad_scan.sdc

create_clock -name clk -period 20.0 [get_ports clk]
最後修改日期: 2025 年 10 月 12 日

作者

留言

撰寫回覆或留言

發佈留言必須填寫的電子郵件地址不會公開。