15 / 24 · 실습
실습: 멈춤을 견디는 창
기존 멈춤 [2,3,12] 대신 사이클 [0,1,2]에서 멈추되 모든 입력이 결국 수락된다면 총 창 개수와 값은 바뀌나요? 완료 사이클 수는 어떻게 되나요?
내용은 무료로 볼 수 있습니다. 수강 신청하면 학습 기록을 저장할 수 있어요.
목표와 실행 방법
순차 입력에서 3×3 창을 만들고 입력이 잠시 멈춰도 창의 값과 순서가 그대로인지 확인합니다. 새 폴더에 window-lab.mjs를 만들고 아래 코드를 붙인 뒤 node window-lab.mjs를 실행하세요. Node.js 외의 의존성은 없습니다. 설치된 Digital Design Studio의 지원되는 파일 만들기·열기 흐름에서 같은 파일을 수동으로 작성해 검토할 수도 있습니다. DDS에서 이 프로그램을 실행했다거나 원격 실습 프로젝트를 자동 생성했다고 주장하지 않습니다.
이 모델은 입력 수락을 멈추는 조건만 다룹니다. 전체 출력 ready·valid, 메모리 버스, 다채널·패딩·dilation을 구현한 것이 아닙니다. stalls 집합의 값은 실제 반복 사이클 번호이고 accepted는 받은 픽셀 번호입니다. 이 둘을 분리해서 비교하는 것이 실습의 핵심입니다. 멈춤 때는 버퍼, 창, 입력 인덱스가 그대로 남아야 합니다.
예상 창과 시간
입력 1부터 16까지를 폭 4로 해석합니다. 정상 실행에서 첫 창의 acceptedIndex는 10이고 values는 [1,2,3,5,6,7,9,10,11]입니다. 네 창의 수락 인덱스는 [10,11,14,15]입니다. 정상 실행은 16사이클이 걸립니다. 사이클 2,3,12를 멈추는 실행은 19사이클이며 첫 창은 실제 사이클 13에서 만들어집니다. 입력의 수락 인덱스는 여전히 10입니다. 결과의 값과 순서는 같고 도착 시간만 달라집니다.
W=4인 모델의 storageSamples는 두 라인 8샘플과 창 9샘플을 더한 17입니다. 프로그램이 JavaScript 배열을 쓰기 때문에 이 수가 실제 SRAM 바이트 수나 합성 후 면적을 의미하지는 않습니다. 회로로 옮기려면 샘플 비트 폭과 저장 구조를 별도로 정해야 합니다. 출력 창을 무한히 모으는 windows 배열도 검증용 기록이며 제한된 하드웨어 버퍼로 해석하면 안 됩니다.
실패를 만드는 변경
먼저 원본을 실행해 두 결과의 values가 일치하는지 봅니다. 다음에 멈춤 분기에서도 accepted를 증가하도록 임시 수정하면 픽셀이 빠지며 assertion이 실패해야 합니다. 이후 원래 코드로 되돌립니다. 또 새 행의 가로 창 초기화를 없애고 열 0에서부터 출력을 허용하면 이전 행의 마지막 열이 섞인 잘못된 창이 나옵니다. 유효 조건과 상태 전진은 함께 검증해야 합니다.
직접 모델과 비교하려면 각 창에 같은 펼친 커널을 내적한 뒤 출력 순서에 따라 접어 보세요. 실습의 capstone에서는 이것을 다른 기준 모델과 비교합니다. 입력이 바뀌면 손계산으로 첫 창 하나를 먼저 확정하고 나머지를 자동 비교합니다. 실행 결과를 보관할 때 조건, 수락 인덱스와 실제 사이클을 함께 남겨 재현할 수 있게 합니다.
실행 코드
import assert from 'node:assert/strict';
// Two row delays plus three horizontal shift registers: valid 3x3, one channel.
export function streamWindows(pixels, width, stalls = new Set()) {
const previous = Array(width).fill(0), older = Array(width).fill(0);
const window = Array.from({length: 3}, () => Array(3).fill(0));
const windows = []; let accepted = 0, cycles = 0;
while (accepted < pixels.length) {
if (stalls.has(cycles++)) continue;
const row = Math.floor(accepted / width), col = accepted % width;
if (col === 0) window.forEach(r => r.fill(0));
const current = pixels[accepted];
[older[col], previous[col], current].forEach((v, r) => {window[r].shift(); window[r].push(v);});
older[col] = previous[col]; previous[col] = current;
if (row >= 2 && col >= 2) windows.push({acceptedIndex: accepted, cycle: cycles - 1, values: window.flat()});
accepted++;
}
return {windows, accepted, cycles, storageSamples: 2 * width + 9};
}
const pixels = Array.from({length: 16}, (_, i) => i + 1);
const normal = streamWindows(pixels, 4);
const paused = streamWindows(pixels, 4, new Set([2, 3, 12]));
assert.deepEqual(normal.windows.map(w => w.acceptedIndex), [10, 11, 14, 15]);
assert.deepEqual(normal.windows[0].values, [1, 2, 3, 5, 6, 7, 9, 10, 11]);
assert.deepEqual(paused.windows.map(w => w.values), normal.windows.map(w => w.values));
assert.equal(paused.cycles, 19);
console.log(JSON.stringify({normal, paused}));RTL 실습: 고정 크기 스트리밍 창
첨부 cnn_window3x3.sv, cnn_conv3x3.sv, tb_cnn_stream.sv를 같은 폴더에 저장하세요. cnn_window3x3는 합성 가능한 SystemVerilog 창 생성기입니다. 한 번 reset한 뒤 signed int8의 4×4 프레임 하나를 행 우선으로 받으며 한 채널,3×3 valid,stride 1,dilation 1,패딩 없음만 지원합니다. 상승 에지에서 in_valid와 in_ready가 모두 1일 때만 입력과 버퍼 좌표가 전진합니다. 입력 생산자는 막힌 동안 값과 valid를 유지해야 합니다. 출력 창도 out_valid=1,out_ready=0이면 유지됩니다. reset은 동기식 active-high이며 다음 프레임 전에 다시 적용합니다.
아래 명령은 Icarus Verilog가 설치된 환경의 외부 시뮬레이터 명령입니다. 설치된 DDS에 파일 만들기·열기 흐름이 지원된다면 파일을 수동 생성·열어 검토할 수 있지만 DDS 실행·합성은 별도 확인이 필요합니다.
iverilog -g2012 -s tb_cnn_stream -o cnn-sim cnn_window3x3.sv cnn_conv3x3.sv tb_cnn_stream.sv
vvp cnn-sim테스트벤치는 모든 창의 9개 값과 좌표,행 경계,입력·출력 멈춤 중 상태 유지,대기 출력이 있을 때 reset과 프레임 종료를 자동 비교합니다. 정상 1~16 입력의 창 좌표는 (0,0),(0,1),(1,0),(1,1)입니다. 최종 PASS 문장은 frames=6,windows=24,outputs=24,kernels=2와 양의 stalled_cycles를 포함해야 합니다. 실제 실행 로그를 제출하며 실행하지 않았다면 통과로 기록하지 마세요.
변경 과제: 입력 첫 값을 2로 바꾸고 첫 창의 왼쪽 위 값만 변하는지 손계산과 파형으로 확인하세요. 출력 ready가 여러 사이클 0일 때도 다음 창의 값·순서는 같아야 합니다. 창을 보관하는 stage를 없애려면 backpressure의 경로와 상태 유지 조건을 다시 증명해야 합니다.
직접 생각해 보기
기존 멈춤 [2,3,12] 대신 사이클 [0,1,2]에서 멈추되 모든 입력이 결국 수락된다면 총 창 개수와 값은 바뀌나요? 완료 사이클 수는 어떻게 되나요?
해설 보기
4개 창의 값과 순서는 동일합니다. 16수락에 멈춤 3개를 더해 19사이클입니다. 창의 수락 인덱스는 그대로이며 실제 도착 시점만 뒤로 이동합니다.