개요
FIFOs에 대한 시리즈 의 마지막인 이 페이지는 기존 FIFO를 다른 것으로 수정하는 방법을 보여줍니다. 먼저 "standard FIFO"를 FWFT FIFO로 바꾸고 그 반대로 하는 방법입니다. 이것은 FIFO의 timing을 개선하기 위한 몇 가지 고급 방법, 즉 더 높은 주파수에서 작동하도록 합니다.
실용적인 관점에서, timing constraints를 달성하는 데 문제가 있는 경우가 아니면 이 페이지를 읽는 것은 전혀 의미가 없습니다. 이 문제는 FIFO와 관련이 있습니다. 이 페이지의 내용은 어렵고 FIFOs를 일반적으로 사용하는 데 필요하지 않습니다. 그럼에도 불구하고 logic, 특히 데이터를 처리하는 logic을 설계하는 동안 사용할 근육을 훈련하는 목적으로 연습으로 노력할 가치가 있을 수 있습니다.
직접적인 관련은 없지만 외부 메모리의 도움으로 매우 깊은 FIFO를 생성하는 방법을 보여주는 또 다른 페이지 가 있습니다(일반적으로 DDR memory가 지만 AXI 인터페이스로 래핑된 모든 것이 가능함). 이 트릭의 좋은 점은 이 거대한 FIFO가 사용하는 외부 메모리만큼 깊을 수 있지만 이 모든 것이 application logic에 투명하다는 것입니다. baseline FIFO와 동일한 인터페이스를 가지고 있습니다.
나는 몇 년 전에 이 페이지에서 Verilog 코드를 작성했기 때문에 코딩 스타일이 오늘날과 약간 다르다는 것을 언급해야 합니다.
Standard FIFO ~ FWFT FIFO
이전 페이지 에서 FWFT FIFOs 에 대해 빠르게 요약하면 다음과 같습니다. "standard FIFO"의 경우, 낮은 @empty port는 유효한 데이터가 rising clock edge에서 @rd_en이 높은 후에 FIFO의 output 에 표시됨을 의미합니다. FWFT FIFO는 사용 가능하자마자 output 에 데이터를 표시하므로 낮은 @empty signal은 output 의 데이터가 유효함 을 의미합니다.
@rd_en 의 의미도 다릅니다. "standard FIFO"의 경우 "데이터 가져오기"를 의미합니다. FWFT FIFO 에서는 "방금 데이터를 사용했습니다. 다음 데이터 가 있으면 가져오세요."와 같습니다.
그래서 이것은 "standard FIFO"를 FWFT FIFO로 바꾸는 module 입니다. 당연히 @rd_en 및 @empty만 조작합니다. 나머지 신호는 그냥 통과됩니다.
module basic_fwft_fifo(rst,
rd_clk, rd_en, dout, empty,
wr_clk, wr_en, din, full);
parameter width = 8;
input rst;
input rd_clk;
input rd_en;
input wr_clk;
input wr_en;
input [(width-1):0] din;
output empty;
output full;
output [(width-1):0] dout;
reg dout_valid;
wire fifo_rd_en, fifo_empty;
// orig_fifo is just a normal (non-FWFT) synchronous or asynchronous FIFO
fifo orig_fifo
(
.rst(rst),
.rd_clk(rd_clk),
.rd_en(fifo_rd_en),
.dout(dout),
.empty(fifo_empty),
.wr_clk(wr_clk),
.wr_en(wr_en),
.din(din),
.full(full)
);
assign fifo_rd_en = !fifo_empty && (!dout_valid || rd_en);
assign empty = !dout_valid;
always @(posedge rd_clk or posedge rst)
if (rst)
dout_valid <= 0;
else
begin
if (fifo_rd_en)
dout_valid <= 1;
else if (rd_en)
dout_valid <= 0;
end
endmodule
일반적으로 여기에서 코드를 설명하지만 이전 페이지 에서 FWFT FIFO 에 대해 제공된 설명을 반복합니다.
FWFT FIFO ~ standard FIFO
이것은 정말 간단합니다. FWFT FIFO 의 낮은 @empty는 데이터가 output port에 있음을 의미하므로 @rd_en이 높을 때 이 데이터를 샘플링하는 register를 만듭니다.
그래서 그것은 바로 이것입니다:
module standard_fifo(rst,
rd_clk, rd_en, dout, empty,
wr_clk, wr_en, din, full);
parameter width = 8;
input rst;
input rd_clk;
input rd_en;
input wr_clk;
input wr_en;
input [(width-1):0] din;
output empty;
output full;
output [(width-1):0] dout;
reg [(width-1):0] dout;
wire [(width-1):0] dout_w;
always @(posedge rd_clk)
if (rd_en && !empty)
dout <= dout_w;
fwft_fifo wrapper
(
.wr_clk(wr_clk),
.rd_clk(rd_clk),
.rst(rst),
.din(din),
.wr_en(wr_en),
.rd_en(rd_en && !empty),
.dout(dout_w),
.full(full),
.empty(empty)
);
endmodule
@dout 만 조작된다는 점에 유의하십시오. @empty는 다음과 같이 전달됩니다. 높으면 @dout_w가 유효하지 않으므로 @dout는 값을 샘플링할 수 없습니다.
timing개선 요령
FIFOs에 대한 이 네 페이지의 최종 결승전에 오신 것을 환영합니다. 확실히 가장 읽기 어려운 부분입니다.
그래서 때때로 FPGA design이 timing constraints 에 도달하지 못하는 이유(즉, 원하는 clock frequency에 도달)를 알아내려고 할 때 critical path가 FIFO에서 시작 및/또는 끝나는 것으로 나타났습니다. 먼저 풀기 쉬운 케이스를 짚고 하드너트로 마무리하자.
@empty 및/또는 @full이 critical path에 있는 경우
@empty signal 와 @full signal은 critical path에 나타날 수 있으며, 특히 @wr_en 와 @rd_en이 이 중 combinatorial functions 인 경우 그렇습니다. 이는 주로 이러한 신호가 종종 FIFO에서 쓰기 작업이나 읽기 작업을 요청하기 위한 것이 아니라 데이터를 소비하거나 생성하는 application logic 에 대한 enable signals 역할도 하기 때문입니다. 데이터가 흐르지 않으면 logic 도 멈춥니다.
따라서 @wr_en 및 @rd_en에 의존하는 logic equations가 많이 있으며 logic functions가 상당히 복잡한 경우가 많습니다. 결과는 높은 fanout입니다. 이 모든 것은 문제가 있는 propagation delay로 요약됩니다.
@empty 및 @full은 제대로 작성된 FIFO에서 flip-flops 의 outputs가 므로 개선할 점이 별로 없습니다. 그러나 FPGA의 소프트웨어는 종종 FIFO를 synthesized netlist로 제공하기 때문에 fanout를 줄이기 위해 이러한 registers를 복제하는 것은 불가능하거나 최소한 어렵습니다. 또한 이러한 registers 와 출력 값을 사용하는 application logic 사이에 FPGA의 logic fabric 에 물리적 거리가 클 수 있습니다. 대형 FPGAs에서 이것은 paths의 delay에 결정적인 기여를 할 수 있습니다.
이 문제에 대한 해결책은 @almost_empty 와 @almost_full을 논의할 때 이 페이지 에서 이미 제공되었습니다. 이러한 ports를 사용하면 @wr_en 와 @rd_en 의 outputs가 registers가 될 수 있습니다. 이렇게 하면 combinatorial function의 문제가 해결되고 이러한 신호의 fanout 도 제어할 수 있습니다. 게다가 이를 통해 도구가 이러한 registers를 값을 소비하는 logic 에 더 가깝게 배치할 수 있으므로 propagation delay을 줄이는 데 도움이 됩니다.
@wr_en 및/또는 @din이 critical path에 있는 경우
이 상황은 확실히 해결하기 가장 쉽습니다. registers레이어를 추가하기만 하면 됩니다. 같은 것
always @(posedge wr_clk)
begin
wr_en_reg <= wr_en;
din_reg <= din_reg;
end
그런 다음 @wr_en_reg 및 @din_reg을 대신 FIFO 에 연결하십시오. overflow에서 FIFO를 방지하려면 @full대신 @almost_full을 사용해야 합니다. 또는 더 일반적으로 말하면 FIFO를 채우기 위한 임계값에서 1을 빼야 합니다.
@rd_en 및/또는 @dout가 critical path에 있는 경우
이제 우리는 심각해지고 있습니다. 이것은 비교적 해결하기 어려운 문제일 뿐만 아니라 발생할 가능성이 가장 높습니다. 그 이유는 다음과 같습니다.
- @rd_en signal은 FIFO내부의 combinatorial logic 에서 사용되고, 그 외의 read address를 계산할 때에도 사용됩니다. 따라서 FIFO 자체가 delay의 양을 기여합니다.
- @rd_en을 생성하는 application logic은 종종 복잡할 수 있는 combinatorial function 입니다. 이 logic function은 state registers 와 logic fabric의 다른 부분에서 나오는 여러 flags 로 구성될 수 있습니다.
@dout의 경우:
- FIFO의 data output은 종종 block RAM에 직접 연결됩니다. FPGA의 flip-flop와 비교할 때 이 RAMs는 clock-to-output timing이 훨씬 더 나쁩니다. FIFO가 여러 개의 RAMs로 구현된 경우 해당 data outputs가 multiplexer에 삽입되므로 @dout는 이 combinatorial logic의 결과입니다. 이것은 delay을 더 추가합니다.
- application logic은 combinatorial functions에서 @dout 의 값을 사용할 수 있습니다.
- FIFO의 RAMs 와 application logic 사이의 FPGA 의 물리적 거리는 대형 FPGAs에 delay을 추가할 수 있습니다.
그래서 목표는 @rd_en 와 FIFO의 logic사이에서 combinatorial path를 없애고, @dout에서도 똑같이 하는 것입니다.
@dout의 combinatorial path만 분리
이것을 해결책으로 제시하려는 것은 아니지만 토론은 이것을 다음 단계를 파악하기 위한 준비로 이해하는 데 도움이 될 수 있습니다. 이것이 혼란스럽다면 이 섹션을 건너뛰십시오.
따라서 @dout의 combinatorial path만 분리하려고 한다고 가정합니다. FWFT FIFO를 "standard FIFO"(위 그림 참조)로 변환하는 wrapper module은 정확히 다음을 수행합니다. register를 추가하여 @dout의 combinatorial path를 종료합니다. 그러나 시작점으로 FWFT FIFO가 필요합니다.
그러나 "standard FIFO"를 FWFT FIFO로 변환하는 wrapper module이 있었습니다. 그렇다면 FIFO를 앞뒤로 변환할 수 있습니까? 아니면 동등한 기능을 하는 단일 module을 작성하시겠습니까? 어느 쪽이든, 이 형식의 솔루션은 @rd_en의 상황을 악화시킵니다.
그러나 이 솔루션은 다음을 자세히 살펴볼 가치가 있습니다. FWFT FIFO 로의 변환은 단지 wrapped FIFO의 @dout가 유효할 때 추적을 유지하고 @dout가 유효하지 않을 때(및/또는 외부 @rd_en이 하이일 때) @fifo_rd_en을 하이로 유지하는 것으로 구성되었습니다.
"standard FIFO"로의 변환은 @rd_en이 높을 때 wrapped FIFO의 @dout 값을 register 로 복사하여 수행되었습니다.
따라서 대체로 첫 번째 메커니즘은 가능한 경우 wrapped FIFO의 @dout를 유효한 상태로 유지했으며 두 번째 메커니즘은 외부 @rd_en이 요청했을 때 @dout를 다른 register 로 복사했습니다.
그러나 이것은 @rd_en의 combinatorial path문제를 해결하지 못합니다. 연속 읽기를 허용하려면 외부 @rd_en이 높은 각 clock 의 원래 FIFO 에서 단어를 읽어야 합니다. 그렇지 않으면 FWFT의 @dout가 사용되었지만 업데이트되지 않았기 때문에 무효화됩니다. 따라서 이 내부 FIFO의 @rd_en은 외부 @rd_en의 combinatorial function 여야 합니다. 이것을 변경하려면 다음과 같이 @dout의 path에 다른 register를 추가해야 합니다.
reg_fifo로 두 combinatorial paths 분리
더 이상 고민하지 않고 @rd_en 및 @dout용 combinatorial paths를 분리하는 reg_fifo module입니다.
module reg_fifo(rst,
rd_clk, rd_en, dout, empty,
wr_clk, wr_en, din, full);
parameter width = 8;
input rst;
input rd_clk;
input rd_en;
input wr_clk;
input wr_en;
input [(width-1):0] din;
output empty;
output full;
output [(width-1):0] dout;
reg fifo_valid, middle_valid;
reg [(width-1):0] dout, middle_dout;
wire [(width-1):0] fifo_dout;
wire fifo_empty, fifo_rd_en;
wire will_update_middle, will_update_dout;
// orig_fifo is "standard" (non-FWFT) FIFO
fifo orig_fifo
(
.rst(rst),
.rd_clk(rd_clk),
.rd_en(fifo_rd_en),
.dout(fifo_dout),
.empty(fifo_empty),
.wr_clk(wr_clk),
.wr_en(wr_en),
.din(din),
.full(full)
);
assign will_update_middle = fifo_valid && (middle_valid == will_update_dout);
assign will_update_dout = rd_en && !empty;
assign fifo_rd_en = !fifo_empty && !(middle_valid && fifo_valid);
assign empty = !(fifo_valid || middle_valid);
always @(posedge rd_clk)
if (rst)
begin
fifo_valid <= 0;
middle_valid <= 0;
dout <= 0;
middle_dout <= 0;
end
else
begin
if (will_update_middle)
middle_dout <= fifo_dout;
if (will_update_dout)
dout <= middle_valid ? middle_dout : fifo_dout;
if (fifo_rd_en)
fifo_valid <= 1;
else if (will_update_middle || will_update_dout)
fifo_valid <= 0;
if (will_update_middle)
middle_valid <= 1;
else if (will_update_dout)
middle_valid <= 0;
end
endmodule
가장 먼저 주목해야 할 점은 @dout가 이 module에 정의된 register가 고 @rd_en이 이 register의 업데이트를 유발한다는 것입니다. 이 두 가지를 @fifo_dout 및 @fifo_rd_en인 내부 FIFO에 연결된 유사한 신호와 혼동하지 않는 것이 중요합니다.
이제 이 module이 어떻게 작동하는지 알아보겠습니다.
pipeline이해하기
FWFT FIFO로의 변환기와 마찬가지로 일반 FIFO, orig_fifo의 instantiation이 있습니다. reg_fifo module 의 logic은 @fifo_dout 에 유효한 값이 없을 때 orig_fifo 에서 단어를 읽어 @fifo_dout의 값을 유효하게 유지하려고 시도합니다. 그러나 그 외에도 @middle_dout라는 두 번째 register가 있습니다. logic은 가능한 경우 @fifo_dout값을 사용하여 이 register 도 유효하게 유지하려고 시도합니다.
따라서 @fifo_dout, @middle_dout 및 @dout를 orig_fifo 의 데이터를 앞으로 이동시키는 pipeline 로 볼 수 있습니다.
이러한 pipeline stages가 유효한 시기를 추적하는 두 개의 registers가 있습니다. @fifo_valid는 @fifo_dout가 유효할 때 하이이고 @middle_valid는 @middle_dout가 유효할 때 하이입니다.
이 pipeline 의 목적은 중간 단계를 우회하는 기능입니다. @rd_en이 높고 @empty가 낮을 때 @dout는 @middle_dout 또는 @fifo_dout에서 새 값을 취하지만 항상 @middle_dout를 선호합니다. 즉, @middle_dout가 유효하면 @dout는 @middle_dout를 사용하고, 그렇지 않으면 @fifo_dout를 대신 사용합니다. 이것이 @rd_en의 combinatorial path를 분리하는 핵심 방법은 나중에 설명합니다.
그럼 먼저 구현의 세부 사항을 살펴 보겠습니다. FIFO의 data output 에서 fifo_reg의 output register로 가는 두 개의 별도 경로가 있습니다. 그들은 이 그림에서 왼쪽과 오른쪽에 별도로 표시됩니다.
두 pipeline stages (@fifo_dout 및 @middle_dout) 중 어느 것도 유효하지 않은 경우 @empty는 높음으로 데이터를 가져올 곳이 없음을 나타냅니다.
assign empty = !(fifo_valid || middle_valid);
이러한 pipeline stages를 유효하게 유지하려는 시도는
assign fifo_rd_en = !fifo_empty && !(middle_valid && fifo_valid);
즉, 두 pipeline stages 중 하나라도 유효하지 않으면 가능하면 orig_fifo에서 읽습니다. @fifo_dout가 이미 유효한 경우 @fifo_dout가 업데이트되는 동시에 해당 값이 @middle_dout 에 복사됩니다(자세한 내용은 아래 참조).
이제 @will_update_* 쌍의 정의를 살펴보겠습니다.
assign will_update_middle = fifo_valid && (middle_valid == will_update_dout);
assign will_update_dout = rd_en && !empty;
먼저 @will_update_dout는 @rd_en 에 @empty가 높을 때 reg_fifo 에서 읽지 못하도록 하는 안전 장치와 같다는 점에 주의하십시오.
다음으로 @middle_out의 업데이트를 제어하는 @will_update_middle이 있습니다.
always @(posedge rd_clk)
if (will_update_middle)
middle_dout <= fifo_dout;
위의 @will_update_middle정의를 보면 @middle_dout를 업데이트하기 위한 두 가지 조건이 있습니다. 하나는 @fifo_dout 의 값이 유효하다는 것인데, 이는 매우 명백하며, (middle_valid == will_update_dout)라는 표현이 있습니다. 전체 기계가 어떻게 작동하는지 설명하므로 이 표현을 4가지 가능한 옵션으로 분류해 보겠습니다. 이 모든 것은 @fifo_dout가 유효한 경우에만 역할을 한다는 점을 명심하십시오.
- @middle_valid == 0 및 @will_update_dout == 0. @middle_dout는 유효하지 않으며 @fifo_dout의 값은 @dout에 복사되지 않습니다. 따라서 @fifo_dout에서 @middle_dout를 업데이트하십시오.
- @middle_valid == 0 및 @will_update_dout == 1. @middle_dout는 유효하지 않지만 @dout는 업데이트될 것이므로 분명히 @fifo_dout에서 업데이트될 것입니다. @fifo_dout의 값이 소모되기 때문에 @middle_out에 복사할 수 없으므로 아무 것도 하지 마십시오.
- @middle_valid == 1 및 @will_update_dout == 0. @middle_dout는 유효하며 @dout에 아무 것도 복사되지 않습니다. 두 pipeline stages 모두 유효하며 어떻게 해서든 그대로 유지됩니다. 아무 것도 하지 마십시오.
- @middle_valid == 1 및 @will_update_dout == 1. @middle_dout는 유효하며 @dout로 복사됩니다. 따라서 유효한 상태를 유지하려면 @fifo_dout 에서 @middle_dout를 업데이트하십시오.
@middle_valid 와 @fifo_valid가 동시에 하이일 때 @fifo_rd_en은 로우입니다. 결과적으로 마지막 두 경우의 시나리오가 발생할 때 orig_fifo 에서 데이터를 가져오지 않습니다.
특히 두 pipeline stages가 모두 유효하고 @rd_en이 하이일 때 @fifo_dout의 값이 @middle_dout에 복사된다. 그리고 @fifo_rd_en이 로우이기 때문에 @fifo_valid는 다음 clock cycle에서 로우로 변경됩니다. @middle_valid는 높은 상태를 유지하므로 필요한 경우 clock cycle 다음에 데이터를 제공할 수 있습니다. clock cycle 에서 그 후 @fifo_valid는 다시 높아집니다( orig_fifo에 데이터가 있는 경우).
그렇다면 왜 @fifo_rd_en이 이 특정 상황에서 @fifo_dout를 유효하게 유지하도록 정의되지 않습니까? @fifo_rd_en은 @rd_en의 combinatorial function 여야 하기 때문에 이 dual-stage pipeline이 피하도록 설계된 것과 정확히 같습니다.
이제 @dout가 어떻게 정의되는지 살펴볼 차례입니다. reset을 제외하고 정의는 다음과 같습니다.
always @(posedge rd_clk)
if (will_update_dout)
dout <= middle_valid ? middle_dout : fifo_dout;
@will_update_dout를 정의로 대체하면 다음과 같습니다.
always @(posedge rd_clk)
if (rd_en && !empty)
dout <= middle_valid ? middle_dout : fifo_dout;
이것은 FWFT FIFO 에서 "standard FIFO"로의 변환과 유사하지만 선택할 수 있는 소스는 두 가지뿐입니다. @middle_dout 에 유효한 값이 포함되어 있으면 사용됩니다. 그렇지 않으면 @fifo_dout. 둘 다 유효하지 않으면 @empty가 높으므로 아무 일도 일어나지 않습니다.
이것이 도움이 되는 이유는 무엇입니까? output timing의 경우 @dout는 분명히 register입니다. @rd_en와 관련하여 @fifo_rd_en은 registers인 @middle_valid 와 @fifo_valid에만 의존합니다. 플러스 @fifo_empty는 orig_fifo 자체의 output 입니다(이 combinatorial path는 불가피합니다). 따라서 @fifo_rd_en은 외부 @rd_en에 의존하지 않으므로 @rd_en 에서 orig_fifo까지 combinatorial path가 없습니다.
pipeline stages의 registers유효성 추적
그림을 완성하려면: 두 개의 *_valid 플래그는 관련 register 에 유효한 데이터가 포함되어 있는지 여부를 알려줍니다. @fifo_valid관련:
if (fifo_rd_en)
fifo_valid <= 1;
else if (will_update_middle || will_update_dout)
fifo_valid <= 0;
이것은 위의 "standard FIFO"에서 FWFT FIFO 로의 변환에서 @dout_valid 의 정의와 같습니다. @fifo_rd_en이 rising edge에서 하이일 때, 그 결과 @fifo_valid가 하이가 됩니다. orig_fifo에서 데이터를 읽으면 이 FIFO의 output이 결과적으로 유효한 것으로 간주되기 때문에 이는 의미가 있습니다. 그러나 @fifo_rd_en이 낮고 데이터가 @middle_dout 또는 @dout중 하나로 복사된 경우 @fifo_dout가 더 이상 유효하지 않다고 간주합니다. FIFO의 output은 사용한 지 얼마 되지 않았고, FIFO는 이를 새로운 데이터로 대체하지 않았습니다.
@middle_valid는 동일한 논리를 따릅니다.
if (will_update_middle)
middle_valid <= 1;
else if (will_update_dout)
middle_valid <= 0;
@will_update_middle이 high이면 데이터가 @middle_dout에 복사되므로 @middle_valid 도 high가 됩니다. 그렇지 않고 @middle_dout 의 데이터가 @dout로 복사되면 @middle_valid는 low로 변경됩니다. @dout는 가능하면 @middle_dout에서 복사하는 것을 선호하기 때문에 @will_update_dout는 이에 대한 충분 조건입니다.
그것은 전혀 작동합니까?
이 module은 너무 복잡하여 작동한다는 거의 공식적인 증거가 필요합니다. 따라서 이 질문에 답하는 한 가지 방법은 두 개의 pipelines stages, @fifo_dout 및 @middle_dout중 몇 개가 유효한지 묻는 것입니다. 이 값 은 reg_fifo module에 정의되어 있지 않지만 다음 과 같이 정의 될 수 있습니다.
wire [1:0] valid_count;
assign valid_count = fifo_valid + middle_valid;
이 가상의 @valid_count는 분명히 0, 1 또는 2값을 가질 수 있습니다. 다음과 같이 증가 또는 감소합니다.
- @valid_count는 @fifo_rd_en이 high 이고 (rd_en && !empty)가 false 인 모든 clock cycle 에서 하나씩 카운트 업합니다.
- @valid_count는 @fifo_rd_en이 낮고 (rd_en && !empty)가 true 인 모든 clock cycle 에서 하나씩 카운트다운합니다.
- 그렇지 않으면 @valid_count가 변경되지 않습니다.
logic equations를 살펴보고 이 세 가지 진술이 옳다는 것을 스스로 확신하십시오.
orig_fifo 에 데이터가 있고 application logic이 계속 읽기를 원할 때 어떤 일이 일어나는지 봅시다.
reg_fifo의 logic은 orig_fifo에서 읽어서 @valid_count를 2로 밀어 올리려고 합니다. 반면, @empty는 @valid_count가 0이 아닐 때 로우이므로 @valid_count가 1이 되자마자 @rd_en이 하이가 될 수 있습니다. 따라서 @valid_count가 1일 때 @valid_count가 2가 아니기 때문에 @fifo_rd_en은 하이가 될 것입니다.
그러나 @valid_count는 2의 값에 도달하지 않을 것입니다. 왜냐하면 @rd_en은 높은 값을 유지함으로써 그것을 방지하기 때문입니다. 따라서 데이터는 @fifo_rd_en 와 @rd_en이 모두 high로 유지되고 @valid_count가 1에 남아 있는 상태로 데이터가 흐릅니다. 시작 부분을 제외하고 데이터는 @fifo_dout 에서 @dout로 복사됩니다.
이 손익분기점은 orig_fifo가 비어 있을 때 깨집니다. 이 경우 @fifo_rd_en은 더 이상 하이가 허용되지 않기 때문에 @valid_count는 0이 됩니다. 또 다른 타이 브레이커는 FIFO가 비어 있지 않고 application logic이 더 읽기를 원하지 않기 때문에 @rd_en이 낮아지는 경우입니다. 이 경우 @valid_count는 2로 상승하고 그대로 유지됩니다.
그러나 나중에 @rd_en이 다시 하이가 되면 @valid_count는 1로 내려가고 그 시점에서만 @fifo_rd_en이 하이로 변경됩니다( orig_fifo가 비어 있지 않는 한).
다시 한 번, @valid_count는 module에서 구현되지 않은 이론적인 신호일 뿐입니다. 이 설명은 두 개의 추가 pipeline stages가 데이터의 지속적인 흐름을 보장하는 이유를 이해하는 데 도움이 되었기를 바랍니다.
사용 참고 사항
이 module은 포장된 "standard FIFO"의 드롭인 교체품으로 사용할 수 있습니다. 기능적 관점에서 보면 아무것도 바뀌지 않습니다. 그러나 orig_fifo는 @rd_en, @dout 및 @empty의 동작에 약간의 변화가 있지만 orig_fifo가 FIFO (안전한 가정)처럼 올바르게 동작하는 한 문제가 되지 않습니다. 데이터 쓰기와 관련된 ports는 그대로 전달되기 때문에 아무런 변화가 없습니다.
module은 두 개의 pipelines stages를 추가하기 때문에 orig_fifo의 fill counters는 reg_fifo 에 저장된 총 단어 수보다 낮은 값을 나타낼 수 있습니다(즉, orig_fifo 와 pipeline stages 에 저장된 단어 수를 함께 계산). 따라서 @almost_empty 또는 이와 유사한 ports가 orig_fifo에서 활성화되면 비관적인 그림이 나타날 수 있습니다.
reg_fifo 의 약간의 단점은 @empty output이 register가 아니라 registers2개로 구성된 combinatorial function 라는 것입니다. 이것은 timing에 최적은 아니지만 대부분의 사용 사례에서 최소한의 영향을 미칩니다. 이것은 이 페이지 에 표시된 것처럼 @next_words_in_ram와 같은 정신으로 @next_fifo_valid 및 @next_middle_valid 와 같은 combinatorial registers를 정의하여 수정할 수 있습니다. 여기서는 reg_fifo가 충분히 복잡하기 때문에 여기에서 구현되지 않습니다.
timing이 개선된 FWFT FIFO
이 주제를 끝내기 위해 아래는 reg_fifo와 동일한 작업을 수행하지만 대신 application logic 에 FWFT FIFO를 제공하는 module 입니다. FWFT FIFO가 아닌 standard FIFO를 기반으로 합니다. 그러니 혼동하지 마세요...
reg_fifo 에서 이 module 로의 전환은 내가 이미 FWFT FIFOs에 대해 논의한 것과 거의 동일합니다.
module fwft_reg_fifo(rst,
rd_clk, rd_en, dout, empty,
wr_clk, wr_en, din, full);
parameter width = 8;
input rst;
input rd_clk;
input rd_en;
input wr_clk;
input wr_en;
input [(width-1):0] din;
output empty;
output full;
output [(width-1):0] dout;
reg middle_valid, dout_valid;
reg [(width-1):0] dout, middle_dout;
wire [(width-1):0] fifo_dout;
wire fifo_empty, fifo_rd_en;
wire will_update_middle, will_update_dout;
// orig_fifo is "standard" (non-FWFT) FIFO
fifo orig_fifo
(
.rst(rst),
.rd_clk(rd_clk),
.rd_en(fifo_rd_en),
.dout(fifo_dout),
.empty(fifo_empty),
.wr_clk(wr_clk),
.wr_en(wr_en),
.din(din),
.full(full)
);
assign will_update_middle = !fifo_empty && (middle_valid == will_update_dout);
assign will_update_dout = (middle_valid || !fifo_empty) && (rd_en || !dout_valid);
assign fifo_rd_en = !fifo_empty && !(middle_valid && dout_valid);
assign empty = !dout_valid;
always @(posedge rd_clk)
if (rst)
begin
middle_valid <= 0;
dout_valid <= 0;
dout <= 0;
middle_dout <= 0;
end
else
begin
if (will_update_middle)
middle_dout <= fifo_dout;
if (will_update_dout)
dout <= middle_valid ? middle_dout : fifo_dout;
if (will_update_middle)
middle_valid <= 1;
else if (will_update_dout)
middle_valid <= 0;
if (will_update_dout)
dout_valid <= 1;
else if (rd_en)
dout_valid <= 0;
end
endmodule
이것으로 FIFOs에 대한 시리즈 를 마칩니다.