if a module assigns a value to a Signal within an finite state machine implemented as a migen.genlib.fsm.FSM, then generated Verilog will append an extra assignment to the always block to reset the generated register.

Implementing the state machine manually and not as an migen.genlib.fsm.FSM generates Verilog where the reset assignment is instead placed within an if (sys_reset) block.

If NextValue is used to assign to the signal this issue does not arise, but does heavily complicate the output, and add delays on the output which may not be desired.

My question is why is this assignment not within the reset block added, and can it be prevented whilst still using the FSM class. Is it a bug, or is it hinting at bad HDL design on my part?

Example:

Consider a module with two button inputs and a single LED output. One button should turn the LED on, and one turn it off. If I were to implement this with an FSM I might try:

from migen import *
from migen.fhdl import *

class FSMExample(Module):
    def __init__(self):
        # declare signals
        self.led = Signal()
        self.btn1 = Signal()
        self.btn2 = Signal()
        # define I/O
        self.ios = {self.led, self.btn1, self.btn2}
        self.fsm = FSM(reset_state="OFF")
        self.fsm.act("OFF",
            self.led.eq(0),
            If(self.btn1, NextState("ON"))
        )
        self.fsm.act("ON",
            self.led.eq(1),
            If(self.btn2, NextState("OFF"))
        )
        self.submodules += self.fsm

yet this generates (with verilog.convert(FSMExample(),FSMExample().ios) the following verilog:

module top(
        input led,
        input btn1,
        input btn2,
        input sys_clk,
        input sys_rst
);

reg fsmexample0_led;
reg fsmexample0_btn1 = 1'd0;
reg fsmexample0_btn2 = 1'd0;
reg state = 1'd0;
reg next_state;

// synthesis translate_off
reg dummy_s;
initial dummy_s <= 1'd0;
// synthesis translate_on


// synthesis translate_off
reg dummy_d;
// synthesis translate_on
always @(*) begin
        fsmexample0_led <= 1'd0;
        next_state <= 1'd0;
        next_state <= state;
        case (state)
                1'd1: begin
                        fsmexample0_led <= 1'd1;
                        if (fsmexample0_btn2) begin
                                next_state <= 1'd0;
                        end
                end
                default: begin
                        fsmexample0_led <= 1'd0;
                        if (fsmexample0_btn1) begin
                                next_state <= 1'd1;
                        end
                end
        endcase
// synthesis translate_off
        dummy_d <= dummy_s;
// synthesis translate_on
end

always @(posedge sys_clk) begin
        state <= next_state;
        if (sys_rst) begin
                state <= 1'd0;
        end
end

endmodule

Note the fsmexample0_led <= 1'd0; within the always block, resetting the LED even after btn2 is pressed.

Even manually implementing an FSM fixes this behaviour:

class FakeFSMExample(Module):
    def __init__(self):
        # declare signals
        self.led = Signal()
        self.btn1 = Signal()
        self.btn2 = Signal()
        self.state = Signal()
        self.next_state = Signal()
        # define I/O
        self.ios = {self.led, self.btn1, self.btn2}
        self.sync += self.state.eq(self.next_state)
        self.sync += If(~self.state,
            self.led.eq(0), 
            If(self.btn1, self.next_state.eq(1))
            ).Elif(self.state,
                self.led.eq(1), 
                If(self.btn2, self.next_state.eq(0))
            )

generates

module top(
        input led,
        input btn1,
        input btn2,
        input sys_clk,
        input sys_rst
);

reg fakefsmexample0_led = 1'd0;
reg fakefsmexample0_btn1 = 1'd0;
reg fakefsmexample0_btn2 = 1'd0;
reg fakefsmexample0_state = 1'd0;
reg fakefsmexample0_next_state = 1'd0;


always @(posedge sys_clk) begin
        fakefsmexample0_state <= fakefsmexample0_next_state;
        if ((~fakefsmexample0_state)) begin
                fakefsmexample0_led <= 1'd0;
                if (fakefsmexample0_btn1) begin
                        fakefsmexample0_next_state <= 1'd1;
                end
        end else begin
                if (fakefsmexample0_state) begin
                        fakefsmexample0_led <= 1'd1;
                        if (fakefsmexample0_btn2) begin
                                fakefsmexample0_next_state <= 1'd0;
                        end
                end
        end
        if (sys_rst) begin
                fakefsmexample0_led <= 1'd0;
                fakefsmexample0_state <= 1'd0;
                fakefsmexample0_next_state <= 1'd0;
        end
end

endmodule

where fakefsmexample0_led <= 1'd0 lives within an if (sys_rst) as desired.

6 days later

The FSM decoding is combinatorial. If you do not "reset" the register as Migen does, you'd get a latch, which is generally not what you want. If NextValue does not do what you want, you can drive a control signal within the FSM that then triggers logic of your choosing.