A Fixed-Cycle, Timer-Driven Controller

Reading a written spec for a phase-cycling controller, and implementing it with a built-in safety property, using the dual-block FSM pattern.

Updated 2026-07-21

Not every FSM branches on input the way a sequence detector does. Some FSMs simply cycle through a fixed sequence of phases, moving to the next one whenever a single "advance" signal pulses — a real pattern behind traffic lights, simple sequencers, and any controller with a strict, non-negotiable order of operations.

Reading a spec like this starts the same way as any other FSM: find the states first. A two-road intersection controller with a phase for each road's green light and each road's yellow light has four states — one condition per phase, nothing more.

reg [1:0] phase, phase_next;

always @(posedge clk) begin
  if (reset) phase <= MAIN_GO;
  else       phase <= phase_next;
end

always @(*) begin
  phase_next = phase;                    // safe default: hold the current phase
  case (phase)
    MAIN_GO:     if (advance) phase_next = MAIN_YIELD;
    MAIN_YIELD:  if (advance) phase_next = CROSS_GO;
    CROSS_GO:    if (advance) phase_next = CROSS_YIELD;
    CROSS_YIELD: if (advance) phase_next = MAIN_GO;
  endcase
end

Every case item follows the exact same shape: if advance fires, move to the next phase in the fixed order; otherwise, the default-at-top assignment holds the current phase. There is no branching logic to design here — the order is given by the spec, not discovered by reasoning about inputs.

The safety property — two directions must never show green at the same time — does not need a special check bolted on afterward. It falls out of the output logic for free, as long as each output is assigned from its own distinct state comparison:

assign main_go = (phase == MAIN_GO);
assign cross_go = (phase == CROSS_GO);

Since the FSM can only occupy one state at a time, and each output only reads true for exactly one state, main_go and cross_go can never both be true simultaneously — the mutual exclusivity is a direct consequence of the state encoding, not something tested in afterward as an extra rule.