在 Vivado 中开发 ZYNQ 系列 SoC 时,AXI4 是使用频次较高的总线,有时候为了使 FPGA 功能模块化,还会将一些特定的模块封装成 AXI4 总线接口的自定义 IP。Xilinx 官方还有针对此项需求发布的官方文档UG1037,可以参阅。本篇博文以 AXI4 总线读写 PL 侧 BRAM 为例,详细讲解如何在 Vivado 中创建及封装自定义 IP 核,以及如何将自定义 IP 移植到其他工程。
一、Vivado 中创建及编写 AXI4 总线 IP
首先创建 IP 核:Tools->Creat and Package New IP,选择创建 AXI4 Peripheral:

在 Name 一栏的名称改为“ pl_bram_plus1”, IP 核的路径改为工程目录下的 ip_repo 文件夹,即删除路径“ /../”中间的一个“ .”符号,其它的设置直接保持默认即可,点击“NEXT”。

直到最后,点击“Finish”按钮完成自定义 IP 核的创建。

到这里 IP 核的框架已经搭建好了,接下来要去进行细节定义。
首先打开 IP Catalog,依次展开 User Repository→AXI Peripheral→“ pl_bram_plus1_v1.0”,右键选择“ Edit in IP Packager”。
打开 pl_bram_plus1_v1_0.v 文件,在 Users to add ports here 和 User port ends 中间行添加如下代码:
output wire ram_clk , //RAM 时钟 input wire [31:0] ram_rd_data, //RAM 中读出的数据 output wire ram_en , //RAM 使能信号 output wire [31:0] ram_addr , //RAM 地址 output wire [3:0] ram_we , //RAM 读写控制信号 output wire [31:0] ram_wr_data, //RAM 写数据 output wire ram_rst , //RAM 复位信号,高电平有
在实例化 pl_ram_plus1_v1_0_S00_AXI 模块的位置,添加以下代码。这里需要注意一下例化时的逗号问题,如果添加在最后,需要在之前的最后一个信号后添加逗号。
.ram_clk (ram_clk ), .ram_rd_data (ram_rd_data), .ram_en (ram_en ), .ram_addr (ram_addr ), .ram_we (ram_we ), .ram_wr_data (ram_wr_data), .ram_rst (ram_rst )
打开 pl_bram_plus1_v1_0_S00_AXI.v 文件,同样在 Users to add ports here 和 User port ends 中间行添加如下代码:
output wire ram_clk , //RAM 时钟 input wire [31:0] ram_rd_data, //RAM 中读出的数据 output wire ram_en , //RAM 使能信号 output wire [31:0] ram_addr , //RAM 地址 output wire [3:0] ram_we , //RAM 读写控制信号 output wire [31:0] ram_wr_data, //RAM 写数据 output wire ram_rst , //RAM 复位信号,高电平有
在程序最后 Add user logic here 和 User logic ends 的中间行,添加如下代码:
bram_ip_plus1 inst_bram_ip_plus1( .clk (S_AXI_ACLK), .rst_n (S_AXI_ARESETN), .start_rd (slv_reg0[0]), .start_addr (slv_reg1), .rd_len (slv_reg2), //RAM 端口 .ram_clk (ram_clk ), .ram_rd_data (ram_rd_data), .ram_en (ram_en ), .ram_addr (ram_addr ), .ram_we (ram_we ), .ram_wr_data (ram_wr_data), .ram_rst (ram_rst ) );
接下来在工程中创建一个新的模块,命名为“bram_ip_plus1”,位于../BRAMIP/ip_repo/pl_bram_plus1_1.0/hdl 路径下, bram_ip_plus1 模块的代码如下:
module bram_ip_plus1( input clk , //时钟信号 input rst_n , //复位信号 input start_rd , //读开始信号 input [31:0] start_addr , //读起始地址 input [31:0] rd_len , //读数据的长度 //RAM 端口 output ram_clk , //RAM 时钟 input [31:0] ram_rd_data, //RAM 中读出的数据 output reg ram_en , //RAM 使能信号 output reg [31:0] ram_addr , //RAM 地址 output reg [3:0] ram_we , //RAM 读写控制信号 output reg [31:0] ram_wr_data, //RAM 写数据 output ram_rst //RAM 复位信号,高电平有效 ); //reg define reg [1:0] flow_cnt; reg start_rd_d0; reg start_rd_d1; //wire define wire pos_start_rd; //***************************************************** //** main code //***************************************************** assign ram_rst = 1'b0; assign ram_clk = clk ; assign pos_start_rd = ~start_rd_d1 & start_rd_d0; //延时两拍,采 start_rd 信号的上升沿 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin start_rd_d0 <= 1'b0; start_rd_d1 <= 1'b0; end else begin start_rd_d0 <= start_rd; start_rd_d1 <= start_rd_d0; end end //根据读开始信号,从 RAM 中读出数据 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin flow_cnt <= 2'd0; ram_en <= 1'b0; ram_addr <= 32'd0; ram_we <= 4'd0; end else begin case(flow_cnt) 2'd0 : begin if(pos_start_rd) begin ram_en <= 1'b1; ram_addr <= start_addr; flow_cnt <= flow_cnt + 2'd1; end end 2'd1 : begin ram_wr_data <= ram_rd_data + 1; flow_cnt <= flow_cnt + 2'd1; ram_we <= 4'b1111; end 2'd2:begin if(ram_addr - start_addr == rd_len - 4) begin //数据读完 flow_cnt <= flow_cnt + 2'd1; end else begin ram_addr <= ram_addr + 32'd4; //地址累加 4 ram_we <= 4'b0000; flow_cnt <= flow_cnt - 2'd1; end end 2'd3 : begin ram_addr <= 32'd0; flow_cnt <= 2'd0; ram_en <= 1'b0; end endcase end end endmodule
代码都添加完毕后,进行 IP 封装。
二、Vivado 中封装自定义 AXI4 总线 IP
双击 IP-XACT 界面下的 component.xml 切换至 Packaging Steps 界面。依次完成下面的操作:
(1)点击“File Groups”一栏,随后点击界面上的“Merge changes from File Groups Wizard”;
(2)点击“Customization Parameters”一栏,随后点击界面上的“Merge changes from Customization Parameters Wizard”;
(3)点击“Ports and Interfaces”一栏,可以看到该 IP 核顶层模块的端口,为了方便在 Block Diagram 界面中对自定义的 IP 核的 BRAM 端口进行连线,我们需要将 BRAM 相关的端口定义成总线接口的形式,方法如下。
a)点击“Add Bus Interfac”图标,如下图所示:

b)在弹出的页面中,在 Name 一栏,输入 BRAM_PORT,然后点击 Interface Definition(总线定义)右侧的“…”图标,如下图所示:

c)接下来在弹出页面的搜索框中输入“BRAM”,选中 Advanced 一栏下的“bram_rtl”,即 BRAM 总线接口。然后点击“OK”按钮,如下图所示:

d)将页面切换至“Port Mapping”选项页,左侧窗口为总线逻辑端口,右侧窗口为 IP 核定义的物理端口,最下面的窗口是映射端口的总结页面,如下图所示:

e)点击左侧的“EN”端口,然后点击右侧的“ram_en”端口,此时页面上的“Map Ports”变成可以点击的按钮,点击这个按钮,此时在映射端口总结页面可以看到这两个端口映射到了一起,如下图所示:

f)这个步骤是将顶层模块定义的“ram_en”端口和 BRAM 的“EN”端口映射到一起,我们接下来将其余接口分别一一映射,如下图所示:

g)接下来将页面切换至“Parameters”选项页,展开 Auto-calculated,选中“MASTER_TYPE”,然后点击单个向右的箭头,如下图所示:

h)此时,“MASTER_TYPE”参数会出现在右侧 Overridden 目录下,最后点击“OK”按钮完成 BRAM 接口的封装,如下图所示:

(4)接下来切换至“Review and Package”页面,点击上侧的“IP has been modified”来更新 IP,最后点击“Re-Package IP”完成 IP 核的封装,此时 IP 核的封装界面会自动关闭。
退出后点击右侧的 IP Catalog,在 user repository 下面的 AXI 处右键添加 IP,添加之后就可以在 BD 中调用了,跟 Vivado 自带的 IP 核使用无异。
补充说明
(1)如果不嫌麻烦,可以在进行最后的封装前,进行一下综合,检查电路是否有问题,是否有信号被优化没了。不综合大多数情况下没问题,偶尔会出现问题。我建议可以不要嫌麻烦,综合后再进行封装。下面是将上述 IP 经过综合后的 RTL。

(2)使用时的注意事项:
在 board diagram 中使用时,需要注意 Address Editor 中是否自动分配了该 IP 的地址和长度,如果没有配置,则点击下图中红框处的图标进行自动分配地址。

否则,编译时会出现如下所示的警告,且编译不通过。

三、Vivado 中自定义 AXI4 总线 IP 核移植到其他工程
要想在一个工程中设置好的 IP 核移植到另一个工程中,只需要将 xci 文件添加即可。步骤如下:
1、add source

2、add file

注意第一个不能勾选,不然会和第二个冲突。
3、选择文件对应 IP 核的 xci 文件即可。
扫码关注尚为网微信公众号

原创文章,作者:sunev,如若转载,请注明出处:https://www.sunev.cn/embedded/1224.html