【RK3568+PG2L50H开发板实验例程】FPGA部分 | 键控LED实验
本原创文章由深圳市小眼睛科技有限公司创作,版权归本公司所有,如需转载,需授权并注明出处(www.meyesemi.com)
1.实验简介
实验目的:
从创建工程到编写代码,完成引脚约束,最后生成 bit 流下载到开发板上,完成 Key0 控 制 led0 闪烁,Key1 控制 led1 亮灭。
实验环境:
Window11 PDS2022.2-SP6.4 芯片型号: PG2L50H-484
2.实验原理
通常的时,分,秒的计时进位大家应该不陌生;
1 小时=60 分钟=3600 秒,当时针转动 1 小时,秒针跳动 3600 次;
在数字电路中的时钟信号也是有固定的节奏的,这种节奏的开始到结束的时间,我们通常称之为周期(T)
在数字系统中通常关注到时钟的频率,那频率与周期的关系如下:
而本次开发板上的晶振提供了一个 25MHZ 的单端时钟。
所以其周期约为 40ns。而在我们FPGA 的设计中,我们的 always 块通常都是在时钟的上升沿时对数据进行赋值,因此我们可以定义一个变量,每到时钟的上升沿就让该变量+1,让其变成一个计数器,该变量每加 1 就表示经过了 40ns,那如果要定时 1s 的话,只需要让其计数到 24999999 即可,因为从 0 开始计数,所以计数到 24999999 即可,此时就是一秒了。以此类推,12499999 就是 0.5s。
错误!未定义书签。为开发板上 2 个 LED 灯的原理图。
错误!未定义书签。为开发板上 2 个按键的原理图。
KEY0 控制 LED0 每 1s 更换一次 LED 灯状态,KE1 控制 LELD1 亮灭状态。(高电平用 1 表示,低电平用 0 表示)
3.接口列表
接口列表 top.v 顶层模块接口列表:
端口 | I/O | 位宽 | 描述 |
sys_clk | input | 1 | 系统时钟 25MHZ |
key0 | input | 1 | 用户按键 0 |
key1 | input | 1 | 用户按键 1 |
led_0 | output | 1 | led 灯控制信号 |
led1 | output | 1 | led 灯控制信号 |
btn_deb_fix.v 按键消抖模块接口列表:
端口 | I/O | 位宽 | 描述 |
BTN_WIDTH | parameter | 4 | 案件数量 |
sys_clk | input | 1 | 系统时钟 25MHZ |
rst_n | input | 1 | 全局复位 |
btn_in | input | BTN_WIDTH | 用户按键输入 |
btn_deb_fix | output | BTN_WIDTH | 按键消抖后的输出(脉冲信号) |
4.工程说明
该工程框架如下所示:
本次工程主要完成按键控制 led 的状态。按键 0 控制 led0 闪烁,按键 1 控制 led1 亮灭。首先,key0 和 key1 均会经过按键消抖模块,因为开发板上使用的是机械按键,所以每次 按下均会产生抖动,如果不进行消抖,会造成误判。经过消抖后,每次按下按键均会产生持续一个 clk 的高电平即key0_flag和key1_flag。key0_flag控制是否打开 1s计数器来开启 led0 的闪烁,key1_flag 直接控制 LED1 翻转,每按下一次 key1,led 的状态翻转一次。
5.代码模块说明
//key0->led0 闪烁//key1->key1 翻转module top(input wire sys_clk , //系统时钟 25MHZinput wire key0 , input wire key1 , output reg led_0 , output reg led_1);//--------------------------------parameter-------------------------------- parameter CNT_MAX = 32'd25_000_000 ; //1s 计数//----------------------------------reg---------------------------------- reg [7:0] rsn_cnt =0 ; //复位计数器reg [31:0] cnt_1s ; //计数器reg led0_en ; //led0 闪烁使能//----------------------------------wire---------------------------------- wire rst_n ; //复位信号,低电平有效wire key0_flag ; //按键按下后的上升沿wire key1_flag ; //按键按下后的上升沿//-----------------------------always & assign----------------------------- //产生复位always@(posedge sys_clk) beginif(rsn_cnt >=100)rsn_cnt <= rsn_cnt;elsersn_cnt <= rsn_cnt + 1'b1;endassign rst_n = (rsn_cnt>=100)?1'b1:1'b0 ;//每按下一次 key0 进行一次翻转always@(posedge sys_clk) beginif(!rst_n)led0_en <= 1'd0;else if(key0_flag)led0_en <= ~led0_en;end//计数 1salways@(posedge sys_clk) beginif(!rst_n)cnt_1s <= 32'd0;else if(led0_en) //led0 闪烁使能beginif(cnt_1s == CNT_MAX-1) //1 秒cnt_1s <= 32'd0;elsecnt_1s <= cnt_1s + 1'b1;endelsecnt_1s <= 32'd0;end//led0 1s 闪烁always@(posedge sys_clk) beginif(!rst_n)led_0 <= 1'd0;else if(led0_en)beginif(cnt_1s == CNT_MAX-1) //1s 翻转 ledled_0 <= ~led_0;elseled_0 <= led_0;endelseled_0 <= 1'd0;end//led1 翻转always@(posedge sys_clk) beginif(!rst_n)led_1 <= 1'd0;else if(key1_flag)led_1 <= ~led_1;end//-----------------------------instance----------------------------- //按键消抖模块btn_deb_fix#(.BTN_WIDTH ( 4'd2 ) //2 个按键)u_btn_deb_fix(.sys_clk ( sys_clk ), .rst_n ( rst_n ), .btn_in ( {key1,key0} ), .btn_deb_fix ( {key1_flag,key0_flag} ));endmodule
CNT_MAX定义了一个最大的计数值,由于我们的系统时钟是 25MHZ,也就是25000000,所以要让 LED 实现 1s 闪烁的话就是从 0 计数到 24999999 的时候让 led 进行一次翻转。
在 26-31 行中,利用系统时钟计数 100 个周期后产生了一个复位信号用来给后续模块和 时序逻辑提供复位。
在 43-55 行中,当 led0_en 拉高时才开始计数一秒。否则计数器一直保持 0。
在 82-89 行中,例化了一个按键消抖的模块,按键按下并松开后将产生一个脉冲信号即 key0_flag,key1_flag,其中 key_0flag 控制 led0 闪烁,key1_flag 控制 led1 翻转。
//按键消抖`define UD #1module btn_deb_fix#(parameter BTN_WIDTH = 4'd8 //按键数量)( input sys_clk , input wire rst_n , input [BTN_WIDTH-1:0] btn_in , output reg [BTN_WIDTH-1:0] btn_deb_fix );//----------------------------parameter---------------------------- parameter CNT_20MS_MAX = 32'd500_000 ; //20MS 计数//-------------------------------reg-------------------------------reg [23:0] cnt[BTN_WIDTH-1:0]; //计数器reg [BTN_WIDTH-1:0] btn_in_reg ; //寄存按键信号//打一拍always @(posedge sys_clk) beginbtn_in_reg <= btn_in;end//------------------------ 消 抖 主 要 逻 辑------------------------ genvar i;generatebeginfor(i=0;i<BTN_WIDTH;i=i+1)beginalways @(posedge sys_clk) beginif(!rst_n)cnt[i] <= 24'd0;if (btn_in_reg[i] == 1'b0) //按下时 计数 20ms 时归零cnt[i] <= 24'd0;else if(cnt[i]==CNT_20MS_MAX) //抖动区间有效时计数cnt[i] <= cnt[i];elsecnt[i] <= cnt[i] + 1'b1;endalways @(posedge sys_clk) beginif(!rst_n)btn_deb_fix[i] <= 1'd0;else if(cnt[i]==CNT_20MS_MAX-1) //消抖后输出一个 clk 的高电平btn_deb_fix[i] <= 1'b1;elsebtn_deb_fix[i] <= 1'b0;endendendendgenerateendmodule
该部分为按键消抖模块,parameter 定义了按键输入的数量,模块的输出将产生一个脉冲信号即产生一个持续一个 clk 的高电平信号。
在 30-50 行中,cnt 会不断进行 20ms 的计数,当按键按下时,cnt 归 0,从 0 开始计数直到 20ms。当计数到 20ms 的时候,就输出一个 clk 的高电平,即将 btn_deb_fix 置 1,并让其只保持一个 clk。
6.实验步骤
这里将会详细介绍从新建工程到下载程序的具体步骤,后续的工程将不再详细解释。
6.1. 打开 PDS 软件,创建工程
Step1:打开 PDS 软件,点击 NEW Project,然后对其设置完成新建工程。
Step2:单击 NEXT
Step3:创建名为 led_water 的工程到对应的文件目录,之后单击 Next。
新建工程大致包括设置工程名和工程路径、工程类型、工程文件及器件信息。
【Project Name】是工程文件名称,默认为 project。(只允许字母、数字、下划线(_)、杠(-)、点(.))。
【Project Location】用于选择新工程的工作路径,文件夹名只允许字母、数字、下划线
(_)、杠(-)、点(.)、@、~、,、+、=、#、空格( ),但空格不能出现在路径名首尾,即工程文件放置的路径。
【Create Preject Subdirectory】将工程文件名作为工作目录的一部分。
Step4:选择 RTL project,点击 Next。
【RTL Project】用于创建 RTL 工程。新建的工程可以执行 synthesize,device map,place& route,report timing,report power,generate netlist 及 generate bitstream 等。
【Post-Synthesize Project】用于创建综合后工程。新建的工程可以执行 device map, place& route,report timing,report power, generate netlist 及 generate bitstream 等。
Step5:单击 Next
该界面可以 Add Files 和 Add Directories 来添加 rtl 源文件及新建 rtl 源文件,以及调整 rtl 文件编译顺序,Add Files 添加选中的文件,Add Directories 添加选中的文件夹下所有合适的文件,若勾选了下方的 Add source from subdirecotires 则添加所有的子目录下合适的文件,也可直接 NEXT 跳过添加文件。
Step6:单击 Next
Step7:单击 Next
Step8:选择器件系列、型号、封装、速率、综合工具,之后单击 Next
synthesize tool 中可以选择综合工具为 Synplify Pro 或 ADS,在实验中使用 ADS 综合工具。
Step9:在 summary 单击 Finish,完成工程的创建
6.2. 添加设计文件
PDS 软件界面如下图:
双击 Designs,将前面设计的 module 新建到文件中,或者将前面编辑好的 verilog 文件添加到工程中:
添加文件到工程:
在窗口中点击 Add Files,选择添加文件到工程;
新建文件到工程:
1)在窗口中点击 Create File;
2)选择 Verilog Design File,文件名和 module 名一致,默认路径,点击 OK;
3)点击 OK;
4)点击 Cancel;
5)默认打开新建文件,将前面设计的 代码 复制进去,
6)点击保存,新建文件完成
Crtl+s 保存。
双击 Designs。
点击 Add Files;
添加 btn_deb_fix.v 模块,即按键消抖模块。
点击 OK。
6.3. 编译
可采用以下方式运行 Compile 流程:
(1)双击 Flow 中的 Compile 进行综合;
(2)右击 Compile 点击 Run 进行综合;
6.4. 工程约束
点击 Tools 选择 User Constraint Editor(Timing and Logic)或者点击工具栏图标 ,User Constraint Editor(Timing and Logic) 选择 Pre Synthesize UCE,如下图所示。
Tools 下的 User Constraint Editor(Timing and Logic)
6.4.1. 时钟约束
打开 UCE 后,选择 Timing Constraints 后选择 Create Clock 添加基准时钟,基准时钟一般是通过输入 port 输入用户所使用的板上时钟。
在弹窗中对时钟命名,关联时钟管脚,添加时钟参数,点击 OK 会创建一条时钟约束,Reset 重置该页面。创建完成如下图所示:
提供给开发板的时钟是 25MHZ,即周期为 40ns。
6.4.2. 物理约束
打开 UCE 后,选择 Device 后选择 I/O,根据原理图编辑 IO 的分配。
按照原理图编辑好 IO 分配后,点击保存,会生成.fdc 文件,完成约束。
6.5. 综合
运行 Synthesize 流程有以下四种方式可以实现:
(1)双击 Flow 中的 Synthesize 进行综合;
(2)右击 Synthesize 点击 Run 进行综合;
完成 Synthesize 操作后,会看到下图所示:
6.6. Device Map
Device Map 的主要作用是将设计映射到具体型号的子单元上(LUT、FF、Carry 等)。
运行 Device Map 流程有以下方式可以实现:
(1)直接双击 Device Map;
(2)右击 Device Map 点击 Run;
完成 Device Map 操作后,会看到下图所示:
6.7. Place & Route
布局布线(Place & Route)根据用户约束和物理约束,对设计模块进行实际的布局及布线。
运行 Place & Route 流程有以下方式可以实现:
(1)直接双击 Place & Route;
(2)右击 Place & Route 点击 Run;
完成 Device Map 操作后,会看到下图所示:
6.8. Generate Bitstream
Generate Bitstream 生成二进制位流文件。运行 Generate Bitstream 流程有以下方式可以实现: (1)直接双击 Generate Bitstream;
(2)右击 Generate Bitstream 点击 Run;
完成以上操作,将会产生位流文件。
运行 Generate Bitstream,可以看到界面如下图所示:
6.9. 下载生成的位流文件
点击 Tools 选择 Configuration 或者点击工具栏图标 Configuration,如下图所示。
Tools 下的 Configuration
工具栏 Configuration 图标
打开 Configuration 后直接选择 Scan Device 直接进行扫描 Jtag 链操作,初始化链成功,会将链上扫描到的所有器件显示于工作区内,并在器件属性窗口显示当前器件的器件 信息,并弹出对话显示能够为器件添加的配置文件:
初始化链成功
在对话框中选择位流文件,添加该配置文件,提示所载入文件的绝对路径并在信息栏中显示,如下图所示:
下载位流文件
当发现这 4 个信号均为 1 时,表示下载成功。
同时,开发板也配置了一个外部 flash,其中,若需要将程序固化到板卡上需要将尾流文 件转化为.sfc 文件。
首先点击 Configuration 页面的 Operations 选项的 Covert File 选项。
点击后会出现如下画面,在Generate Flash Programing File页面选择对应的Flash 器件的厂商名、型号、再在 BitStreamFile 位置选择位流文件的路径,点击 OK。(若使用的 flash 器件不在可选的 flash 列表中,需手动添加对应 flash 型号,操作步骤请参考开发板下载与固化相说明)
转化.sfc 文件成功后,页面会如下图所示,点击 OK。
页面会显示板卡搭载的 Flash 的型号,点击.sfc 文件,点击 OPEN。
在下图位置点击鼠标右键后,点击 Program。
固化 Flash 成功如下图所示:
此时将板卡断电,再重新上电,如果按下 key0 和 key1 能看到对应的实验现象的话,则表示固化成功。(等大概 15s)