前言
本文基于【开源骚客】FPGA超详细入门视频教程,简单做个笔记
00. FPGA开发软件的安装
- Quartus II 13.1
- Modelsim
- Notepad++
- Vim
01. 我的第一个FPGA工程:点亮led
led.v
//led.v
module led(
output wire led0 ,//ctrl+D快速复制当前行
output wire led1 ,
output wire led2 ,
output wire led3
);
// EP4CE6E22CBN 是FPGA开发板上的型号。
assign led0 = 0;
assign led1 = 0;
assign led2 = 0;
assign led3 = 0;
endmodule
pin planner
初次下载代码到FPGA时,需要给下载器安装驱动,Altera USB-Blaster。
驱动目录选择Quartus安装目录下的drivers即可。
下载到板子上
03. Verilog HDL语法
test.v
module test(
input port_a , //只有wire,没有reg型
input port_b , //只有wire,没有reg型
input port_c , //只有wire,没有reg型
output wire port_d , //默认为wire
output wire port_e ,
output wire port_f ,
output wire port_g ,
output wire port_h
);
//assign: 对wire型进行赋值
//always:对reg 型进行赋值
assign port_d = port_a & port_b;
assign port_e = port_a | port_c;
assign port_f = ~port_a;
assign port_g = port_b ^ port_c;
assign port_h = port_a ^~ port_c;
endmodule
04. Modelsim 进行仿真(小编在这里用的questasim)
tb_test.v
`timescale 1ns/1ns // 时间单位 / 时间精度
module tb_test;
reg test_a;
reg test_b;
reg test_c;
wire rslt_d;
wire rslt_e;
wire rslt_f;
wire rslt_g;
wire rslt_h;
// initial 赋值的信号,必须定义成reg型
initial begin
test_a = 1; // 在0时刻赋初值
test_b = 1;
test_c = 1;
#5
test_a = 0; // 在5时刻赋初值
test_b = 1;
test_c = 1;
#5
test_a = 0; // 在10时刻赋初值
test_b = 0;
test_c = 0;
end
test test_inst(
.port_a (test_a ), //只有wire,没有reg型
.port_b (test_b ), //只有wire,没有reg型
.port_c (test_c ), //只有wire,没有reg型
.port_d (rslt_d ), //默认为wire
.port_e (rslt_e ),
.port_f (rslt_f ),
.port_g (rslt_g ),
.port_h (rslt_h )
);
endmodule
wave
05. verilog 语法讲解2(二选一选择器)
- 上面这个图,是一个简单的、由一个信号控制的选择器,设计代码如下:
switch_2to1.v(一部分)
module switch_2to1(
input sel , //只有wire,没有reg型
input data_a , //只有wire,没有reg型
input data_b , //只有wire,没有reg型
output wire data_c //默认为wire
);
//wire -- > assign: 对wire型进行赋值
assign data_c = (sel == 0) ? data_a : data_b;
//assign 目标信号 = (判断表达式) ? (判断表达式为真时执行) : (判断表达式为假时执行);
//
endmodule
- 下面将采取两个选择信号,完成选择,设计和验证代码如下:
switch_2to1.v(扩充)
module switch_2to1(
input sel_a ,
input sel_b ,
input data_a ,
input data_b ,
output wire data_c , //wire -- assign
output reg data_d //reg -- always
);
//wire -- > assign: 对wire型进行赋值
assign data_c = (sel == 0) ? data_a : data_b;
//assign 目标信号 = (判断表达式) ? (判断表达式为真时执行) : (判断表达式为假时执行);
//
assign data_c = (sel_a == 0) ? ((sel_b == 0) ? data_a : 0):((sel_b == 1) ? data_b : 0);
//always --> 对reg型进行赋值
//if_else是就近配对
//可以用*替代 =s el_a or sel_b or data_a or data_b
always @(*)begin
if(sel_a == 0)
if(sel_b == 0)
data_d = data_a;
else
data_d = 0;
else //sel_a = 1
if(sel_b == 1)
data_d = data_b;
else
data_d = 0;
end
endmodule
testbench
`timescale 1ns/1ns
module tb_switch_2to1;
reg sel_a ;
reg sel_b ;
reg data_a ;
reg data_b ;
wire data_c ;
wire data_d ;
initial begin
sel_a = 0;
sel_b = 0;
data_a = 1;
data_b = 0;
#5
sel_a = 1;
sel_b = 0;
data_a = 1;
data_b = 0;
#5
sel_a = 0;
sel_b = 1;
data_a = 0;
data_b = 1;
#5
sel_a = 1;
sel_b = 1;
data_a = 0;
data_b = 1;
end
switch_2to1 switch_2to1_inst(
.sel_a (sel_a ),
.sel_b (sel_b ),
.data_a (data_a),
.data_b (data_b),
.data_c (data_c), //wire -- assign
.data_d (data_d) //reg -- always
);
endmodule
06.verilog语法讲解3(三八译码器)
- 三八译码器的设计代码如下:
decode_3to8.v
module decode_3to8(
input [ 2:0] pi_data ,
output reg [ 7:0] po_data
);
always @(*)begin
case(pi_data)
0: po_data = 1; //8'b0000_0001
1: po_data = 2; //8'b0000_0010
2: po_data = 4; //8'b0000_0100
3: po_data = 8; //8'b0000_1000
4: po_data = 16; //8'b0001_0000
5: po_data = 32; //8'b0010_0000
6: po_data = 64; //8'b0100_0000
7: po_data = 128;//8'b1000_0000
default:po_data = 1;
endcase
end
endmodule
对应的testbench如下:
tb_decode_3to8.v
`timescale 1ns/1ns
module tb_decode_3to8;
reg [2:0] pi_data;
wire [7:0] po_data;
initial begin
pi_data = 0;
#5
pi_data = 1;
#5
pi_data = 2;
#5
pi_data = 3;
#5
pi_data = 4;
#5
pi_data = 5;
#5
pi_data = 6;
#5
pi_data = 7;
end
decode_3to8 decode_3to8_inst(
.pi_data (pi_data),
.po_data (po_data)
);
endmodule
07.让LED灯闪起来
- 设计代码如下:
flash_led.v
module flash_led(
input sclk ,
input s_rst_n ,
output reg [3:0] led
);
//EP4CE6E22CBN这是FPGA开发板上的型号。
//50Mhz
//T = 1_000_000_000 /50_000_0000 = 20ns
reg [25:0] cnt;
// 50_000_000 - 1 =49_999_999
// 0 - 49_999_999 0
//always用时钟沿进行工作,<=赋值
always @(posedge sclk or negedge s_rst_n)begin
if(s_rst_n == 1'b0)
cnt <= 'd0;
else if(cnt == 'd49_999_999)
cnt <= 'd0;
else
cnt <= cnt + 1'b1;
end
//0-1-0-1-0
always @(posedge sclk or negedge s_rst_n)begin
if(s_rst_n == 1'b0)
led <= 4'b0000;
else if(cnt == 'd49_999_999)
led <= ~led;
end
endmodule
- 下载到板子上,同01中所提到的。
08.verilog语法讲解4()
tb_flash_led.v
`timescale 1ns/1ns
module tb_flash_led;
reg sclk;
reg s_rst_n;
wire [3:0] led;
initial begin
sclk = 1;
s_rst_n <= 0;
#100
s_rst_n <= 1;
end
always #5 sclk = ~sclk;
flash_led flash_led_inst(
.sclk (sclk ), //system clock
.s_rst_n (s_rst_n ), //system rst_n,active low
.led (led )
);
endmodule
- 如上所述,设计中用到的49_999_999太长,可以改成49观察,最后改回去,启用参数的延迟代码如下:
//flash_led.v
module flash_led(
input sclk ,
input s_rst_n ,
output reg [3:0] led
);
//localparam DELAY_1S = 'd49_999_999 ;
localparam DELAY_1S = 'd49 ;
//EP4CE6E22CBN这是FPGA开发板上的型号。
//50Mhz
//T = 1_000_000_000 /50_000_0000 = 20ns
reg [25:0] cnt;
// 50_000_000 - 1 =49_999_999
// 0 - 49_999_999 0
//always用时钟沿进行工作,<=赋值
always @(posedge sclk or negedge s_rst_n)begin
if(s_rst_n == 1'b0)
cnt <= 'd0;
else if(cnt == DELAY_1S)
cnt <= 'd0;
else
cnt <= cnt + 1'b1;
end
//0-1-0-1-0
always @(posedge sclk or negedge s_rst_n)begin
if(s_rst_n == 1'b0)
led <= 4'b0000;
else if(cnt == DELAY_1S)
led <= ~led;
end
endmodule
阻塞赋值(=)和非阻塞赋值(<=)
- 简单讲,always块中有时钟沿就用非阻塞赋值(<=),时序电路
- 没有时钟沿就用 阻塞赋值(=),组合电路
- 下面的这个例子tx_en.v,就是对这段话作解释的!
tb_ex.v
`timescale 1ns/1ns
module tb_ex;
reg a;
reg b;
reg c;
reg d;
reg e;
reg f;
initial begin
a = 1;
b = 0;
c = a + b;
end
initial begin
d <= 1;
e <= 0;
f <= d + e;
end
endmodule
- 这段代码仿真的波形如下,很容易看出阻塞和非阻塞是不一样的。
组合逻辑和时序逻辑
09.FPGA实现流水灯
-
{ }位拼接操作符
-
此处使用位拼接符,简化了代码,与前面的代码相比,变动不大。
shift_led.v
module shift_led(
input sclk ,
input s_rst_n ,
output reg [3:0] led
);
//localparam DELAY_1S = 'd49_999_999 ;
localparam DELAY_1S = 'd49 ;
//EP4CE6E22CBN这是FPGA开发板上的型号。
//50Mhz
//T = 1_000_000_000 /50_000_0000 = 20ns
reg [25:0] cnt;
// 50_000_000 - 1 =49_999_999
// 0 - 49_999_999 0
//always用时钟沿进行工作,<=赋值
always @(posedge sclk or negedge s_rst_n)begin
if(s_rst_n == 1'b0)
cnt <= 'd0;
else if(cnt == DELAY_1S)
cnt <= 'd0;
else
cnt <= cnt + 1'b1;
end
//0-1-0-1-0
always @(posedge sclk or negedge s_rst_n)begin
if(s_rst_n == 1'b0)
led <= 4'b1110;
else if(cnt == DELAY_1S)
led <= {led[2:0],led[3]};
end
endmodule
tb_shift_led
`timescale 1ns/1ns
module tb_shift_led;
reg sclk;
reg s_rst_n;
wire [3:0] led;
initial begin
sclk = 1;
s_rst_n <= 0;
#100
s_rst_n <= 1;
end
always #5 sclk = ~sclk;
shift_led shift_led_inst(
.sclk (sclk ), //system clock
.s_rst_n (s_rst_n ), //system rst_n,active low
.led (led )
);
endmodule
10-11.实现呼吸灯原理(PWM)
[“流水灯”升级,基于FPGA的呼吸灯设计](http://dengkanwen.com/24.html)
- 逐渐变亮:
- 逐渐变暗(cnt_2ms >= cnt_2s)
breath_led.v
module breath_led(
input sclk ,
input s_rst_n ,
output reg [3:0] led
);
/* localparam DELAY_2US = 'd99;
localparam DELAY_2MS = 'd999;
localparam DELAY_2S = 'd999; */
localparam DELAY_2US = 'd9;
localparam DELAY_2MS = 'd99;
localparam DELAY_2S = 'd99;
reg [ 6:0] cnt_2us ;
reg [ 9:0] cnt_2ms ;
reg [ 9:0] cnt_2s ;
reg flag_2s ;
always @(posedge sclk or negedge s_rst_n)begin
if(s_rst_n == 1'b0)
cnt_2us <= 'd0;
else if(cnt_2us == DELAY_2US)
cnt_2us <= 'd0;
else
cnt_2us <= cnt_2us + 1'b1;
end
// cnt_2us = 9,cnt_2ms = 99
// 小范围条件写前面,大范围条件写后面
always @(posedge sclk or negedge s_rst_n)begin
if(s_rst_n == 1'b0)
cnt_2ms <= 'd0;
else if(cnt_2ms == DELAY_2MS && cnt_2us == DELAY_2US)
cnt_2ms <= 'd0;
else if(cnt_2us == DELAY_2US)
cnt_2ms <= cnt_2ms + 1'b1;
end
always @(posedge sclk or negedge s_rst_n)begin
if(s_rst_n == 1'b0)
cnt_2s <= 'd0;
else if(cnt_2s == DELAY_2S && cnt_2ms == DELAY_2MS && cnt_2us == DELAY_2US)
cnt_2s <= 'd0;
else if(cnt_2ms == DELAY_2MS && cnt_2us == DELAY_2US)
cnt_2s <= cnt_2s + 1'b1;
end
always @(posedge sclk or negedge s_rst_n)begin
if(s_rst_n == 1'b0)
led <= 4'b1111;
else if(cnt_2s >= cnt_2ms && flag_2s == 1'b0) //逐渐变亮
led <= 4'b0000;
else if(cnt_2s <= cnt_2ms && flag_2s == 1'b1) //逐渐变暗
led <= 4'b0000;
else
led <= 4'b1111;
end
always @(posedge sclk or negedge s_rst_n)begin
if(s_rst_n == 1'b0)
flag_2s <= 1'b0;
else if(cnt_2s == DELAY_2S && cnt_2ms == DELAY_2MS && cnt_2us == DELAY_2US)
flag_2s <= ~flag_2s;
end
endmodule
tb_breath_led.v
`timescale 1ns/1ns
module tb_breath_led;
reg sclk;
reg s_rst_n;
wire [3:0] led;
initial begin
sclk = 1;
s_rst_n <= 0;
#100
s_rst_n <= 1;
end
always #5 sclk = ~sclk;
breath_led breath_led_inst(
.sclk (sclk ), //system clock
.s_rst_n (s_rst_n ), //system rst_n,active low
.led (led )
);
endmodule
12-13. 按键控制流水灯
#后续更新代码
14. TOP-DOWN(自顶向下)设计思想
#后续更新代码
15. FPGA开发流程
- 设计定义:明确项目用FPGA需要实现的功能,指标、性能等,进行模块划分
- 设计输入:根据第一步的想法和思路,开始代码设计(代码: verilog,VHDL,原理图方式)
- 功能仿真:对第二步设计的仿真(Modelsim)
- 逻辑综合
- 布局布线
- 验证在布局布线后,时序是否违例:后仿真或静态时序分析
- 上板验证,看FPGA否能接照最初的想法实际跑起来
后仿真用的不是很多