범위
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"를 조정하는 방법을 보여줍니다.