在zynq的使用中,我们会经常使用一些自建的IP,本文章向大家介绍如何自建一个axi-lite的基于寄存器模式下的gpio模块
硬件: fii_7030
vivado: 2018.3
在zynq 系统设计中,我们会创建一些自定义的IP ,以实现用户的某些功能。其中,比较常见的是创建axi-lite 寄存器IP(当然也有其他一些自建的IP)。 寄存器模块可以实现很多功能,可以为用户的IP提供cpu的寄存器接口, 也可以用来控制gpio等。
打开vivado 工程,选择create and Package New IP…

选择next

选择Create a new axi4 peripheral

添加用户自建IP的名称:

选择lite,这里我们选择8个寄存器,用户可以根据实际需求更改寄存器个数,每个寄存器都是32bit的,(data width = 32).

选择 edit IP,编辑系统自动生成的带有axi-lite接口的寄存器模块。


双击my_module_v1.0 文件
我们会发现打开的文件没有实质内容, 只是负责将信号进一步的传递,所以我们继续打开my_module_v1.0_s00_axi文件:

打开文件后,我们会发现文件很长,内容也比较多, 如果大家学习过axi总线协议,可以对照协议对每个信号进行理解,如果对axi总线不是很熟悉,那么我们就直接找到我们需要的代码即可。
首先,在模块接口上会发现:

我们可以把自己定义的信号添加到这个位置即可。(注:当前的模块由vivado 自动生成的, 但系统生成的模块不一定满足用户的需求,所以我们有时会添加一些用户自定义的接口)。这里,我们就可以把系统生成的寄存器引出,形成自建的gpio (通过定义某个寄存器为gpio)。
这里,我们输出8组寄存器:

这样,这些寄存器可以被zynq cpu 控制, 同时,这些寄存器的输出后,我们就可以写自己的verilog 代码,实现相关的自定义功能了。
接下来我们找到cpu写寄存器, 这些寄存器被当前模块输出reg_out_x:

通过assign 命令,我们就可以实现cpu 对寄存器的写操作输出。
接下来找到寄存器的输入,然后被cpu 读走的代码:
原始的代码如下:

修改之后的代码如下:

reg_in_x 由模块顶端输入。
接下来,我们将用户修改好的端口,逐层传递到顶层即可:

接下来完成IP核的设计:

到此ip核已经设计完毕,这个ip核存放在相应的目录下。

到此,这个ip核就可以正常使用,使用方法,和vivado自带的ip核的使用相同。
接下来,我们使用刚刚建立的ip(my_module) 组建一个cpu 系统。

当然zynq cpu的配置,根据相关的硬件进行设置。
修改zynq 设置:


选择让系统自动连接:

最终的cpu 连接如下:



这时,我们看到最终生成的ip 例化接口文件(verilog 文件如下):
design_1 design_1_i
(.DDR_addr(DDR_addr),
.DDR_ba(DDR_ba),
.DDR_cas_n(DDR_cas_n),
.DDR_ck_n(DDR_ck_n),
.DDR_ck_p(DDR_ck_p),
.DDR_cke(DDR_cke),
.DDR_cs_n(DDR_cs_n),
.DDR_dm(DDR_dm),
.DDR_dq(DDR_dq),
.DDR_dqs_n(DDR_dqs_n),
.DDR_dqs_p(DDR_dqs_p),
.DDR_odt(DDR_odt),
.DDR_ras_n(DDR_ras_n),
.DDR_reset_n(DDR_reset_n),
.DDR_we_n(DDR_we_n),
.FCLK_CLK0(FCLK_CLK0),
.FIXED_IO_ddr_vrn(FIXED_IO_ddr_vrn),
.FIXED_IO_ddr_vrp(FIXED_IO_ddr_vrp),
.FIXED_IO_mio(FIXED_IO_mio),
.FIXED_IO_ps_clk(FIXED_IO_ps_clk),
.FIXED_IO_ps_porb(FIXED_IO_ps_porb),
.FIXED_IO_ps_srstb(FIXED_IO_ps_srstb),
.peri_reset_n_out(peri_reset_n_out),
.reg_in_0_0(reg_in_0_0),
.reg_in_1_0(reg_in_1_0),
.reg_in_2_0(reg_in_2_0),
.reg_in_3_0(reg_in_3_0),
.reg_in_4_0(reg_in_4_0),
.reg_in_5_0(reg_in_5_0),
.reg_in_6_0(reg_in_6_0),
.reg_in_7_0(reg_in_7_0),
.reg_out_0_0(reg_out_0_0),
.reg_out_1_0(reg_out_1_0),
.reg_out_2_0(reg_out_2_0),
.reg_out_3_0(reg_out_3_0),
.reg_out_4_0(reg_out_4_0),
.reg_out_5_0(reg_out_5_0),
.reg_out_6_0(reg_out_6_0),
.reg_out_7_0(reg_out_7_0));
之后,我们可以正常的写相关的verilog代码,把reg_in_x_0 和 reg_out_x_0 连接到用户自定义的逻辑上了。
举例:
(* mark_debug = “true” *)wire [31:0] reg_in_0_0;
wire [31:0] reg_in_1_0;
wire [31:0] reg_in_2_0;
wire [31:0] reg_in_3_0;
wire [31:0] reg_in_4_0;
wire [31:0] reg_in_5_0;
wire [31:0] reg_in_6_0;
wire [31:0] reg_in_7_0;
wire [31:0] reg_out_0_0;
(* mark_debug = “true” *)wire [31:0] reg_out_1_0;
wire [31:0] reg_out_2_0;
wire [31:0] reg_out_3_0;
wire [31:0] reg_out_4_0;
wire [31:0] reg_out_5_0;
wire [31:0] reg_out_6_0;
wire [31:0] reg_out_7_0;
genvar pin_cnt;
generate for (pin_cnt = 0; pin_cnt < 8; pin_cnt = pin_cnt + 1)
begin: test_reg
assign led[pin_cnt] = reg_out_1_0[pin_cnt] ? 1’bz : reg_out_0_0[pin_cnt];
assign reg_in_0_0[pin_cnt] = reg_out_1_0[pin_cnt] ? led[pin_cnt] : reg_out_0_0[pin_cnt];
end
endgenerate
assign reg_in_0_0[31:8] = reg_out_0_0[31:8];
assign reg_in_1_0 = reg_out_1_0;
assign reg_in_2_0 = reg_out_2_0;
assign reg_in_3_0 = reg_out_3_0;
assign reg_in_4_0 = reg_out_4_0;
assign reg_in_5_0 = reg_out_5_0;
assign reg_in_6_0 = reg_out_6_0;
assign reg_in_7_0 = reg_out_7_0;
led[7:0] 设计为inout的,
寄存器reg_out_1_0 为方向寄存器, 1 input; 0 output
Reg_out_0_0 为cpu 输出值到led gpio 上。
Reg_in_0_0 为 cpu 读取led 上的值。
其他的寄存器 直接做输入,输出连接(没有实际意义,可以作为测试).
到此: cpu 就可以通过读写寄存器来控制led 了。