Quick Start
- 准备开发环境:安装 Vivado 或 Quartus II(任一版本),确认支持目标器件(如 Xilinx Artix-7 或 Intel Cyclone IV)。
- 新建工程:在 EDA 工具中创建新工程,选择器件型号(例如 xc7a35tcsg324-1)。
- 编写顶层模块:新建 Verilog 文件,定义模块端口:clk、rst_n、light1(输出 2 位)、light2(输出 2 位),分别控制主路和支路红绿灯。
- 编写状态机模块:使用三段式状态机(状态编码、次态逻辑、输出逻辑),定义 4 个状态:S0(主路绿灯、支路红灯,持续 30 秒)、S1(主路黄灯、支路红灯,持续 3 秒)、S2(主路红灯、支路绿灯,持续 20 秒)、S3(主路红灯、支路黄灯,持续 3 秒)。
- 编写分频模块:将板载 50 MHz 时钟分频为 1 Hz 信号,用于状态切换计时。分频系数 = 50_000_000 / 2 = 25_000_000(占空比 50%)。
- 编写测试激励:在仿真中给 clk 50 MHz、rst_n 先低后高,观察 light1 和 light2 是否按时序切换。
- 运行行为仿真:在 EDA 工具中启动仿真,添加顶层模块和 testbench,运行 60 秒仿真时间,检查波形中 light1 和 light2 的状态序列。
- 综合与实现:对工程进行综合(Synthesis)和实现(Implementation),检查无时序违例。
- 生成比特流并下载:生成 .bit 文件,通过 JTAG 下载到开发板,观察 LED 灯是否按预期亮灭。
- 验收:确认主路绿灯亮 30 秒 → 黄灯亮 3 秒 → 支路绿灯亮 20 秒 → 黄灯亮 3 秒,循环。若不符合,检查分频系数或状态转移条件。
前置条件与环境
| 项目 | 推荐值/说明 | 替代方案 |
|---|---|---|
| 器件/板卡 | Xilinx Artix-7 (xc7a35tcsg324-1) 或 Intel Cyclone IV (EP4CE6E22C8) | 其他 FPGA 板卡,需调整时钟频率和引脚约束 |
| EDA 版本 | Vivado 2018.3 或 Quartus II 13.1 | 更高版本也可,注意兼容性 |
| 仿真器 | Vivado Simulator 或 ModelSim | QuestaSim、VCS |
| 时钟/复位 | 板载 50 MHz 晶振;低电平异步复位 | 若为高电平复位,修改 rst_n 为 rst |
| 接口依赖 | 至少 4 个 LED(主路绿、黄、红;支路绿、黄、红可复用) | 可用 6 个 LED 分别对应 |
| 约束文件 | .xdc (Vivado) 或 .sdc (Quartus),定义时钟周期 20 ns、引脚分配 | 需根据板卡原理图修改 |
目标与验收标准
- 功能点:交通灯按 4 状态循环切换,每个状态持续时间准确(30s/3s/20s/3s),主路和支路灯光互斥。
- 性能指标:系统时钟 50 MHz,无时序违例;状态切换无毛刺(输出寄存器化)。
- 资源占用:逻辑单元 < 50 个,寄存器 < 30 个(取决于分频器实现)。
- 验收方式:仿真波形确认 light1 和 light2 的 2 位编码(00=绿,01=黄,10=红)按时间轴正确变化;上板后 LED 灯肉眼观察符合时序。
实施步骤
工程结构
- 顶层模块(traffic_light_top):例化分频器、状态机、输出寄存器。
- 分频模块(clk_div):将 50 MHz 分频为 1 Hz,输出 clk_1hz。
- 状态机模块(fsm_controller):三段式状态机,输入 clk_1hz 和 rst_n,输出 light1 和 light2。
- 注意:分频模块可能消耗较多寄存器,若资源紧张可用计数器替代。
关键模块:状态机实现
// 三段式状态机示例(fsm_controller.v)
module fsm_controller (
input wire clk, // 1 Hz 时钟
input wire rst_n, // 低电平复位
output reg [1:0] light1, // 主路:00绿 01黄 10红
output reg [1:0] light2 // 支路:00绿 01黄 10红
);
// 状态编码(独热码或二进制,此处用二进制)
localparam S0 = 2'b00, // 主路绿30s
S1 = 2'b01, // 主路黄3s
S2 = 2'b10, // 支路绿20s
S3 = 2'b11; // 支路黄3s
reg [1:0] state, next_state;
reg [5:0] counter; // 计时计数器,最大30
// 第一段:状态寄存器(时序逻辑)
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= S0;
else
state <= next_state;
end
// 第二段:次态逻辑(组合逻辑)
always @(*) begin
next_state = state;
case (state)
S0: if (counter == 30) next_state = S1;
S1: if (counter == 3) next_state = S2;
S2: if (counter == 20) next_state = S3;
S3: if (counter == 3) next_state = S0;
endcase
end
// 第三段:输出逻辑(时序逻辑,寄存器化输出)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
light1 <= 2'b00;
light2 <= 2'b10;
counter <= 0;
end else begin
case (state)
S0: begin light1 <= 2'b00; light2 <= 2'b10; end
S1: begin light1 <= 2'b01; light2 <= 2'b10; end
S2: begin light1 <= 2'b10; light2 <= 2'b00; end
S3: begin light1 <= 2'b10; light2 <= 2'b01; end
endcase
// 计数器逻辑:每个时钟上升沿递增,状态切换时清零
if (next_state != state)
counter <= 0;
else
counter <= counter + 1;
end
end
endmodule关键模块:分频器实现
// 分频模块示例(clk_div.v)
module clk_div (
input wire clk_50mhz,
input wire rst_n,
output reg clk_1hz
);
reg [24:0] counter;
// 分频系数 = 50_000_000 / 2 = 25_000_000
// 注意:实际使用中需考虑计数器位宽,此处为 25 位
localparam DIV = 25_000_000;
always @(posedge clk_50mhz or negedge rst_n) begin
if (!rst_n) begin
counter <= 0;
clk_1hz <= 0;
end else begin
if (counter == DIV - 1) begin
counter <= 0;
clk_1hz <= ~clk_1hz;
end else begin
counter <= counter + 1;
end
end
end
endmodule顶层模块例化
// 顶层模块示例(traffic_light_top.v)
module traffic_light_top (
input wire clk,
input wire rst_n,
output wire [1:0] light1,
output wire [1:0] light2
);
wire clk_1hz;
// 例化分频器
clk_div u_clk_div (
.clk_50mhz(clk),
.rst_n(rst_n),
.clk_1hz(clk_1hz)
);
// 例化状态机
fsm_controller u_fsm (
.clk(clk_1hz),
.rst_n(rst_n),
.light1(light1),
.light2(light2)
);
endmodule验证结果
- 仿真验证:运行 60 秒仿真时间,观察 light1 和 light2 波形。主路绿灯(00)持续 30 秒后变为黄灯(01)持续 3 秒,然后变为红灯(10);支路红灯(10)持续 33 秒后变为绿灯(00)持续 20 秒,再变为黄灯(01)持续 3 秒。循环周期为 56 秒。
- 上板验证:下载比特流后,肉眼观察 LED 灯,确认时序与仿真一致。
排障指南
- 问题1:状态切换时间不准确。检查分频器输出频率是否为 1 Hz,可用仿真或示波器测量。确认分频系数计算正确(50 MHz / 2 / 25_000_000 = 1 Hz)。
- 问题2:输出有毛刺。确保输出逻辑为时序逻辑(寄存器化),而非组合逻辑。本设计第三段已使用 always @(posedge clk) 实现。
- 问题3:复位后状态不正确。检查复位信号极性,确认 rst_n 为低电平有效。若板卡复位为高电平有效,需取反或修改模块。
- 问题4:综合后资源超限。分频器计数器位宽较大(25 位),可改用 PLL IP 核生成 1 Hz 时钟,或使用计数器分频后直接驱动状态机(不产生独立时钟域)。
扩展建议
- 增加紧急模式:添加外部输入(如 emergency),当拉高时强制所有灯为红灯,持续 5 秒后恢复。
- 调整时间参数:将状态持续时间改为参数化(parameter),便于修改。
- 使用独热码编码:对于 4 状态,独热码(4 位)可减少组合逻辑,但寄存器数增加。
- 添加行人按钮:增加行人请求信号,在安全时机插入行人绿灯时间。
参考
- Verilog HDL 语法参考:IEEE Std 1364-2005
- 三段式状态机设计模式:Clifford E. Cummings, "State Machine Coding Styles for Synthesis"
- Vivado 用户指南:UG901
附录
附录A:完整代码清单(略,见正文各模块)
附录B:约束文件示例(.xdc)
# 时钟约束
create_clock -period 20.000 -name sys_clk [get_ports clk]
# 引脚分配(根据板卡修改)
set_property PACKAGE_PIN E3 [get_ports clk]
set_property PACKAGE_PIN C12 [get_ports rst_n]
set_property PACKAGE_PIN F4 [get_ports {light1[0]}]
set_property PACKAGE_PIN F5 [get_ports {light1[1]}]
set_property PACKAGE_PIN G4 [get_ports {light2[0]}]
set_property PACKAGE_PIN G5 [get_ports {light2[1]}]附录C:仿真脚本示例(Tcl)
# 启动仿真
restart
run 60 us
# 观察波形
add wave -r /traffic_light_top/*


