6 / 20 · 실습
실습: 부호 있는 MAC 처리 요소
독립 실행 가능한 모델로 유효 입력, 정지와 clear를 검사합니다.
내용은 무료로 볼 수 있습니다. 수강 신청하면 학습 기록을 저장할 수 있어요.
실행 목표
이 실습에서는 처리 요소 하나의 계약을 실행 가능한 정수 모델로 검사합니다. Node.js가 있는 터미널에서 아래 코드를 pe-lab.mjs로 저장하고 node pe-lab.mjs를 실행합니다. 외부 패키지는 필요하지 않습니다. 정상 출력은 PE lab passed입니다. 이 실행은 산술·제어 모델의 검증이며 SystemVerilog 컴파일이나 DDS에서의 실행을 의미하지 않습니다. 제공된 모델은 W=8, K=2, AW=17인 단일 작업을 생각하고 넓은 정수로 정답을 계산합니다.
PE 입력 valid는 A와 B에 따로 있습니다. 두 값이 모두 유효한 경우에만 곱을 더하며, 하나만 유효하더라도 등록 전달은 각자의 valid를 따라 수행합니다. 배열 전체에서는 짝이 없는 토큰이 계산에 끼어들지 않도록 태그 검사를 더해야 합니다. 여기서는 단일 PE 제어를 집중적으로 검증하고 다음 단원에서 태그를 추가합니다.
import assert from 'node:assert/strict';
const pe = {acc: 0, a: 0, b: 0, av: false, bv: false};
function edge({a=0, b=0, av=false, bv=false,
advance=true, clear=false, reset=false}={}) {
if (reset || clear) {
Object.assign(pe, {acc:0, a:0, b:0, av:false, bv:false});
} else if (advance) {
if (av && bv) pe.acc += a*b;
Object.assign(pe, {a, b, av, bv});
}
return {...pe};
}
edge({clear:true});
assert.equal(edge({a:-2,b:3,av:true,bv:true}).acc, -6);
const held = {...pe};
assert.deepEqual(edge({a:99,b:99,av:true,bv:true,advance:false}), held);
assert.equal(edge({a:4,b:-5,av:true,bv:true}).acc, -26);
assert.equal(edge({a:8,b:8,av:true,bv:false}).acc, -26);
assert.equal(pe.av, true);
assert.equal(pe.bv, false);
edge({clear:true,a:100,b:100,av:true,bv:true});
assert.equal(pe.acc, 0);
assert.equal(pe.av, false);
assert.equal(edge({a:-128,b:-128,av:true,bv:true}).acc, 16384);
edge({reset:true,advance:false});
assert.deepEqual(pe, {acc:0,a:0,b:0,av:false,bv:false});
console.log('PE lab passed');실행 전에 예상하기
첫 유효 쌍 (−2,3)은 −6을 만듭니다. 다음 에지는 advance=0이므로 공급한 99를 무시하고 상태를 유지합니다. 두 번째 유효 쌍 (4,−5)이 −20을 더해 −26이 됩니다. B valid만 0인 에지는 누산을 바꾸지 않지만 A valid는 오른쪽으로 전달됩니다. clear와 유효 입력을 동시에 주는 에지는 상태를 0으로 만들며 100×100은 더하지 않습니다. 마지막으로 최솟값 곱이 16384임을 확인하고, reset은 정지 상태에서도 유효 플래그까지 지웁니다.
RTL로 옮길 때의 경계
수치 단원의 product와 addend 신호를 always_ff에 연결하고, reset·clear·advance의 순서를 그대로 표현합니다. 배열 연결에서는 모든 PE가 같은 에지 전 상태를 읽어야 합니다. JavaScript 객체를 행 순서로 직접 덮어쓰는 방식으로 배열을 흉내 내면 뒤의 PE가 새 값을 즉시 읽어 한 에지에 여러 칸 이동할 수 있습니다. 다음 배열 실습은 새 상태와 옛 상태를 분리합니다.
의도적으로 av&&bv를 av||bv로 바꿔 보세요. 한쪽만 유효한 검사에서 누산기가 변해 실패해야 합니다. clear 분기 순서를 MAC 뒤로 옮기거나 advance가 누산기에만 적용되도록 바꾸었을 때도 어떤 단언이 실패하는지 적습니다. 실습 완료 조건은 성공 문자열만 보는 것이 아니라, 각 실패가 계약의 어느 문장을 보호하는지 설명하는 것입니다.
검사 로그를 작성할 때는 공급한 값과 실제 소비한 값을 구분하세요. pause 에지의 99는 인터페이스에서 보였지만 소비되지 않았습니다. RTL 래퍼는 준비되지 않은 입력을 버리거나 다음 에지에 재사용할지 계약으로 정해야 하며, 여기서는 경계 스케줄을 유지하여 다음 항을 그대로 보관합니다. 이 모델에서는 함수가 반환하는 복사본을 관측 기록으로 사용하므로 나중에 상태가 바뀌어도 이전 기록이 변하지 않습니다. 실제 파형에서 acc뿐 아니라 전달 valid도 확인하면 계산이 정지했지만 링크가 움직인 오류를 찾을 수 있습니다. 한 작업의 시작과 종료를 같은 방식으로 기록하여 재현 가능한 관측표를 만드세요.
실제 RTL: 부호 있는 MAC PE
첨부 파일 signed_mac_pe.sv, systolic_2x2.sv, tb_systolic_2x2.sv를 같은 디렉터리에 저장하세요. 첫 파일은 합성 가능한 PE, 둘째는 이 PE 네 개를 연결한 배열이며, 테스트벤치는 시뮬레이션 전용입니다. PE는 완전한 2W비트 signed 곱을 AW로 부호 확장한 뒤 누산합니다. W=8,K=4 시험은 AW=18에서 65536·−65024·64516을 정확히 검사합니다. count는 0..K를 담고 k태그는 0..K−1을 담습니다. 태그 불일치는 protocol_error를 설정하며 MAC을 소비하지 않습니다.
reset > clear > advance 우선순위를 사용합니다. clear 에지가 끝난 다음 첫 항을 공급하고, 각 상승 에지 직후의 새 acc를 관측합니다. 전달 출력도 등록되므로 이웃은 다음 advance 에지에서 소비합니다. advance=0은 acc·count·데이터·valid·k태그를 모두 유지합니다. 이 구현은 한 clear 구간에 한 작업만 허용하며 출력 양자화와 DDS 연결은 포함하지 않습니다.
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정상 종료에는 RTL PASS:가 표시되며 실패하면 $fatal로 종료합니다. 수정 과제로 addend의 부호 확장을 0 확장으로 바꾸어 음수 행렬 예제의 실패를 확인하세요. 처음 잘못된 PE 누산값과 원인을 설명한 뒤 원래 구현을 복구하고 다시 통과시키세요. Node 모델 로그와 HDL 시뮬레이터 로그는 별도로 기록합니다.
직접 생각해 보기
첫 쌍 (−2,3) 다음에 유효한 0과 7을 공급하고 마지막에 (4,−5)를 공급하면 최종 acc는 얼마인가요? 0 토큰의 valid를 0으로 만드는 것과 계산 결과 측면·태그 측면에서 무엇이 다른가요?
해설 보기
합은 −6+0−20=−26입니다. valid 0은 수치 합을 바꾸지 않지만 실제 k항으로 소비되므로 카운트와 태그가 진행합니다. invalid 토큰은 항을 소비하지 않습니다. 실제 축약 길이를 3으로 바꾸면 K와 폭·완료 제어도 그에 맞춰야 합니다.