module axi_bram #( parameter AXI_IDWIDTH = 4, // AXI的id宽度 parameter AXI_AWIDTH = 64, // AXI的 地址总线宽度 parameter AXI_DWIDTH = 256, // AXI的数据总线宽度 一次读取的数据位数, 每次数据传输的bit位宽 parameter MEM_AWIDTH = 12 // 内存二维数组的长度len的宽度 也就是索引一共 1<<12个, BRAM size = MEM_AWIDTH*C_M_AXI_DATA_WIDTH (bits) = MEM_AWIDTH*C_M_AXI_DATA_WIDTH/8 (bytes) ) ( input wire rstn, input wire clk, // AXI-MM AW interface ---------------------------------------------------- output wire s_axi_awready, // 从S 写地址准备好接收了 input wire s_axi_awvalid, // 主准备好 input wire [ AXI_AWIDTH-1:0] s_axi_awaddr, // 传入的地址, 注意这个地址是bit, 后面计算出来通过右移得到二维数组中的索引 input wire [ 7:0] s_axi_awlen, input wire [ AXI_IDWIDTH-1:0] s_axi_awid, // AXI-MM W interface ---------------------------------------------------- output wire s_axi_wready, input wire s_axi_wvalid, input wire s_axi_wlast, input wire [ AXI_DWIDTH-1:0] s_axi_wdata, input wire [(AXI_DWIDTH/8)-1:0] s_axi_wstrb, // AXI-MM B interface ---------------------------------------------------- input wire s_axi_bready, output wire s_axi_bvalid, output wire [ AXI_IDWIDTH-1:0] s_axi_bid, output wire [ 1:0] s_axi_bresp, // AXI-MM AR interface ---------------------------------------------------- output wire s_axi_arready, // 表示S 已经可以接受 M读地址信号 input wire s_axi_arvalid, // 表示M 地址已经准备就绪 input wire [ AXI_AWIDTH-1:0] s_axi_araddr, // M 发送的地址, 这是个bit的地址, 后面通过右移计算得到索引 input wire [ 7:0] s_axi_arlen, // 突发长度 input wire [ AXI_IDWIDTH-1:0] s_axi_arid, // AXI-MM R interface ---------------------------------------------------- input wire s_axi_rready, // 只有当M ready的时候, S才可以发数据 output wire s_axi_rvalid, // S的数据已经准备好, 可以发送到M output wire s_axi_rlast, output reg [ AXI_DWIDTH-1:0] s_axi_rdata, output wire [ AXI_IDWIDTH-1:0] s_axi_rid, output wire [ 1:0] s_axi_rresp ); function automatic int log2 (input int x); int xtmp = x, y = 0; while (xtmp != 0) begin y ++; xtmp >>= 1; end return (y == 0) ? 0 : (y - 1); endfunction // --------------------------------------------------------------------------------------- // AXI READ state machine // --------------------------------------------------------------------------------------- enum reg [0:0] {R_IDLE, R_BUSY} rstate = R_IDLE; reg [AXI_IDWIDTH-1:0] rid = '0; // 从 读地址 传过来的id reg [ 7:0] rcount = '0; // 从 读地址 传过来的读取次数 reg [ MEM_AWIDTH-1:0] mem_raddr, mem_raddr_last; // 当前操作的地址, 和上次操作的地址 assign s_axi_arready = (rstate == R_IDLE); // 空闲 读地址 可以进行 接收 assign s_axi_rvalid = (rstate == R_BUSY); // 正在传送数据 读数据 以准备就绪 assign s_axi_rlast = (rstate == R_BUSY) && (rcount == 8'd0); // 是否是最后一个数据, 直接连线 assign s_axi_rid = rid; // 直接连线, 反馈给 M assign s_axi_rresp = '0; always @ (posedge clk or negedge rstn) if (~rstn) begin rstate <= R_IDLE; rid <= '0; rcount <= '0; end else begin case (rstate) R_IDLE : if (s_axi_arvalid) begin // M发起 读地址 请求 rstate <= R_BUSY; // S切换到忙碌 rid <= s_axi_arid; // 记录本次 M读地址 ID rcount <= s_axi_arlen; // 记录本次 M的读长度 end R_BUSY : if (s_axi_rready) begin if (rcount == 8'd0) // 如果是 忙碌状态, 且 是最后一个数据 rstate <= R_IDLE; // 则切换 S状态 为 空闲, 状态结束 rcount <= rcount - 8'd1; // 每次忙碌状态 rcount 都会减1 end endcase end // 组合逻辑块,根据当前状态计算内存读地址 always_comb if (rstate == R_IDLE && s_axi_arvalid) // 如果空闲状态 且 M已经准备好地址 mem_raddr = (MEM_AWIDTH)'(s_axi_araddr >> log2(AXI_DWIDTH/8)); // 计算地址, 字节对齐 //不太懂这个为啥除以8 // 因为每次数据都会 传256位数据, 这里除以8, 得到需要传的次数, 每次1字节传输, 256/8就是32次 // 看能整除多少次2, 然后 s_axi_araddr 进行右移, 移动到传过来的地址的 AXI_DWIDTH的字节边界 start起始的位置 // 将AXI_DWIDTH除以8的原因是为了从字节(byte)粒度转换到数据宽度的粒度。在很多基于AXI协议的系统中,地址通常是按字节对齐的,而数据宽度(AXI_DWIDTH)可能不是8的整数倍,比如可能是16位、32位、64位等 else if (rstate == R_BUSY && s_axi_rready) // 如果当前正在传输数据, 且 M 已经准备好 接收 S发来的数据 mem_raddr = mem_raddr_last + (MEM_AWIDTH)'(1); // 计算下一个内存地址, 加一即可, 这是一个二维数组 else mem_raddr = mem_raddr_last; // 保持地址不变 // 在每个时钟上升沿,更新上一次的读地址 always @ (posedge clk) mem_raddr_last <= mem_raddr; // --------------------------------------------------------------------------------------- // AXI WRITE state machine // --------------------------------------------------------------------------------------- enum reg [1:0] {W_IDLE, W_BUSY, W_RESP} wstate = W_IDLE; reg [AXI_IDWIDTH-1:0] wid = '0; reg [ 7:0] wcount = '0; reg [ MEM_AWIDTH-1:0] mem_waddr = '0; assign s_axi_awready = (wstate == W_IDLE); assign s_axi_wready = (wstate == W_BUSY); assign s_axi_bvalid = (wstate == W_RESP); assign s_axi_bid = wid; assign s_axi_bresp = '0; always @ (posedge clk or negedge rstn) if (~rstn) begin wstate <= W_IDLE; wid <= '0; wcount <= '0; mem_waddr <= '0; end else begin case (wstate) W_IDLE : if (s_axi_awvalid) begin wstate <= W_BUSY; wid <= s_axi_awid; wcount <= s_axi_awlen; mem_waddr <= (MEM_AWIDTH)'(s_axi_awaddr >> log2(AXI_DWIDTH/8)); end W_BUSY : if (s_axi_wvalid) begin if (wcount == 8'd0 || s_axi_wlast) wstate <= W_RESP; wcount <= wcount - 8'd1; mem_waddr <= mem_waddr + (MEM_AWIDTH)'(1); end W_RESP : if (s_axi_bready) wstate <= W_IDLE; default : wstate <= W_IDLE; endcase end // --------------------------------------------------------------------------------------- // a block RAM // --------------------------------------------------------------------------------------- reg [AXI_DWIDTH-1:0] mem [ 1<