寄存器堆设计:多端口读写的小存储器
更新时间:2026-09-12。本文是「集成电路大类 · 芯片设计」入门层(beginner)数字逻辑基础第三十二篇。前面认识了单个触发器/寄存器和计数器/移位寄存器,这一篇把一排寄存器组织成能按地址随机读写、还能多端口同时访问的小存储器——寄存器堆(register file),它是处理器核里最忙的部件,也是理解一切「阵列型存储」的起点。
本文要回答的问题
- 寄存器堆由哪三部分组成?地址怎么找到「第几个」寄存器?
- 读一个寄存器为什么是「译码 + 多路汇合」?异步读和同步读差在哪?
- 两个读端口怎么做到同一拍各读各的?写端口为什么不能随便加?
- 同一拍读和写同一个地址,读出新值还是旧值(read-first/write-first)?
- 什么时候用触发器堆,什么时候该用 SRAM/BRAM 宏?
一、三要素:阵列、读端口、写端口
最朴素的寄存器堆是「一排 D 触发器 + 地址译码 + 输出多路选择」:
- 存储阵列:N 个 W 位寄存器
regs[0..N-1],每个有自己的写使能; - 写端口:写地址经译码器产生 N 根「one-hot 写使能」,时钟沿只有被选中那个寄存器装载
wdata; - 读端口:读地址同样译码成 one-hot,用一组 W 位宽的大 MUX 从 N 个寄存器里选出对应那个送到
rdata。
写是时序的(沿上发生),读可以是组合的(地址一变,MUX 输出立刻变)。这正是「异步读寄存器堆」——经典 5 级流水线的寄存器组要求同一拍给出源操作数,用的就是组合读。
二、可综合模型:异步读、同步写
module regfile_1r1w #(
parameter W=32, N=32, AW=$clog2(N)
)(
input logic clk,
// 读端口(组合/异步读)
input logic [AW-1:0] raddr,
output logic [W-1:0] rdata,
// 写端口(同步写)
input logic we,
input logic [AW-1:0] waddr,
input logic [W-1:0] wdata
);
logic [W-1:0] regs [0:N-1];
// 读:纯组合,地址变输出立即变(FPGA 上会被推断成分布式 RAM/LUT RAM)
assign rdata = regs[raddr];
// 写:时钟沿装载,we 与译码后的写使能相与
always_ff @(posedge clk)
if (we) regs[waddr] <= wdata;
endmoduleassign rdata = regs[raddr] 这一句综合器会展开成「N 选 1」的 MUX 树;写则展开成「地址译码 + 各寄存器写使能」。这就是前面两节元件的直接拼装。
三、同步读 vs 异步读,以及同址读写
- 异步读(组合读):
assign rdata=regs[raddr],读地址改变后经 MUX 延迟输出即变,不占时钟沿。处理器要「这一拍给出 rs1/rs2 操作数」靠它;FPGA 上只能用 LUT/分布式 RAM 实现(块 RAM 不支持纯异步读)。 - 同步读(寄存读):
always_ff @(posedge clk) rdata<=regs[raddr],读地址先寄一拍、下一拍才出数。映射到 FPGA BRAM/ASIC SRAM 的天然行为,省功耗、时序好,但多一拍延迟。
同一拍又读又写同一地址有三种常见语义,必须和微结构约定一致:
| 模式 | 读出 | 典型位置 |
|---|---|---|
| READ_FIRST | 旧值(写之前) | 行为像「先读后写」 |
| WRITE_FIRST | 新值(写穿透) | 流水线希望拿到刚写的结果 |
| NO_CHANGE | 保持锁存器 | 省功耗的 RAM 模式 |
WRITE_FIRST 常额外用一个旁路:if (we && raddr==waddr) rdata=wdata;(用相等比较判同址),否则读到的是旧值。
四、多端口:读端口便宜、写端口昂贵
- 多读端口:每个读口就是「一套地址译码 + 一棵 W 位宽 MUX 树」,复制即可、互不干扰,所以 2R1W、3R 很常见(处理器要同时读两个源操作数)。阵列本身要向多个读口扇出,面积随读口数增长但规则。
- 多写端口:两个写口可能同一拍写同一寄存器,必须定义优先级/冲突处理(谁后写生效),每个寄存器的写使能变成「多口仲裁 + 或」,面积和布线代价随写口数接近平方增长。因此多数设计写口只有 1~2 个,真有多写需求(如乱序物理寄存器堆)要付出大面积,相关仲裁见 one-hot/round-robin。
五、DFF 堆、分布式 RAM、SRAM/BRAM 的分界
| 规模/读法 | 综合结果 | 说明 |
|---|---|---|
| 很小(十几个寄存器)、异步读 | 离散 D 触发器 + MUX | 灵活、可复位、可多口,最费面积 |
| 浅数组、异步读(FPGA) | 分布式 RAM(LUT 当存储) | 不占 BRAM,见 LUT 即存储 |
| 深数组、同步读 | BRAM(FPGA)/ SRAM compiler 宏(ASIC) | 面积、功耗最优,但端口数/复位受限 |
经验线:FPGA 上数组到几百 bit 且同步读写就该考虑 BRAM;ASIC 上寄存器堆由 memory compiler 或专门的 RF 编译器生成。要不要把数组推断成存储、怎么写才能稳定推断,是进阶存储推断专题,复位设计还会讲「存储阵列通常不做逐位复位」的原因。
与现有篇目的衔接
总结
一句话总结:寄存器堆 = 一排寄存器 + 地址译码产生 one-hot 写使能 + 按读地址做 N 选 1 的 MUX 汇合,写是沿上时序、读可组合可寄存;异步读(assign 索引)同拍给操作数、在 FPGA 上落分布式 RAM,同步读多一拍但天然映射 BRAM/SRAM;同址读写要约定 READ_FIRST/WRITE_FIRST/NO_CHANGE,想要新值可用「同址比较 + 写数据旁路」;多读端口只是复制译码+MUX、代价线性,多写端口会撞车、要仲裁且代价近平方,所以写口通常很少;规模从小到大依次用 DFF 堆、分布式 RAM、SRAM/BRAM 宏,按深度和读写方式让综合器推断,存储阵列一般也不做逐位复位。