ISA 관측 계약: 내부 계산은 달라도 덧셈의 의미는 같아야 한다
같은 바이너리를 작은 마이크로컨트롤러와 큰 비순차 실행 코어에서 돌릴 수 있는 이유는 무엇일까. 내부에는 명령을 처리하는 순서, 레지스터를 저장하는 방식과 메모리 지연이 크게 다를 수 있다. ISA 관측 계약은 이런 구현 차이를 허용하면서 소프트웨어가 의지할 수 있는 동작을 정한다.
한 덧셈에서 무엇이 같아야 하는가
가상의 두 RV32I 코어 A와 B에서 다음 명령을 실행한다고 하자. 실행 전 x1은 0xFFFFFFFF, x2는 1이다.
add x3, x1, x2
RV32I의 ADD는 두 입력을 더해 결과의 낮은 32비트를 목적지에 쓴다. 따라서 x3는 0이다. 부호 없는 관점에서는 2³²가 되어 범위를 넘지만, 이 명령은 그 이유로 산술 예외를 발생시키지 않는다. RISC-V 원문은 ADD와 SUB의 overflow를 무시하고 낮은 XLEN 비트를 쓴다고 명시한다. RISC-V, RV32I의 Integer Register-Register Operations
A가 단순한 실행 경로를 쓰고 B가 내부에서 명령을 다른 물리 레지스터로 바꾸어 계산해도 이 결과의 의미는 같아야 한다. 설명용으로 A는 결과를 한 사이클 뒤, B는 세 사이클 뒤에 사용할 수 있게 한다고 가정해도, 이 지연 차이만으로 ADD의 의미가 달라지지는 않는다. 이 숫자는 실제 제품의 지연을 비교한 것이 아니다.
반대로 B가 “부호 없는 overflow를 더 엄격하게 처리하겠다”며 이 ADD에서 임의의 trap을 만들면, 같은 명령을 구현했다는 계약과 맞지 않는다. 내부 방식을 바꿀 자유와 소프트웨어에 보이는 명령 의미를 바꿀 자유는 구별된다.
원문이 구현도를 쓰지 않은 이유
RISC-V의 서론은 ISA를 특정 하드웨어의 설계가 아니라 다양한 구현에 대한 software-visible interface로 읽어야 한다고 설명한다. 명령 의미에 필요한 내용을 정하면서, 특정 캐시 크기나 비순차 실행 방식에 묶이지 않도록 하는 것이다. RISC-V, Introduction
이 경계 덕분에 같은 ADD를 조합 회로 하나로 계산하거나 여러 단계에 나누어 계산할 수 있다. 뒤의 독립 명령을 먼저 실행하더라도 프로그램에 보이는 상태의 순서와 예외 규칙을 지키면 구현 방식은 달라질 수 있다. 소프트웨어는 내부에서 x3 대신 p37을 썼는지까지 알아야 하지 않는다.
여기서 관측 상태는 마지막 레지스터 값 하나에 그치지 않는다. 어느 명령이 실행되었는지, 입력과 목적지 변경, 메모리 접근과 예외도 명령의 계약에 속한다. 명령 단위로 이런 상태를 보고하는 RVFI 역시 PC와 레지스터·메모리의 전후 값을 구분한다. 이는 구현 속도와 별도로 의미를 비교할 수 있는 관측 예다. YosysHQ, RVFI specification
RISC-V라는 이름 하나가 모든 실행 조건을 맞추지는 않는다
같은 명령 이름이라도 폭을 확인해야 한다. 위 입력값을 0으로 확장해 RV64의 64비트 레지스터에 넣고 ADD를 수행하면 결과는 0x0000000100000000이다. RV32의 0과 다르다. “둘 다 RISC-V”라는 분류만으로 같은 상태 변화라고 비교할 수 없는 예다.
지원하는 확장도 중요하다. 곱셈 명령을 요구하는 프로그램과 그 기능을 제공하지 않는 실행 환경은 같은 기본 ISA 이름만으로 맞지 않는다. 더 넓은 호환성에는 호출 규약, 운영체제의 서비스, 메모리와 I/O 주소, 특권 상태도 들어간다. RISC-V 원문은 ISA를 execution environment interface의 한 구성요소로 두며, 환경이 초기 상태와 메모리 접근, 예외 처리 등을 정의한다고 설명한다. 명령 trap을 받아 소프트웨어로 기능을 에뮬레이션하는 환경도 가능하다. RISC-V, Software Execution Environments and Harts
따라서 하드웨어에서 기능을 직접 구현했는지와 실행 환경 전체가 그 기능을 제공하는지도 구분해야 한다. 사용자 프로그램이 필요한 동작을 얻었다고 모든 연산이 같은 회로에서 계산되었다는 뜻은 아니다.
같은 결과가 같은 관측 숫자를 뜻하지는 않는다
동작이 호환되어도 실행 시간과 소비 전력, 캐시 적중은 다를 수 있다. 프로그램이 시간이나 사이클 카운터를 읽으면 실제 관측값도 달라질 수 있다. 같은 환경에서도 외부 입력과 인터럽트 도착 시점이 다르면 명령 스트림이 달라질 수 있다. ISA의 계약을 “모든 실행이 비트 단위로 똑같은 전체 기록을 만든다”로 확장해서는 안 된다.
검증에서는 결정된 명령 의미와 허용된 변동을 분리한다. ADD의 낮은 XLEN 비트처럼 정해진 결과는 정확히 비교하고, 환경이나 동시성에 따라 달라질 수 있는 동작은 그 조건에서 허용되는지를 본다. 몇 사이클 걸렸는지는 성능 검증의 중요한 항목이지만, 임의로 정한 지연을 명령의 기능적 정답에 섞으면 다른 구현을 잘못 판정할 수 있다.
ISA 관측 계약은 구현 차이를 지우는 약속이 아니라, 그 차이 속에서도 프로그램이 사용할 수 있는 의미를 정하는 약속이다. 위 두 RV32I 코어가 같은 입력에서 x3=0을 만들고 필요한 상태·예외 규칙을 지킨다면 내부 계산 경로는 달라도 된다. 같은 바이너리를 실행할 수 있는지는 그 명령 계약에 더해 프로그램이 요구하는 확장과 실행 환경까지 맞추어 판단해야 한다.