저번 글에서는 서로 다른 clock 도메인간의 통신에서 Gray code가 가지는 신뢰성에 대해서 알아보았습니다. Gray Code는 Asynchronous FIFO의 Pointer로 사용하게 되는데, gray counter의 형태로 사용됩니다. Asynchronous FIFO에 대해 공부하기 전에 gray counter부터 공부하겠습니다.
말 그대로 counter인데, binary counter가 아닌 gray counter입니다. 어떤 방법으로 gray counter를 verilog로 구현할 수 있을까요? 또 FIFO의 어디에 사용될까요?

Gray code를 보면 0~7, 8~15가 MSB를 제외한 하위 3bit는 대칭 구조임을 그림을 통해 알 수 있습니다.
FIFO에서는 pointer의 하위비트로 buffer address를 표현합니다. 예를 들어, Pointer가 4비트면 하위 3비트는 buffer address로 사용됩니다. 하지만 gray code의 하위 3비트를 사용하게 되면, 7에서 8이 될 때, 하위 3비트의 변화가 없습니다. 또한 Binary에서는 0~7의 하위 3비트가 8~15의 하위 3비트와 순서와 값이 일치하는데, gray code에서는 그러지 못합니다. 제대로된 buffer address의 역할을 하지 못합니다.
따라서 4비트의 pointer를 address로 변환할 때, 상위 2비트를 xor([3]^[2])하는 방법을 사용합니다.
Gray Converted Address Gray Converted Address
0(0000) --> 0(000) <-> 15(1000) --> 7(100)
1(0001) --> 1(001) <-> 14(1001) --> 6(101)
2(0011) --> 2(011) <-> 13(1011) --> 5(111)
3(0010) --> 3(010) <-> 12(1010) --> 4(110)
4(0110) --> 4(110) <-> 11(1110) --> 3(010)
5(0111) --> 5(111) <-> 10(1111) --> 2(011)
6(0101) --> 6(101) <-> 9 (1101) --> 1(001)
7(0100) --> 7(100) <-> 8 (1100) --> 0(000)
그러면 buffer address가 0~7까지 갔다가 다시 0~7을 반복하게 됩니다.
이를 공식화하면 아래와 같습니다.

요약하자면 Clock Domain을 넘어가는 Code는 N비트 Gray Code이지만, 하위 (N-1)비트를 그대로 address로 사용하지 않고, 위와 같은 공식을 통해서 (N-1)bit Gray code Counter로 변환하여 buffer address로서 사용합니다.
CDC를 수행할 때, Multi-bit의 전송이 필요할 때 많이 사용되는 Asynchronous FIFO에 대해 알아보겠습니다.
Asynchronous FIFO에서 가장 중요한 점은 Asynchronous FIFO의 데이터가 아닌 Write Domain과 Read Domain의 Pointer에 CDC(Clock Domain Crossing) 기법을 적용한다는 것입니다. 포인터가 핵심!!
Asynchronous FIFO에 저장된 데이터가 stable해야하는거 아니냐구요? 데이터는 Pointer들의 CDC가 이뤄지고, Full/Empty를 판별하는 것으로 Stable한 상태임을 보장받습니다. Full/Empty가 확실하게 stable하면 데이터도 stable하다는 뜻입니다.

Asynchronous FIFO에서 write 할때는 Read domain으로부터 read pointer를 받아 full 신호를 판단하고 read를 할때는 wirte domain으로부터 write pointer를 받아 empty를 판단합니다.
일반 Binary Counter를 Pointer로 사용하는 FIFO의 Empty와 Full 조건
1. Empty Condition
if(Write Pointer == Read Pointer) Empty = 1;
else Empty = 0;
2. Full Condition
if((Write Pointer[MSB] != Read Pointer[MSB])
&& (Write Pointer[MSB-1:0] == Read Pointer[MSB-1:0])) Full = 1;
else Full = 0;
같은 방식을 Gray Counter를 사용하는 Pointer에 적용하면 아래와 같다.
1. Empty Condition
if(Write Gray Pointer == Read Gray Pointer) Empty = 1;
else Empty = 0;
2. Full Condition
if (Write Gray Pointer[MSB] != Read Gray Pointer[MSB])
&& (Write Gray Pointer[MSB-1] != Read Gray Pointer[MSB-1])
&& (Write Gray Pointer[MSB-2:0] == Read Gray Pointer[MSB-2:0])) Full = 1;
else Full = 0;
Pointer로부터 buffer address를 추출하여 FULL/EMPTY Condition을 확인합니다.
이것만 알아두면 쉽게 Asynchronous FIFO를 코드로 구현할 수 있습니다.
코드는 인프런 강의의 삼코치님 코드를 참고했습니다.
module async_fifo #(
parameter DEPTH=8,
parameter WIDTH=8,
parameter DEPTH_LOG=$clog2(DEPTH)
)(
input rstn,
// WCLK DOMAIN
input wclk,
input push,
input [WIDTH-1:0] din,
output full,
// RCLK DOMAIN
input rclk,
input pop,
output [WIDTH-1:0] dout,
output empty
);
reg [WIDTH-1:0] mem [DEPTH-1:0];
// WCLK DOMAIN
reg [DEPTH_LOG:0] wptr_bin;
wire [DEPTH_LOG:0] wptr_gray;
reg [DEPTH_LOG:0] rptr_gray_meta, rptr_gray_wclk;
// RCLK DOMAIN
reg [DEPTH_LOG:0] rptr_bin;
wire [DEPTH_LOG:0] rptr_gray;
reg [DEPTH_LOG:0] wptr_gray_meta, wptr_gray_rclk;
// WCLK DOMAIN
always @(posedge wclk or negedge rstn)
if (!rstn) for (int i=0;i<DEPTH;i++) mem[i] <= 0; //mem 초기화
else if (push & !full) mem[wptr_bin[DEPTH_LOG-1:0]] <= din; //push는 write domain
always @(posedge wclk or negedge rstn) //push신호가 들어오면 full이 아닌 경우에 ptr+1
if (!rstn) wptr_bin <= 0;
else if (push & !full) wptr_bin <= wptr_bin + 1;
assign wptr_gray = bin2gray(wptr_bin); //gray신호로 변환
always @(posedge wclk or negedge rstn) //synchronizer를 이용하여 RCLK 도메인 신호를
if (!rstn) begin //WCLK 도메인으로 전달
rptr_gray_meta <= 0;
rptr_gray_wclk <= 0;
end else begin
rptr_gray_meta <= rptr_gray;
rptr_gray_wclk <= rptr_gray_meta; //synchronizer
end
//GRAY STYLE full condition
assign full = (wptr_gray[DEPTH_LOG-:2] == ~rptr_gray_wclk[DEPTH_LOG-:2] ) &&
(wptr_gray[DEPTH_LOG-2:0] == rptr_gray_wclk[DEPTH_LOG-2:0]);
// RCLK DOMAIN
always @(posedge rclk or negedge rstn) //pop
if (!rstn) rptr_bin <= 0;
else if (pop & ~empty) rptr_bin <= rptr_bin + 1;
assign rptr_gray = bin2gray(rptr_bin); //gray신호로 변환
always @(posedge rclk or negedge rstn) //synchronizer를 이용하여 WCLK 도메인 신호를
if (!rstn) begin //RCLK 도메인으로 전달
wptr_gray_meta <= 0;
wptr_gray_rclk <= 0;
end else begin
wptr_gray_meta <= wptr_gray;
wptr_gray_rclk <= wptr_gray_meta;
end
//GRAY STYLE empty condition
assign empty = (rptr_gray == wptr_gray_rclk);
//This will be read when data is stable enough
assign dout = mem[rptr_bin[DEPTH_LOG-1:0]]; //pop data값
function [DEPTH_LOG:0] bin2gray ; //binary code를 gray code로 변환하는 function
input [DEPTH_LOG:0] bin;
begin
bin2gray = (bin>>1) ^ bin;
end
endfunction
endmodule

개념이 생각보다 어려웠습니다.
자세한 내용은 아래 Reference 참고 부탁드립니다.
댓글과 하트 감사합니다😍