input wire s00_axi_aclk, input wire s00_axi_aresetn, input wire [C_S00_AXI_ADDR_WIDTH-1 : 0] s00_axi_awaddr, input wire [2 : 0] s00_axi_awprot, input wire s00_axi_awvalid, output wire s00_axi_awready, input wire [C_S00_AXI_DATA_WIDTH-1 : 0] s00_axi_wdata, input wire [(C_S00_AXI_DATA_WIDTH/8)-1 : 0] s00_axi_wstrb, input wire s00_axi_wvalid, output wire s00_axi_wready, output wire [1 : 0] s00_axi_bresp, output wire s00_axi_bvalid, input wire s00_axi_bready, input wire [C_S00_AXI_ADDR_WIDTH-1 : 0] s00_axi_araddr, input wire [2 : 0] s00_axi_arprot, input wire s00_axi_arvalid, output wire s00_axi_arready, output wire [C_S00_AXI_DATA_WIDTH-1 : 0] s00_axi_rdata, output wire [1 : 0] s00_axi_rresp, output wire s00_axi_rvalid, input wire s00_axi_rready
没错笔者曾在《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] ) 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
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 slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; end
其中,C_S_AXI_DATA_WIDTH的宏定义的值为32,也就是数据位宽,S_AXI_WSTRB就是写选通信号,S_AXI_WDATA就是写数据信号。
存在于for循环中的最关键的一句:
slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
当byte_index = 0的时候这句话就等价于:
slv_reg0[7:0] <= S_AXI_WDATA[7:0];
当byte_index = 1的时候这句话就等价于:
slv_reg0[15:8] <= S_AXI_WDATA[15:8];
当byte_index = 2的时候这句话就等价于:
slv_reg0[23:16] <= S_AXI_WDATA[23:16];
当byte_index = 3的时候这句话就等价于:
slv_reg0[31:24] <= S_AXI_WDATA[31:24];
也就是说,只有当写选通信号为1时,它所对应S_AXI_WDATA的字节才会被读取。
读懂了这段话之后,我们就知道了,如果我们想得到PS写到总线上的数据,我们只需要读取slv_reg0的值即可。
那如果,我们想写数据到总线让PS读取该数据,我们该怎么做呢?我们继续来看有关RADTA读数据代码:
// Output register or memory read data always @( posedge S_AXI_ACLK ) begin if ( S_AXI_ARESETN == 1'b0 ) begin axi_rdata <= 0; end else begin // When there is a valid read address (S_AXI_ARVALID) with // acceptance of read address by the slave (axi_arready), // output the read dada if (slv_reg_rden) begin axi_rdata <= reg_data_out; // register read data end end end
观察可知,当PS读取数据时,程序会把reg_data_out复制给axi_rdata(RADTA读数据)。我们继续追踪reg_data_out:
always @(*) begin // Address decoding for reading registers case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] ) 2'h0 : reg_data_out <= slv_reg0; 2'h1 : reg_data_out <= slv_reg1; 2'h2 : reg_data_out <= slv_reg2; 2'h3 : reg_data_out <= slv_reg3; default : reg_data_out <= 0; endcase end
和前面分析的一样此时通过判断axi_awaddr[3:2]的值来判断将那个值给reg_data_out上,同样当PS调用读取函数时,这里axi_awaddr[3:2]默认是0,所以我们只需要把slv_reg0替换成我们自己数据,就可以让PS通过总线读到我们提供的数据。
这里可能有的读者会问了,slv_reg0不是总线写过来的数据吗?因为笔者说过这个程序是Vivado为我们提供的例子,它这么做无非是想验证我写出去的值和我读进入的值相等。但是他怎么写确实会对初看代码的人造成困扰。
最后笔者提出一个问题,为什么写通道要比读通道多了一列应答通道,这是为什么呢?
首先,你要知道这个应答信号是干什么用的?
写应答,主要是回复主机你这个写过程是没有问题的,那读为什么不需要这个过程呢?
这时因为主机在读取数据时,从机可以直接通过读数据通道给主机反馈信息,因此就没有必要再来开辟一个单独的应答通道了。
小结:
如果我们想读AXI4_Lite总线上的数据时,只需关注slv_reg的数据,我们可自行添加一段代码,如:
reg [11:0]rlcd_rgb; always @( posedge S_AXI_ACLK ) begin if ( S_AXI_ARESETN == 1'b0 ) begin rlcd_rgb <= 12'd0; end else begin rlcd_rgb <= slv_reg0[11:0]; end end assign lcd_rgb = rlcd_rgb;
如果我们想对AXI4_Lite信号写数据时,我们只需修改对reg_data_out的赋值,如:
//写总线测试修改!!!!!!!!! wire[31:0]wlcd_xy;// = {10'd0,lcd_xy}; assign wlcd_xy = {10'd0,lcd_xy}; assign slv_reg_rden = axi_arready & S_AXI_ARVALID & ~axi_rvalid; always @(*) begin // Address decoding for reading registers case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] ) 2'h0 : reg_data_out <= wlcd_xy;//slv_reg0; 2'h1 : reg_data_out <= slv_reg1; 2'h2 : reg_data_out <= slv_reg2; 2'h3 : reg_data_out <= slv_reg3; default : reg_data_out <= 0; endcase end
最后强调下如果我们自定义的IP的地址被映射为0x43C00000,那么我们Xil_Out32(0x43C00000,Value)写的就是slv_reg0的值。如果地址偏移4位,如Xil_Out32(0x43C00000 + 4,Value) 写的就是slv_reg1的值,依次类推。
目前这里只有4个寄存器,那是因为之前选择的是4个,其实我们可以定义的更多:
#define XPAR_ MYIPFREQUENCY_ 0_ S00_ AXI_ BASEADDR 0x43C00000 #define XPAR_ MYIPFREQUENCY_ 0_ S00_ AXI_ HIGHADDR 0x43C0FFFF
理论上只要基地址 + 偏移量不要超过HIGHADDR即可。
Step9:接下来依然是,右键单击Block文件,文件选择Generate the Output Products。
Step10:继续右键单击Block文件,选择Create a HDL wrapper,根据Block文件内容产生一个HDL 的顶层文件,并选择让vivado自动完成。
Setp11:单击Run Synthesis,如果有 Save 对话框弹出选择保存。
Setp12:综合结束后选择Synthesized Design option单击 OK。
Step13:在如下对话框中找到Unassigned debug nets(如果对话框没有出现选择 菜单->Window > Debug)
Step14:右击 Unassigned Debug Nets 选择Set up Debug… 之后单击 Next
Step15:删除红色错误的信号然后单击Next 到结束