赞
踩
官方手册里,Master 称为 Manager,Slave 称为 Subordinate
这一部分可以参考我之前的一篇博客 AXI4-Lite 实战,这里不再赘述
首先是AXI4-Full接口的五个通道,VALID 和 READY 信号的握手,以及突发传输的基本概念,这部分可以参考我之前的一篇博客 AXI4-Lite 实战 里对AXI4-Lite接口的描述,这里不再赘述。
此外,AXI4-Full 接口和 Lite 接口的一个不同点在于,前者支持 Transaction Identififiers
,也就是给每个读/写事务赋予一个 ID,具有相同 ID 的事务的顺序不能打乱,不同 ID 的事务的顺序可以打乱(即重排序),这个东西的作用是即使当前还有事务没有完成,Master 可以发出新事务(新事物的ID和未完成事务的ID要不一样),可以提高系统性能
Slave 可以不支持重排序,如果 Slave 不支持重排序但是有 ID 信号,那么一定要将信号 BID 的值赋值为 AWID,将信号 RID 的值赋值为 ARID,因为 Master 可能会用到不同的 ID 号,而此时 Master 需要通过 BID 和 RID 与 AWID 和 ARID 进行对应
下面主要讲解AXI-Full Slave接口,在讲解时,是直接把一个主设备和一个从设备连接在一起,从而忽略总线的仲裁和地址译码,重点关注协议内容。
这一部分可以参考我之前的一篇博客 AXI4-Lite 实战,这里不再赘述,需要注意的是,接口类型选择 Full
,接口模式选择 Slave
。
接下来主要是分析官方给的Slave示例,即 s_axi4_full_v1_0_S00_AXI.v
。
名称 | 解释 |
---|---|
C_S_AXI_ID_WIDTH | ID 信号的宽度 |
C_S_AXI_DATA_WIDTH | 数据宽度 |
C_S_AXI_ADDR_WIDTH | 地址宽度 |
C_S_AXI_AWUSER_WIDTH | 写地址通道中可选的用户定义的信号的宽度 |
C_S_AXI_ARUSER_WIDTH | 读地址通道中可选的用户定义的信号的宽度 |
C_S_AXI_WUSER_WIDTH | 写数据通道中可选的用户定义的信号的宽度 |
C_S_AXI_RUSER_WIDTH | 读数据通道中可选的用户定义的信号的宽度 |
C_S_AXI_BUSER_WIDTH | 写响应通道中可选的用户定义的信号的宽度 |
名称 | 解释 |
---|---|
S_AXI_ACLK | 时钟 |
S_AXI_ARESETN | 复位信号,低电平复位 |
名称 | 解释 |
---|---|
input wire [C_S_AXI_ID_WIDTH-1 : 0] S_AXI_AWID | 写地址ID,示例代码的 Slave 不支持重排序 |
input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_AWADDR | 写地址 |
input wire [7 : 0] S_AXI_AWLEN | 突发长度(Burst length),给出了突发中传输(Transfer)的数量 |
input wire [2 : 0] S_AXI_AWSIZE | 突发大小,给出了突发中每次传输的大小 |
input wire [1 : 0] S_AXI_AWBURST | 突发类型,AWBURST 和 AWSIZE 决定了此处突发中每次传输的地址是如何计算的 |
input wire S_AXI_AWLOCK | 锁类型(Lock Type),示例代码没用这个信号,这里不管它的作用 |
input wire [3 : 0] S_AXI_AWCACHE | 存储类型,示例代码没用这个信号,这里不管它的作用 |
input wire [2 : 0] S_AXI_AWPROT | 保护类型,示例代码没用这个信号,这里不管它的作用 |
input wire [3 : 0] S_AXI_AWQOS | 服务质量(Quality of Service),示例代码没用这个信号,这里不管它的作用 |
input wire [3 : 0] S_AXI_AWREGION | 区域标识符(Region identifier),允许 Slave 的单个物理接口用于多个逻辑接口,示例代码没用这个信号,这里不管它的作用 |
input wire [C_S_AXI_AWUSER_WIDTH-1 : 0] S_AXI_AWUSER | 写地址通道中可选的用户定义的信号,示例代码没用这个信号 |
input wire S_AXI_AWVALID | 写地址有效 |
output wire S_AXI_AWREADY | 写地址准备 |
下面主要解释一下 S_AXI_AWLEN、S_AXI_AWSIZE 和 S_AXI_AWBURST
突发长度(S_AXI_AWLEN):一次突发中可能会有多次数据的传输,该信号给出了传输的次数,需要注意的是,实际传输的次数是 S_AXI_AWLEN + 1
,比如该信号的值是2,那么实际上一共有三次传输,即会对三个地址处的值进行写入
需要注意的是
- 对于 Wrapping Burst(一种突发类型,后面会介绍),长度只能是 2、4、8 或 16,即 AWLEN 的值只能为 1、3、7 或 16
- 对于 Fixed Burst(一种突发类型,后面会介绍),长度可以从 1 到 16,即 AWLEN 的值可以从 0 到 15
- 事务不得跨越 4KB 地址边界
突发大小(S_AXI_AWSIZE ):该信号给出了每次传输的数据的大小,需要注意的是,该信号给出的数据大小不能超过数据宽度(即 C_S_AXI_DATA_WIDTH),下表是该信号的值与其表示的传输数据大小的对应关系
比如,如果数据宽度是32位,那么 AxSIZE 的值只能为 0b000, 0b001 或 0b010
在示例中,输入给 Slave 的 AxSIZE 的值恒为 0b010
突发类型(S_AXI_AWBURST):AWBURST 和 AWSIZE 决定了此处突发中每次传输的地址是如何计算的,下表给出了该信号的值与突发类型的对应关系
Fixed Burst:每次传输的地址是固定的,即为 S_AXI_AWADDR。该类型主要用于读或者清空 FIFO。
INCR (incrementing) Burst:每次传输的地址等于上一次传输的地址加上传输的大小(即S_AXI_AWSIZE信号所给出的传输的字节数),需要注意的是,Master 给出的初始地址必须是对齐的,比如说如果 AxSIZE 的值为 0b010,那么起始地址必须要是4字节对齐。该类型主要用于访问一般的存储器。
WRAP (wrapping) Burst:根据起始地址和传输大小,可以计算出一个
lower address
和upper address
,Wrapping Burst 下每次传输地址的计算和 Incrementing Burst 是相似的,除了当地址增加到upper address
时,下次传输的地址需要回落到lower address
。需要注意的是,起始地址必须是和 AxSIZE 对齐的,而且突发长度只能为2、4、8或16。lower address
必须与此次突发中所有传输的字节总数(即突发大小 * 突发长度)是对齐的,现在举个例子说明lower address
和upper address
如何计算的。例如,突发长度为 8(即AxLEN的值为7),突发大小为 4 字节(即AxSIZE为0b010),那么此次突发中传输的总字节数是 32,因此lower address
必须是32的整数倍。假如起始地址是 72(必须和突发大小是对齐的),相邻两个32字节对齐、并且起始地址位于其间的地址是 64 和 96,因此lower address
便是 64,upper address
便是 96-4=92 (即减去突发大小),因此此次突发的地址依次是72, 76, 80, 84, 88, 92, 64, 68
,需要注意的是传输地址中不包括96这个地址。Wrapping Burst 主要用于 cache line 的访问,当我们访问 cache line 中的某个地址时,如果发生缺失,需要从内存中搬整个 line 大小的数据放到这个 cache line 中来,我们访问的地址不一定就是 cache line 的边界地址,此时便可以用 Wrapping Burst 来方便地解决这个问题:如果 cache line 的大小是 32 字节,便可设置突发长度为 8,突发大小为 4 字节,突发类型为 Wrapping Burst
Reserved:这个官方没有定义,用户可以根据需求自己定义
名称 | 解释 |
---|---|
input wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_WDATA | 写数据 |
input wire [(C_S_AXI_DATA_WIDTH/8)-1 : 0] S_AXI_WSTRB | 写数据选通 |
input wire S_AXI_WLAST | 写最后,表示当前传输是突发中的最后一个传输 |
input wire [C_S_AXI_WUSER_WIDTH-1 : 0] S_AXI_WUSER | 写数据通道中可选的用户定义的信号,示例代码没用这个信号 |
input wire S_AXI_WVALID | 写数据有效 |
output wire S_AXI_WREADY | 写数据准备 |
在进行最后一次传输的时候,S_AXI_WLAST 信号要拉高
名称 | 解释 |
---|---|
output wire [C_S_AXI_ID_WIDTH-1 : 0] S_AXI_BID | 写响应的ID,示例代码的 Slave 不支持重排序,因此该信号必须赋值为 S_AXI_AWID |
output wire [1 : 0] S_AXI_BRESP | 写响应,给出写事务的状态,示例代码中该信号恒为 2’b0 |
output wire [C_S_AXI_BUSER_WIDTH-1 : 0] S_AXI_BUSER | 写响应通道中可选的用户定义的信号 |
output wire S_AXI_BVALID | 写响应有效 |
input wire S_AXI_BREADY | 写响应准备 |
名称 | 解释 |
---|---|
input wire [C_S_AXI_ID_WIDTH-1 : 0] S_AXI_ARID | 读地址ID,示例代码的 Slave 不支持重排序 |
input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_ARADDR | 读地址 |
input wire [7 : 0] S_AXI_ARLEN | 突发长度,给出了突发中传输的数量 |
input wire [2 : 0] S_AXI_ARSIZE | 突发大小,给出了突发中每次传输的大小 |
input wire [1 : 0] S_AXI_ARBURST | 突发类型,ARBURST 和 ARSIZE 决定了此处突发中每次传输的地址是如何计算的 |
input wire S_AXI_ARLOCK | 锁类型(Lock Type),示例代码没用这个信号,这里不管它的作用 |
input wire [3 : 0] S_AXI_ARCACHE | 存储类型,示例代码没用这个信号,这里不管它的作用 |
input wire [2 : 0] S_AXI_ARPROT | 保护类型,示例代码没用这个信号,这里不管它的作用 |
input wire [3 : 0] S_AXI_ARQOS | 服务质量(Quality of Service),示例代码没用这个信号,这里不管它的作用 |
input wire [3 : 0] S_AXI_ARREGION | 区域标识符(Region identifier),允许 Slave 的单个物理接口用于多个逻辑接口,示例代码没用这个信号,这里不管它的作用 |
input wire [C_S_AXI_ARUSER_WIDTH-1 : 0] S_AXI_ARUSER | 读地址通道中可选的用户定义的信号 |
input wire S_AXI_ARVALID | 读地址有效 |
output wire S_AXI_ARREADY | 读地址准备 |
S_AXI_ARLEN、S_AXI_ARSIZE 和 S_AXI_ARBURST 的含义参考写地址通道中的解释
名称 | 解释 |
---|---|
output wire [C_S_AXI_ID_WIDTH-1 : 0] S_AXI_RID | 读ID,示例代码的 Slave 不支持重排序,因此该信号必须赋值为 S_AXI_ARID |
output wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_RDATA | 读数据 |
output wire [1 : 0] S_AXI_RRESP | 读响应,示例代码中该信号恒为 2’b0 |
output wire S_AXI_RLAST | 读最后,表示当前传输是突发中的最后一个传输 |
output wire [C_S_AXI_RUSER_WIDTH-1 : 0] S_AXI_RUSER | 读地址通道中可选的用户定义的信号 |
output wire S_AXI_RVALID | 读数据有效 |
input wire S_AXI_RREADY | 读数据准备 |
在进行最后一次传输的时候,S_AXI_RLAST 信号要拉高
下面解释部分信号和参数,没解释的信号先不管,后面碰到了再解释
aw_wrap_en
: 在 WRAP 突发中,当下一次传输的地址需要回落到 lower address
时,该信号会拉高ar_wrap_en
: 在 WRAP 突发中,当下一次传输的地址需要回落到 lower address
时,该信号会拉高aw_wrap_size
: 该信号的值等于 SIZE * AWLEN,其中 SIZE 是单次传输的字节数(SIZE 不等于信号 AWSIZE的值),需要注意的是,该信号的值并不等于突发中传输的总字节数,实际传输的总字节数应该为 SIZE * (AWLEN + 1)。该信号的作用是,在 WRAP 突发中,当 aw_wrap_en
使能的时候,下一次传输的地址就等于当前地址减去 aw_wrap_size
。ar_wrap_size
: 该信号的值等于 SIZE * ARLEN,其中 SIZE 是单次传输的字节数(SIZE 不等于信号 ARSIZE的值),需要注意的是,该信号的值并不等于突发中传输的总字节数,实际传输的总字节数应该为 SIZE * (ARLEN + 1)。该信号的作用是,在 WRAP 突发中,当 ar_wrap_en
使能的时候,下一次传输的地址就等于当前地址减去 ar_wrap_size
。ADDR_LSB
: 回忆之前说的,在类型为 WRAP 或 INCR 的突发中,起始地址要和 AxSIZE 表示的传输大小对齐,假如说 AxSIZE 的值为 0b010,即传输大小为 4 字节,那么意味着起始地址的 [1:0] 位都为 0,此时 ADDR_LSB 的值即为 2,也就是说,起始地址的 [ADDR_LSB-1:0] 位需要都为0,从而和传输大小保持对齐。又比如说,AxSIZE 的值为 0b011,即传输大小为 8 字节,那么此时 ADDR_LSB 的值应该为 3,即起始地址的 [3-1:0] 位需要都为0。总之需要注意的是,ADDR_LSB 的值是由 AxSIZE 的值决定的。// AXI4FULL signals reg [C_S_AXI_ADDR_WIDTH-1 : 0] axi_awaddr; reg axi_awready; reg axi_wready; reg [1 : 0] axi_bresp; reg [C_S_AXI_BUSER_WIDTH-1 : 0] axi_buser; reg axi_bvalid; reg [C_S_AXI_ADDR_WIDTH-1 : 0] axi_araddr; reg axi_arready; reg [C_S_AXI_DATA_WIDTH-1 : 0] axi_rdata; reg [1 : 0] axi_rresp; reg axi_rlast; reg [C_S_AXI_RUSER_WIDTH-1 : 0] axi_ruser; reg axi_rvalid; // aw_wrap_en determines wrap boundary and enables wrapping wire aw_wrap_en; // ar_wrap_en determines wrap boundary and enables wrapping wire ar_wrap_en; // aw_wrap_size is the size of the write transfer, the // write address wraps to a lower address if upper address // limit is reached wire [31:0] aw_wrap_size ; // ar_wrap_size is the size of the read transfer, the // read address wraps to a lower address if upper address // limit is reached wire [31:0] ar_wrap_size ; // The axi_awv_awr_flag flag marks the presence of write address valid reg axi_awv_awr_flag; //The axi_arv_arr_flag flag marks the presence of read address valid reg axi_arv_arr_flag; // The axi_awlen_cntr internal write address counter to keep track of beats in a burst transaction reg [7:0] axi_awlen_cntr; //The axi_arlen_cntr internal read address counter to keep track of beats in a burst transaction reg [7:0] axi_arlen_cntr; reg [1:0] axi_arburst; reg [1:0] axi_awburst; reg [7:0] axi_arlen; reg [7:0] axi_awlen; //local parameter for addressing 32 bit / 64 bit C_S_AXI_DATA_WIDTH //ADDR_LSB is used for addressing 32/64 bit registers/memories //ADDR_LSB = 2 for 32 bits (n downto 2) //ADDR_LSB = 3 for 64 bits (n downto 3) localparam integer ADDR_LSB = (C_S_AXI_DATA_WIDTH/32)+ 1; localparam integer OPT_MEM_ADDR_BITS = 7; localparam integer USER_NUM_MEM = 1; //---------------------------------------------- //-- Signals for user logic memory space example //------------------------------------------------ wire [OPT_MEM_ADDR_BITS:0] mem_address; wire [USER_NUM_MEM-1:0] mem_select; reg [C_S_AXI_DATA_WIDTH-1:0] mem_data_out[0 : USER_NUM_MEM-1]; genvar i; genvar j; genvar mem_byte_index;
有几个地方需要注意:
aw_wrap_size
和 ar_wrap_size
计算方法,示例代码中 Slave 不支持可变的传输大小,传递给 Slave 的AxSIZE 信号所表示的传输字节数目,恰好等于数据宽度,所以这里每次传输的字节数,就用 C_S_AXI_DATA_WIDTH/8 来表示了aw_wrap_en
和 ar_wrap_en
的计算方法,当地址等于 upper address
时, aw_wrap_en
和 ar_wrap_en
会被使能,需要注意的是 ar_wrap_size 不等于传输的总字节数// I/O Connections assignments assign S_AXI_AWREADY = axi_awready; assign S_AXI_WREADY = axi_wready; assign S_AXI_BRESP = axi_bresp; assign S_AXI_BUSER = axi_buser; assign S_AXI_BVALID = axi_bvalid; assign S_AXI_ARREADY = axi_arready; assign S_AXI_RDATA = axi_rdata; assign S_AXI_RRESP = axi_rresp; assign S_AXI_RLAST = axi_rlast; assign S_AXI_RUSER = axi_ruser; assign S_AXI_RVALID = axi_rvalid; assign S_AXI_BID = S_AXI_AWID; assign S_AXI_RID = S_AXI_ARID; assign aw_wrap_size = (C_S_AXI_DATA_WIDTH/8 * (axi_awlen)); assign ar_wrap_size = (C_S_AXI_DATA_WIDTH/8 * (axi_arlen)); assign aw_wrap_en = ((axi_awaddr & aw_wrap_size) == aw_wrap_size)? 1'b1: 1'b0; assign ar_wrap_en = ((axi_araddr & ar_wrap_size) == ar_wrap_size)? 1'b1: 1'b0;
首先是 axi_awready
和 axi_awv_awr_flag
的生成。
这里需要先解释一下
axi_awv_awr_flag
和axi_arv_arr_flag
什么时候会被置位和复位:axi_awv_awr_flag
/axi_arv_arr_flag
会在检测到 Master 即将开启一次写/读事务的时候(即检测到 S_AXI_AxVALID 被置位时)被置位,会在检测到此次突发中最后一次写/读传输完成的时候(即检测到 S_AXI_xLAST && axi_xready)被复位
// Implement axi_awready generation // 下面这个英文注解写的有问题,axi_awready 不需要等 S_AXI_WVALID 置位 // axi_awready is asserted for one S_AXI_ACLK clock cycle when both // S_AXI_AWVALID and S_AXI_WVALID are asserted. axi_awready is // de-asserted when reset is low. // always @( posedge S_AXI_ACLK ) begin if ( S_AXI_ARESETN == 1'b0 ) begin axi_awready <= 1'b0; axi_awv_awr_flag <= 1'b0; end else begin // 没有读事务的数据正在传输:~axi_arv_arr_flag // 没有写事务的数据正在传输(但是可能有写响应正在进行):~axi_awv_awr_flag // Master 想要发起一次写事务:S_AXI_AWVALID if (~axi_awready && S_AXI_AWVALID && ~axi_awv_awr_flag && ~axi_arv_arr_flag) begin // slave is ready to accept an address and // associated control signals axi_awready <= 1'b1; axi_awv_awr_flag <= 1'b1; // used for generation of bresp() and bvalid end // S_AXI_WLAST 表示是突发中的最后一次传输 // axi_wready 表示这次传输已经完成了 else if (S_AXI_WLAST && axi_wready) // preparing to accept next address after current write burst tx completion begin axi_awv_awr_flag <= 1'b0; end else // axi_awready 只会拉高一个周期 begin axi_awready <= 1'b0; end end end
然后是 axi_awaddr
的生成和其他控制信号的锁存
axi_awaddr
表示的是当前传输的地址
// 下面这个英文注解写的有问题,不需要等 S_AXI_WVALID 置位 // Implement axi_awaddr latching // This process is used to latch the address when both // S_AXI_AWVALID and S_AXI_WVALID are valid. always @( posedge S_AXI_ACLK ) begin if ( S_AXI_ARESETN == 1'b0 ) begin axi_awaddr <= 0; // 此次突发中,当前传输的地址 axi_awlen_cntr <= 0; // 该信号表示此次突发中,当前是第几个传输,注意是从0开始计数 axi_awburst <= 0; // 突发类型 axi_awlen <= 0; // 实际传输的次数是 axi_awlen + 1 end else begin // 下面这个if的作用是将有关的信号先锁存起来 // 这里的条件比 axi_awready 的生成代码里少了一个,因为无论是否在进行读事务,都不会妨碍先把有关写事务的信息锁存起来,只要没有在进行写事务即可 if (~axi_awready && S_AXI_AWVALID && ~axi_awv_awr_flag) begin // address latching axi_awaddr <= S_AXI_AWADDR[C_S_AXI_ADDR_WIDTH - 1:0]; axi_awburst <= S_AXI_AWBURST; axi_awlen <= S_AXI_AWLEN; // start address of transfer axi_awlen_cntr <= 0; end // axi_wready && S_AXI_WVALID:当前传输已经完成 // (axi_awlen_cntr <= axi_awlen):示例代码用这个条件我感觉不是很合适,后面再详细解释 // 需要注意的是,每次传输都需要ready和valid握手 // Master一定是一次传输最多只能占一个周期,不能说一次传输我把Valid拉高多个周期 // 因为Slave每次时钟上升沿都会检查 axi_wready && S_AXI_WVALID,如果条件成立就认为已经完成一次传输了 // 当然相邻传输之间可以等多个周期,但是需要把valid拉低 else if((axi_awlen_cntr <= axi_awlen) && axi_wready && S_AXI_WVALID) begin axi_awlen_cntr <= axi_awlen_cntr + 1; // 根据突发类型,来生成下一次要写入数据的地址 // 由于示例代码的 Slave 不支持可变的传输大小,所以下面的这段代码都默认传输大小为数据宽度 case (axi_awburst) 2'b00: // fixed burst // The write address for all the beats in the transaction are fixed begin axi_awaddr <= axi_awaddr; //for awsize = 4 bytes (010) end 2'b01: //incremental burst // The write address for all the beats in the transaction are increments by awsize begin axi_awaddr[C_S_AXI_ADDR_WIDTH - 1:ADDR_LSB] <= axi_awaddr[C_S_AXI_ADDR_WIDTH - 1:ADDR_LSB] + 1; //awaddr aligned to 4 byte boundary axi_awaddr[ADDR_LSB-1:0] <= {ADDR_LSB{1'b0}}; //for awsize = 4 bytes (010) end 2'b10: //Wrapping burst // The write address wraps when the address reaches wrap boundary if (aw_wrap_en) begin axi_awaddr <= (axi_awaddr - aw_wrap_size); end else begin axi_awaddr[C_S_AXI_ADDR_WIDTH - 1:ADDR_LSB] <= axi_awaddr[C_S_AXI_ADDR_WIDTH - 1:ADDR_LSB] + 1; axi_awaddr[ADDR_LSB-1:0] <= {ADDR_LSB{1'b0}}; end default: //reserved (incremental burst for example) begin axi_awaddr <= axi_awaddr[C_S_AXI_ADDR_WIDTH - 1:ADDR_LSB] + 1; //for awsize = 4 bytes (010) end endcase end end end
这里解释一下,为什么我认为示例代码使用 (axi_awlen_cntr <= axi_awlen)
不太合理
从直觉上来说,只有当还有传输没有完成时,我们才需要更新下一次传输的地址,但是根据示例代码,当最后一次传输已经完成时(即axi_awlen_cntr == axi_awlen && axi_wready && S_AXI_WVALID),此时传输的地址还是会更新,这可以从模拟波形得到验证,下图是模拟的波形,可以看到,最后一次传输的地址是 0x1c,但是实际上传输完成后,axi_awaddr
的值变成了 0x20
当然这个在写地址生成这里问题不大,仅仅只是不符合直觉而已;但是在读地址那块,代码仍然采用了 (axi_awlen_cntr <= axi_awlen)
作为条件,那里会牵涉到另一个问题,后面再解释
这块主要是 axi_wready
的生成,该信号会在传输中一直保持高电平,直至最后一次传输完成,这也是为什么我在上一节的代码注解中说 Master一定是一次传输最多只能占一个周期,不能说一次传输我把Valid拉高多个周期
,因为ready信号一直拉高,如果一次传输把Valid拉高多个周期,那么slave会认为已经完成了多次传输
// Implement axi_wready generation // 下面这个英文注解写的有问题,不需要检测 S_AXI_AWVALID 置位 // axi_wready is asserted for one S_AXI_ACLK clock cycle when both // S_AXI_AWVALID and S_AXI_WVALID are asserted. axi_wready is // de-asserted when reset is low. always @( posedge S_AXI_ACLK ) begin if ( S_AXI_ARESETN == 1'b0 ) begin axi_wready <= 1'b0; end else begin // 对方发送的数据有效:S_AXI_WVALID // 之前已经完成写地址:axi_awv_awr_flag if ( ~axi_wready && S_AXI_WVALID && axi_awv_awr_flag) begin // slave can accept the write data axi_wready <= 1'b1; end // axi_wready 会一直拉高,直至所有的传输均已完成 // S_AXI_WLAST 表示是突发中的最后一次传输 // axi_wready 表示这次传输已经完成了 //else if (~axi_awv_awr_flag) else if (S_AXI_WLAST && axi_wready) begin axi_wready <= 1'b0; end end end
这块比较简单,不再赘述
// Implement write response logic generation // The write response and response valid signals are asserted by the slave // when axi_wready, S_AXI_WVALID, axi_wready and S_AXI_WVALID are asserted. // This marks the acceptance of address and indicates the status of // write transaction. always @( posedge S_AXI_ACLK ) begin if ( S_AXI_ARESETN == 1'b0 ) begin axi_bvalid <= 0; axi_bresp <= 2'b0; axi_buser <= 0; end else begin // 最后一次传输已完成: S_AXI_WLAST && S_AXI_WVALID && axi_wready if (axi_awv_awr_flag && axi_wready && S_AXI_WVALID && ~axi_bvalid && S_AXI_WLAST ) begin axi_bvalid <= 1'b1; axi_bresp <= 2'b0; // 'OKAY' response end else begin if (S_AXI_BREADY && axi_bvalid) //check if bready is asserted while bvalid is high) //(there is a possibility that bready is always asserted high) begin axi_bvalid <= 1'b0; end end end end
我们可以结合仿真波形,来理解写地址、写数据和写响应,下面是有关的波形,读者可以自行结合代码进一步理解
首先是 axi_arready
和 axi_arv_arr_flag
的生成。
这块代码和写地址通道差不多,这里不再赘述
// Implement axi_arready generation // axi_arready is asserted for one S_AXI_ACLK clock cycle when // S_AXI_ARVALID is asserted. axi_awready is // de-asserted when reset (active low) is asserted. // The read address is also latched when S_AXI_ARVALID is // asserted. axi_araddr is reset to zero on reset assertion. always @( posedge S_AXI_ACLK ) begin if ( S_AXI_ARESETN == 1'b0 ) begin axi_arready <= 1'b0; axi_arv_arr_flag <= 1'b0; end else begin if (~axi_arready && S_AXI_ARVALID && ~axi_awv_awr_flag && ~axi_arv_arr_flag) begin axi_arready <= 1'b1; axi_arv_arr_flag <= 1'b1; end // 最后一次传输已经完成 else if (axi_rvalid && S_AXI_RREADY && axi_arlen_cntr == axi_arlen) // preparing to accept next address after current read completion begin axi_arv_arr_flag <= 1'b0; end else begin axi_arready <= 1'b0; end end end
然后是 axi_araddr
的生成和其他控制信号的锁存,这块大体上和写地址那块差不多,除了增加了一个 axi_rlast 信号的生成,这个稍后再描述
// Implement axi_araddr latching //This process is used to latch the address when both //S_AXI_ARVALID and S_AXI_RVALID are valid. always @( posedge S_AXI_ACLK ) begin if ( S_AXI_ARESETN == 1'b0 ) begin axi_araddr <= 0; axi_arlen_cntr <= 0; axi_arburst <= 0; axi_arlen <= 0; axi_rlast <= 1'b0; axi_ruser <= 0; end else begin // 没有读事务在进行 if (~axi_arready && S_AXI_ARVALID && ~axi_arv_arr_flag) begin // address latching axi_araddr <= S_AXI_ARADDR[C_S_AXI_ADDR_WIDTH - 1:0]; axi_arburst <= S_AXI_ARBURST; axi_arlen <= S_AXI_ARLEN; // start address of transfer axi_arlen_cntr <= 0; axi_rlast <= 1'b0; end else if((axi_arlen_cntr <= axi_arlen) && axi_rvalid && S_AXI_RREADY) begin axi_arlen_cntr <= axi_arlen_cntr + 1; axi_rlast <= 1'b0; case (axi_arburst) 2'b00: // fixed burst // The read address for all the beats in the transaction are fixed begin axi_araddr <= axi_araddr; //for arsize = 4 bytes (010) end 2'b01: //incremental burst // The read address for all the beats in the transaction are increments by awsize begin axi_araddr[C_S_AXI_ADDR_WIDTH - 1:ADDR_LSB] <= axi_araddr[C_S_AXI_ADDR_WIDTH - 1:ADDR_LSB] + 1; //araddr aligned to 4 byte boundary axi_araddr[ADDR_LSB-1:0] <= {ADDR_LSB{1'b0}}; //for awsize = 4 bytes (010) end 2'b10: //Wrapping burst // The read address wraps when the address reaches wrap boundary if (ar_wrap_en) begin axi_araddr <= (axi_araddr - ar_wrap_size); end else begin axi_araddr[C_S_AXI_ADDR_WIDTH - 1:ADDR_LSB] <= axi_araddr[C_S_AXI_ADDR_WIDTH - 1:ADDR_LSB] + 1; //araddr aligned to 4 byte boundary axi_araddr[ADDR_LSB-1:0] <= {ADDR_LSB{1'b0}}; end default: //reserved (incremental burst for example) begin axi_araddr <= axi_araddr[C_S_AXI_ADDR_WIDTH - 1:ADDR_LSB]+1; //for arsize = 4 bytes (010) end endcase end else if((axi_arlen_cntr == axi_arlen) && ~axi_rlast && axi_arv_arr_flag ) begin axi_rlast <= 1'b1; end else if (S_AXI_RREADY) begin axi_rlast <= 1'b0; end end end
这里解释一下,为什么我认为示例代码使用 (axi_arlen_cntr <= axi_arlen)
不太合理,除了上面说的不符合直觉外,还存在一个问题
需要注意的是,axi_rlast 置位的条件是 else if((axi_arlen_cntr == axi_arlen) && ~axi_rlast && axi_arv_arr_flag)
,而在这之前是地址的更新,这块的条件是 else if((axi_arlen_cntr <= axi_arlen) && axi_rvalid && S_AXI_RREADY)
,这里存在一个问题,当 axi_arlen_cntr 刚增加到 axi_arlen 的时候,此时如果 axi_rvalid 已经是置位的,并且对方也拉高了 S_AXI_RREADY 信号,那么 if 语句会进入到地址更新的代码,而不会进入 axi_rlast 置位的代码,但是此时已经最后一次传输已经完成了,但是 axi_rlast 却没有置位,这是不符合规范的。我描述的这种情况是可能存在的,因为官方的示例中,写事务在进行若干次数据传输的过程中,valid 和 ready 信号就是一直拉高的,这样就是一个周期完成一次传输,如果读事务也是这样一周期完成一次传输,那就会出现我上面说的 axi_rlast 没有置位的情况。
所幸的是,官方示例在读事务中并不是一周期完成一次传输,这在后面 valid 信号的生成代码可以看出来,每次valid置位后,都会复位一个周期,这样就不会出现我上面这个问题:当 axi_arlen_cntr 刚增加到 axi_arlen 的时候,由于此时valid刚复位,所以下一个时钟上升沿 else if((axi_arlen_cntr <= axi_arlen) && axi_rvalid && S_AXI_RREADY)
会为 False,而 else if((axi_arlen_cntr == axi_arlen) && ~axi_rlast && axi_arv_arr_flag)
会为 True,从而成功进入到 axi_rlast 置位的代码。
如果后续要将 slave 优化为读事务中一周期完成一次传输,一旦没有注意到 (axi_arlen_cntr <= axi_arlen)
这个问题,就会出现上面说的 axi_rlast 没有置位的情况。因此我认为,应该使用 (axi_arlen_cntr < axi_arlen)
作为判断条件才更为合理。
在一次突发中,每完成一次传输后,axi_rvalid 都会复位一个周期,具体见下面代码
// Implement axi_rvalid generation // axi_rvalid is asserted for one S_AXI_ACLK clock cycle when both // S_AXI_ARVALID and axi_arready are asserted. The slave registers // data are available on the axi_rdata bus at this instance. The // assertion of axi_rvalid marks the validity of read data on the // bus and axi_rresp indicates the status of read transaction.axi_rvalid // is deasserted on reset (active low). axi_rresp and axi_rdata are // cleared to zero on reset (active low). always @( posedge S_AXI_ACLK ) begin if ( S_AXI_ARESETN == 1'b0 ) begin axi_rvalid <= 0; axi_rresp <= 0; end else begin if (axi_arv_arr_flag && ~axi_rvalid) begin axi_rvalid <= 1'b1; axi_rresp <= 2'b0; // 'OKAY' response end else if (axi_rvalid && S_AXI_RREADY) begin axi_rvalid <= 1'b0; end end end
可以结合仿真波形,进一步理解读地址和读数据
在官方的 Slave 的示例中,可以生成 USER_NUM_MEM
个存储器,这里 USER_NUM_MEM
的值默认为 1。
需要说明的是,有些信号官方示例给出了,但是却不用,下面只是我自己的理解,可能有误,欢迎指正
首先是 mem_select
和 mem_address
的生成
generate
if (USER_NUM_MEM >= 1)
begin
assign mem_select = 1;
assign mem_address = (axi_arv_arr_flag? axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB]:(axi_awv_awr_flag? axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB]:0));
end
endgenerate
mem_select
一共有 USER_NUM_MEM
个比特位,置位就表示对应的存储器被选择用来完成读/写事务,这个信号官方示例就是简单的置1,但是实际使用肯定不是这样,这里我提出自己的两种解释,我更倾向于第一种解释
需要注意的是, mem_address
一共是 OPT_MEM_ADDR_BITS+1 个比特位,因此 Master 给出的地址,只有低OPT_MEM_ADDR_BITS+1+ADDR_LSB位是真正对 Slave 有用的
然后是 USER_NUM_MEM
个存储器的生成
// implement Block RAM(s) generate // 这个循环是生成 USER_NUM_MEM 个存储器 for(i=0; i<= USER_NUM_MEM-1; i=i+1) begin:BRAM_GEN wire mem_rden; wire mem_wren; assign mem_wren = axi_wready && S_AXI_WVALID ; assign mem_rden = axi_arv_arr_flag ; //& ~axi_rvalid // 这个循环是生成单个存储器 for(mem_byte_index=0; mem_byte_index<= (C_S_AXI_DATA_WIDTH/8-1); mem_byte_index=mem_byte_index+1) begin:BYTE_BRAM_GEN wire [8-1:0] data_in ; wire [8-1:0] data_out; // 这里用255这个数字不太好,这个值等于 2^(OPT_MEM_ADDR_BITS+1) - 1 更好 reg [8-1:0] byte_ram [0 : 255]; integer j; //assigning 8 bit data assign data_in = S_AXI_WDATA[(mem_byte_index*8+7) -: 8]; assign data_out = byte_ram[mem_address]; always @( posedge S_AXI_ACLK ) begin if (mem_wren && S_AXI_WSTRB[mem_byte_index]) begin byte_ram[mem_address] <= data_in; end end always @( posedge S_AXI_ACLK ) begin if (mem_rden) begin // 这里使用了 :- 这个操作符 // [end_idx -: width] 等效于 [end_idx : end_idx - width + 1] // [(mem_byte_index*8+7) : (mem_byte_index*8+7)-8+1] // 这里的8给出的是宽度 mem_data_out[i][(mem_byte_index*8+7) -: 8] <= data_out; end end end end endgenerate
上面生成单个存储器的循环可以用下图解释,需要注意的是,Slave 默认传输的大小等于数据宽度
最后是生成 axi_rdata
//Output register or memory read data
always @( mem_data_out, axi_rvalid)
begin
if (axi_rvalid)
begin
// Read address mux
axi_rdata <= mem_data_out[0];
end
else
begin
axi_rdata <= 32'h00000000;
end
end
值得一提的是,上面代码生成的电路是一个组合逻辑电路,通过查看RTL分析得到的网表结构可以知道,该代码的实现使用了一个LUT2,如下图所示
LUT的介绍可以参考我之前的一篇博客 FPGA原理介绍 (CLB, LUT, 进位链, 存储元素, RAM)
在修改官方示例从而用于自己的应用中时,一定要弄清楚 Master 和 Slave 各自支持什么功能,比如说 Master 可能会改变 AxSIZE 的值,但是 Slave 又不支持,那这时候就需要进行修改了,要么把 Master 改为 AxSIZE 的值不变,要么把 Slave 改为支持可变的 AxSIZE。
官方示例的 Slave 恰好可以满足我自己项目中 Master 的需求,因此我并不需要对 Slave 代码进行大规模修改。下面是对 Slave 官方示例的修改和优化。
首先代码中两个地方直接使用了数值,而不是使用参数,这里我将其修改为参数
// 第一处:存储器 byte_ram 的大小使用数值 255 来表示,这里将其用参数 OPT_MEM_ADDR_BITS 表示 for(mem_byte_index=0; mem_byte_index<= (C_S_AXI_DATA_WIDTH/8-1); mem_byte_index=mem_byte_index+1) begin:BYTE_BRAM_GEN wire [8-1:0] data_in ; wire [8-1:0] data_out; // reg [8-1:0] byte_ram [0 : 255]; reg [8-1:0] byte_ram [0 : 2**(OPT_MEM_ADDR_BITS+1)-1]; integer j; // 第二处:直接使用 axi_rdata <= 32'h00000000,实际上 axi_rdata 的宽度也可能为 64,应该用 C_S_AXI_ADDR_WIDTH 来表示 always @( mem_data_out, axi_rvalid) begin if (axi_rvalid) begin // Read address mux axi_rdata <= mem_data_out[0]; end else begin // axi_rdata <= 32'h00000000; axi_rdata <= {C_S_AXI_ADDR_WIDTH{1'b0}}; end end
我的需求是测试项目中各模块的延迟,Slave 只要有返回值即可,但我并不关心 Slave 返回的值,因此我这里将读出的数据恒赋为 1
always @( posedge S_AXI_ACLK )
begin
if (mem_rden)
begin
// mem_data_out[i][(mem_byte_index*8+7) -: 8] <= data_out;
mem_data_out[i][(mem_byte_index*8+7) -: 8] <= 8'b01;
end
end
接下来是优化读事务,官方的 Slave 示例在一次突发中,每次数据传输至少需要 2 个周期,这里将其优化为至少需要 1 个周期,首先我将代码进行了如下的修改(需要注意的是下面的修改是错误的,这里仅仅是将我遇到的问题记录下来)
// 将
// else if((axi_arlen_cntr <= axi_arlen) && axi_rvalid && S_AXI_RREADY)
// 改为
else if((axi_arlen_cntr < axi_arlen) && axi_rvalid && S_AXI_RREADY)
// 将
//else if (axi_rvalid && S_AXI_RREADY)
// begin
// axi_rvalid <= 1'b0;
// end
// 改为
else if (axi_rvalid && S_AXI_RREADY && axi_rlast)
begin
axi_rvalid <= 1'b0;
end
修改后进行仿真出现了问题,起初我以为是项目本身有问题,在查看了很久的波形图和项目代码后,我最终发现问题还是在我修改的 Slave 代码上。按照我上面的修改方式,axi_rlast 的拉高会延迟一个周期,从而使得一次突发中传输的次数比预期的要多 1 次。
在我没有进行上面的修改时,即每次数据传输至少需要 2 个周期,此时的仿真是正确的,但是修改后就出现了问题,起初我以为是由于项目的 Master 不支持一周期一次传输,这导致我 debug 的方向不对,最终饶了一圈,结果发现是 Slave 修改错了
这里给我的一个提示就是,首先要对自己修改的代码要十分谨慎,要先确保没有修改错,其次就是可以进行修改前后一些核心信号的波形图的对比,这样可以更加快速的锁定问题所在
最终正确的修改方式如下
// 将 // else if((axi_arlen_cntr <= axi_arlen) && axi_rvalid && S_AXI_RREADY) // begin // axi_arlen_cntr <= axi_arlen_cntr + 1; // axi_rlast <= 1'b0; // case (axi_arburst) // 2'b00: // fixed burst // // The read address for all the beats in the transaction are fixed // begin // axi_araddr <= axi_araddr; // //for arsize = 4 bytes (010) // end // 2'b01: //incremental burst // // The read address for all the beats in the transaction are increments by awsize // begin // axi_araddr[C_S_AXI_ADDR_WIDTH - 1:ADDR_LSB] <= axi_araddr[C_S_AXI_ADDR_WIDTH - 1:ADDR_LSB] + 1; // //araddr aligned to 4 byte boundary // axi_araddr[ADDR_LSB-1:0] <= {ADDR_LSB{1'b0}}; // //for awsize = 4 bytes (010) // end // 2'b10: //Wrapping burst // // The read address wraps when the address reaches wrap boundary // if (ar_wrap_en) // begin // axi_araddr <= (axi_araddr - ar_wrap_size); // end // else // begin // axi_araddr[C_S_AXI_ADDR_WIDTH - 1:ADDR_LSB] <= axi_araddr[C_S_AXI_ADDR_WIDTH - 1:ADDR_LSB] + 1; // //araddr aligned to 4 byte boundary // axi_araddr[ADDR_LSB-1:0] <= {ADDR_LSB{1'b0}}; // end // default: //reserved (incremental burst for example) // begin // axi_araddr <= axi_araddr[C_S_AXI_ADDR_WIDTH - 1:ADDR_LSB]+1; // //for arsize = 4 bytes (010) // end // endcase // end // else if((axi_arlen_cntr == axi_arlen) && ~axi_rlast && axi_arv_arr_flag ) // begin // axi_rlast <= 1'b1; // end // 修改为 else if((axi_arlen_cntr < axi_arlen) && axi_rvalid && S_AXI_RREADY) begin if (axi_arlen_cntr == axi_arlen - 1) begin axi_rlast <= 1'b1; end else begin axi_rlast <= 1'b0; end axi_arlen_cntr <= axi_arlen_cntr + 1; case (axi_arburst) 2'b00: // fixed burst // The read address for all the beats in the transaction are fixed begin axi_araddr <= axi_araddr; //for arsize = 4 bytes (010) end 2'b01: //incremental burst // The read address for all the beats in the transaction are increments by awsize begin axi_araddr[C_S_AXI_ADDR_WIDTH - 1:ADDR_LSB] <= axi_araddr[C_S_AXI_ADDR_WIDTH - 1:ADDR_LSB] + 1; //araddr aligned to 4 byte boundary axi_araddr[ADDR_LSB-1:0] <= {ADDR_LSB{1'b0}}; //for awsize = 4 bytes (010) end 2'b10: //Wrapping burst // The read address wraps when the address reaches wrap boundary if (ar_wrap_en) begin axi_araddr <= (axi_araddr - ar_wrap_size); end else begin axi_araddr[C_S_AXI_ADDR_WIDTH - 1:ADDR_LSB] <= axi_araddr[C_S_AXI_ADDR_WIDTH - 1:ADDR_LSB] + 1; //araddr aligned to 4 byte boundary axi_araddr[ADDR_LSB-1:0] <= {ADDR_LSB{1'b0}}; end default: //reserved (incremental burst for example) begin axi_araddr <= axi_araddr[C_S_AXI_ADDR_WIDTH - 1:ADDR_LSB]+1; //for arsize = 4 bytes (010) end endcase end // 将 //else if (axi_rvalid && S_AXI_RREADY) // begin // axi_rvalid <= 1'b0; // end // 改为 else if (axi_rvalid && S_AXI_RREADY && axi_rlast) begin axi_rvalid <= 1'b0; end
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。