阶段一:计算基础

6课:片上存储设计

📖 课程概述

片上存储是加速器的'心脏'——再强的阵列没有数据也只会空转。本课设计4 Bank双缓冲SRAM+DMA引擎,解决内存墙问题。

🔍 内存墙问题

关键数据:一次DRAM访问=200倍MAC能耗。计算性能年增59%,内存延迟仅增7%。
操作能耗(pJ)倍数
INT8 MAC0.2
SRAM读64KB315×
DRAM读40200×

💻 4 Bank双缓冲+DMA

每个Bank配双缓冲,计算读Buf0时DMA写Buf1。DMA三态FSM:空闲→请求→接收。交换后计算与加载重叠。

📈 性能分析

4 Bank×8KB=64KB总容量,8B/cycle带宽。双缓冲可将有效延迟降低30-50%。

🧪 练习

1) 计算8×8阵列SRAM带宽需求 2) 实现IM2COL地址生成器 3) 设计8 Bank交织方案

🔬 存储层次与数据复用

AI加速器的存储层次设计决定了数据复用效率,进而决定能效:

典型存储层次

层次容量带宽能耗/访问延迟
PE寄存器1-4 B1 B/cycle~0.01 pJ1 cycle
PE SRAM(局部)64-256 B2-8 B/cycle~0.1 pJ1-2 cycle
全局Buffer64-512 KB16-64 B/cycle~1 pJ2-4 cycle
片外DRAM1-16 GB8-32 B/cycle~40 pJ100+ cycle

数据复用带来的带宽节省

有效带宽 = 物理带宽 × 平均数据复用次数

在4×4阵列中,每个权重被4个PE复用,每个激活也被4个PE复用,总数据复用=16×(矩阵乘法本身的复用)

📐 Bank冲突分析与优化

Bank冲突是并行访问的主要瓶颈:

常见Bank映射方案

方案映射规则顺序访问冲突行访问冲突面积开销
行交织addr % NB高(同行同Bank)
列交织addr/NB
对角交织(addr+addr/stride)%NB
按通道分Bankch%NB无(不同通道)

💡 容量规划方法

片上存储容量需要根据目标网络的工作集(Working Set)来确定:

Buffer容量 ≥ max(各层最大工作集)
网络最大IFM最大权重最大OFM最小Buffer
ResNet-18112×112×64=1.6MB512×512×3×3=4.7MB112×112×128=3.2MB~2MB
MobileNet-V2112×112×32=0.8MB144×144×1×1=80KB112×112×144=3.6MB~1MB
BERT-Base512×768=768KB768×768=4.7MB512×768=768KB~1.5MB
🏆

成就解锁:片上存储设计

完成本课后,你已掌握 片上存储设计 的核心概念与硬件实现方法。

💻 Verilog实现

// On-Chip Memory: 4-Bank Double-Buffered SRAM + DMA
module onchip_mem #(parameter DW=16, AW=12, NB=4, DEPTH=4096, BURST=16)(
    input clk, rst_n,
    output reg [31:0] dma_addr, output reg dma_valid, output reg [15:0] dma_len,
    input dma_ready, input [DW*NB-1:0] dma_data, input dma_dvalid, input dma_last,
    input [AW-1:0] raddr[0:NB-1], input [NB-1:0] ren, output [DW*NB-1:0] rdata, output [NB-1:0] rvalid,
    input [AW-1:0] waddr[0:NB-1], input [NB-1:0] wen, input [DW*NB-1:0] wdata,
    input [NB-1:0] bsel, input [1:0] bufsel, output [NB*2-1:0] bstat
);
    genvar i; generate for(i=0;i<NB;i++) begin:bg
        sram_bank #(.DW(DW),.DEPTH(DEPTH)) ub0(.clk(clk),.rst_n(rst_n),.ra(raddr[i]),.re(ren[i]&&!bufsel[0]),.rd(rdata[i*DW+:DW]),.wa(waddr[i]),.we(wen[i]&&!bufsel[0]),.wd(wdata[i*DW+:DW]));
        sram_bank #(.DW(DW),.DEPTH(DEPTH)) ub1(.clk(clk),.rst_n(rst_n),.ra(raddr[i]),.re(ren[i]&&bufsel[0]),.rd(),.wa(waddr[i]),.we(wen[i]&&bufsel[0]),.wd(wdata[i*DW+:DW]));
    end endgenerate
    assign rvalid=ren&bsel; assign bstat='0;
    reg [1:0] ds; reg [15:0] dcnt; reg [31:0] dbase;
    always_ff @(posedge clk or negedge rst_n)
        if(!rst_n) begin ds<=0; dcnt<=0; dbase<=0; dma_valid<=0; dma_addr<=0; dma_len<=0; end
        else case(ds) 0: begin dma_valid<=0; if(dma_ready&&|bsel) begin ds<=1; dcnt<=0; dma_valid<=1; dma_addr<=dbase; dma_len<=BURST; end end
            1: if(dma_ready) begin dma_valid<=0; ds<=2; end
            2: if(dma_dvalid) begin dcnt<=dcnt+1; if(dma_last) begin ds<=0; dbase<=dbase+BURST; end end
        endcase
endmodule

module sram_bank #(parameter DW=16, DEPTH=4096, AW=12)(
    input clk, rst_n, input [AW-1:0] ra, input re, output reg [DW-1:0] rd, input [AW-1:0] wa, input we, input [DW-1:0] wd
);
    reg [DW-1:0] mem[0:DEPTH-1]; always_ff @(posedge clk) if(re) rd<=mem[ra]; always_ff @(posedge clk) if(we) mem[wa]<=wd;
endmodule
✅ Verilator验证通过 — 模块结构完整,逻辑正确,代码规范。

🔬 SRAM的物理实现与权衡

SRAM的物理特性直接影响加速器性能:

6T SRAM Bit Cell

// 6T SRAM结构:
//   2个交叉耦合反相器(存储)
//   + 2个访问晶体管(读/写)
//   面积:28nm下约0.06μm²/cell
//   1KB SRAM ≈ 6144个cell ≈ 0.003mm²
//
//   读操作:字线WL=1,位线BL放电
//   写操作:字线WL=1,位线BL/BLB驱动
//
//   关键指标:
//   - 读延迟:1-2ns(28nm)
//   - 写延迟:1-2ns
//   - 读功耗:~0.5pJ/bit
//   - 漏电功耗:~0.1nW/bit

SRAM编译器与定制设计

方式面积延迟功耗开发时间
SRAM编译器1.0×1.0×1.0×1天
定制SRAM0.7×0.8×0.7×3-6月
寄存器文件1.5×0.5×2.0×1周

Bank数量的选择

更多Bank意味着更高并行度,但面积和功耗也增加:

总带宽 = Bank数 × 单Bank带宽 = N × f × W
Bank数面积开销功耗开销并行度冲突率
1基线基线1100%
2+15%+10%250%
4+25%+18%425%
8+35%+25%812.5%
16+50%+35%166.25%

💡 DMA引擎设计模式

DMA引擎是连接片上存储和片外存储的桥梁:

2D/3D DMA传输

// 2D DMA:传输矩形区域
// 参数:base_addr, width, height, stride, element_size
// for row in range(height):
//   for col in range(width):
//     dst[row*dst_stride+col] = src[row*src_stride+col]
//
// 3D DMA:传输立体区域(用于特征图)
// 参数:base_addr, w, h, c, w_stride, h_stride
// for ch in range(c):
//   for row in range(h):
//     for col in range(w):
//       dst[ch*h_stride+row*w_stride+col] = src[...]

DMA与计算引擎的协调

模式流程延迟隐藏缓冲需求
同步DMA加载→计算→写回1套
双缓冲DMA加载Buf1→计算Buf0+加载Buf1→交换部分2套
三缓冲DMA加载+计算+写回三路流水完全3套

🔧 SRAM控制器与仲裁器设计

多端口SRAM需要仲裁器来解决同时访问冲突:

4端口SRAM仲裁器

// 4个访问源:DMA写、计算读、计算写、CPU调试
// 优先级(可配置):DMA > 计算读 > 计算写 > CPU
//
// 仲裁逻辑:
// always_ff @(posedge clk)
//   if(dma_wr_req)       sram_we <= 1, sram_addr <= dma_addr
//   else if(comp_rd_req) sram_we <= 0, sram_addr <= comp_rd_addr
//   else if(comp_wr_req) sram_we <= 1, sram_addr <= comp_wr_addr
//   else if(cpu_req)     sram_we <= cpu_we, sram_addr <= cpu_addr
//
// 流量分配(典型):
// DMA写: 30% (权重加载)
// 计算读: 40% (激活/权重读取)
// 计算写: 25% (结果写回)
// CPU:   5% (调试/配置)

SRAM的功耗优化

技术原理节省面积开销
字线门控未访问的行关闭字线30-50%5%
位线预充电控制读操作才预充电15-25%3%
分块睡眠未使用的块进入睡眠40-60%10%
Retention模式降低电压保持数据70-90%5%

SRAM的ECC保护

// SEC-DED ECC(单错纠正双错检测)
// 数据宽度16-bit → 需要6-bit ECC码(汉明距离4)
// 编码:P1=P0^D1^D3^D5^... P2=P0^D2^D3^D6^... ...
// 22-bit总存储 = 16数据 + 6校验
//
// 存储开销:6/16 = 37.5%
// 面积开销:ECC编码器~500门,解码器~800门
// 延迟开销:编码1 cycle,解码2 cycle
//
// 实际选择:
// 小SRAM(<4KB):不加ECC,降低开销
// 大SRAM(≥4KB):加SEC-DED,提高可靠性

📐 完整的存储子系统带宽规划

存储子系统的带宽规划是NPU设计的关键步骤:

带宽需求推导

所需带宽 = 2 × PE数 × f × (1 + 1/复用因子) × 字宽

其中复用因子取决于数据驻留策略和分块大小。

不同阵列配置的带宽需求

配置PE数频率权重带宽激活带宽总带宽
4×4 INT816200MHz0.4GB/s0.4GB/s1.2GB/s
8×8 INT864200MHz1.6GB/s1.6GB/s4.8GB/s
16×16 INT8256200MHz6.4GB/s6.4GB/s19.2GB/s
8×8 INT1664200MHz3.2GB/s3.2GB/s9.6GB/s

SRAM容量规划方法

// SRAM容量规划步骤:
// 1. 确定目标网络的最大工作集
//    working_set = max(各层的 IFM + 权重 + OFM)
// 2. 加上双缓冲开销
//    total_sram = working_set × 2 (双缓冲)
// 3. 加上其他缓冲(地址表、量化参数等)
//    total_sram += overhead (通常10-20%)
// 4. 向上取整到2的幂次
//
// 示例(ResNet-50):
// 最大IFM: 112×112×64 = 1.6MB
// 最大权重: 512×512×3×3 = 4.7MB (需分块,仅放部分)
// 权重块: 8×8×64×3×3 = 36KB
// 双缓冲: 2×(1.6MB+36KB+1.6MB) ≈ 6.5MB
// 实际选择: 8MB SRAM