토글 CDC는 짧은 펄스를 살리지만 이벤트 개수는 기억하지 않는다
짧은 이벤트 펄스를 그대로 보내면 느린 클록이 놓칠 수 있다. 그래서 이벤트가 올 때마다 한 비트를 반전시키면 어떨까. 펄스가 사라져도 반전된 상태는 남는다. 이것이 토글 CDC의 출발점이다. 토글은 0과 1을 번갈아 바꾸는 동작이고, CDC는 서로 다른 클록 도메인 사이의 전달을 뜻한다.
그러나 상태가 남는다는 사실이 이벤트 개수까지 무제한으로 보존한다는 뜻은 아니다. 토글 한 비트가 가진 정보는 마지막 수준과 이전 수준의 차이뿐이다. 두 번 반전하면 원래 수준으로 돌아온다. 이 단순한 성질이 토글 전달의 속도 제한을 만든다.
펄스를 상태 변화로 바꾸기
원천 클록에서 이벤트를 받을 때마다 event_toggle을 반전시킨다. 목적지에서는 그 수준을 동기화한 뒤, 이전에 본 수준과 다른지를 검사한다.
always_ff @(posedge src_clk or negedge src_rst_n) begin
if (!src_rst_n)
event_toggle <= 1'b0;
else if (src_event)
event_toggle <= !event_toggle;
end
always_ff @(posedge dst_clk or negedge dst_rst_n) begin
if (!dst_rst_n) begin
sync1 <= 1'b0;
sync2 <= 1'b0;
previous <= 1'b0;
end else begin
sync1 <= event_toggle;
sync2 <= sync1;
previous <= sync2;
end
end
assign dst_event = sync2 ^ previous;
이 설명용 코드는 두 reset이 같은 초기 상태를 만들고, 정상 운용 중 독립적인 reset은 없다고 가정한다. 원천의 src_event는 해당 에지의 이벤트 한 개를 나타낸다. 목적지의 dst_event는 동기화된 수준이 이전 관측과 다를 때 높다.
원천이 한 번 반전해 1을 유지하면, 목적지의 첫 단계가 이를 포착하고 두 번째 단계로 옮긴다. sync2가 1로 바뀌고 previous가 아직 0인 구간에 XOR가 1이다. 다음 목적지 에지 뒤 previous도 1이 되면 다시 0이다. 목적지 동기 소비자는 이 펄스가 높은 에지에 이벤트를 센다.
PULP의 2-phase CDC 구현은 요청 수준을 반전시키고, 그 변화를 목적지에서 식별하는 실제 사례다. 원문은 단순 이벤트 관측뿐 아니라 확인 신호를 포함하는 프로토콜이다. 위 최소 코드는 그중 토글 변화 표현을 설명하기 위한 별도 예제이며 같은 전달 보장을 가진 구현은 아니다.
두 번의 반전이 지워지는 반례
목적지 에지가 0ns와 10ns에 있고, 원천 이벤트가 2ns와 6ns에 각각 발생했다고 가정하자. event_toggle은 0→1→0으로 바뀐다. 첫 목적지 플립플롭은 0ns에 0, 10ns에도 0을 본다. 두 이벤트가 있었다는 흔적은 동기화 체인에 들어오지 않는다.
이 반례에는 준안정성이 필요하지 않다. 두 변화가 모두 샘플링 사이에서 끝나면, 목적지는 변화가 없었던 상황과 구별할 수 없다. 이벤트가 셋이면 최종 수준이 달라져 변화 하나로 보일 수 있다. 토글은 이벤트 개수의 홀짝 일부를 나타낼 뿐, 관측하지 못한 모든 개수를 복원하지 못한다.
따라서 확인 없는 토글을 쓸 때에는 각 수준이 목적지에서 확실히 관측될 만큼 이벤트 사이 간격이 있어야 한다. 특정 클록 비율 하나에서 성공한 테스트만으로 모든 위상과 속도를 보장할 수 없다. 목적지 클록이 멈춘 동안 원천 이벤트가 계속 발생한다면 어떤 고정 간격 가정도 유지되지 않을 수 있다.
확인을 돌려보내면 바뀌는 계약
이벤트마다 수신을 확인하고 싶다면 목적지가 관측한 수준을 원천으로 돌려보낼 수 있다. 원천은 요청 토글과 동기화된 확인 토글이 같을 때만 다음 이벤트를 허용한다.
source_ready = (request_toggle == acknowledged_toggle)
초기에 둘 다 0이면 새 요청을 보낼 수 있다. 요청을 1로 바꾸면 확인은 아직 0이므로 더 보내지 못한다. 목적지가 요청을 처리한 뒤 확인을 1로 바꾸고, 그 값이 원천으로 동기화되어 돌아와야 다시 보낼 수 있다. 한 요청이 두 번 반전되어 사라지는 일을 막는다.
대신 처리율은 왕복 시간에 제한된다. 목적지로 가는 동기화, 목적지의 수락 대기, 원천으로 돌아오는 동기화가 필요하다. 원천 이벤트가 이보다 빠르다면 상류에 기다림을 요구하거나 저장 큐를 두어야 한다. 원천이 기다릴 수 없는 이벤트를 버퍼도 없이 계속 토글하면 확인 프로토콜 밖에서 정보가 다시 손실된다.
PULP 구현의 src_ready가 요청과 확인의 동등성으로 정해지는 이유도 여기에 있다. 목적지에서 단지 토글을 보았는지, payload를 보관했는지, 최종 소비자가 수락했는지를 어느 사건으로 확인하는지도 구현 계약에 따라 달라진다. 확인의 의미가 명확해야 소유권이 끝나는 시점이 정해진다.
reset이 만드는 변화도 구별해야 한다
event_toggle이 1일 때 원천만 reset하여 0으로 만들면 목적지는 이를 새로운 변화로 볼 수 있다. 실제 이벤트 없이 dst_event가 생기는 것이다. 반대로 목적지의 previous만 초기화하면 이미 처리한 높은 수준을 다시 이벤트로 해석할 수 있다.
그러므로 초기 수준이 같다는 조건과, 운용 중 reset에서 무엇을 취소하거나 다시 맞추는지는 별도로 필요하다. 위 PULP 원문도 기본 2-phase CDC의 warm reset 제약을 명시한다. 토글을 데이터 없는 간단한 비트처럼 생각하고 reset 조건을 생략하면 유령 이벤트나 영구 대기가 생길 수 있다.
검증에서 유용한 질문은 “펄스가 보이는가”보다 “허용한 원천 이벤트 각각이 목적지에서 정확히 한 번 세어지는가”다. 너무 가까운 이벤트는 최소 코드의 허용 범위 밖임을 드러내고, 확인형 구조에서는 ready를 무시한 원천이 있는지도 검사한다. 전달 회로와 상류의 이벤트 생성 조건을 함께 보아야 한다.
토글 CDC는 짧은 펄스가 오래 남는 상태 변화로 바뀐다는 점에서 유용하다. 하지만 토글이 보존하는 것은 아직 관측하지 않은 변화 하나이지, 무제한 이벤트 이력은 아니다. 이벤트 간격을 보장하거나 확인으로 다음 변화를 제한할 때, 비트 한 개는 느린 도메인에서도 놓치지 않는 전달 계약의 일부가 될 수 있다.