01signal.com

Verilog에서 single clock FIFOs 구현

범위

FIFOs에 관한 5페이지로 구성된 시리즈 중 세 번째인 이 페이지는 baseline single clock FIFO의 Verilog 구현을 보여줍니다. 이는 portable code를 작성하는 데 유용할 수 있지만 이 페이지의 주요 요점은 FIFO가 어떻게 작동하는지 반복하는 것입니다. 따라서 FWFT FIFO뿐만 아니라 "standard FIFO"의 구현도 보여 드리겠습니다. 하지만 먼저 두 FIFOs가 모두 사용할 dual port RAM이 필요합니다.

dual port RAM

이것은 inference덕분에 RAM을 구현한 Verilog module 입니다. 모든 synthesizer가 이 문제를 해결할 가능성이 높지만 원하지 않는 종류의 RAM (block RAM 대 distributed RAM)를 생성할 수 있으므로 synthesizer directives를 추가해야 할 수도 있습니다. 또는 가장 잘 작동하는 경우 dual port RAM용 FPGA 공급업체에서 제공하는 IP를 사용할 수도 있습니다.

module은 다음과 같습니다.

module dualport_ram #(parameter depth = 64,
                      log2_depth = 6,
                      width = 8
                     )
   (
    input                    clk,
    input [(log2_depth-1):0] wr_addr,
    input [(log2_depth-1):0] rd_addr,
    output reg [(width-1):0] rd_data,
    input [(width-1):0]      wr_data,
    input                    rd_en,
    input                    wr_en
    );

   reg [(width-1):0] inferred_ram[0:(depth-1)];

   always @(posedge clk)
     begin
        if (wr_en)
          inferred_ram[wr_addr] <= wr_data;

        if (rd_en)
          rd_data <= inferred_ram[rd_addr];
     end
endmodule

"standard FIFO"

이제 "standard FIFO"(즉, FWFT FIFO아님 )를 구현하는 module을 볼 준비가 되었습니다.

module fifo
  #(parameter depth = 64, // Must equal 2^log2_depth exactly
    log2_depth = 6,
    width = 8
    )
   (
    input  clk,
    input  rst,

    input  wr_en,
    input [(width-1):0] din,

    input  rd_en,
    output [(width-1):0] dout,

    output reg full,
    output reg empty
    );

   reg [log2_depth:0]         next_words_in_ram; // Combinatorial
   reg [log2_depth:0]         words_in_ram;
   reg [(log2_depth-1):0]     rd_addr;
   reg [(log2_depth-1):0]     wr_addr;
   wire                       fetch_data, commit_data;

   assign fetch_data = rd_en && !empty;
   assign commit_data = wr_en && !full;

   always @(*)
     if (commit_data && !fetch_data)
       next_words_in_ram <= words_in_ram + 1;
     else if (!commit_data && fetch_data)
       next_words_in_ram <= words_in_ram - 1;
     else
       next_words_in_ram <= words_in_ram;

   always @(posedge clk)
     begin
        words_in_ram <= next_words_in_ram;
        full <= (next_words_in_ram == depth);
        empty <= (next_words_in_ram == 0);

        if (fetch_data)
          rd_addr <= rd_addr + 1;

        if (commit_data)
          wr_addr <= wr_addr + 1;

        if (rst)
          begin
             empty <= 1;
             full <= 1;
             words_in_ram <= 0;
             rd_addr <= 0;
             wr_addr <= 0;
          end
     end

   dualport_ram
     #(.depth(depth), .log2_depth(log2_depth), .width(width)) dp_ins
       (.clk(clk), .wr_addr(wr_addr),
        .rd_addr(rd_addr),
        .wr_en(commit_data),
        .rd_en(fetch_data),
        .wr_data(din),
        .rd_data(dout)
        );
endmodule

"depth" 및 "log2_depth" 매개변수와 관련하여 맨 위에 있는 주석에 유일한 중요한 사용법 메모가 있습니다. depth는 2log2_depth와 같아야 합니다.

이 module 의 작동은 매우 간단합니다. @fetch_data는 @rd_en와 비슷하지만 @empty를 고려합니다. 따라서 @fetch_data는 @rd_en의 안전한 버전입니다. @rd_en 와 @empty가 동시에 높은 경우에도(불법) @rd_en이 이 경우 무시되기 때문에 나쁜 일은 일어나지 않습니다.

@commit_data는 @full을 고려하여 동일한 방식으로 @wr_en 의 안전한 버전입니다.

@words_in_ram의 다음 값인 @next_words_in_ram은 @fetch_data 및 @commit_data를 기반으로 combinatorial function로 계산됩니다( always @(*) 문 참고). @next_words_in_ram은 다음 코드 조각으로 여러 registers 의 값을 생성하는 데 사용됩니다.

always @(posedge clk)
     begin
        words_in_ram <= next_words_in_ram;
        full <= (next_words_in_ram == depth);
        empty <= (next_words_in_ram == 0);
[ ... ]

여기에 @full 및 @empty가 정의되어 있습니다.

dual-clock FIFO와 비교하여 이 FIFO를 구현하기 쉽게 만드는 것은 이와 같이 @words_in_ram을 정의하고 FIFO의 양쪽에서 동일한 register를 사용할 수 있다는 것입니다.

다음 코드에서는 @rd_addr 및 @wr_addr의 업데이트와 @rst에 대한 절이 있습니다. @empty 와 @full은 모두 reset의 결과로 하이로 변경되지만 @full은 reset이 출시되면 로우로 돌아갑니다.

코딩 스타일에 대한 참고 사항: @rst가 높으면 if statement의 begin-end 절에 있는 할당이 이전에 할당되었을 가능성을 무시하므로 @rst는 실제로 모든 registers를 초기 값으로 재설정합니다. 이것은 가장 일반적인 코딩 스타일은 아니지만 모든 registers가 재설정되지 않을 때 분명한 이점이 있습니다. 이 특정 사례는 이러한 이점을 보여주지는 않지만 이 페이지 를 참조하십시오.

마지막으로 dual port RAM의 instantiation이 있습니다.

FWFT FIFO

이 페이지 에 표시된 것처럼 "standard FIFO"를 FWFT FIFO로 변환하는 것은 매우 쉽습니다. 그러나 직접 구현하면 몇 가지 흥미로운 점을 논의할 수 있으므로 다음과 같습니다.

module fwft_fifo
  #(parameter depth = 64, // Must equal 2^log2_depth exactly
    log2_depth = 6,
    width = 8
    )
   (
    input  clk,
    input  rst,

    input  wr_en,
    input [(width-1):0] din,

    input  rd_en,
    output [(width-1):0] dout,

    output reg full,
    output reg empty
    );

   reg [log2_depth:0]         next_words_in_ram; // Combinatorial
   reg [log2_depth:0]         words_in_ram;
   reg [(log2_depth-1):0]     rd_addr;
   reg [(log2_depth-1):0]     wr_addr;
   reg                        has_more_words;
   wire                       fetch_data, commit_data;

   assign fetch_data = (rd_en || empty) && has_more_words;
   assign commit_data = wr_en && !full;

   always @(*)
     if (commit_data && !fetch_data)
       next_words_in_ram <= words_in_ram + 1;
     else if (!commit_data && fetch_data)
       next_words_in_ram <= words_in_ram - 1;
     else
       next_words_in_ram <= words_in_ram;

   always @(posedge clk)
     begin
        words_in_ram <= next_words_in_ram;
        full <= (next_words_in_ram == depth);
        has_more_words <= (next_words_in_ram != 0);

        if (fetch_data)
          rd_addr <= rd_addr + 1;

        if (commit_data)
          wr_addr <= wr_addr + 1;

        if (fetch_data)
          empty <= 0;
        else if (rd_en)
          empty <= 1;

        if (rst)
          begin
             empty <= 1;
             full <= 1;
             words_in_ram <= 0;
             has_more_words <= 0;
             rd_addr <= 0;
             wr_addr <= 0;
          end
     end

   dualport_ram
     #(.depth(depth), .log2_depth(log2_depth), .width(width)) dp_ins
       (.clk(clk), .wr_addr(wr_addr),
        .rd_addr(rd_addr),
        .wr_en(commit_data),
        .rd_en(fetch_data),
        .wr_data(din),
        .rd_data(dout)
        );
endmodule

먼저 @has_more_words인 새로운 register가 있다는 점에 유의하십시오. 그 정의를 위의 "standard FIFO"와 비교하고 @has_more_words가 @empty의 logical NOT 라는 것을 확신하십시오.

다음으로 @fetch_data 에 대한 정의가 변경되었음을 유의하십시오. 이제 대신 @has_more_words 에 의해 보호되므로(당연하지 않음) 다음과 같이 표시됩니다.

assign fetch_data = (rd_en || empty) && has_more_words;

@empty는 FWFT FIFO 에서 "@dout가 유효하지 않음"을 의미합니다. 따라서 이 할당은 @rd_en외에도 output이 유효하지 않고 메모리 어레이에 읽을 데이터가 있는 경우 계속 진행해야 함을 의미합니다. first word의 falling through 입니다.

그리고 마지막으로 @empty를 할당하기 위한 logic이 다음으로 변경되었습니다.

always @(posedge clk)
     if (fetch_data)
       empty <= 0;
     else if (rd_en)
       empty <= 1;

이것은 단순히 메모리 어레이에서 단어가 읽혀지면 @empty가 다음 clock cycle에서 로우로 변경된다는 것입니다. 왜냐하면 지금 분명히 새롭고 유효한 데이터가 있기 때문입니다. 그러나 그런 일이 발생하지 않고 @rd_en이 활성화되어 있으면 application logic이 사용 가능한 마지막 단어를 사용하므로 @empty를 높음으로 변경하십시오. @rd_en이 높고 @fetch_data가 낮으면 @has_more_words는 확실히 낮습니다(위의 @fetch_data 할당 참조). 이것이 이 조건이 FIFO의 마지막 단어를 읽는 것과 같은 이유입니다.

이것으로 FIFOs에 관한 이 시리즈 의 세 번째 페이지를 마무리합니다. 다음 페이지에서는 data acquisition 애플리케이션에서 사용하기 위해 "standard FIFO"를 조정하는 방법을 보여줍니다.

이 페이지는 영어에서 자동으로 번역됩니다. 불분명한 사항이 있으면 원본 페이지를 참조하십시오.
Copyright © 2021-2024. All rights reserved. (c3c02857)