在本实验中,我们将采用封装带有AXI4接口的IP的方式,实现PS和PL的数据交换,另外自定义IP核可以定制化系统设计,以达到设计重用的目的,可以很大程度上简化系统设计和缩短产品上市的时间。
本次实验任务:通过自定义一个含有AXI总线的加减法器 IP核,在ps端随机生成数据,传输到pl端,在pl端进行计算后,将结果发送到ps端并通过uart打印出来。同时用pl端控制的LED灯显示此时计算的是加法还是减法。
下面开始实验步骤。
步骤一:打开Vivado后,点击下图位置的Manage IP,并在选项中选择New IP Location。
然后再设置界面自行选择一个保存IP的地址,其他保持默认即可。点击Finish,完成创建。
进入设计后在工具栏点击下图位置,
在创建过程中,参数配置过程如下图所示,其他配置则默认即可
上图参数在本实验中可保持默认,但此配置比较重要,则此处做个介绍:
Interface Tpye(接口类型):共三种接口类型可选,分别是 Lite、 Full 和 Stream。 AXI4-Lite 接口是简化版的 AXI4 接口, 用于较少数据量的存储映射通信; AXI4-Full 接口是高性能存储映射接口,用于较多数据量的存储映射通信; AXI4-Stream 用于高速数据流传输,非存储映射接口。本次实验只需少量数据的通信,因此接口类型选择默认的 Lite 接口。
Interface Mode(接口模式):接口模式有 Slave(从机)和 Master(主机)两种模式可选, AXI 协议是主机和从机通过“握手”的方式建立连接,这里选择默认的 Slave 接口模式。
Data Width(数据宽度):数据位宽保持默认,即 32 位位宽。
Memory Size(存储器大小): 在 AXI4-Lite 接口模式下,该选项不可设置。
Number of Registers(寄存器数量):用于配置寄存器的数量,这里保持默认。
点击“ Next”按钮。
步骤二:添加完成后,在IP Catalog界面可以看见我们新添加的IP。接下来我们需要对这个IP进行编辑。选中这个IP,右键—Edit in IP Packager。弹出窗口默认点OK即可,此时会新打开一个工程。打开这两个文件的Verilog源代码。
此处我们需要简单介绍下面这个文件实现AXI4协议下的读写寄存器的功能。如下图所示,下面定义的四个寄存器则为我们前面配置时候设置的四个寄存器,对这四个寄存器的读写即可实现PS与PL端的数据交互,且这四个寄存器均为32位宽。
下面我们在工程的Design Sources上右键选择添加文件,并按照下图设置文件名并存放在这个IP工程的hdl相对位置下。
在新创建的文件中敲入以下代码:
module calculate(
input wire clk,
input wire rst,
input wire[9:0] add1, //第一个数
input wire[9:0] add2, //第二个数
input wire calculate_sign, //1表示加和,0表示作差
output wire led, //亮表示此时计算加法,灭表示减法
output reg[10:0] result
);
assign led = calculate_sign;
always @(posedge clk or negedge rst) begin
if (!rst) begin
result <= 0;
end
else if (calculate_sign == 1) begin
result <= add1 + add2;
end
else if (calculate_sign == 0) begin
if(add1 > add2)
result <= add1 - add2;
else begin
result <= add2 - add1;
end
end
end
endmodule
由上可知:在该代码中我们与PS交互的数据有10位宽的add1、add2、1位宽的calculate_sign和11位宽的result。接下来我们需要将该模块例化在前面打开的文件中:
1.在Calculate_IP_v1_0_S00_AXI文件中的如下位置,加入如下例化过程:
在例化时我们需要清楚calculate模块中的引脚分别需要连接到哪里,首先时钟和复位模块可直接与Calculate_IP_v1_0_S00_AXI中的时钟和复位相连;至于需要与PS交互读写的几个变量则可按照自己的习惯按照相同的位宽与四个寄存器合理相连;led灯引脚则需要牵出与PL端的LED灯相连。
2.由于led灯需要与pl端资源相连,因此应该在Calculate_IP_v1_0_S00_AXI模块的如下位置,添加上led灯的输出:
3.此外还需要在Calculate_IP_v1_0文件中的例化和输出中分别加入led变量:
4. 上述例化过程完成了ps端到pl的数据传输,下面介绍pl到ps端数据传输的部分。下列代码是Calculate_IP_v1_0_S00_AXI文件中对四个寄存器进行写操作的代码:
always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 1'b0 )
begin
slv_reg0 <= 0;
slv_reg1 <= 0;
// slv_reg2 <= 0;
slv_reg3 <= 0;
end
else begin
if (slv_reg_wren)
begin
case ( axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] ) //[3:2]
2'h0:
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
if ( S_AXI_WSTRB[byte_index] == 1 ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 0
slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
end
2'h1:
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
if ( S_AXI_WSTRB[byte_index] == 1 ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 1
slv_reg1[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
end
// 2'h2:
// for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
// if ( S_AXI_WSTRB[byte_index] == 1 ) begin
// // Respective byte enables are asserted as per write strobes
// // Slave register 2
// slv_reg2[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
// end
2'h3:
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
if ( S_AXI_WSTRB[byte_index] == 1 ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 3
slv_reg3[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
end
default : begin
slv_reg0 <= slv_reg0;
slv_reg1 <= slv_reg1;
// slv_reg2 <= slv_reg2;
slv_reg3 <= slv_reg3;
end
endcase
end
end
end
我们需要在这里把slv_reg2作为pl到ps传输时的中转寄存器,因此需要将它的赋值屏蔽掉,即直接注释掉即可。并在该文件中加入下列代码(下列例化过程与前面介绍的略有不同,最终版本如下)
// Add user logic here
//屏蔽掉前面所有对slv_reg2的赋值
always @( posedge S_AXI_ACLK )begin
slv_reg2[10:0] <= result;
end
wire[10:0] result;
calculate u_calculate(
.clk (S_AXI_ACLK),
.rst (S_AXI_ARESETN),
.add1 (slv_reg0[9:0]),
.add2 (slv_reg1[9:0]),
.calculate_sign (slv_reg0[31]),
.led (led),
.result (result) //slv_reg2[10:0]
);
完成上述步骤后即可,点击Run Synthesis进行编译。
步骤三:编译完成后,可通过IP-XACT 界面下的 component.xml 打开Package IP界面,开始设置IP的封装。依次按照下图步骤进行操作:
到此位置,自定义IP就配置完成了,该IP工程可以直接关掉,下面按照以往的步骤新建工程。
步骤四:在新建的工程中,先将刚才建立的IP的地址导入到该工程的IP目录下,操作如下:
再Create Block Design,先添加zynq,并对其进行设置:(1)添加UART。(2)根据各自开发板更改Clock Congfiguration中Basic Clocking的输入频率。(3)根据各自开发板对DDR进行配置。
。再搜索添加我们刚才建立的IP:Calculate_IP,然后直接点击Run Block Automation和Run Connection Automation,然后在LED灯引脚上Make External:
然后添加引脚约束文件,代码如下:
set_property IOSTANDARD LVCMOS33 [get_ports {led_0}]
set_property PACKAGE_PIN R14 [get_ports led_0]
对该Block Design 依次Generate Output Products,Create HDL Wrapper,点击保存,再Generate Bitstream,在耐心等待之后再Export Hardware,记住选择Include bitstream。再launch SDK。
第五步:SDK中新建Empty Application。在src文件夹下添加新source file。并输入如下代码(下列代码中,最可能需要更改的就是#define宏定义的那几个变量,其中XPAR_CALCULATE_IP_0_S00_AXI_BASEADDR在xparameters.h文件的38行附近可以找到;CALCULATE_IP_S00_AXI_SLV_REG0_OFFSET的这三个变量可以在Calculate_IP.h文件的10行附近可以找到;IP核名称不同,这些变量的名称是一定不同的,需要按照上述方法寻找)
#include "stdio.h"
#include "xparameters.h"
#include "Calculate_IP.h"
#include "xil_io.h"
#include "sleep.h"
#include "stdlib.h"
#define CALCULATE_IP_BASEADDR XPAR_CALCULATE_IP_0_S00_AXI_BASEADDR //IP的基地址
#define CALCULATE_IP_REG0 CALCULATE_IP_S00_AXI_SLV_REG0_OFFSET //REG0地址
#define CALCULATE_IP_REG1 CALCULATE_IP_S00_AXI_SLV_REG1_OFFSET //REG1地址
#define CALCULATE_IP_REG2 CALCULATE_IP_S00_AXI_SLV_REG2_OFFSET //REG2地址
int main(){
int result=0,add1=1,add2=2,calculate_sign=0;
printf("test\n");
while(1){
//生成0-999的随机数
add1 = rand()%1000;
add2 = rand()%1000;
calculate_sign = rand()%2;
if(calculate_sign == 1){
CALCULATE_IP_mWriteReg(CALCULATE_IP_BASEADDR,CALCULATE_IP_REG0, 0x80000000+add1);
printf("%d + %d = ", add1, add2);
}
else{
CALCULATE_IP_mWriteReg(CALCULATE_IP_BASEADDR,CALCULATE_IP_REG0, add1);
if (add1>add2){
printf("%d - %d = ", add1, add2);
}
else{
printf("%d - %d = ", add2, add1);
}
}
CALCULATE_IP_mWriteReg(CALCULATE_IP_BASEADDR,CALCULATE_IP_REG1, add2);
// usleep(3);
//读取结果
result = CALCULATE_IP_mReadReg(CALCULATE_IP_BASEADDR,CALCULATE_IP_REG2);
printf("%d\n",result);
sleep(1);
}
return 0;
}