모든 이야기

transaction trace: 뒤늦은 읽기 오류에서 마지막 쓰기를 찾는 법

수백만 사이클 뒤 메모리 읽기 값이 어긋났다. 실패 시각의 파형에는 읽기 요청과 잘못된 응답만 보인다. 원인은 훨씬 앞선 부분 쓰기에 있을 수 있다. 긴 파형을 처음부터 확대하기 전에, 그 주소에 어떤 거래가 있었는지를 추적하면 조사할 위치를 줄일 수 있다.

transaction trace는 실제 수락된 요청과 응답을 의미 단위로 기록한다. 신호를 줄여 저장하는 목적뿐 아니라, 입력에서 결과까지 연결할 수 있는 기록을 만드는 것이 중요하다.

값만 찍은 로그에는 연결 정보가 없다

가상의 32비트 little-endian 메모리에서 주소 0x100의 초기 word는 0x11223344다. 각 byte는 낮은 주소부터 44, 33, 22, 11이다. 외부에서 메모리를 바꾸는 주체는 없고, 같은 주소의 접근은 설명한 순서대로 완료된다고 정한다.

transaction 42가 주소 0x100에 data=0x000000AA, byte enable=0001로 쓰기를 수행한다. 올바른 결과는 낮은 byte만 바뀐 0x112233AA다. 이후 transaction 43이 같은 word를 읽었는데 0xAA223344가 돌아왔다고 하자.

42 WRITE_REQ addr=0x100 data=0x000000AA be=0001
42 WRITE_DONE status=OK
43 READ_REQ  addr=0x100
43 READ_DONE expected=0x112233AA actual=0xAA223344

이 기록은 실제 제품의 측정값이 아닌 설명용 예다. 로그의 ID는 요청과 응답을 연결하는 식별자이며, accepted event에서 기록한 데이터다. 요청을 제시했지만 수락되지 않은 사이클을 별도 완료 거래로 세지 않는다.

byte enable 0001은 낮은 byte를 선택한다. 실제 값의 높은 byte가 AA가 된 모양은 byte 위치 해석을 의심하게 한다. 그러나 로그만으로 ‘42번 쓰기에서 반드시 잘못 저장했다’고 단정할 수는 없다. 저장은 맞았고 읽기 경로가 데이터를 잘못 재배치했을 수도 있다.

관측된 첫 차이와 실제 원인을 구분한다

이 예에서 첫 관측 mismatch는 43번 읽기 응답이다. 원인이 될 만한 후보에는 이전 쓰기와 읽기 처리 두 곳이 있다. 42번을 동일 조건에서 다시 실행하고 부분 쓰기 직후의 저장 상태나 이후의 다른 읽기 결과를 확인하면 두 후보를 나눌 수 있다.

주소별 마지막 완료 쓰기 기록이 있으면 같은 주소의 후보를 찾기 쉽다. 여러 ID가 동시에 진행하는 인터페이스에서는 요청 순서와 완료 순서를 모두 남기고, 실제 명세의 메모리 가시성·순서 규칙을 적용해야 한다. 단순히 가장 최근 로그 줄을 정답의 원인으로 고르는 방식은 충분하지 않다.

기록에는 transaction ID, 주소, 명령 종류, 폭, byte mask, 입력 데이터, 응답 상태와 실제 결과가 필요하다. reset이 요청을 취소할 수 있다면 reset 경계와 취소도 남긴다. 응답을 비교한 시각과 요청을 받아들인 시각을 각각 남겨야 지연과 순서 문제를 따로 볼 수 있다.

UVM의 monitor와 analysis 연결은 관찰한 transaction을 검사 구성요소에 전달하는 구조를 제공한다. 그 항목을 일관된 형식으로 기록하면 비교에 사용한 입력과 실패 보고가 같은 관측 사실을 가리키게 할 수 있다. Accellera UVM User’s Guide 1.1, §1.2.4 및 §4.10

CPU에서는 명령의 의미도 기록 단위가 된다

CPU 검증에서는 retirement에 해당하는 명령 관측 정보가 유용하다. RISC-V Formal의 RVFI는 유효 신호와 명령 순서, 명령 word, register 입력·출력, PC와 메모리 접근 mask 등을 정의한다. 전처리 상태와 실행 후 결과를 함께 볼 수 있어, 단지 ALU 출력 한 신호를 찍는 것보다 명령 의미를 비교하기 쉽다. RISC-V Formal, RVFI Specification

예를 들어 값 1을 더하는 명령의 register 입력이 이미 틀렸다면, 그 명령의 덧셈기를 먼저 의심하기보다 입력을 만든 앞선 명령을 추적한다. PC가 틀리면 연산값 이전에 명령 흐름을 확인한다. 의미 단위의 기록은 첫 결과 차이를 그 차이에 필요한 전제와 연결한다.

다만 RVFI의 order는 단순한 로그 출력 시각이 아니다. 원문은 인과성을 보존하는 재정렬 출력을 허용하며, 선택적인 rollback 인터페이스가 활성화된 경우 이전에 출력한 특정 order 이상의 항목이 무효화될 수 있다. rollback과 같은 사이클의 새 항목은 새 흐름에 속한다. 이런 관측 계약을 무시하고 모든 줄을 영구 완료로 읽으면 trace 분석 자체가 틀린다.

적은 로그로 충분한 문맥을 남긴다

transaction trace도 거래 수가 많으면 커진다. 일정 개수의 최근 기록을 ring buffer에 보관하고 실패 때 저장하거나, 주소별 마지막 쓰기와 미완료 요청을 요약하는 방법을 사용할 수 있다. 그러나 오래된 원인이 buffer 밖으로 사라졌다면 최근 기록만으로 최초 원인을 찾을 수는 없다. 필요한 보존 범위는 오류의 지연과 상태 의존성에 맞춰 정한다.

텍스트를 사람이 읽기 좋게 만드는 것과 자동 비교하기 좋게 만드는 것도 함께 고려한다. field 이름과 숫자 표현을 일관되게 하고, expected와 actual을 별도 field로 두면 두 실행의 첫 차이를 찾기 쉽다. 여러 thread의 출력이 섞일 수 있다면 기록의 식별과 정렬 규칙도 명시한다.

trace가 파형을 모두 대신하는 것은 아니다. accepted transaction은 glitch, 잘못된 샘플링, stall 중 payload 변화 같은 사이클 내부 문제를 충분히 보여 주지 못할 수 있다. 의미 기록으로 후보 거래를 좁힌 뒤 관련 구간의 신호와 시간 단계를 자세히 확인하는 편이 효율적이다.

transaction trace는 뒤늦은 읽기 mismatch를 이전 쓰기와 연결하는 길을 만든다. 연결 정보와 순서, mask 및 reset 의미가 있어야 값의 나열이 조사 가능한 기록이 된다. 먼저 관측된 차이를 찾고 그 차이를 만든 전제로 거슬러 올라가면, 긴 파형에서도 원인 후보를 구체적으로 줄일 수 있다.