博主参考和学习的博客
- AXI协议基础知识 。这篇博客比较详细地介绍了AXI总线,并且罗列了所有的通道和端口,写代码的时候可以方便地进行查表。
- AXI总线,AXI_BRAM读写仿真测试 。 这篇文章为代码的书写提供大致的思路,比如状态机和时序的控制问题,可以参考。
valid-ready双向握手机制
- 双向握手机制的实质是:
数据接收方R告诉数据发送方T“我准备好接收数据了”,并拉高ready;同样的,数据发送方T告诉数据接收方R“我准备好发送数据了”,并拉高valid。
数据发送方给出valid,数据接收方给出ready
-
重点:只有在valid和ready同时拉高时,表面成功握手,数据才得以传输。
【例】比如下图,当前clk上升沿检测到awvalid和awready都拉高,aw握手成功,此时32位的awaddr地址得以写入bram。
【例】比如下图,当前clk上升沿检测到wvalid和wready都拉高,w握手成功,此时32位的wdata数据得以写入bram。
-
读写BRAM需要用到四个的AXI通道。每一个通道下都有很多端口,包括常规的数据线和valid-ready线:
① 写地址通道AW(address write)
//写地址通道AW
reg [31:0] awaddr; //in(视角:bram)
reg awvalid; //in
wire awready; //out
② 写数据通道W(write)
//写数据通道W
reg [31:0] wdata; //in
reg wvalid; //in
wire wready; //out
reg [3:0] wstrb; //in
③ 写响应通道B
//写响应通道B
reg bready; //in:表示主机准备好接收bram的数据
wire bvalid; //out:表示bram可以给master发送数据
wire [1:0] bresp; //out
④ 读地址通道AR(address read)
//读地址通道AR
reg [31:0] araddr; //in
reg arvalid; //in
wire arready; //out
⑤ 读数据通道R(read)
//读数据通道R
wire [31:0] rdata; //out
wire rvalid; //out
reg rready; //in
wire [1:0] rresp; //out
博主画了一张图方便理解和查询:
这是BRAM的IP核设置界面,可以清晰地看到各个通道。
突发读和突发写时序
有关AXI进行BRAM读写的细节问题
【主从valid-ready互不干扰】
正常工作情况下valid不等待ready,ready也不等待valid。因为主机和从机的逻辑判断是独立的,都是根据自己的情况拉高valid或者ready,换句话说就是:“主机想发数据就把自己的valid拉高,从机能接收数据就把自己的ready拉高”。正好握上了数据就开始传。
【有一边出现繁忙,才会出现等待握手的情况】
如果现在主机可以发送数据,拉高了valid,但是从机处于繁忙状态不能接收数据,没有拉高ready。这个时候主机valid信号就要等待从机退出繁忙并将ready拉高。
代码详解
我们书写代码,是站在“主机master”的视角,去操控bram的。
我们的程序中关于valid-ready双向握手协议的部分,只需要操纵主机的三个valid信号(awvalid,wvalid,arvalid)和一个ready信号(rready),从机bram的ready不需要我们操心。如果主从握手成功,数据便会成功传输,即对于数据传输的过程只要握手成功便会进行,也不需要我们操心。
- 状态机——状态空间:
由于AXI读写时序较为复杂,为了更加好地操控时序和debug,博主使用状态机进行书写。
reg [3:0] STATE;
parameter wr_addr = 4'b0000;
parameter wr_addr_shakehand = 4'b0001;
parameter wr_data = 4'b0010;
parameter wr_data_shakehand = 4'b0011;
parameter write = 4'b0100;
parameter rd_addr = 4'b0101;
parameter rd_addr_shakehand = 4'b0110;
parameter rd_data = 4'b0111;
parameter rd_data_shakehand = 4'b1000;
parameter read = 4'b1001;
parameter init = 4'b1010;
parameter buffer = 4'b1011;
parameter stop = 4'b1111;
- 状态机——状态跳转:
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) STATE <= init;
else begin
case(STATE)
init: begin
if(init_cnt == 5) STATE <= wr_addr;
else STATE <= init;
end
wr_addr: STATE <= wr_addr_shakehand;
wr_addr_shakehand: begin
if(awvalid == 1'b1 && awready == 1'b1) STATE <= wr_data;
else STATE <= wr_addr_shakehand;
end
wr_data: STATE <= wr_data_shakehand;
wr_data_shakehand: begin
if(wvalid == 1'b1 && wready == 1'b1) STATE <= write;
else STATE <= wr_data_shakehand;
end
write: STATE <= buffer;
buffer: STATE <= rd_addr;
rd_addr: STATE <= rd_addr_shakehand;
rd_addr_shakehand: begin
if(arvalid == 1'b1 && arready == 1'b1) STATE <= rd_data;
else STATE <= rd_addr_shakehand;
end
rd_data: STATE <= rd_data_shakehand;
rd_data_shakehand: begin
if(rready == 1'b1 && rvalid == 1'b1) STATE <= read;
else STATE <= rd_data_shakehand;
end
read: STATE <= stop;
stop: STATE <= stop;
default: STATE <= init;
endcase
end
end
- 写地址通道AW:
① 给出要写入的地址ADDRESS,并将awvalid拉高,等待bram拉高awready进行握手。
(准确来说不是等待,因为bram只要满足条件就会拉高awready,主从逻辑独立。此处为方便表达。)
② awvalid-awready握手后,数据awaddr传输完成,此时主机要主动拉低awvalid。
//写地址通道AW
parameter ADDRESS = 114;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
awaddr <= 32'b0;
awvalid <= 1'b0;
end
else if(STATE == wr_addr) begin
awaddr <= ADDRESS;
awvalid <= 1'b1;
end
else begin
awaddr <= awaddr;
awvalid <= awvalid;
end
end
always@(posedge clk or negedge rst_n)
begin
if(STATE == wr_addr_shakehand) begin
if(awvalid == 1'b1 && awready == 1'b1) begin
awvalid <= 1'b0; //成功握手,valid拉低
awaddr <= 32'b0;
end
end
end
- 写数据通道W:
① 给出要写入的数据WR_DATA,同时拉高wvalid,等待bram拉高wready进行握手
② wvalid-wready握手后,数据wdata传输完成,此时主机要主动拉低wvalid。
③ 对于wstrb,在数据传输时赋为 4’b1111,表示wdata32位数据均有效。
(wstrb:写数据有效的字节线,用来表明哪8bits数据是有效的。)
//写数据通道W
parameter WR_DATA = 514;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
wdata <= 32'b0;
wvalid <= 1'b0;
wstrb <= 4'b0;
end
else if(STATE == wr_data) begin
wdata <= WR_DATA;
wvalid <= 1'b1;
wstrb <= 4'b1111;
end
else begin
wdata <= wdata;
wvalid <= wvalid;
wstrb <= wstrb;
end
end
always@(posedge clk or negedge rst_n)
begin
if(STATE == wr_data_shakehand) begin
if(wvalid == 1'b1 && wready == 1'b1) begin
wvalid <= 1'b0;
wstrb <= 4'b0000;
wdata <= 0;
end
else begin
wvalid <= wvalid;
wstrb <= wstrb;
end
end
end
- 写响应通道B:
在AXI-Lite中,我们只需要管bready这一个端口就行。我们只需要一直拉高bready就行。
(bready:主机接受响应就绪信号 。该信号表示主机是否能够接受响应信息。1 = 主机就绪,0 = 主机未就绪。)
对于bvalid和bresp不需要我们操心,因为那是bram给到主机的,不由我们控制。
//写响应通道B
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
bready <= 1'b1;
end
else if(STATE == write || STATE == read) begin
bready <= 1'b1;
end
end
- 读地址通道AR:
① 给出要读取的地址ADDRESS,并将arvalid拉高,等待bram拉高arready进行握手。
② arvalid-arready握手后,数据araddr传输完成,此时主机要主动拉低arvalid。
//读地址通道AR
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
araddr <= 32'b0;
arvalid <= 1'b0;
end
else if (STATE == rd_addr) begin
araddr <= ADDRESS;
arvalid <= 1'b1;
end
else begin
araddr <= araddr;
arvalid <= arvalid;
end
end
always@(posedge clk or negedge rst_n)
begin
if(STATE == rd_addr_shakehand) begin
if(arvalid == 1'b1 && arready == 1'b1) begin
arvalid <= 1'b0;
end
else arvalid <= arvalid;
end
end
- 读数据通道R:
① 将rready拉高,等待bram拉高rvalid进行握手。
② rready-rvalid握手后,数据rdata传输完成,此时主机要主动拉低rready。
(注意:此时的数据流向是bram→主机,因此主机端为ready信号,bram端为valid信号。)
//读数据通道R
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
rready <= 1'b0;
end
else if(STATE == rd_data) begin
rready <= 1'b1;
end
end
always@(posedge clk or negedge rst_n)
begin
if(STATE == rd_data_shakehand) begin
if(rready == 1'b1 && rvalid == 1'b1) begin
rready <= 1'b0;
end
else rready <= rready;
end
end
- 实例化block memory generator IP核模块:
blk_mem_gen_0 mybram(
.s_aclk(clk),
.s_aresetn(rst_n),
.s_axi_awaddr(awaddr),
.s_axi_awvalid(awvalid),
.s_axi_awready(awready),
.s_axi_wdata(wdata),
.s_axi_wvalid(wvalid),
.s_axi_wready(wready),
.s_axi_wstrb(wstrb),
.s_axi_bready(bready),
.s_axi_bvalid(bvalid),
.s_axi_bresp(bresp),
.s_axi_araddr(araddr),
.s_axi_arvalid(arvalid),
.s_axi_arready(arready),
.s_axi_rready(rready),
.s_axi_rvalid(rvalid),
.s_axi_rdata(rdata)
);
完整代码(包含Testbench)
module axi4_light_bram(
input wire clk,
input wire rst_n
);
reg [3:0] STATE;
parameter wr_addr = 4'b0000;
parameter wr_addr_shakehand = 4'b0001;
parameter wr_data = 4'b0010;
parameter wr_data_shakehand = 4'b0011;
parameter write = 4'b0100;
parameter rd_addr = 4'b0101;
parameter rd_addr_shakehand = 4'b0110;
parameter rd_data = 4'b0111;
parameter rd_data_shakehand = 4'b1000;
parameter read = 4'b1001;
parameter init = 4'b1010;
parameter buffer = 4'b1011;
parameter stop = 4'b1111;
//写地址通道AW
reg [31:0] awaddr; //in(视角:bram)
reg awvalid; //in
wire awready; //out
//写数据通道W
reg [31:0] wdata; //in
reg wvalid; //in
wire wready; //out
reg [3:0] wstrb; //in
//写响应通道B
reg bready; //in:表示主机准备好接收bram的数据
wire bvalid; //out:表示bram可以给master发送数据
wire [1:0] bresp; //out
//读地址通道AR
reg [31:0] araddr; //in
reg arvalid; //in
wire arready; //out
//读数据通道R
wire [31:0] rdata; //out
wire rvalid; //out
reg rready; //in
wire [1:0] rresp; //out
//初始缓冲init
reg [3:0] init_cnt;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) init_cnt <= 0;
else init_cnt <= (init_cnt == 5) ? 0 : (init_cnt + 1);
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) STATE <= init;
else begin
case(STATE)
init: begin
if(init_cnt == 5) STATE <= wr_addr;
else STATE <= init;
end
wr_addr: STATE <= wr_addr_shakehand;
wr_addr_shakehand: begin
if(awvalid == 1'b1 && awready == 1'b1) STATE <= wr_data;
else STATE <= wr_addr_shakehand;
end
wr_data: STATE <= wr_data_shakehand;
wr_data_shakehand: begin
if(wvalid == 1'b1 && wready == 1'b1) STATE <= write;
else STATE <= wr_data_shakehand;
end
write: STATE <= buffer;
buffer: STATE <= rd_addr;
rd_addr: STATE <= rd_addr_shakehand;
rd_addr_shakehand: begin
if(arvalid == 1'b1 && arready == 1'b1) STATE <= rd_data;
else STATE <= rd_addr_shakehand;
end
rd_data: STATE <= rd_data_shakehand;
rd_data_shakehand: begin
if(rready == 1'b1 && rvalid == 1'b1) STATE <= read;
else STATE <= rd_data_shakehand;
end
read: STATE <= stop;
stop: STATE <= stop;
default: STATE <= init;
endcase
end
end
//写地址通道AW
parameter ADDRESS = 114;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
awaddr <= 32'b0;
awvalid <= 1'b0;
end
else if(STATE == wr_addr) begin
awaddr <= ADDRESS;
awvalid <= 1'b1;
end
else begin
awaddr <= awaddr;
awvalid <= awvalid;
end
end
always@(posedge clk or negedge rst_n)
begin
if(STATE == wr_addr_shakehand) begin
if(awvalid == 1'b1 && awready == 1'b1) begin
awvalid <= 1'b0; //成功握手,valid拉低
awaddr <= 32'b0;
end
end
end
//写数据通道W
parameter WR_DATA = 514;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
wdata <= 32'b0;
wvalid <= 1'b0;
wstrb <= 4'b0;
end
else if(STATE == wr_data) begin
wdata <= WR_DATA;
wvalid <= 1'b1;
wstrb <= 4'b1111;
end
else begin
wdata <= wdata;
wvalid <= wvalid;
wstrb <= wstrb;
end
end
always@(posedge clk or negedge rst_n)
begin
if(STATE == wr_data_shakehand) begin
if(wvalid == 1'b1 && wready == 1'b1) begin
wvalid <= 1'b0;
wstrb <= 4'b0000;
wdata <= 0;
end
else begin
wvalid <= wvalid;
wstrb <= wstrb;
end
end
end
//写响应通道B
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
bready <= 1'b1;
end
else if(STATE == write || STATE == read) begin
bready <= 1'b1;
end
end
//读地址通道AR
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
araddr <= 32'b0;
arvalid <= 1'b0;
end
else if (STATE == rd_addr) begin
araddr <= ADDRESS;
arvalid <= 1'b1;
end
else begin
araddr <= araddr;
arvalid <= arvalid;
end
end
always@(posedge clk or negedge rst_n)
begin
if(STATE == rd_addr_shakehand) begin
if(arvalid == 1'b1 && arready == 1'b1) begin
arvalid <= 1'b0;
end
else arvalid <= arvalid;
end
end
//读数据通道R
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
rready <= 1'b0;
end
else if(STATE == rd_data) begin
rready <= 1'b1;
end
end
always@(posedge clk or negedge rst_n)
begin
if(STATE == rd_data_shakehand) begin
if(rready == 1'b1 && rvalid == 1'b1) begin
rready <= 1'b0;
end
else rready <= rready;
end
end
blk_mem_gen_0 mybram(
.s_aclk(clk),
.s_aresetn(rst_n),
.s_axi_awaddr(awaddr),
.s_axi_awvalid(awvalid),
.s_axi_awready(awready),
.s_axi_wdata(wdata),
.s_axi_wvalid(wvalid),
.s_axi_wready(wready),
.s_axi_wstrb(wstrb),
.s_axi_bready(bready),
.s_axi_bvalid(bvalid),
.s_axi_bresp(bresp),
.s_axi_araddr(araddr),
.s_axi_arvalid(arvalid),
.s_axi_arready(arready),
.s_axi_rready(rready),
.s_axi_rvalid(rvalid),
.s_axi_rdata(rdata)
);
endmodule
Testbench如下:
`timescale 1ns / 1ps
module axi4_light_bram_tb();
reg clk;
reg rst_n;
initial begin
clk = 1'b0;
rst_n = 1'b0;
#150
rst_n = 1'b1;
end
always # 10 clk <= ~clk;
axi4_light_bram axi4_light_bram_uut(
.clk(clk),
.rst_n(rst_n)
);
endmodule
仿真结果分析
心得和后续工作
- AXI总线算是比较复杂的总线,虽然后续开发的过程中大多不需要自己去写读写时序,但是经过这么一个过程能加深我对AXI协议的理解。“握手”是协议的精髓所在,它有效地控制着整个时序,并且保证主从机互相了解对方的状态,方便数据传输的开始和终止。
- 本程序只是最简单的AXI-Lite,如果书写常规的AXI协议,将多出一些端口需要配置(比如id,len,size),后续博主会在这个代码的基础上写常规AXI协议。
- 本程序只进行了一次数据的读和写。但是使用AXI协议可以同时写很多数据,然后用last指示最后一个数据,此类云云。后续我会去写这样的代码的。