在之前的博客中有介绍到AXI接口和AXI-stream接口,AXI-lite接口也经常使用。最近恰好在做一个小的项目,需要对采集到的ADC数据进行缓存。由于采集到的数据是经过数字下变频DDC的,因此其速率不会很快,根据速率变换的不同,可以在1M到50K之间。因此也不需要使用外部的存储器,片上的RAM就足够了。 由于使用到的ZYNQ,希望使用ZYNQ的网口来进行数据的传输,因此就免不了使用AXI接口。这个就相较于使用存粹的FPGA逻辑的开发要稍微复杂一点。但是在进行数据传输的时候,使用lwip以太网就可以很简单的进行传输了,有失必有得,有得必有失。
AXI-Lite的接口时序和AXI_MM也就是AXI4的时序基本上是一致的,AXI4是可以突发的,但AXI-Lite每次只能传输一个数据。
最近调试这个AXI-lite接口,通过AXI4-lite接口与BRAM进行通信,由于BRAM采用的是传统的接口,没有使用BRAM controller这个IP,因此需要自己手动来写IP完成和BRAM的数据交互。但是在使用AXI-Lite接口的时候发现实际上的AXI4-Lite接口时序,在Slave IP中好像与常规的不一样。下面是通过ILA抓取到的AXI4-Lite接口的数据。 需要说明的,这个是Slave器件的时序图: 把写时序这个过程绘制下来如下图所示:红色的信号是该IP输出的信号。可以看到,这个AXI-Lite接口和常规的AXI时序有点不太。一般情况下的AXI时序,都是首先给出地址,地址响应后才进行数据的传输。 AXI4-Lite的读时序也和写时序相同,相较于常规的时序有一点不同。
弄清了上面的AXI-Lite时序滞后,就需要对BRAM接口进行简单的设置的,使用纯逻辑进行开发的时候,RAM这个IP那是使用了相当多的次数,Block Design中使用的时候,还是有一点区别。 这里的模式选择Stand Alone模式,因为是自己来实现这个AXI接口,将DDC后的数据写入到RAM中。使用真双口RAM,这个真双口RAM在纯逻辑开发的时候,用的不多,在Block Design的设计中用得还是挺多得。 接下来就是位宽和深度得设置,这里设置位宽位32,深度为2048.因此采样得IQ对,IQ数据都是32位,在向上位机传递数据的时候,都是以IQ对来传递的,上位机接收数据的时候需要最小的IQ对位512。
封装两个IP,一个用来对BRAM进行写, 一个用来读。
// 该模块只向block ram进行数据的写入 module axi_bram_write# ( parameter integer AXI_DATA_WIDTH = 32, parameter integer AXI_ADDR_WIDTH = 16, parameter integer BRAM_DATA_WIDTH = 32, parameter integer BRAM_ADDR_WIDTH = 10 )( // System signals input wire aclk, input wire aresetn, // Slave side input wire [AXI_ADDR_WIDTH-1:0] s_axi_awaddr, // AXI4-Lite slave: Write address input wire s_axi_awvalid, // AXI4-Lite slave: Write address valid output wire s_axi_awready, // AXI4-Lite slave: Write address ready input wire [AXI_DATA_WIDTH-1:0] s_axi_wdata, // AXI4-Lite slave: Write data input wire [AXI_DATA_WIDTH/8-1:0] s_axi_wstrb, // AXI4-Lite slave: Write strobe input wire s_axi_wvalid, // AXI4-Lite slave: Write data valid output wire s_axi_wready, // AXI4-Lite slave: Write data ready output wire [1:0] s_axi_bresp, // AXI4-Lite slave: Write response output wire s_axi_bvalid, // AXI4-Lite slave: Write response valid input wire s_axi_bready, // AXI4-Lite slave: Write response ready input wire [AXI_ADDR_WIDTH-1:0] s_axi_araddr, // AXI4-Lite slave: Read address input wire s_axi_arvalid, // AXI4-Lite slave: Read address valid output wire s_axi_arready, // AXI4-Lite slave: Read address ready output wire [AXI_DATA_WIDTH-1:0] s_axi_rdata, // AXI4-Lite slave: Read data output wire [1:0] s_axi_rresp, // AXI4-Lite slave: Read data response output wire s_axi_rvalid, // AXI4-Lite slave: Read data valid input wire s_axi_rready, // AXI4-Lite slave: Read data ready // BRAM port output wire bram_porta_clk, output wire bram_porta_rst, output wire [BRAM_ADDR_WIDTH-1:0] bram_porta_addr, output wire [BRAM_DATA_WIDTH-1:0] bram_porta_wrdata, output wire [BRAM_DATA_WIDTH/8-1:0] bram_porta_we ); //==================================================== //axi_lite port //==================================================== reg awready; reg wready ; reg bvalid ; assign s_axi_bresp = 2'd0; assign s_axi_wready = wready; assign s_axi_awready = awready; assign s_axi_bvalid = bvalid; always @(posedge aclk) begin if (aresetn == 1'b0) begin awready <= 1'b0; end else if (s_axi_awvalid == 1'b1 && s_axi_wvalid == 1'b1) begin awready <= 1'b1; end else begin awready <= 1'b0; end end always @(posedge aclk) begin if (aresetn == 1'b0) begin wready <= 1'b1; end else if (s_axi_wvalid == 1'b1) begin wready <= 1'b0; end else begin wready <= 1'b1; end end always @(posedge aclk) begin if (aresetn == 1'b0) begin bvalid <= 1'b0; end else if (s_axi_awvalid == 1'b1 && s_axi_wvalid == 1'b0) begin bvalid <= 1'b1; end else begin bvalid <= 1'b0; end end //==================================================== //bram port //==================================================== function integer clogb2 (input integer value); for(clogb2 = 0; value > 0; clogb2 = clogb2 + 1) value = value >> 1; endfunction localparam integer ADDR_LSB = clogb2(AXI_DATA_WIDTH/8 - 1); assign bram_porta_clk = aclk; assign bram_porta_rst = ~aresetn; // 字节地址到Block RAM的存储地址的转换 assign bram_porta_addr = s_axi_awaddr[ADDR_LSB+BRAM_ADDR_WIDTH-1:ADDR_LSB]; assign bram_porta_wrdata = s_axi_wdata; // 写入数据的字节有效信号 assign bram_porta_we = s_axi_awvalid & s_axi_wvalid ? s_axi_wstrb : {(BRAM_DATA_WIDTH/8){1'b0}}; endmodule module axi_bram_reader # ( parameter integer AXI_DATA_WIDTH = 32, parameter integer AXI_ADDR_WIDTH = 16, parameter integer BRAM_DATA_WIDTH = 32, parameter integer BRAM_ADDR_WIDTH = 10 ) ( // System signals input wire aclk, input wire aresetn, // Slave side input wire [AXI_ADDR_WIDTH-1:0] s_axi_awaddr, // AXI4-Lite slave: Write address input wire s_axi_awvalid, // AXI4-Lite slave: Write address valid output wire s_axi_awready, // AXI4-Lite slave: Write address ready input wire [AXI_DATA_WIDTH-1:0] s_axi_wdata, // AXI4-Lite slave: Write data input wire s_axi_wvalid, // AXI4-Lite slave: Write data valid output wire s_axi_wready, // AXI4-Lite slave: Write data ready output wire [1:0] s_axi_bresp, // AXI4-Lite slave: Write response output wire s_axi_bvalid, // AXI4-Lite slave: Write response valid input wire s_axi_bready, // AXI4-Lite slave: Write response ready input wire [AXI_ADDR_WIDTH-1:0] s_axi_araddr, // AXI4-Lite slave: Read address input wire s_axi_arvalid, // AXI4-Lite slave: Read address valid output wire s_axi_arready, // AXI4-Lite slave: Read address ready output wire [AXI_DATA_WIDTH-1:0] s_axi_rdata, // AXI4-Lite slave: Read data output wire s_axi_rlast, output wire [1:0] s_axi_rresp, // AXI4-Lite slave: Read data response output wire s_axi_rvalid, // AXI4-Lite slave: Read data valid input wire s_axi_rready, // AXI4-Lite slave: Read data ready // BRAM port output wire bram_porta_clk, output wire bram_porta_rst, output wire [BRAM_ADDR_WIDTH-1:0] bram_porta_addr, input wire [BRAM_DATA_WIDTH-1:0] bram_porta_rddata ); reg arready; reg rvalid ; reg rlast ; assign s_axi_arready = arready; assign s_axi_rdata = bram_porta_rddata; assign s_axi_rresp = 2'd0; assign s_axi_rvalid = rvalid; assign s_axi_rlast = rlast; always @(posedge aclk) begin if (aresetn == 1'b0) begin arready <= 1'b0; end else if (s_axi_arvalid == 1'b1 && s_axi_arready == 1'b0) begin arready <= 1'b1; end else begin arready <= 1'b0; end end always @(posedge aclk) begin if (aresetn == 1'b0) begin rvalid <= 1'b0; rlast <= 1'b0; end else if (arready == 1'b1) begin rvalid <= 1'b1; rlast <= 1'b1; end else begin rvalid <= 1'b0; rlast <= 1'b0; end end //==================================================== // bram port //==================================================== function integer clogb2 (input integer value); for(clogb2 = 0; value > 0; clogb2 = clogb2 + 1) value = value >> 1; endfunction localparam integer ADDR_LSB = clogb2(AXI_DATA_WIDTH/8 - 1); assign bram_porta_clk = aclk; assign bram_porta_rst = ~aresetn; assign bram_porta_addr = s_axi_araddr[ADDR_LSB+BRAM_ADDR_WIDTH-1:ADDR_LSB]; endmodule最终搭建好的Block Design如下: 地址映射如下:
搭建完毕后,就可以简单验证以下,写一个循环读写的简单的demo. 只需要正确的掌握映射的地址就可以
#include <stdio.h> #include "platform.h" #include "xil_printf.h" #define READ_ADDR 0x43C00000 #define WRITE_ADDR 0x43C02000 char * wrBuf = (char *)WRITE_ADDR; char * rdBuf = (char *)READ_ADDR; int main() { init_platform(); for (u8 i = 0; i < 100; i++) { *(wrBuf + i) = i; } for(int i = 0; i < 100; i++) { xil_printf("rdBuf[%d] = %d \n", i, rdBuf[i]); } print("Hello World\n\r"); cleanup_platform(); return 0; }