out-of-order scoreboard: 먼저 돌아온 응답이 먼저 보낸 요청은 아니다
요청 13을 보낸 뒤 요청 4를 보냈는데, 응답은 4부터 돌아왔다. 두 값은 모두 정확하다. 그러나 정답을 하나의 FIFO에 넣어 비교하면 첫 응답부터 mismatch가 발생한다. 완료 순서가 달라질 수 있는 회로에서 비교 모델은 ‘다음 정답’ 하나를 기억하는 것으로 충분하지 않다.
out-of-order scoreboard는 관측한 응답을 그 응답의 요청과 연결한다. 정답 계산 못지않게 중요한 것은 이 연결 규칙이다.
값이 맞아도 짝이 틀리면 실패한다
가상의 요청·응답 인터페이스를 생각하자. 요청에는 ID가 있고, 동시에 진행 중인 요청들의 ID는 서로 다르다. 각각 정확히 한 응답을 반환하며 서로 다른 ID의 완료 순서는 자유롭다고 정한다. 요청 13의 정답은 33이고 요청 4의 정답은 90이다.
요청 순서로 쌓은 FIFO는 [33, 90]이다. 회로가 (ID=4, 값=90), (ID=13, 값=33) 순으로 응답하면 단순 비교는 90과 33을 먼저 대조한다. 이 실패는 계산 오류가 아니라 연결 오류다. 정답을 ID별로 저장하면 첫 응답은 4의 90과, 둘째는 13의 33과 비교할 수 있다.
UVM 가이드는 scoreboard를 DUT의 예상 기능과 관찰한 동작을 검증하는 구성요소로 설명하며, monitor의 analysis port에서 정보를 받는 예를 보여 준다. 그러나 그 전달 경로만으로 응답의 순서 정책이 결정되지는 않는다. 순서가 바뀔 수 있는지는 대상 인터페이스의 계약에서 가져와야 한다. Accellera UVM User’s Guide 1.1, §4.10 Implementing a Scoreboard
ID로 찾되 비교 전에 기록을 잃지 않는다
위의 단일 응답 계약을 위한 최소 비교 로직은 다음과 같다. pending에는 실제로 수락된 요청의 ID와 독립적으로 계산한 정답을 넣는다. 이미 진행 중인 ID의 재사용은 요청을 등록할 때 오류로 처리한다고 가정한다.
pending = {13: 33, 4: 90}
def accept_response(response_id, actual):
if response_id not in pending:
raise AssertionError("unknown or duplicate response")
expected = pending[response_id]
if actual != expected:
raise AssertionError(
f"id={response_id} expected={expected} actual={actual}")
del pending[response_id]
응답 4를 처리한 뒤에는 13의 정답만 남는다. 응답 13까지 처리하면 비어 있다. 존재하지 않는 ID나 이미 완료한 ID의 응답은 정상 결과와 구분하여 실패시킨다. mismatch가 나기 전에 정답을 무조건 제거하지 않으면, 실패 보고에도 해당 요청의 문맥이 남는다.
정상 응답 두 개를 모두 비교한 것만으로 테스트 종료 조건이 완성되지는 않는다. 요청 13의 응답이 영원히 오지 않으면 pending에 남아야 한다. 테스트 종료 시 남은 요청을 확인하고, 계약에 맞는 timeout이나 진행 조건으로 누락도 판정한다. 그렇지 않으면 ‘돌아온 응답은 모두 맞았다’가 ‘모든 요청을 처리했다’로 바뀌어 버린다.
같은 ID를 여러 번 허용하면 큐가 필요하다
앞의 dictionary는 outstanding ID가 유일할 때만 충분하다. 만약 같은 ID의 요청을 여러 개 허용하면서 그 ID 안에서는 요청 순서대로 응답해야 한다면, ID별 FIFO가 필요하다.
예를 들어 요청이 13:A, 4:B, 13:C이고 ID 13 안에서는 A가 C보다 먼저 완료되어야 한다고 하자. 전체 응답이 4:B, 13:A, 13:C인 것은 합법이다. ID 13의 저장 공간은 [A의 정답, C의 정답], ID 4의 저장 공간은 [B의 정답]이다. 하나의 dictionary 값에 C를 덮어 쓰면 A의 정답이 사라진다.
PULP의 공개 axi_scoreboard는 읽기 요청과 응답을 ID별 배열의 큐에 저장한다. handle_read(id)는 그 ID의 요청을 꺼내고 해당 ID의 응답 beat를 순서대로 처리한다. 쓰기 응답도 ID별 큐를 사용한다. 이 구현은 burst와 메모리 모델까지 다루며 지원하지 않는 atomic transfer 등을 명시하므로, 단순 ID 예제를 완전한 AXI 검증기로 확대해 읽어서는 안 된다. PULP AXI, axi_test.sv의 axi_scoreboard
같은 ID 안에서도 순서를 자유롭게 바꿀 수 있는 별도 프로토콜이라면 ID별 FIFO도 부족하다. 추가 식별자나 그 프로토콜이 보장하는 구분 규칙이 있어야 한다. 수락 시각은 지연 분석과 timeout에는 유용하지만, 회로가 보내지 않는 timestamp를 정답 연결 키로 임의로 사용할 수는 없다.
관대한 연결이 오류를 지우지 않도록 한다
값이 같은 정답을 아무 데서나 찾아 제거하는 방식은 더 위험하다. 서로 다른 요청이 우연히 같은 값을 만들면 잘못된 ID의 응답도 통과할 수 있다. 값의 일치와 요청의 정체성은 따로 확인해야 한다.
또한 모든 완료 순서를 허용하는 scoreboard는 원래 금지된 재정렬을 놓친다. 데이터 비교와 함께 같은 ID 안의 순서, burst 종료, 응답 개수 같은 계약도 검사해야 한다. 모델이 유연하다는 이유로 인터페이스의 제약까지 풀어 주면 안 된다.
먼저 도착한 응답에는 그 응답의 정답이 필요하다. out-of-order scoreboard를 만들 때는 전체 FIFO인지, ID별 FIFO인지, 유일 ID로 찾는 저장소인지부터 계약에 맞춰 정한다. 그런 다음 값, 중복, 누락, 허용되는 순서를 함께 확인해야 올바른 재정렬을 오류로 오인하지 않고 잘못된 연결도 놓치지 않는다.