FPGA入门
1.认识Vivado
Sources: 显示项目中的所有源文件,如设计源文件、约束文件等。你可以在这里添加、编辑和组织Verilog文件。
点击 Create File
,输入文件名,例如 top.v
(对于Verilog)
为方便管理,保持顶层文件与工程文件一致。
**分析与综合:**点击Run Synthesis
**约束:**生成原理图,点开I/O Port分配管脚和电压(或直接编辑约束文件),点击 Generate Bitstream
生成比特流。
烧录: Open Hardware Manager
->Open Target
-> auto connect
2.例程1:LED_Bink
module top( // 定义一个名为top的模块
//定义它的io
input wire clk, // FPGA的时钟信号
output reg led // 控制LED的输出端口
);
// 定义一个计数器,用于生成时间延迟
reg [24:0] counter = 0; // 定义一个25位宽的寄存器counter,初始值为0
always @(posedge clk) begin
counter <= counter + 1;
if(counter == 25_000_000) begin
led <= ~led; // 翻转LED状态
counter <= 0; // 重置计数器
end
end
endmodule
2.1.Verilog模块结构
Verilog代码基于模块(module)结构,每个模块可以表示一个硬件组件。
模块以关键词 module
开始,并以 endmodule
结束。模块可以包含输入输出端口、内部信号定义、逻辑描述等。
2.2.寄存器和线网
- verilog基本数据类型:
wire
:用于连续赋值,不能在过程控制块(如always
块)内被赋值。reg
:不仅仅是寄存器,它可以在过程控制块中被赋值。parameter
关键字用于定义常量值。- 形如
[24:0]
是一种位向量的声明方式。表示位0到位24。
2.3.重要概念:阻塞赋值与非阻塞赋值
- 阻塞赋值(
=
):执行顺序依赖,前一个赋值完成后,后一个才开始。 - 非阻塞赋值(
<=
):所有赋值看似同时发生,更接近硬件的行为。
在 always
块中,通常使用非阻塞赋值 <=
来更新寄存器。这是为了确保所有赋值在同一个时钟周期内并行更新,模拟真实硬件的行为。
2.4.控制过程块
-
always
块Verilog使用
always
块来描述硬件的行为,这类似于软件中的函数,但是是并行执行的。基本语法:
always @(触发条件) begin // 代码块 end
触发条件:
@(posedge)
:信号的上升沿@(negedge)
:信号的下降沿@(signal)
或@(any changes)
:信号值的任何变化
-
initial
块initial
块用于在仿真开始时执行一次性的操作,通常用于初始化变量或在测试中设置环境。基本语法:
initial begin // 代码块 end
3.例程2:LED_Breath
module breath_led(
input sys_clk , //系统时钟 50MHz
input sys_rst_n , //系统复位,低电平有效
output reg led //LED灯
);
//parameter define
parameter CNT_2US_MAX = 7'd100;
parameter CNT_2MS_MAX = 10'd1000;
parameter CNT_2S_MAX = 10'd1000;
//reg define
reg [6:0] cnt_2us;
reg [9:0] cnt_2ms;
reg [9:0] cnt_2s;
reg inc_dec_flag; //亮度递增/递减 0:递增 1:递减
//*****************************************************
//** main code
//*****************************************************
//cnt_2us:计数2us
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_2us <= 7'b0;
else if(cnt_2us == (CNT_2US_MAX - 7'b1 ))
cnt_2us <= 7'b0;
else
cnt_2us <= cnt_2us + 7'b1;
end
//cnt_2ms:计数2ms
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_2ms <= 10'b0;
else if(cnt_2ms == (CNT_2MS_MAX - 10'b1) && cnt_2us == (CNT_2US_MAX - 7'b1))
cnt_2ms <= 10'b0;
else if(cnt_2us == CNT_2US_MAX - 7'b1)
cnt_2ms <= cnt_2ms + 10'b1;
else
cnt_2ms <= cnt_2ms;
end
//cnt_2s:计数2s
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_2s <= 10'b0;
else if(cnt_2s == (CNT_2S_MAX - 10'b1) && cnt_2ms == (CNT_2MS_MAX - 10'b1) && cnt_2us == (CNT_2US_MAX - 7'b1))
cnt_2s <= 10'b0;
else if(cnt_2ms == (CNT_2MS_MAX - 10'b1) && cnt_2us == (CNT_2US_MAX - 7'b1))
cnt_2s <= cnt_2s + 10'b1;
else
cnt_2s <= cnt_2s;
end
//inc_dec_flag为低电平,led灯由暗变亮,inc_dec_flag为高电平,led灯由亮变暗
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
inc_dec_flag <= 1'b0;
else if(cnt_2s == (CNT_2S_MAX - 10'b1) && cnt_2ms ==( CNT_2MS_MAX - 10'b1) && cnt_2us == (CNT_2US_MAX - 7'b1))
inc_dec_flag <= ~inc_dec_flag;
else
inc_dec_flag <= inc_dec_flag;
end
//led:输出信号连接到外部的led灯
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
led <= 1'b0;
else if((inc_dec_flag == 1'b1 && cnt_2ms >= cnt_2s) || (inc_dec_flag == 1'b0 && cnt_2ms <= cnt_2s))
led <= 1'b1;
else
led <= 1'b0;
end
endmodule
memo:
-
代码通过控制一个周期内LED点亮时间来控制亮度。
-
所有
always
都是并行运行的 -
reg不可在
always
和initial
外赋值 -
重新编译完记得reload
-
一个reg最好只在一个
always
内赋值
3.1.数字常量表示形式
<size>'<base><number>
-
size:数字的位宽。
-
base:数字的基数,可以是
b
(二进制)、o
(八进制)、d
(十进制)或h
(十六进制)。 -
number:具体的数字值。
例如:
10'd1000
表示一个十进制的1000,它是一个10位宽的数字。7'b1
表示二进制的1,它是一个7位宽的数字,实际上等同于二进制的0000001
。
遇到非常奇怪的问题,就是原理图无法正确生成。(发现是无论如何始终LED输出都是0导致的)
3.2.计数器实现
通过三个级联的always
计时器,实现精确定时。
-
cnt_2us
计数器:这是最基础的计数器,它在每个系统时钟的上升沿递增,当达到预设的最大值,并且触发下一个级别的计数器cnt_2ms
。 -
cnt_2ms
计数器:这个计数器仅在cnt_2us
计数器达到其最大值并复位时递增。这样,每当cnt_2us
完成一轮计数,cnt_2ms
才递增一次。 -
cnt_2s
计数器:这个计数器依赖于cnt_2ms
的递增。当cnt_2ms
达到其最大值并复位,且同时cnt_2us
也在同一时刻达到最大值并复位时,cnt_2s
才会递增。
条件复位和递增:
-
对于
cnt_2ms
:这个计数器检查cnt_2us
是否达到了其最大值 (CNT_2US_MAX - 1
)。仅当这个条件为真时,cnt_2ms
才会递增。 -
对于
cnt_2s
:这个计数器的递增依赖于cnt_2ms
达到其最大值且cnt_2us
同时达到其最大值的情况。 -
上级的所有计数器达到最大值时当级才递增。
4.Day2
今天开始正经看文档和视频,不瞎捣鼓了。
4.1assign
语句
assign target = expression;
- target:目标信号,线网(wire)类型的变量。不可以是寄存器(reg)类型。
- expression:表达式,可以包含常数、信号名、逻辑运算符等,用于计算赋值给目标信号的值。
- 特点:
-
实时更新:当
expression
中任何值发生变化时,target
的值会立即更新。 -
组合逻辑:通常用于实现无时钟控制的组合逻辑电路。
-
所以,我们有一个更好的实现计时器的方法:
//计数器溢出使能信号
assign counter_en = (counter == (COUNT_MAX - 1'b1)) ? 1'b1 : 1'b0;
//用于产生 0.5 秒使能信号的计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n == 1'b0)
counter <= 1'b0;
else if (counter_en)
counter <= 1'b0;
else
counter <= counter + 1'b1;
end
4.2硬件调试
首先检查时钟和复位。
4.2.1ILA调试
ILA可以实时监控FPGA内部的信号,不需要将信号引出到外部测试设备。
用了ip核里的ILA,加上条件,很快就定位到问题语句
assign pwm_width_ov = (pwm_cnt == (CNT_PWM_MAX - 16'd1)) ? 1'b1 : 1'b0;
非常低级的错误,但也算给我第一次使用ip核提供了宝贵的素材。
通过ILA的帮助,费劲千辛万苦,终于实现了一个呼吸灯。