You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

178 lines
8.0 KiB
Systemverilog

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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<<MEM_AWIDTH ]; // 这是一个二维数组, 数组中每个元素为 AXI_DWIDTH 宽, 数组长度为 MEM_AWIDTH
always @ (posedge clk)
s_axi_rdata <= mem[mem_raddr]; // 一次读出索引mem_raddr 处 AXI_DWIDTH 位的数据
always @ (posedge clk)
if (s_axi_wvalid & s_axi_wready)
for (int i=0; i<(AXI_DWIDTH/8); i++)
if (s_axi_wstrb[i])
mem[mem_waddr][i*8+:8] <= s_axi_wdata[i*8+:8]; // 每次根据地址写入8bit
endmodule