22 / 24 · Lab
Capstone lab: a FIFO and two-stage streaming calculator
Connect a depth-two input FIFO, two elastic stages, and a scoreboard.
Lessons are free to read. Enroll to save your learning progress.
Combine storage, computation, and delivery
The capstone has a two-entry input FIFO and two elastic computation stages. Stage one stores the nine-bit sum a+b; stage two stores 3×sum modulo 256. Tags follow every value. Total capacity is four transactions. Backpressure propagates from the output while empty slots may still fill. Assume one clock, a registered FIFO, simultaneous pop/push, and no output fall-through.
Manually save capstone-model.mjs and run node capstone-model.mjs. Compute all readiness and next-stage values from old state before updating the FIFO and registers. The JavaScript array is a functional queue, not synthesizable 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');| Tag | a,b | Wide result | y |
|---|---|---|---|
| 2 | 250,10 | 780 | 12 |
| 3 | 255,255 | 1530 | 250 |
| 5 | 17,240 | 771 | 3 |
The first input enters the FIFO at edge 0, s1 at edge 1, and s2 at edge 2. Sink stalls at edges 2,3,4 delay its delivery to edge 5. Unstalled latency is three edges; this transaction takes five. Other empty storage fills while s2 holds.
The reference queue computes expected values only on acceptance. Compare tags and results on delivery and check occupancy each edge. After edge 25, drain everything. The Node run checks eight ordered results; the SystemVerilog files below independently exercise actual FIFO and pipeline registers.
Build and simulate the RTL
Download dp_stream_calculator.sv and tb_dp_stream_calculator.sv into one folder. The unsigned eight-bit a,b,tag interface accepts on in-valid/in-ready and delivers y with its tag on out-valid/out-ready. Nine-bit sums and eleven-bit scaled values narrow only at output. Reset empties storage and suppresses transfers.
Run with Icarus Verilog installed:
iverilog -g2012 -Wall -s tb_dp_stream_calculator -o dp-test.vvp dp_stream_calculator.sv tb_dp_stream_calculator.sv
vvp dp-test.vvpAuthor verification compiled and simulated these files successfully:
RTL PASS: accepted=527 delivered=523 canceled=4 stalls=517 full_replace=64 wraps=262 reset_with_work=1 zero=3The testbench checks arithmetic, tags, order, stalls, full replacement, pointer wrap, four-item reset cancellation, and drain. Change stall duration and compare latency against three unstalled cycles. This verifies Icarus RTL simulation, not synthesis, physical timing, or DDS execution.
Try it yourself
With an always-ready sink, at which edge does tag0 transfer? If FIFO depth becomes one, what happens to unstalled latency and total capacity?
Read the explanation
Tag0 transfers at edge 3 after acceptance at 0, s1 at 1, and s2 at 2. Keeping the registered FIFO preserves three-edge latency; capacity decreases to 1+1+1=3 transactions. Change the input-ready depth limit and recheck the model.