本文基于小梅哥的FPGA学习所得。
推荐一个更加完善的笔记
00.FPGA基本单元
可编程逻辑块
可编程IO块
可编程内部互联
PLL、DLL
块RAM存储器
数学运算单元
高速串行I/O
PCIE、DDR等硬核控制器
嵌入式处理器硬核()
01.科学FPGA开发流程
- 设计定义
- 设计输入
- 分析和综合(Quartus初学)
- 功能仿真(modelsim-altera)
- 设计约束
- 布局布线
- 时序仿真(modelsim-altera)
- IO分配以及配置文件的生成
- 配置(烧结FPGA)
- 在线调试(…)
设计定义:
二选一多路器
两个输入IO,可以是高电平,也可以是低电平,
输入按键按下时,LED灯与a端口保持一致。
输入按键释放时,LED灯与b端口状态保持一致。
led_test.v
module led_test(a,b,key_in,led_out);
input a;//输入端口A
input b;//输入端口B
input key_in;//按键输入,实现输入通道的选择
output led_out;//led控制端口
//当key——in == 0;led_out = a;
assign led_out = (key_in == 0) ? a : b;
endmodule
- 编译快捷键ctrl+k
- 模块名和信号名不要有冲突
led_test_tb.v
`timescale 1ns/1ps
module led_test_tb;
//激励信号定义,对应连接到待测模块的输入端口
reg signal_a;
reg signal_b;
reg signal_c;
//待检测信号定义,对应连接到待测模块的输出端口
wire led;
//例化待测试模块
led_test led_test0(
.a(signal_a),
.b(signal_b),
.key_in(signal_c),
.led_out(led)
);
//产生激励
initial begin
signal_a = 0;signal_b = 0;signal_c = 0;
#100//延时100ns
signal_a = 0;signal_b = 0;signal_c = 1;
#100
signal_a = 0;signal_b = 1;signal_c = 0;
#100
signal_a = 0;signal_b = 1;signal_c = 1;
#100
signal_a = 1;signal_b = 0;signal_c = 0;
#100
signal_a = 1;signal_b = 0;signal_c = 1;
#100
signal_a = 1;signal_b = 1;signal_c = 0;
#100
signal_a = 1;signal_b = 1;signal_c = 1;
#200
$stop;
end
endmodule
功能仿真(前仿真)——RTL Simulation
门级仿真(时序仿真、后仿真)——Gate Level Simulation
与前仿真相比,存在延迟和脉冲
管脚分配pin planner
02.3-8译码器设计与验证
A | B | C | out |
---|---|---|---|
0 | 0 | 0 | 0000_0001 |
0 | 0 | 1 | 0000_0010 |
0 | 1 | 0 | 0000_0100 |
0 | 1 | 1 | 0000_1000 |
1 | 0 | 0 | 0001_0000 |
1 | 0 | 1 | 0010_0000 |
1 | 1 | 0 | 0100_0000 |
1 | 1 | 1 | 1000_0000 |
my3_8.v
module my3_8(a,b,c,out);
input a;//输入端口A
input b;//输入端口B
input c;//输入端口C
output [7:0] out;//输出端口
reg [7:0] out;//always块中生成的必须用reg型
always@(a,b,c)begin
case({a,b,c})
3'b000:out = 8'b0000_0001;
3'b001:out = 8'b0000_0010;
3'b010:out = 8'b0000_0100;
3'b011:out = 8'b0000_1000;
3'b100:out = 8'b0001_0000;
3'b100:out = 8'b0010_0000;
3'b110:out = 8'b0100_0000;
3'b111:out = 8'b1000_0000;
//default: out = 8'b1000_0000;
endcase
end
endmodule
my3_8_tb.v
`timescale 1ns/1ps
module my3_8_tb;
reg a;
reg b;
reg c;
wire [7:0] out;
my3_8 u1(
.a(a),
.b(b),
.c(c),
.out(out)
);
initial begin
a = 0;b = 0;c = 0;
#200
a = 0;b = 0;c = 1;
#200
a = 0;b = 1;c = 0;
#200
a = 0;b = 1;c = 1;
#200
a = 1;b = 0;c = 0;
#200
a = 1;b = 0;c = 1;
#200
a = 1;b = 1;c = 0;
#200
a = 1;b = 1;c = 1;
#200
$stop;
end
endmodule
功能仿真(前仿真)
门级仿真(时序仿真、后仿真)
- 上面标记的地方有下面这种情况(竞争冒险?):
错误:modelsim只支持打开一次,如果未关闭打开则会报错如下:
02. 4-16译码器(对比3-8译码器练习)
A | B | C | D | out |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0000_0000_0000_0001 |
0 | 0 | 0 | 1 | 0000_0000_0000_0010 |
0 | 0 | 1 | 0 | 0000_0000_0000_0100 |
0 | 0 | 1 | 1 | 0000_0000_0000_1000 |
0 | 1 | 0 | 0 | 0000_0000_0001_0000 |
0 | 1 | 0 | 1 | 0000_0000_0010_0000 |
0 | 1 | 1 | 0 | 0000_0000_0100_0000 |
0 | 1 | 1 | 1 | 0000_0000_1000_0000 |
1 | 0 | 0 | 0 | 0000_0001_0000_0000 |
1 | 0 | 0 | 1 | 0000_0010_0000_0000 |
1 | 0 | 1 | 0 | 0000_0100_0000_0000 |
1 | 0 | 1 | 1 | 0000_1000_0000_0000 |
1 | 1 | 0 | 0 | 0001_0000_0000_0000 |
1 | 1 | 0 | 1 | 0010_0000_0000_0000 |
1 | 1 | 1 | 0 | 0100_0000_0000_0000 |
1 | 1 | 1 | 1 | 1000_0000_0000_0000 |
my4_16.v
module my4_16(a,b,c,d,out);
input a;
input b;
input c;
input d;
output [15:0]out;
reg [15:0]out;
always@(a,b,c,d)begin
case({a,b,c,d})
4'b0000 :out = 16'b0000_0000_0000_0001;
4'b0001 :out = 16'b0000_0000_0000_0010;
4'b0010 :out = 16'b0000_0000_0000_0100;
4'b0011 :out = 16'b0000_0000_0000_1000;
4'b0100 :out = 16'b0000_0000_0001_0000;
4'b0101 :out = 16'b0000_0000_0010_0000;
4'b0110 :out = 16'b0000_0000_0100_0000;
4'b0111 :out = 16'b0000_0000_1000_0000;
4'b1000 :out = 16'b0000_0001_0000_0000;
4'b1001 :out = 16'b0000_0010_0000_0000;
4'b1010 :out = 16'b0000_0100_0000_0000;
4'b1011 :out = 16'b0000_1000_0000_0000;
4'b1100 :out = 16'b0001_0000_0000_0000;
4'b1101 :out = 16'b0010_0000_0000_0000;
4'b1110 :out = 16'b0100_0000_0000_0000;
4'b1111 :out = 16'b1000_0000_0000_0000;
endcase
end
endmodule
my4_16_tb.v
`timescale 1ns/1ps
module my4_16_tb;
reg a;
reg b;
reg c;
reg d;
wire [15:0] out;
my4_16 my4_16_inst(
.a (a ),
.b (b ),
.c (c ),
.d (d ),
.out (out)
);
initial begin
a = 0;b = 0;c = 0;d = 0;
#200
a = 0;b = 0;c = 0;d = 1;
#200
a = 0;b = 0;c = 1;d = 0;
#200
a = 0;b = 0;c = 1;d = 1;
#200
a = 0;b = 1;c = 0;d = 0;
#200
a = 0;b = 1;c = 0;d = 1;
#200
a = 0;b = 1;c = 1;d = 0;
#200
a = 0;b = 1;c = 1;d = 1;
#200
a = 1;b = 0;c = 0;d = 0;
#200
a = 1;b = 0;c = 0;d = 1;
#200
a = 1;b = 0;c = 1;d = 0;
#200
a = 1;b = 0;c = 1;d = 1;
#200
a = 1;b = 0;c = 0;d = 0;
#200
a = 1;b = 0;c = 0;d = 1;
#200
a = 1;b = 0;c = 1;d = 0;
#200
a = 1;b = 0;c = 1;d = 1;
#200
$stop;
end
endmodule
- 在仿真中,任何信号都要赋给初始值
前仿真(功能仿真)
后仿真
- 后仿真和之前一样,也存在延迟和上电的高阻。
语法乱记
模块调用
综合与不可综合
task中只要没有不可综合语句(如延时#),就可以综合!
initial也未必,
时序逻辑:
有时钟和存储能力(reg),计数器是最简单的时序逻辑(时序逻辑中也需要组合逻辑的配合才有意义)。
03.计数器
LED,每500ms,状态翻转一次,
系统时钟为50M,对应周期为20ns
500ms = 500_000_000ns/20 = 25_000_000;
电路原理图
counter.v
module counter(Clk50M,Rst_n,led);
input Clk50M;//系统时钟,50M
input Rst_n;//全局复位,低电平复位
output reg led;//led输出,always块中的变量都要声明为reg型
reg [24:0] cnt;//定义计数器寄存器
//计数器计数进程
always@(posedge Clk50M or negedge Rst_n)begin
if(Rst_n == 1'b0)
cnt <= 25'd0;
//else if(cnt == 25'd24_999_999)
else if(cnt == 25'd24_999)
cnt <= 25'd0;
else
cnt <= cnt + 1'b1;
end
//led输出控制进程
always@(posedge Clk50M or negedge Rst_n)begin
if(Rst_n == 1'b0)
led <= 1'b1;
//else if(cnt == 25'd24_999_999)
else if(cnt == 25'd24_999)
led <= ~led;
else
led <= led;
end
endmodule
couter_tb.v
`timescale 1ns/1ps
`define clock_period 20
module counter_tb;
reg clk;
reg rst_n;
wire led;
counter counter0(
.Clk50M(clk),
.Rst_n(rst_n),
.led(led)
);
initial clk = 1;
always #(`clock_period/2) clk = ~clk;
initial begin
rst_n = 1'b0;
#(`clock_period *200);
rst_n = 1'b1;
#2000000000;
$stop;
end
endmodule
前仿真(功能仿真)
后仿真(时序仿真、门级仿真)
IO管脚分配
04.计数器IP核调用与验证
四位计数器 (Quartus II 提供的LPM_counter IP核的使用)
- FPGA设计方式:
原理图(不推荐)
Verilog HDL设计方式
IP核输入方式
调用步骤
counter.v
`timescale 1 ps / 1 ps
// synopsys translate_on
module counter (
cin,
clock,
cout,
q);
input cin;
input clock;
output cout;
output [3:0] q;
wire sub_wire0;
wire [3:0] sub_wire1;
wire cout = sub_wire0;
wire [3:0] q = sub_wire1[3:0];
lpm_counter LPM_COUNTER_component (
.cin (cin),
.clock (clock),
.cout (sub_wire0),
.q (sub_wire1),
.aclr (1'b0),
.aload (1'b0),
.aset (1'b0),
.clk_en (1'b1),
.cnt_en (1'b1),
.data ({4{1'b0}}),
.eq (),
.sclr (1'b0),
.sload (1'b0),
.sset (1'b0),
.updown (1'b1));
defparam
LPM_COUNTER_component.lpm_direction = "UP",
LPM_COUNTER_component.lpm_modulus = 10,
LPM_COUNTER_component.lpm_port_updown = "PORT_UNUSED",
LPM_COUNTER_component.lpm_type = "LPM_COUNTER",
LPM_COUNTER_component.lpm_width = 4;
endmodule
tb_counter.v
`timescale 1ns/1ns
`define clock_period 20
module counter_tb;
reg cin;//进位输入
reg clk;//计数基准时钟
wire cout;//进位输出
wire [3:0] q;
counter counter0(
.cin(cin),
.clock(clk),
.cout(cout),
.q(q)
);
initial clk = 1;
always #(`clock_period/2) clk = ~clk;
initial begin
repeat(5)begin
cin = 0;
#(`clock_period *5) cin = 1;
#(`clock_period ) cin = 0;
end
#(`clock_period *200);
$stop;
end
endmodule
前仿真(功能仿真)
- 把counter_tb.v中的repeat(5)改为repeat(10),出现如下的仿真结果:(记得选无符号显示)
- 这个IP核的这个学习小节中,我们开始设置了计数到10,其实我们还可以计数到满(四位计数到4’b1111),下面将重新配置
- 这里的功能仿真如下:
八位计数器(四位计数器拼接升级)
前面的例码,说的是4位的,如果说要8位的,该怎么办?
一种是:在设计代码中,将位宽设置为8位的;还有一种就是将两个四位的拼接,原理图如下:
功能仿真
- 当然,肯定是直接在设计中修改为8位位宽比较方便,如果继续同这节课开始,把计数器设置回10的时候,继续仿真,就会出现如下的效果,引出BCD计数器
05. BCD计数器设计与使用
158
158/100 = 1
158%100 = 5
58/10 = 5
158%10 = 8
BCD计数器
BCD计数器基本模块
BCD_counter.v
module BCD_counter(Clk, Cin, Rst_n, Cout, q );
input Clk;//计数基准时钟
input Cin;//计数器进位输入
input Rst_n;//系统复位
output reg Cout;//计数器进位输出
output [3:0] q;//计数器输出
reg [3:0] cnt;//定义计数器寄存器
//执行计数过程
always@(posedge Clk or negedge Rst_n)
if(Rst_n == 1'b0)
cnt <= 4'd0;
else if(Cin == 1'b1) begin
if(cnt == 4'd9)
cnt <= 4'd0;
else
cnt <= cnt + 4'd1;
end
else
cnt <= cnt;
//产生进位输出信号
always@(posedge Clk or negedge Rst_n)
if(Rst_n == 1'b0)
Cout = 1'b0;
else if(Cin == 1'b1 && cnt == 4'd9 )
Cout <= 1'b1;
else Cout <= 1'b0;
assign q = cnt;
endmodule
BCD_counter_tb.v
`timescale 1ns/1ns
`define clock_period 20
module BCD_counter_tb;
reg clk;
reg cin;
reg rst_n;
wire cout;
wire [3:0]q;
BCD_counter BCD_counter_inst(
.Clk(clk),
.Cin(cin),
.Rst_n(rst_n),
.Cout(cout),
.q(q)
);
initial clk = 1'b1;
always #(`clock_period/2) clk = ~clk;
initial begin
rst_n = 1'b0;
cin = 1'b0;
#(`clock_period *200);
rst_n = 1'b1;
#(`clock_period *20);
repeat(30)begin
cin = 1'b1;
#`clock_period;
cin = 1'b0;
#(`clock_period *5);
end
#(`clock_period *20);
$stop;
end
endmodule
功能仿真
BCD计数器综合(由模块组成top)
按照上节课的套路,这次按照12位输出,也就是需要例化三次counter
BCD_counter_top.v
module BCD_counter_top(Clk, Cin, Rst_n, Cout, q );
input Clk;//计数基准时钟
input Cin;//计数器进位输入
input Rst_n;//系统复位
output Cout;//计数器进位输出
output [11:0] q;//计数器输出
wire Cout1;
wire Cout2;
wire [3:0] q0,q1,q2;
assign q = {q2,q1,q0};
BCD_counter BCD_counter_inst0(
.Clk(Clk),
.Cin(Cin),
.Rst_n(Rst_n),
.Cout(Cout1),
.q(q0)
);
BCD_counter BCD_counter_inst1(
.Clk(Clk),
.Cin(Cout1),
.Rst_n(Rst_n),
.Cout(Cout2),
.q(q1)
);
BCD_counter BCD_counter_inst2(
.Clk(Clk),
.Cin(Cout2),
.Rst_n(Rst_n),
.Cout(Cout),
.q(q2)
);
endmodule
BCD_counter_top_tb.v
`timescale 1ns/1ns
`define clock_period 20
module BCD_counter_top_tb;
reg clk;
reg cin;
reg rst_n;
wire cout;
wire [11:0]q;
BCD_counter_top BCD_counter_top_inst(
.Clk(clk),
.Cin(cin),
.Rst_n(rst_n),
.Cout(cout),
.q(q)
);
initial clk = 1'b1;
always #(`clock_period/2) clk = ~clk;
initial begin
rst_n = 1'b0;
cin = 1'b0;
#(`clock_period *200);
rst_n = 1'b1;
#(`clock_period *20);
cin = 1'b1;
#(`clock_period *5000);
$stop;
end
endmodule
功能仿真(发现错误)
修改后的BCD_counter.v
module BCD_counter(Clk, Cin, Rst_n, Cout, q );
input Clk;//计数基准时钟
input Cin;//计数器进位输入
input Rst_n;//系统复位
output Cout;//计数器进位输出
output [3:0] q;//计数器输出
reg [3:0] cnt;//定义计数器寄存器
//执行计数过程
always@(posedge Clk or negedge Rst_n)
if(Rst_n == 1'b0)
cnt <= 4'd0;
else if(Cin == 1'b1) begin
if(cnt == 4'd9)
cnt <= 4'd0;
else
cnt <= cnt + 1'b1;
end
else
cnt <= cnt;
//产生进位输出信号
assign Cout = (Cin == 1'b1 && cnt == 4'd9 );
assign q = cnt;
endmodule
修改后的功能仿真
07.阻塞赋值与非阻塞赋值
这节课,为了讲述阻塞与非阻塞,感受一下非阻塞与阻塞之间的区别,
将 out <= a+b+c 设计拆分为:
d <= a+b;
out <= c+d;
当然,实际过程中,就是直接用out = a+b+c即可,不用再引入寄存器增加延迟。
block_nonblock.v(第一种)
module block_nonblock(Clk,Rst_n,a,b,c,out);
input Clk;
input Rst_n;
input a;
input b;
input c;
output reg [1:0] out;
//out = a + b + c;
//d = a + b;
//out = d + c;
reg [1:0]d;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
out = 2'b0;
else begin
d = a + b;
out = d + c;
end
endmodule
综合之后的电路原理图
block_nonblock.v(第二种)
module block_nonblock(Clk,Rst_n,a,b,c,out);
input Clk;
input Rst_n;
input a;
input b;
input c;
output reg [1:0] out;
//out = a + b + c;
//d = a + b;
//out = d + c;
reg [1:0]d;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
out = 2'b0;
else begin
out = d + c;
d = a + b;
end
endmodule
综合之后的电路原理图(第二种)
block_nonblock.v(第三种)
module block_nonblock(Clk,Rst_n,a,b,c,out);
input Clk;
input Rst_n;
input a;
input b;
input c;
output reg [1:0] out;
//out = a + b + c;
//d = a + b;
//out = d + c;
reg [1:0]d;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
out <= 2'b0;
else begin
out <= d + c;
d <= a + b;
end
endmodule
综合之后的电路原理图(第三种)
block_nonblock.v(第四种)
module block_nonblock(Clk,Rst_n,a,b,c,out);
input Clk;
input Rst_n;
input a;
input b;
input c;
output reg [1:0] out;
//out = a + b + c;
//d = a + b;
//out = d + c;
reg [1:0]d;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
out <= 2'b0;
else begin
d <= a + b;
out <= d + c;
end
endmodule
综合之后的电路原理图(第四种)
测试平台block_nonblock_tb.v
`timescale 1ns/1ns
`define clock_period 20
module block_nonblock_tb;
reg Clock;
reg Rst_n;
reg a,b,c;
wire [1:0]out;
block_nonblock block_nonblock0(Clock,Rst_n,a,b,c,out);
initial Clock = 1'b1;
always #(`clock_period/2) Clock = ~Clock;
initial begin
Rst_n = 1'b0;
a = 0;
b = 0;
c = 0;
#(`clock_period*200 +1);
#(`clock_period*200);
Rst_n = 1'b1;
a = 0;b = 0;c = 0;
#(`clock_period*200);
a = 0;b = 0;c = 1;
#(`clock_period*200);
a = 0;b = 1;c = 0;
#(`clock_period*200);
a = 0;b = 1;c = 1;
#(`clock_period*200);
a = 1;b = 0;c = 0;
#(`clock_period*200);
a = 1;b = 0;c = 1;
#(`clock_period*200);
a = 1;b = 1;c = 0;
#(`clock_period*200);
a = 1;b = 1;c = 1;
#(`clock_period*200);
$stop;
end
endmodule
第四种的功能仿真
block_nonblock.v(在第四种的基础上添加了延迟)
`timescale 1ns/1ns
`define tp 1
module block_nonblock(Clk,Rst_n,a,b,c,out);
input Clk;
input Rst_n;
input a;
input b;
input c;
output reg [1:0] out;
//out = a + b + c;
//d = a + b;
//out = d + c;
reg [1:0]d;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
out <= #`tp 2'b0;
else begin
d <= #`tp a + b;
out <= #`tp d + c;
end
endmodule
对应的功能仿真
后仿真(这里去掉延迟,用第四种设计代码)
从这里可以考虑一下:时序分析常识
不推荐使用阻塞赋值的原因如下:
08.状态机的设计思想
(工作日,闹钟响起)
- 06:00~09:00 |(起床后的整理以及工作前的准备)
询问是否需要上班——是(工作),不是(其他) - 09:00~18:00|(工作)
是否需要加班——需要加班(工作),不需要工作(回家) - 18:00~22:00|(下班回家、做饭、吃饭)
是否需要休息——是(休息),不是(其他) - 22:00~06:00|(睡觉)
序列检测的一个例子
Hello.v
module Hello(Clk,Rst_n,data,led);
input Clk;//50M
input Rst_n;//
input [7:0]data;
output reg led;
localparam
CHECk_H = 5'b0_0001,
CHECk_e = 5'b0_0010,
CHECk_la = 5'b0_0100,
CHECk_lb = 5'b0_1000,
CHECk_o = 5'b1_0000;
reg [4:0] state;
//一段式状态机、两段式状态机、三段式状态机
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
led <= 1'b1;
state <= CHECk_H;
end
else begin
case(state)
CHECk_H:
if(data == "H")//这是可综合的
state <= CHECk_e;
else
state <= CHECk_H;
CHECk_e:
if(data == "e")
state <= CHECk_la;
else
state <= CHECk_H;
CHECk_la:
if(data == "l")
state <= CHECk_lb;
else
state <= CHECk_H;
CHECk_lb:
if(data == "l")
state <= CHECk_o;
else
state <= CHECk_H;
CHECk_o:
begin
state <= CHECk_H;
if(data == "o")
led <= ~led;
else led <=led;
end
default:state <= CHECk_H;
endcase
end
endmodule
Hello_tb.v
`timescale 1ns/1ns
`define clock_period 20
module Hello_tb;
reg Clk;
reg Rst_n;
reg [7:0]ASCII;
wire led;
Hello Hello(
.Clk(Clk),
.Rst_n(Rst_n),
.data(ASCII),
.led(led)
);
initial Clk = 1'b1;
always #(`clock_period/2) Clk = ~Clk;
initial begin
Rst_n = 1'b0;
#(`clock_period*200);
Rst_n = 1;
#(`clock_period*200);
forever begin
ASCII = "I";
#(`clock_period);
ASCII = "A";
#(`clock_period);
ASCII = "M";
#(`clock_period);
ASCII = "X";
#(`clock_period);
ASCII = "i";
#(`clock_period);
ASCII = "a";
#(`clock_period);
ASCII = "o";
#(`clock_period);
ASCII = "M";
#(`clock_period);
ASCII = "e";
#(`clock_period);
ASCII = "i";
#(`clock_period);
ASCII = "g";
#(`clock_period);
ASCII = "e";
#(`clock_period);
ASCII = "H";
#(`clock_period);
ASCII = "E";
#(`clock_period);
ASCII = "M";
#(`clock_period);
ASCII = "l";
#(`clock_period);
ASCII = "H";
#(`clock_period);
ASCII = "E";
#(`clock_period);
ASCII = "L";
#(`clock_period);
ASCII = "L";
#(`clock_period);
ASCII = "O";
#(`clock_period);
ASCII = "H";
#(`clock_period);
ASCII = "e";
#(`clock_period);
ASCII = "l";
#(`clock_period);
ASCII = "l";
#(`clock_period);
ASCII = "o";
#(`clock_period);
ASCII = "l";
end
end
endmodule
功能仿真
门级仿真
- 对比功能仿真,发现led状态并没有发生立即的翻转,而是在采集到的时钟沿后面一段时间才翻转,这是符合真实电路的。因此建议在功能仿真时,建议在testbench中加上一定的延时,和后仿真贴近。在tb中修改成如下的格式:
Rst_n = 1;
#(`clock_period*200 + 1);
添加延迟后的功能仿真
09.A按键消抖模式设计与验证
独立按键消抖实验
课程目标:复习状态机的设计思想
实验平台:Snow Dream starter board 核心板
实验现象:每次按下键0,4个LED显示状态以二进制加法格式加1,每次按下按键1,4个LED显示状态以二进制加法格式减1
独立按键的物理模型
知识点:
- testbench 中随机数发生函数$random的使用;
- 仿真模型的概念;
未按下时空闲状态(IDLE)
按下抖动滤除状态(FILTER)
按下稳定状态(DOWN)
释放抖动滤除状态(FILTER)
消抖模型
边缘检测
key_filter.v
module key_filter(Clk,Rst_n,key_in,key_flag,key_state);
input Clk;
input Rst_n;
input key_in;
output reg key_flag;
output reg key_state;
localparam
IDLE = 4'b0001,
FILTER0 = 4'b0010,
DOWN = 4'b0100,
FILTER1 = 4'b1000;
reg [3:0] state;
reg [19:0] cnt;
reg en_cnt;//使能计数寄存器
//50_000_000 20ns
//20ms=20_000_000ns ,则需要计数1_000_000即为20ms
reg key_tmp0,key_tmp1;
wire pedge,nedge;
reg cnt_full;//计数满表示信号
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
key_tmp0 <= 1'b0;
key_tmp1 <= 1'b0;
end
else begin
key_tmp0 <= key_in;
key_tmp1 <= key_tmp0;
end
assign nedge = !key_tmp0 & key_tmp1;
assign pedge = key_tmp0 &(!key_tmp1);
//assign pedge = key_tmp0 &(~key_tmp1);
//~0110 = 1001
//!0110 = 0000
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
en_cnt <= 1'b0;
state <= IDLE;
key_flag <=1'b0;
key_state <= 1'b1;
end
else begin
case(state)
IDLE:
begin
key_flag <= 1'b0;
if(nedge)begin
state <= FILTER0;
en_cnt <= 1'b1;
end
else
state <= IDLE;
end
FILTER0:
begin
if(cnt_full)begin
key_flag <=1'b1;
key_state <= 1'b0;
state <= DOWN;
en_cnt <= 1'b0;
end
else if(pedge)begin
state <= IDLE;
en_cnt <= 1'b0;
end
else state <= FILTER0;
end
DOWN:
begin
key_flag <= 1'b0;
if(pedge)begin
state <= FILTER1;
en_cnt <= 1'b1;
end
else
state <= DOWN;
end
FILTER1:
begin
if(cnt_full)begin
key_flag <=1'b1;
key_state <= 1'b1;
state <= IDLE;
end
else if(nedge)begin
en_cnt <= 1'b0;
state <= DOWN;
end
else
state <= FILTER1;
end
default:
begin
state <= IDLE;
en_cnt <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end
endcase
end
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
cnt <= 20'd0;
else if (en_cnt)
cnt <= cnt + 1'b1;
else
cnt <= 20'd0;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
cnt_full <= 1'b0;
else if (cnt == 999_999)
cnt_full <= 1'b1;
else
cnt_full <= 1'b0;
endmodule
综合之后得到的状态图
key_filter_tb.v
`timescale 1ns/1ns
`define clk_period 20
module key_filter_tb;
reg Clk;
reg Rst_n;
reg key_in;
wire key_flag;
wire key_state;
key_filter key_filter0(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
initial Clk = 1'b1;
always #(`clk_period/2) Clk = ~Clk;
initial begin
Rst_n = 1'b0;
key_in = 1'b1;
#(`clk_period *10) Rst_n = 1'b1;
#(`clk_period *10 +1);
//第一次
key_in = 0; #1000;
key_in = 1; #2000;
key_in = 0; #1400;
key_in = 1; #2600;
key_in = 0; #1300;
key_in = 1; #200;
key_in = 0; #20000100;
#50000100;
key_in = 1; #2000;
key_in = 0; #1000;
key_in = 1; #2000;
key_in = 0; #1400;
key_in = 1; #2600;
key_in = 0; #1300;
key_in = 1; #20000100;
#50000100;
//第二次
key_in = 0; #1000;
key_in = 1; #2000;
key_in = 0; #1400;
key_in = 1; #2600;
key_in = 0; #1300;
key_in = 1; #200;
key_in = 0; #20000100;
#50000100;
key_in = 1; #2000;
key_in = 0; #1000;
key_in = 1; #2000;
key_in = 0; #1400;
key_in = 1; #2600;
key_in = 0; #1300;
key_in = 1; #20000100;
#50000100;
//第三次
key_in = 0; #1000;
key_in = 1; #2000;
key_in = 0; #1400;
key_in = 1; #2600;
key_in = 0; #1300;
key_in = 1; #200;
key_in = 0; #20000100;
#50000100;
key_in = 1; #2000;
key_in = 0; #1000;
key_in = 1; #2000;
key_in = 0; #1400;
key_in = 1; #2600;
key_in = 0; #1300;
key_in = 1; #20000100;
#50000100;
//第四次
key_in = 0; #1000;
key_in = 1; #2000;
key_in = 0; #1400;
key_in = 1; #2600;
key_in = 0; #1300;
key_in = 1; #200;
key_in = 0; #20000100;
#50000100;
key_in = 1; #2000;
key_in = 0; #1000;
key_in = 1; #2000;
key_in = 0; #1400;
key_in = 1; #2600;
key_in = 0; #1300;
key_in = 1; #20000100;
#50000100;
$stop;
end
endmodule
功能仿真
- 向上的箭头:按下抖动
- 向下的箭头:释放抖动
通过$random简化测试平台
随机数发生函数
`timescale 1ns/1ns
`define clk_period 20
module key_filter_tb;
reg Clk;
reg Rst_n;
reg key_in;
wire key_flag;
wire key_state;
key_filter key_filter0(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
initial Clk = 1'b1;
always #(`clk_period/2) Clk = ~Clk;
initial begin
Rst_n = 1'b0;
key_in = 1'b1;
#(`clk_period *10) Rst_n = 1'b1;
#(`clk_period *10 +1);
press_key;
#10000;
press_key;
#10000;
press_key;
$stop;
end
reg [15:0] myrand;
task press_key;
begin
//按下抖动过程
repeat(50)begin
myrand = {$random}%65536;//0-65535;
#myrand key_in = ~key_in;
end
key_in = 0;
#50000000;
//释放抖动过程
repeat(50)begin
myrand = {$random}%65536;//0-65535;
#myrand key_in = ~key_in;
end
key_in = 1;
#50000000;
end
endtask
endmodule
功能仿真(随机函数是起作用的)
仿真模型
key_model.v
`timescale 1ns/1ns
module key_model(key);
output reg key;
reg [15:0] myrand;
initial begin
key = 1'b1;
press_key;
#10000;
press_key;
#10000;
press_key;
$stop;
end
task press_key;
begin
//按下抖动过程
repeat(50)begin
myrand = {$random}%65536;//0-65535;
#myrand key = ~key;
end
key = 0;
#50000000;
//释放抖动过程
repeat(50)begin
myrand = {$random}%65536;//0-65535;
#myrand key = ~key;
end
key = 1;
#50000000;
end
endtask
endmodule
key_filter_tb.v
`timescale 1ns/1ns
`define clk_period 20
module key_filter_tb;
reg Clk;
reg Rst_n;
wire key_in;
wire key_flag;
wire key_state;
key_filter key_filter0(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
key_model key_model1(.key(key_in));
initial Clk = 1'b1;
always #(`clk_period/2) Clk = ~Clk;
initial begin
Rst_n = 1'b0;
#(`clk_period *10) Rst_n = 1'b1;
#(`clk_period *10 +1);
end
endmodule
09.B按键消抖与亚稳态问题引入
- 异步信号:它与系统没有关系的,由外部产生的
- 模块化思想
- 异步信号的同步,使用两级D触发器进行同步
key_led_top.v
module key_led_top(Clk,Rst_n,key_in0,key_in1,led);
input Clk;
input Rst_n;
input key_in0;
input key_in1;
output [3:0] led;
wire key_flag0,key_flag1;
wire key_state0,key_state1;
key_filter key_filter0(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in0),
.key_flag(key_flag0),
.key_state(key_state0)
);
key_filter key_filter1(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in1),
.key_flag(key_flag1),
.key_state(key_state1)
);
led_ctrl led_ctrl0(
.Clk(Clk),
.Rst_n(Rst_n),
.key_flag0(key_flag0),
.key_flag1(key_flag1),
.key_state0(key_state0),
.key_state1(key_state1),
.led(led)
);
endmodule
key_filter.v
module key_filter(Clk,Rst_n,key_in,key_flag,key_state);
input Clk;
input Rst_n;
input key_in;
output reg key_flag;
output reg key_state;
localparam
IDLE = 4'b0001,
FILTER0 = 4'b0010,
DOWN = 4'b0100,
FILTER1 = 4'b1000;
reg [3:0] state;
reg [19:0] cnt;
reg en_cnt;//使能计数寄存器
//50_000_000 20ns
//20ms=20_000_000ns ,则需要计数1_000_000即为20ms
//对外部输入的异步信号进行同步处理
reg key_in_s0,key_in_s1;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
key_in_s0 <= 1'b0;
key_in_s1 <= 1'b0;
end
else begin
key_in_s0 <= key_in;
key_in_s1 <= key_in_s0;
end
reg key_tmp0,key_tmp1;
wire pedge,nedge;
reg cnt_full;//计数满表示信号
//使用D触发器存储两个相邻时钟上升沿外部输入信号(已经同步到系统时钟域中)的电平状态
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
key_tmp0 <= 1'b0;
key_tmp1 <= 1'b0;
end
else begin
key_tmp0 <= key_in_s1;
key_tmp1 <= key_tmp0;
end
//产生跳变沿信号
assign nedge = !key_tmp0 & key_tmp1;
assign pedge = key_tmp0 &(!key_tmp1);
//assign pedge = key_tmp0 &(~key_tmp1);
//~0110 = 1001
//!0110 = 0000
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
en_cnt <= 1'b0;
state <= IDLE;
key_flag <=1'b0;
key_state <= 1'b1;
end
else begin
case(state)
IDLE:
begin
key_flag <= 1'b0;
if(nedge)begin
state <= FILTER0;
en_cnt <= 1'b1;
end
else
state <= IDLE;
end
FILTER0:
begin
if(cnt_full)begin
key_flag <=1'b1;
key_state <= 1'b0;
state <= DOWN;
en_cnt <= 1'b0;
end
else if(pedge)begin
state <= IDLE;
en_cnt <= 1'b0;
end
else state <= FILTER0;
end
DOWN:
begin
key_flag <= 1'b0;
if(pedge)begin
state <= FILTER1;
en_cnt <= 1'b1;
end
else
state <= DOWN;
end
FILTER1:
begin
if(cnt_full)begin
key_flag <=1'b1;
key_state <= 1'b1;
state <= IDLE;
end
else if(nedge)begin
en_cnt <= 1'b0;
state <= DOWN;
end
else
state <= FILTER1;
end
default:
begin
state <= IDLE;
en_cnt <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end
endcase
end
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
cnt <= 20'd0;
else if (en_cnt)
cnt <= cnt + 1'b1;
else
cnt <= 20'd0;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
cnt_full <= 1'b0;
else if (cnt == 999_999)
cnt_full <= 1'b1;
else
cnt_full <= 1'b0;
endmodule
led_ctrl.v
module led_ctrl(Clk,Rst_n,key_flag0,key_flag1,key_state0,key_state1,led);
input Clk;
input Rst_n;
input key_flag0,key_flag1;
input key_state0,key_state1;
output [3:0] led;
reg [3:0] led_r;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
led_r <= 4'b0000;
else if(key_flag0 && !key_state0)
led_r <= led_r + 1'b1;
else if(key_flag1 && !key_state1)
led_r <= led_r - 1'b1;
else
led_r <= led_r;
assign led = ~led_r;
endmodule
key_led_top_tb.v
`timescale 1ns/1ns
`define clk_period 20
module key_led_top_tb;
reg Clk;
reg Rst_n;
wire key_in0,key_in1;
reg press0,press1;
wire [3:0] led;
key_led_top key_led_top0(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in0(key_in0),
.key_in1(key_in1),
.led(led)
);
key_model key_model0(
.press(press0),
.key(key_in0)
);
key_model key_model1(
.press(press1),
.key(key_in1)
);
initial Clk = 1'b1;
always #(`clk_period/2) Clk = ~Clk;
initial begin
Rst_n = 1'b0;
press0 = 0;
press1 = 0;
#(`clk_period *10) Rst_n = 1'b1;
#(`clk_period *10 +1);
press0 = 1;
#(`clk_period *3)
press0 = 0;
#80_000_000
press0 = 1;
#(`clk_period *3)
press0 = 0;
#80_000_000
press0 = 1;
#(`clk_period *3)
press0 = 0;
#80_000_000
press1 = 1;
#(`clk_period *3)
press1 = 0;
#80_000_000
press1 = 1;
#(`clk_period *3)
press1 = 0;
$stop;
end
endmodule
key_model.v
`timescale 1ns/1ns
module key_model(press,key);
input press;
output reg key;
reg [15:0] myrand;
initial begin
key = 1'b1;
// press_key;
// #10000;
// press_key;
// #10000;
// press_key;
// $stop;
end
always@(posedge press)
press_key;
task press_key;
begin
//按下抖动过程
repeat(50)begin
myrand = {$random}%65536;//0-65535;
#myrand key = ~key;
end
key = 0;
#25000000;
//释放抖动过程
repeat(50)begin
myrand = {$random}%65536;//0-65535;
#myrand key = ~key;
end
key = 1;
#25000000;
end
endtask
endmodule
功能仿真
10.A数码管动态扫描设计与验证
8位7段数码管驱动实验
课程目标:FPGA驱动数码管显示实验
实验平台:Snow Dream starter board 核心板 + 数码管_VGA_PS2学生模块
实验现象:在Quartus II 中,使用In system sources andprobes editor工具,输入需要显示在数码管上的数据,则数码管显示对应数值。
知识点:
- 数码管动态扫描实现
- In system sources and probes editor(ISSP)调试工具的使用。
- 数码管显示数字原理
- 数码管动态扫描原理
- ISSP调试工具的简单使用
a. 4输入查找表,8位输出
b. 分频模块,从系统时钟分频得到1KHz的扫描时钟
c. 8选1多路器,选择端为当前扫描的数码管位置
d. 8位循环移位寄存器,1000_0000 ->0000_0001
由于代码加长,之后的代码都不附上
恰好可以不看参考代码,自己从头写一下代码,这样才有长进
HEX8.v
设计代码
HEX8_tb.v
测试平台
HEX_top.v和ISSP.v
板级验证的文件
10.B串行移位寄存器原理与结构分析
串转并
锁存器
10.C串行移位寄存器驱动数码管显示设计与实现
11.A串口发送模块与验证
-
实验平台:芯航线FPGA学习套件核心板、PC机
-
实验现象:在 Quartus II中,使用 In system sources and probes editor工具,输入需要通过串口发送出去的数据,然后按下学习板上的按键O,则FPGA自动将所需要发送的数据发送出去
-
知识点:
-
1.UART通信协议实现
-
2.In system sources and probes editor(ISSP)调试工具的使用
11.B例解使用串口发送多个字节的数据方案
通过串口发送模块发送“HELLO”字符串。