56 / 63 · 概念
两读一写寄存器文件与旁路
实现两个独立读端口与同步写入,检查各地址冲突下的旁路及初始化、valid 策略。
课时内容可免费阅读,选课后可保存学习进度。
同时取出两个值的存储结构
运算单元计算 A+B 时,需要在同一周期取得两个操作数。两读一写寄存器寄存器 存储多位状态的电路。本课程中的同步寄存器在时钟边沿存储指定输入。了解更多文件提供两个读地址和一个写地址。本例存储八个十六位字,两个读取结果也在上升沿寄存。地址宽度为三位,存储量为 8×16=128 位。地址 0 同样是普通存储,不具有特殊常量寄存器规则。
每个读端口有独立 enable 与 valid,一个端口空闲时另一个仍可读,两个端口也可读取同一地址。同时读取两个不同地址的能力,带来的成本不同于单纯增加容量。小规模电路可由触发器触发器 在指定时钟边沿存储输入,并保持到下一边沿的元件。输入必须满足 setup、hold 条件。了解更多数组与两套选择网络实现;采用特定 RAM 时,也可考虑复制存储体并同时广播写入,以获得足够读端口。仅凭 RTL 声明无法确定最终综合资源。
正在写入的值也是当前读取的值时
本约定要求同一边沿读写同址时,返回新的写数据。因此设置 bypass,将 wr_data 直接送到读输出的输入端,无需等待数组更新。端口 的冲突条件定义如下。
时,响应为:
是边沿前的存储值。 对各读地址独立比较。不能因为一个端口冲突就向两个输出都传写数据,否则另一个地址的操作数也被改变。若漏掉 wr_en,实际没有写入时,也会因 wr_addr 恰好相同而错误旁路。
module regfile8x16_2r1w (
input logic clk, rst, wr_en,
input logic [2:0] wr_addr,
input logic [15:0] wr_data,
input logic rd_en0, rd_en1,
input logic [2:0] rd_addr0, rd_addr1,
output logic [15:0] rd_data0, rd_data1,
output logic rd_valid0, rd_valid1
);
timeunit 1ns;
timeprecision 1ps;
logic [15:0] mem [0:7];
always_ff @(posedge clk) begin
if (rst) begin
rd_valid0 <= 1'b0;
rd_valid1 <= 1'b0;
end else begin
rd_valid0 <= rd_en0;
rd_valid1 <= rd_en1;
if (wr_en)
mem[wr_addr] <= wr_data;
if (rd_en0)
rd_data0 <= (wr_en && wr_addr == rd_addr0)
? wr_data : mem[rd_addr0];
if (rd_en1)
rd_data1 <= (wr_en && wr_addr == rd_addr1)
? wr_data : mem[rd_addr1];
end
end
endmodule这条旁路改变输入选择,却不消除输出寄存级。地址和请求在边沿前给出,响应在该边沿后观察,下游寄存器在下一个上升沿接收。若改为组合读取寄存器文件,地址到输出的传播路径与延迟约定都会改变,不能把它当作简单代码精简。
示例:只有一个端口冲突
假设此前已写入 M[2]=12、M[5]=7。E0 前打开两个读 enable,设置 rd_addr0=2、rd_addr1=5、wr_en=1、wr_addr=2、wr_data=20。E0 后,端口 0 返回旁路的 20,端口 1 返回已存的 7。这两个有效响应相加为 27,不同于无旁路 read-first 实现得到的 19。
E1 将两个读地址都设为 2,同时向该地址写入 30,两个输出都为 30。E2 设置 wr_en=0、wr_addr=2、wr_data=99,并读取地址 2,两个输出都必须为 30。这个周期就能暴露只看地址相等就旁路的错误。E3 若 rd_en0=0、rd_en1=1,则端口 0 的 valid 为 0,端口 1 的 valid 为 1。
每列记录一个上升沿上升沿 时钟从 0 变为 1 的瞬间。它不同于表示整个 CLK=1 区间的电平。了解更多的观察结果。输入取自边沿前,名称带 after 的状态取自更新后。列的排列表示采样顺序,并非描绘物理传播延迟传播延迟 从输入变化到输出稳定为正确值所需的时间。逻辑表达式等价与时间特性是两回事。了解更多。预先存有 M[2]=12、M[5]=7,两个读 enable 始终为 1,写地址为 2。最后一列 wr_en=0,不能旁路 wr_data=99。
查看波形数据
| 信号 | 波形 | 总线值 |
|---|---|---|
| edge | 234 | E0 → E1 → E2 |
| wr_en | 1.0 | |
| wr_data | 234 | 20 → 30 → 99 |
| rd_addr0 | 2.. | 2 |
| rd_addr1 | 23. | 5 → 2 |
| out0 after | 23. | 20 → 30 |
| out1 after | 23. | 7 → 30 |
哪些内容需要初始化
同步高有效 reset 阻止写入,并把两个 valid 清零,不复位数组与输出数据。因此复位后输出总线保留旧值,只要 valid=0 就属正常。数组中已存内容不会被该 RTL 的 reset 清除;若系统只允许复位后的新任务,就需另行规定这些任务的初始化顺序。
首次写入的地址若在同一边沿被读取,本实现可通过旁路给出明确新值。但不写入而读取从未初始化的地址,结果仍未定义。rd_valid 只标记请求响应时刻,并不是逐地址 initialized 位。验证器在生成普通读取的预期值前,应检查该地址是否已知,并单独处理同边沿旁路情况。
验证项包括:两个读取 enable 的四种组合,读地址相同或不同,与写入冲突的端口为无、0、1 或两个,以及连续写入和 reset 优先级。多写端口超出本课范围;若增加,首先要规定两个写入同址时的优先级。端口数改变,会同时改变功能约定与资源成本。
自己试试
已知 M[1]=4、M[4]=9。两个读 enable 均为 1,读地址分别是 1 和 4,同时向地址 4 写入 13,两个输出分别是什么?下一边沿设置 wr_en=0、wr_addr=1、wr_data=99,两个读地址均为 1,结果又是什么?最后只将端口 0 的 enable 置 0,说明 valid 与数据消费规则。
阅读解释
第一组响应为 (4,13),因为只有端口 1 与写地址冲突,旁路新值 13。下一组为 (4,4);wr_en=0,因此不使用 99。最后一周期 rd_valid0=0、rd_valid1=1。端口 0 的数据寄存器保留旧值,但不能作为有效响应消费。地址 0 也与其他地址一样可以读写。