FPGA交通灯实战:用Verilog在Quartus里实现红绿灯倒计时(附状态机源码)
·
FPGA交通灯实战:状态机设计与Verilog实现全解析
1. 项目背景与设计目标
交通信号灯控制系统是数字电路设计的经典案例,也是FPGA初学者的理想练手项目。通过这个项目,我们不仅能掌握状态机设计思想,还能深入理解时序逻辑在实际工程中的应用。
传统交通灯系统通常包含以下几个核心需求:
- 东西和南北方向交替通行
- 绿灯转红灯前需经过黄灯过渡
- 各状态持续时间可配置
- 倒计时显示功能
使用FPGA实现交通灯控制具有明显优势:
- 并行处理能力:FPGA可同时处理多个方向的信号控制
- 灵活可配置:通过修改Verilog代码即可调整时序参数
- 实时性强:硬件实现确保响应速度
- 扩展性好:便于添加传感器输入等高级功能
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 工程设置关键步骤
- 新建工程:选择正确的FPGA器件型号
- 添加源文件:将Verilog模块加入工程
- 引脚分配:根据开发板分配时钟、LED和数码管引脚
- 编译设置:优化选项选择"Balanced"
4.2 常见问题解决
-
时序不满足:
- 检查时钟约束是否正确
- 添加适当的流水线寄存器
- 降低时钟频率测试
-
资源利用率高:
- 优化状态编码方式
- 共享部分计算逻辑
- 检查是否有多余寄存器
-
仿真与实现不一致:
- 确保测试激励覆盖所有状态
- 检查异步复位处理
- 验证时钟域交叉处理
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 时序优化策略
- 流水线设计:将长组合逻辑拆分为多级
- 寄存器平衡:重分布组合逻辑
- 状态编码优化:尝试Gray码或独热码
6.2 调试方法
- SignalTap逻辑分析仪:实时捕获内部信号
- 虚拟JTAG:通过Quartus调试接口
- 仿真对比:确保行为仿真与时序仿真一致
6.3 资源优化对比
不同实现方式的资源占用对比:
| 实现方式 | LUT使用量 | 寄存器数量 | 最大频率 |
|---|---|---|---|
| 基本状态机 | 85 | 32 | 120MHz |
| 优化状态机 | 78 | 28 | 150MHz |
| 带智能控制 | 112 | 45 | 100MHz |
7. 工程实践建议
- 模块化设计:将功能拆分为独立模块
- 参数化设计:使用parameter提高代码复用性
- 版本控制:使用Git管理设计版本
- 文档记录:详细注释并维护设计文档
实际项目中遇到的典型问题及解决方案:
在第一次板级测试时,发现黄灯闪烁频率不稳定。经过分析发现是时钟分频模块的计数器位宽不足导致溢出。将计数器位宽从24位扩展到32位后问题解决。这个教训告诉我们,在编写通用模块时,参数校验和边界条件处理非常重要。
更多推荐

所有评论(0)