设计前言
掐指一算,学习FPGA也有好一段时间了,大一开始接触,到现在都研一了,跌跌撞撞,中途也断了好长时间没有继续学习,现在研究生阶段打算继续研究
FPGA相关的设计,因为爱好硬件设计,所以做这方面也更有兴趣,未来打算往SoC设计方向发展,有高手欢迎带我啊啊。以前的学习都是看别人的代码,拿着程序
烧进板子去看结果,很少会自己动手写程序,仿真测试,学到了现在,我有点领会了,学习FPGA,仿真验证设计才是真正掌握这门技术的核心,最后上板验证只是
水到渠成。因为一个复杂的设计都是许多模块组合成的,都要一个个验证逻辑正确了才能保证最后的结果达到要求。以前自己就是啥也不懂,写完就烧程序,编译
又长时间,学不到仿真的核心,现在打算重新学习一下,整理出来,写一写,方便自己查看,也希望能给刚入门的小伙伴一些小小的指导,互相学习。
设计要求
1.4个LED灯在1秒内轮流点亮,间隔250ms
2.一个轮流结束后,用一个四位加法器依次累加LED循环的次数,加法器数值从0~F循环
3.当第四盏LED熄灭后,改用这四盏LED灯同时点亮显示加法器中16进制的时间,点亮时间2s
初看这设计还挺简单的啊,于是我便开始写流水灯的程序,简单的循环流水用一个循环移位可以实现,用了一个位拼接的语法,直接放上程序和仿真结果。
module led_test
(
input clk, // 50M时钟
input rst_n, // 复位
output reg[3:0] led // 输出LED,低电平点亮
);
reg [23:0] cnt_250ms;
parameter TIME_250MS = 24'd12_500_000;//50MHz的晶振,周期为20ns,计数时间为250ms,
//则参数定义计算TIME_250MS = 250*1000000/20 = 12500000
always@(posedge clk or negedge rst_n)begin//250ms计数器,每隔250ms点亮一个灯
if (rst_n == 1'b0)
cnt_250ms <= 24'd0;
else if (cnt_250ms == TIME_250MS - 1) //计数满了清0
cnt_250ms <= 24'd0;
else
cnt_250ms <= cnt_250ms + 24'd1;
end
always@(posedge clk or negedge rst_n)begin
if (rst_n == 1'b0)
led <= 4'b1110;
else if (cnt_250ms == TIME_250MS - 1) //time counter count to 1st sec,LED1 lighten
led <= {led[2:0],led[3]}; //循环左移 每次把最高位移动到最低位,依次循环,
else //D4 D3 D2 D1 1110 -> 1101 -> 1011 -> 0111 -> 1110
led <= led;
end
endmodule
`timescale 1ns/1ns
`define clk_period 20
module led_test_tb;
reg clk;
reg rst_n;
wire [3:0]led;
led_test #( .TIME_250MS(6'd50)) led_test__uo(
.clk(clk),
.rst_n(rst_n),
.led(led)
);
initial clk= 1;
always#(`clk_period/2) clk = ~clk;
initial begin
rst_n = 1'b0;
#(`clk_period*10)
rst_n = 1'b1;
#(`clk_period*1000)
$stop;
end
endmodule
从仿真的结果来看实现了四盏灯的循环点亮,在仿真测试文件中参数例化了TIME_250MS,减少计数次数可以加快仿真速度,是编写测试文件中经常使用的
一种方法,保护源文件中的参数。接下来的工作就是继续统计流水的次数,然后通过十六进制的形式在LED灯上显示出来,开始打算继续在循环移位的基础上写
下去,但是实际仿真出来的结果总有一段时间的数据不对,就打算不采用移位的方式,简单粗暴一点的,采用类似线性序列机的形式。
module led_test
(
input Clk,
input Rst_n,
output reg[3:0] led
);
reg [31:0] cnt_48s;
reg[3:0] led_r;
parameter TIME_1S = 32'd50_000_000 - 1,
TIME_3S = 32'd150_000_000 -1,//0000
TIME_4S = 32'd200_000_000 -1,
TIME_6S = 32'd300_000_000 -1,//0001
TIME_7S = 32'd350_000_000 -1,
TIME_9S = 32'd450_000_000 -1,//0010
TIME_10S = 32'd500_000_000 -1,
TIME_12S = 32'd600_000_000 -1,//0011
TIME_13S = 32'd650_000_000 -1,
TIME_15S = 32'd750_000_000 -1,//0100
TIME_16S = 32'd800_000_000 -1,
TIME_18S = 32'd900_000_000 -1,//0101
TIME_19S = 32'd950_000_000 -1,
TIME_21S = 32'd1_050_000_000 -1,//0110
TIME_22S = 32'd1_100_000_000 -1,
TIME_24S = 32'd1_200_000_000 -1,//0111
TIME_25S = 32'd1_250_000_000 -1,
TIME_27S = 32'd1_350_000_000 -1,//1000
TIME_28S = 32'd1_400_000_000 -1,
TIME_30S = 32'd1_500_000_000 -1,//1001
TIME_31S = 32'd1_550_000_000 -1,
TIME_33S = 32'd1_650_000_000 -1,//1010
TIME_34S = 32'd1_700_000_000 -1,
TIME_36S = 32'd1_800_000_000 -1,//1011
TIME_37S = 32'd1_850_000_000 -1,
TIME_39S = 32'd1_950_000_000 -1,//1100
TIME_40S = 32'd2_000_000_000 -1,
TIME_42S = 32'd2_100_000_000 -1,//1101
TIME_43S = 32'd2_150_000_000 -1,
TIME_45S = 32'd2_250_000_000 -1,//1110
TIME_46S = 32'd2_300_000_000 -1,
TIME_48S = 32'd2_400_000_000 -1;//1111
parameter
TIME_250MS = 24'd12_500_000 -1,
TIME_500MS = 32'd25_000_000 -1,
TIME_750MS = 32'd37_500_000 -1;
always@(posedge Clk or negedge Rst_n)begin//完成整个过程需要48s
if (Rst_n == 1'b0)
cnt_48s <= 32'd0;
else if (cnt_48s == TIME_48S)
cnt_48s <= 32'd0;
else
cnt_48s <= cnt_48s + 32'd1;
end
always@(posedge Clk or negedge Rst_n)begin//取反输出,开发板LED是低电平点亮
if (Rst_n == 1'b0)
led <= 4'b1111;
else
led <= ~led_r;
end
//LED工作模式的触发信号,___| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|
// 1s 2s 1s 2s 1s 2s 1s 2s 1s 2s 1s 2s 1s 2s 1s 2s
always@(posedge Clk or negedge Rst_n)begin//在1s内让进行流水灯,在2s内十六进制显示流水灯的次数
if(Rst_n == 1'b0)
led_r <= 4'b0000;
else begin
case(cnt_48s)
0,TIME_3S,TIME_6S,TIME_9S,TIME_12S,TIME_15S,TIME_18S,TIME_21S,TIME_24S,
TIME_27S,TIME_30S,TIME_33S,TIME_36S,TIME_39S,TIME_42S,TIME_45S: led_r <= 4'b0001;
TIME_250MS,TIME_3S + TIME_250MS,TIME_6S + TIME_250MS,TIME_9S + TIME_250MS,
TIME_12S + TIME_250MS,TIME_15S + TIME_250MS,TIME_18S + TIME_250MS,
TIME_21S + TIME_250MS,TIME_24S + TIME_250MS,TIME_27S + TIME_250MS,
TIME_30S + TIME_250MS,TIME_33S + TIME_250MS,TIME_36S + TIME_250MS,
TIME_39S + TIME_250MS, TIME_42S + TIME_250MS, TIME_45S + TIME_250MS: led_r <= 4'b0010;
TIME_500MS,TIME_3S + TIME_500MS,TIME_6S + TIME_500MS,TIME_9S + TIME_500MS,
TIME_12S + TIME_500MS,TIME_15S + TIME_500MS,TIME_18S + TIME_500MS,
TIME_21S + TIME_500MS,TIME_24S + TIME_500MS,TIME_27S + TIME_500MS,
TIME_30S + TIME_500MS,TIME_33S + TIME_500MS,TIME_36S + TIME_500MS,
TIME_39S + TIME_500MS, TIME_42S + TIME_500MS, TIME_45S + TIME_500MS: led_r <= 4'b0100;
TIME_750MS,TIME_3S + TIME_750MS,TIME_6S + TIME_750MS,TIME_9S + TIME_750MS,
TIME_12S + TIME_750MS,TIME_15S + TIME_750MS,TIME_18S + TIME_750MS,
TIME_21S + TIME_750MS,TIME_24S + TIME_750MS,TIME_27S + TIME_750MS,
TIME_30S + TIME_750MS,TIME_33S + TIME_750MS,TIME_36S + TIME_750MS,
TIME_39S + TIME_750MS, TIME_42S + TIME_750MS, TIME_45S + TIME_750MS: led_r <= 4'b1000;
TIME_1S: led_r <= 4'b0000;//第0次
TIME_4S: led_r <= 4'b0001;//第1次
TIME_7S: led_r <= 4'b0010;//第2次
TIME_10S: led_r <= 4'b0011;//第3次
TIME_13S: led_r <= 4'b0100;//第4次
TIME_16S: led_r <= 4'b0101;//第5次
TIME_19S: led_r <= 4'b0110;//第6次
TIME_22S: led_r <= 4'b0111;//第7次
TIME_25S: led_r <= 4'b1000;//第8次
TIME_28S: led_r <= 4'b1001;//第9次
TIME_31S: led_r <= 4'b1010;//第10次
TIME_34S: led_r <= 4'b1011;//第11次
TIME_37S: led_r <= 4'b1100;//第12次
TIME_40S: led_r <= 4'b1101;//第13次
TIME_43S: led_r <= 4'b1110;//第14次
TIME_46S: led_r <= 4'b1111;//第15次
TIME_48S: led_r <= 4'b1111;
default:;
endcase
end
end
endmodule
思路很简单就是在明确的时间做该做的事情,很容易写出来,事实证明这种方法写出来的效率不高,用的资源也很多,于是思考着另一种方法,先把1s和2s的
那个波形图描述出来,然后通过一个加法器来统计到1s过渡到2s 的下降沿,也就是用到了题目设计给出的要求,这样的写法实际用到的资源比第一种写法要高效,
也省了不少资源,代码如下。
module led_test_v2
(
input clk, // 时钟
input rst_n, // 复位
output reg[3:0] led // 4个输出LED
);
reg [23:0] cnt_250ms;//250ms计数器
reg [25:0] cnt_1s;//1s计数器
reg [31:0] cnt_48s;//48s计数器
reg [3:0] cnt_waterfall;//4位计数器,用来计数流水灯次数,范围0~F
reg waterfall_flag;//LED标志信号,高电平表示进行流水灯,低电平表示显示当前第几次流水灯,总共有0~F次流水灯
reg waterfall_flag_r,waterfall_flag_rr;//信号打两拍,用于下降沿检测
wire nedge;//下降沿标志信号
reg [3:0] led_r;//将当前寄存器取反输出到LED引脚,因为开发板低电平点亮LED,取反后位高电平点亮,符合习惯而已
//时间参数的定义
parameter COUNT_16 = 4'd16,
TIME_250MS = 24'd12_500_000,//点亮LED1
TIME_500MS = 26'd25_000_000,//点亮LED2
TIME_750MS = 26'd37_500_000,//点亮LED3
TIME_1S = 32'd50_000_000,
TIME_3S = 32'd150_000_000,//0000
TIME_4S = 32'd200_000_000,
TIME_6S = 32'd300_000_000,//0001
TIME_7S = 32'd350_000_000,
TIME_9S = 32'd450_000_000,//0010
TIME_10S = 32'd500_000_000,
TIME_12S = 32'd600_000_000,//0011
TIME_13S = 32'd650_000_000,
TIME_15S = 32'd750_000_000,//0100
TIME_16S = 32'd800_000_000,
TIME_18S = 32'd900_000_000,//0101
TIME_19S = 32'd950_000_000,
TIME_21S = 32'd1_050_000_000,//0110
TIME_22S = 32'd1_100_000_000,
TIME_24S = 32'd1_200_000_000,//0111
TIME_25S = 32'd1_250_000_000,
TIME_27S = 32'd1_350_000_000,//1000
TIME_28S = 32'd1_400_000_000,
TIME_30S = 32'd1_500_000_000,//1001
TIME_31S = 32'd1_550_000_000,
TIME_33S = 32'd1_650_000_000,//1010
TIME_34S = 32'd1_700_000_000,
TIME_36S = 32'd1_800_000_000,//1011
TIME_37S = 32'd1_850_000_000,
TIME_39S = 32'd1_950_000_000,//1100
TIME_40S = 32'd2_000_000_000,
TIME_42S = 32'd2_100_000_000,//1101
TIME_43S = 32'd2_150_000_000,
TIME_45S = 32'd2_250_000_000,//1110
TIME_46S = 32'd2_300_000_000,
TIME_48S = 32'd2_400_000_000;//1111
always@(posedge clk or negedge rst_n) //250ms实现
begin
if (rst_n == 1'b0)
cnt_250ms <= 24'd0;
else if (cnt_250ms == TIME_250MS - 1)
cnt_250ms <= 24'd0;
else
cnt_250ms <= cnt_250ms + 24'd1;
end
always@(posedge clk or negedge rst_n) //1000ms实现
begin
if (rst_n == 1'b0)
cnt_1s <= 24'd0;
else if (cnt_1s == TIME_1S - 1)
cnt_1s <= 24'd0;
else
cnt_1s <= cnt_1s + 24'd1;
end
always@(posedge clk or negedge rst_n) //48s实现 3*16=48
begin
if (rst_n == 1'b0)
cnt_48s <= 32'd0;
else if (cnt_48s == TIME_48S - 1)
cnt_48s <= 32'd0;
else
cnt_48s <= cnt_48s + 32'd1;
end
always@(posedge clk or negedge rst_n)begin
if (rst_n == 1'b0)
cnt_waterfall <= 4'd0;
else if(nedge)begin
if (cnt_waterfall == COUNT_16 - 1)
cnt_waterfall <= 4'd0;
else
cnt_waterfall <= cnt_waterfall + 4'd1;
end
end
always@(posedge clk or negedge rst_n)begin
if (rst_n == 1'b0)
led_r <= 4'b0000;
else if(waterfall_flag)begin
case(cnt_1s)
0: led_r <= 4'b0001; //点亮LED1
TIME_250MS-1: led_r <= 4'b0010; //点亮LED2
TIME_500MS-1: led_r <= 4'b0100; //点亮LED3
TIME_750MS-1: led_r <= 4'b1000; //点亮LED4
TIME_1S-1 : led_r <= 4'b0000; //全部熄灭
endcase
end
else if(nedge)begin//流水灯次数显示 第0次显示0000,第1次显示0001 ... 第15次显示1111
case(cnt_waterfall)
0: led_r <= 4'b0000;
1: led_r <= 4'b0001;
2: led_r <= 4'b0010;
3: led_r <= 4'b0011;
4: led_r <= 4'b0100;
5: led_r <= 4'b0101;
6: led_r <= 4'b0110;
7: led_r <= 4'b0111;
8: led_r <= 4'b1000;
9: led_r <= 4'b1001;
10: led_r <= 4'b1010;
11: led_r <= 4'b1011;
12: led_r <= 4'b1100;
13: led_r <= 4'b1101;
14: led_r <= 4'b1110;
15: led_r <= 4'b1111;
default:;
endcase
end
end
//LED工作模式的触发信号,___| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|
// 1s 2s 1s 2s 1s 2s 1s 2s 1s 2s 1s 2s 1s 2s 1s 2s
always@(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)
waterfall_flag <= 1'b1;
else begin
case(cnt_48s)
0,TIME_3S-1,TIME_6S-1,TIME_9S-1,TIME_12S-1,TIME_15S-1,
TIME_18S-1,TIME_21S-1,TIME_24S-1,TIME_27S-1,TIME_30S-1,
TIME_33S-1,TIME_36S-1,TIME_39S-1,TIME_42S-1,TIME_45S-1,TIME_48S-1: waterfall_flag <= 1'b1;
TIME_1S-1,TIME_4S-1,TIME_7S-1,TIME_10S-1,TIME_13S-1,
TIME_16S-1,TIME_19S-1,TIME_22S-1,TIME_25S-1,TIME_28S-1,
TIME_31S-1,TIME_34S-1,TIME_37S-1,TIME_40S-1,TIME_43S-1,TIME_46S-1: waterfall_flag <= 1'b0;
default:;
endcase
end
end
//waterfall_flag信号的下降沿检测
always@(posedge clk or negedge rst_n)begin
if (rst_n == 1'b0)begin
waterfall_flag_r <= 1'b0;
waterfall_flag_rr <= 1'b0;
end
else begin
waterfall_flag_r <= waterfall_flag;
waterfall_flag_rr <= waterfall_flag_r;
end
end
assign nedge = !waterfall_flag_r & waterfall_flag_rr;
//LED取反后输出
always@(posedge clk or negedge rst_n)begin
if (rst_n == 1'b0)
led <= 4'b1111;
else
led <= ~led_r;
end
endmodule
从仿真结果中可以看出统计流水灯次数从0~F可以显示,满足设计要求。`
从上面两个图来看第一个方法需要更多的逻辑单元,第二种方法可以节约逻辑单元,代码量也少,一开始写完程序自己就直接上板子看结果,结果发现现象
不对,看程序又看不出啥毛病,后来还是仿真得知了不正确的地方,以后还是多学习一下仿真啊,这次就写到这里,下次再分享啦!