AXI4-Lite总线读写BRAM
创始人
2024-03-25 04:01:06
0

博主参考和学习的博客

  1. AXI协议基础知识 。这篇博客比较详细地介绍了AXI总线,并且罗列了所有的通道和端口,写代码的时候可以方便地进行查表。
  2. AXI总线,AXI_BRAM读写仿真测试 。 这篇文章为代码的书写提供大致的思路,比如状态机和时序的控制问题,可以参考。

valid-ready双向握手机制

  1. 双向握手机制的实质是:
    数据接收方R告诉数据发送方T“我准备好接收数据了”,并拉高ready;同样的,数据发送方T告诉数据接收方R“我准备好发送数据了”,并拉高valid。

数据发送方给出valid,数据接收方给出ready

  1. 重点:只有在valid和ready同时拉高时,表面成功握手,数据才得以传输。
    【例】比如下图,当前clk上升沿检测到awvalid和awready都拉高,aw握手成功,此时32位的awaddr地址得以写入bram。
    在这里插入图片描述
    【例】比如下图,当前clk上升沿检测到wvalid和wready都拉高,w握手成功,此时32位的wdata数据得以写入bram。
    在这里插入图片描述

  2. 读写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不需要我们操心。如果主从握手成功,数据便会成功传输,即对于数据传输的过程只要握手成功便会进行,也不需要我们操心。

  1. 状态机——状态空间:
    由于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;
  1. 状态机——状态跳转:
always@(posedge clk or negedge rst_n)
beginif(!rst_n)    STATE <= init;else    begincase(STATE)init:   beginif(init_cnt == 5)   STATE <= wr_addr;else            STATE <= init;                    endwr_addr:            STATE <= wr_addr_shakehand;wr_addr_shakehand:  beginif(awvalid == 1'b1 && awready == 1'b1)  STATE <= wr_data;else                                STATE <= wr_addr_shakehand;     endwr_data:            STATE <= wr_data_shakehand;wr_data_shakehand:  beginif(wvalid == 1'b1 && wready == 1'b1)    STATE <= write;else                                STATE <= wr_data_shakehand;endwrite:              STATE <= buffer;buffer:             STATE <= rd_addr;rd_addr:            STATE <= rd_addr_shakehand;rd_addr_shakehand:  beginif(arvalid == 1'b1 && arready == 1'b1)  STATE <= rd_data;else                                STATE <= rd_addr_shakehand;endrd_data:            STATE <= rd_data_shakehand;rd_data_shakehand:  beginif(rready == 1'b1 && rvalid == 1'b1)    STATE <= read;else                                STATE <= rd_data_shakehand;endread:               STATE <= stop;stop:               STATE <= stop;default:           STATE <= init;endcaseend    
end
  1. 写地址通道AW:

① 给出要写入的地址ADDRESS,并将awvalid拉高,等待bram拉高awready进行握手。
(准确来说不是等待,因为bram只要满足条件就会拉高awready,主从逻辑独立。此处为方便表达。)
② awvalid-awready握手后,数据awaddr传输完成,此时主机要主动拉低awvalid。

//写地址通道AW
parameter   ADDRESS = 114;
always@(posedge clk or negedge rst_n)
beginif(!rst_n) beginawaddr  <= 32'b0;awvalid <= 1'b0;end     else if(STATE == wr_addr)    beginawaddr <= ADDRESS;awvalid <= 1'b1;endelse    beginawaddr <= awaddr;awvalid <= awvalid;end
end
always@(posedge clk or negedge rst_n)
beginif(STATE == wr_addr_shakehand)  beginif(awvalid == 1'b1 && awready == 1'b1)  beginawvalid <= 1'b0;     //成功握手,valid拉低awaddr <= 32'b0;endend
end
  1. 写数据通道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)
beginif(!rst_n) beginwdata <= 32'b0;wvalid <= 1'b0;wstrb <= 4'b0;endelse if(STATE == wr_data)   beginwdata <= WR_DATA;wvalid <= 1'b1;wstrb <= 4'b1111;end    else    beginwdata <= wdata;wvalid <= wvalid;wstrb <= wstrb;end
end
always@(posedge clk or negedge rst_n)
beginif(STATE == wr_data_shakehand)   beginif(wvalid == 1'b1 && wready == 1'b1)    beginwvalid <= 1'b0;wstrb  <= 4'b0000;wdata  <= 0;endelse    beginwvalid <= wvalid;wstrb <= wstrb;endend
end
  1. 写响应通道B:

在AXI-Lite中,我们只需要管bready这一个端口就行。我们只需要一直拉高bready就行。
(bready:主机接受响应就绪信号 。该信号表示主机是否能够接受响应信息。1 = 主机就绪,0 = 主机未就绪。)
对于bvalid和bresp不需要我们操心,因为那是bram给到主机的,不由我们控制。

//写响应通道B
always@(posedge clk or negedge rst_n)
beginif(!rst_n) beginbready <= 1'b1;endelse if(STATE == write || STATE == read)  beginbready <= 1'b1;end
end
  1. 读地址通道AR:

① 给出要读取的地址ADDRESS,并将arvalid拉高,等待bram拉高arready进行握手。
② arvalid-arready握手后,数据araddr传输完成,此时主机要主动拉低arvalid。

//读地址通道AR
always@(posedge clk or negedge rst_n)
beginif(!rst_n) beginaraddr <= 32'b0;arvalid <= 1'b0;endelse if (STATE == rd_addr)  beginaraddr <= ADDRESS;arvalid <= 1'b1;endelse    beginaraddr <= araddr;arvalid <= arvalid;end
end
always@(posedge clk or negedge rst_n)
beginif(STATE == rd_addr_shakehand)  beginif(arvalid == 1'b1 && arready == 1'b1)  beginarvalid <= 1'b0;endelse    arvalid <= arvalid;end
end
  1. 读数据通道R:

① 将rready拉高,等待bram拉高rvalid进行握手。
② rready-rvalid握手后,数据rdata传输完成,此时主机要主动拉低rready。
(注意:此时的数据流向是bram→主机,因此主机端为ready信号,bram端为valid信号。)

//读数据通道R
always@(posedge clk or negedge rst_n)
beginif(!rst_n) beginrready <= 1'b0;endelse if(STATE == rd_data)    beginrready <= 1'b1;end
end
always@(posedge clk or negedge rst_n)
beginif(STATE == rd_data_shakehand)  beginif(rready == 1'b1 && rvalid == 1'b1)    beginrready <= 1'b0;endelse    rready <= rready;end
end
  1. 实例化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)
beginif(!rst_n) init_cnt <= 0;else    init_cnt <= (init_cnt == 5) ? 0 : (init_cnt + 1);
endalways@(posedge clk or negedge rst_n)
beginif(!rst_n)    STATE <= init;else    begincase(STATE)init:   beginif(init_cnt == 5)   STATE <= wr_addr;else            STATE <= init;                    endwr_addr:            STATE <= wr_addr_shakehand;wr_addr_shakehand:  beginif(awvalid == 1'b1 && awready == 1'b1)  STATE <= wr_data;else                                STATE <= wr_addr_shakehand;     endwr_data:            STATE <= wr_data_shakehand;wr_data_shakehand:  beginif(wvalid == 1'b1 && wready == 1'b1)    STATE <= write;else                                STATE <= wr_data_shakehand;endwrite:              STATE <= buffer;buffer:             STATE <= rd_addr;rd_addr:            STATE <= rd_addr_shakehand;rd_addr_shakehand:  beginif(arvalid == 1'b1 && arready == 1'b1)  STATE <= rd_data;else                                STATE <= rd_addr_shakehand;endrd_data:            STATE <= rd_data_shakehand;rd_data_shakehand:  beginif(rready == 1'b1 && rvalid == 1'b1)    STATE <= read;else                                STATE <= rd_data_shakehand;endread:               STATE <= stop;stop:               STATE <= stop;default:           STATE <= init;endcaseend    
end//写地址通道AW
parameter   ADDRESS = 114;
always@(posedge clk or negedge rst_n)
beginif(!rst_n) beginawaddr  <= 32'b0;awvalid <= 1'b0;end     else if(STATE == wr_addr)    beginawaddr <= ADDRESS;awvalid <= 1'b1;endelse    beginawaddr <= awaddr;awvalid <= awvalid;end
end
always@(posedge clk or negedge rst_n)
beginif(STATE == wr_addr_shakehand)  beginif(awvalid == 1'b1 && awready == 1'b1)  beginawvalid <= 1'b0;     //成功握手,valid拉低awaddr <= 32'b0;endend
end//写数据通道W
parameter   WR_DATA = 514;
always@(posedge clk or negedge rst_n)
beginif(!rst_n) beginwdata <= 32'b0;wvalid <= 1'b0;wstrb <= 4'b0;endelse if(STATE == wr_data)   beginwdata <= WR_DATA;wvalid <= 1'b1;wstrb <= 4'b1111;end    else    beginwdata <= wdata;wvalid <= wvalid;wstrb <= wstrb;end
end
always@(posedge clk or negedge rst_n)
beginif(STATE == wr_data_shakehand)   beginif(wvalid == 1'b1 && wready == 1'b1)    beginwvalid <= 1'b0;wstrb  <= 4'b0000;wdata  <= 0;endelse    beginwvalid <= wvalid;wstrb <= wstrb;endend
end//写响应通道B
always@(posedge clk or negedge rst_n)
beginif(!rst_n) beginbready <= 1'b1;endelse if(STATE == write || STATE == read)  beginbready <= 1'b1;end
end//读地址通道AR
always@(posedge clk or negedge rst_n)
beginif(!rst_n) beginaraddr <= 32'b0;arvalid <= 1'b0;endelse if (STATE == rd_addr)  beginaraddr <= ADDRESS;arvalid <= 1'b1;endelse    beginaraddr <= araddr;arvalid <= arvalid;end
end
always@(posedge clk or negedge rst_n)
beginif(STATE == rd_addr_shakehand)  beginif(arvalid == 1'b1 && arready == 1'b1)  beginarvalid <= 1'b0;endelse    arvalid <= arvalid;end
end//读数据通道R
always@(posedge clk or negedge rst_n)
beginif(!rst_n) beginrready <= 1'b0;endelse if(STATE == rd_data)    beginrready <= 1'b1;end
end
always@(posedge clk or negedge rst_n)
beginif(STATE == rd_data_shakehand)  beginif(rready == 1'b1 && rvalid == 1'b1)    beginrready <= 1'b0;endelse    rready <= rready;end
endblk_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 beginclk = 1'b0;rst_n = 1'b0;#150rst_n = 1'b1; 
endalways # 10  clk <= ~clk;axi4_light_bram axi4_light_bram_uut(.clk(clk),.rst_n(rst_n)
);endmodule

仿真结果分析

在这里插入图片描述


心得和后续工作

  1. AXI总线算是比较复杂的总线,虽然后续开发的过程中大多不需要自己去写读写时序,但是经过这么一个过程能加深我对AXI协议的理解。“握手”是协议的精髓所在,它有效地控制着整个时序,并且保证主从机互相了解对方的状态,方便数据传输的开始和终止。
  2. 本程序只是最简单的AXI-Lite,如果书写常规的AXI协议,将多出一些端口需要配置(比如id,len,size),后续博主会在这个代码的基础上写常规AXI协议。
  3. 本程序只进行了一次数据的读和写。但是使用AXI协议可以同时写很多数据,然后用last指示最后一个数据,此类云云。后续我会去写这样的代码的。

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...