EDA课程设计:基于FPGA的交通灯控制系统
设计目标
本课程设计旨在利用EDA技术和FPGA(现场可编程门阵列)芯片,设计并实现一个功能完备、结构清晰、易于扩展的十字路口交通灯控制系统,具体目标如下:
-
基本功能实现:
- 实现一个十字路口(主干道A与次干道B)的交通灯控制。
- 主干道A:绿灯 -> 黄灯 -> 红灯。
- 次干道B:红灯 -> 绿灯 -> 黄灯。
- A、B两方向灯色变化必须互锁,确保不会出现冲突。
- 各灯色持续时间可预置并显示。
-
核心能力培养:
- 掌握使用Verilog HDL进行数字逻辑电路的设计方法。
- 熟悉FPGA开发流程(设计输入、功能仿真、综合实现、硬件测试)。
- 理解并应用状态机(Finite State Machine, FSM)来设计时序逻辑控制电路。
- 掌握分频器、计数器等基本数字逻辑模块的设计与应用。
-
扩展功能(可选,用于提高设计难度和创新性):
- 紧急车辆优先通行: 当有紧急车辆(如救护车、消防车)信号时,两个方向的交通灯全部变为红灯,以便紧急车辆快速通过。
- 倒计时显示: 在数码管上显示当前灯色的剩余时间。
- 车流量检测与动态调整: 模拟通过传感器检测车辆排队情况,动态调整绿灯持续时间。
设计方案论证
1 核心控制器选择:状态机
交通灯的控制是一个典型的时序逻辑问题,其输出(灯的状态)不仅取决于当前的输入,还取决于之前的历史状态,采用有限状态机作为核心控制器是最合适、最清晰的设计方案。
- 为什么是状态机?
- 结构清晰: 可以将交通灯的每一个工作阶段(如A绿灯、A黄灯、B绿灯等)定义为一个独立的状态,状态之间的转换关系一目了然。
- 易于实现: Verilog HDL对状态机的描述非常方便,可以使用
case语句来实现状态转移和输出。 - 可靠性强: 状态机的设计能确保系统在任意状态下都能正确地进入下一个状态,避免逻辑混乱。
2 状态定义
根据基本功能需求,我们可以定义4个核心状态:
- S_A_GREEN (状态0): 主干道A绿灯亮,次干道B红灯亮。
- S_A_YELLOW (状态1): 主干道A黄灯亮,次干道B红灯亮。
- S_B_GREEN (状态2): 主干道A红灯亮,次干道B绿灯亮。
- S_B_YELLOW (状态3): 主干道A红灯亮,次干道B黄灯亮。
状态转移流程为:S_A_GREEN -> S_A_YELLOW -> S_B_GREEN -> S_B_YELLOW -> S_A_GREEN ... 循环往复。
3 模块划分
为了设计的模块化和可复用性,将整个系统划分为以下几个子模块:
-
顶层模块 (
traffic_light_top):- 功能: 连接所有子模块,作为系统的总入口。
- 端口: 系统时钟、复位信号、紧急信号、LED灯输出、数码管输出(如果有时)。
-
分频器模块 (
clk_divider):- 功能: 将FPGA开发板提供的较高频率时钟(如50MHz)分频为适合系统计时的较低频率时钟(如1Hz)。
- 理由: 交通灯的秒级计时不需要高频时钟,分频可以降低计数器的位数和功耗。
-
状态控制器模块 (
state_controller):- 功能: 系统的核心,根据当前状态和计时器的值,在满足时间条件时进行状态转移。
- 内部逻辑: 使用
always块实现状态寄存器和状态转移逻辑。case语句根据当前状态决定下一状态和输出。
-
计时器模块 (
timer):- 功能: 在每个状态下进行倒计时。
- 逻辑: 这是一个简单的计数器,当状态控制器进入一个新状态时,计时器加载该状态的预设时间,并开始递减,当计时器减到0时,向状态控制器发送一个时间到信号。
-
LED驱动模块 (
led_driver):- 功能: 根据当前状态,控制6个LED灯(A红、A黄、A绿、B红、B黄、B绿)的亮灭。
- 逻辑: 这是一个组合逻辑电路,直接将状态编码转换为LED灯的控制信号。
-
紧急处理逻辑 (
emergency_handler- 可选):- 功能: 当检测到紧急信号时,强制系统进入一个特殊状态(所有灯为红),并屏蔽正常的状态转移。
- 逻辑: 可以在状态控制器中增加对紧急信号的判断,作为最高优先级的条件。
-
数码管显示模块 (
seg_display- 可选):- 功能: 动态扫描显示当前状态的剩余时间。
- 逻辑: 包含一个BCD码计数器和段选/位选信号生成逻辑。
详细Verilog代码实现
1 顶层模块 (traffic_light_top.v)
// 顶层模块:连接所有子模块
module traffic_light_top(
input clk, // 系统时钟 (e.g., 50MHz)
input rst_n, // 异步复位,低电平有效
input emergency, // 紧急信号,高电平有效
output reg [5:0] led // 6个LED灯 [A_red, A_yellow, A_green, B_red, B_yellow, B_green]
);
// 内部信号
wire clk_1hz; // 1Hz时钟,用于计时
wire [3:0] current_state; // 当前状态编码
wire time_up; // 计时器时间到信号
// 实例化分频器模块
clk_divider u_clk_divider(
.clk_in (clk),
.rst_n (rst_n),
.clk_out (clk_1hz)
);
// 实例化状态控制器模块
state_controller u_state_controller(
.clk (clk_1hz),
.rst_n (rst_n),
.emergency (emergency),
.current_state(current_state),
.time_up (time_up),
.led (led)
);
// 实例化计时器模块
timer u_timer(
.clk (clk_1hz),
.rst_n (rst_n),
.current_state(current_state),
.time_up (time_up)
);
endmodule
2 状态控制器模块 (state_controller.v)
// 状态控制器:核心逻辑
module state_controller(
input clk,
input rst_n,
input emergency,
input [3:0] current_state, // 从计时器或其他模块反馈回来的当前状态
input time_up, // 计时器时间到信号
output reg [5:0] led // LED控制信号
);
// 定义状态编码 (使用独热码 One-Hot 更安全,但这里用4位二进制码节省资源)
parameter S_A_GREEN = 4'b0001;
parameter S_A_YELLOW = 4'b0010;
parameter S_B_GREEN = 4'b0100;
parameter S_B_YELLOW = 4'b1000;
// 内部状态寄存器
reg [3:0] state_reg;
// 状态转移和输出逻辑 (同步时序逻辑)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state_reg <= S_A_GREEN; // 复位后进入A绿灯状态
end else if (emergency) begin
// 紧急模式:所有灯为红
state_reg <= 4'b0000;
led <= 6'b100001; // A红, B红
end else begin
case (state_reg)
S_A_GREEN: begin
if (time_up) begin
state_reg <= S_A_YELLOW;
led <= 6'b010001; // A黄, B红
end else begin
led <= 6'b001001; // A绿, B红
end
end
S_A_YELLOW: begin
if (time_up) begin
state_reg <= S_B_GREEN;
led <= 6'b100100; // A红, B绿
end else begin
led <= 6'b010001; // A黄, B红
end
end
S_B_GREEN: begin
if (time_up) begin
state_reg <= S_B_YELLOW;
led <= 6'b100010; // A红, B黄
end else begin
led <= 6'b100100; // A红, B绿
end
end
S_B_YELLOW: begin
if (time_up) begin
state_reg <= S_A_GREEN;
led <= 6'b001001; // A绿, B红
end else begin
led <= 6'b100010; // A红, B黄
end
end
default: begin // 包含紧急状态(4'b0000)和其他未知状态
state_reg <= S_A_GREEN;
led <= 6'b001001; // 默认回到A绿灯
end
endcase
end
end
endmodule
注意:上面的state_controller将状态寄存器和输出逻辑放在一个always块中,更紧凑,也可以将输出逻辑分离出来,形成摩尔型或米里型状态机。
3 计时器模块 (timer.v)
// 计时器模块:为每个状态倒计时
module timer(
input clk,
input rst_n,
input [3:0] current_state,
output reg time_up
);
// 定义各状态的持续时间 (单位:秒)
parameter T_A_GREEN = 30; // 主干道绿灯30秒
parameter T_A_YELLOW = 5; // 主干道黄灯5秒
parameter T_B_GREEN = 20; // 次干道绿灯20秒
parameter T_B_YELLOW = 5; // 次干道黄灯5秒
reg [7:0] counter; // 8位计数器,最大可计255秒,足够用
// 计数器逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
counter <= 8'd0;
time_up <= 1'b0;
end else begin
time_up <= 1'b0; // 默认不产生时间到信号
if (counter == 8'd1) begin // 当减到1时,下一个时钟周期就会到0
counter <= 8'd0;
time_up <= 1'b1; // 产生时间到脉冲
end else begin
counter <= counter - 1'b1;
end
end
end
// 根据当前状态加载计数值
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
counter <= 8'd0;
end else if (time_up) begin // 当时间到信号有效时,在下一个时钟周期重新加载
case (current_state)
4'b0001: counter <= T_A_GREEN; // S_A_GREEN
4'b0010: counter <= T_A_YELLOW; // S_A_YELLOW
4'b0100: counter <= T_B_GREEN; // S_B_GREEN
4'b1000: counter <= T_B_YELLOW; // S_B_YELLOW
default: counter <= 8'd0;
endcase
end
end
endmodule
4 分频器模块 (clk_divider.v)
假设系统时钟为50MHz,我们需要分频到1Hz。
- 分频系数 N = 50,000,000
- 计数器位数为
log2(50,000,000) ≈ 25.57,所以需要26位计数器。 - 计数器从0数到
N/2 - 1(24,999,999) 产生一个周期的翻转。
// 分频器模块:将50MHz分频为1Hz
module clk_divider(
input clk_in,
input rst_n,
output reg clk_out
);
reg [25:0] counter; // 26位计数器
always @(posedge clk_in or negedge rst_n) begin
if (!rst_n) begin
counter <= 26'd0;
clk_out <= 1'b0;
end else if (counter == 26'd24_999_999) begin // 50MHz / 2 - 1
counter <= 26'd0;
clk_out <= ~clk_out; // 翻转输出
end else begin
counter <= counter + 1'b1;
end
end
endmodule
仿真与测试
- 仿真工具: 使用ModelSim, Vivado Simulator, Quartus Prime Simulator等。
- 测试平台 (
tb_traffic_light.v)- 生成时钟信号(如50MHz)。
- 生成复位信号,初始化系统。
- 模拟时间的流逝(对于秒级仿真,可以加快仿真速度或将分频器模块替换成一个更快的虚拟计时器)。
- 在不同时间点,观察
led信号的输出是否符合预期。 - 模拟
emergency信号,观察系统是否立即响应。
仿真波形分析要点:
- 复位阶段:
rst_n为低时,led是否初始化为A绿, B红的状态? - 状态转移: 在每个状态持续设定的时间后,
led是否正确转移到下一个状态?(A绿灯 -> A黄灯 -> B绿灯...) - 紧急模式: 在任意状态下拉高
emergency,所有led是否立即变为红灯?当emergency取消后,系统是否从上一个状态继续运行(或回到初始状态,取决于设计)?
硬件实现与验证
- 开发环境: Vivado (Xilinx) 或 Quartus Prime (Intel/Altera)。
- 流程:
- 创建工程: 新建工程,选择目标FPGA芯片型号(如Basys3开发板的XC7A35T)。
- 代码输入: 将上述
.v文件添加到工程中。 - 综合与实现: 运行综合工具,将Verilog代码转换为网表,然后进行布局布线。
- 生成比特流: 生成最终可下载到FPGA的
.bit文件。 - 硬件连接:
- 将6个LED灯连接到FPGA的相应GPIO引脚(在约束文件
.xdc中定义)。 - 将一个拨码开关或按键连接到
emergency信号。 - 连接时钟和复位按键。
- 将6个LED灯连接到FPGA的相应GPIO引脚(在约束文件
- 下载与测试: 使用JTAG下载器将比特流烧录到FPGA中,观察实际硬件上的交通灯运行情况,并与仿真结果对比。
扩展功能实现思路
1 紧急车辆优先通行
已在state_controller.v中实现,只需将emergency信号作为最高优先级判断条件,当其为高时,强制state_reg进入一个特殊状态(如4'b0000),并输出A红, B红的信号。
2 倒计时显示
- 修改计时器模块: 计时器不再减到0就清零,而是继续减,直到0,并将计数值
counter输出。 - 创建BCD转换模块: 将8位二进制计数器的值转换为两个4位的BCD码(十位和个位)。
- 创建数码管显示模块:
- 使用动态扫描技术,快速轮流点亮4位或8位数码管,利用人眼视觉暂留效应,看起来所有数码管是同时点亮的。
- 该模块需要接收BCD码,并生成对应的段选信号(a-g, dp)和位选信号。
- 顶层模块连接: 将计时器的
counter值送入BCD转换模块,再将BCD码送入数码管显示模块。
3 车流量检测与动态调整
- 输入信号: 增加2个输入信号,模拟A、B方向的车流量传感器(
sensor_A,sensor_B),高电平表示有车排队。 - 修改状态机逻辑:
- 在
S_A_GREEN状态,除了检测time_up,还检测sensor_A是否为低电平(即A方向车流已空),如果为空,则提前进入S_A_YELLOW状态。 - 在
S_B_GREEN状态同理。 - 可以设置一个“最小绿灯时间”,防止绿灯时间过短。
- 在
设计总结
本设计成功利用Verilog HDL和FPGA技术,实现了一个模块化、可扩展的交通灯控制系统,通过状态机的设计,清晰地表达了交通灯的控制逻辑,整个设计流程覆盖了从方案论证、代码编写、功能仿真到硬件实现的全过程,充分体现了EDA技术在现代数字系统设计中的强大优势,通过增加扩展功能,可以进一步深化对FPGA编程和复杂系统设计的理解。