1,实现的主要要求:
(1)东西方向红灯亮30s,南北方向绿灯亮;
(2)东西方向黄灯亮3s,南北方向绿灯亮;
(3)南北方向红灯亮30s,东西方向绿灯亮;
(4)南北方向黄灯亮3s,东西方向绿灯亮;
(5)回到(1),同时用数码管显示每个灯亮的剩余时间;
2,实现方案
电路的整体RTL图如下,
由上图可知,电路主要分为两个部分,一个是交通灯控制模块,其中状态的流转通过状态机实现,状态转移图如下:
另一个是数码管显示模块,通过两个数码管来显示剩余时间;
3,顶层模块代码
module traffic_top(
input clk,
input rst_n,
output [5:0]led,
output [1:0]seg_sel,
output [7:0]seg_led
);
wire en;
wire data;
traffic_led u1(
.clk (clk),
.rst_n (rst_n),
.led (led),
.time_cnt (data),
.en (en)
);
smg_display u2(
.clk (clk),
.rst_n (rst_n),
.data (data),
.en (en),
.seg_sel (seg_sel),
.seg_led (seg_led)
);
endmodule
4,traffic_led模块代码
module traffic_led(
input clk, //系统时钟
input rst_n, //复位信号
output reg[5:0] led, //从高到低分别表示x方向红黄绿,y方向红黄绿
output reg[4:0] time_cnt, //灯亮的时间的倒计时
output reg en //数码管显示使能信号
);
//状态定义,独热码
parameter x_pass = 4'b0001,
x_flash = 4'b0010,
y_pass = 4'b0100,
y_flash = 4'b1000;
parameter t1s = 27'd50_000_000; //1S时间在系统时钟下需要计数次数
reg [26:0] cnt; //1s计时计数器
//当前状态和次态
reg [3:0] state,nextstate;
//红灯和黄灯亮的时间
parameter time_x_pass = 6'd30,
time_x_flash = 6'd3,
time_y_pass = 6'd30,
time_y_flash = 6'd3;
//1s计数
always @(posedge clk,negedge rst_n)begin
if(!rst_n)
cnt <= 27'd0;
else if(cnt < t1s)
cnt <= cnt + 1'b1;
else
cnt <= 27'd0;
end
//状态转换
always @(posedge clk,negedge rst_n)begin
if(!rst_n)
state <= x_pass;
else
state <= nextstate;
end
//组合逻辑,描述状态的转换
always @* begin
if(!rst_n)
nextstate <= x_pass;
else begin
case(state)
x_pass:if(time_cnt == 1'b1) //当time_cnt 计数到1时,进入下一状态
nextstate <= x_flash;
else
nextstate <= x_pass;
x_flash:if(time_cnt == 1'b1)
nextstate <= y_pass;
else
nextstate <= x_flash;
y_pass:if(time_cnt == 1'b1)
nextstate <= y_flash;
else
nextstate <= y_pass;
y_flash:if(time_cnt == 1'b1)
nextstate <= x_pass;
else
nextstate <= y_flash;
default:nextstate <= x_pass;
endcase
end
end
//状态输出
always @(posedge clk,negedge rst_n)begin
if(!rst_n)begin //复位时,x方向红灯亮,y方向绿灯亮,且数码管不使能
led <= 6'b100001;
time_cnt <= time_x_pass;
en <= 1'b0;
end
else begin
en <= 1'b1;
if(cnt == t1s)
case(nextstate)
x_pass:begin
led <= 6'b100001;
if(time_cnt == 1'b1)
time_cnt <= time_x_pass;
else
time_cnt <= time_cnt - 1'b1;
end
x_flash:begin
led <= 6'b010001;
if(time_cnt == 1'b1)
time_cnt <= time_x_flash;
else
time_cnt <= time_cnt - 1'b1;
end
y_pass:begin
led <= 6'b001100;
if(time_cnt == 1'b1)
time_cnt <= time_y_pass;
else
time_cnt <= time_cnt - 1'b1;
end
y_flash:begin
led <= 6'b001010;
if(time_cnt == 1'b1)
time_cnt <= time_y_flash;
else
time_cnt <= time_cnt - 1'b1;
end
default:led <= 6'b100001;
endcase
end
end
endmodule
仿真文件及仿真结果
`timescale 1 ns/ 1 ns
module traffic_led_tb();
reg clk;
reg rst_n;
// wires
wire [5:0] led;
wire [5:0] time_cnt;
wire en;
// assign statements (if any)
traffic_led i1 (
// port map - connection between master ports and signals/registers
.clk (clk),
.led (led),
.rst_n (rst_n),
.time_cnt(time_cnt),
.en (en)
);
initial
begin
clk = 1'b0;
rst_n = 1'b0;
#20 rst_n = 1'b1;
end
always #2 clk = ~clk;
endmodule
5,数码管显示模块代码
module smg_display(
input clk,
input rst_n,
input [4:0] data, //要显示的数据
input en, //数码管使能信号
output reg [1:0] seg_sel, //位选
output reg [7:0] seg_led //段选
);
localparam CLK_DIV = 4'd10; //时钟分频系数,5MHZ的时钟,用于驱动数码管
localparam MAX_NUM = 13'd5000;//计数1ms
reg [3:0] clk_cnt; //时钟分频计数器
reg d_clk; //数码管驱动时钟
reg [7:0] num; //8位的bcd码寄存器
reg [12:0] cnt0; //数码管驱动时钟计数器
reg flag; //标记计数达到1ms
reg cnt_sel; //数码管位选计数器
reg [3:0] num_disp; //数码管当前显示数据
wire [3:0] data0; //个位
wire [3:0] data1; //十位
assign data0 = data%10;
assign data1 = data/10;
//对系统十分频,得到驱动时钟d_clk
always @(posedge clk,negedge rst_n)begin
if(!rst_n)begin
clk_cnt <= 4'd0;
d_clk <= 1'b0;
end
else if(clk_cnt == (CLK_DIV/2 - 1'b1))begin
clk_cnt <= 4'd0;
d_clk <= ~d_clk;
end
else begin
clk_cnt <= clk_cnt + 1'b1;
d_clk <= d_clk;
end
end
//将十进制数转换为用8421bcd码表示
always @(posedge d_clk,negedge rst_n)begin
if(!rst_n)
num <= 8'd0;
else begin
num[7:4] <= data1;
num[3:0] <= data0;
end
end
//每当计时1ms输出一个时钟周期的脉冲
always @(posedge d_clk,negedge rst_n) begin
if(!rst_n) begin
cnt0 <= 13'd0;
flag <= 1'b0;
end
else if(cnt0 < MAX_NUM - 1'b1) begin
cnt0 <= cnt0 + 1'b1;
flag <= 1'b0;
end
else begin
cnt0 <= 13'd0;
flag <= 1'b1;
end
end
//seg_sel在0,1之间转换,用于选择当前要显示的数码管
always @(posedge d_clk,negedge rst_n)begin
if(!rst_n)
cnt_sel <= 1'b0;
else if(flag)
cnt_sel <= ~cnt_sel;
else
cnt_sel <= cnt_sel;
end
//控制数码管位选信号,使两个数码管轮流显示
always @(posedge d_clk,negedge rst_n)begin
if(!rst_n) begin
seg_sel <= 2'b11;
num_disp <= 4'd0;
end
else begin
if(en)begin
case(cnt_sel)
1'b0:begin
seg_sel <= 2'b10;
num_disp <= num[3:0];
end
1'b1:begin
seg_sel <= 2'b01;
num_disp <= num[7:4];
end
default:begin
seg_sel <= 2'b11;
num_disp <= 4'd0;
end
endcase
end
else
begin
seg_sel <= 2'b11;
num_disp <= 4'd0;
end
end
end
//控制数码管段选信号,显示字符
always @(posedge d_clk,negedge rst_n)begin
if(!rst_n)
seg_led <= 8'hc0;
else begin
case(num_disp)
4'd0 : seg_led <= 8'b01000000; //显示数字 0
4'd1 : seg_led <= 8'b01111001; //显示数字 1
4'd2 : seg_led <= 8'b00100100; //显示数字 2
4'd3 : seg_led <= 8'b00110000; //显示数字 3
4'd4 : seg_led <= 8'b00011001; //显示数字 4
4'd5 : seg_led <= 8'b00010010; //显示数字 5
4'd6 : seg_led <= 8'b00000010; //显示数字 6
4'd7 : seg_led <= 8'b01111000; //显示数字 7
4'd8 : seg_led <= 8'b00000000; //显示数字 8
4'd9 : seg_led <= 8'b00010000; //显示数字 9
default:seg_led <= 8'b01000000;
endcase
end
end
endmodule
仿真文件及结果
`timescale 1 ns/ 1 ns
module smg_display_tb();
// test vector input registers
reg clk;
reg [4:0] data;
reg en;
reg rst_n;
// wires
wire [7:0] seg_led;
wire [1:0] seg_sel;
wire cnt_sel;
// assign statements (if any)
smg_display i1 (
// port map - connection between master ports and signals/registers
.clk (clk),
.data (data),
.en (en),
.rst_n (rst_n),
.seg_led (seg_led),
.seg_sel (seg_sel)
);
initial
begin
clk = 1'b0;
en = 1'b0;
data = 5'd0;
rst_n = 1'b0;
#20 rst_n = 1'b1;
en = 1'b1;
data = 5'd9;
end
always #1 clk = ~clk;
endmodule
6,总结与反思
再一次的受到了巨大打击,本以为这么简单的东西很快就能做好,但还是耗了不少时间,尤其是在最开始竟然没有丝毫的思路,最后还是借鉴了别人的思路并且只是实现了灯亮,没有能够实现灯的闪烁。在写的过程中很多细节的东西很容易的就忽略了,经常就是这样想得很简单,看别人的代码也觉得容易,一到自己做动手了,就会出现各种问题。最严重的感觉现在的思路和大脑的灵活度都不如以前了。以前刚学完verilog做数字中的时候,各个模块的共能以及模块之间的联系都是很清楚的,而现在对模块的理解以及记忆就远不如从前了。可能是因为太久的时间没有认真用心去学习了,导致大脑都有些退化了。
自己还是太菜了,差到我自己都不知道该怎么说,所以啊,后面还是要多写代码,多思考才行。