Verilog三段式状态机设计:从摩尔/米利原理到SPI控制器实践
2026/7/29 6:45:38 网站建设 项目流程

1. 项目概述:为什么状态机是数字逻辑设计的灵魂

在数字电路和FPGA/ASIC设计领域,状态机(Finite State Machine, FSM)绝对是一个绕不开的核心概念。无论你是想实现一个简单的按键消抖,还是构建一个复杂的通信协议控制器,状态机都是将复杂时序逻辑行为清晰化、模块化的最佳工具。我见过太多新手工程师写的代码,满屏的if-else嵌套和计数器,时序混乱,调试起来如同大海捞针。而一个设计良好的状态机,其逻辑清晰得像一张地图,状态流转一目了然。

Verilog作为硬件描述语言,其精髓在于“描述”硬件电路,而非编写软件程序。用Verilog编写状态机,就是在用代码“画”出电路的时序和组合逻辑。网上教程很多,但往往只讲“三段式”的模板,却不深究其背后的硬件电路对应关系,导致很多人照猫画虎,写出了看似正确却存在潜在风险的代码。比如,状态编码选择不当可能导致功耗激增,输出处理不当可能引入毛刺。

这篇文章,我将结合十多年的数字前端设计经验,从状态机的核心思想讲起,彻底拆解用Verilog实现状态机的各种方法、陷阱和最佳实践。我们的目标不仅是“写出来”,更是要写出“完全可综合”、“时序干净”、“易于维护”的工业级代码。无论你是正在学习Verilog的学生,还是初入行业的工程师,相信这篇详尽的指南都能让你对状态机有一个脱胎换骨的理解。

2. 状态机核心思想与分类:摩尔与米利之辨

在动手写代码之前,我们必须从根本上理解状态机是什么。简单说,状态机是一种数学模型,它包含一组有限的状态,在特定输入条件下,从一个状态转换到另一个状态,并可能产生输出。在硬件中,状态就是用一组触发器(Flip-Flop)记忆下来的当前工作模式。

2.1 摩尔型与米利型状态机

这是状态机最基础的分类,理解它们的区别是正确设计的前提。

摩尔型状态机:输出仅由当前状态决定。你可以把它想象成一个反应比较“慢”或“沉稳”的系统。例如,一个控制交通灯的状态机,红灯、绿灯、黄灯各自对应一个状态。只要系统处于“红灯”状态,输出就是红灯亮,与它刚才是不是绿灯、或者有没有行人按钮按下(输入)无关,只与它现在处在哪个状态有关。在电路上,输出是状态寄存器经过组合逻辑直接产生的。

米利型状态机:输出由当前状态和当前输入共同决定。它更像一个反应“敏捷”的系统。还用交通灯类比不太恰当,我们举一个更贴切的例子:一个序列检测器,用于检测输入序列中的“101”。当它处于“已接收到‘10’”的状态时,如果此时输入是‘1’,则不仅状态会跳转到“检测成功”状态,输出也会立即产生一个有效的脉冲。这个输出脉冲的产生,直接依赖于当前状态(已接收10)和当前的输入(1)。

为什么这个区别至关重要?

  1. 时序路径:米利机的输出路径短,因为它不经过状态寄存器,直接从输入和当前状态经过组合逻辑产生。这可能导致输出出现毛刺,因为输入信号可能是异步的、不稳定的。摩尔机的输出由于经过了状态寄存器同步,输出更干净,与时钟沿对齐,但会慢一个时钟周期。
  2. 代码风格与综合:在Verilog中,两种状态机的写法结构有差异。错误地将米利机输出写成摩尔机形式,可能导致功能错误;反之,则可能引入不必要的时序问题。

我的经验之谈:在复杂的FPGA或ASIC设计中,我强烈推荐优先使用摩尔型状态机。虽然它响应慢一拍,但其输出是寄存器输出的,无毛刺,时序更容易满足,对静态时序分析(STA)友好。米利机除非在那些对响应延迟极其苛刻(且输入信号质量很高)的场景,否则尽量用摩尔机替代。例如,可以将米利机的输出逻辑转化为下一个状态的一部分,在下一个时钟周期输出,本质上就变成了摩尔机。

2.2 状态编码的艺术:二进制、格雷码与独热码

状态本身需要一组二进制码来表示,这就是状态编码。选择哪种编码方式,直接影响电路的面积、速度和功耗。

二进制编码:最紧凑的编码方式。n个状态只需要log2(n)个触发器。例如8个状态只需3个触发器。缺点是状态跳转时,可能有多位同时变化(如从011跳转到100,三位全变),在异步电路中风险高,即使在同步电路中,也可能因触发器偏移导致短暂误状态,增加瞬态功耗。

格雷码:相邻状态间只有一位变化。这大大减少了状态跳变时的开关活动,能有效降低动态功耗,并且对避免亚稳态有一定好处。常用于计数器、低功耗设计以及一些异步状态机接口。

独热码:n个状态就用n个触发器,每个状态只有一位为‘1’,其余为‘0’。例如4个状态,编码为0001,0010,0100,1000。这种编码方式非常“奢侈”,但有其巨大优势:

  • 译码简单:判断当前是什么状态,几乎不需要组合逻辑,直接看哪一位是‘1’即可。输出逻辑也简单。
  • 速度可能更快:因为状态比较逻辑简单,在含有大量状态的复杂状态机中,关键路径可能更短。
  • FPGA友好:FPGA内部有丰富的触发器资源,而组合逻辑资源相对紧张。独热码用触发器换组合逻辑,往往是FPGA设计中的首选。

如何选择?

  • 小型状态机(状态数<8):二进制或格雷码即可,面积最优。
  • 中型到大型状态机(状态数>=8),尤其是在FPGA中强烈建议使用独热码。它能带来更好的时序性能和更简单的代码逻辑。
  • 超低功耗设计:考虑格雷码。
  • 控制密集型设计:独热码的优势明显。

在Verilog中,我们通常用parameterlocalparam来定义这些状态编码,增强代码可读性。

// 独热码示例:一个简单的4状态机(IDLE, START, WORK, DONE) localparam [3:0] S_IDLE = 4'b0001; localparam [3:0] S_START = 4'b0010; localparam [3:0] S_WORK = 4'b0100; localparam [3:0] S_DONE = 4'b1000;

3. Verilog实现状态机的经典范式:一段、二段与三段式

这是Verilog状态机编写的核心方法论。网上常说的“三段式”是最推荐、最安全的结构,但理解其演变过程(一段式、二段式)能让你更清楚为什么它是最佳实践。

3.1 一段式状态机(不推荐)

将所有逻辑(状态转移、状态寄存器更新、输出产生)都写在一个always块中,通常由时钟触发。

always @(posedge clk or negedge rst_n) begin if (!rst_n) begin state <= S_IDLE; out <= 1'b0; end else begin case (state) S_IDLE: begin if (start) begin state <= S_START; out <= 1'b1; // 输出可能随状态立即变化,是米利型输出 end end S_START: begin state <= S_WORK; out <= 1'b0; end // ... 其他状态 default: state <= S_IDLE; endcase end end

问题:混合了时序和组合逻辑,代码冗长,不易维护。输出outS_IDLE状态下直接因start信号变化,这是典型的米利输出,容易产生毛刺。综合工具虽然能处理,但设计师对电路结构的控制力最弱。

3.2 二段式状态机

用两个always块:一个时序逻辑块用于状态寄存器更新,一个组合逻辑块用于计算次态和输出。

// 时序逻辑部分:状态寄存器更新 always @(posedge clk or negedge rst_n) begin if (!rst_n) state_cur <= S_IDLE; else state_cur <= state_next; end // 组合逻辑部分:次态和输出逻辑 always @(*) begin // 默认值,避免锁存器 state_next = state_cur; out = 1'b0; case (state_cur) S_IDLE: begin if (start) begin state_next = S_START; end out = 1'b0; // 摩尔输出,仅依赖state_cur end S_START: begin state_next = S_WORK; out = 1'b1; end // ... default: begin state_next = S_IDLE; out = 1'b0; end endcase end

优点:结构清晰了一些,时序和组合逻辑分离。缺点:输出out仍然在组合逻辑块中产生。如果是摩尔输出,它只依赖于state_cur,但state_cur是寄存器,所以问题不大。但若设计者不小心,仍可能写出依赖于输入的米利输出,导致毛刺。最大的问题是,组合逻辑块容易因case语句分支赋值不全而综合出不希望的锁存器

3.3 三段式状态机(强烈推荐)

这是工业界的主流和最佳实践。使用三个always块,严格分离:次态组合逻辑、状态寄存器时序逻辑、输出时序逻辑。

// 第一段:同步时序逻辑,状态寄存器更新 always @(posedge clk or negedge rst_n) begin if (!rst_n) state_cur <= S_IDLE; else state_cur <= state_next; end // 第二段:组合逻辑,计算次态 always @(*) begin state_next = state_cur; // 默认保持当前状态 case (state_cur) S_IDLE: if (start) state_next = S_START; S_START: state_next = S_WORK; S_WORK: if (work_done) state_next = S_DONE; S_DONE: state_next = S_IDLE; default: state_next = S_IDLE; endcase end // 第三段:同步时序逻辑(或组合逻辑),产生输出 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin out1 <= 1'b0; out2 <= 4'b0; end else begin case (state_next) // 注意!这里常用state_next S_IDLE: begin out1 <= 1'b0; out2 <= 4'h0; end S_START: begin out1 <= 1'b1; out2 <= 4'h1; end // 输出提前一拍准备 S_WORK: begin out1 <= 1'b1; out2 <= data_in; end S_DONE: begin out1 <= 1'b0; out2 <= 4'hF; end default: begin out1 <= 1'b0; out2 <= 4'h0; end endcase end end

三段式的精妙之处

  1. 无毛刺输出:第三段的输出是寄存器输出(always @(posedge clk)),这意味着输出信号干净,与时钟同步,没有毛刺。这是摩尔机的实现。
  2. 输出基于state_next:这是一个关键技巧。输出逻辑根据state_next(下一个状态)来赋值,而不是state_cur。这意味着输出比状态“提前一拍”被确定。当state_cur跳转到S_START时,对应于S_START的输出已经在时钟沿被锁存好了。这有时能更好地对齐输出时序,尤其当输出需要驱动复杂逻辑时。如果希望输出严格与状态同拍,也可以基于state_cur,但基于state_next是更常见的做法。
  3. 安全性:组合逻辑块(第二段)只负责状态转移,功能单一。只要给state_next赋了默认值,就绝不会产生锁存器。第三段是时序逻辑,更安全。
  4. 综合结果最优:这种结构被所有综合工具完美支持,通常会得到面积和速度最优的电路。

实操心得:养成习惯,所有状态机都用三段式写。它就像一套“铠甲”,保护你的设计免受毛刺、锁存器和时序问题的困扰。在第三段输出逻辑中,我强烈建议使用时序逻辑always @(posedge clk)),这是获得稳定输出的基石。基于state_next赋值能让你的输出控制更精准。

4. 从理论到实践:设计一个可综合的SPI主机控制器状态机

让我们用一个实际例子——一个简易的SPI主机控制器——来贯穿上述所有概念。SPI通信需要产生SCK时钟、片选CS、发送数据MOSI,并读取MISO数据。我们将设计一个支持单字节发送/接收的状态机。

4.1 系统需求与状态定义

假设系统时钟clk为50MHz,SPI时钟sck为5MHz。我们需要以下状态:

  • S_IDLE:空闲状态,CS拉高,SCK为初始电平(假设CPOL=0,则SCK空闲为低)。
  • S_START:启动传输,拉低CS,准备第一个数据位。
  • S_SHIFT:移位状态。在这个状态,我们需要产生SCK时钟边沿,并在正确的边沿移位数据。由于SCK频率较低,我们需要一个计数器来分频。
  • S_STOP:传输结束,拉高CS,恢复空闲。

我们选择独热码编码,因为状态数不多但逻辑清晰。

localparam [3:0] S_IDLE = 4'b0001; localparam [3:0] S_START = 4'b0010; localparam [3:0] S_SHIFT = 4'b0100; localparam [3:0] S_STOP = 4'b1000;

4.2 模块接口与内部信号定义

module spi_master_fsm ( input wire clk, // 50MHz 系统时钟 input wire rst_n, // 低电平复位 input wire start, // 启动传输脉冲 input wire [7:0] tx_data, // 待发送数据 output reg [7:0] rx_data, // 接收到的数据 output reg sck, // SPI 时钟 output reg cs_n, // SPI 片选,低有效 output reg mosi, // SPI 主机输出 input wire miso // SPI 主机输入 ); // 状态寄存器定义 reg [3:0] state_cur, state_next; // 移位寄存器与位计数器 reg [7:0] tx_shift_reg, rx_shift_reg; reg [2:0] bit_cnt; // 计数0-7,共8位 reg [3:0] div_cnt; // 分频计数器,50M/5M/2 = 5,需要计数到4 // 常量定义 localparam DIV_MAX = 4; // 分频比-1

关键点

  • tx_shift_regrx_shift_reg用于在S_SHIFT状态进行移位操作。
  • bit_cnt记录已传输的位数,8位满则结束。
  • div_cnt用于从50MHz系统时钟产生5MHz的SCK。因为SCK一个周期需要10个系统时钟(50M/5M),每个半周期(边沿)需要5个时钟。我们将在S_SHIFT状态用这个计数器来翻转sck

4.3 三段式状态机完整实现

第一段:状态寄存器

always @(posedge clk or negedge rst_n) begin if (!rst_n) state_cur <= S_IDLE; else state_cur <= state_next; end

第二段:次态组合逻辑这是状态机的核心决策逻辑。

always @(*) begin state_next = state_cur; // 默认保持原状态 case (state_cur) S_IDLE: begin if (start) // 收到启动信号 state_next = S_START; end S_START: begin // 启动状态只保持一个周期,进入移位状态 state_next = S_SHIFT; end S_SHIFT: begin // 当8位数据都移完,且分频计数器在一个周期结束时,跳出 if ((bit_cnt == 3'd7) && (div_cnt == DIV_MAX)) state_next = S_STOP; end S_STOP: begin // 停止状态保持一个周期,回到空闲 state_next = S_IDLE; end default: state_next = S_IDLE; endcase end

第三段:输出与时序逻辑这是最复杂的一段,包含了输出生成和内部计数器逻辑。

always @(posedge clk or negedge rst_n) begin if (!rst_n) begin sck <= 1'b0; // CPOL=0,空闲低电平 cs_n <= 1'b1; // 空闲时片选无效 mosi <= 1'b0; tx_shift_reg <= 8'h00; rx_shift_reg <= 8'h00; rx_data <= 8'h00; bit_cnt <= 3'd0; div_cnt <= 4'd0; end else begin case (state_next) // 注意是state_next S_IDLE: begin cs_n <= 1'b1; sck <= 1'b0; // 保持空闲电平 bit_cnt <= 3'd0; div_cnt <= 4'd0; // 可以在这里锁存发送数据 if (start) tx_shift_reg <= tx_data; end S_START: begin cs_n <= 1'b0; // 拉低片选,启动传输 // 其他信号保持或初始化 end S_SHIFT: begin cs_n <= 1'b0; // --- 分频计数器逻辑 --- if (div_cnt == DIV_MAX) begin div_cnt <= 4'd0; sck <= ~sck; // 翻转SCK,产生边沿 end else begin div_cnt <= div_cnt + 1; end // --- 在SCK的特定边沿进行数据移位 --- // 假设CPHA=0,数据在第一个边沿(上升沿)变化,在第二个边沿(下降沿)采样 // 我们这里在sck的下降沿(即div_cnt计数到一半时)改变MOSI,在上升沿采样MISO if (div_cnt == (DIV_MAX/2)) begin // 下降沿附近 // 准备下一位MOSI数据(最高位先发) mosi <= tx_shift_reg[7]; // 左移,为下一位做准备 tx_shift_reg <= {tx_shift_reg[6:0], 1'b0}; end if (div_cnt == 4'd0 && sck == 1'b1) begin // 上升沿时刻 // 采样MISO数据 rx_shift_reg <= {rx_shift_reg[6:0], miso}; bit_cnt <= bit_cnt + 1; // 增加位计数 end end S_STOP: begin cs_n <= 1'b1; // 拉高片选,结束传输 sck <= 1'b0; // 恢复空闲电平 // 将接收到的移位寄存器值赋给输出寄存器 rx_data <= rx_shift_reg; end default: begin // 安全默认值 cs_n <= 1'b1; sck <= 1'b0; end endcase end end

这段代码的详细解读

  1. 复位:所有信号初始化为确定值。
  2. S_IDLE:等待start信号。当start有效时,锁存tx_data到发送移位寄存器。这是一个很好的数据缓冲点。
  3. S_START:拉低cs_n。这个状态通常只持续一个时钟周期,用于产生一个干净的片选下降沿。
  4. S_SHIFT:核心工作状态。
    • div_cnt循环计数,用于产生SCK。DIV_MAX=4,计数0,1,2,3,4,然后归零并翻转sck。这样sck的半周期是5个clk,全周期10个clk,即5MHz。
    • 数据移位时机是关键。我们模拟CPHA=0模式:
      • div_cnt计数到一半(DIV_MAX/2)时,此时sck即将或刚变为低电平(下降沿),我们更新mosi为要发送位的值(tx_shift_reg[7]),然后移位寄存器左移。
      • div_cnt归零且sck为高电平时(上升沿),我们采样miso数据到接收移位寄存器,并递增bit_cnt
    • bit_cnt用于跟踪已传输的位数,达到7时表示最后一位正在传输。
  5. S_STOP:传输结束,拉高cs_n,将接收到的完整字节rx_shift_reg赋值给输出端口rx_data。这个状态也通常只维持一个周期。

注意事项:SPI的时钟极性(CPOL)和相位(CPHA)有四种模式。上述代码实现了模式0(CPOL=0, CPHA=0)。如果要支持其他模式,需要调整sck的初始值和数据移位的边沿。最好的做法是将CPOL和CPHA作为模块参数(parameter),然后在代码中用if-elsecase语句根据参数选择不同的边沿控制逻辑。

5. 状态机设计的进阶技巧与常见陷阱

掌握了基本写法,我们还需要关注那些让状态机更稳健、更高效的细节。

5.1 状态机初始化与“死机”恢复

一个健壮的状态机必须能从上电或异常中恢复。

  • 复位信号:必须对所有状态寄存器、内部计数器和输出寄存器进行复位。异步复位同步释放是常用技巧。
  • default分支:在第二段的case (state_cur)语句中,务必添加default: state_next = S_IDLE;。这能确保综合工具不会生成锁存器,并且当状态机因干扰进入非法状态(如独热码变成了4‘b0011)时,能在下一个时钟周期回到初始状态。对于独热码,非法状态很多,这个default分支是安全的保障。
  • 看门狗:对于极其关键的状态机,可以添加一个超时计数器。如果在一个状态停留时间远超预期,强制跳回S_IDLE

5.2 输出寄存与消除毛刺

这是三段式状态机最大的优势之一。第三段的输出是寄存器输出,这意味着:

  • 无毛刺:输出信号的变化只发生在时钟边沿,消除了组合逻辑产生的毛刺。毛刺在高速系统中是致命的,可能导致后续电路误动作。
  • 时序可预测:输出延迟是固定的(一个时钟周期+寄存器Tco),便于系统级时序分析。
  • 如果输出需要立即响应输入(米利特性)怎么办?尽量避免。如果必须,可以单独为该输出设计一个短路径的组合逻辑,但必须清楚其风险。更好的方法是重新设计状态,将输入的影响纳入状态跳转条件,在下一个周期输出。

5.3 状态转移图与代码的对应关系

在动手写代码前,先用纸笔画一个状态转移图。圆圈代表状态,箭头代表转移条件和输出。这个图是你的设计蓝图。

  • 工具:可以用Visio、Draw.io,甚至白纸手绘。
  • 检查:画完后,检查是否所有状态都有出路?是否存在无法进入的“孤岛”状态?复位状态是否明确?
  • 对应:状态转移图中的每一个箭头,都对应第二段case语句里的一个if条件。图中的输出标注,对应第三段case语句里的赋值。做到图码一致,是调试的基石。

5.4 仿真与调试技巧

写状态机不难,难的是验证它是否正确。

  • 仿真波形:在仿真工具(如ModelSim, VCS)中,将state_curstate_next、关键输入输出、内部计数器(如bit_cnt,div_cnt)都添加到波形窗口。
  • 查看状态跳转:确保状态跳转与设计意图一致,特别是在边界条件下(如start脉冲过短、过长、与时钟不同步)。
  • 检查输出时序:确认mosi数据是否在正确的SCK边沿变化,miso是否在正确的边沿被采样。检查cs_n的建立和保持时间。
  • 编写全面的测试平台:测试平台应覆盖所有状态转移路径,包括正常流程和异常情况(如复位发生在传输中、连续启动等)。

6. 综合与实现中的实际问题

代码写得好,还要综合工具“认得准”。

6.1 综合属性与指令

有时需要指导综合工具如何优化状态机。

  • full_caseparallel_case:在Verilog中,case语句如果不完整,会综合出锁存器。我们已经通过赋默认值避免了。full_case指令告诉工具所有情况已覆盖,parallel_case指令告诉工具分支是互斥的。但谨慎使用!在Synopsys DC中,这些指令会影响优化。在FPGA综合中(如Vivado, Quartus),现代工具很智能,通常不需要。滥用可能导致功能错误。
  • 状态编码约束:如果你想强制使用独热码,可以在综合工具中设置。例如在Xilinx Vivado的XDC文件中:set_property FSM_ENCODING "one_hot" [get_cells -hierarchical *state_cur_reg*]。在代码中用(* fsm_encoding = "one_hot" *)reg [3:0] state_cur; 这样的属性语句也可以(工具支持的话)。

6.2 面积与速度的权衡

  • 独热码 vs 二进制码:独热码用更多的触发器换更简单的组合逻辑。在FPGA中,触发器资源丰富,这通常是好交易。在ASIC中,需要更仔细地评估。工具的综合报告会告诉你用了多少LUTs和FFs。
  • 输出逻辑优化:第三段的输出逻辑如果很复杂,可能会成为关键路径。如果时序紧张,可以考虑对输出进行流水线打拍,或者将部分输出逻辑计算提前到第二段(组合逻辑部分),但要注意这可能会将毛刺引入计算路径。

6.3 跨时钟域与亚稳态处理

如果状态机的输入信号来自另一个时钟域(比如start信号来自一个更慢的按钮时钟),直接使用是危险的,会导致亚稳态。

  • 同步器:必须使用两级或多级触发器进行同步。
    reg start_sync1, start_sync2; always @(posedge clk or negedge rst_n) begin if (!rst_n) {start_sync2, start_sync1} <= 2'b00; else {start_sync2, start_sync1} <= {start_sync1, start_async}; end // 然后使用 start_sync2 作为状态机的输入
  • 边沿检测:对于脉冲信号,同步后还需要进行边沿检测,确保每个启动脉冲只被捕获一次。
    reg start_sync2_dly; always @(posedge clk) start_sync2_dly <= start_sync2; wire start_pulse = start_sync2 & ~start_sync2_dly; // 上升沿检测

7. 状态机设计模式与复杂场景应对

简单的线性状态机容易,但现实工程中状态机往往更复杂。

7.1 层次化状态机

当状态太多时,一个大的状态机会变得难以维护。可以采用层次化设计,即“大状态机”包含“小状态机”。

  • 主从状态机:一个主状态机(如S_IDLE,S_TX_CMD,S_TX_DATA,S_RX_DATA)控制高层流程。在每个主状态下,可以激活一个独立的子状态机(如S_TX_DATA状态下,运行一个具体的字节发送状态机)。子状态机完成工作后,给主状态机一个完成信号。
  • 优点:结构清晰,模块化,易于调试和复用。

7.2 超时与错误处理机制

健壮的状态机必须处理异常。

  • 超时计数器:在可能“卡住”的状态(如S_SHIFT等待外部响应),添加一个计数器。如果超时,则跳转到错误处理状态(如S_ERRORS_IDLE),并拉高一个错误标志error_flag
  • 错误状态:设计一个专门的S_ERROR状态。进入该状态后,可以尝试恢复操作(如重试几次),或者通知上层系统,并等待复位。

7.3 状态机与数据路径的分离

这是一个重要的设计哲学:控制流与数据流分离

  • 控制流:由状态机实现,负责产生控制信号(如shift_en,load_en,cnt_en)。
  • 数据路径:由独立的模块或逻辑实现,如移位寄存器、计数器、FIFO等。它们受状态机产生的控制信号指挥。
  • 好处:状态机变得简洁,只关注状态跳转条件;数据路径可以独立优化和验证。例如,上面的SPI例子中,tx_shift_regrx_shift_reg的移位操作,就是在状态机产生的shift_en(隐含在div_cnt条件中)控制下进行的。更清晰的写法是状态机输出明确的shift_ensample_en信号。

最后,记住状态机设计是一个迭代过程:设计 -> 画图 -> 编码 -> 仿真 -> 调试 -> 优化。从简单的摩尔型三段式开始,严格遵守寄存器输出原则,处理好复位和默认情况,你的状态机就成功了一大半。在后续的实践中,你会遇到更复杂的场景,但万变不离其宗,理解状态、转移、输出这三个核心要素,你就能驾驭任何复杂的时序逻辑设计。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询