內容目錄

Lab 5: Combination Lock Part 1

Modified from Digital Design and Computer Architecture: RISC-V Edition (Harris & Harris, Elsevier © 2021)

Objective

The purpose of this lab is learn to design a small Moore FSM and accompanying combinational logic circuit. You will use both manual gate-level design and behavioural SystemVerilog design with Quartus.

In this lab you will design a circuit that locks and unlocks a safe that you might find in a hotel room.

To use the safe, you place an item inside, close the door and enter any combination as a 10-bit password to lock the safe. To retrieve your item, you enter the same password again to unlock the safe. The process can be repeated as often as desired, each time using any combination you want.

1. Overall Operation

  • Inputs:

    CLK50: automatically generated 50MHz clock

    SW[9:0]: 10-bit user-entered numeric value

    KEY[0]: synchronous RESET

    KEY[1] : ENTER

  • Outputs:

    HEX5 to HEX0: user interface, displays LOCHED or _OPEn_

    LEDR[9] : MATCH displays (ATTEMPT==PASSWORD)

    LEDR[8]: PREMATCH displays (SW==PASSWORD)

    LEDR[3:0]: COUNT=count_ones(ATTEMPT XOR PASSWORD)

  • State Bits:

    PASSWORD: 10-bit user-entered password to unlock the safe

    ATTEMPT: 10-bit user-entered attempt to unlock the safe

    CURRENT_STATE: as many bits as required for FSM

Operation:

  • 50MHz clock drives all flip-flops in CURRENT_STATE, PASSWORD and ATTEMPT
  • Synchronous reset occurs when RESET is low:
    • CURRENT_STATE changes to the OPEN state
    • ATTEMPT is set to all 1s
    • PASSWORD is cleared to all 0s
  • The FSM has two primary states, LOCKED and OPEN
    • User must press and release ENTER to switch to the other primary state
    • LOCKED: displays LOCHED and stores SW as ATTEMPT when ENTER pressed
    • OPEN: displays _OPEn_ and stores SW as PASSWORD when ENTER pressed
    • OPEN –> LOCKED transitions when ENTER is pressed
    • LOCKED –> OPEN transitions when ENTER is pressed and MATCH
  • The FSM has additional states (as many as required)
    • The role of these states is to wait for the user to release ENTER
    • Without these, the FSM races between the two primary states while ENTER is pressed
  • Diagnostic display (for testing and safe cracking)
    • LEDR[9] displays MATCH to indicate when ATTEMPT == PASSWORD
    • LEDR[8] displays PREMATCH to indicate when SW == PASSWORD
    • LEDR[3:0] displays COUNT, the number of incorrect ATTEMPT bits

2. Combinational Logic

Write one always_comb block to:

  • Compute MATCH, PREMATCH, and COUNT
    • Use inputs SW, ATTEMPT and PASSWORD
  • Connect these to LEDR

3. Registers

Using the CLK50, RESET and other signals noted below, write one always_ff block to:

  • Update PASSWORD register
    • When logic signal savePW is set, save the value of SW to PASSWORD
  • Update ATTEMPT register
    • When logic signal saveAT is set, save the value of SW to ATTEMPT

4. Manual FSM Design

On paper, using the Moore Machine FSM model, follow the FSM design process:

  • Determine what FSM inputs are required
  • Determine what FSM outputs are required
  • Draw the State Diagram needed to control the safe
  • Convert the State Diagram to a State Table that clearly shows the next states and all outputs
  • Write down your desired State Encoding
  • Convert the State Table to a State Table with State Encodings
  • Convert the State Table with State Encodings into Karnaugh maps
  • From the Karnaugh Map, determine the sum-of-product logic equations for
    • All next-state bits
    • All output bits

Create a SystemVerilog module for the FSM including all necessary inputs and outputs. Don’t forget the clock and reset signals!

module fsm_gates( 
    input logic xxx,
    // etc 
    output logic zzz,
    // etc 
); 

5. Putting it All Together, Part One

Create a new Quartus project and top-level module the following declaration:

module safe(
    input logic. CLK50, 
    input logic  [1:0] KEY, 
    input logic  [9:0] SW, 
    output logic [9:0] LEDR, 
    output logic [7:0] HEX5, 
    output logic [7:0] HEX4, 
    output logic [7:0] HEX3, 
    output logic [7:0] HEX2, 
    output logic [7:0] HEX1, 
    output logic [7:0] HEX0 
);

Inside the safe module, create an instance of your fsm module.
Normally, you would write a testbench for each module to fully test it out.

However, testing sequential logic can be challenging. However, for expedience, we will forgot writing a testbench for this lab.
Instead, compile your project in Quartus, program your FPGA board, and test it.

Debugging:

At this stage, it is extremely unlikely you will get it to work – don’t let that discourage you.
Check over your work, especially the manual design process of the FSM. If you notice any errors, correct your logic equations and update your SystemVerilog code.

6. Putting it All Together, Part Two

In this section, instead of manually creating the FSM logic, write a SystemVerilog module named fsm_verilog that follows directly from the State Diagram or the State Table. Use as many always_ff and/or always_comb blocks as you need.
In your top-level safe module, replace the fsm_gates module with fsm_verilog. Compile the project, program the board, and test it.

Debugging:

An FSM written this way is much easier to debug and alter. It is also more descriptive, making it easier to get your FSM to work. Instead of worrying about state assignment and extracting correct logic equations from the Karnaugh maps, you can focus on describing the correct state transitions.

7. What to Turn In and Demonstrate

TBD.


Step-by-Step Strategy

1. Combinational Logic Block

Create an always_comb block to compute:

verilog

always_comb begin
    MATCH    = (ATTEMPT == PASSWORD);
    PREMATCH = (SW == PASSWORD);
    COUNT    = 0;
    for (int i = 0; i < 10; i++)
        COUNT += ATTEMPT[i] ^ PASSWORD[i];

    LEDR[9]   = MATCH;
    LEDR[8]   = PREMATCH;
    LEDR[3:0] = COUNT;
end

2. Register Update Block

Use always_ff for synchronous updates:

verilog

always_ff @(posedge CLK50) begin
    if (!RESET) begin
        PASSWORD <= 10'b0000000000;
        ATTEMPT <= 10'b1111111111;
    end else begin
        if (savePW)
            PASSWORD <= SW;
        if (saveAT)
            ATTEMPT <= SW;
    end
end

3. Manual FSM Design

Inputs:

  • ENTER, RESET, MATCH

Outputs:

  • savePW, saveAT, display_state

States:

  • OPEN, WAIT_OPEN_RELEASE, LOCKED, WAIT_LOCK_RELEASE, CHECK_MATCH

State Diagram:

  • OPEN → (ENTER) → WAIT_OPEN_RELEASE → (ENTER released) → LOCKED
  • LOCKED → (ENTER) → CHECK_MATCH → (MATCH) → WAIT_LOCK_RELEASE → (ENTER released) → OPEN
  • If no MATCH, stay in LOCKED

Encoding Example:

verilog

typedef enum logic [2:0] {
    OPEN              = 3'b000,
    WAIT_OPEN_RELEASE = 3'b001,
    LOCKED            = 3'b010,
    CHECK_MATCH       = 3'b011,
    WAIT_LOCK_RELEASE = 3'b100
} state_t;

FSM Logic (fsm_verilog):

verilog

module fsm_verilog(
    input  logic CLK50, RESET, ENTER, MATCH,
    output logic savePW, saveAT,
    output state_t CURRENT_STATE
);
    state_t state, next;

    always_ff @(posedge CLK50 or negedge RESET) begin
        if (!RESET)
            state <= OPEN;
        else
            state <= next;
    end

    always_comb begin
        savePW = 0;
        saveAT = 0;
        case (state)
            OPEN: begin
                if (ENTER) next = WAIT_OPEN_RELEASE;
                else       next = OPEN;
                savePW = ENTER;
            end
            WAIT_OPEN_RELEASE: begin
                if (!ENTER) next = LOCKED;
                else        next = WAIT_OPEN_RELEASE;
            end
            LOCKED: begin
                if (ENTER) next = CHECK_MATCH;
                else       next = LOCKED;
                saveAT = ENTER;
            end
            CHECK_MATCH: begin
                if (MATCH) next = WAIT_LOCK_RELEASE;
                else       next = LOCKED;
            end
            WAIT_LOCK_RELEASE: begin
                if (!ENTER) next = OPEN;
                else        next = WAIT_LOCK_RELEASE;
            end
            default: next = OPEN;
        endcase
    end
endmodule

4. Top-Level Integration

verilog

module safe(
    input  logic CLK50,
    input  logic [1:0] KEY,
    input  logic [9:0] SW,
    output logic [9:0] LEDR,
    output logic [7:0] HEX5, HEX4, HEX3, HEX2, HEX1, HEX0
);
    logic [9:0] PASSWORD, ATTEMPT;
    logic MATCH, PREMATCH;
    logic [3:0] COUNT;
    logic savePW, saveAT;

    // Instantiate FSM
    fsm_verilog fsm_inst(
        .CLK50(CLK50),
        .RESET(KEY[0]),
        .ENTER(KEY[1]),
        .MATCH(MATCH),
        .savePW(savePW),
        .saveAT(saveAT)
    );

    // Combinational logic
    always_comb begin
        MATCH    = (ATTEMPT == PASSWORD);
        PREMATCH = (SW == PASSWORD);
        COUNT    = 0;
        for (int i = 0; i < 10; i++)
            COUNT += ATTEMPT[i] ^ PASSWORD[i];

        LEDR[9]   = MATCH;
        LEDR[8]   = PREMATCH;
        LEDR[3:0] = COUNT;
    end

    // Register logic
    always_ff @(posedge CLK50) begin
        if (!KEY[0]) begin
            PASSWORD <= 10'b0000000000;
            ATTEMPT <= 10'b1111111111;
        end else begin
            if (savePW) PASSWORD <= SW;
            if (saveAT) ATTEMPT <= SW;
        end
    end
endmodule

FSM State Diagram

Here’s a conceptual sketch of the Moore FSM for your safe:

程式碼

            +------------------+
            |                  |
            |      OPEN        |
            |  Display: _OPEn_ |
            +--------+---------+
                     |
                     | ENTER pressed
                     v
        +------------+-------------+
        |                          |
        |   WAIT_OPEN_RELEASE      |
        | (wait for ENTER release) |
        +------------+-------------+
                     |
                     v
            +--------+---------+
            |                  |
            |     LOCKED       |
            |  Display: LOCHED |
            +--------+---------+
                     |
                     | ENTER pressed
                     v
            +--------+---------+
            |                  |
            |   CHECK_MATCH    |
            |  (ATTEMPT==PW?)  |
            +--------+---------+
                     |
             +-------+-------+
             |               |
         MATCH=1         MATCH=0
             |               |
             v               v
+------------------+     +--------+---------+
| WAIT_LOCK_RELEASE|     |     LOCKED       |
| (wait for release)|     |  Display: LOCHED|
+------------------+     +------------------+
             |
             v
         +---+---+
         | OPEN  |
         +-------+

Karnaugh Map Setup

Let’s assume you use 3 bits to encode states:

State Encoding
OPEN 000
WAIT_OPEN_RELEASE 001
LOCKED 010
CHECK_MATCH 011
WAIT_LOCK_RELEASE 100

State Table Example

Current State ENTER MATCH Next State savePW saveAT
OPEN 0 X OPEN 0 0
OPEN 1 X WAIT_OPEN_RELEASE 1 0
WAIT_OPEN_REL 0 X LOCKED 0 0
LOCKED 0 X LOCKED 0 0
LOCKED 1 X CHECK_MATCH 0 1
CHECK_MATCH X 0 LOCKED 0 0
CHECK_MATCH X 1 WAIT_LOCK_RELEASE 0 0
WAIT_LOCK_REL 0 X OPEN 0 0

Karnaugh Map for Next State Bits

Let’s say you’re solving for NS[0] (next state bit 0). You’d fill in a 4-variable K-map using:

  • Current state bits: S2 S1 S0
  • Inputs: ENTER, MATCH

Each cell in the K-map represents a combination of these inputs, and you fill in the value of NS[0] from the state table.

Once filled, you simplify using grouping rules to extract the SOP (Sum of Products) expression.

HEX Display Logic

To display "LOCHED" and "_OPEn_", you’ll need a segment decoder for each character. For example:

Character HEX Code
L 0b01110011
O 0b00111111
C 0b01001110
H 0b01110110
E 0b01111001
D 0b01011110
_ 0b00001000
P 0b01110011
n 0b01010100

You can use a case statement inside an always_comb block to map each character to its 7-segment code.

Would you like help writing that decoder module next? Or want to walk through a Karnaugh map example for one of the next-state bits?

ok

Great! Let’s walk through two things: a Karnaugh map example for one next-state bit, and a segment decoder for displaying "LOCHED" and "_OPEn_" on HEX displays.

Karnaugh Map Example: Next-State Bit

Let’s say we’re solving for NS[0], the least significant bit of the next state. Assume the current state is encoded as S2 S1 S0, and inputs are ENTER and MATCH.

We’ll build a 4-variable Karnaugh map:

Variables:

  • S2 S1 S0 → current state
  • ENTER → input
  • MATCH → input

Step 1: Fill the truth table

From your FSM state table, extract the value of NS[0] for each combination of inputs and current state.

Example row:

S2 S1 S0 ENTER MATCH NS[0]
0 0 0 1 X 1 ← OPEN → WAIT_OPEN_RELEASE

Repeat for all transitions.

Step 2: Fill Karnaugh map

Use the truth table to populate the K-map. Group adjacent 1s to simplify.

Step 3: Extract SOP expression

From the groups, derive a simplified Boolean expression for NS[0]. For example:

text

NS[0] = S2' S1 ENTER + S2 S0' MATCH

Repeat for NS[1] and NS[2].

HEX Display Decoder

Let’s create a decoder to display "LOCHED" and "_OPEn_". Each HEX digit maps to a 7-segment code.

Segment Encoding Table

Char HEX Code Segment
L 7’h38 0110000
O 7’h3F 1111110
C 7’h39 0111001
H 7’h76 1110110
E 7’h79 1111001
D 7’h5E 1011110
_ 7’h08 0001000
P 7’h73 1110011
n 7’h54 1010100

Decoder Module

verilog

function logic [7:0] char_to_hex(input logic [2:0] char_index, input logic locked);
    case (char_index)
        3'd0: char_to_hex = locked ? 7'h38 : 7'h08; // L or _
        3'd1: char_to_hex = locked ? 7'h3F : 7'h73; // O or P
        3'd2: char_to_hex = locked ? 7'h39 : 7'h08; // C or _
        3'd3: char_to_hex = locked ? 7'h76 : 7'h79; // H or E
        3'd4: char_to_hex = locked ? 7'h79 : 7'h54; // E or n
        3'd5: char_to_hex = locked ? 7'h5E : 7'h08; // D or _
        default: char_to_hex = 7'h00;
    endcase
endfunction

Usage in safe module

verilog

assign HEX5 = char_to_hex(3'd0, locked);
assign HEX4 = char_to_hex(3'd1, locked);
assign HEX3 = char_to_hex(3'd2, locked);
assign HEX2 = char_to_hex(3'd3, locked);
assign HEX1 = char_to_hex(3'd4, locked);
assign HEX0 = char_to_hex(3'd5, locked);

You’ll need to define locked as a signal from your FSM state.


Step-by-Step Implementation Plan

1. FSM Inputs and Outputs

Inputs:

  • CLK50: System clock
  • KEY[0]: Active-low reset
  • KEY[1]: ENTER button
  • SW[9:0]: User input (password or attempt)

Outputs:

  • LEDR[9]: MATCH = (ATTEMPT == PASSWORD)
  • LEDR[8]: PREMATCH = (SW == PASSWORD)
  • LEDR[3:0]: COUNT = count_ones(ATTEMPT ^ PASSWORD)
  • HEX0: Displays "LOCHED" or "_OPEn_"

Character Breakdown

HEX Display "LOCHED" "_OPEn_"
HEX5 'L' '_'
HEX4 'O' 'O'
HEX3 'C' 'P'
HEX2 'H' 'E'
HEX1 'E' 'n'
HEX0 'D' '_'

7 Seg. value:

Char Segments 1 as light,bit order: (gfedcba) Hex Value,
invert since0 as light
'L' 0111000 ~7'h38
'O' 0011111 ~7'h3F
'C' 0110111 ~7'h67
'H' 0111100 ~7'h3C
'E' 0110110 ~7'h76
'D' 0011110 ~7'h3E
'P' 0111001 ~7'h79
'n' 0101010 ~7'h2A
'_' 0001000 ~7'h08

HEX5-0 control be:

always_comb begin
    if (CURRENT_STATE == LOCKED) begin
        HEX5 = ~7'h38; // L
        HEX4 = ~7'h3F; // 0 (0 as O)
        HEX3 = ~7'h67; // C
        HEX2 = ~7'h3C; // H (H as K)
        HEX1 = ~7'h76; // E
        HEX0 = ~7'h3E; // d (d as D)
    end else begin // OPEN
        HEX5 = ~7'h08; // _
        HEX4 = ~7'h3F; // O
        HEX3 = ~7'h79; // P
        HEX2 = ~7'h76; // E
        HEX1 = ~7'h2A; // n
        HEX0 = ~7'h08; // _
    end
end

2. Combinational Logic Block

always_comb begin
    MATCH    = (ATTEMPT == PASSWORD);
    PREMATCH = (SW == PASSWORD);
    COUNT    = $countones(ATTEMPT ^ PASSWORD); // or use a loop if $countones unsupported

    LEDR[9]   = MATCH;
    LEDR[8]   = PREMATCH;
    LEDR[3:0] = COUNT;
end

3. Register Update Block

always_ff @(posedge CLK50) begin
    if (!KEY[0]) begin // synchronous reset
        PASSWORD      <= 10'b0000000000;
        ATTEMPT       <= 10'b1111111111;
        CURRENT_STATE <= OPEN;
    end else begin
        if (savePW)
            PASSWORD <= SW;
        if (saveAT)
            ATTEMPT <= SW;
    end
end

4. Manual FSM Design (Moore Model)

State Definitions

State Description
OPEN Safe is unlocked
WAIT_OPEN Wait for ENTER release after storing PASSWORD
LOCKED Safe is locked
WAIT_LOCKED Wait for ENTER release after storing ATTEMPT

State Diagram

程式碼

OPEN --(ENTER)--> WAIT_OPEN --(release)--> LOCKED
LOCKED --(ENTER & MATCH)--> WAIT_LOCKED --(release)--> OPEN
graph LR
OPEN -->|ENTER| WAIT_OPEN -->|release| LOCKED
LOCKED -->|ENTER & MATCH| WAIT_LOCKED -->|release| OPEN

State Encoding

typedef enum logic [1:0] {
    OPEN        = 2'b00,
    WAIT_OPEN   = 2'b01,
    LOCKED      = 2'b10,
    WAIT_LOCKED = 2'b11
} state_t;

5. FSM Logic (Manual Gate-Level)

Use Karnaugh maps to derive SOP equations for next-state bits and outputs. Then implement them in fsm_gates using basic gates or assign statements.

6. FSM Logic (Behavioral)

module fsm_verilog(
    input logic CLK50, RESET, ENTER,
    input logic MATCH,
    output logic savePW, saveAT,
    output logic [1:0] CURRENT_STATE
);
    typedef enum logic [1:0] {OPEN, WAIT_OPEN, LOCKED, WAIT_LOCKED} state_t;
    state_t state, next_state;

    always_ff @(posedge CLK50) begin
        if (!RESET)
            state <= OPEN;
        else
            state <= next_state;
    end

    always_comb begin
        savePW = 0;
        saveAT = 0;
        case (state)
            OPEN: begin
                if (ENTER) begin
                    savePW    = 1;
                    next_state = WAIT_OPEN;
                end else
                    next_state = OPEN;
            end
            WAIT_OPEN: next_state = (ENTER) ? WAIT_OPEN : LOCKED;

            LOCKED: begin
                if (ENTER && MATCH) begin
                    saveAT    = 1;
                    next_state = WAIT_LOCKED;
                end else
                    next_state = LOCKED;
            end
            WAIT_LOCKED: next_state = (ENTER) ? WAIT_LOCKED : OPEN;
        endcase
    end

    assign CURRENT_STATE = state;
endmodule

7. Top-Level Module safe

module safe(
    input logic CLK50,
    input logic [1:0] KEY,
    input logic [9:0] SW,
    output logic [9:0] LEDR,
    output logic [7:0] HEX5, HEX4, HEX3, HEX2, HEX1, HEX0
);
    logic [9:0] PASSWORD, ATTEMPT;
    logic MATCH, PREMATCH;
    logic [3:0] COUNT;
    logic savePW, saveAT;
    logic [1:0] CURRENT_STATE;

    // Instantiate FSM
    fsm_verilog fsm_inst(
        .CLK50(CLK50),
        .RESET(KEY[0]),
        .ENTER(KEY[1]),
        .MATCH(MATCH),
        .savePW(savePW),
        .saveAT(saveAT),
        .CURRENT_STATE(CURRENT_STATE)
    );

    // Register logic
    always_ff @(posedge CLK50) begin
        if (!KEY[0]) begin
            PASSWORD <= 10'b0000000000;
            ATTEMPT  <= 10'b1111111111;
        end else begin
            if (savePW) PASSWORD <= SW;
            if (saveAT) ATTEMPT  <= SW;
        end
    end

    // Combinational logic
    always_comb begin
        MATCH    = (ATTEMPT == PASSWORD);
        PREMATCH = (SW == PASSWORD);
        COUNT    = $countones(ATTEMPT ^ PASSWORD);

        LEDR[9]   = MATCH;
        LEDR[8]   = PREMATCH;
        LEDR[3:0] = COUNT;
    end

    // HEX0 display logic (simplified)
    assign HEX0 = (CURRENT_STATE == LOCKED) ? 8'hDE : 8'h0E; // placeholder for LOCHED/_OPEn_
endmodule

Would you like help generating the HEX segment decoder for "LOCHED" and "_OPEn_" or visualizing the FSM diagram? I can also walk you through Karnaugh map derivation if you’re tackling the manual gate-level version.

HEX5 to HEX0: user interface, displays LOCHED or _OPEn_

To display "LOCHED" or "_OPEn_" across HEX5 to HEX0, you’ll need to drive each 7-segment display with the correct segment encoding for each character. Here’s how you can implement it in SystemVerilog:

Segment Mapping for "LOCHED" and "_OPEn_"

Each HEX display shows one ASCII-like character using 7 segments. We’ll define segment encodings for each character.

Character Breakdown

HEX Display "LOCHED" "_OPEn_"
HEX5 'L' '_'
HEX4 'O' 'O'
HEX3 'C' 'P'
HEX2 'H' 'E'
HEX1 'E' 'n'
HEX0 'D' '_'

Segment Encoding (Common Active-Low)

Each character maps to a 7-bit pattern for segments A–G. Here’s a sample encoding table:

Char Segments (gfedcba) Hex Value
'L' 0111000 7'h38
'O' 0011111 7'h3F
'C' 0110111 7'h67
'H' 0111100 7'h3C
'E' 0110110 7'h76
'D' 0011110 7'h3E
'P' 0111001 7'h79
'n' 0101010 7'h2A
'_' 0001000 7'h08

Note: These encodings assume active-low segments. You may need to invert them (~) depending on your board.

SystemVerilog Implementation

always_comb begin
    if (CURRENT_STATE == LOCKED) begin
        HEX5 = ~7'h38; // L
        HEX4 = ~7'h3F; // O
        HEX3 = ~7'h67; // C
        HEX2 = ~7'h3C; // H
        HEX1 = ~7'h76; // E
        HEX0 = ~7'h3E; // D
    end else begin // OPEN
        HEX5 = ~7'h08; // _
        HEX4 = ~7'h3F; // O
        HEX3 = ~7'h79; // P
        HEX2 = ~7'h76; // E
        HEX1 = ~7'h2A; // n
        HEX0 = ~7'h08; // _
    end
end

Would you like a visual layout of the 7-segment characters or help testing this on your FPGA board? I can also help you write a reusable decoder module if you want to support more characters.

最後修改日期: 2025 年 10 月 5 日

作者

留言

撰寫回覆或留言

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