모든 이야기

데이터가 X여도 괜찮을까, valid 기반 초기화의 조건

reset 직후 파형에서 valid는 0인데 데이터 버스는 X다. 보기에는 초기화가 덜 된 회로 같지만, 이 상태가 항상 기능 오류인 것은 아니다. valid 기반 초기화는 모든 데이터 비트를 같은 값으로 만드는 대신, 어떤 데이터가 의미 있는지를 제어 상태로 확정하는 방식이다. 질문은 데이터가 0인가보다, 무효인 데이터가 동작에 사용될 수 있는가에 가깝다.

여기서는 한 개의 값을 저장하는 동기 버퍼를 생각하자. reset 중에는 거래를 세지 않고, reset 해제 이후에 입력 valid와 ready가 함께 높은 클록 에지에서만 새 데이터를 받는다고 정한다. 외부에서는 출력 valid가 높을 때만 data를 의미 있는 값으로 읽는 계약이다.

비어 있다는 사실부터 초기화하기

assign in_ready  = !valid_q || out_ready;
assign out_valid = valid_q;
assign out_data  = data_q;

always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n)
    valid_q <= 1'b0;
  else if (in_ready)
    valid_q <= in_valid;
end

always_ff @(posedge clk) begin
  if (rst_n && in_valid && in_ready)
    data_q <= in_data;
end

data_q에는 reset 대입이 없다. reset을 거친 뒤 valid_q는 0이고 버퍼는 비어 있다. data_q가 어떤 값인지는 아직 정하지 않았다. 4-state 시뮬레이션에서는 X가 남을 수 있고 실제 회로의 전원 직후 값도 보장하지 않는다. 하지만 out_valid=0이므로 이 값은 유효한 출력이 아니다.

처음으로 in_valid=1, in_ready=1인 에지가 오면 data_q는 입력을 받고 valid_q도 1이 된다. 예를 들어 설명용 입력을 8'h52로 두면 에지 이후 유효한 출력은 0x52다. 이후 out_ready=0이고 버퍼가 차 있다면 in_ready=0이므로 데이터와 valid가 유지된다. out_ready=1인 동안 다음 입력도 유효하다면 현재 출력을 내보내면서 새 값으로 교체할 수 있다.

이 코드의 핵심 조건은 valid가 1이 되는 에지에 해당 데이터도 실제로 기록된다는 것이다. valid만 먼저 올리고 데이터 쓰기를 한 에지 미루면, 그 사이 오래된 데이터나 초기 X가 유효하다고 선언된다. reset을 생략해도 된다는 일반론이 아니라, 제어와 데이터의 정렬이 보장될 때의 기능 계약이다.

무효한 데이터가 비교기에 들어가면

데이터 경로의 일부가 X여도 그 결과를 사용하지 않으면 기능적으로 문제가 없을 수 있다. 그러나 data를 읽어 부작용을 만드는 경로가 valid를 빼먹으면 상황이 달라진다.

assign is_command = (out_data == 8'hA5);
assign launch_bad = is_command;
assign launch_ok  = out_valid && is_command;

launch_bad는 버퍼가 비어 있어도 data_q의 잔여 값을 명령으로 해석할 수 있다. launch_ok는 출력의 의미가 있을 때만 비교 결과를 사용한다. reset으로 valid=0이 확정된 4-state 논리에서 0 && X는 0이므로, 무효 데이터의 불확실성이 이 동작을 켜지 못한다.

같은 원칙은 주소, 누적합, 인터럽트, 오류 상태에도 적용된다. 무효 payload가 출력에 보이는 것 자체와, 그 payload가 저장 상태를 바꾸는 것은 다르다. valid 기반 초기화를 선택했다면 데이터의 모든 의미 있는 소비 경로에 유효성 조건이 포함되어야 한다. 눈에 보이는 출력 하나만 가려서는 충분하지 않다.

lowRISC의 X와 출력 검사 지침은 초기의 무효 데이터와 실제 사용 시의 확정성을 구분하는 데 참고할 수 있다. 다음과 같이 유효 출력일 때만 데이터가 알려져 있어야 한다는 조건도 명시할 수 있다.

assert property (@(posedge clk) disable iff (!rst_n)
  out_valid |-> !$isunknown(out_data));

이 assertion은 4-state 검사의 예다. 이것만으로 데이터가 기대한 값과 같다는 사실을 증명하지는 않는다. 유효한 데이터가 X가 아닌지와, 입력 순서대로 올바른 값이 나오는지는 별도 조건이다.

파형을 깨끗하게 만드는 reset의 비용

데이터 레지스터에도 reset을 넣으면 초기 파형이 더 읽기 쉬워진다. 요구하는 동작이 reset 후 data=0이라면 그렇게 해야 한다. 하지만 payload의 폭이 크고 valid=0일 때 데이터에 의미가 없다면, 초기 숫자를 일정하게 만들기 위해 모든 비트를 reset하는 것이 필수 기능은 아닐 수 있다.

구현에서는 reset 가능한 셀의 선택, reset 배선, fan-out과 타이밍 제약이 달라질 수 있다. 어느 쪽이 실제로 더 작고 빠른지는 라이브러리와 합성·배치 결과에 따라 결정된다. 위 예제가 reset 생략의 면적 절감 비율을 보장하는 것은 아니다. 먼저 기능상 필요한 초기 상태의 범위를 정하고, 그다음 구현상의 효과를 확인하는 순서가 맞다.

또 valid만 초기화하면 테스트벤치도 계약을 따라야 한다. 매 사이클 out_data를 정답 값과 비교하는 검사기는 버퍼가 비어 있는 동안 불필요하게 실패할 수 있다. 반대로 out_valid=1인 거래를 건너뛰면 진짜 문제를 놓친다. 검사는 유효한 출력의 값과 순서를 비교하되, invalid 구간에 데이터 안정성 요구가 있는지는 따로 판단해야 한다.

기능상 무효와 흔적 제거는 같은 요구가 아니다

valid=0은 “이 값으로 기능을 수행하지 않는다”는 선언이다. 데이터 비트가 실제로 지워졌다는 보장은 아니다. 이전 값을 읽을 수 있는 debug 경로가 있거나, 보안 요구가 잔여 데이터의 제거를 요구한다면 valid를 내리는 것만으로 충족되지 않는다. 그런 경우에는 초기화와 별개로 접근 제한 또는 삭제 동작이 필요할 수 있다.

또 reset 해제와 첫 입력이 맞물리는 경우, 클록 도메인이나 비동기 reset의 취급이 달라지는 경우에는 이 글의 단일 동기 버퍼 모델만으로 판단할 수 없다. reset 이후 유효성 제어가 알려진 상태인지, 데이터가 준비된 에지와 정확히 맞는지를 구현 조건에 맞게 확인해야 한다. OpenTitan의 형식 검증 흐름 같은 속성 기반 접근은 reset 뒤에 유효 데이터가 언제 생기는지 명시하는 데 유용하다.

처음의 X 버스는 이제 “reset이 없는 데이터”라는 표면만으로 평가하지 않는다. valid가 0으로 확정되어 있고, 그 상태에서는 어떤 소비자도 데이터를 동작에 쓰지 않으며, valid가 올라갈 때 새 데이터가 이미 기록된다면 기능 계약은 성립할 수 있다. valid 기반 초기화의 정당성은 0으로 채운 비트 수가 아니라, 무효 상태를 끝까지 무효로 취급하는 회로의 일관성에 달려 있다.

데이터가 X여도 괜찮을까, valid 기반 초기화의 조건 · ariser