Altifigence Academy

22 / 24 · 실습

종합 실습: FIFO와 두 단계 스트리밍 연산기

깊이 2의 입력 FIFO, 두 elastic 단계와 scoreboard를 한 실행 모델로 연결합니다.

저장·계산·전달을 하나의 계약으로 묶기

최종 장치는 입력 FIFO 두 칸과 두 계산 단계로 구성합니다. 1단계는 9비트 sum=a+b, 2단계는 y=3×sum modulo 256을 저장합니다. 각 항목의 tag는 전체 경로를 통과합니다. 전체 저장 용량은 네 거래입니다. 소비자 ready가 낮아지면 출력 단계부터 정지가 뒤로 전달되며 빈 칸은 계속 채울 수 있습니다. 이 모델은 한 클록, 등록형 FIFO, 동시 pop·push 허용, 출력 fall-through 없음이라는 계약을 사용합니다.

capstone-model.mjs를 수동으로 만들고 아래 코드를 저장한 뒤 Node.js 터미널에서 node capstone-model.mjs를 실행하세요. q는 FIFO 순서를 나타내며 s1,s2는 한 칸 단계입니다. 먼저 에지 전 상태로 모든 ready와 다음 단계 값을 계산하고 그 뒤 FIFO와 레지스터를 갱신합니다. 코드의 배열 큐는 기능 모델이며 그대로 합성할 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');
입력 taga,b수학적 값y
2250,1078012
3255,2551530250
517,2407713

에지 0에는 첫 입력이 FIFO에 저장됩니다. 에지 1에는 s1으로, 에지 2에는 s2로 이동합니다. 하지만 소비자가 에지 2,3,4에서 멈추므로 첫 출력의 전달은 에지 5입니다. 정상적인 무정지 latency는 세 에지이고 이 첫 거래는 두 에지의 추가 대기로 다섯 에지가 걸립니다. s2를 보존하는 동안 다른 빈 칸은 채워져 버퍼링과 계산 정지가 함께 드러납니다.

기대 큐는 입력 수락 때 넓은 정수 식으로 결과를 계산합니다. 출력 전달 때 tag와 y를 비교하고 매 에지 전체 점유도 검사합니다. 25에지 뒤 소비자는 계속 ready라서 남은 작업이 반드시 drain되어야 합니다. Node 완료 메시지는 이 모델의 여덟 결과와 순서가 맞다는 뜻입니다. 아래 SystemVerilog 파일은 실제 FIFO 저장소·포인터·count와 두 단계 레지스터를 별도로 검증합니다.

RTL 파일 만들기와 시뮬레이션

첨부의 dp_stream_calculator.sv와 tb_dp_stream_calculator.sv를 내려받아 같은 폴더에 저장하세요. 연산기는 8비트 unsigned a,b,tag를 in-valid/in-ready에서 수락하며 out-valid/out-ready에서 y와 같은 tag를 전달합니다. 9비트 합과 11비트 세 배 값을 사용하고 마지막에만 8비트로 줄입니다. reset은 두 유효 비트와 FIFO 점유를 비우고 그 에지의 전달을 막습니다.

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

테스트벤치는 값·tag·순서, 정지 중 보존, full 교체, 포인터 순환, 네 거래를 취소하는 reset과 drain을 검사합니다. 소비자 정지 길이를 바꿔 다시 실행하고, 무정지 첫 결과의 3사이클 지연과 정지 후 추가 지연을 에지 표로 비교하세요. 이 결과는 Icarus의 RTL 시뮬레이션이며 합성·물리 타이밍 또는 DDS 실행의 검증은 아닙니다.

직접 생각해 보기

소비자가 처음부터 항상 ready이면 tag0의 전달은 어느 에지인가요? FIFO 깊이를 1로 줄이면 무정지 latency와 전체 용량은 어떻게 바뀌나요?

해설 보기

tag0는 입력 수락 0, s1 이동 1, s2 이동 2를 거쳐 에지 3에 전달됩니다. 등록형 FIFO 단계가 그대로이므로 깊이 1에서도 latency=3에지이고 전체 용량만 1+1+1=3거래로 줄어듭니다. 코드의 inReady 한계를 1로 바꾸고 다시 검사할 수 있습니다.

실습 파일

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

선택은 이 브라우저에만 적용됩니다. 언제든 푸터에서 변경할 수 있습니다.