Altifigence Academy

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ではありません。

javascript
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
2250,1078012
3255,2551530250
517,2407713

最初の入力はエッジ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をインストールした環境で実行します。

text
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

著者検証ではこれらのファイルのコンパイルとシミュレーションに成功しました。

text
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の深さ制限を変更し、モデルを再検査します。

Lesson files

capstone-model.mjs ↓dp_stream_calculator.sv ↓tb_dp_stream_calculator.sv ↓

選択はこのブラウザに適用されます。フッターからいつでも変更できます。