使用頻度の高い部品のHDL記述例 -FIFO-

2021/07/29に公開

使用頻度の高い部品のHDL記述例 -カウンタ-使用頻度の高い部品のHDL記述例 -内部RAM-を組み合わせて、FIFOを作ることが出来ます。
FIFOはデータのバッファとして使用したり、異なるクロックドメインのデータ載せ替えに使用したり、比較的使用頻度は高いです。
もちろんツールで作ることも可能ですが、自作でないと難しいこともあります。(例えば、入り口と出口でバス幅が異なるとか。)
基本の作り方が分かれば、色々と応用出来ると思います。

例1 同期FIFO

入力も出力も同じクロックで動作するFIFOの例です。デュアルポートRAMを用いて、書き込んだら書き込み側アドレスをインクリメント、読み出したら読み出し側アドレスをインクリメントというのを基本的な動作にして、後は互いに追い越してしまわないよう制御出来るようにします。アドレスの差分を用いてFIFOに溜まっているデータ量や、それらを基にしたFULLやEMPTY等が一般的です。

VHDL
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;
verilog
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に溜まっているデータ量をそれぞれのクロックで出力して外側で判断するというのが有効になると思います。

VHDL
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;
verilog
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ビットしか変化しません。1ビットの変化だけであれば、単純なFF2段の同期化でメタステーブルの対応が可能となります。 ↩︎

Discussion