22 / 24 · 实验
综合实验:FIFO 与两级流式计算器
连接深度二的输入 FIFO、两个弹性级和记分板。
课时内容可免费阅读,选课后可保存学习进度。
结合存储、计算与交付
综合项目包含两条目的输入 FIFO 和两个弹性计算级。第一级存储九位和 a+b;第二级存储 3×sum 对 256 取模的结果。每个数值都带标签。总容量为四个事务。背压从输出端传播,而空槽仍可被填入。假定只有一个时钟,FIFO 为寄存式,支持同时出队/入队,且没有输出直通。
手动保存 capstone-model.mjs,运行 node capstone-model.mjs。更新 FIFO 和寄存器前,根据旧状态计算所有 ready 和下一状态。JavaScript 数组是功能队列,不是可综合 HDL。
import assert from 'node:assert/strict';
const input = [[0,0],[1,2],[250,10],[255,255],
[128,128],[17,240],[90,90],[4,8]];
const q = [], expected = [], seen = [];
let s1 = null, s2 = null, issued = 0, delivered = 0;
for (let edge = 0; edge < 50; edge++) {
const ready = edge >= 25 || ![2,3,4].includes(edge % 7);
const old1 = s1, old2 = s2;
const r2 = old2 === null || ready;
const r1 = old1 === null || r2;
const pop = q.length > 0 && r1;
const inReady = q.length < 2 || pop;
if (old2 !== null && ready) {
assert.deepEqual(old2, expected.shift());
seen.push(old2.y);
delivered++;
}
const n2 = r2 ? (old1 === null ? null :
{ tag: old1.tag, y: old1.sum * 3 % 256 }) : old2;
const n1 = r1 ? (pop ?
{ tag: q[0].tag, sum: q[0].a + q[0].b } : null) : old1;
if (pop) q.shift();
if (issued < input.length && inReady) {
const [a,b] = input[issued];
q.push({ tag: issued, a, b });
expected.push({ tag: issued, y: (3*a + 3*b) % 256 });
issued++;
}
s1 = n1; s2 = n2;
if (old2 !== null && !ready) assert.deepEqual(s2, old2);
assert.ok(q.length <= 2);
assert.equal(issued - delivered,
q.length + Number(s1 !== null) + Number(s2 !== null));
}
assert.equal(issued, input.length);
assert.equal(delivered, input.length);
assert.deepEqual(seen, [0,9,12,250,0,3,28,36]);
assert.equal(expected.length + q.length, 0);
assert.equal(s1, null); assert.equal(s2, null);
console.log('CAPSTONE PASS: 8 ordered outputs, drained');| 标签 | a,b | 宽位结果 | y |
|---|---|---|---|
| 2 | 250,10 | 780 | 12 |
| 3 | 255,255 | 1530 | 250 |
| 5 | 17,240 | 771 | 3 |
第一个输入在时钟沿 0 进入 FIFO,时钟沿 1 进入 s1,时钟沿 2 进入 s2。接收端在时钟沿 2、3、4 停顿,使其交付延后到时钟沿 5。无停顿延迟为三个沿;这个事务需要五个。在 s2 保持期间,其他空存储仍可被填入。
参考队列只在接收时计算预期值。在交付时比较标签和结果,并在每个时钟沿检查占用量。时钟沿 25 之后,排空全部工作。Node 运行检查八个有序结果;下面的 SystemVerilog 文件独立测试真实 FIFO 和流水线寄存器。
构建并仿真 RTL
将 dp_stream_calculator.sv 和 tb_dp_stream_calculator.sv 下载到同一文件夹。无符号八位 a,b,tag 接口在 in-valid/in-ready 握手时接收输入,并在 out-valid/out-ready 握手时交付 y 及其标签。九位和与十一位缩放值只在输出端缩窄。复位清空存储并禁止传送。
安装 Icarus Verilog 后运行:
iverilog -g2012 -Wall -s tb_dp_stream_calculator -o dp-test.vvp dp_stream_calculator.sv tb_dp_stream_calculator.sv
vvp dp-test.vvp作者验证已成功编译并仿真这些文件:
RTL PASS: accepted=527 delivered=523 canceled=4 stalls=517 full_replace=64 wraps=262 reset_with_work=1 zero=3测试平台检查算术、标签、顺序、停顿、满状态替换、指针回绕、四项复位取消和排空。改变停顿持续时间,并将延迟与三个无停顿周期比较。这验证 Icarus RTL 仿真,不验证综合、物理时序或 DDS 执行。
自己试试
接收端始终 ready 时,tag0 在哪个时钟沿传送?如果 FIFO 深度变为一,无停顿延迟和总容量如何变化?
阅读解释
Tag0 在时钟沿 3 传送,之前在 0 接收、1 进入 s1、2 进入 s2。保留寄存式 FIFO 会保持三个沿的延迟;容量降为 1+1+1=3 个事务。修改 input-ready 的深度界限,并重新检查模型。