FPGA交通灯实战:状态机设计与Verilog实现全解析

1. 项目背景与设计目标

交通信号灯控制系统是数字电路设计的经典案例,也是FPGA初学者的理想练手项目。通过这个项目,我们不仅能掌握状态机设计思想,还能深入理解时序逻辑在实际工程中的应用。

传统交通灯系统通常包含以下几个核心需求:

  • 东西和南北方向交替通行
  • 绿灯转红灯前需经过黄灯过渡
  • 各状态持续时间可配置
  • 倒计时显示功能

使用FPGA实现交通灯控制具有明显优势:

  1. 并行处理能力:FPGA可同时处理多个方向的信号控制
  2. 灵活可配置:通过修改Verilog代码即可调整时序参数
  3. 实时性强:硬件实现确保响应速度
  4. 扩展性好:便于添加传感器输入等高级功能

2. 状态机设计与建模

2.1 交通灯状态分析

典型的十字路口交通灯包含4个主要状态:

状态编号东西方向南北方向持续时间
S0绿灯红灯30秒
S1黄灯红灯2秒
S2红灯绿灯30秒
S3红灯黄灯2秒

2.2 Moore型状态机实现

我们采用Moore型状态机,其输出仅与当前状态有关。以下是状态转移图的关键要素:

graph LR
    S0 -->|计时30秒| S1
    S1 -->|计时2秒| S2
    S2 -->|计时30秒| S3
    S3 -->|计时2秒| S0

对应的Verilog状态编码建议采用独热码(one-hot):

localparam S0 = 4'b0001;
localparam S1 = 4'b0010; 
localparam S2 = 4'b0100;
localparam S3 = 4'b1000;

2.3 状态转移条件实现

状态转移的核心是计时器控制,下面给出计时器模块的关键代码:

module timer(
    input clk,
    input reset,
    input enable,
    input [7:0] limit,
    output reg done
);
    reg [7:0] count;
    
    always @(posedge clk or posedge reset) begin
        if(reset) begin
            count <= 0;
            done <= 0;
        end else if(enable) begin
            if(count >= limit-1) begin
                count <= 0;
                done <= 1;
            end else begin
                count <= count + 1;
                done <= 0;
            end
        end else begin
            done <= 0;
        end
    end
endmodule

3. Verilog实现详解

3.1 顶层模块设计

顶层模块负责实例化各子模块并实现互联:

module traffic_light(
    input clk,         // 系统时钟(如50MHz)
    input reset,       // 异步复位
    output reg [1:0] ew_light, // 东西方向灯 00-灭 01-红 10-黄 11-绿
    output reg [1:0] ns_light, // 南北方向灯
    output [7:0] display      // 数码管显示
);

    // 状态寄存器
    reg [3:0] state;
    
    // 分频器生成1Hz时钟
    wire clk_1Hz;
    clock_divider #(.DIV(50_000_000)) clk_div(
        .clk_in(clk),
        .clk_out(clk_1Hz)
    );
    
    // 状态机核心逻辑
    always @(posedge clk_1Hz or posedge reset) begin
        if(reset) begin
            state <= S0;
        end else begin
            case(state)
                S0: if(timer_done) state <= S1;
                S1: if(timer_done) state <= S2;
                S2: if(timer_done) state <= S3;
                S3: if(timer_done) state <= S0;
                default: state <= S0;
            endcase
        end
    end
    
    // 输出逻辑
    always @(*) begin
        case(state)
            S0: begin
                ew_light = 2'b11; // 绿
                ns_light = 2'b01; // 红
            end
            // 其他状态类似...
        endcase
    end
endmodule

3.2 关键功能模块

3.2.1 时钟分频模块

将高频系统时钟分频为1Hz用于计时:

module clock_divider #(
    parameter DIV = 50_000_000
)(
    input clk_in,
    output reg clk_out
);
    reg [31:0] counter;
    
    always @(posedge clk_in) begin
        if(counter >= DIV-1) begin
            counter <= 0;
            clk_out <= ~clk_out;
        end else begin
            counter <= counter + 1;
        end
    end
endmodule
3.2.2 倒计时显示模块

驱动数码管显示剩余时间:

module display_driver(
    input [7:0] value,
    output reg [7:0] seg
);
    always @(*) begin
        case(value % 10)
            0: seg = 8'b11000000; // 0
            1: seg = 8'b11111001; // 1
            // 其他数字...
        endcase
    end
endmodule

3.3 完整状态机实现

module traffic_fsm(
    input clk,
    input reset,
    output reg [1:0] ew_light,
    output reg [1:0] ns_light,
    output reg [7:0] timer_val
);
    // 状态定义
    localparam S0 = 2'b00; // 东西绿,南北红
    localparam S1 = 2'b01; // 东西黄,南北红 
    localparam S2 = 2'b10; // 东西红,南北绿
    localparam S3 = 2'b11; // 东西红,南北黄
    
    // 状态寄存器
    reg [1:0] state, next_state;
    
    // 计时器
    reg [7:0] counter;
    wire timer_done = (counter == 0);
    
    // 状态持续时间配置
    wire [7:0] state_duration = 
        (state == S0) ? 8'd30 :
        (state == S1) ? 8'd2 :
        (state == S2) ? 8'd30 : 
        8'd2;
    
    // 状态转移逻辑
    always @(posedge clk or posedge reset) begin
        if(reset) begin
            state <= S0;
            counter <= state_duration;
        end else begin
            if(timer_done) begin
                state <= next_state;
                counter <= state_duration;
            end else begin
                counter <= counter - 1;
            end
        end
    end
    
    // 下一状态逻辑
    always @(*) begin
        case(state)
            S0: next_state = S1;
            S1: next_state = S2;
            S2: next_state = S3;
            S3: next_state = S0;
            default: next_state = S0;
        endcase
    end
    
    // 输出逻辑
    always @(*) begin
        timer_val = counter;
        case(state)
            S0: begin
                ew_light = 2'b11; // 绿
                ns_light = 2'b01; // 红
            end
            S1: begin
                ew_light = 2'b10; // 黄
                ns_light = 2'b01; // 红
            end
            S2: begin
                ew_light = 2'b01; // 红
                ns_light = 2'b11; // 绿
            end
            S3: begin
                ew_light = 2'b01; // 红
                ns_light = 2'b10; // 黄
            end
        endcase
    end
endmodule

4. Quartus工程实现

4.1 工程设置关键步骤

  1. 新建工程:选择正确的FPGA器件型号
  2. 添加源文件:将Verilog模块加入工程
  3. 引脚分配:根据开发板分配时钟、LED和数码管引脚
  4. 编译设置:优化选项选择"Balanced"

4.2 常见问题解决

  1. 时序不满足:

    • 检查时钟约束是否正确
    • 添加适当的流水线寄存器
    • 降低时钟频率测试
  2. 资源利用率高:

    • 优化状态编码方式
    • 共享部分计算逻辑
    • 检查是否有多余寄存器
  3. 仿真与实现不一致:

    • 确保测试激励覆盖所有状态
    • 检查异步复位处理
    • 验证时钟域交叉处理

4.3 仿真验证

测试平台(Testbench)示例:

`timescale 1ns/1ns
module tb_traffic_light;
    reg clk;
    reg reset;
    wire [1:0] ew, ns;
    
    traffic_light dut(
        .clk(clk),
        .reset(reset),
        .ew_light(ew),
        .ns_light(ns)
    );
    
    initial begin
        clk = 0;
        reset = 1;
        #20 reset = 0;
        
        // 监控状态变化
        $monitor("Time=%t, State=%b, EW=%b, NS=%b", 
                $time, dut.state, ew, ns);
                
        #1000 $finish;
    end
    
    always #5 clk = ~clk;
endmodule

5. 高级功能扩展

5.1 动态时间调整

增加按钮输入,允许实时调整各状态持续时间:

reg [7:0] green_time = 30;
reg [7:0] yellow_time = 2;

always @(posedge adjust_btn) begin
    if(sel) green_time <= green_time + 1;
    else yellow_time <= yellow_time + 1;
end

5.2 传感器输入

添加车辆检测传感器,实现智能控制:

wire ew_sensor, ns_sensor; // 车辆检测信号

// 智能调整绿灯时间
wire [7:0] dynamic_green = 
    (ew_sensor && !ns_sensor) ? 40 :
    (!ew_sensor && ns_sensor) ? 20 : 
    30;

5.3 多模式控制

实现不同工作模式切换:

reg [1:0] mode; // 00-正常 01-夜间 10-紧急

always @(*) begin
    if(mode == 2'b01) begin // 夜间模式
        ew_light = {blink, blink};
        ns_light = {blink, blink};
    end else if(mode == 2'b10) begin // 紧急模式
        ew_light = 2'b01;
        ns_light = 2'b01;
    end else begin
        // 正常模式逻辑
    end
end

6. 优化与调试技巧

6.1 时序优化策略

  1. 流水线设计:将长组合逻辑拆分为多级
  2. 寄存器平衡:重分布组合逻辑
  3. 状态编码优化:尝试Gray码或独热码

6.2 调试方法

  1. SignalTap逻辑分析仪:实时捕获内部信号
  2. 虚拟JTAG:通过Quartus调试接口
  3. 仿真对比:确保行为仿真与时序仿真一致

6.3 资源优化对比

不同实现方式的资源占用对比:

实现方式LUT使用量寄存器数量最大频率
基本状态机8532120MHz
优化状态机7828150MHz
带智能控制11245100MHz

7. 工程实践建议

  1. 模块化设计:将功能拆分为独立模块
  2. 参数化设计:使用parameter提高代码复用性
  3. 版本控制:使用Git管理设计版本
  4. 文档记录:详细注释并维护设计文档

实际项目中遇到的典型问题及解决方案:

在第一次板级测试时,发现黄灯闪烁频率不稳定。经过分析发现是时钟分频模块的计数器位宽不足导致溢出。将计数器位宽从24位扩展到32位后问题解决。这个教训告诉我们,在编写通用模块时,参数校验和边界条件处理非常重要。

更多推荐