尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

芯片开发学习笔记·十七——硬件矩阵转置

芯片开发学习笔记·十七——硬件矩阵转置 矩阵转置原理1. 基本定义对于一个M × N的矩阵A其转置矩阵Aᵀ为N × M定义为Aᵀ[i][j] A[j][i] (0 ≤ i N, 0 ≤ j M)示意图原矩阵 A (3×4): 转置矩阵 Aᵀ (4×3): col0 col1 col2 col3 col0 col1 col2 row0 [ 1 2 3 4 ] row0 [ 1 5 9 ] row1 [ 5 6 7 8 ] ──▶ row1 [ 2 6 10 ] row2 [ 9 10 11 12 ] row2 [ 3 7 11 ] row3 [ 4 8 12 ]2. 存储模型与地址映射2.1 行优先存储Row-MajorC/Verilog 约定矩阵元素在内存中按行连续排列矩阵 A (3×3): 内存线性地址: ┌ a00 a01 a02 ┐ 地址: 0 1 2 3 4 5 6 7 8 │ a10 a11 a12 │ 数据: [a00][a01][a02][a10][a11][a12][a20][a21][a22] └ a20 a21 a22 ┘ 寻址公式: addr(row, col) row × COLS col2.2 列优先存储Column-MajorFortran/MATLAB 约定矩阵元素在内存中按列连续排列矩阵 A (3×3): 内存线性地址: ┌ a00 a01 a02 ┐ 地址: 0 1 2 3 4 5 6 7 8 │ a10 a11 a12 │ 数据: [a00][a10][a20][a01][a11][a21][a02][a12][a22] └ a20 a21 a22 ┘ 寻址公式: addr(row, col) col × ROWS row2.3 转置的地址本质核心结论矩阵转置 行优先地址 ↔ 列优先地址 互换写入地址行优先 wr_addr row × COLS col 读取地址列优先 rd_addr col × ROWS row 转换关系 设 wr_addr k则 row k / COLScol k % COLS 对应的 rd_addr col × ROWS row (k % COLS) × ROWS (k / COLS)3. 数学性质性质公式说明对合性(Aᵀ)ᵀ A转置两次还原加法线性(A B)ᵀ Aᵀ Bᵀ转置可分配到加法标量乘法(kA)ᵀ k·Aᵀ标量可提出乘法反转(AB)ᵀ Bᵀ·Aᵀ顺序必须反转行列式不变det(Aᵀ) det(A)仅方阵适用迹不变tr(Aᵀ) tr(A)仅方阵适用4. 硬件实现原理4.1 方案一组合逻辑直接连线重排原理纯粹的信号连线交换无需存储零延迟。原始位置 转置位置 A[0][1] ──────────▶ B[1][0] A[1][0] ──────────▶ B[0][1] A[0][2] ──────────▶ B[2][0] A[2][0] ──────────▶ B[0][2] ... ... Verilog核心: assign data_out[(j*ROWSi)*W : W] data_in[(i*COLSj)*W : W];适用场景小矩阵≤ 8×8面积换速度。4.2 方案二单 RAM 地址重映射原理先将矩阵行优先写入RAM再以列优先地址读出实现转置。写入阶段行优先 读出阶段列优先 行0: [00][01][02]...[07] 第0次读: ram[0×80]A[0][0] → 输出行0字节0 行1: [08][09][10]...[15] 第1次读: ram[1×80]A[1][0] → 输出行0字节1 行2: [16][17][18]...[23] 第2次读: ram[2×80]A[2][0] → 输出行0字节2 ... ... 行7: [56][57][58]...[63] 第7次读: ram[7×80]A[7][0] → 输出行0字节7 地址转换: wr_addr row × COLS col rd_addr col × ROWS row (k % COLS) × ROWS (k / COLS)时序图时钟: 1 2 3 ... 8 9 10 ... 16 │ │ │ │ │ │ │ 输入: ─[R0]─[R1]─[R2]──[R7]─ 无 ─ 无 ─── 无 ─ 输出: ─ 无 ─ 无 ─ 无 ── 无 ─[C0]─[C1]──[C7]─ ↑写完 ↑读完 切换读模式适用场景中大型矩阵资源节省但输入输出不能同时进行。4.3 方案三双 Buffer 乒乓流水原理两个 RAM 交替使用写 Buffer A 的同时读 Buffer B实现流水并行。┌───────────────┐ │ Buffer A │ ◀── 正在写入矩阵 N1 输入数据流 ─▶ │ (写入中) │ └───────────────┘ ↕ 写满后交换 ┌───────────────┐ │ Buffer B │ ──▶ 正在读出矩阵 N 的转置 │ (读出中) │ 输出数据流 ◀─ └───────────────┘流水时序时钟: 1 2 3 ... 8 9 10 ... 16 17 18 ... 24 │ │ │ │ │ │ │ │ │ │ 输入: R0 R1 R2 ... R7 R0 R1 ... R7 R0 R1 ... R7 └─────────────────┘ └─────────────────┘ 写入 Buffer A 写入 Buffer B 写 A 输出: C0 C1 ... C7 C0 C1 ... C7 └─────────────────┘ └─────────────────┘ 读出 Buffer B (转置A) 读出 A (转置B) 等效吞吐率: ≈ 100% 输入输出几乎同步进行适用场景流式连续数据神经网络加速器、视频处理。verilog代码参考如下// // 按字节矩阵转置模块 - 双Buffer乒乓流水处理 // // 接口信号: // clk, rst_n - 时钟和复位 // data_i_vld - 输入数据有效 (一行) // data_i_rdy - 输入准备好接收 // data_i [COLS*8-1:0] - 输入一行数据 (COLS个字节) // data_o_vld - 输出数据有效 (一行) // data_o_rdy - 输出准备好接收 // data_o [ROWS*8-1:0] - 输出一行数据 (ROWS个字节) // module byte_matrix_transpose #( parameter ROWS 8, // 矩阵行数 parameter COLS 8, // 矩阵列数 parameter BYTE_WIDTH 8 // 字节位宽 )( input wire clk, input wire rst_n, // 输入接口 (每行数据) input wire data_i_vld, output wire data_i_rdy, input wire [COLS*BYTE_WIDTH-1:0] data_i, // 输出接口 (每行数据 - 转置后的列) output wire data_o_vld, input wire data_o_rdy, output wire [ROWS*BYTE_WIDTH-1:0] data_o ); integer c; localparam TOTAL ROWS * COLS; localparam ADDR_W $clog2(TOTAL); // // 双Buffer RAM (乒乓操作) // Buffer 0: 当前写入 Buffer 1 // Buffer 1: 当前读取 Buffer 0 // 读写完成后交换 // (* ram_style block *) reg [BYTE_WIDTH-1:0] ram_0 [0:TOTAL-1]; (* ram_style block *) reg [BYTE_WIDTH-1:0] ram_1 [0:TOTAL-1]; // // 状态机 // localparam S_IDLE 3b000; localparam S_WRITE0 3b001; // 写入Buffer 0 localparam S_WRITE1 3b010; // 写入Buffer 1 localparam S_READ0 3b011; // 读取Buffer 0 localparam S_READ1 3b100; // 读取Buffer 1 localparam S_WAIT0 3b101; // 等待Buffer 0可写/读 localparam S_WAIT1 3b111; // 等待Buffer 1可写/读 reg [2:0] c_state_w; reg [2:0] c_state_r; reg buf_sel;// 0: 使用Buffer0, 1: 使用Buffer1 wire data_i_hs; reg data_i_hs_d1; wire data_o_hs; reg [ADDR_W-1:0] row_cnt_0; // 行计数器 reg [ADDR_W-1:0] row_cnt_1; // 行计数器 reg [ADDR_W-1:0] col_cnt_0; // 行计数器 reg [ADDR_W-1:0] col_cnt_1; // 行计数器 integer idx; wire [ROWS*BYTE_WIDTH-1:0] data_out_0; wire [ROWS*BYTE_WIDTH-1:0] data_out_1; // // 写操作: 写入当前选中的Buffer // always (posedge clk or negedge rst_n) begin if (!rst_n) c_state_w S_IDLE; else begin case(c_state_w) S_IDLE : begin if(data_i_vld (c_state_r ! S_READ0)) // 开始写入Buffer 0 c_state_w S_WRITE0; end S_WRITE0 : begin if(data_i_vld (row_cnt_0 ROWS)) begin if((c_state_r S_READ1) (col_cnt_1 COLS-1)) c_state_w S_WAIT1; //写完了Buffer 0但没有读完Buffer 1需要等待 else c_state_w S_WRITE1; //写完Buffer 0没有读Buffer 1操作有数据输入则切换到写Buffer 1 end else if(row_cnt_0 ROWS) c_state_w S_IDLE; //写完Buffer 0没有数据输入则回IDLE end S_WRITE1 : begin if(data_i_vld (row_cnt_1 ROWS)) begin if((c_state_r S_READ0) (col_cnt_0 COLS-1)) c_state_w S_WAIT0; //写完了Buffer 1但没有读完Buffer 0需要等待 else c_state_w S_WRITE0; //写完Buffer 1没有读Buffer 0操作有数据输入则切换到写Buffer 0 end else if(row_cnt_1 ROWS) c_state_w S_IDLE; //写完Buffer 1没有数据输入则回IDLE end S_WAIT0 : begin if((c_state_r S_READ0) (col_cnt_0 COLS-1)) c_state_w S_WRITE0; end S_WAIT1 : begin if((c_state_r S_READ1) (col_cnt_1 COLS-1)) c_state_w S_WRITE1; end endcase end end assign data_i_hs data_i_vld data_i_rdy; always (posedge clk or negedge rst_n) begin if (!rst_n) data_i_hs_d1 1b0; else data_i_hs_d1 data_i_hs; end always (posedge clk or negedge rst_n) begin if (!rst_n) row_cnt_0 1b1; else if((c_state_w S_WRITE0) (row_cnt_0 ROWS)) row_cnt_0 1b1; else if((c_state_w S_WRITE0) data_i_hs) row_cnt_0 row_cnt_0 1b1; end always (posedge clk or negedge rst_n) begin if (!rst_n) row_cnt_1 1b1; else if((c_state_w S_WRITE1) (row_cnt_1 ROWS)) row_cnt_1 1b1; else if((c_state_w S_WRITE1) data_i_hs) row_cnt_1 row_cnt_1 1b1; end always (posedge clk or negedge rst_n) begin if (data_i_hs (c_state_w S_IDLE)) begin // 写入第0行 for (c 0; c COLS; c c 1) begin ram_0[c] data_i[c*BYTE_WIDTH : BYTE_WIDTH]; end end else if(data_i_hs (((c_state_w S_WRITE0) (row_cnt_0 ROWS)) || ((c_state_w S_WRITE1) (row_cnt_1 ROWS)))) // 写入Buffer 0的当前行 for (c 0; c COLS; c c 1) begin ram_0[row_cnt_0 * COLS c] data_i[c*BYTE_WIDTH : BYTE_WIDTH]; end end always (posedge clk or negedge rst_n) begin if (data_i_hs ((c_state_w S_WRITE0) (row_cnt_0 ROWS)))begin // 写入第0行 for (c 0; c COLS; c c 1) begin ram_1[c] data_i[c*BYTE_WIDTH : BYTE_WIDTH]; end end else if (data_i_hs ((c_state_w S_WRITE1) (row_cnt_1 ROWS))) // 写入Buffer 1的当前行 for (c 0; c COLS; c c 1) begin ram_1[row_cnt_1 * COLS c] data_i[c*BYTE_WIDTH : BYTE_WIDTH]; end end // // 读操作: 从当前选中的Buffer读取 // always (posedge clk or negedge rst_n) begin if (!rst_n) c_state_r S_IDLE; else begin case(c_state_r) S_IDLE : begin if((c_state_w S_WRITE0) (row_cnt_0 ROWS) data_o_rdy) c_state_r S_READ0; end S_READ0 : begin if(data_o_rdy (col_cnt_0 COLS-1)) begin if((c_state_w S_WRITE1) (row_cnt_1 ROWS)) c_state_r S_WAIT1; //读完Buffer 0但没有写完BUffer 1需等待 else if(c_state_w S_IDLE) c_state_r S_IDLE; //读完Buffer 0没有后续写数据回IDLE else c_state_r S_READ1; //读完Buffer 0没有写Buffer 1则切换到读Buffer 1 end end S_READ1 : begin if(data_o_rdy (col_cnt_1 COLS-1)) begin if((c_state_w S_WRITE0) (row_cnt_0 ROWS)) c_state_r S_WAIT0; //读完Buffer 1但没有写完BUffer 0需等待 else if(c_state_w S_IDLE) c_state_r S_IDLE; //读完Buffer 1没有后续写数据回IDLE else c_state_r S_READ0; //读完Buffer 1没有写Buffer 0则切换到读Buffer 0 end end S_WAIT0 : begin if((c_state_w S_WRITE0) (row_cnt_0 ROWS)) c_state_r S_READ0; end S_WAIT1 : begin if((c_state_w S_WRITE1) (row_cnt_1 ROWS)) c_state_r S_READ1; end endcase end end assign data_o_hs data_o_rdy data_o_vld; always (posedge clk or negedge rst_n) begin if (!rst_n) col_cnt_0 {ADDR_W{1b0}}; else if((c_state_r S_READ0) (col_cnt_0 COLS-1)) col_cnt_0 {ADDR_W{1b0}}; else if((c_state_r S_READ0) data_o_hs) col_cnt_0 col_cnt_0 1b1; end always (posedge clk or negedge rst_n) begin if (!rst_n) col_cnt_1 {ADDR_W{1b0}}; else if((c_state_r S_READ1) (col_cnt_1 COLS-1)) col_cnt_1 {ADDR_W{1b0}}; else if((c_state_r S_READ1) data_o_hs) col_cnt_1 col_cnt_1 1b1; end genvar i; generate for (i 0; i ROWS; i i 1) begin : GEN_READ_DATA assign data_out_0[i*BYTE_WIDTH : BYTE_WIDTH] ram_0[i * COLS col_cnt_0]; assign data_out_1[i*BYTE_WIDTH : BYTE_WIDTH] ram_1[i * COLS col_cnt_1]; end endgenerate // // 接口信号 // // 输入就绪 assign data_i_rdy (c_state_w S_WRITE0) || (c_state_w S_WRITE1) || ((c_state_w S_IDLE) (c_state_r ! S_READ0)); // 输出有效 assign data_o_vld (c_state_r S_READ0) || (c_state_r S_READ1); // 输出数据选择 assign data_o (c_state_r S_READ0) ? data_out_0 : data_out_1; endmodule上面的代码的buffer是用寄存器搭建的每个buffer粒度为BYTE_WIDTH需要2 * ROWS * COLS个这样的寄存器才能实现。如果矩阵大小较大的时候那需要调用的寄存器就很多对面积不友好。在7nm的工艺下超过1KB大小的memory用寄存器搭建的mem 比 调用RAM库的mem 面积还要大所以尽量少使用小粒度的mem。如果功能需要可以用其他方式合并成大的mem调用SRAM库。【转置实现代码优化】下面的代码是把矩阵切分存放到不同的mem bank中通过移位后再写入mem bank可以实现同时读不同bank但属于同一列的数值来实现转置。// // 按字节矩阵转置模块 - 双Buffer乒乓流水处理 // 把mem切分成多个bank每个mem bank为ROWS*BYTE_WIDTH的大小利用 // 错位读写可以同时都多个bank的数据来组成一列的数值实现转置 // ***reg [BYTE_WIDTH-1:0] ram [0:BANK_NUM-1][0:ROWS-1]*** // // 接口信号: // clk, rst_n - 时钟和复位 // data_i_vld - 输入数据有效 (一行) // data_i_rdy - 输入准备好接收 // data_i [COLS*8-1:0] - 输入一行数据 (COLS个字节) // data_o_vld - 输出数据有效 (一行) // data_o_rdy - 输出准备好接收 // data_o [ROWS*8-1:0] - 输出一行数据 (ROWS个字节) // module byte_matrix_transpose_bank #( parameter ROWS 8, // 矩阵行数 parameter COLS 8, // 矩阵列数 parameter BYTE_WIDTH 8 // 字节位宽 )( input wire clk, input wire rst_n, // 输入接口 (每行数据) input wire data_i_vld, output wire data_i_rdy, input wire [COLS*BYTE_WIDTH-1:0] data_i, // 输出接口 (每行数据 - 转置后的列) output wire data_o_vld, input wire data_o_rdy, output wire [ROWS*BYTE_WIDTH-1:0] data_o ); integer c; localparam TOTAL ROWS * COLS; localparam BANK_NUM (ROWSCOLS) ? ROWS : COLS; localparam ROW_W $clog2(ROWS); localparam COL_W $clog2(COLS); // // 双Buffer RAM (乒乓操作) // Buffer 0: 当前写入 Buffer 1 // Buffer 1: 当前读取 Buffer 0 // 读写完成后交换 // (* ram_style block *) reg [BYTE_WIDTH-1:0] ram_0 [0:BANK_NUM-1][0:ROWS-1]; (* ram_style block *) reg [BYTE_WIDTH-1:0] ram_1 [0:BANK_NUM-1][0:ROWS-1]; // // 状态机 // localparam S_IDLE 3b000; localparam S_WRITE0 3b001; // 写入Buffer 0 localparam S_WRITE1 3b010; // 写入Buffer 1 localparam S_READ0 3b011; // 读取Buffer 0 localparam S_READ1 3b100; // 读取Buffer 1 localparam S_WAIT0 3b101; // 等待Buffer 0可写/读 localparam S_WAIT1 3b111; // 等待Buffer 1可写/读 reg [2:0] c_state_w; reg [2:0] c_state_r; reg buf_sel;// 0: 使用Buffer0, 1: 使用Buffer1 wire data_i_hs; reg data_i_hs_d1; wire data_o_hs; reg [ROW_W:0] row_cnt_0; // 行计数器 reg [ROW_W:0] row_cnt_1; // 行计数器 reg [COL_W-1:0] col_cnt_0; // 列计数器 reg [COL_W-1:0] col_cnt_1; // 列计数器 wire [BANK_NUM*BYTE_WIDTH*2-1:0] data_i_combine; integer idx; wire [ROWS*BYTE_WIDTH-1:0] data_out_0; wire [ROWS*BYTE_WIDTH-1:0] data_out_1; // // 写操作: 写入当前选中的Buffer // always (posedge clk or negedge rst_n) begin if (!rst_n) c_state_w S_IDLE; else begin case(c_state_w) S_IDLE : begin if(data_i_vld (c_state_r ! S_READ0)) // 开始写入Buffer 0 c_state_w S_WRITE0; end S_WRITE0 : begin if(data_i_vld (row_cnt_0 ROWS)) begin if((c_state_r S_READ1) (col_cnt_1 COLS-1)) c_state_w S_WAIT1; //写完了Buffer 0但没有读完Buffer 1需要等待 else c_state_w S_WRITE1; //写完Buffer 0没有读Buffer 1操作有数据输入则切换到写Buffer 1 end else if(row_cnt_0 ROWS) c_state_w S_IDLE; //写完Buffer 0没有数据输入则回IDLE end S_WRITE1 : begin if(data_i_vld (row_cnt_1 ROWS)) begin if((c_state_r S_READ0) (col_cnt_0 COLS-1)) c_state_w S_WAIT0; //写完了Buffer 1但没有读完Buffer 0需要等待 else c_state_w S_WRITE0; //写完Buffer 1没有读Buffer 0操作有数据输入则切换到写Buffer 0 end else if(row_cnt_1 ROWS) c_state_w S_IDLE; //写完Buffer 1没有数据输入则回IDLE end S_WAIT0 : begin if((c_state_r S_READ0) (col_cnt_0 COLS-1)) c_state_w S_WRITE0; end S_WAIT1 : begin if((c_state_r S_READ1) (col_cnt_1 COLS-1)) c_state_w S_WRITE1; end endcase end end assign data_i_hs data_i_vld data_i_rdy; always (posedge clk or negedge rst_n) begin if (!rst_n) data_i_hs_d1 1b0; else data_i_hs_d1 data_i_hs; end always (posedge clk or negedge rst_n) begin if (!rst_n) row_cnt_0 1b1; else if((c_state_w S_WRITE0) (row_cnt_0 ROWS)) row_cnt_0 1b1; else if((c_state_w S_WRITE0) data_i_hs) row_cnt_0 row_cnt_0 1b1; end always (posedge clk or negedge rst_n) begin if (!rst_n) row_cnt_1 1b1; else if((c_state_w S_WRITE1) (row_cnt_1 ROWS)) row_cnt_1 1b1; else if((c_state_w S_WRITE1) data_i_hs) row_cnt_1 row_cnt_1 1b1; end assign data_i_combine {{(BYTE_WIDTH*(BANK_NUM-COLS)){1b0}},data_i,{(BYTE_WIDTH*(BANK_NUM-COLS)){1b0}},data_i}; always (posedge clk or negedge rst_n) begin if (data_i_hs (c_state_w S_IDLE)) begin // 写入第0行 for (c 0; c BANK_NUM; c c 1) begin ram_0[c][0] data_i_combine[(c*BYTE_WIDTH BANK_NUM*BYTE_WIDTH) : BYTE_WIDTH]; end end else if(data_i_hs (((c_state_w S_WRITE0) (row_cnt_0 ROWS)) || ((c_state_w S_WRITE1) (row_cnt_1 ROWS)))) // 写入Buffer 0的当前行 for (c 0; c BANK_NUM; c c 1) begin ram_0[c][row_cnt_0] data_i_combine[(c*BYTE_WIDTH BANK_NUM*BYTE_WIDTH - row_cnt_0*BYTE_WIDTH) : BYTE_WIDTH]; end end always (posedge clk or negedge rst_n) begin if (data_i_hs ((c_state_w S_WRITE0) (row_cnt_0 ROWS)))begin // 写入第0行 for (c 0; c BANK_NUM; c c 1) begin ram_1[c][0] data_i_combine[(c*BYTE_WIDTH BANK_NUM*BYTE_WIDTH) : BYTE_WIDTH]; end end else if (data_i_hs ((c_state_w S_WRITE1) (row_cnt_1 ROWS))) // 写入Buffer 1的当前行 for (c 0; c BANK_NUM; c c 1) begin ram_1[c][row_cnt_1] data_i_combine[(c*BYTE_WIDTH BANK_NUM*BYTE_WIDTH - row_cnt_1*BYTE_WIDTH) : BYTE_WIDTH]; end end // // 读操作: 从当前选中的Buffer读取 // always (posedge clk or negedge rst_n) begin if (!rst_n) c_state_r S_IDLE; else begin case(c_state_r) S_IDLE : begin if((c_state_w S_WRITE0) (row_cnt_0 ROWS) data_o_rdy) c_state_r S_READ0; end S_READ0 : begin if(data_o_rdy (col_cnt_0 COLS-1)) begin if((c_state_w S_WRITE1) (row_cnt_1 ROWS)) c_state_r S_WAIT1; //读完Buffer 0但没有写完BUffer 1需等待 else if(c_state_w S_IDLE) c_state_r S_IDLE; //读完Buffer 0没有后续写数据回IDLE else c_state_r S_READ1; //读完Buffer 0没有写Buffer 1则切换到读Buffer 1 end end S_READ1 : begin if(data_o_rdy (col_cnt_1 COLS-1)) begin if((c_state_w S_WRITE0) (row_cnt_0 ROWS)) c_state_r S_WAIT0; //读完Buffer 1但没有写完BUffer 0需等待 else if(c_state_w S_IDLE) c_state_r S_IDLE; //读完Buffer 1没有后续写数据回IDLE else c_state_r S_READ0; //读完Buffer 1没有写Buffer 0则切换到读Buffer 0 end end S_WAIT0 : begin if((c_state_w S_WRITE0) (row_cnt_0 ROWS)) c_state_r S_READ0; end S_WAIT1 : begin if((c_state_w S_WRITE1) (row_cnt_1 ROWS)) c_state_r S_READ1; end endcase end end assign data_o_hs data_o_rdy data_o_vld; always (posedge clk or negedge rst_n) begin if (!rst_n) col_cnt_0 {COL_W{1b0}}; else if((c_state_r S_READ0) (col_cnt_0 COLS-1)) col_cnt_0 {COL_W{1b0}}; else if((c_state_r S_READ0) data_o_hs) col_cnt_0 col_cnt_0 1b1; end always (posedge clk or negedge rst_n) begin if (!rst_n) col_cnt_1 {COL_W{1b0}}; else if((c_state_r S_READ1) (col_cnt_1 COLS-1)) col_cnt_1 {COL_W{1b0}}; else if((c_state_r S_READ1) data_o_hs) col_cnt_1 col_cnt_1 1b1; end genvar i; generate for (i 0; i ROWS; i i 1) begin : GEN_READ_DATA assign data_out_0[i*BYTE_WIDTH : BYTE_WIDTH] ram_0[(col_cnt_0 i)%BANK_NUM][i]; assign data_out_1[i*BYTE_WIDTH : BYTE_WIDTH] ram_1[(col_cnt_1 i)%BANK_NUM][i]; end endgenerate // // 接口信号 // // 输入就绪 assign data_i_rdy (c_state_w S_WRITE0) || (c_state_w S_WRITE1) || ((c_state_w S_IDLE) (c_state_r ! S_READ0)); // 输出有效 assign data_o_vld (c_state_r S_READ0) || (c_state_r S_READ1); // 输出数据选择 assign data_o (c_state_r S_READ0) ? data_out_0 : data_out_1; endmodule5. 三种方案综合对比组合逻辑单Buffer RAM双Buffer乒乓延迟周期0N行N行首帧吞吐率100%~50%~100%RAM资源无1×(M×N)2×(M×N)逻辑资源高O(M×N×W)极低极低时序约束难大矩阵易易最大矩阵规模≤ 8×8任意任意流式处理支持否否✔ 是连续数据处理否否✔ 是6. 地址映射计算示例8×8矩阵以 8×8 矩阵为例展示地址映射全表部分线性地址k → (row, col) → 转置后地址 col×8row k0 → (0,0) → rd0×80 0 k1 → (0,1) → rd1×80 8 k2 → (0,2) → rd2×80 16 k3 → (0,3) → rd3×80 24 k8 → (1,0) → rd0×81 1 k9 → (1,1) → rd1×81 9 k10 → (1,2) → rd2×81 17 k63 → (7,7) → rd7×87 63Verilog 地址转换// 读地址 rd_addr 对应的转置写地址 wire [ADDR_W-1:0] trans_addr; assign trans_addr (rd_addr % COLS) * ROWS (rd_addr / COLS);7. 分块转置大矩阵优化当矩阵超过片上 RAM 容量时采用**分块Tiling**策略大矩阵 (M×N): ┌────┬────┬────┐ │ T00│ T01│ T02│ 每个 Tij 是一个小块如 8×8 ├────┼────┼────┤ │ T10│ T11│ T12│ 转置策略 ├────┼────┼────┤ 1. 对每个块内部转置 │ T20│ T21│ T22│ 2. 同时交换块的位置Tij ↔ Tji └────┴────┴────┘参考矩阵转置地址公式rd_addr (wr_addr % COLS) × ROWS (wr_addr / COLS)双Buffer乒乓写满后切换读写角色实现流水并行分块转置适用于超大矩阵减少片外存储访问次数
返回列表