Xilinx ZYNQ系列的芯片,GPIO分为 MIO 、EMIO、AXI_GPIO三种方式。
MIO:固定管脚,属于PS端,也就是ARM端
EMIO:通过PL扩展,使用时需要分配PL(FPGA)管脚,消耗PL端资源
AXI_GPIO:封装好的IP核,PS通过M_AXI总线可以控制PL的IO接口,使用时,消耗管脚资源和逻辑资源。
MIO(Multiuse I/O),多功能IO接口,分配在GPIO的Bank0 和 Bank1,属于Zynq的PS(ARM)部分。
这些引脚,可以直接用在GPIO、SPI、UART、TIMER等等一些PS端外设,这些引脚与PS直接相连接,不需要添加引脚约束,并且也不占PL端资源。
Bank0有32个MIO引脚,Bank1有22个MIO引脚,54个引脚直接通过MIO连接到PS上。
EMIO(Express Multiuse I/O),扩展多功能IO,分布在BANK2、BANK3,依然属于Zynq的PS部分,只是直接连接到了PL上,当MIO不够使用时,可以通过EMIO控制PL部分的引脚。
当我们想通过PS来访问PL又不想浪费AXI总线时,就通过EMIO接口来访问,使用的时候,需要添加约束文件,分配PL端的引脚。
AXI_GPIO,相当于GPIO的IP核,通过AXI总线挂在PS上的GPIO。
我们在FPGA工程上添加相应的GPIO的IP核,然后生成相关PL端的逻辑,并且添加约束文件,以分配PL端的管脚才能正常使用。其占用了PL端的逻辑和资源。
AXI4协议支持以下三种接口:
存储映射(Meamory Map):如果一个协议是存储映射的,那么主机 所发出的会话(无论读或写)就会标明一个地址。这个地址对应于系统存储空间中的一个地址,表明是针对 该存储空间的读写操作。
AXI4总线共有五个通道:
Vivado可以使用向导创建带有AXI接口的IP模块,点击Tool里的Create and Package New IP即可打开向导。
下面是简单地配置AXI接口的相关信息:
我们主要目的是制作一个AXI接口的IP,因此我们选择编辑这个IP
工程里自动多了一个顶层模块和一个实例模块,这是Xilinx官方给的一个AXI接口的示例,我们设计AXI接口的IP核,如果是主接口,那么我们要有一套操控AXI接口,符合AXI时序的硬件;如果是从接口,那么我们要有能响应AXI接口请求的的一套硬件。
Xilinx官方在创建AXI接口IP的时候提供给了我们一个例子,让我们可以在这个例子的基础上进行改造。
编辑模块代码,这里截取部分
// Users to add parameters here
output wire [3:0] idin ,
output wire en ,
output wire [8:0] wd ,
output wire [8:0] ht ,
output wire [2:0] strd ,
output wire [2:0] pad ,
output wire [2:0] wd_dsc ,
output wire [2:0] ht_dsc ,
input wire [3:0] id_out ,
input wire done ,
// User parameters ends
......
//----------------------------------------------
//-- Signals for user logic register space example
//------------------------------------------------
//-- Number of Slave Registers 4
reg [C_S_AXI_DATA_WIDTH-1:0] slv_reg0;
reg [C_S_AXI_DATA_WIDTH-1:0] slv_reg1;
reg [C_S_AXI_DATA_WIDTH-1:0] slv_reg2;
reg [C_S_AXI_DATA_WIDTH-1:0] slv_reg3;
wire slv_reg_rden;
wire slv_reg_wren;
reg [C_S_AXI_DATA_WIDTH-1:0] reg_data_out;
integer byte_index;
reg aw_en;
......
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] )
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
// Add user logic here
assign idin = slv_reg0[ 3: 0];
assign en = slv_reg1[ 31];
assign wd = slv_reg1[29:21];
assign ht = slv_reg1[20:12];
assign strd = slv_reg1[11: 9];
assign pad = slv_reg1[ 8: 6];
assign wd_dsc = slv_reg1[ 5: 3];
assign ht_dsc = slv_reg1[ 2: 0];
// User logic ends
实现数据流向: 主机写数据--->接口实例--->寄存器-->自定义功能模块 (数据流向)
编辑完毕,切换到封装IP窗口
新建指令编码模块
//对axi4接收的数据进行处理
module instr_code(
input clk ,
input rst_n ,
input [3:0] idin ,
input en ,
input [8:0] wd ,
input [8:0] ht ,
input [2:0] strd ,
input [2:0] pad ,
input [2:0] wd_dsc,
input [2:0] ht_dsc,
output [3:0] id_out,
output done
);
reg [3:0] idin_reg;
assign id_out = idin + 1;
assign done = idin_reg!=idin;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n)
idin_reg <= 0;
else
idin_reg <= idin;
end
endmodule
创建Block Design,添加ZYNQ PS,双击ZYNQ核,启用GP从接口
添加自定义AXI-Lite IP
把指令编码模块添加进Block Design,并自动连线
这个教程选择使用ILA来观测信号,所以添加ILA IP
设计好Block Design后,生成output products和wrapper(.v文件)
然后生成比特流,导出硬件,打开Vitis导入
创建一个新的Application Project,新建main.c文件 在板级支持包里找到参数头文件xparameters.h,在里面找到自定义IP的基地址,然后在main.c中重命名一下方便使用。 在板级支持包里找到自定义IP的头文件ps_pl_axilite.h,在里面找到读写寄存器调用的函数Xil_In32()和Xil_Out32()(调用需包含头文件xil_io.h),复制到main.c中重命名一下方便使用。同时观察到自定义IP中的4个从寄存器在基地址上的偏移量依次递增4(4个Byte,即每个从寄存器32bit)。 main.c完整代码如下
#include "xparameters.h"
#include "xil_io.h"
#include "stdio.h"
#define INSTR_BASE XPAR_PS_PL_AXILITE_0_S_AXI_BASEADDR
#define SREG0_OFFSET 0
#define SREG1_OFFSET 4
#define SREG2_OFFSET 8
#define INSTR_WriteReg(BaseAddress, RegOffset, Data) \
Xil_Out32((BaseAddress) + (RegOffset), (u32)(Data))
#define INSTR_ReadReg(BaseAddress, RegOffset) \
Xil_In32((BaseAddress) + (RegOffset))
int main()
{
int idin[2] = {1, 2};
int en[2] = {1, 1};
int wd[2] = {496, 248};
int ht[2] = {432, 216};
int strd[2] = {2, 1};
int pad[2] = {1, 1};
int wd_dsc[2] = {1, 0};
int ht_dsc[2] = {1, 0};
int id_out = 0;
int done = 0;
int i = 0;
int instruction0 = 0;
int instruction1 = 0;
int instruction2 = 0;
while(1)
{
scanf("%1d", &i);
printf("The input model number is: %1d\n", i);
instruction0 = idin[i];
instruction1 = (en[i]<<31) + (wd[i]<<21) + (ht[i]<<12) + (strd[i]<<9) \
+ (pad[i]<<6) + (wd_dsc[i]<<3) + ht_dsc[i];
INSTR_WriteReg(INSTR_BASE, SREG0_OFFSET, instruction0);
INSTR_WriteReg(INSTR_BASE, SREG1_OFFSET, instruction1);
instruction2 = INSTR_ReadReg(INSTR_BASE, SREG2_OFFSET);
id_out = (instruction2 & 0x000E)>>1;
done = instruction2 & 0x0001;
printf("\t id_out: %3d, done: %1d\n", id_out, done);
}
}
然后编译下载,Vivado打开ILA调试,debug观察数据传输是否正确
参考: https://blog.csdn.net/weixin_54358182/article/details/127041949
https://zhuanlan.zhihu.com/p/582335657
https://blog.csdn.net/weixin_54358182/article/details/127081516?spm=1001.2014.3001.5502
本文章使用limfx的vscode插件快速发布