[CDC] 10_Multi Bit CDC: Asynchronous FIFO (Code, Simulation)

2025. 12. 21. 21:00·VLSI/Design
728x90
반응형

Asynchronous FIFO의 SystemVerilog 코드와 시뮬레이션을 확인해보자.

 

Code

module async_fifo #(
    parameter int DEPTH  = 8,
    parameter int DWIDTH = 16
)(
    input  logic              wclk,
    input  logic              rclk,
    input  logic              rst_n,

    input  logic              wr_en,
    input  logic              rd_en,
    input  logic [DWIDTH-1:0] din,

    output logic [DWIDTH-1:0] dout,
    output logic              full,
    output logic              empty
);

    // --------------------------------------
    // Local parameters
    // --------------------------------------
    localparam int ADDR_W = $clog2(DEPTH);

    // --------------------------------------
    // Memory
    // --------------------------------------
    logic [DWIDTH-1:0] mem [0:DEPTH-1];

    // --------------------------------------
    // Binary pointers (with wrap bit)
    // --------------------------------------
    logic [ADDR_W:0] wptr_bin, rptr_bin;

    // --------------------------------------
    // Gray pointers
    // --------------------------------------
    logic [ADDR_W:0] wptr_gray, rptr_gray;
    logic [ADDR_W:0] wptr_gray_next, rptr_gray_next;

    // --------------------------------------
    // Synchronized Gray pointers
    // --------------------------------------
    logic [ADDR_W:0] rptr_gray_wclk_ff1, rptr_gray_wclk_ff2;
    logic [ADDR_W:0] wptr_gray_rclk_ff1, wptr_gray_rclk_ff2;

    // --------------------------------------
    // Binary → Gray
    // --------------------------------------
    function automatic [ADDR_W:0] bin2gray(input [ADDR_W:0] bin);
        return (bin >> 1) ^ bin;
    endfunction

    // ================================================================
    // WRITE DOMAIN
    // ================================================================
    assign wptr_gray      = bin2gray(wptr_bin);
    assign wptr_gray_next = bin2gray(wptr_bin + 1'b1);

    // Write pointer
    always_ff @(posedge wclk or negedge rst_n) begin
        if (!rst_n)
            wptr_bin <= '0;
        else if (wr_en && !full) begin
            mem[wptr_bin[ADDR_W-1:0]] <= din;
            wptr_bin <= wptr_bin + 1'b1;
        end
    end

    // Read pointer Gray sync into write clock
    always_ff @(posedge wclk or negedge rst_n) begin
        if (!rst_n) begin
            rptr_gray_wclk_ff1 <= '0;
            rptr_gray_wclk_ff2 <= '0;
        end else begin
            rptr_gray_wclk_ff1 <= rptr_gray;
            rptr_gray_wclk_ff2 <= rptr_gray_wclk_ff1;
        end
    end

    // Full condition (Gray style)
    assign full =
        (wptr_gray_next[ADDR_W:ADDR_W-1] ==
         ~rptr_gray_wclk_ff2[ADDR_W:ADDR_W-1]) &&
        (wptr_gray_next[ADDR_W-2:0] ==
         rptr_gray_wclk_ff2[ADDR_W-2:0]);

    // ================================================================
    // READ DOMAIN
    // ================================================================
    assign rptr_gray      = bin2gray(rptr_bin);
    assign rptr_gray_next = bin2gray(rptr_bin + 1'b1);

    // Read pointer
    always_ff @(posedge rclk or negedge rst_n) begin
        if (!rst_n) begin
            rptr_bin <= '0;
            dout     <= '0;
        end else if (rd_en && !empty) begin
            dout     <= mem[rptr_bin[ADDR_W-1:0]];
            rptr_bin <= rptr_bin + 1'b1;
        end
    end

    // Write pointer Gray sync into read clock
    always_ff @(posedge rclk or negedge rst_n) begin
        if (!rst_n) begin
            wptr_gray_rclk_ff1 <= '0;
            wptr_gray_rclk_ff2 <= '0;
        end else begin
            wptr_gray_rclk_ff1 <= wptr_gray;
            wptr_gray_rclk_ff2 <= wptr_gray_rclk_ff1;
        end
    end

    // Empty condition
    assign empty = (rptr_gray == wptr_gray_rclk_ff2);

endmodule

module tb_async_fifo;

    // --------------------------------------------------
    // Parameters
    // --------------------------------------------------
    localparam int DEPTH  = 8;
    localparam int DWIDTH = 16;

    // --------------------------------------------------
    // DUT signals
    // --------------------------------------------------
    logic              wclk;
    logic              rclk;
    logic              rst_n;

    logic              wr_en;
    logic              rd_en;
    logic [DWIDTH-1:0] din;
    logic [DWIDTH-1:0] dout;
    logic              full;
    logic              empty;

    // --------------------------------------------------
    // Clock generation
    // --------------------------------------------------
    initial wclk = 0;
    always #5  wclk = ~wclk;   // 100 MHz

    initial rclk = 0;
    always #7  rclk = ~rclk;   // ~71 MHz (async)

    // --------------------------------------------------
    // DUT
    // --------------------------------------------------
    async_fifo #(
        .DEPTH (DEPTH),
        .DWIDTH(DWIDTH)
    ) dut (
        .wclk  (wclk),
        .rclk  (rclk),
        .rst_n (rst_n),
        .wr_en (wr_en),
        .rd_en (rd_en),
        .din   (din),
        .dout  (dout),
        .full  (full),
        .empty (empty)
    );

    // --------------------------------------------------
    // Test sequence
    // --------------------------------------------------
    initial begin
        // init
        rst_n = 0;
        wr_en = 0;
        rd_en = 0;
        din   = 0;

        #50;
        rst_n = 1;

        // ----------------------------------------------
        // 1. WRITE until FULL
        // ----------------------------------------------
        $display("\n=== WRITE UNTIL FULL ===");
        repeat (DEPTH) begin
            @(posedge wclk);
            if (!full) begin
                wr_en = 1;
                din   = din + 1;
            end
        end
        @(posedge wclk);
        wr_en = 0;

        // Attempt overflow
        @(posedge wclk);
        wr_en = 1;
        din   = 16'hDEAD;
        @(posedge wclk);
        wr_en = 0;

        // ----------------------------------------------
        // 2. READ until EMPTY
        // ----------------------------------------------
        $display("\n=== READ UNTIL EMPTY ===");
        repeat (DEPTH) begin
            @(posedge rclk);
            if (!empty)
                rd_en = 1;
        end
        @(posedge rclk);
        rd_en = 0;

        // Attempt underflow
        @(posedge rclk);
        rd_en = 1;
        @(posedge rclk);
        rd_en = 0;

        // ----------------------------------------------
        // Finish
        // ----------------------------------------------
        #100;
        $finish;
    end

    // --------------------------------------------------
    // Monitor
    // --------------------------------------------------
    initial begin
        $display("time | wr rd | din     dout    | full empty");
        $monitor("%4t |  %0b  %0b | 0x%04h 0x%04h |  %0b     %0b",
                 $time, wr_en, rd_en, din, dout, full, empty);
    end

    // --------------------------------------------------
    // Waveform
    // --------------------------------------------------
    initial begin
        $dumpfile("async_fifo.vcd");
        $dumpvars(0, tb_async_fifo);
    end

endmodule

 

wptr_gray_next는 “이번 사이클에 write를 하면 다음 사이클에 포인터가 가리킬 값”을 미리 계산해서, overflow를 사전에 막기 위해 필요하다.

 

조금만 풀면, Async FIFO에서 full은 “이미 이동한 포인터”가 아니라 “이동하려는 다음 포인터” 기준으로 판단해야 한다.
예를 들어 write 도메인에서 full을 현재 wptr_gray로 비교하면, 이미 한 번 더 써버린 뒤에야 full이 검출되어 메모리를 한 칸 침범할 수 있다. 그래서 wptr_gray_next = bin2gray(wptr_bin + 1) 를 미리 만들어서 “지금 write를 하면 다음 포인터가 read 포인터를 넘어서지 않는가?” 를 사전에 체크하는 것이다.

 

rptr_gray_next도 선언은 했지만 굳이 사용하지 않아도 문제가 없다. 보수적 설계에서 언급했듯이, read 쪽은 조금 늦게 막아도 메모리의 데이터를 망가뜨리지 않는다. 즉 지연되도 문제가 없다는 의미이다.

 

Simulation

time | wr rd | din     dout    | full empty
   0 |  0  0 | 0x0000 0x0000 |  0     1

=== WRITE UNTIL FULL ===
  55 |  1  0 | 0x0001 0x0000 |  0     1
  65 |  1  0 | 0x0002 0x0000 |  0     1
  75 |  1  0 | 0x0003 0x0000 |  0     1
  77 |  1  0 | 0x0003 0x0000 |  0     0
  85 |  1  0 | 0x0004 0x0000 |  0     0
  95 |  1  0 | 0x0005 0x0000 |  0     0
 105 |  1  0 | 0x0006 0x0000 |  0     0
 115 |  1  0 | 0x0007 0x0000 |  1     0
 135 |  0  0 | 0x0007 0x0000 |  1     0
 145 |  1  0 | 0xdead 0x0000 |  1     0

=== READ UNTIL EMPTY ===
 155 |  0  0 | 0xdead 0x0000 |  1     0
 161 |  0  1 | 0xdead 0x0001 |  1     0
 175 |  0  1 | 0xdead 0x0002 |  0     0
 189 |  0  1 | 0xdead 0x0003 |  0     0
 203 |  0  1 | 0xdead 0x0004 |  0     0
 217 |  0  1 | 0xdead 0x0005 |  0     0
 231 |  0  1 | 0xdead 0x0006 |  0     0
 245 |  0  1 | 0xdead 0x0007 |  0     1
 273 |  0  0 | 0xdead 0x0007 |  0     1
 287 |  0  1 | 0xdead 0x0007 |  0     1
 301 |  0  0 | 0xdead 0x0007 |  0     1

 

 

WRITE 구간에서 wr_en이 wclk 기준으로 올라가면서 wptr_bin이 증가하고 데이터가 메모리에 정상적으로 저장된다. 이때 empty 신호는 즉시 0으로 내려가지 않는데, 이는 write pointer(wptr_gray)가 read 클럭 도메인으로 Gray + 2FF synchronizer를 거쳐 전달되기 때문이다. 따라서 몇 번의 write 이후에 empty가 1 → 0으로 전이되는 지연이 보이며, 이는 CDC 구조상 정상적인 동작이다. full은 wptr_gray_next와 동기화된 rptr_gray를 비교해 판단되며, DEPTH(8)만큼 정확히 write가 누적된 시점에 1로 올라가고 이후 write는 차단된다.

 

READ 구간에서는 rd_en이 rclk 기준으로 올라가며 rptr_bin이 증가하고, write 시 저장된 데이터가 순서대로 dout에 출력된다. full 신호가 read 중간에 1 → 0으로 내려가는 것도 read pointer(rptr_gray)가 write 클럭 도메인으로 전달되는 데 필요한 동기화 지연 때문이다. 모든 데이터가 읽힌 뒤 rptr_gray와 동기화된 wptr_gray가 같아지는 시점에 empty가 1로 올라가며, 이후 read 요청은 무시되어 underflow가 발생하지 않는다.

 

결론적으로, full과 empty가 즉각 반응하지 않고 몇 클럭 늦게 변하는 현상은 Async FIFO의 필수적인 CDC 안정화 특성이며, 포인터 비교·데이터 순서·overflow/underflow 차단 모두가 정상적으로 동작하고 있음을 이 결과가 보여준다.

728x90
반응형
저작자표시 비영리 변경금지 (새창열림)

'VLSI > Design' 카테고리의 다른 글

[Architecture] GALS: Globally Asynchronous, Locally Synchronous  (0) 2025.12.31
[CDC] 11_Shadow Register, Active Register  (0) 2025.12.21
[CDC] 9_Multi Bit CDC: Asynchronous FIFO (Full 판단, Pointer Jump)  (0) 2025.12.21
[CDC] 8_Multi Bit CDC: Asynchronous FIFO (Full/Empty Pessimistic Design)  (0) 2025.12.21
[CDC] 7_Multi Bit CDC: Asynchronous FIFO (First-In First Out)  (0) 2025.12.20
'VLSI/Design' 카테고리의 다른 글
  • [Architecture] GALS: Globally Asynchronous, Locally Synchronous
  • [CDC] 11_Shadow Register, Active Register
  • [CDC] 9_Multi Bit CDC: Asynchronous FIFO (Full 판단, Pointer Jump)
  • [CDC] 8_Multi Bit CDC: Asynchronous FIFO (Full/Empty Pessimistic Design)
리미와감자
리미와감자
공부한 내용을 포스팅합니다.
    반응형
    250x250
  • 리미와감자
    리미창고
    리미와감자
  • 전체
    오늘
    어제
    • 분류 전체보기 (213)
      • Programming (92)
        • Common (6)
        • C (3)
        • C++ (16)
        • Python (35)
        • Front End (8)
        • Linux (1)
        • Script (7)
        • Data Structure (5)
        • Tool (11)
      • Computer Science (0)
      • VLSI (30)
        • Common (4)
        • Design (15)
        • SystemVerilog (9)
        • UVM (2)
        • FPGA (0)
      • Embedded System (31)
        • Arduino (2)
        • STM32 (7)
        • Embedded Recipes (22)
      • Semiconductor (11)
        • Semiconductor Device (1)
        • Display (10)
      • Algorithm (8)
        • Image Processing (8)
        • AI (0)
      • Certificate (26)
        • ADsP (26)
      • 일상생활 (15)
        • 맛집 리뷰 (4)
        • 나는 오늘 무엇을 샀나 ! (5)
        • 국내여행 (5)
        • 나들이 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 최근 글

  • 최근 댓글

  • 태그

    티스토리챌린지
    data structure
    arduino
    SVA
    BeautifulSoup4
    UVM
    아두이노
    군산가볼만한곳
    systemverilog assertions
    STM32
    파이참
    Handshake
    tkinter
    Metastability
    Asynchronous FIFO
    디더링
    SystemVerilog
    c++ 기초
    임베디드레시피
    Bash
    assertion
    ADsP
    Clock Domain Crossing
    오블완
    Dither
    CDC
    openpyxl
    임베디드시스템
    자료구조
    git
  • 링크

    • chipverify
    • vlsiverify
    • iksciting
    • 오늘은 맑음
    • verificationguide
  • hELLO· Designed By정상우.v4.10.6
리미와감자
[CDC] 10_Multi Bit CDC: Asynchronous FIFO (Code, Simulation)
상단으로

티스토리툴바