第 28 环 · 权重内存化(整网层权重从 SRAM 流式取)
还是同一个圆心:一台开机只有一个 AI 的手机。上一环把「启动整张网」收成了一条 NPU 指令,可层权重还住在寄存器里:开机时核得把每层 4 张投影矩阵(Wq / Wq2 / Wk / Wv)、FFN 两层、LayerNorm 的 gamma/beta 一个 32bit 一个字地 sw 进 ql_w / net_wei 寄存器堆——权重永远占着片内那块小寄存器,也永远长不大。
真芯片不是这样:权重必须住在内存(几 MB 到几 GB),推理时按层流式取进片上小缓冲,用完就走。这一环把层权重搬进权重 SRAM,让整网引擎每层开跑前自己从内存取——软硬协同里的「内存带宽优先于峰值算力」,第一次落到权重上。
· 权重有地方住:第 3 圈那块权重 SRAM 从 16 字扩到 64 字(0x100~0x1FF 原地址区间,地址映射一个字没动),按「每层 16 字」排布:0/1=Wq、2/3=Wq2、4/5=Wk、6/7=Wv、8/9=W1、10/11=W2、12=gamma、13=beta,层基址 = 层号 × 16 —— 2 层共 32 字;
· 引擎自己取:layer_net 新增一条权重流式取通路(w_addr / w_rd_en / w_rd_data / w_rd_valid)与两个状态 ST_WLD → ST_WLDR:每层开跑前逐字取 16 个字,落进片内「本层权重」小寄存器,取满一层才 ST_PROJ;跑完下一层再取一遍;
· 只扩不推翻:w_src 选择权重来源——1 = 从 SRAM 流式取(本环新增、主路默认),0 = 前 26 圈的寄存器驻留路径(一个字没删、单测照跑);数据面(整网计算)一个门都没动;
· 顺手修掉一颗旧钉子:SRAM 的写译码原先只挡了 bit9、没挡 bit10 —— 写 0x500 页(输出投影页)的 WO_X0 会静默改掉 SRAM 第 0 字。权重真住进内存后这颗钉子会直接毁模型,本环把 bit10 一并挡掉;
· 两个探针:0x2C8 NET_W_CNT(这趟流式取了多少字)、0x2CC NET_W_FIRST(取到的第一个字)、0x2D0 NET_W_DONE,把「权重真从内存来」钉在寄存器上。
rtl/layer_net.sv 新增端口与 WLOAD 状态(每层从内存流式取 16 字):
// layer_net:每层开跑前从 SRAM 流式取权重
ST_WLD: begin w_rd_en_r <= 1'b1;
w_addr_r <= (li * WS) + wcnt; // 层基址 + 层内序号
st <= ST_WLDR; end
ST_WLDR: if (w_rd_valid) begin // 同步读:下一拍数据回来
case (wcnt)
0: wq_r[31:0] <= w_rd_data; // 0/1=Wq
1: wq_r[63:32] <= w_rd_data;
... // 2/3=Wq2 … 12=gamma 13=beta
endcase
wtot <= wtot + 1; // 探针:累计取字数
if (wcnt == WS-1) st <= ST_PROJ; // 一层取满 → 开跑本层
else st <= ST_WLD;
end
rtl/min_bus.sv 扩 SRAM 并把它接到引擎,同时修掉 bit10 写穿透:
sram_cache #(.DEPTH(64), .DATA_W(32), .ADDR_W(6)) weight_sram (
.wr_en(dmem_wen && dmem_addr[8] && !dmem_addr[9] && !dmem_addr[10]),
.wr_addr(dmem_addr[7:2]), ... );
// 读端口仲裁:整网引擎的流式取优先,其次第 3 圈的老装载 FSM
assign sram_rd_en = net_w_rd_en | (ld_state == LD_RD);
assign sram_rd_addr = net_w_rd_en ? net_w_addr : 6'd0;
OS 只多两件事:开机时把每层权重也写进 SRAM(sw 到 0x100 + 层号×64 + 字号×4),然后 sw 1, 0x2C4(NET_W_SRC=1)——此后核不再碰权重寄存器。
单元仿真 tb_layer_net28(引擎 + 行为级权重 SRAM 模型,4 组用例):寄存器路径 y=0xFA03FC07、探针 w_cnt=0;复位后内存路径 同一个 y=0xFA03FC07(逐位一致)、w_cnt=32(2 层×16 字)、首字 = wmem[0]、w_ld_done=1;把 SRAM 里层 1 的 FFN W2 改成单位阵 → 内存路径变 0xF703F90A、寄存器路径仍 0xFA03FC07。
协同仿真 tb_min_soc28(整机:核 → OS → 解码循环 → 收尾八项试验):
| 探针 / 断言 | 结果 | 说明 |
|---|---|---|
| NET_W_CNT | 32 | 每层 16 字 × 2 层,真从 SRAM 流式取 |
| NET_W_FIRST | 0x01110F01 | 取到的第一个字 = 层 0 的 Wq 低 32bit |
| NET_W_SRC=0 时 NET_W_CNT | 0 | 寄存器路径一个字都不从内存取 |
| 内存路径 vs 寄存器路径 | 逐位一致 | 同一份计算,只是权重住在哪 |
| 改 SRAM 层1 的 W2 → 内存路径 | [10,-7,3,-9] | 内存里的权重真在算 |
| 同一改动下的寄存器路径 | [7,-4,3,-6] 不变 | 两个来源确实不同 |
SCREEN2 = 0x02040404(整网 digest,同第 26/27 环)、SCREEN3 = 0x04000204(ISA 契约 digest,同第 27 环)、SCREEN4 = 0x20010707(权重内存化 digest:流式取 32 字 | 首字低 8bit | 内存路径 lane0 | 寄存器路径 lane0 —— 最后两位相等正是「两条权重来源逐位一致」)。全量回归 41 项(新增 layer_net28 与 min_soc28)全部通过。
这一环之后,芯片上的权重第一次住在内存里:核不再需要把整张网的权重一个个写进寄存器堆,开机只需把权重摆到内存、把引擎的权重来源拨到内存,剩下的「取谁、什么时候取、取到哪」全由硬件按层自己做。
对「开机只有一个 AI」的手机来说,这是能装下真模型的前提——真模型的权重是几亿到几十亿个数,寄存器装不下,内存装得下;权重住进内存,片内小缓冲只当「本层工作台」,这条路才通到能跑真 DeepSeek 的那一天。
权重预取双缓冲 · 算本层时先取下一层(取数不等总线)。现在每层是「先停下来取 16 字,取满了才算」——取数是串行的,总线一慢引擎就空转。下一环给权重通路加双缓冲预取:跑本层的同时,硬件用空闲的读周期把下一层的权重取进第二块缓冲,层切换时直接换台,引擎不再等内存。