一、前言
利用FPGA设计算法一直以来都是热点,同样也是难点。将复杂的数学公式 模型通过硬件系统来搭建,在低延时 高并行性等优势背后极大提高了设计难度和开发周期。Xilinx公司的sysGen(system generator)工具扩展了MATLAB的simulink,提供很多IP Catalog中没有的基础模块和针对DSP应用的硬件模型。工程师利用丰富的模块和MATLAB强大的数据处理及可视化能力能够更快速完成设计与仿真验证工作。
二、sysGen算法系统设计
本文以个最简单的例子讲述利用sysGen搭建算法IP核,并集成到IP Integrator中作为ZYNQ PS端CPU的“定制外设”。仅用于测试目的。设计需求:在sysGen中搭建系统,将输入定点整数数据*2后输出,输入位宽为8bit。
在System Generator token中设定仿真步长为1sec。点击需要观测的信号连线,右击选择Xilinx add to viewer。启动仿真并启动Xilinx waveform viewer:
本质上就是调用Vivado的XSim工具进行行为仿真。仿真结果可见完成预期目标,现双击System Generator token ,选择Compiliation类型为IP Catalog并勾选Create testbench,按下Generate生成IP核。
三、仿真测试
根据User Guide介绍sysGen是“周期和比特精准的”,我们还是在Vivado环境下再次验证下。netlist文件夹内子文件夹ip_catalog中为IP核示例工程,由于自动生成了testbench,打开后直接进行行为仿真。sysGen在创建testbench时会将经过gatein和gateout的数据储存到文件中,testbench进行的工作为:将gatein数据作为测试激励送入到相应设计输入端口,之后把设计输出得到结果与gateout文件数据进行逐一比较从而验证设计是否与sysGen环境下仿真结果一致。
发现个比较有意思的现象,自动生成的testbench中clock生成并约束的50MHz,而是认为进行了拓展。
仿真波形如图:
将clock处改动为50MHz后,经过测试发现如果系统一开始就输入数据,前几个数据没有被真正处理,输出错误。可能是软件BUG吧,不过这种情况也非常少见,实际系统中输入数据大多情况会启动一段时间后才输入。这里等待100ns后再启动clock翻转:
改动后仿真波形:
四、AXI-Stream总线形式IP
到此算法IP的设计与验证结束。如果想将这个IP核导入到IP Integrator中作为CPU的外设,其接口必须满足AXI总线标准,因此回到sysGen中更改端口名称和位宽。端口要符合AXI-Stream标准信号名称,位宽为8bit整数倍。
生成IP核后,打开新的工程,导入该IP核到repository。
五、Block Design系统搭建
系统结构与上一篇该系列博文类似,均是以AXI DMA为核心的Loop系统,只是将AXI-Stream Data FIFO改成了自定义IP核。由于IP核slave和master接口只包含tdata和tvalid信号,因此需要添加接口衔接的一些简单逻辑。tready信号和tkeep信号直接连接constant使用常数驱动,DMA的s_axis_s2mm接口的tlast由wrapper内计数器逻辑驱动,将system中FCLK_CLK0 peripheral_aresetn m_axis_tvalid和s_axis_s2mm_tlast信号引出到wrapper中。
有一点比较坑:自定义IP通过AXI总线与DMA互联时,总线下相应的接口不一定会正确对应,所以需要分别将两端的每个接口相连。可以通过打开综合后的设计来确认连线无误。
自动生成wrapper后改动添加代码如下:
1 `timescale 1 ps / 1 ps 2 3 module user_wrapper 4 (DC, 5 DDR_addr, 6 DDR_ba, 7 DDR_cas_n, 8 DDR_ck_n, 9 DDR_ck_p, 10 DDR_cke, 11 DDR_cs_n, 12 DDR_dm, 13 DDR_dq, 14 DDR_dqs_n, 15 DDR_dqs_p, 16 DDR_odt, 17 DDR_ras_n, 18 DDR_reset_n, 19 DDR_we_n, 20 //FCLK_CLK0, 21 FIXED_IO_ddr_vrn, 22 FIXED_IO_ddr_vrp, 23 FIXED_IO_mio, 24 FIXED_IO_ps_clk, 25 FIXED_IO_ps_porb, 26 FIXED_IO_ps_srstb, 27 RES, 28 SCLK, 29 SDIN, 30 VBAT, 31 VDD 32 //m_axis_tvalid, 33 //peripheral_aresetn, 34 //s_axis_s2mm_tlast 35 ); 36 output DC; 37 inout [14:0]DDR_addr; 38 inout [2:0]DDR_ba; 39 inout DDR_cas_n; 40 inout DDR_ck_n; 41 inout DDR_ck_p; 42 inout DDR_cke; 43 inout DDR_cs_n; 44 inout [3:0]DDR_dm; 45 inout [31:0]DDR_dq; 46 inout [3:0]DDR_dqs_n; 47 inout [3:0]DDR_dqs_p; 48 inout DDR_odt; 49 inout DDR_ras_n; 50 inout DDR_reset_n; 51 inout DDR_we_n; 52 //output FCLK_CLK0; 53 inout FIXED_IO_ddr_vrn; 54 inout FIXED_IO_ddr_vrp; 55 inout [53:0]FIXED_IO_mio; 56 inout FIXED_IO_ps_clk; 57 inout FIXED_IO_ps_porb; 58 inout FIXED_IO_ps_srstb; 59 output RES; 60 output SCLK; 61 output SDIN; 62 output VBAT; 63 output VDD; 64 //output [0:0]m_axis_tvalid; 65 //output [0:0]peripheral_aresetn; 66 //input s_axis_s2mm_tlast; 67 68 69 localparam DATA_NUM = 256; 70 71 wire DC; 72 wire [14:0]DDR_addr; 73 wire [2:0]DDR_ba; 74 wire DDR_cas_n; 75 wire DDR_ck_n; 76 wire DDR_ck_p; 77 wire DDR_cke; 78 wire DDR_cs_n; 79 wire [3:0]DDR_dm; 80 wire [31:0]DDR_dq; 81 wire [3:0]DDR_dqs_n; 82 wire [3:0]DDR_dqs_p; 83 wire DDR_odt; 84 wire DDR_ras_n; 85 wire DDR_reset_n; 86 wire DDR_we_n; 87 wire FCLK_CLK0; 88 wire FIXED_IO_ddr_vrn; 89 wire FIXED_IO_ddr_vrp; 90 wire [53:0]FIXED_IO_mio; 91 wire FIXED_IO_ps_clk; 92 wire FIXED_IO_ps_porb; 93 wire FIXED_IO_ps_srstb; 94 wire RES; 95 wire SCLK; 96 wire SDIN; 97 wire VBAT; 98 wire VDD; 99 wire [0:0]m_axis_tvalid; 100 wire [0:0]peripheral_aresetn; 101 wire s_axis_s2mm_tlast; 102 103 reg [8-1:0] cnt; 104 wire add_cnt; 105 wire end_cnt; 106 107 system system_i 108 (.DC(DC), 109 .DDR_addr(DDR_addr), 110 .DDR_ba(DDR_ba), 111 .DDR_cas_n(DDR_cas_n), 112 .DDR_ck_n(DDR_ck_n), 113 .DDR_ck_p(DDR_ck_p), 114 .DDR_cke(DDR_cke), 115 .DDR_cs_n(DDR_cs_n), 116 .DDR_dm(DDR_dm), 117 .DDR_dq(DDR_dq), 118 .DDR_dqs_n(DDR_dqs_n), 119 .DDR_dqs_p(DDR_dqs_p), 120 .DDR_odt(DDR_odt), 121 .DDR_ras_n(DDR_ras_n), 122 .DDR_reset_n(DDR_reset_n), 123 .DDR_we_n(DDR_we_n), 124 .FCLK_CLK0(FCLK_CLK0), 125 .FIXED_IO_ddr_vrn(FIXED_IO_ddr_vrn), 126 .FIXED_IO_ddr_vrp(FIXED_IO_ddr_vrp), 127 .FIXED_IO_mio(FIXED_IO_mio), 128 .FIXED_IO_ps_clk(FIXED_IO_ps_clk), 129 .FIXED_IO_ps_porb(FIXED_IO_ps_porb), 130 .FIXED_IO_ps_srstb(FIXED_IO_ps_srstb), 131 .RES(RES), 132 .SCLK(SCLK), 133 .SDIN(SDIN), 134 .VBAT(VBAT), 135 .VDD(VDD), 136 .m_axis_tvalid(m_axis_tvalid), 137 .peripheral_aresetn(peripheral_aresetn), 138 .s_axis_s2mm_tlast(s_axis_s2mm_tlast)); 139 140 always @(posedge FCLK_CLK0)begin 141 if(!peripheral_aresetn)begin 142 cnt <= 0; 143 end 144 else if(add_cnt)begin 145 if(end_cnt) 146 cnt <= 0; 147 else 148 cnt <= cnt + 1; 149 end 150 end 151 152 assign add_cnt = m_axis_tvalid; 153 assign end_cnt = add_cnt && cnt== DATA_NUM-1; 154 155 assign s_axis_s2mm_tlast = end_cnt; 156 157 endmodule
当自定义IP核输出256个数据时,拉高tlast信号结束传输。打开综合后的设计,添加调试探针,抓取DMA与自定义IP之间的接口信号,set up debug后完成接下来的流程。
六、软硬件联调
在硬件系统中定义数据帧长度为256个,数据位宽为16bit,因此C代码中DMA启动传输函数中数据长度参数为512byte。测试数据生成与检测代码非常简单:
我们直接查看ILA抓取AXI S总线波形:
看到CPU产生数据从1到4重复递增,IP核输出结果从2到8重复递增,输出为输入的2倍。
传输完成后进入DMA发送和接收中断,软件检测结果正确。在Memory窗口能够直接查看内存绝对地址里的数据,选定DDR接收缓存区起始地址,其中的数据与AXI总线传回数据一致,证明系统联调成功。之后任意算法模块均可采用本文方式进行设计和集成,可以说一劳永逸!