使用頻度の高い部品のHDL記述例 -FIFO-
使用頻度の高い部品のHDL記述例 -カウンタ-と使用頻度の高い部品のHDL記述例 -内部RAM-を組み合わせて、FIFOを作ることが出来ます。
FIFOはデータのバッファとして使用したり、異なるクロックドメインのデータ載せ替えに使用したり、比較的使用頻度は高いです。
もちろんツールで作ることも可能ですが、自作でないと難しいこともあります。(例えば、入り口と出口でバス幅が異なるとか。)
基本の作り方が分かれば、色々と応用出来ると思います。
例1 同期FIFO
入力も出力も同じクロックで動作するFIFOの例です。デュアルポートRAMを用いて、書き込んだら書き込み側アドレスをインクリメント、読み出したら読み出し側アドレスをインクリメントというのを基本的な動作にして、後は互いに追い越してしまわないよう制御出来るようにします。アドレスの差分を用いてFIFOに溜まっているデータ量や、それらを基にしたFULLやEMPTY等が一般的です。
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
entity fifo is
port(
clk : in std_logic;
rst : in std_logic;
full : out std_logic;
empty : out std_logic;
din_en : in std_logic;
din : in std_logic_vector(7 downto 0);
dout_en : in std_logic;
dout : out std_logic_vector(7 downto 0)
);
end fifo;
architecture RTL of fifo is
component ram is
port(
clk : in std_logic;
wea : in std_logic;
addra : in std_logic_vector(5 downto 0);
addrb : in std_logic_vector(5 downto 0);
dina : in std_logic_vector(7 downto 0);
doutb : out std_logic_vector(7 downto 0)
);
end component ram;
signal wr_en : std_logic;
signal wr_addr : std_logic_vector(5 downto 0);
signal wr_addr1 : std_logic_vector(5 downto 0);
signal rd_en : std_logic;
signal rd_addr : std_logic_vector(5 downto 0);
signal fifo_full : std_logic;
signal fifo_empty : std_logic;
begin
fifo_ram : ram
port map (
clk => clk,
wea => wr_en,
addra => wr_addr,
addrb => rd_addr,
dina => din,
doutb => dout
);
wr_en <= din_en and not fifo_full;
rd_en <= dout_en and not fifo_empty;
process (clk, rst) begin
if (rst = '1') then
wr_addr <= (others => '0');
elsif (clk'event and clk = '1') then
if (wr_en = '1') then
wr_addr <= wr_addr1;
end if;
end if;
end process;
wr_addr1 <= wr_addr + 1;
process (clk, rst) begin
if (rst = '1') then
rd_addr <= (others => '0');
elsif (clk'event and clk = '1') then
if (rd_en = '1') then
rd_addr <= rd_addr + 1;
end if;
end if;
end process;
fifo_full <= '1' when (wr_addr1 = rd_addr) else '0';
fifo_empty <= '1' when (wr_addr = rd_addr) else '0';
full <= fifo_full;
empty <= fifo_empty;
end architecture RTL;
module fifo
(
input wire clk,
input wire rst,
output wire full,
output wire empty,
input wire din_en,
input wire [7:0] din,
input wire dout_en,
output wire [7:0] dout
);
wire wr_en;
reg [5:0] wr_addr;
wire [5:0] wr_addr1;
wire rd_en;
reg [5:0] rd_addr;
wire fifo_full;
wire fifo_empty;
ram fifo_ram (
.clk (clk),
.wea (wr_en),
.addra (wr_addr),
.addrb (rd_addr),
.dina (din),
.doutb (dout)
);
assign wr_en = din_en & ~fifo_full;
assign rd_en = dout_en & ~fifo_empty;
always@(posedge clk or posedge rst) begin
if (rst == 1'b1) begin
wr_addr <= 6'd0;
end else begin
if (wr_en == 1'b1) begin
wr_addr <= wr_addr1;
end
end
end
assign wr_addr1 = wr_addr + 6'd1;
always@(posedge clk or posedge rst) begin
if (rst == 1'b1) begin
rd_addr <= 6'd0;
end else begin
if (rd_en == 1'b1) begin
rd_addr <= rd_addr + 6'd1;
end
end
end
assign fifo_full = (wr_addr1 == rd_addr) ? 1'b1 : 1'b0;
assign fifo_empty = (wr_addr == rd_addr) ? 1'b1 : 1'b0;
assign full = fifo_full;
assign empty = fifo_empty;
endmodule
例2 非同期FIFO
入力と出力が異なるクロックで動作するFIFOの例です。同期FIFOと同じ構造にすると、アドレスの差分を求める際に非同期の影響が出てしまい、FIFOに溜まっているデータ量が化けたり、FULLやEMPTYの誤判断が起こり得ます。それによってFULL状態での書き込みやEMPTY状態での読み出しが起こるとデータ異常につながります。正しいFULLやEMPTYとするためには、相手側へグレイコード[1]でアドレスを渡すことが有効です。
FULLやEMPTYが反映されるまでに遅延があるので、連続で書き込んだり読み出したりする場合には注意が必要です。FULLやEMPTYのいくつか手前で、もう少しでFULLやEMPTYになるということが分かるように、ALMOST_FULLやALMOST_EMPTYという信号を追加したり、FIFOに溜まっているデータ量をそれぞれのクロックで出力して外側で判断するというのが有効になると思います。
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
entity fifo is
port(
clki : in std_logic;
clko : in std_logic;
rst : in std_logic;
full : out std_logic;
empty : out std_logic;
din_en : in std_logic;
din : in std_logic_vector(7 downto 0);
dout_en : in std_logic;
dout : out std_logic_vector(7 downto 0)
);
end fifo;
architecture RTL of fifo is
component ram is
port(
clka : in std_logic;
clkb : in std_logic;
wea : in std_logic;
web : in std_logic;
addra : in std_logic_vector(5 downto 0);
addrb : in std_logic_vector(5 downto 0);
dina : in std_logic_vector(7 downto 0);
dinb : in std_logic_vector(7 downto 0);
douta : out std_logic_vector(7 downto 0);
doutb : out std_logic_vector(7 downto 0)
);
end component ram;
signal wr_en : std_logic;
signal wr_addr : std_logic_vector(5 downto 0);
signal wr_addr1 : std_logic_vector(5 downto 0);
signal wr_gray : std_logic_vector(5 downto 0);
signal wr_gray_d1 : std_logic_vector(5 downto 0);
signal wr_gray_d2 : std_logic_vector(5 downto 0);
signal wr_bin : std_logic_vector(5 downto 0);
signal rd_en : std_logic;
signal rd_addr : std_logic_vector(5 downto 0);
signal rd_gray : std_logic_vector(5 downto 0);
signal rd_gray_d1 : std_logic_vector(5 downto 0);
signal rd_gray_d2 : std_logic_vector(5 downto 0);
signal rd_bin : std_logic_vector(5 downto 0);
signal fifo_full : std_logic;
signal fifo_empty : std_logic;
begin
fifo_ram : ram
port map (
clka => clki,
clkb => clko,
wea => wr_en,
web => '0',
addra => wr_addr,
addrb => rd_addr,
dina => din,
dinb => (others => '0'),
douta => open,
doutb => dout
);
wr_en <= din_en and not fifo_full;
rd_en <= dout_en and not fifo_empty;
process (clki, rst) begin
if (rst = '1') then
wr_addr <= (others => '0');
elsif (clki'event and clki = '1') then
if (wr_en = '1') then
wr_addr <= wr_addr1;
end if;
end if;
end process;
wr_addr1 <= wr_addr + 1;
process (clki, rst) begin
if (rst = '1') then
wr_gray <= (others => '0');
elsif (clki'event and clki = '1') then
wr_gray <= wr_addr(5) & (wr_addr(4 downto 0) xor wr_addr(5 downto 1));
end if;
end process;
process (clki, rst) begin
if (rst = '1') then
wr_gray_d1 <= (others => '0');
wr_gray_d2 <= (others => '0');
elsif (clki'event and clki = '1') then
wr_gray_d1 <= wr_gray;
wr_gray_d2 <= wr_gray_d1;
end if;
end process;
process (wr_bin, wr_gray_d2) begin
for i in 5 downto 0 loop
if (i=5) then
wr_bin(i) <= wr_gray_d2(i);
else
wr_bin(i) <= wr_gray_d2(i) xor wr_bin(i+1);
end if;
end loop;
end process;
process (clko, rst) begin
if (rst = '1') then
rd_addr <= (others => '0');
elsif (clko'event and clko = '1') then
if (rd_en = '1') then
rd_addr <= rd_addr + 1;
end if;
end if;
end process;
process (clko, rst) begin
if (rst = '1') then
rd_gray <= (others => '0');
elsif (clko'event and clko = '1') then
rd_gray <= rd_addr(5) & (rd_addr(4 downto 0) xor rd_addr(5 downto 1));
end if;
end process;
process (clko, rst) begin
if (rst = '1') then
rd_gray_d1 <= (others => '0');
rd_gray_d2 <= (others => '0');
elsif (clko'event and clko = '1') then
rd_gray_d1 <= rd_gray;
rd_gray_d2 <= rd_gray_d1;
end if;
end process;
process (rd_bin, rd_gray_d2) begin
for i in 5 downto 0 loop
if (i=5) then
rd_bin(i) <= rd_gray_d2(i);
else
rd_bin(i) <= rd_gray_d2(i) xor rd_bin(i+1);
end if;
end loop;
end process;
fifo_full <= '1' when (wr_addr1 = rd_bin) else '0';
fifo_empty <= '1' when (wr_bin = rd_addr) else '0';
full <= fifo_full;
empty <= fifo_empty;
end architecture RTL;
module fifo
(
input wire clki,
input wire clko,
input wire rst,
output wire full,
output wire empty,
input wire din_en,
input wire [7:0] din,
input wire dout_en,
output wire [7:0] dout
);
wire wr_en;
reg [5:0] wr_addr;
wire [5:0] wr_addr1;
reg [5:0] wr_gray;
reg [5:0] wr_gray_d1;
reg [5:0] wr_gray_d2;
reg [5:0] wr_bin;
wire rd_en;
reg [5:0] rd_addr;
reg [5:0] rd_gray;
reg [5:0] rd_gray_d1;
reg [5:0] rd_gray_d2;
reg [5:0] rd_bin;
wire fifo_full;
wire fifo_empty;
ram fifo_ram (
.clka (clki),
.clkb (clko),
.wea (wr_en),
.web (1'b0),
.addra (wr_addr),
.addrb (rd_addr),
.dina (din),
.dinb (8'd0),
.douta (),
.doutb (dout)
);
assign wr_en = din_en & ~fifo_full;
assign rd_en = dout_en & ~fifo_empty;
always@(posedge clki or posedge rst) begin
if (rst == 1'b1) begin
wr_addr <= 6'd0;
end else begin
if (wr_en == 1'b1) begin
wr_addr <= wr_addr1;
end
end
end
assign wr_addr1 = wr_addr + 6'd1;
always@(posedge clki or posedge rst) begin
if (rst == 1'b1) begin
wr_gray <= 6'd0;
end else begin
wr_gray <= {wr_addr[5],wr_addr[4:0]^wr_addr[5:1]};
end
end
always@(posedge clko or posedge rst) begin
if (rst == 1'b1) begin
wr_gray_d1 <= 6'd0;
wr_gray_d2 <= 6'd0;
end else begin
wr_gray_d1 <= wr_gray;
wr_gray_d2 <= wr_gray_d1;
end
end
always @* begin
integer i;
for(i=5; i>=0; i=i-1) begin
if (i==5)
wr_bin[i] <= wr_gray_d2[i];
else
wr_bin[i] <= wr_gray_d2[i]^wr_bin[i+1];
end
end
always@(posedge clko or posedge rst) begin
if (rst == 1'b1) begin
rd_addr <= 6'd0;
end else begin
if (rd_en == 1'b1) begin
rd_addr <= rd_addr + 6'd1;
end
end
end
always@(posedge clko or posedge rst) begin
if (rst == 1'b1) begin
rd_gray <= 6'd0;
end else begin
rd_gray <= {rd_addr[5],rd_addr[4:0]^rd_addr[5:1]};
end
end
always@(posedge clki or posedge rst) begin
if (rst == 1'b1) begin
rd_gray_d1 <= 6'd0;
rd_gray_d2 <= 6'd0;
end else begin
rd_gray_d1 <= rd_gray;
rd_gray_d2 <= rd_gray_d1;
end
end
always @* begin
integer i;
for(i=5; i>=0; i=i-1) begin
if (i==5)
rd_bin[i] <= rd_gray_d2[i];
else
rd_bin[i] <= rd_gray_d2[i]^rd_bin[i+1];
end
end
assign fifo_full = (wr_addr1 == rd_bin) ? 1'b1 : 1'b0;
assign fifo_empty = (wr_bin == rd_addr) ? 1'b1 : 1'b0;
assign full = fifo_full;
assign empty = fifo_empty;
endmodule
例では、アドレスはバイナリとしましたが、アドレスをグレイコードカウンタで生成するという方法も出来ると思います。(その場合、アドレスの差分をどう計算するかという問題があります。)
-
グレイコードは前後に隣接する符号間のハミング距離が必ず1、つまりある値から隣接した値に変化する際に常に1ビットしか変化しません。1ビットの変化だけであれば、単純なFF2段の同期化でメタステーブルの対応が可能となります。 ↩︎
Discussion