1. 前言
在之前的博客中有介绍到AXI接口和AXI-stream接口,AXI-lite接口也经常使用。最近恰好在做一个小的项目,需要对采集到的ADC数据进行缓存。由于采集到的数据是经过数字下变频DDC的,因此其速率不会很快,根据速率变换的不同,可以在1M到50K之间。因此也不需要使用外部的存储器,片上的RAM就足够了。
由于使用到的ZYNQ,希望使用ZYNQ的网口来进行数据的传输,因此就免不了使用AXI接口。这个就相较于使用存粹的FPGA逻辑的开发要稍微复杂一点。但是在进行数据传输的时候,使用lwip以太网就可以很简单的进行传输了,有失必有得,有得必有失。
2. AXI-Lite 接口时序
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的读时序也和写时序相同,相较于常规的时序有一点不同。
2.1 Block RAM 设置
弄清了上面的AXI-Lite时序滞后,就需要对BRAM接口进行简单的设置的,使用纯逻辑进行开发的时候,RAM这个IP那是使用了相当多的次数,Block Design中使用的时候,还是有一点区别。
这里的模式选择Stand Alone模式,因为是自己来实现这个AXI接口,将DDC后的数据写入到RAM中。使用真双口RAM,这个真双口RAM在纯逻辑开发的时候,用的不多,在Block Design的设计中用得还是挺多得。
接下来就是位宽和深度得设置,这里设置位宽位32,深度为2048.因此采样得IQ对,IQ数据都是32位,在向上位机传递数据的时候,都是以IQ对来传递的,上位机接收数据的时候需要最小的IQ对位512。
2.2 封装AXI-lite接口IP
封装两个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如下:
地址映射如下:
3 SDK下验证
搭建完毕后,就可以简单验证以下,写一个循环读写的简单的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;
}