【技术分享】fpga杂记之基础篇二及demo案例-凯发官网入口

发布时间2021-01-19

前言


本文接续上一篇《fpga杂记基础篇》,继续为大家分享ip例化和几个基于fpga芯片实现的demo工程。


ip例化


ip即是一个封装好的模块,集成在相应的开发环境里面,以安路的td软件为例,不同系列的芯片集成了不同的ip模块,可以通过软件例化调用。


以下是安路td4.6.5集成的ef3l40cg332b的相关ip。



1.1 pll&ram


以例化pll和ram为例,实现两个异步双口 ram。


读写时钟都设置 100mhz, 两个 ram 为 rama 和ramb, 深度为 1024,位宽为 8bit,写入数据为 8bit,100mhz 持续数据流, 当 rama被写入 1024 字节数据后切换到写 ramb, ramb 被写入 1024 字节后切换 rama。以此循环类推。


当 rama 被写入 1024 字节时, 给读时序提供一个启动信号读取 rama 的数据, 读取完 rama 的 1024 字节数据时, 切换读 ramb 以此类推。


这个工程的工程结构如下图:



首先ef3l40cg332b_dev开发板提供了25mhz的晶振时钟输入到ef3l40cg332b的时钟管脚。



想要得到100mhz的读写速率,需要先用pll得到倍频时钟。



在tools目录下点击ip generator进入ip core页面,并选择pll,输入时钟填入板子晶振25mhz。



输出时钟填入所需要的100mhz,并从c0输出。



设置完成后,生成的module声明如下(完整模块可参考代码)



再生成ram的ip模块


在ip core中选择ram



memory type选择简单双口ram,memory size设置位宽8bit深度1024


设置完成后,生成的module声明如下(完整模块可参考代码)



然后编写顶层文件并且在顶层例化pll和rama、ramb。


顶层文件中主要是对ram的输入口进行时序操作,包括rama、ramb的读地址,写地址、使能信号和输入输出数据,详细代码笔记中不再赘述,可以直接参考代码。


可综合模块编写完成后,编写仿真模块并使用仿真软件进行仿真。


由于本次工程使用到了安路的ip库,因此也需要在modelsim中添加相应的安路仿真库,添加方法如下:


首先在modelsim的安装目录下面编辑modelsim的初始化文件modelsim.ini,右键属性后,将它的只读属性取消,然后用文本文件(本工程使用的是notepad )编辑。



在modelsim.ini的[library]列表下添加安路的仿真库文件目录,安路所有的仿真库文件都在安路的编译软件td安装目录下的sim文件夹中,此处将其所有的库文件都复制进了modelsim的文件夹里,若不复制,也可直接输入安路文件夹的路径。



保存后退出,打开modelsim并创建工程,编译通过后,进入仿真步骤,在simulate状态栏下选择start simulate,如下图:



选择后进入到如下页面:



选择仿真的顶层并且关闭优化选项。


同一个窗口打开libraries页面并在search libraries栏右侧选择add,下拉列表选择对应的ef3的库文件。



设置完成后点击ok进入仿真即可。


本次实验中遇见问题和调试如下:


01



刚开始pll没有输出信号,因此打开了pll查看波形发现pll波形如下:



发现是置位了reset信号导致的,查看代码发现如下:



复位信号直接连接到了pll的置位信号,由于复位信号是低电平有效而置位信号是高电平有效,因此导致了pll一直处于复位的情况。更改后,直接将pll复位信号置0,代码和仿真结果正常,如下所示:



02


pll问题解决后,观测数据整体读写情况。



初步观测可以发现,rama读使能信号只转变了一次,而ramb的读使能始终未能跳变,返回代码查看发现:



逻辑判断时未将rama的写使能信号置1的条件写出来。


ramb的写使能同理:



修改后,代码如下:



波形仿真如下:



粗看基本符合rama、ramb交替读写的功能,观测rama、ramb时序交替细节。



在rama和ramb写使能信号转换时,发现空了一拍,再查看代码,发现rama、ramb写使能在条件判断时,使用的判断逻辑是不一样的,导致ramb_wren的置位会在rama_wren置位后的下一拍进行。



因此更改ramb_wren的判断条件,使之与rama_wren的一样,都以写地址为条件判断再仿真。



可观测到,时序正常。



quick start & gpio demo


本次demo实现功能如下:fpga控制led d1闪烁,mcu控制led d2常亮。


2.1 keil工程环境创建


创建文件夹目录如下:


图1


其中板级支持包直接由原厂提供。


先创建keil工程,打开keil,创建工程,保存在对应的mcu→project目录下。



器件选择arm cortex m3器件。



工程建好后,添加必要的bsp包中的文件如下,创建好后的工程目录如左栏:



其中,startup组下的文件分别来自mcu\elf2_bsp\device\elf2\source和mcu\elf2_bsp\device\elf2\source\arm目录下;lib组的文件来自mcu\elf2_bsp\driver;log组文件保存在mcu\elf2_bsp\debug和mcu\elf2_bsp\debug\rtt目录下。


新建main.c文件并保存在图1所示的总文件目录浏览的mcuàsrc文件夹下并添加main.c到工程main组中。



下面设置一些工程的环境,打开options for target对话框。



切换到user栏,设置如下参数,这些参数会影响输出keil工程的*.asm 和*.bin 文件,我们需要通过添加这两条指令得到bin文件并最终提供给fpga。



添加的语句分别如下:


fromelf -c -v -a --output=@p.asm objects\%l

fromelf --bin --output=@p.bin objects\%l


再切换到c/c 栏,设置头文件路径如下:



也可以直接添加如下目录


..\elf2_bsp;..\elf2_bsp\cmsis\core\include;..\elf2_bsp\debug;..\elf2_bsp\debug\rtt;..\elf2_bsp\driver;..\elf2_bsp\driver\regmap;..\elf2_bsp\device\elf2\include;..\elf2_bsp\device\elf2\source\arm


其余设置如下图:



添加分散加载文件elf2_example.sct(elf2_example.sct文件具体代码可参考工程)



环境设置完毕后可以开始编写工程代码。


2.2 c代码编辑


在main函数中编写对gpio的操作。



先对gpio初始化结构体赋值,再调用gpio初始化函数,hal_gpio_writepin函数对相应的gpio进行高低赋值。


本次使用c代码对gpio1_0的操作是置低,gpio1_0具体含义会在下一节(1.3)进行说明。


2.3 td工程创建和代码编辑


打开td4.6.5或其他版本创建新的工程。



保存在总目录的fpga→project目录下,并选择对应的器件类型。



添加或者编辑源文件,本次工程模块声明如下:



其中hw_led是由fpga逻辑控制的led,sw_led是由mcu代码控制的led(即1.2中的gpio1_0)。hw_led的控制代码如下,sw_led的控制代码详见1.2:



然后例化mcu和pll,pll例化主要得到输入到mcu的系统时钟,例化过程略,这里贴上在顶层中调用的结果:



输出的200m的时钟接到mcu的系统时钟。


例化mcu界面如下:



如图所示,mcu支持最大 32 个gpio,其中低16位,即gpio_l0~gpio_l15是直接连接至pad的;而gpioh0~gpioh15则是通过fpga连接至外部,因此,当使用这16个gpio的时候,需要在fpga工程的管教约束文件中指定具体连接至哪个脚。


在例化mcu时,使用到哪个脚就可以打开对应的开关,例如本例中,打开了l0、l1和h0,ppm_clk,其中ppm_clk是fpga fabric 输入时钟,连接至fpga的pll输出clk200;l0、l1连接至pad,观察原理图。



gpio0和gpio1连接的是调试口;最后h0连接至fpga 中sw_led并通过管脚约束连接至led d2。


工程的管教约束文件如下:



查开发板原理图,d2连接至fpga的16脚,且从原理图可观察,keil工程中对该gpio的操作是置低,具体显示是d2常亮。



设置完毕后,完成结果声明如下:



并在顶层中调用:



2.4 下载


keil和td的工程都创建编写完成后,编译工程。其中,keil生成的工程bin文件需要与td关联并通过td下载至芯片或开发板中。


关联的步骤如下:


在hdl2bit flow栏右键选择properties。



在generate bitstream的第六项instruct ram中选择keil工程生成的bin文件的目录(此时keil工程已经编译通过),并保存。



保存后,双击generate bitstream编译td工程,假如在选择路径前已经编译过td工程了,需要右键选择rerun重新编译(注意:假如修改了keil的c文件而td的hdl文件没有变化,建议也rerun后再将文件下载至开发板)



下载:



板子现象如图:


d1持续闪烁,d2常亮:



fpga串口通信


本demo案例基于安路的ef2m45lg48_mini_dev2开发板,通过测试板的uart口和pc机的uart口连接来形成一个闭环回路,即pc机发送数据至fpga测试板,fpga接收并返回相同的数据。实验结果通过pc机的串口调试助手调试查看。


3.1 uart协议


uart 是一种通用串行数据总线,用于异步通信,将数据在串行通信和并行通信间的传输转换。通俗的讲就是把多比特的数据转化为单比特的数据(tx端),或者把单比特的数据转化为多比特的数据(rx端)。工作原理是将数据的每个 bit 一位接一位地传输。


rx,接收端,位宽为 1 比特, pc 机通过串口往 fpga 发 8 比特数据时,fpga 通过串口线 rx 一位一位地接收,从最低位到最高位依次接收,最后在 fpga 里面位拼接成8 比特数据。


tx,发送端,位宽为 1 比特, fpga 通过串口往 pc 机发 8 比特数据时, fpga 把 8 比特数据通过 tx 线一位一位的传给 pc 机,从最低位到最高位依次发送,最后上位机通过串口助手把这一位一位的数据位拼接成 8 比特数据。


注意点:


1、串行数据的发送和接收都是从低位到高位。


2、在不发送或者不接收数据的情况下, rx 和 tx 处于空闲状态,此时 rx 和 tx 线都保持【高电平】,如果有数据传递,首先会有一个起始位0,然后是 8 比特的数据位,接着有 1 比特的停止位(高电平),如果停止位以后不再发数据,将进入空闲状态,否则又将数据线拉低(进入起始位状态)。


3、波特率计算:uart传输有不同的波特率,使用hdl语言描述时,通常使用计数器来实现不同波特率的数据传播。计数器的计数值与具体波特率有关,以常见的115200为例,假设系统时钟是25mhz,则传输1bit所需要的时钟周期为25 * 1000 *1000 /115200 = 217个,因此计数器计数值即216(从0开始计数)。


3.2 模块总框架


模块的总体框架如下:



top层除了时钟和复位信号的输入,还有输入信号rx和输出信号tx,分别来自pc机和输出到pc机,形成闭环。子模块中,rx信号再作为uart_rx模块的输入,经过uart_rx模块的处理,转换成八位并行数据o_data输出;对于uart_tx模块,主要将输入的i_data并行信号转换成串行数据再输出到pc机。


3.3 代码实现


1. rx端



2.tx端



3.4 顶层



3.5 仿真



本次仿真使用到了task语句,task语句通常在当仿真时需要给输入变量特定的输入值时使用,例如本次仿真对rx端进行赋值。


3.6 仿真结果及问题排查


rx端:


整体波形如图:



查看细节如下:



当rx=1时,输出的o_data并行数据在o_flag = 1(即表示传输结束)时也为1,结果正常。


tx端:


整体波形如下:



上图很明显可以看出tx端传输有问题,当tx发送起始位(即拉低)后,没有将数据输出。观测其他信号波形,基本正常。可见问题大概率出现在tx赋值部分,一开始以为是发送数据位的条件判断有问题,检查代码,数据传输时的判断条件如下:



查看波形发现该条件可以被满足。



后来查看起始位的发送条件时发现了错误:



起始位发送要与tx_en同步。假设条件使用tx_en判断,则会比tx_en慢一拍。


另外,不能使用tx_en == 1'b1作为发送起始位的判断条件,因为tx_en 在数据发送时一直为1,这样tx端会恒为0,修改后代码如下:



再观察波形正常。



3.7 上板最终效果


代码下载进开发板后,在串口调试助手中可以正常收发数据,如下:



网站地图