news 2026/6/13 17:31:37

用Verilog三段式状态机搞定一个智能交通灯:从状态图到FPGA上板全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用Verilog三段式状态机搞定一个智能交通灯:从状态图到FPGA上板全流程

用Verilog三段式状态机实现智能交通灯:从设计到FPGA部署的工程实践

十字路口的红绿灯控制系统是数字逻辑设计的经典案例,也是初学者掌握状态机应用的绝佳切入点。本文将带你完整实现一个具备车辆检测功能的智能交通灯控制器,从状态图绘制到FPGA上板调试,全程采用业界推崇的三段式状态机写法。不同于教科书上的简化示例,这里会直面工程实践中的真实问题:如何设计可综合的延时逻辑?怎样避免阻塞赋值导致的仿真与硬件行为不一致?Testbench如何覆盖边界条件?

1. 需求分析与状态图设计

任何可靠的状态机设计都始于清晰的需求文档。我们的智能交通灯需要实现以下功能:

  • 主路优先:默认状态下主路保持绿灯,支路红灯
  • 车辆感应:通过传感器信号x判断支路是否有车辆等待(x=1表示有车)
  • 状态转换
    • x=1时,主路绿灯→黄灯(5秒)→红灯(1秒缓冲)→支路绿灯
    • x=0时,支路绿灯→黄灯(5秒)→红灯(1秒缓冲)→主路绿灯

1.1 状态转移图绘制

用Graphviz绘制的状态转移图更直观(实际工程文档推荐使用专业工具如Visio):

digraph traffic_light { rankdir=LR; node [shape=circle]; S0 [label="S0\n主路绿\n支路红"]; S1 [label="S1\n主路黄\n支路红"]; S2 [label="S2\n主路红\n支路红"]; S3 [label="S3\n主路红\n支路绿"]; S4 [label="S4\n主路红\n支路黄"]; S0 -> S1 [label="x==1"]; S1 -> S2 [label="计时5s"]; S2 -> S3 [label="计时1s"]; S3 -> S4 [label="x==0"]; S4 -> S0 [label="计时5s"]; S0 -> S0 [label="x==0"]; S3 -> S3 [label="x==1"]; }

1.2 状态编码策略

在FPGA实现中,状态编码方式直接影响资源利用率:

编码方式优点缺点
顺序二进制节省触发器容易产生毛刺
One-Hot简化译码逻辑占用更多触发器
Gray码减少状态切换时的毛刺编码复杂度较高

对于这个5状态的设计,推荐使用One-Hot编码:

parameter S0 = 5'b00001, S1 = 5'b00010, S2 = 5'b00100, S3 = 5'b01000, S4 = 5'b10000;

2. 三段式状态机实现

业界公认的三段式写法将状态机清晰地划分为三个部分,有效避免组合逻辑与时序逻辑的混合。

2.1 状态寄存器(时序逻辑)

// 第一段:状态寄存器 always @(posedge clk or negedge rst_n) begin if (!rst_n) current_state <= S0; else current_state <= next_state; end

注意:必须使用非阻塞赋值(<=),这是硬件并行的关键特征

2.2 次态逻辑(组合逻辑)

// 第二段:次态逻辑 always @(*) begin case (current_state) S0: next_state = (x) ? S1 : S0; S1: next_state = (timer_done) ? S2 : S1; S2: next_state = (timer_done) ? S3 : S2; S3: next_state = (!x) ? S4 : S3; S4: next_state = (timer_done) ? S0 : S4; default: next_state = S0; endcase end

2.3 输出逻辑(时序逻辑)

// 第三段:输出逻辑 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin main_light <= GREEN; branch_light <= RED; end else begin case (current_state) S0: begin main_light <= GREEN; branch_light <= RED; end S1: begin main_light <= YELLOW; branch_light <= RED; end S2: begin main_light <= RED; branch_light <= RED; end S3: begin main_light <= RED; branch_light <= GREEN; end S4: begin main_light <= RED; branch_light <= YELLOW; end endcase end end

3. 关键子模块设计

3.1 可综合的定时器模块

硬件中的延时不能直接用#delay,需要设计计数器:

module timer ( input clk, input reset, input enable, output reg done ); parameter COUNT_MAX = 500_000_000; // 5秒@100MHz reg [31:0] counter; always @(posedge clk or posedge reset) begin if (reset) begin counter <= 0; done <= 0; end else if (enable) begin if (counter >= COUNT_MAX-1) begin counter <= 0; done <= 1; end else begin counter <= counter + 1; done <= 0; end end else begin done <= 0; end end endmodule

3.2 防抖滤波电路

车辆传感器信号容易产生毛刺,需要添加防抖逻辑:

module debounce ( input clk, input noisy, output reg clean ); reg [19:0] count; reg new; always @(posedge clk) begin if (noisy != new) begin new <= noisy; count <= 0; end else if (count == 20'd999_999) begin clean <= new; end else begin count <= count + 1; end end endmodule

4. 验证与调试

4.1 自动化Testbench设计

`timescale 1ns/1ps module tb_traffic(); reg clk = 0; reg rst_n = 0; reg x = 0; wire [1:0] main, branch; always #5 clk = ~clk; traffic_light dut (.*); initial begin // 复位序列 #100 rst_n = 1; // 测试场景1:支路有车 #200 x = 1; #1000 x = 0; // 测试场景2:随机车辆触发 repeat (10) begin #($urandom_range(500,2000)) x = 1; #($urandom_range(100,500)) x = 0; end #2000 $finish; end // 自动检查状态转换 always @(posedge clk) begin if (rst_n) begin // 检查黄灯持续时间 if (dut.current_state == dut.S1) begin #600 assert (dut.next_state == dut.S2) else $error("S1->S2 transition failed"); end end end endmodule

4.2 常见调试问题排查

  1. 状态锁死

    • 检查所有状态转移条件是否完备
    • 添加default分支捕获非法状态
  2. 输出抖动

    • 确认所有输出寄存器都有复位值
    • 检查组合逻辑是否产生毛刺
  3. 时序违例

    # Vivado中查看时序报告 report_timing_summary -delay_type min_max -max_paths 10

5. FPGA实现步骤

5.1 Vivado工程创建流程

  1. 创建RTL工程:

    vivado -mode gui &
  2. 添加设计文件:

    add_files -norecurse { traffic_light.v timer.v debounce.v }
  3. 设置引脚约束(以Basys3为例):

    set_property PACKAGE_PIN W5 [get_ports clk] set_property IOSTANDARD LVCMOS33 [get_ports clk] set_property PACKAGE_PIN V17 [get_ports x]

5.2 上板调试技巧

  • LED呼吸灯调试法

    // 在状态机中添加调试输出 assign debug_led = (current_state == S0) ? slow_pwm : (current_state == S1) ? fast_pwm : 0;
  • 虚拟逻辑分析仪

    # 在Vivado中设置ILA create_debug_core u_ila ila set_property ALL_PROBE_SAME_MU true [get_debug_cores u_ila]

在Basys3开发板上实际部署时,发现当主路绿灯切换到黄灯时,如果突然有车辆通过,状态机需要完成当前黄灯周期后再响应新的车辆信号。这需要通过添加pending_request寄存器来实现请求缓存,确保交通灯变化的确定性。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/13 12:20:40

告别SecureCRT?在VSCode里用lrzsz插件搞定YModem文件传输(配置详解)

在VSCode中高效实现YModem文件传输&#xff1a;告别传统终端工具的终极指南对于嵌入式开发者、网络设备运维工程师以及需要频繁通过串口与远程设备交互的技术人员而言&#xff0c;文件传输是日常工作中不可或缺的环节。传统解决方案如SecureCRT、Xshell等独立终端软件虽然功能完…

作者头像 李华