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
LOCHEDor_OPEn_LEDR[9] :
MATCHdisplays (ATTEMPT==PASSWORD)LEDR[8]:
PREMATCHdisplays (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 pressedOPEN: displays_OPEn_and stores SW as PASSWORD when ENTER pressedOPEN–>LOCKEDtransitions when ENTER is pressedLOCKED–>OPENtransitions when ENTER is pressed andMATCH
- 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) →LOCKEDLOCKED→ (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 stateENTER→ inputMATCH→ 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 clockKEY[0]: Active-low resetKEY[1]: ENTER buttonSW[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.
留言