10 / 20 · 실습
실습: 스큐된 2×2 배열
전달 지연을 한 에지씩 모델링하고 모든 중간 누산값을 확인합니다.
내용은 무료로 볼 수 있습니다. 수강 신청하면 학습 기록을 저장할 수 있어요.
입력을 같은 k에서 만나게 하기
행 i의 A(i,k)는 경계에서 논리 에지 k+i에 공급하고, 열 j의 B(k,j)는 k+j에 공급합니다. 오른쪽·아래쪽 전달이 각각 한 에지이므로 PE(i,j)에서는 두 값 모두 k+i+j에 만납니다. 이 지연 공급이 입력 스큐입니다. 모든 행·열을 동시에 k=0으로 시작하면 대각선 밖의 PE에서 서로 다른 k가 만날 수 있습니다.
A=[[1,2],[3,4]], B=[[5,6],[7,8]], N=K=2입니다. clear를 E0 이전에 마치고, 아래 경계 스케줄에서 빈칸은 invalid로 공급합니다. 여기서 마지막 경계 입력은 E2에 들어가지만 PE(1,1)의 마지막 MAC은 E3입니다.
| 에지 | 왼쪽 행0 | 왼쪽 행1 | 위 열0 | 위 열1 |
|---|---|---|---|---|
| E0 | 1(k0) | — | 5(k0) | — |
| E1 | 2(k1) | 3(k0) | 7(k1) | 6(k0) |
| E2 | — | 4(k1) | — | 8(k1) |
| E3 | — | — | — | — |
실행 가능한 전체 모델
아래를 schedule-lab.mjs로 저장하고 node schedule-lab.mjs를 실행합니다. 외부 패키지는 필요 없으며 정상 출력은 Schedule lab passed입니다. a와 b는 invalid이면 null이고, 유효하면 value와 k를 가진 토큰입니다. 이 단일 작업에서는 job 태그를 생략하지만 k 일치는 실제로 단언합니다. 모든 PE가 옛 상태를 읽고 새 배열에 씁니다. 배열 복사 없이 앞 PE부터 즉시 갱신하면 전달 지연을 잘못 모델링합니다.
import assert from 'node:assert/strict';
const A=[[1,2],[3,4]], B=[[5,6],[7,8]], N=2, K=2;
const fresh=()=>({a:null,b:null,acc:0,count:0});
let grid=Array.from({length:N},()=>Array.from({length:N},fresh));
const expected=[[[5,0],[0,0]],[[19,6],[15,0]],
[[19,22],[43,18]],[[19,22],[43,50]]];
const token=(value,k)=>({value,k});
for(let t=0;t<K+2*N-2;t++) {
const next=grid.map(row=>row.map(pe=>({...pe})));
for(let i=0;i<N;i++) for(let j=0;j<N;j++) {
const ka=t-i, kb=t-j;
const a=j ? grid[i][j-1].a :
(ka>=0&&ka<K ? token(A[i][ka],ka) : null);
const b=i ? grid[i-1][j].b :
(kb>=0&&kb<K ? token(B[kb][j],kb) : null);
const pe=next[i][j]; pe.a=a; pe.b=b;
if(a && b) {
assert.equal(a.k,b.k);
assert.equal(a.k,pe.count);
pe.acc+=a.value*b.value; pe.count++;
}
}
grid=next;
assert.deepEqual(grid.map(r=>r.map(p=>p.acc)),expected[t]);
}
assert.deepEqual(grid.map(r=>r.map(p=>p.count)),[[2,2],[2,2]]);
console.log('Schedule lab passed');중간 상태를 해석하기
E0 직후에는 PE(0,0)만 5입니다. E1에서 그 PE는 19를 완성하고, PE(0,1)은 6, PE(1,0)은 15를 얻습니다. E2에서 옆 두 PE는 22와 43을 완성하지만 PE(1,1)은 첫 항 18만 갖습니다. E3에서 마지막 32가 더해져 50이 됩니다. 최종 결과만 검사하면 E2에서 잘못 done을 낸 오류를 놓칠 수 있으므로, 이 실습은 매 에지의 누산 배열도 검사합니다.
행1의 입력을 한 에지 일찍 시작하도록 ka=t-i를 ka=t로 바꿔 보세요. PE(1,0)에서 A의 다음 k와 B의 이전 k가 만나는 태그 단언이 실패해야 합니다. 완료 조건은 4개의 advance 에지와 최종 [[19,22],[43,50]], 모든 count=2입니다. 출력 버스에서 결과를 한 개씩 보내는 시간과 작업 초기화 시간은 포함하지 않았습니다. RTL로 옮긴 뒤에는 같은 중간 상태 표와 독립적인 수치 참조를 함께 사용합니다.
스큐는 행렬 원소를 바꾸거나 전치하는 연산이 아닙니다. 같은 값을 늦게 공급하여 공간 이동 시간과 맞춥니다. 경계 표의 k가 한 행에서는 0,1 순서이고 다른 행에서도 0,1 순서인지 확인하세요. 각 값은 한 번만 들어가며 이웃 PE에서 재사용됩니다. trace의 0은 아직 항을 받지 않은 누산기 값일 수 있으므로 수치 0만으로 done을 추정하지 않고 count를 확인합니다.
실제 RTL: 등록된 2×2 배열
첨부된 systolic_2x2.sv는 signed_mac_pe.sv 네 개를 연결한 N=K=2의 합성 가능한 배열입니다. A는 오른쪽, B는 아래쪽으로 한 에지씩 등록 전달하며 valid와 k태그도 같은 경로를 사용합니다. 입력 스큐는 래퍼 외부의 경계 공급자가 수행합니다. tb_systolic_2x2.sv의 step 작업이 위 E0..E3 표를 직접 구동합니다. clear 완료 에지는 계산에 포함하지 않고, 추가 곱셈기 파이프라인도 없습니다.
세 첨부 파일을 같은 디렉터리에 저장하고 Icarus Verilog로 실행하세요.
iverilog -g2012 -s tb_systolic_2x2 -o systolic.vvp signed_mac_pe.sv systolic_2x2.sv tb_systolic_2x2.sv
vvp systolic.vvp테스트벤치는 각 상승 에지 뒤 상태를 검사합니다. E0..E3의 누산값은 [[5,0],[0,0]], [[19,6],[15,0]], [[19,22],[43,18]], [[19,22],[43,50]]입니다. done은 E3 이후에만 1입니다. 두 pause를 추가해도 4 advance 에지는 유지되며 계산 구간의 물리 에지는 6개입니다. 음수 예제 [[10,9],[−6,3]], 정지 중 모든 상태 유지, 작업 중 reset도 검사합니다. 성공 로그에는 RTL PASS:가 포함됩니다.
RTL 수정 과제로 전달 A 링크의 valid만 다음 에지보다 일찍 갱신하거나 등록 링크를 조합 경로로 바꾸세요. 어떤 PE의 k 정렬 또는 중간 누산 검사가 먼저 실패하는지 예측하고 실행 로그와 비교하세요. 수정은 학습용 복사본에서 수행하고 복구 후 재검증합니다. 이 경계 인터페이스를 N×N으로 일반화할 때는 배선 반복과 스큐뿐 아니라 폭·태그·완료 공식을 함께 다시 검토해야 합니다.
직접 생각해 보기
E2 직후 PE(1,1)의 누산값과 아직 필요한 항을 쓰세요. E3 경계 입력이 모두 invalid인데도 결과가 바뀌는 이유는?
해설 보기
E2 직후 acc=3×6=18이고 k=1의 4×8=32가 남습니다. 4와 8은 내부 전달 레지스터에 있으므로 E3에 PE(1,1)에 도착합니다. 경계 invalid는 즉시 배열 전체를 비우지 않습니다.