基于ZCU104的PS和PL数据交互例程(三):vivado中创建IP
基于ZCU104的PS和PL数据交互例程(一):简介
基于ZCU104的PS和PL数据交互例程(二):vivado中封装现有工程成IP
基于ZCU104的PS和PL数据交互例程(三):vivado中创建IP
基于ZCU104的PS和PL数据交互例程(四):vivado中制作Block Design
基于ZCU104的PS和PL数据交互例程(五):Petalinux流程
基于ZCU104的PS和PL数据交互例程(六):上板流程
基于ZCU104的PS和PL数据交互例程(七):驱动编写与测试
以创建带有AXI-LITE接口的IP为例子
按照下面步骤创建
这里注意,这里选择的Number of Registers,会在后面的代码里面对应slv_reg0, slv_reg1, …, slv_reg3
打开IP目录,右键刚才的IP,选择Eidt in IP Packager
![image-20231019172920235](https://i-blog.csdnimg.cn/blog_migrate/c054404f19df6ca19f5032ec9df8ebad.png)
controller_v1_0
双击打开controller_v1_0.v文件
添加端口
连接端口
controller_v1_0_S00_AXI_LITE
双击打开controller_v1_0_S00_AXI_LITE.v文件
添加端口
添加必要逻辑
-
声明parameter
-
在前面声明变量
-
修改slv_reg逻辑,这里的slv_reg就是当时选择的Number of Registers,在代码里面找到原来的代码,添加下面的逻辑
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 //Add User logic else if (slv_reg_vld_axi[0] | slv_reg_vld_axi[1]) begin case (slv_reg_vld_axi) SLV0:begin slv_reg0 <= slv_reg0_data; slv_reg1 <= slv_reg1; slv_reg2 <= slv_reg2; slv_reg3 <= slv_reg3; end SLV1:begin slv_reg0 <= slv_reg0; slv_reg1 <= slv_reg1_data; slv_reg2 <= slv_reg2; slv_reg3 <= slv_reg3; end default: begin slv_reg0 <= slv_reg0; slv_reg1 <= slv_reg1; slv_reg2 <= slv_reg2; slv_reg3 <= slv_reg3; end endcase end //Add User logic 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
例化模块
先通过添加文件,添加两个新模块:reset_asyn_syn.v和user_ctrl.v
再完成例化
异步复位同步释放
module reset_asyn_syn(
input clk ,
input rst_asyn,
output rstb_syn
);
// maybe key jitter but not matter
reg [1:0] reset_reg;
always @(posedge clk or posedge rst_asyn) begin
if(rst_asyn) begin
reset_reg <= 2'b0;
end
else begin
reset_reg[0] <= 1'b1;
reset_reg[1] <= reset_reg[0];
end
end
assign rstb_syn = reset_reg[1];
endmodule
控制逻辑
- 时钟:axi有个时钟,PL端有个时钟,可以是同一个,也可以是不同的。这里添加有跨时钟域处理。选择slv_reg0的bit0作为启动信号的判断,如果检测到slv_reg0_bit0的上升沿,则表示PS端要求启动PL端,所以拉高start_DUT信号一个周期。
- 状态机逻辑
状态机:
注意事项:
1.读写slv_reg
对于slv_reg1的写入控制,内部模块输出一个数据slv_reg1_data,和对应的有效信号slv_reg1_vld。
然后在slv_reg1接受AXI_lite里面赋值的地方多加一组if-else情况,使得当AXI_lite写入数据时,赋值给slv_reg1;或者slv_reg1_vld拉高时,把slv_reg1_data赋值给slv_reg1;或者保持不变。
slv_reg0同理
2.简单的跨时钟域处理
这里注意写slv_reg的时候,属于跨时钟域操作,对于数据信号可以设置为静态数据,对于vld信号,可以通过打两拍同步到axi时钟域。这里axi的时钟域一般是100M,默认是快时钟域。
代码
一般认为ctrl模块跟PL属于同时钟域,跟AXI属于跨时钟域。
module user_ctrl#(
// Width of S_AXI data bus
parameter integer C_S_AXI_DATA_WIDTH = 32,
// Width of FSM
parameter FSM_WIDTH = 4,
parameter IDLE = 4'b1,
parameter ENABLE = 4'b10,
parameter MYWAIT = 4'b100,
parameter FINISH = 4'b1000
)(
input pl_clk ,
input axi_clk ,
input pl_rstb ,
input axi_rstb ,
input DUT_finish ,
input slv_reg0_bit0 ,
output [C_S_AXI_DATA_WIDTH-1:0] slv_reg0_data ,
output slv_reg0_vld_axi ,
output [C_S_AXI_DATA_WIDTH-1:0] slv_reg1_data ,
output slv_reg1_vld_axi ,
output start_DUT
);
reg [FSM_WIDTH-1:0] FSM_state ;
wire slv_reg0_vld,slv_reg1_vld;
reg [1:0] slv_reg0_vld_reg , slv_reg1_vld_reg;
always @(posedge pl_clk or negedge pl_rstb) begin
if(!pl_rstb)
FSM_state <= IDLE;
else begin
case (FSM_state)
IDLE: begin
if (slv_reg0_bit0) begin
FSM_state <= ENABLE;
end
end
ENABLE:begin
FSM_state <= MYWAIT;
end
MYWAIT:begin
if (DUT_finish) begin
FSM_state <= FINISH;
end
end
FINISH:begin
FSM_state <= IDLE;
end
default: FSM_state <= IDLE;
endcase
end
end
//slv_reg0 output signals
assign slv_reg0_data = {C_S_AXI_DATA_WIDTH{1'b0}};
assign slv_reg0_vld = (FSM_state == ENABLE) ? 1'b1 : 1'b0;
//slv_reg1 output signals
assign slv_reg1_data = {31'd0,1'b1};
assign slv_reg1_vld = (FSM_state == FINISH) ? 1'b1 : 1'b0;
//translate to axi clk
assign slv_reg0_vld_axi = slv_reg0_vld_reg[1];
assign slv_reg1_vld_axi = slv_reg1_vld_reg[1];
always @(posedge axi_clk or negedge axi_rstb) begin
if(!axi_rstb) begin
slv_reg0_vld_reg <= 2'b0;
slv_reg1_vld_reg <= 2'b0;
end
else begin
slv_reg0_vld_reg <= {slv_reg0_vld_reg[0],slv_reg0_vld};
slv_reg1_vld_reg <= {slv_reg1_vld_reg[0],slv_reg1_vld};
end
end
//enable output
assign start_DUT= (FSM_state == ENABLE) ? 1'b1 : 1'b0;
endmodule
封装IP
具体的封装步骤,跟基于ZCU104的PS和PL数据交互例程(二):vivado中封装现有工程成IP类似,也可以参考正点原子的2_启明星ZYNQ之嵌入式SDK开发指南_V2.0.pdf------>第六章 自定义IP核-呼吸灯实验