SystemVerilog语法(8)-有限状态机(FSM)

📌 本篇导读

知识点 难度 实战价值 典型场景
FSM 三要素 ⭐⭐ ⭐⭐⭐⭐⭐ 状态、输入、输出
摩尔型 vs 米利型 ⭐⭐ ⭐⭐⭐⭐ 输出与时钟/输入的关系
三段式 FSM 写法 ⭐⭐⭐ ⭐⭐⭐⭐⭐ 最稳健的编码风格
状态编码(独热/二进制/格雷) ⭐⭐⭐ ⭐⭐⭐⭐ 面积与速度权衡
unique case 与状态转移 ⭐⭐ ⭐⭐⭐⭐⭐ 消除歧义,提高安全性
复位同步器与安全状态机 ⭐⭐⭐ ⭐⭐⭐⭐⭐ 避免上电/复位亚稳态
自启动与异常处理 ⭐⭐⭐ ⭐⭐⭐⭐⭐ 避免挂死在非法状态

💡 常见误区:摩尔型组合输出误认为无毛刺;米利型输出未同步直接使用;状态转移条件优先级错误;次态逻辑漏默认赋值产生 latch;枚举未指定位宽浪费资源。

🧭 本文全部代码 综合可用 (除 ifndef SYNTHESIS 部分),符合 IEEE 1800-2017。


1 FSM 的基本概念

有限状态机是时序逻辑的核心,由三部分组成:

  1. 状态寄存器:当前状态(时序逻辑)
  2. 次态逻辑:根据当前状态和输入计算下一个状态(组合逻辑)
  3. 输出逻辑:根据当前状态(或状态+输入)产生输出

1.1 摩尔型(Moore)vs 米利型(Mealy)

类型 输出依赖 特点 注意事项
摩尔型 仅当前状态 输出稳定(时序输出时无毛刺),延迟一个时钟 组合输出仍有毛刺,推荐时序输出
米利型 当前状态 + 输入 响应快,但组合输出可能产生毛刺 输出必须同步打拍后再使用
对比图

结论

  • 摩尔组合输出:与状态同时变化,但有毛刺风险
  • 摩尔时序输出(用 next_state):与状态同步,无毛刺
  • 米利同步输出:比输入晚1个时钟,无毛刺

1.2 复位类型选择

复位类型 优点 缺点 适用场景
异步复位 不依赖时钟,上电即复位;不占用组合逻辑 复位释放时接近时钟沿产生亚稳态 FPGA 设计(配合复位同步器)
同步复位 无亚稳态风险;便于时序分析 依赖时钟,上电无时钟时无法复位 ASIC、高可靠性设计

异步复位同步释放(推荐用于所有异步复位设计):

systemverilog 复制代码
module rst_sync (
    input  logic clk,
    input  logic rst_n_async,   // 外部异步复位
    output logic rst_n_sync     // 同步后的复位
);
    logic rst_n_ff1;
    always_ff @(posedge clk or negedge rst_n_async) begin
        if (!rst_n_async) begin
            rst_n_ff1  <= 1'b0;
            rst_n_sync <= 1'b0;
        end 
        else begin
            rst_n_ff1  <= 1'b1;
            rst_n_sync <= rst_n_ff1;
        end
    end
endmodule

2 三段式 FSM 编码风格(工业标准)

2.1 模板:摩尔型 + 时序输出(无毛刺)+ unique case

systemverilog 复制代码
module fsm_moore_optimized (
    input  logic clk,
    input  logic rst_n_sync,    // 已同步的复位信号
    input  logic din,
    output logic dout
);
    // 状态编码:独热码(FPGA最优),兼容主流工具
    (* fsm_encoding = "one_hot", syn_encoding = "onehot" *)
    typedef enum logic [2:0] {
        S_IDLE   = 3'b001,
        S_GOT1   = 3'b010,
        S_GOT11  = 3'b100
    } state_t;
    // 实际项目状态数多于3个时,按需扩展

    state_t cur, nxt;

    // 第一段:状态寄存器(同步复位)
    always_ff @(posedge clk) begin
        if (!rst_n_sync)
            cur <= S_IDLE;
        else
            cur <= nxt;
    end

    // 第二段:次态逻辑(组合,必须默认赋值 + unique case)
    always_comb begin
        nxt = cur;   // 默认保持,避免 latch
        unique case (cur)
            S_IDLE:  if (din) nxt = S_GOT1;
            S_GOT1:  if (din) nxt = S_GOT11; else nxt = S_IDLE;
            S_GOT11: if (!din) nxt = S_IDLE; else nxt = S_GOT11;
            default: nxt = S_IDLE;   // 自启动
        endcase
    end

    // 第三段:时序输出(无毛刺,使用 next_state)
    always_ff @(posedge clk) begin
        if (!rst_n_sync)
            dout <= 1'b0;
        else
            dout <= (nxt == S_GOT11);   // 示例:状态 S_GOT11 时输出1
    end

    // 仿真断言:检测非法状态(独热码检查)
`ifndef SYNTHESIS
    assert property (@(posedge clk) disable iff (!rst_n_sync)
        $onehot0(cur)) else $error("[%0t] 非法状态:%b", $time, cur);
`endif
endmodule

2.2 米利型输出同步模板

systemverilog 复制代码
// 米利型组合输出 + 同步打拍
logic out_comb;
always_comb begin
    out_comb = 1'b0;
    if (cur == S_DATA && din_valid)   // 依赖输入
        out_comb = 1'b1;
end
always_ff @(posedge clk or negedge rst_n_sync) begin
    if (!rst_n_sync) 
    	out_sync <= 1'b0;
    else             
    	out_sync <= out_comb;
end

2.3 混合状态机(工程常用)

systemverilog 复制代码
// 同时产生摩尔型稳定输出和米利型快速响应输出
always_comb begin
    // 摩尔部分:仅依赖当前状态
    led_on = (cur == S_ACTIVE);
    
    // 米利部分:依赖状态+输入
    intr_comb = (cur == S_DATA && din_last);
end

3 状态编码与工具指令

编码方式 特点 触发器数 组合复杂度 适用场景
二进制 顺序编码,N→log₂N 最少 较高 ASIC、面积优先
格雷码 相邻仅1bit变化 较少 中等 异步FIFO指针
独热码 每状态1位 最多 最简单 FPGA、高速设计

3.1 综合指令(兼容主流工具)

systemverilog 复制代码
// Xilinx Vivado, Synopsys DC, Intel Quartus 通用写法
(* fsm_encoding = "one_hot", syn_encoding = "onehot" *)
typedef enum logic [3:0] {
    S_IDLE  = 4'b0001,
    S_START = 4'b0010,
    S_DATA  = 4'b0100,
    S_STOP  = 4'b1000
} state_t;

3.2 独热码优势原理

  • FPGA 基本单元为 LUT + 触发器,触发器资源丰富,独热码占用更多触发器但大幅简化次态逻辑
  • 次态判断只需检查 1 位,路径延迟极短,有利于时序收敛
  • 典型适用:状态数 ≤ 32,时钟频率 > 200MHz。

4 自启动与异常处理

4.1 代码级安全措施

systemverilog 复制代码
always_comb begin
    nxt = cur;            // 必须的默认赋值
    unique case (cur)
        // 正常状态转移
        default: nxt = S_IDLE;   // 捕获非法状态
    endcase
end

4.2 工具级安全状态机

  • Vivado :综合选项 -safe_fsm,自动将非法状态跳转到复位状态。
  • DCset_fsm_safe 命令。
  • 使用时无需修改 RTL,但会增加面积。

4.3 独热码非法状态显式检测

systemverilog 复制代码
logic fsm_error;
always_comb begin
    unique case (1'b1)
        cur[0]: fsm_error = 1'b0;
        cur[1]: fsm_error = 1'b0;
        cur[2]: fsm_error = 1'b0;
        cur[3]: fsm_error = 1'b0;
        default: fsm_error = 1'b1;   // 多位为1或全0
    endcase
end
always_ff @(posedge clk or negedge rst_n_sync) begin
    if (!rst_n_sync) 
    	fsm_rst <= 1'b0;
    else if (fsm_error) 
	 	fsm_rst <= 1'b1;   // 触发软复位
    else 
    	fsm_rst <= 1'b0;
end

5 完整设计实例:1101 序列检测器(重叠)

5.1 设计代码

systemverilog 复制代码
module seq_detector_1101 (
    input  logic clk,
    input  logic rst_n_sync,
    input  logic din,
    output logic dout
);
    typedef enum logic [4:0] {
        S_IDLE   = 5'b00001,
        S_GOT1   = 5'b00010,
        S_GOT11  = 5'b00100,
        S_GOT110 = 5'b01000,
        S_MATCH  = 5'b10000
    } state_t;

    state_t cur, nxt;

    always_ff @(posedge clk) begin
        if (!rst_n_sync) 
        	cur <= S_IDLE;
        else             
        	cur <= nxt;
    end

    always_comb begin
        nxt = cur;
        unique case (cur)
            S_IDLE:   if (din) nxt = S_GOT1;   else nxt = S_IDLE;
            S_GOT1:   if (din) nxt = S_GOT11;  else nxt = S_IDLE;
            S_GOT11:  if (!din) nxt = S_GOT110; else nxt = S_GOT11;
            S_GOT110: if (din) nxt = S_MATCH;  else nxt = S_IDLE;
            S_MATCH:  if (din) 
            			nxt = S_GOT11;  	
                      else 
                      	nxt = S_IDLE;
            default:  nxt = S_IDLE;
        endcase
    end

    always_ff @(posedge clk) begin
        if (!rst_n_sync) 
        	dout <= 1'b0;
        else             
        	dout <= (nxt == S_MATCH);
    end
endmodule

5.2 Testbench(带自动比对 & 下降沿驱动)

systemverilog 复制代码
module tb_seq_detector;
    logic clk, rst_n_sync, din, dout;
    logic dout_ref;   // 参考模型

    // 实例化 DUT
    seq_detector_1101 u_dut (.*);

    // 参考模型:移位寄存器 + 重叠检测
    logic [3:0] sr;
    always_ff @(posedge clk or negedge rst_n_sync) begin
        if (!rst_n_sync) 
        	sr <= 4'b0;
        else             
        	sr <= {sr[2:0], din};
    end
    assign dout_ref = (sr == 4'b1101);

    // 时钟:10ns 周期
    always #5 clk = ~clk;

    // 测试激励
    initial begin
        clk = 0; rst_n_sync = 0; din = 0;
        #10 rst_n_sync = 1;

        // 测试序列:1,1,0,1 -> 匹配
        @(negedge clk) din = 1;
        @(negedge clk) din = 1;
        @(negedge clk) din = 0;
        @(negedge clk) din = 1;   // 第一个匹配
        // 重叠测试:继续发送 1,0,1,1,0,0...
        @(negedge clk) din = 1;
        @(negedge clk) din = 0;
        @(negedge clk) din = 1;   // 重叠匹配(1101 中的 1 作为新序列首)
        @(negedge clk) din = 1;
        @(negedge clk) din = 0;
        @(negedge clk) din = 0;
        @(negedge clk) din = 0;
        #20 $finish;
    end

    // 自动比对(在时钟上升沿后采样)
    always @(posedge clk) begin
        #1;   // 等待输出稳定
        if (rst_n_sync && (dout !== dout_ref)) begin
            $error("[%0t] 错误!dout=%0d, 期望=%0d", $time, dout, dout_ref);
        end 
        else if (rst_n_sync) begin
            $display("[%0t] 正确 dout=%0d", $time, dout);
        end
    end
endmodule

6 常见错误与编码建议(10条必读)

  1. 次态逻辑忘记默认赋值 → 产生 latch。必须写 nxt = cur;
  2. 仅靠 default 防 latchdefault 只负责自启动,不能替代默认赋值。
  3. 输出逻辑未赋默认值 → 组合输出也会产生 latch。必须写 out = 1'b0;
  4. 枚举未指定位宽 → 默认 32bit,浪费资源。写 enum logic [2:0] {...}
  5. 米利型输出直接使用 → 有毛刺且异步,打一拍再使用。
  6. 测试激励与时钟沿对齐 → 违反建立时间,用 @(negedge clk) 驱动输入。
  7. 使用 always @(*) 而非 always_combalways_comb 更安全,自动初始化。
  8. 次态逻辑用非阻塞赋值 → 组合逻辑必须用 =
  9. 异步复位无同步释放 → 会产生亚稳态,使用复位同步器。
  10. 状态机无 default 或自启动 → 一旦进入非法状态就挂死,必须处理。

7 工程调试技巧

7.1 FPGA 调试信号选取(ILA)

  • current_state → 观察实际状态转移
  • next_state → 检查次态计算
  • 关键输入(din, valid 等)
  • 关键输出(dout, interrupt 等)

7.2 状态机划分原则

  • 一个状态机负责一个独立功能,状态数 ≤ 20。
  • 复杂系统拆分为多个子状态机,通过握手信号通信。
  • 状态命名体现操作,如 S_TX_START, S_RX_WAIT_DATA

📢 关于文章 :原创分享,转载需注明出处。

🔔 点击关注,第一时间收到后续内容推送(第9篇:验证基础与简单Testbench)。
📚 参考文档IEEE Standard for SystemVerilog

相关推荐
Kent Gu4 小时前
Lattice FPGA选型
fpga开发
嵌入式小站5 小时前
STM32 零基础可移植教程 05:按键消抖,为什么按一次会触发好几次
chrome·stm32·嵌入式硬件
慧都小妮子5 小时前
告别看图抓数据:DeviceXPlorer OPC Server 助力数据自动化管理
运维·物联网·自动化·takebishi·dxpserver·opc server
czhaii5 小时前
跟我动手学FX系列PLC GX2环境
嵌入式硬件
Wpa.wk5 小时前
APP自动化-Appium环境安装
运维·appium·自动化
qingfeng154155 小时前
企业微信 API 自动化开发指南:从消息回调到智能运营实战
java·开发语言·python·自动化·企业微信
志栋智能6 小时前
超自动化巡检:为智能运维(AIOps)铺平道路
运维·安全·自动化
Elecard 中国7 小时前
大规模媒体库如何实现自动化 QC?聊聊 VoD 文件检测架构
运维·自动化·ott·视频质检·vod·#视频编码·#音视频技术
Tel199253080047 小时前
pt100转pt100 PT500转PT500二通道中继器/信号转换变送器模块
自动化·工业自动化·工控设备