22 / 24 · 実習
総合実習:FIFOと2段のストリーミング計算器
深さ2の入力FIFO、2つの弾力的な段、スコアボードを接続します。
レッスンは無料で読めます。受講登録すると進捗を保存できます。
記憶、計算、引き渡しを組み合わせる
総合課題は2エントリの入力FIFOと、弾力的な2演算段を持ちます。第1段は9ビットの和a+b、第2段は3×sumを256を法として保持します。タグは各値に追従します。総容量は4トランザクションです。空のスロットを埋められる間も、バックプレッシャーは出力から伝わります。1クロック、レジスタ付きFIFO、同時pop/push、出力直通なしを仮定します。
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へ遅らせます。ストールなしのレイテンシは3エッジですが、このトランザクションは5です。s2が保持する間に他の空記憶領域が埋まります。
参照キューは受け入れ時だけ期待値を計算します。引き渡しでタグと結果を比較し、各エッジで占有数を検査します。エッジ25以降にすべて排出します。Node実行は順序付き8結果を検査し、以下のSystemVerilogファイルは実際のFIFOとパイプラインレジスタを独立に試します。
RTLをビルドしてシミュレーションする
dp_stream_calculator.svとtb_dp_stream_calculator.svを1フォルダへダウンロードします。符号なし8ビットのa,b,tagインターフェースはin-valid/in-readyで受け入れ、out-valid/out-readyでタグ付きyを引き渡します。9ビット和と11ビット定数倍値は出力時だけ縮小します。リセットは記憶領域を空にして転送を抑えます。
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テストベンチは算術、タグ、順序、ストール、満杯での置き換え、ポインタ周回、4項目のリセット取り消し、排出を検査します。ストール時間を変え、ストールなしの3サイクルとレイテンシを比較します。これはIcarusのRTLシミュレーションを検証し、合成、物理タイミング、DDS実行は検証しません。
自分で考えてみましょう
受け取り側が常にreadyなら、tag0はどのエッジで転送されますか。FIFO深さを1にすると、ストールなしのレイテンシと総容量はどうなりますか。
解説を見る
tag0は0で受け入れ、1でs1、2でs2に入り、エッジ3で転送されます。レジスタ付きFIFOを維持すればレイテンシは3エッジのままで、容量は1+1+1=3トランザクションに減ります。入力readyの深さ制限を変更し、モデルを再検査します。