Course Companion
Building the Capstone Controller FSM
A concept-level guide to the capstone controller pattern, worked on a different FSM.
2 min read · Updated
The capstone controller keeps the Module 4 dual-block pattern with one change, and adds one stored field. This page works both ideas on a different controller, so deciding what each capstone state drives stays your reasoning.
Outputs in the combinational block
In Lessons 4.5 and 4.6b, each output was its own assign line. A controller with several outputs per state often writes them in the same always @(*) block as the next state instead, the way Lesson 5.7 wrote its control table:
- give every output a safe default at the top of the block, beside the
next_state = state;default; - let each
caseitem override only what its own state asserts.
Every path then assigns every output, so no latch forms, and a state that asserts nothing needs no lines at all.
Capture a request once
When a request arrives with a setting, such as which operation or which channel, the controller can store that setting at the rising edge where it accepts the request. Later states read the stored copy, not the live input, so a caller changing the input afterwards cannot alter work already in progress, and the caller only has to hold the setting for the one cycle of acceptance.
Worked on a display controller
This two-state controller shows one chosen input on a display register. snap requests a capture; pick chooses which input to show:
module show_ctrl(input clk, input rst, input snap, input [1:0] pick,
output reg show_en, output reg [1:0] disp_sel);
localparam WAIT = 1'b0, SHOW = 1'b1;
reg state, next_state;
reg [1:0] pick_hold;
always @(posedge clk) begin
if (rst) begin
state <= WAIT;
pick_hold <= 2'b00;
end else begin
state <= next_state;
if (state == WAIT && snap) pick_hold <= pick;
end
end
always @(*) begin
show_en = 1'b0; // safe defaults
disp_sel = 2'b00;
next_state = state; // stay
case (state)
SHOW: begin
disp_sel = pick_hold;
show_en = 1'b1;
next_state = WAIT;
end
WAIT: if (snap) next_state = SHOW;
endcase
end
endmoduleAt a rising edge in WAIT with snap at 1 and pick at 10, the state becomes SHOW and pick_hold stores 10. During SHOW, disp_sel reads 10 even if pick has changed. The display register loads whatever its select path shows at the edge that ends SHOW, and the select path stores nothing, so SHOW must keep driving disp_sel with the stored copy right up to that edge. If disp_sel fell back to its default there, the display would load input 00 instead.
Why these outputs are Moore-style
Every output reads only the current state and the stored copy, and both are registers. So the outputs change only after a rising edge, never partway through a cycle.
For the capstone, apply the same two questions to each state: what must this state override from the defaults, and what must still be true at the edge that ends it?