当前位置: 首页 > news >正文

第四课:时序逻辑进阶 - 有限状态机(FSM)设计

🎓 第四课:时序逻辑进阶 - 有限状态机(FSM)设计

上节回顾:我们学会了用if/else和case实现组合逻辑判断。这节课学习状态机——硬件设计中最重要的思想,像给电路"编排剧本"!


🎭 4.1 什么是状态机?

🎬 生活比喻:自动售货机

想象你在用自动售货机买饮料(2元):

[等待投币] 状态↓ 投入1元
[已投1元] 状态  ↓ 再投1元
[已投2元] 状态↓ 自动出货
[出货中] 状态↓ 完成
回到 [等待投币]

关键特征

  1. 有明确的状态(等待、已投1元、已投2元、出货)
  2. 状态之间有转换条件(投币、取货)
  3. 每个状态有对应动作(显示金额、出货)

💡 硬件类比

  • 状态 = 寄存器存储的数值(电路"记住"当前在哪个阶段)
  • 转换条件 = 输入信号(什么情况下切换到下一个阶段)
  • 动作 = 输出信号(当前阶段应该输出什么)

🔑 核心概念理解

状态机就是一个"记忆+判断"的电路

  1. 记忆:用寄存器记住"现在处于哪个状态"
  2. 判断:根据当前状态和输入,决定"下一步去哪个状态"
  3. 动作:根据状态,输出对应的控制信号

为什么叫"有限"状态机?

  • 因为状态的数量是有限的(如售货机只有4个状态)
  • 不像计数器可以数到无穷大

📊 4.2 状态机的两大类型

🔷 Moore型状态机

核心特点:输出只看当前状态,不管输入是什么

生活比喻:像"背台词的演员"

  • 每个状态有固定的台词(输出)
  • 不管观众(输入)怎么反应,该说的话不变
  • 例:红绿灯到了红灯状态,就一定输出"停车",不管有没有车

状态图示例

     [红灯]───────→[绿灯]───────→[黄灯]↑ 输出:停车    输出:通行     输出:减速 ↓└──────────────────────────────────┘每个状态的输出是固定的

优点

  • ✅ 逻辑清晰,每个状态"该干啥就干啥"
  • ✅ 输出稳定,不会因为输入抖动而变化
  • ✅ 适合初学者理解

缺点

  • ❌ 响应慢1拍(输出要等到下个时钟周期才变)

🔶 Mealy型状态机

核心特点:输出既看状态又看输入

生活比喻:像"即兴演员"

  • 根据观众反应(输入)调整台词(输出)
  • 同一状态下,不同输入产生不同输出
  • 例:等待状态时,投1元说"已投1元",投2元说"出货中"

状态图示例

     [等待]↓ 投币1元 → 输出:"已投1元,请继续"↓ 投币2元 → 输出:"金额足够,出货中"[已投1元]↓ 投币1元 → 输出:"金额足够,出货中"↓ 投币2元 → 输出:"金额超过,找零1元"[出货]同一状态,不同输入有不同输出

优点

  • ✅ 响应快,输出立即跟随输入变化
  • ✅ 状态数量可能更少(一个状态可以处理多种情况)

缺点

  • ❌ 逻辑复杂,容易出错
  • ❌ 输出可能不稳定(输入抖动会影响输出)

📋 两者对比表

对比项Moore型Mealy型
输出依赖只看当前状态状态+输入
输出时机状态稳定后才输出输入变化立即影响输出
输出延迟慢1个时钟周期即时响应(组合逻辑延迟)
电路复杂度简单,易于理解稍复杂,需要更多组合逻辑
输出稳定性稳定,不受输入抖动影响可能不稳定,需要考虑毛刺
状态数量可能需要更多状态状态数量可能更少
适用场景固定时序控制,稳定输出需要快速响应,输入驱动输出
典型例子交通灯、洗衣机、电梯串口接收、握手协议、按键检测

💡 初学者建议:先学Moore型,逻辑更清晰!本课主要讲Moore型


🎯 例题7:简易交通灯(Moore型)

📐 需求分析

功能描述

  • 3个状态循环:绿灯(5秒) → 黄灯(2秒) → 红灯(5秒)
  • 每个状态显示对应的LED灯
  • 这是典型的Moore型:每个状态输出固定,不管输入

状态转换图

          计时5秒到┌─────────────────┐↓                 │[绿灯]            [红灯]输出:001          输出:100│ 计时2秒到        ↑↓                 │ 计时5秒到[黄灯]──────────────┘输出:010注意:每个状态的输出是固定的(Moore型特征)

📝 代码实现(三段式写法)

🔧 为什么用"三段式"?

三段式状态机是业界标准写法,把逻辑分成3个独立的always块:

段落负责内容逻辑类型优点
第1段状态转移(状态寄存器更新)时序逻辑清晰区分时序/组合
第2段下一状态判断(状态转移条件)组合逻辑避免锁存器
第3段输出控制(根据当前状态输出)组合逻辑便于修改输出

为什么要分开写?

  1. 职责单一:每个always块只做一件事,不容易出错
  2. 易于调试:哪里有问题一眼就能看出来
  3. 避免陷阱:组合逻辑和时序逻辑混在一起容易生成锁存器

完整代码
// ============================================================
// 模块名称: traffic_light (交通灯控制器)
// 功能描述: 实现绿(5秒)->黄(2秒)->红(5秒)的循环控制
// 作者提示: 这是Moore型状态机的标准三段式写法
// ============================================================
module traffic_light(input wire clk,           // 🕐 系统时钟(假设1Hz,即每秒触发一次)input wire rst_n,         // 🔄 复位信号(低电平有效,按下复位按钮时为0)output reg [2:0] led      // 💡 3位LED输出 [红,黄,绿],如100表示红灯亮
);// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 📊 第一步:定义状态编码// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 使用独热码(One-Hot)编码,每个状态只有1位是1// 优点:仿真时一眼看出当前状态,逻辑简单// 缺点:比二进制编码多用寄存器(但现在FPGA资源充足,不是问题)localparam GREEN  = 3'b001;  // 绿灯状态: bit0=1表示绿灯localparam YELLOW = 3'b010;  // 黄灯状态: bit1=1表示黄灯localparam RED    = 3'b100;  // 红灯状态: bit2=1表示红灯// localparam是局部参数,类似C语言的#define,但有类型检查// 用大写字母表示这是常量// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 📊 第二步:定义计时参数// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━localparam GREEN_TIME  = 5;   // 绿灯持续5秒localparam YELLOW_TIME = 2;   // 黄灯持续2秒localparam RED_TIME    = 5;   // 红灯持续5秒// 把时间定义成参数的好处:// 1. 修改时间只需改一个地方// 2. 代码可读性强,看到GREEN_TIME比看到数字5更清楚// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 📊 第三步:定义状态寄存器和计时器// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━reg [2:0] current_state;      // 当前状态寄存器(3位,存储当前在哪个状态)reg [2:0] next_state;         // 下一状态寄存器(3位,提前算好下一步去哪)// 为什么要分current和next?// - current是时序逻辑,在时钟沿更新,代表"现在"// - next是组合逻辑,随时计算,代表"将来"// 这样分开写清晰,不会混淆reg [3:0] counter;            // 计时器(4位,最大可以数到15)// 为什么4位够用?因为最长时间是5秒,0~4共5个数,4位(0~15)足够//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 🔹 第1段:状态转移 - 时序逻辑(有时钟)// 功能:在每个时钟上升沿更新状态和计时器// 重点:这里用 <= 非阻塞赋值(时序逻辑专用)//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━always @(posedge clk or negedge rst_n) begin// 敏感列表解读:// posedge clk: 时钟上升沿触发(0→1的瞬间)// negedge rst_n: 复位下降沿触发(1→0的瞬间,按下复位按钮)if (!rst_n) begin// ═══ 复位逻辑:按下复位按钮时的初始化 ═══current_state <= GREEN;   // 复位到绿灯状态(交通灯启动先是绿灯)counter <= 0;             // 计时器清零// 注意:这里不需要初始化next_state,因为它是组合逻辑算出来的end else begin// ═══ 正常工作逻辑:每个时钟周期执行 ═══// 步骤1:状态更新(把提前算好的next_state赋给current_state)current_state <= next_state;  // 这就是状态转移!电路"跳"到了下一个状态// 步骤2:计时器更新if (current_state != next_state) begin// 判断:如果状态发生了切换counter <= 0;              // 计时器清零,重新开始计时// 比如从绿灯切换到黄灯,黄灯的2秒要从0开始数end else begin// 判断:如果状态没变(还在同一个状态)counter <= counter + 1;    // 计时器累加1// 比如绿灯状态,counter会从0数到4(共5个时钟)endendend// 第1段总结:这段代码只负责"更新",不负责"判断"// 判断逻辑在第2段!//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 🔹 第2段:下一状态判断 - 组合逻辑(无时钟)// 功能:根据当前状态和计时器,判断下一步该去哪个状态// 重点:这里用 = 阻塞赋值(组合逻辑专用)//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━always @(*) begin// 敏感列表解读:// @(*) 表示对所有输入信号敏感,任何输入变化都会触发重新计算// 这是组合逻辑的标准写法// ⚡ 关键技巧:先给默认值,避免生成锁存器!next_state = current_state;  // 默认保持当前状态不变,如果下面的条件都不满足,就维持现状// 这是组合逻辑设计的黄金法则!// 根据当前状态,判断是否需要转移case(current_state)// ═══ 绿灯状态的判断 ═══GREEN: begin// 判断:绿灯时间(5秒)是否到了?if (counter >= GREEN_TIME - 1) begin// 为什么是GREEN_TIME-1?// 因为counter从0开始:0,1,2,3,4 共5个数// 当counter=4时,已经是第5秒了,该切换了next_state = YELLOW;  // 时间到了,下一步去黄灯end// 如果时间没到,保持默认值(留在绿灯)end// ═══ 黄灯状态的判断 ═══YELLOW: beginif (counter >= YELLOW_TIME - 1) begin// 黄灯2秒到了(counter=0,1,当=1时是第2秒)next_state = RED;     // 下一步去红灯endend// ═══ 红灯状态的判断 ═══RED: beginif (counter >= RED_TIME - 1) begin// 红灯5秒到了next_state = GREEN;   // 循环回绿灯endend// ═══ 异常处理 ═══default: next_state = GREEN;  // 如果current_state是未定义的值(比如上电时的X态)// 强制跳转到绿灯,保证电路不会卡死endcaseend// 第2段总结:这段代码只负责"判断",不负责"更新"// 它会提前算好next_state,等待第1段在时钟沿更新//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 🔹 第3段:输出控制 - 组合逻辑(无时钟)// 功能:根据当前状态,决定LED的输出// 重点:Moore型状态机的特征 - 输出只看状态!//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━always @(*) begin// 根据当前状态,直接决定输出// 注意:这里不关心输入,也不关心计时器,只看current_state// 这就是Moore型状态机的精髓!case(current_state)GREEN:  led = 3'b001;  // 绿灯状态 → bit0=1,绿灯亮YELLOW: led = 3'b010;  // 黄灯状态 → bit1=1,黄灯亮RED:    led = 3'b100;  // 红灯状态 → bit2=1,红灯亮default:led = 3'b000;  // 异常状态 → 全灭(保护措施)// 如果状态是未知值,所有灯都不亮,避免危险endcaseend// 第3段总结:输出简单明了,一个状态对应一个输出// 不会受到输入抖动的影响,输出稳定endmodule
// ============================================================
// 模块结束
// 三段式状态机完成!回顾三段的职责:
// 第1段:时序逻辑,负责状态更新(current_state = next_state)
// 第2段:组合逻辑,负责判断下一状态(计算next_state)
// 第3段:组合逻辑,负责输出(根据current_state输出)
// ============================================================

🔍 关键代码解析

1️⃣ 状态编码技巧详解
localparam GREEN = 3'b001;  // 独热码↑        ↑常量参数   只有1位是1// 读法:3位二进制数001
// 含义:bit[2]=0(红灯灭), bit[1]=0(黄灯灭), bit[0]=1(绿灯亮)

编码方式对比

编码方式编码示例优点缺点适用场景
二进制00,01,10,11节省寄存器译码复杂,不易调试状态多,资源紧张
格雷码00,01,11,10减少毛刺,功耗低编码规则复杂高速电路
独热码001,010,100译码简单,易调试浪费寄存器初学者,调试阶段

独热码的妙处

// 判断当前是否绿灯状态,只需检查1位
if (current_state[0]) begin  // 如果bit0=1,就是绿灯// ...
end// 而二进制编码需要比较完整的值
if (current_state == 2'b00) begin  // 需要比较2位// ...
end

💡 建议:初学者用独热码,仿真时一眼看出当前状态!等熟练后可以考虑二进制编码节省资源


2️⃣ 计时器边界条件详解
if (counter >= GREEN_TIME - 1) begin↑为什么要-1?

原因:计数器从0开始,数5个数是0~4

时间轴: 0秒  1秒  2秒  3秒  4秒  (共5秒)
计数值:  0 →  1 →  2 →  3 →  4
时钟沿: ↑    ↑    ↑    ↑    ↑第1个 第2个 第3个 第4个 第5个时钟当counter=4时,这是第5个时钟到来,已经等了5秒!
所以判断条件是: counter >= 5-1 (即>=4)

错误示范

// ❌ 错误写法
if (counter >= GREEN_TIME) begin  // 这样会等6秒!// 因为counter会数到0,1,2,3,4,5才满足条件
end// ✅ 正确写法
if (counter >= GREEN_TIME - 1) begin  // 等5秒// counter数到0,1,2,3,4就满足条件
end

3️⃣ 默认赋值防锁存器详解
always @(*) beginnext_state = current_state;  // ✅ 第一步:必须先给默认值!case(current_state)GREEN: if (counter >= 4) next_state = YELLOW;// 如果counter<4,不满足if条件// 但因为有默认值,next_state保持=current_state// 不会生成锁存器!endcase
end

为什么不给默认值会出问题?

// ❌ 错误示范:没有默认值
always @(*) begincase(current_state)GREEN: if (counter >= 4) next_state = YELLOW;// 如果是GREEN状态,但counter<4呢?// next_state没有被赋值!// 综合器会生成锁存器,保持上一次的值// 锁存器在FPGA中是不稳定的,容易出错!endcase
end

锁存器是什么?为什么要避免?

  • 锁存器(Latch):电平触发的存储元件,只要使能信号有效就透明传输

  • 触发器(Flip-Flop):边沿触发的存储元件,只在时钟沿更新

  • 为什么避免锁存器

    1. 时序难以控制,容易产生毛刺
    2. FPGA中锁存器资源少,性能差
    3. 违反同步设计原则,增加调试难度

记忆口诀

组合逻辑写always,
第一行必给默认值,
避免锁存保平安!

🧪 仿真测试代码

// ============================================================
// 测试模块名称: tb_traffic_light (testbench = 测试平台)
// 功能:验证交通灯模块的功能是否正确
// 测试要点:
//   1. 复位功能是否正常
//   2. 状态转换顺序是否正确(绿→黄→红→绿)
//   3. 每个状态持续时间是否准确
//   4. LED输出是否与状态对应
// ============================================================
module tb_traffic_light;// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 📊 第一步:定义测试信号// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 输入信号用reg类型(因为测试时我们要给它赋值)reg clk;              // 时钟信号(测试时我们自己生成)reg rst_n;            // 复位信号(测试时我们控制)// 输出信号用wire类型(因为它是被测模块输出的,我们只观察)wire [2:0] led;       // LED输出// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 📊 第二步:信号解码(方便观察)// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 把3位LED拆分成单独的信号,仿真时更直观wire green_on  = led[0];  // 绿灯是否亮(led[0]=1表示亮)wire yellow_on = led[1];  // 黄灯是否亮wire red_on    = led[2];  // 红灯是否亮// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 📊 第三步:实例化被测模块// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━traffic_light uut(      // uut = Unit Under Test(被测单元).clk(clk),          // 把测试的clk信号连接到模块的clk端口.rst_n(rst_n),      // 把测试的rst_n信号连接到模块的rst_n端口.led(led)           // 把模块的led输出连接到测试的led信号);// 这就像把芯片插到测试板上,连接好所有引脚// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 📊 第四步:生成时钟信号// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 时钟初始化:开始时设为0initial clk = 0;// 时钟翻转:每500ms翻转一次 → 周期=1000ms = 1秒(1Hz)always #500 clk = ~clk;  // 解读:// #500 表示延迟500个时间单位(这里是ms)// ~clk 表示对clk取反(0→1或1→0)// 整体效果:每500ms翻转一次,形成1Hz的方波/*时钟波形示意:时间:  0ms   500ms  1000ms 1500ms 2000msclk:   0  →   1  →   0  →   1  →   0└─500ms─┘└─500ms─┘周期 = 1000ms = 1秒*/// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 📊 第五步:测试流程控制// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━initial begin// 打印表头,方便观察结果$display("============================================");$display("   交通灯仿真测试开始");$display("============================================");$display("时间(s) | 状态  | 红 黄 绿 | 计数器");$display("--------|-------|----------|--------");// ═══ 步骤1:复位测试 ═══rst_n = 0;        // 拉低复位信号(按下复位按钮)#1000;            // 保持1秒(1000ms)// 这段时间观察:模块应该进入初始状态(绿灯)rst_n = 1;        // 释放复位信号(松开复位按钮)$display("复位完成,开始正常工作...");$display("--------|-------|----------|--------");// ═══ 步骤2:观察状态循环 ═══// 观察2个完整周期:(5+2+5)*2 = 24秒repeat(24) begin  @(posedge clk);  // 等待时钟上升沿// 在每个时钟上升沿打印当前状态$display(" %2d     | %b  |  %b  %b  %b  |   %d", $time/1000,              // 当前时间(秒)uut.current_state,       // 当前状态编码red_on, yellow_on, green_on,  // 三个灯的状态uut.counter);            // 内部计数器的值// 解读:// %2d: 打印2位十进制数// %b:  打印二进制数// uut.current_state: 访问被测模块内部的信号end// ═══ 步骤3:测试结束 ═══$display("============================================");$display("   测试完成!");$display("============================================");$finish;  // 结束仿真end// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 📊 第六步:波形记录(用于GTKWave查看)// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━initial begin$dumpfile("traffic_light.vcd");  // 波形文件名$dumpvars(0, tb_traffic_light);  // 记录本模块及子模块所有信号// 0表示记录所有层次// tb_traffic_light是起始模块end// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 📊 第七步:自动检查(可选)// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 实时监控:如果LED输出异常就报警always @(led) begin// 检查是否只有一个灯亮(独热码检查)if ((led != 3'b001) && (led != 3'b010) && (led != 3'b100)) begin$display("❌ 错误!时间=%0t, LED输出异常: %b", $time, led);// 异常情况:多个灯同时亮,或全灭endendendmodule
// ============================================================
// 测试模块结束
// 使用方法:
//   1. 编译:iverilog -o sim traffic_light.v tb_traffic_light.v
//   2. 运行:vvp sim
//   3. 查看波形:gtkwave traffic_light.vcd
// ============================================================

📊 预期输出示例

============================================交通灯仿真测试开始
============================================
时间(s) | 状态  | 红 黄 绿 | 计数器
--------|-------|----------|--------
复位完成,开始正常工作...
--------|-------|----------|--------1      | 001  |  0  0  1  |   0    ← 绿灯开始,计数器从0开始2      | 001  |  0  0  1  |   1    ← 绿灯持续,计数器+13      | 001  |  0  0  1  |   24      | 001  |  0  0  1  |   35      | 001  |  0  0  1  |   4    ← 计数到4(第5秒)6      | 010  |  0  1  0  |   0    ← 切换到黄灯,计数器清零7      | 010  |  0  1  0  |   1    ← 黄灯持续8      | 100  |  1  0  0  |   0    ← 切换到红灯,计数器清零9      | 100  |  1  0  0  |   1    ← 红灯持续10     | 100  |  1  0  0  |   211     | 100  |  1  0  0  |   312     | 100  |  1  0  0  |   4    ← 计数到4(第5秒)13     | 001  |  0  0  1  |   0    ← 循环回绿灯14     | 001  |  0  0  1  |   1    ← 第二个周期开始...
============================================测试完成!
============================================

观察要点

  1. ✅ 状态按 001→010→100→001 循环
  2. ✅ 绿灯持续5秒(counter: 0→4)
  3. ✅ 黄灯持续2秒(counter: 0→1)
  4. ✅ 红灯持续5秒(counter: 0→4)
  5. ✅ 状态切换时计数器立即清零

⚠️ 状态机设计常见错误及解决

❌ 错误1:忘记默认赋值(最常见!)

// ❌ 错误示范
always @(*) begin// 缺少默认值!case(current_state)GREEN: if (counter >= 4) next_state = YELLOW;// 如果是GREEN但counter<4,next_state未赋值 → 生成锁存器!endcase
end// 综合后会报警告:
// Warning: Latch inferred for signal 'next_state'

后果

  • 生成不稳定的锁存器
  • 仿真结果可能正常,但实际硬件会出错
  • 时序分析失败

✅ 正确做法

always @(*) beginnext_state = current_state;  // ✅ 第一行就给默认值!case(current_state)GREEN: if (counter >= 4) next_state = YELLOW;// 即使if不满足,next_state也有值(保持当前状态)endcase
end

❌ 错误2:状态编码冲突

// ❌ 错误示范
localparam S0 = 2'b00;
localparam S1 = 2'b01;
localparam S2 = 2'b01;  // ❌ 与S1相同!复制粘贴的坑
localparam S3 = 2'b10;

后果

  • S1和S2无法区分
  • 状态转移逻辑混乱
  • 难以调试,因为看起来是2个状态,实际是同一个

✅ 正确做法

// 方法1:手动检查,确保每个编码唯一
localparam S0 = 2'b00;
localparam S1 = 2'b01;
localparam S2 = 2'b10;  // ✅ 唯一
localparam S3 = 2'b11;// 方法2:用独热码,每次只移动1位
localparam S0 = 4'b0001;  // 只有bit0是1
localparam S1 = 4'b0010;  // 只有bit1是1
localparam S2 = 4'b0100;  // 只有bit2是1
localparam S3 = 4'b1000;  // 只有bit3是1
// 这样不容易出错,一眼看出不同

❌ 错误3:组合逻辑用非阻塞赋值

// ❌ 错误示范:组合逻辑用了<=
always @(*) beginnext_state <= current_state;  // ❌ 应该用=case(current_state)GREEN: if (...) next_state <= YELLOW;  // ❌ 应该用=endcase
end

后果

  • 综合器会报错或产生意外结果
  • 仿真时可能看起来正常,但综合后不对

✅ 正确做法

// 记忆口诀:
// 时序逻辑(有时钟) → 用 <= (非阻塞赋值)
// 组合逻辑(无时钟) → 用 =  (阻塞赋值)// 时序逻辑示例
always @(posedge clk) begincurrent_state <= next_state;  // ✅ 有时钟,用<=
end// 组合逻辑示例
always @(*) beginnext_state = current_state;   // ✅ 无时钟,用=
end

为什么要这样区分?

  • 非阻塞赋值(<=):所有赋值"同时"生效,适合寄存器
  • 阻塞赋值(=):按顺序立即生效,适合连线

❌ 错误4:计时器边界错误

// ❌ 错误示范1:判断条件错误
if (counter == GREEN_TIME) begin  // ❌ 会多等1秒next_state = YELLOW;
end
// counter会数:0,1,2,3,4,5 才等于5,实际等了6秒!// ❌ 错误示范2:忘记清零
always @(posedge clk) begincurrent_state <= next_state;counter <= counter + 1;  // ❌ 状态切换时没清零
end
// 切换到新状态后,counter继续累加,时间不准确!

✅ 正确做法

// 判断条件要-1
if (counter >= GREEN_TIME - 1) begin  // ✅ 等5秒:0,1,2,3,4next_state = YELLOW;
end// 状态切换时清零
always @(posedge clk) begincurrent_state <= next_state;if (current_state != next_state) begincounter <= 0;  // ✅ 状态变化时清零end else begincounter <= counter + 1;end
end

❌ 错误5:复位信号没处理或处理不当

// ❌ 错误示范1:忘记复位
always @(posedge clk) begin  // ❌ 没有复位信号current_state <= next_state;
end
// 上电时current_state是未知值(X态),电路无法正常工作!// ❌ 错误示范2:只复位部分寄存器
always @(posedge clk or negedge rst_n) beginif (!rst_n) begincurrent_state <= GREEN;// ❌ 忘记复位counter,可能导致时间错乱end else begincurrent_state <= next_state;counter <= counter + 1;end
end

✅ 正确做法

always @(posedge clk or negedge rst_n) beginif (!rst_n) begin// ✅ 复位所有需要初始化的寄存器current_state <= GREEN;   // 复位到初始状态counter <= 0;             // 计数器清零end else begin// 正常工作逻辑current_state <= next_state;if (current_state != next_state) begincounter <= 0;end else begincounter <= counter + 1;endend
end

🎯 例题8:按键消抖状态机(Mealy型)

📚 背景知识:什么是抖动?

物理现象:机械按键按下瞬间会产生多次通断(像弹簧一样弹跳)

理想情况(我们希望的):  
按键: ___┌────────────┐___按下      松开实际情况(会抖动):
按键: ___┌┐┌┐┌────────┐┌┐___抖动  稳定    抖动|←20ms→|

为什么会抖动?

  • 按键是机械触点,按下时金属片会弹跳
  • 弹跳时间通常是5-20ms
  • 如果不处理,会被识别成多次按键

解决方案

  • 检测到按键变化后,等待20ms再确认
  • 如果20ms内一直保持稳定,才认为是真正的按键
  • 这就是"消抖"

📝 代码实现(Mealy型特点)

// ============================================================
// 模块名称: key_debounce (按键消抖器)
// 功能描述: 过滤按键抖动,输出稳定的按键信号
// 类型:Mealy型状态机
// 特点:输出不仅取决于状态,还取决于输入(key_in)
// ============================================================
module key_debounce(input wire clk,         // 1kHz时钟(每1ms触发一次)input wire rst_n,       // 复位信号(低电平有效)input wire key_in,      // 🔘 原始按键输入(按下=0,松开=1,低电平有效)output reg key_out      // ✅ 消抖后的稳定输出(按下=0,松开=1)
);// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 📊 第一步:状态定义// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━localparam IDLE    = 2'b00;  // 空闲状态:等待按键按下localparam FILTER  = 2'b01;  // 滤波状态:正在等待20ms确认localparam PRESSED = 2'b10;  // 按下状态:已确认按键按下// 只需要3个状态:// IDLE: 按键松开,等待// FILTER: 检测到可能的按键,正在确认// PRESSED: 确认按键按下,等待松开reg [1:0] state;             // 当前状态寄存器(2位够存储3个状态)reg [4:0] cnt;               // 5位计数器(最大32,20ms够用:0~19)// 为什么用5位?2^5=32 > 20,足够了// 如果用4位,2^4=16 < 20,不够// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 📊 第二步:状态转移 + 输出控制(Mealy型合并写法)// 注意:Mealy型可以把状态转移和输出写在一起// 因为输出依赖输入,在状态转移时就能确定输出// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━always @(posedge clk or negedge rst_n) beginif (!rst_n) begin// ═══ 复位逻辑 ═══state <= IDLE;       // 复位到空闲状态cnt <= 0;            // 计数器清零key_out <= 1'b1;     // 输出默认高电平(按键未按下)end else begin// ═══ 正常工作逻辑:根据当前状态和输入决定下一步 ═══case(state)// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 状态1:IDLE - 空闲状态// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━IDLE: beginkey_out <= 1'b1;  // 输出:未按下(高电平)// 检查输入:是否检测到按键?if (key_in == 1'b0) begin  // key_in=0表示按键可能被按下(低电平有效)state <= FILTER;  // 进入滤波状态,开始确认cnt <= 0;         // 计数器清零,准备计时// 注意:这里不立即改变key_out,要等确认后end// 如果key_in=1,保持IDLE状态,继续等待end// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 状态2:FILTER - 滤波状态(关键状态!)// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━FILTER: begin// 这里体现Mealy型特点:根据输入和状态共同决定if (key_in == 1'b1) begin  // 情况1:按键又松开了 → 说明是误触发或抖动state <= IDLE;         // 回到空闲状态key_out <= 1'b1;       // 输出保持未按下// 这就是消抖!短暂的抖动不会触发输出end else if (cnt >= 19) begin  // 情况2:按键保持低电平20ms(cnt: 0~19) → 确认是真的按下state <= PRESSED;      // 进入按下状态key_out <= 1'b0;       // 🎯 输出:确认按下(低电平)// Mealy型优势:一旦确认就立即输出,不用等下个时钟end else begin// 情况3:正在计时中,还没到20mscnt <= cnt + 1;        // 计数器+1,继续等待// state保持FILTER,key_out保持1(还没确认)end/*滤波过程示意:时间:  0  1  2  ...  18  19  20mscnt:   0  1  2  ...  18  19  (到19就确认)如果在这期间key_in变回1,说明是抖动,回到IDLE如果cnt到19,key_in还是0,说明真的按下,去PRESSED*/end// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 状态3:PRESSED - 按下状态// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━PRESSED: beginkey_out <= 1'b0;  // 输出保持:按下状态// 检查输入:按键是否松开?if (key_in == 1'b1) begin  // key_in=1表示按键松开了state <= IDLE;   // 回到空闲状态,等待下次按键key_out <= 1'b1; // 🎯 输出:松开(立即变高)// Mealy型:松开也是立即响应end// 如果key_in=0,保持PRESSED状态,继续输出按下end// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 异常处理// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━default: beginstate <= IDLE;       // 强制回到初始状态key_out <= 1'b1;endendcaseendendendmodule
// ============================================================
// 模块结束
// Mealy型特点总结:
// 1. 输出(key_out)在多个地方被赋值,取决于state+key_in
// 2. 输出变化更快,不用等到下个状态才改变
// 3. 代码可以把状态转移和输出写在一起(时序逻辑块)
// ============================================================

🔍 Mealy型与Moore型的关键区别

对比代码结构:

Moore型(三段式)

// 第1段:状态转移(只管状态)
always @(posedge clk) begincurrent_state <= next_state;
end// 第2段:下一状态判断(看状态+输入)
always @(*) beginnext_state = ...;  // 根据current_state和输入判断
end// 第3段:输出(只看状态)
always @(*) begincase(current_state)GREEN: led = 3'b001;  // 输出只依赖状态...endcase
end

Mealy型(可以合并)

// 状态转移+输出一起写(因为输出依赖输入)
always @(posedge clk) begincase(state)IDLE: beginkey_out <= 1'b1;  // 输出依赖状态if (key_in == 0) begin  // 同时检查输入state <= FILTER;endendFILTER: beginif (key_in == 1) beginkey_out <= 1'b1;  // 输出同时依赖状态+输入end else if (...) beginkey_out <= 1'b0;endendendcase
end

####时序图对比:

Moore型时序:_   _   _   _   _   _
clk   __| |_| |_| |_| |_| |_| |__input ____┌─────────────┐______按下      松开state __IDLE__┌FILTER┐_IDLE___↓ 
output ____________┌───┐________慢1拍!要等到状态稳定才输出Mealy型时序:_   _   _   _   _   _
clk   __| |_| |_| |_| |_| |_| |__input ____┌─────────────┐______按下      松开state __IDLE__┌FILTER┐_IDLE___↓立即
output _____┌────────┐_________快!输入变化立即影响输出

🧪 测试代码

// ============================================================
// 测试模块:验证按键消抖功能
// ============================================================
module tb_key_debounce;reg clk, rst_n, key_in;wire key_out;// 实例化被测模块key_debounce uut(.clk(clk),.rst_n(rst_n),.key_in(key_in),.key_out(key_out));// 生成1kHz时钟(每1ms一个周期)initial clk = 0;always #0.5 clk = ~clk;  // 0.5ms翻转一次 → 周期1ms// 测试流程initial begin$display("时间(ms) | key_in | 状态 | key_out | 说明");$display("---------|--------|------|---------|----------");// 初始化rst_n = 0;key_in = 1;  // 按键未按下#5;rst_n = 1;// ═══ 测试1:正常按键(无抖动) ═══#10;$display(">>> 测试1:模拟正常按键按下");key_in = 0;  // 按下#25;  // 等待25ms(超过20ms滤波时间)key_in = 1;  // 松开#10;// ═══ 测试2:模拟抖动(短暂的毛刺) ═══#10;$display(">>> 测试2:模拟按键抖动");key_in = 0;  // 按下#5;   // 5ms后key_in = 1;  // 抖动松开#3;key_in = 0;  // 又按下#5;key_in = 1;  // 又松开(这些都是抖动)#10;key_in = 0;  // 最后真正按下#25;  // 保持25mskey_in = 1;  // 松开#10;$finish;end// 监控并打印always @(posedge clk) begin$display(" %4t    |   %b    | %b  |    %b    | cnt=%d", $time, key_in, uut.state, key_out, uut.cnt);end// 波形记录initial begin$dumpfile("key_debounce.vcd");$dumpvars(0, tb_key_debounce);endendmodule

📋 状态机设计流程总结

🎯 设计五步法(按顺序执行)

步骤内容工具/方法检查要点
1. 画状态图标出所有状态和转换纸笔/Visio/Draw.io每个状态都有出口?有无死循环?
2. 状态编码选择编码方式二进制/独热码编码唯一?位宽够用?
3. 写状态转移第1段always时序逻辑有复位?用非阻塞赋值?
4. 写转移条件第2段always组合逻辑有默认值?用阻塞赋值?
5. 写输出逻辑第3段always组合逻辑覆盖所有状态?有default?

📝 详细步骤说明

步骤1:画状态图

目的:理清楚整个系统的行为逻辑

画图要素

  • 圆圈:表示状态
  • 箭头:表示转换
  • 箭头上的文字:转换条件
  • 圆圈内的文字:状态名称和输出(Moore型)

示例:交通灯状态图

    [GREEN]输出:001计时5秒↓ counter>=4[YELLOW]输出:010计时2秒↓ counter>=1[RED]输出:100计时5秒↓ counter>=4回到[GREEN]

检查清单

  • 是否有初始状态?(复位后进入哪个状态)
  • 每个状态是否都有出口?(不会卡死)
  • 是否有不可达状态?(画不到的状态)
  • 转换条件是否互斥?(不会同时满足多个条件)

步骤2:状态编码

选择原则

初学者/调试阶段 → 独热码优点:直观,易调试缺点:浪费寄存器项目成熟/资源紧张 → 二进制优点:节省资源缺点:不直观高速/低功耗设计 → 格雷码优点:相邻状态只变1位,减少毛刺缺点:编码规则复杂

编码示例(4个状态)

// 独热码(推荐初学者)
localparam S0 = 4'b0001;  // 第0位是1
localparam S1 = 4'b0010;  // 第1位是1
localparam S2 = 4'b0100;  // 第2位是1
localparam S3 = 4'b1000;  // 第3位是1// 二进制码(节省资源)
localparam S0 = 2'b00;
localparam S1 = 2'b01;
localparam S2 = 2'b10;
localparam S3 = 2'b11;// 格雷码(减少毛刺)
localparam S0 = 2'b00;
localparam S1 = 2'b01;
localparam S2 = 2'b11;  // 与S1只差1位
localparam S3 = 2'b10;  // 与S2只差1位

检查清单

  • 每个状态的编码是否唯一?
  • 寄存器位宽是否足够?(独热码n状态需n位,二进制需log2(n)位)
  • 是否用localparam定义?(不要直接写数字)

步骤3:写状态转移(第1段always)

模板代码

// 时序逻辑:负责在时钟沿更新状态
always @(posedge clk or negedge rst_n) beginif (!rst_n) begin// 复位:给所有寄存器赋初值current_state <= 初始状态;counter <= 0;// 其他寄存器的初始化...end else begin// 正常工作:更新状态current_state <= next_state;// 其他寄存器的更新逻辑// 如计数器、数据寄存器等end
end

检查清单

  • 敏感列表是否包含时钟和复位?
  • 是否用非阻塞赋值(<=)?
  • 复位是否初始化了所有寄存器?
  • 复位状态是否合理?(通常是初始状态)

步骤4:写转移条件(第2段always)

模板代码

// 组合逻辑:根据当前状态和输入,判断下一状态
always @(*) begin// ⚡关键:先给默认值,避免锁存器!next_state = current_state;  // 根据当前状态分情况讨论case(current_state)STATE1: beginif (条件1) beginnext_state = STATE2;endelse if (条件2) beginnext_state = STATE3;end// 如果都不满足,保持默认值(留在STATE1)endSTATE2: beginif (条件3) beginnext_state = STATE1;endend// ... 其他状态default: next_state = 初始状态;  // 异常保护endcase
end

检查清单

  • 第一行是否给了默认值?
  • 是否用阻塞赋值(=)?
  • 是否有default分支?
  • 每个状态的转移条件是否完整?

步骤5:写输出逻辑(第3段always)

Moore型模板

// Moore型:输出只看当前状态
always @(*) begin// 根据状态直接决定输出case(current_state)STATE1: beginoutput1 = 值1;output2 = 值2;endSTATE2: beginoutput1 = 值3;output2 = 值4;enddefault: beginoutput1 = 默认值;output2 = 默认值;endendcase
end

Mealy型特点

// Mealy型:输出看状态+输入,通常和状态转移写在一起
always @(posedge clk) begincase(state)STATE1: beginif (input1) beginoutput = 值1;  // 输出依赖输入state <= STATE2;end else beginoutput = 值2;state <= STATE1;endendendcase
end

检查清单

  • 是否覆盖了所有状态?
  • 是否有default分支?
  • 输出类型是否正确?(reg for always块)
  • Moore型是否只依赖状态?

✅ 设计完成后的检查清单

代码层面
□ 是否有复位状态?(通常是第一个状态)
□ 复位是否初始化所有寄存器?
□ 所有状态是否都有出口?(不会卡死)
□ 组合逻辑是否有默认值?(避免锁存器)
□ 是否避免了锁存器?(综合时检查警告)
□ 状态编码是否唯一?
□ 计时器边界是否正确?(注意-1)
□ 时序逻辑用<=,组合逻辑用=?
□ 敏感列表是否正确?(时序:clk/rst,组合:*)
□ 是否有default分支?(case语句)
功能层面
□ 状态转换顺序是否正确?
□ 转换条件是否正确?
□ 输出是否符合需求?
□ 时序是否满足要求?
□ 是否考虑了边界情况?
□ 是否考虑了异常情况?
仿真验证
 是否写了testbench?
□ 是否测试了复位功能?
□ 是否测试了所有状态转换?
□ 是否测试了边界条件?
□ 是否测试了异常输入?
□ 波形是否符合预期?

🚀 进阶挑战

💡 挑战1:电梯控制器

需求分析

  • 输入:3个楼层按钮(btn[2:0]),当前楼层传感器

  • 输出:电梯移动方向(up/down),开门信号(door_open)

  • 规则

    1. 优先响应同方向的请求
    2. 到达目标楼层后开门2秒
    3. 无请求时停在当前楼层

状态设计提示

状态定义:
- IDLE: 空闲等待
- MOVING_UP: 上行中
- MOVING_DOWN: 下行中
- DOOR_OPENING: 开门中
- DOOR_CLOSING: 关门中状态转换:
IDLE → (有上层请求) → MOVING_UP→ (有下层请求) → MOVING_DOWN
MOVING_UP → (到达目标层) → DOOR_OPENING
DOOR_OPENING → (2秒后) → DOOR_CLOSING
DOOR_CLOSING → (有请求) → MOVING_UP/DOWN→ (无请求) → IDLE

代码框架

module elevator_ctrl(input wire clk,input wire rst_n,input wire [2:0] btn_request,  // 按钮请求[2楼,1楼,0楼]input wire [1:0] current_floor, // 当前楼层(0~2)output reg moving_up,          // 上行信号output reg moving_down,        // 下行信号output reg door_open           // 开门信号
);// 状态定义localparam IDLE         = 3'b000;localparam MOVING_UP    = 3'b001;localparam MOVING_DOWN  = 3'b010;localparam DOOR_OPENING = 3'b011;localparam DOOR_CLOSING = 3'b100;reg [2:0] state;reg [1:0] target_floor;  // 目标楼层reg [3:0] timer;         // 开门计时// TODO: 实现三段式状态机// 第1段:状态转移// 第2段:下一状态判断(判断是否有请求,优先级)// 第3段:输出控制(根据状态输出moving_up/down/door_open)endmodule

💡 挑战2:串口接收状态机

背景知识

  • 串口协议:1个起始位(0) + 8个数据位 + 1个停止位(1)
  • 波特率:9600bps,每位持续约104μs

需求分析

  • 输入:串行数据线(rxd),波特率时钟(baud_clk)
  • 输出:接收到的8位数据(data[7:0]),接收完成标志(done)

状态设计提示

状态定义:
- IDLE: 等待起始位
- START: 检测到起始位,准备接收
- DATA0~DATA7: 接收8位数据
- STOP: 接收停止位
- ERROR: 校验错误状态转换图:
IDLE → (检测到rxd=0) → START
START → (采样中间时刻) → DATA0
DATA0 → DATA1 → ... → DATA7
DATA7 → STOP
STOP → (rxd=1,正确) → IDLE→ (rxd=0,错误) → ERROR

代码框架

module uart_rx(input wire clk,input wire rst_n,input wire rxd,           // 串行输入output reg [7:0] data,    // 接收到的数据output reg done,          // 接收完成标志output reg error          // 错误标志
);// 状态定义localparam IDLE   = 4'd0;localparam START  = 4'd1;localparam DATA0  = 4'd2;localparam DATA1  = 4'd3;// ... DATA2~DATA7localparam STOP   = 4'd10;localparam ERROR  = 4'd11;reg [3:0] state;reg [7:0] data_shift;  // 数据移位寄存器reg [7:0] bit_cnt;     // 位计数器(用于波特率定时)// TODO: 实现串口接收状态机// 关键点:// 1. 在每个数据位的中间时刻采样(最稳定)// 2. 使用移位寄存器逐位接收// 3. 检测停止位判断接收是否正确endmodule

难点提示

  • 采样时机:要在每位的中间时刻采样,不是开始或结束
  • 位同步:用计数器数到半位时间(52μs)再采样

💡 挑战3:自动售货机

需求分析

  • 商品价格:2元
  • 输入:投币信号(coin_1yuan, coin_2yuan),取货信号(取消按钮)
  • 输出:找零信号(change),出货信号(vend),显示金额(display)

状态设计提示

状态定义:
- IDLE: 等待投币(显示:请投币)
- COIN_1: 已投1元(显示:还需1元)
- COIN_2: 已投2元(显示:可以取货)
- VENDING: 出货中(显示:出货中...)
- CHANGE: 找零中(显示:找零中...)特殊情况:
- 在COIN_1状态投2元 → 金额3元 → CHANGE
- 在任何状态按取消 → 退币 → IDLE

代码框架

module vending_machine(input wire clk,input wire rst_n,input wire coin_1yuan,     // 投1元硬币input wire coin_2yuan,     // 投2元硬币input wire cancel,         // 取消/退币按钮output reg vend,           // 出货信号output reg change,         // 找零信号output reg [7:0] display   // 显示信息(ASCII码)
);// 状态定义localparam IDLE    = 3'b000;localparam COIN_1  = 3'b001;localparam COIN_2  = 3'b010;localparam VENDING = 3'b011;localparam CHANGE  = 3'b100;reg [2:0] state;reg [3:0] total_money;  // 当前总金额// TODO: 实现售货机状态机// 难点:// 1. 金额累加逻辑// 2. 找零逻辑(投入>2元)// 3. 取消/退币处理endmodule

扩展功能

  1. 支持多种商品(不同价格)
  2. 支持5元、10元纸币
  3. 库存管理(商品卖完后不出货)
  4. 显示屏显示(7段数码管或LCD)

🎓 学习建议

对于初学者:

  1. 先画图再写代码:状态图画清楚了,代码就是翻译
  2. 从简单开始:先做2-3个状态的简单状态机
  3. 重视仿真:每个状态转换都要在波形中验证
  4. 多写注释:状态机逻辑复杂,注释帮助理解
  5. 保存模板:三段式写法固定,保存模板可重复使用

进阶技巧:

  1. 状态机优化
    • 减少状态数量(合并相似状态)
    • 减少组合逻辑深度(避免过长的if-else链)
  2. 时序优化
    • 关键路径分析(最长组合逻辑路径)
    • 流水线技术(把一个复杂状态拆成多个)
  3. 可维护性
    • 用枚举类型定义状态(SystemVerilog)
    • 状态命名要有意义
    • 画出完整的状态图文档

📌 下节预告

第五课:模块化设计与层次结构

学习内容:

  • ✨ 模块实例化与端口连接
  • ✨ 参数传递与参数化设计
  • ✨ 多模块协同工作
  • ✨ 顶层模块设计规范
  • ✨ 实战项目:数字时钟系统
    • 分频器模块
    • 计数器模块
    • 显示控制模块
    • 按键控制模块
    • 顶层整合

💬 本课学习检查

完成以下检查,确保掌握本课内容:

概念理解

  • ✅ 理解Moore型和Mealy型的区别(输出依赖什么)
  • ✅ 知道为什么状态机要用寄存器(记忆功能)
  • ✅ 理解状态转换的本质(寄存器的值变化)
  • ✅ 知道什么时候用Moore,什么时候用Mealy

代码能力

  • ✅ 掌握三段式状态机写法(三个always各司其职)
  • ✅ 会画状态转换图(圆圈+箭头)
  • ✅ 知道如何选择状态编码(独热码vs二进制)
  • ✅ 理解为什么组合逻辑要给默认值(避免锁存器)

调试技巧

  • ✅ 能避免组合逻辑生成锁存器(第一行赋默认值)
  • ✅ 会设计计时器控制状态转换(counter与时间的关系)
  • ✅ 知道如何在仿真中观察状态变化
  • ✅ 能通过波形判断状态机是否正常工作

实践应用

  • ✅ 理解交通灯例子(Moore型典型应用)
  • ✅ 理解按键消抖例子(Mealy型典型应用)
  • ✅ 能独立设计简单的状态机(3-5个状态)
  • ✅ 知道状态机在实际项目中的作用(控制流程)

延伸思考

  • 🤔 如果状态很多(比如100个),还适合用独热码吗?
  • 🤔 状态机能不能嵌套?(一个状态机内部还有状态机)
  • 🤔 如何设计一个"可配置"的状态机?(用参数控制状态数量)

🎉 恭喜完成第四课!

状态机是数字电路设计的灵魂,掌握了状态机,你就掌握了用硬件"编排逻辑"的能力。

下一课我们将学习如何把多个模块组合起来,构建更大的系统。就像搭积木一样,把小功能组合成大功能!

💪 继续加油!有问题随时问!

如果你是路过的大佬,发现我哪里理解错了,请务必指出来!如果你也是正在入门的小伙伴,欢迎一起交流学习心得~

http://www.dtcms.com/a/586184.html

相关文章:

  • Unicode全字符集加解密工具 - 强大的编码转换GUI应用
  • 网站管理和维护设计师学编程能自己做网站吗
  • PyInstaller 工具使用文档及打包教程
  • 怎么建商业网站外国广告公司网站
  • USB Gadget 技术
  • 常州小型网站建设北京电商网站开发公司哪家好
  • 1108秋招随记
  • 做自己视频教程的网站wordpress去除谷歌
  • 咋把网站制作成软件建设网站需要注意什么手续
  • 线程4.2
  • SOAR:利用状态空间模型和可编程梯度进行航空影像中小目标物体检测的进展
  • 开一个网站需要多少钱网站开发工作量评估
  • [SPSS] SPSS数据的保存
  • Verilog中+:和 -:
  • 清理空壳网站做网站的程序员工资大约月薪
  • 架构设计:基于拼多多 API 构建商品数据实时同步服务
  • 常州建设局下属网站深圳市住房和建设局高泉
  • SQL时间函数全解析从基础日期处理到高级时间序列分析
  • 单片机通信协议--USART(串口通信)
  • 1.21 Profiler提供的API
  • 网站建设维护的知识wordpress搜索被攻击
  • 网站的文件夹wordpress引导页
  • 自然语言处理实战——基于k近邻法的文本分类
  • 柳南网站建设珠海市横琴建设局网站
  • 11.8 脚本网页 塔防游戏
  • FreeRTOS 使用目录
  • 网站代码框架云南安宁做网站的公司
  • 企业网站源码简约郑州住房城乡建设官网
  • 研发地网站建设第三次网站建设的通报
  • 企业网站分为哪四类中国与俄罗斯最新局势