모든 이야기

Idle로 돌아가면 끝일까, 불법 상태 복구의 남은 조건

상태기계가 모르는 비트 패턴을 만나면 Idle로 돌아가도록 만들었다. 다음 클록에는 정상 상태가 되니 복구가 끝난 것처럼 보인다. 하지만 그 직전 사이클에 쓰기 신호가 켜졌거나, 진행 중이던 거래의 예약이 남았다면 어떨까. 불법 상태 복구는 정상 상태 번호를 다시 넣는 동작보다, 잘못된 상태가 보이는 동안과 그 이후의 동작을 정의하는 문제다.

불법 상태는 RTL에서 정의한 상태 집합에 속하지 않는 레지스터 값이다. 초기화 문제, 의도한 검증 주입, 물리적인 고장 등 다양한 경로로 생각할 수 있다. 여기서는 원인별 발생 확률을 계산하지 않고, 상태 비트가 지정한 집합 밖으로 바뀌었을 때 제어기가 무엇을 해야 하는지에 집중한다.

한 비트가 바뀌었을 때 출력부터 보기

가상의 제어기에 Idle=0001, Work=0010, Commit=0100, Fault=1000을 사용하자. 한 비트만 켜진 one-hot 인코딩이다. 상태기계는 Commit에서 유효한 결과를 외부에 쓴다고 가정한다.

만약 출력 논리를 다음처럼 단순화했다면 문제가 생길 수 있다.

assign write_enable = state_q[2] && result_valid;

정상 상태 집합에서 bit2는 Commit만 구분하므로 식이 맞아 보인다. 하지만 Idle의 0001이 0101로 바뀌면 상태는 불법인데 bit2는 1이다. result_valid도 남아 있다면 쓰기 신호가 켜진다. 다음 에지에 default가 Fault나 Idle로 보내더라도, 그 전에 출력 계약이 이미 깨질 수 있다.

안전한 출력 요구가 있다면 불법 상태에서 부작용을 내지 않는 조건을 함께 정의해야 한다.

assign state_legal = (state_q == 4'b0001)
                  || (state_q == 4'b0010)
                  || (state_q == 4'b0100)
                  || (state_q == 4'b1000);

assign write_enable = state_legal
                   && (state_q == 4'b0100)
                   && result_valid;

이 예제에서는 Commit의 정확한 비교만으로도 다른 값의 쓰기가 막히지만, state_legal을 따로 적어 합법성 검사와 출력 정책의 관계를 드러냈다. 실제 회로가 모든 고장에 안전하다는 주장은 아니다. 상태 레지스터의 비트 패턴이 바뀌는 모델에서 불법 값의 기능을 정의한 것이다.

돌아갈 장소를 선택하는 일

불법 상태에서 Idle로 돌아가는 정책은 정상 서비스 재개를 우선한다. 반면 Fault로 가서 멈추는 정책은 오류를 숨기지 않고 처리 주체에게 넘긴다. 어느 쪽이 맞는지는 진행 중이던 동작을 잃어도 되는지, 재실행이 안전한지, 외부 부작용이 있었는지에 따라 달라진다.

always_comb begin
  state_d = state_q;
  accept_new = 1'b0;
  case (state_q)
    4'b0001: begin
      accept_new = 1'b1;
      if (request) state_d = 4'b0010;
    end
    4'b0010: if (finished) state_d = 4'b0100;
    4'b0100: if (committed) state_d = 4'b0001;
    4'b1000: ;
    default: state_d = 4'b1000;
  endcase
end

이 설명용 다음 상태 블록에서 Fault는 새 요청을 받지 않고, 별도의 reset 복구를 기다린다. request, finished, committed는 각 단계의 사건이라고 가정했다. 불법 0101이면 accept_new=0이고 다음 상태는 Fault다. 정상으로 보이는 처리를 임의로 재개하지 않는다.

lowRISC의 FSM 작성 지침은 현재 상태와 다음 상태, 출력 기본값, default 경로를 구분해서 작성한다. 그 틀에서 어떤 default 정책이 시스템 요구에 맞는지 결정하는 것은 설계자의 몫이다.

상태 번호만 고치면 다른 상태는 남는다

Work에서 결과 슬롯을 예약하고 Commit에서 해제하는 제어기를 생각하자. 불법 상태를 Idle로 덮어쓰면 슬롯은 여전히 예약되어 있을 수 있다. 다음 요청은 영원히 막히거나, 반대로 같은 슬롯을 중복해서 예약할 수 있다. 상태 번호가 합법해진 것과 제어기의 전체 불변식이 복구된 것은 다르다.

따라서 복구에는 해당 작업을 오류 완료로 처리할지, 취소하고 예약을 풀지, 모듈 전체 reset을 요구할지의 정책이 필요하다. 결과의 valid, 진행 카운터, 요청 식별자처럼 상태기계와 함께 움직이는 정보도 같은 정책을 따라야 한다. Fault로 멈추는 방식도 외부가 오류를 알아야 유용하다. 멈췄는데 완료 신호도 오류 신호도 없으면 소비자는 정상 완료를 계속 기다린다.

중복 실행에도 주의가 필요하다. Work에서 이미 외부 요청을 보낸 뒤 Idle로 돌아가 같은 입력을 다시 받으면 외부 동작이 두 번 실행될 수 있다. 재시작은 복구처럼 보이지만, 그 동작이 반복해도 안전하다는 보장이 없다면 새 실패를 만든다.

인코딩이 검출하는 범위도 정해져 있다

one-hot에서 정상 코드 0001의 한 비트를 뒤집으면 0000 또는 비트가 둘 켜진 값이 된다. 모두 위 정상 집합 밖이다. 이 단일 비트 변경 모델에서는 합법성 검사로 검출할 수 있다. 하지만 여러 비트가 동시에 바뀌어 0001이 0100으로 바뀌면 다른 합법 상태다. 단순한 집합 검사는 이를 발견하지 못한다.

다른 인코딩도 같은 방식으로 어떤 변경을 검출하는지 따져야 한다. 상태 사이의 비트 거리가 크다고 모든 출력 경로의 고장까지 보호되는 것은 아니다. 검사 논리 자체나 result_valid 같은 다른 제어가 잘못되면 별도의 문제가 생긴다. 검출 범위를 상태 비트의 변경 모델보다 넓게 주장하면 안 된다.

OpenTitan의 하드웨어 보안 구현 지침은 희소한 FSM 인코딩과 불법 상태, 합성 과정에서 보호 구조가 최적화될 수 있는 문제를 다룬다. RTL에서 고른 상태 코드가 구현에서도 그대로 유지되는지 확인해야 하는 이유다. 합성기가 상태를 재인코딩했다면 같은 비트 변경 모델의 의미도 달라질 수 있다.

복구를 검증하는 관측 기준

불법 값을 주입해 다음 상태가 Fault가 되는지만 검사하면 출력의 짧은 부작용을 놓칠 수 있다. 불법 상태가 있는 동안 새 거래가 수락되지 않는지, 외부 쓰기가 발생하지 않는지, 기존 거래가 어떤 오류로 끝나는지 함께 봐야 한다. 정상 상태로 돌아간 이후에도 예약 수량과 결과 식별자가 일관되어야 한다.

정상 동작을 증명하기 위해 불법 상태가 절대 없다고 가정하는 검증과, 불법 상태에서의 복구를 검증하는 작업도 구별해야 한다. 전자는 후자의 입력을 탐색 대상에서 제외한다. 목적이 다르므로 환경 조건도 달라져야 한다.

처음의 default→Idle은 복구의 한 줄일 뿐 전체 답은 아니었다. 불법 상태 복구가 완료되려면 즉시 출력, 다음 상태, 함께 보유한 거래와 자원, 검출 가능한 고장의 범위를 연결해서 설명할 수 있어야 한다. 상태 번호가 정상이라는 표면보다, 다시 믿을 수 있는 동작을 시작할 조건이 무엇인지가 더 중요한 기준이다.