针对iCE40构建时Yosys处理FSM的异常行为排查
针对你遇到的SpinalHDL生成的FSM在Yosys中无法正常工作的问题,我整理了几个可行的解决方案,同时解释问题的根源:
问题回顾
你发现只有给SpinalHDL生成的Verilog状态寄存器state添加(* fsm_encoding = "none" *)属性,代码才能按预期运行,但手动修改大量生成代码不现实,且不确定是否有Yosys配置可以替代这个操作。当前使用的Yosys版本是Yosys 0.9+3667 (git sha1 e7f36d01, gcc 7.3.0-27ubuntu1~18.04 -fPIC -Os),综合命令为:
yosys -v2 -p "synth_ice40 -top MyTopLevel -json MyTopLevel.json" MyTopLevel.v
有问题的Verilog代码如下:
// Generator : SpinalHDL v1.4.2 git head : 804c7bd7b7feaddcc1d25ecef6c208fd5f776f79 // Component : MyTopLevel module MyTopLevel ( input io_but0, input io_but1, output [1:0] io_leds, input clk, input reset ); reg [31:0] counter; //(* fsm_encoding = "none" *) reg [1:0] state; reg [1:0] state; reg [1:0] leds; assign io_leds = leds; always @ (posedge clk) begin if(reset) begin counter <= 32'h0; state <= 2'b00; leds <= 2'b00; end else begin counter <= (counter + 32'h00000001); if((state == 2'b00))begin if(((io_but0 == 1'b0) && (io_but1 == 1'b0)))begin state <= 2'b10; leds[0] <= 1'b1; leds[1] <= 1'b1; end end else begin if((state == 2'b01))begin if(((io_but0 == 1'b1) && (io_but1 == 1'b1)))begin state <= 2'b00; leds[0] <= 1'b0; leds[1] <= 1'b0; end end else begin if(((io_but0 == 1'b0) && (io_but1 == 1'b1)))begin state <= 2'b01; leds[0] <= 1'b1; leds[1] <= 1'b0; end if(((io_but0 == 1'b1) && (io_but1 == 1'b0)))begin state <= 2'b01; leds[0] <= 1'b0; leds[1] <= 1'b1; end end end end end endmodule
解决方案1:修改Yosys综合命令,跳过自动FSM优化
Yosys的synth_ice40流程默认会自动检测并优化FSM编码(比如使用one-hot、Gray码等),但这种优化可能与SpinalHDL生成的FSM逻辑不兼容。你可以通过以下两种方式禁用或修改FSM处理:
方法A:使用-nofsm选项完全禁用FSM优化
在synth_ice40命令中添加-nofsm参数,让Yosys不对FSM做任何特殊处理,保持原始的状态寄存器逻辑:
yosys -v2 -p "synth_ice40 -nofsm -top MyTopLevel -json MyTopLevel.json" MyTopLevel.v
方法B:全局指定FSM编码为none
如果不想完全禁用FSM处理,只是希望默认使用none编码,可以在综合前添加fsm -encoding none命令:
yosys -v2 -p "fsm -encoding none; synth_ice40 -top MyTopLevel -json MyTopLevel.json" MyTopLevel.v
解决方案2:让SpinalHDL自动生成fsm_encoding属性
从根源解决问题,让SpinalHDL在生成Verilog时自动给FSM状态寄存器添加属性,无需手动修改代码:
单个FSM添加属性
在SpinalHDL代码中,给状态寄存器调用addAttribute方法:
import spinal.core._ import spinal.lib._ class MyTopLevel extends Component { val io = new Bundle { val but0 = in Bool() val but1 = in Bool() val leds = out UInt(2 bits) } val counter = Reg(UInt(32 bits)) init 0 val state = Reg(UInt(2 bits)) init 0 // 给状态寄存器添加fsm_encoding属性 state.addAttribute("fsm_encoding", "none") val leds = Reg(UInt(2 bits)) init 0 io.leds := leds counter := counter + 1 switch(state) { is(0) { when(io.but0 === False && io.but1 === False) { state := 2 leds := 3 } } is(1) { when(io.but0 === True && io.but1 === True) { state := 0 leds := 0 } } default { when(io.but0 === False && io.but1 === True) { state := 1 leds := 1 } when(io.but0 === True && io.but1 === False) { state := 1 leds := 2 } } } }
全局给所有FSM添加属性
如果需要给项目中所有FSM状态寄存器自动添加属性,可以通过SpinalHDL的全局配置实现:
SpinalConfig( verilogAttributeProvider = (name, obj) => { obj match { // 根据寄存器名称判断是否为FSM状态寄存器(这里假设状态寄存器命名包含"state") case reg: Reg[_] if reg.getName().contains("state") => List(VerilogAttribute("fsm_encoding", "none")) case _ => Nil } } ).generateVerilog(new MyTopLevel)
问题根源分析
Yosys的FSM优化模块会自动分析状态转移逻辑,尝试用更高效的编码减少资源占用,但你的代码中FSM的默认分支处理了两种独立的转移情况,这种非标准的转移结构可能导致Yosys的自动优化逻辑误判,破坏了原始的状态行为。通过禁用自动FSM优化或指定none编码,Yosys会保持原始的状态寄存器取值和转移逻辑,从而解决问题。
内容的提问来源于stack exchange,提问作者pointcheck

