FPGA超入門 その2-VHDLとVelilog-

2021/04/16に公開

ハードウェア記述言語は様々なものがありますが、代表的な2つ(VHDLとVelilog)について、簡単に説明します。

VHDL

IEEE 1076として標準化されています。最新版はIEEE 1076-2019ですが、まだ対応したツールが少ないので、IEEE 1076-2008が実質的な最新版と言えるでしょう。
拡張子は.vhdです。windowsだとハードディスクイメージファイルと受け取られてしまうこともあります。.vhdlでも可だったかと思います。

Velilog

IEEE 1364として標準化されています。最新版はIEEE 1364-2005です。後継言語として、System Verilogがあり、こちらもIEEE 1800として標準化されています。最新版はIEEE 1800-2017です。Velilogより色々と扱い易くなっている面もあるので、これから習得するのであれば、System Verilogの方がよいかもしれません。
verilogの拡張子は.v、System Verilogの拡張子は.svです。

記述例

例として、下記のようなserial_adder回路について、それぞれの記述例を示します。

色の付いた四角で囲まれた部分の機能は、それぞれ下記のような感じです。

ブロック 機能
half_adder AとBのXORをSに出力
AとBのANDをCに出力
full_adder 後段のhalf_adderのAに前段のhalf_adderのSを入力
後段のhalf_adderのBにXを入力
後段のhalf_adderのSをSに出力
前段と後段のhalf_adderのCのORをCに出力
serial_adder AとBをそれぞれfull_adderのAとBに入力
full_adderのSをSに出力
full_adderのCをD-FFのDに入力
D-FFのQをfull_adderのXに入力
CLKはD-FFに接続
それぞれソースコードとして内側から順に記述してみます。

VHDL

half_adder.vhd
library IEEE;
use IEEE.std_logic_1164.all;

entity half_adder is
  port ( 
    A : in  std_logic;
    B : in  std_logic;
    S : out std_logic;
    C : out std_logic
  );
end entity half_adder;

architecture RTL of half_adder is
begin
  S <= A xor B;
  C <= A and B;
end architecture RTL;
full_adder.vhd
library IEEE;
use IEEE.std_logic_1164.all;

entity full_adder is
  port ( 
    A : in  std_logic;
    B : in  std_logic;
    X : in  std_logic;
    S : out std_logic;
    C : out std_logic
  );
end entity full_adder;

architecture RTL of full_adder is
component half_adder is
  port ( 
    A : in  std_logic;
    B : in  std_logic;
    S : out std_logic;
    C : out std_logic
  );
end component half_adder;

signal S1 : std_logic;
signal C1 : std_logic;
signal C2 : std_logic;

begin
half_adder1 : half_adder
  port map ( 
    A => A,
    B => B,
    S => S1,
    C => C1
  );
half_adder2 : half_adder
  port map ( 
    A => S1,
    B => X,
    S => S,
    C => C2
  );
  C <= C1 or C2;
end architecture RTL;
serial_adder.vhd
library IEEE;
use IEEE.std_logic_1164.all;

entity serial_adder is
  port ( 
    A   : in  std_logic;
    B   : in  std_logic;
    CLK : in  std_logic;
    S   : out std_logic
  );
end entity serial_adder;

architecture RTL of serial_adder is
component full_adder is
  port ( 
    A : in  std_logic;
    B : in  std_logic;
    X : in  std_logic;
    S : out std_logic;
    C : out std_logic
  );
end component full_adder;

signal C1 : std_logic;
signal Q1 : std_logic;

begin
full_adder1 : full_adder
  port map ( 
    A => A,
    B => B,
    X => Q1,
    S => S,
    C => C1
  );
  process(CLK) begin
    if(CLK'event and CLK = '1') then
      Q1 <= C1;
    end if;
  end process;
end architecture RTL;

Velilog

half_adder.v
module half_adder
  ( 
    input  wire A,
    input  wire B,
    output wire S,
    output wire C
  );

  assign S = A ^ B;
  assign C = A & B;
endmodule
full_adder.v
module full_adder
  ( 
    input  wire A,
    input  wire B,
    input  wire X,
    output wire S,
    output wire C 
  );

wire S1;
wire C1;
wire C2;

half_adder half_adder1
  ( 
    .A(A),
    .B(B),
    .S(S1),
    .C(C1)
  );
half_adder half_adder2
  (
    .A(S1),
    .B(X),
    .S(S),
    .C(C2)
  );
  assign C = C1 | C2;
endmodule
serial_adder.v
module serial_adder
  ( 
    input  wire A,
    input  wire B,
    input  wire CLK,
    output wire S
  );

wire C1;
reg  Q1;

full_adder full_adder1
  ( 
    .A(A),
    .B(B),
    .X(Q1),
    .S(S),
    .C(C1)
  );
  always@(posedge CLK) begin
    Q1 <= C1;
  end
endmodule

細かいことは追々書いていくことにして、こんな感じで回路を記述していきます。
実はこのserial_adderは、2進数の足し算の筆算を行うような回路になっていて、加算したい2つの数を2進数にして、CLK毎に最下位bitから順に入力していくと、出力Sに最下位bitから順に加算結果が出てきます。
full_adderはA+B+Xの足し算を行い、結果がCとS(Cがbit1、Sがbit0)に出てきますので、もっと簡単な書き方で同じ動作を実現することが出来ます。

serial_adder2.vhd
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;

entity serial_adder2 is
  port ( 
    A   : in  std_logic;
    B   : in  std_logic;
    CLK : in  std_logic;
    S   : out std_logic
  );
end entity serial_adder2;

architecture RTL of serial_adder2 is

signal add : std_logic_vector(1 downto 0);
signal Q1  : std_logic;

begin
  add <= ('0' & A) + ('0' & B) + ('0' & Q1);
  S   <= add(0);
  process(CLK) begin
    if(CLK'event and CLK = '1') then
      Q1 <= add(1);
    end if;
  end process;
end architecture RTL;
serial_adder2.v
module serial_adder2
  ( 
    input  wire A,
    input  wire B,
    input  wire CLK,
    output wire S
  );

wire [1:0] add;
reg  Q1;

  assign add = A + B + Q1;
  assign S   = add[0];

  always@(posedge CLK) begin
    Q1 <= add[1];
  end
endmodule

このように、同じ機能を実現するのに全く違った書き方もあります。上記の場合は恐らく似たような回路が合成されると思いますが、途中の回路構成が全く異なっても最終的な結果は同じになるように作ることもあります。それは、例えばリソースを少なくするものだったり、早く動作するものだったり。どう実現するかを考えるのも醍醐味であり、難しいところでもあると思います。

Discussion