Altifigence Academy

56 / 63 · 概念

两读一写寄存器文件与旁路

实现两个独立读端口与同步写入,检查各地址冲突下的旁路及初始化、valid 策略。

同时取出两个值的存储结构

运算单元计算 A+B 时,需要在同一周期取得两个操作数。两读一写寄存器寄存器 存储多位状态的电路。本课程中的同步寄存器在时钟边沿存储指定输入。了解更多文件提供两个读地址和一个写地址。本例存储八个十六位字,两个读取结果也在上升沿寄存。地址宽度为三位,存储量为 8×16=128 位。地址 0 同样是普通存储,不具有特殊常量寄存器规则。

每个读端口有独立 enable 与 valid,一个端口空闲时另一个仍可读,两个端口也可读取同一地址。同时读取两个不同地址的能力,带来的成本不同于单纯增加容量。小规模电路可由触发器触发器 在指定时钟边沿存储输入,并保持到下一边沿的元件。输入必须满足 setup、hold 条件。了解更多数组与两套选择网络实现;采用特定 RAM 时,也可考虑复制存储体并同时广播写入,以获得足够读端口。仅凭 RTL 声明无法确定最终综合资源。

正在写入的值也是当前读取的值时

本约定要求同一边沿读写同址时,返回新的写数据。因此设置 bypass,将 wr_data 直接送到读输出的输入端,无需等待数组更新。端口 ii 的冲突条件定义如下。

Hi=wr_en∧(wr_addr=rd_addri)H_i=\mathrm{wr\_en}\land(\mathrm{wr\_addr}=\mathrm{rd\_addr}_i)

rd_eni=1\mathrm{rd\_en}_i=1 时,响应为:

Ri+={wr_data,Hi=1M−[rd_addri],Hi=0R_i^{+}=\begin{cases} \mathrm{wr\_data}, & H_i=1\\ M^{-}[\mathrm{rd\_addr}_i], & H_i=0 \end{cases}

M−M^{-} 是边沿前的存储值。HiH_i 对各读地址独立比较。不能因为一个端口冲突就向两个输出都传写数据,否则另一个地址的操作数也被改变。若漏掉 wr_en,实际没有写入时,也会因 wr_addr 恰好相同而错误旁路。

SystemVerilog
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。

同一次写入,两个端口独立选择
查看波形数据
波形数据:每个字符表示一个区间,点表示保持前一状态,p 表示一个时钟周期。
信号波形总线值
edge234E0 → E1 → E2
wr_en1.0
wr_data23420 → 30 → 99
rd_addr02..2
rd_addr123.5 → 2
out0 after23.20 → 30
out1 after23.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 也与其他地址一样可以读写。

你的选择适用于此浏览器,可随时在页脚更改。