最近因开发需要在学习AXI4协议,这里对其中的Narrow transfer机制做一点总结,如有疏漏敬请批评指正!
1-基本规定
Narrow transfer适用于传输位宽小于总线位宽的情况。这种情况下只有部分总线被占用,因此需要通过wstrb信号来控制我们需要写入的字节。
AXI4规定如果burst type为INCR或WRAP,则必须使用不同的通道来传输字节。以总线宽度32bit、transfer宽度16bit为例,如果第一次传输使用了总线的0-15位,那么下一次传输必须使用16-31位;
注意地址都是按照4KB对齐的!
2-仿真测试分析
我使用Vivado的Block Memory Generator v8.4生成AXI4接口的SDP RAM,然后编写测试文件进行仿真时序分析。
详细参数:SDP的AB端口均为32*1024,协议采用AXI4,初始化RAM的内容为:
0x0000-AA0000AA,0x0001-BB0000BB,
0x0010-CC0000CC,0x0011-DD0000DD,
0x0100-EE0000EE,0x0101-FF0000FF;
上述地址为字地址,字节地址需要换算。
编写tb文件,awsize设为1,即1次transfer只能传2个byte,然后进行写操作,写完之后读数据判断写入是否正确;
module axi4_full_test();
wire rsta_busy;
wire rstb_busy;
reg s_aclk;
reg s_aresetn;
reg [3:0] s_axi_awid;
reg [31:0] s_axi_awaddr;
reg [7:0] s_axi_awlen;
reg [2:0] s_axi_awsize;
reg [1:0] s_axi_awburst;
reg s_axi_awvalid;
wire s_axi_awready;
reg [31:0] s_axi_wdata;
reg [3:0] s_axi_wstrb;
reg s_axi_wlast;
reg s_axi_wvalid;
wire s_axi_wready;
wire [3:0] s_axi_bid;
wire [1:0] s_axi_bresp;
wire s_axi_bvalid;
reg s_axi_bready;
reg [3:0] s_axi_arid;
reg [31:0] s_axi_araddr;
reg [7:0] s_axi_arlen;
reg [2:0] s_axi_arsize;
reg [1:0] s_axi_arburst;
reg s_axi_arvalid;
wire s_axi_arready;
wire [3:0] s_axi_rid;
wire [31:0] s_axi_rdata;
wire [1:0] s_axi_rresp;
wire s_axi_rlast;
wire s_axi_rvalid;
reg s_axi_rready;
blk_mem_gen_0 my_interface_full(
.rsta_busy(rsta_busy), // output wire rsta_busy
.rstb_busy(rstb_busy), // output wire rstb_busy
.s_aclk(s_aclk), // input wire s_aclk
.s_aresetn(s_aresetn), // input wire s_aresetn
.s_axi_awid(s_axi_awid), // input wire [3 : 0] s_axi_awid
.s_axi_awaddr(s_axi_awaddr), // input wire [31 : 0] s_axi_awaddr
.s_axi_awlen(s_axi_awlen), // input wire [7 : 0] s_axi_awlen
.s_axi_awsize(s_axi_awsize), // input wire [2 : 0] s_axi_awsize
.s_axi_awburst(s_axi_awburst), // input wire [1 : 0] s_axi_awburst
.s_axi_awvalid(s_axi_awvalid), // input wire s_axi_awvalid
.s_axi_awready(s_axi_awready), // output wire s_axi_awready
.s_axi_wdata(s_axi_wdata), // input wire [31 : 0] s_axi_wdata
.s_axi_wstrb(s_axi_wstrb), // input wire [3 : 0] s_axi_wstrb
.s_axi_wlast(s_axi_wlast), // input wire s_axi_wlast
.s_axi_wvalid(s_axi_wvalid), // input wire s_axi_wvalid
.s_axi_wready(s_axi_wready), // output wire s_axi_wready
.s_axi_bid(s_axi_bid), // output wire [3 : 0] s_axi_bid
.s_axi_bresp(s_axi_bresp), // output wire [1 : 0] s_axi_bresp
.s_axi_bvalid(s_axi_bvalid), // output wire s_axi_bvalid
.s_axi_bready(s_axi_bready), // input wire s_axi_bready
.s_axi_arid(s_axi_arid), // input wire [3 : 0] s_axi_arid
.s_axi_araddr(s_axi_araddr), // input wire [31 : 0] s_axi_araddr
.s_axi_arlen(s_axi_arlen), // input wire [7 : 0] s_axi_arlen
.s_axi_arsize(s_axi_arsize), // input wire [2 : 0] s_axi_arsize
.s_axi_arburst(s_axi_arburst), // input wire [1 : 0] s_axi_arburst
.s_axi_arvalid(s_axi_arvalid), // input wire s_axi_arvalid
.s_axi_arready(s_axi_arready), // output wire s_axi_arready
.s_axi_rid(s_axi_rid), // output wire [3 : 0] s_axi_rid
.s_axi_rdata(s_axi_rdata), // output wire [31 : 0] s_axi_rdata
.s_axi_rresp(s_axi_rresp), // output wire [1 : 0] s_axi_rresp
.s_axi_rlast(s_axi_rlast), // output wire s_axi_rlast
.s_axi_rvalid(s_axi_rvalid), // output wire s_axi_rvalid
.s_axi_rready(s_axi_rready) // input wire s_axi_rready
);
//test
initial begin
//initializing
s_aclk = 1'b0;
s_aresetn = 1'b0;
s_axi_awid = 4'b1010;//compare with the output s_axi_bid to see the relations
s_axi_awaddr =32'hFFFFFF00;
s_axi_awlen = 8'b0;
s_axi_awsize = 3'b0;
s_axi_awburst = 2'b01;
s_axi_awvalid = 1'b0;
s_axi_wstrb = 4'b1111;
s_axi_wvalid = 1'b0;
s_axi_bready = 1'b1;
s_axi_arid = 4'b0101;//compare with the output s_axi_rid to see the relations
s_axi_araddr = 32'hFFFFFF00;
s_axi_arlen = 8'b0;
s_axi_arsize = 3'b0;
s_axi_arburst = 2'b01;
s_axi_arvalid = 1'b0;
s_axi_rready = 1'b1;
s_axi_wlast = 1'b0;
end
always #5 s_aclk = ~s_aclk;//generate global clk
initial begin
#22 s_aresetn =1'b1;//reset before everything
//burst write,
#10;
s_axi_wstrb = 4'b0011;
s_axi_awaddr = 32'd4;
s_axi_awlen = 8'd4;
s_axi_awsize = 3'd1;
s_axi_awvalid = 1'b1;
s_axi_wdata = 32'hAAAA0001;
wait(s_axi_awready == 1'b1 && s_aclk == 1'b1);
#1 s_axi_awvalid = 1'b0;
s_axi_wvalid = 1'b1;
repeat(3) begin
#10 s_axi_wdata = s_axi_wdata + 32'h11110001;
s_axi_wstrb = ~s_axi_wstrb;
end
#10 s_axi_wlast = 1'b1;
s_axi_wdata = s_axi_wdata + 32'd1;
s_axi_wstrb = ~s_axi_wstrb;
#10 s_axi_wlast = 1'b0;
//read to check if we've written the correct data
#5;
s_axi_araddr = 32'd0;
s_axi_arlen = 8'd4;
s_axi_arsize = 3'd2;
s_axi_arvalid = 1'b1;
wait(s_axi_arready == 1'b1 && s_aclk == 1'b1);
#1 s_axi_arvalid = 1'b0;
s_axi_arid = 4'bx;
#5 s_axi_rready = 1'b1;
s_axi_araddr = 32'bx;
#5 s_axi_araddr = 32'd8;
s_axi_arvalid = 1'b1;
s_axi_arid = 4'd15;
#45 s_axi_arvalid = 1'b0;
end
endmodule
测试得到的的结果如下图:这里主机做的是把32位的数据和控制信号wstrb放到总线上,进而实现只传输2个字节的功能。
4条蓝线为写数据,而黄线开始是连续的读数据。根据wstrb信号的值,可以看到, 第一次写入的是0xaaaa0001的低2字节,即0001,第二次写入的是0xbbbb0001的高2字节,即bbbb,以此类推。因而原先字节地址0x0004位置储存的0xbb0000bb,就变成了0xbbbb0001;注意,传输中总线上对应字节写入也是在对应字节的位置,我画了一个并不准确的传输机制图:
那如果只想要写入1个16位数据怎么办呢?很简单,让wstrb信号保持2个时钟周期不变就可以了,正如我们最后读出的数据0xdd000005一样,就是只把低16位写入了。