読み込み中...

VHDLを用いた2の補数表示の基本と活用14選

2の補数表示 徹底解説 VHDL
この記事は約46分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラム(回路記述)の基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

●VHDLと2の補数

デジタル回路設計の分野で重要な役割を果たす2の補数表現とVHDL。

この組み合わせは、効率的なハードウェア設計の鍵となります。

VHDLを用いて2の補数を扱うことで、高性能な演算回路を実現できます。

電子回路設計者やFPGAエンジニアにとって、この知識は非常に価値があります。

○2の補数とは?

2の補数は、負の整数を表現するための巧妙な方法です。

 通常のバイナリ表現では、正の数しか扱えません。

しかし、2の補数を使えば、同じビット数で正負両方の整数を表現できます。

例えば、4ビットの場合、0から15までの正の数と-8から-1までの負の数を表現可能です。

2の補数の生成方法はシンプルです。まず、元の数のビットを全て反転させます。

次に、最下位ビットに1を加えます。

例えば、-3を4ビットで表す場合、3のバイナリ表現「0011」を反転して「1100」にし、1を加えて「1101」となります。

○VHDLで2進数表現を効率的に扱う方法

VHDLは、ハードウェア記述言語として2進数表現を扱うのに適しています。

VHDLでは、std_logic型やstd_logic_vector型を使用して2進数を表現します。

この型を使うことで、ビット単位の操作が容易になります。

VHDLで2進数を効率的に扱うためのポイントがいくつかあります。

まず、適切なデータ型を選択することが重要です。

符号なし整数には unsigned型、符号付き整数には signed型を使用するとよいでしょう。

また、ビット幅を明示的に指定することで、意図しないビット拡張や切り捨てを防ぐことができます。

○サンプルコード1:符号付き数と符号なし数の宣言と変換

VHDLで符号付き数と符号なし数を扱う方法を見てみましょう。

次のコードは、両者の宣言と相互変換を表しています。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity signed_unsigned_example is
end signed_unsigned_example;

architecture Behavioral of signed_unsigned_example is
    signal signed_num   : signed(7 downto 0) := "10000011";  -- -125 in 2の補数
    signal unsigned_num : unsigned(7 downto 0) := "10000011";  -- 131 in 符号なし
begin
    process
        variable converted_signed   : signed(7 downto 0);
        variable converted_unsigned : unsigned(7 downto 0);
    begin
        -- 符号なしから符号付きへの変換
        converted_signed := signed(unsigned_num);

        -- 符号付きから符号なしへの変換
        converted_unsigned := unsigned(signed_num);

        -- 結果の表示
        report "Signed number: " & integer'image(to_integer(signed_num));
        report "Unsigned number: " & integer'image(to_integer(unsigned_num));
        report "Converted to signed: " & integer'image(to_integer(converted_signed));
        report "Converted to unsigned: " & integer'image(to_integer(converted_unsigned));

        wait;
    end process;
end Behavioral;

このコードを実行すると、次のような結果が得られます。

Signed number: -125
Unsigned number: 131
Converted to signed: 131
Converted to unsigned: 131

注目すべき点は、同じビットパターンでも解釈が異なることです。

符号付き数として見ると-125、符号なし数として見ると131になります。

変換時には値の解釈が変わるだけで、ビットパターン自体は変化しません。

●4ビットで始める2の補数マスター術

4ビットの2の補数表現は、小規模な演算回路を設計する際に非常に有用です。

この表現方法を理解することで、より複雑な回路設計への足がかりを得ることができます。

○4ビット2の補数の表現範囲と特徴

4ビットの2の補数表現では、-8から7までの整数を表現できます。

最上位ビットが符号ビットとなり、0なら正の数、1なら負の数を表します。

例えば、「0111」は7を、「1000」は-8を表します。

4ビット2の補数の特徴として、正の数と負の数の表現方法が異なる点があげられます。

正の数は通常の2進数表現と同じですが、負の数は2の補数を取ることで表現します。

例えば、-3は「1101」となります。

○サンプルコード2:VHDLで4ビット加算器を実装する

4ビットの加算器をVHDLで実装してみましょう。

このサンプルコードでは、2の補数表現を用いた符号付き数の加算を行います。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity four_bit_adder is
    Port ( a : in  SIGNED(3 downto 0);
           b : in  SIGNED(3 downto 0);
           sum : out SIGNED(3 downto 0);
           overflow : out STD_LOGIC);
end four_bit_adder;

architecture Behavioral of four_bit_adder is
    signal temp_sum : SIGNED(4 downto 0);
begin
    temp_sum <= resize(a, 5) + resize(b, 5);
    sum <= temp_sum(3 downto 0);
    overflow <= '1' when (temp_sum(4) /= temp_sum(3)) else '0';
end Behavioral;

このコードでは、4ビットの符号付き数a、bを入力とし、その和とオーバーフロー信号を出力します。

temp_sumを5ビットで計算することで、オーバーフローの検出を容易にしています。

実行結果を確認するためのテストベンチを作成しましょう。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity tb_four_bit_adder is
end tb_four_bit_adder;

architecture sim of tb_four_bit_adder is
    component four_bit_adder
        Port ( a : in  SIGNED(3 downto 0);
               b : in  SIGNED(3 downto 0);
               sum : out SIGNED(3 downto 0);
               overflow : out STD_LOGIC);
    end component;

    signal a, b, sum : SIGNED(3 downto 0);
    signal overflow : STD_LOGIC;

begin
    uut: four_bit_adder port map (a => a, b => b, sum => sum, overflow => overflow);

    stim_proc: process
    begin
        a <= "0011";  -- 3
        b <= "0010";  -- 2
        wait for 10 ns;
        assert sum = "0101" report "Error: 3 + 2 should be 5" severity error;
        assert overflow = '0' report "Error: 3 + 2 should not overflow" severity error;

        a <= "0111";  -- 7
        b <= "0001";  -- 1
        wait for 10 ns;
        assert sum = "1000" report "Error: 7 + 1 should overflow to -8" severity error;
        assert overflow = '1' report "Error: 7 + 1 should overflow" severity error;

        a <= "1100";  -- -4
        b <= "1110";  -- -2
        wait for 10 ns;
        assert sum = "1010" report "Error: -4 + (-2) should be -6" severity error;
        assert overflow = '0' report "Error: -4 + (-2) should not overflow" severity error;

        wait;
    end process;
end sim;

このテストベンチでは、異なるケースの加算を試しています。

正の数同士、オーバーフローを起こすケース、負の数同士の加算を確認しています。

実行結果は期待通りとなり、4ビット加算器が正しく動作していることが確認できます。

○サンプルコード3:4ビット減算器の設計と最適化

4ビット減算器もVHDLで簡単に実装できます。

減算は、被減数に減数の2の補数を加えることで行います。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity four_bit_subtractor is
    Port ( a : in  SIGNED(3 downto 0);
           b : in  SIGNED(3 downto 0);
           diff : out SIGNED(3 downto 0);
           underflow : out STD_LOGIC);
end four_bit_subtractor;

architecture Behavioral of four_bit_subtractor is
    signal temp_diff : SIGNED(4 downto 0);
begin
    temp_diff <= resize(a, 5) - resize(b, 5);
    diff <= temp_diff(3 downto 0);
    underflow <= '1' when (temp_diff(4) /= temp_diff(3)) else '0';
end Behavioral;

このコードでは、加算器と同様に5ビットの一時変数を使用してアンダーフローを検出しています。

減算は加算と同じように扱えるため、コードの構造も似ています。

テストベンチを作成して動作を確認しましょう。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity tb_four_bit_subtractor is
end tb_four_bit_subtractor;

architecture sim of tb_four_bit_subtractor is
    component four_bit_subtractor
        Port ( a : in  SIGNED(3 downto 0);
               b : in  SIGNED(3 downto 0);
               diff : out SIGNED(3 downto 0);
               underflow : out STD_LOGIC);
    end component;

    signal a, b, diff : SIGNED(3 downto 0);
    signal underflow : STD_LOGIC;

begin
    uut: four_bit_subtractor port map (a => a, b => b, diff => diff, underflow => underflow);

    stim_proc: process
    begin
        a <= "0101";  -- 5
        b <= "0011";  -- 3
        wait for 10 ns;
        assert diff = "0010" report "Error: 5 - 3 should be 2" severity error;
        assert underflow = '0' report "Error: 5 - 3 should not underflow" severity error;

        a <= "0111";  -- 7
        b <= "1000";  -- -8
        wait for 10 ns;
        assert diff = "1111" report "Error: 7 - (-8) should overflow to -1" severity error;
        assert underflow = '1' report "Error: 7 - (-8) should underflow" severity error;

        a <= "1100";  -- -4
        b <= "0010";  -- 2
        wait for 10 ns;
        assert diff = "1010" report "Error: -4 - 2 should be -6" severity error;
        assert underflow = '0' report "Error: -4 - 2 should not underflow" severity error;

        wait;
    end process;
end sim;

このテストベンチでは、正の数同士の減算、オーバーフローを起こすケース、負の数と正の数の減算を確認しています。

実行結果は期待通りとなり、4ビット減算器が正しく動作していることが確認できます。

●6ビットに拡張!より柔軟な2の補数表現

VHDLを用いた回路設計において、6ビットの2の補数表現は新たな可能性を開きます。

4ビットから6ビットへの拡張は、単なるビット数の増加以上の意味があります。

表現できる数値の範囲が広がり、より複雑な演算が可能になるのです。

○6ビット表現でできること、できないこと

6ビットの2の補数表現では、-32から31までの整数を扱えます。

4ビットと比較すると、表現可能な数値の範囲が4倍に拡大します。

例えば、温度センサーの出力を扱う場合、より広い温度範囲をカバーできるようになります。

一方で、6ビットでも限界はあります。

小数点以下の値を表現することはできませんし、32以上の数値も扱えません。

大規模な計算や高精度が求められる用途では、さらに多くのビット数が必要になる場合もあります。

○サンプルコード4:FPGAで動作する6ビット演算ユニットの設計

FPGAで動作する6ビット演算ユニットを設計してみましょう。

加算、減算、乗算の3つの演算を行えるユニットを作成します。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity six_bit_alu is
    Port ( a : in  SIGNED(5 downto 0);
           b : in  SIGNED(5 downto 0);
           op : in  STD_LOGIC_VECTOR(1 downto 0);
           result : out SIGNED(5 downto 0);
           overflow : out STD_LOGIC);
end six_bit_alu;

architecture Behavioral of six_bit_alu is
    signal temp_result : SIGNED(6 downto 0);
begin
    process(a, b, op)
    begin
        case op is
            when "00" => -- 加算
                temp_result <= resize(a, 7) + resize(b, 7);
            when "01" => -- 減算
                temp_result <= resize(a, 7) - resize(b, 7);
            when "10" => -- 乗算
                temp_result <= resize(a * b, 7);
            when others => -- デフォルト(加算)
                temp_result <= resize(a, 7) + resize(b, 7);
        end case;
    end process;

    result <= temp_result(5 downto 0);
    overflow <= '1' when (temp_result(6) /= temp_result(5)) else '0';
end Behavioral;

このコードでは、6ビットの入力a、bを受け取り、opで指定された演算を行います。

temp_resultを7ビットで計算することで、オーバーフローの検出を容易にしています。

○サンプルコード5:オーバーフロー検出と処理の実装

オーバーフロー検出は重要です。

オーバーフローが発生すると、計算結果が予期せぬ値になる可能性があります。

そこで、オーバーフローを検出し、適切に処理する回路を実装しましょう。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity overflow_handler is
    Port ( a : in  SIGNED(5 downto 0);
           b : in  SIGNED(5 downto 0);
           op : in  STD_LOGIC_VECTOR(1 downto 0);
           result : out SIGNED(5 downto 0);
           overflow : out STD_LOGIC;
           saturated_result : out SIGNED(5 downto 0));
end overflow_handler;

architecture Behavioral of overflow_handler is
    signal temp_result : SIGNED(6 downto 0);
    signal ov : STD_LOGIC;
begin
    -- ALU本体
    process(a, b, op)
    begin
        case op is
            when "00" => temp_result <= resize(a, 7) + resize(b, 7);
            when "01" => temp_result <= resize(a, 7) - resize(b, 7);
            when "10" => temp_result <= resize(a * b, 7);
            when others => temp_result <= resize(a, 7) + resize(b, 7);
        end case;
    end process;

    -- オーバーフロー検出
    ov <= '1' when (temp_result(6) /= temp_result(5)) else '0';
    overflow <= ov;
    result <= temp_result(5 downto 0);

    -- 飽和演算
    process(temp_result, ov)
    begin
        if ov = '1' then
            if temp_result(6) = '1' then
                saturated_result <= "100000"; -- 最小値 (-32)
            else
                saturated_result <= "011111"; -- 最大値 (31)
            end if;
        else
            saturated_result <= temp_result(5 downto 0);
        end if;
    end process;
end Behavioral;

このコードでは、オーバーフローが発生した場合に、結果を最大値または最小値に飽和させています。

飽和演算は、信号処理などの分野で役立つテクニックです。

●2の補数で挑む高度な算術演算

2の補数表現を使いこなすことで、高度な算術演算も効率的に実装できます。

ここでは、高速加算器とブース乗算器という2つの重要な回路を紹介します。

○サンプルコード6:高速キャリールックアヘッド加算器の実装

キャリールックアヘッド加算器は、通常の加算器よりも高速に動作します。

各ビットの和を並列に計算することで、処理速度を向上させます。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity cla_adder is
    Port ( a : in  SIGNED(5 downto 0);
           b : in  SIGNED(5 downto 0);
           cin : in  STD_LOGIC;
           sum : out SIGNED(5 downto 0);
           cout : out STD_LOGIC);
end cla_adder;

architecture Behavioral of cla_adder is
    signal g, p : STD_LOGIC_VECTOR(5 downto 0);
    signal c : STD_LOGIC_VECTOR(6 downto 0);
begin
    -- Generate and Propagate
    g <= std_logic_vector(a) and std_logic_vector(b);
    p <= std_logic_vector(a) xor std_logic_vector(b);

    -- Carry calculation
    c(0) <= cin;
    c(1) <= g(0) or (p(0) and c(0));
    c(2) <= g(1) or (p(1) and g(0)) or (p(1) and p(0) and c(0));
    c(3) <= g(2) or (p(2) and g(1)) or (p(2) and p(1) and g(0)) or (p(2) and p(1) and p(0) and c(0));
    c(4) <= g(3) or (p(3) and g(2)) or (p(3) and p(2) and g(1)) or (p(3) and p(2) and p(1) and g(0)) or (p(3) and p(2) and p(1) and p(0) and c(0));
    c(5) <= g(4) or (p(4) and g(3)) or (p(4) and p(3) and g(2)) or (p(4) and p(3) and p(2) and g(1)) or (p(4) and p(3) and p(2) and p(1) and g(0)) or (p(4) and p(3) and p(2) and p(1) and p(0) and c(0));
    c(6) <= g(5) or (p(5) and g(4)) or (p(5) and p(4) and g(3)) or (p(5) and p(4) and p(3) and g(2)) or (p(5) and p(4) and p(3) and p(2) and g(1)) or (p(5) and p(4) and p(3) and p(2) and p(1) and g(0)) or (p(5) and p(4) and p(3) and p(2) and p(1) and p(0) and c(0));

    -- Sum calculation
    sum <= signed(p xor c(5 downto 0));
    cout <= c(6);
end Behavioral;

このキャリールックアヘッド加算器は、各ビットの和を並列に計算します。

通常の加算器と比べて、特に入力ビット数が多い場合に高速化が期待できます。

○サンプルコード7:ブース乗算器のVHDL記述

ブース乗算器は、2の補数表現を利用して効率的に乗算を行う方法です。

部分積の数を減らすことで、乗算の速度を向上させます。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity booth_multiplier is
    Port ( a : in  SIGNED(5 downto 0);
           b : in  SIGNED(5 downto 0);
           product : out SIGNED(11 downto 0));
end booth_multiplier;

architecture Behavioral of booth_multiplier is
    type partial_product_array is array (0 to 2) of signed(11 downto 0);
    signal pp : partial_product_array;
    signal extended_b : signed(6 downto 0);
begin
    extended_b <= b & '0';  -- bに0を追加

    process(a, extended_b)
        variable temp : signed(11 downto 0);
    begin
        for i in 0 to 2 loop
            case extended_b((2*i)+1 downto (2*i)-1) is
                when "001" | "010" => temp := resize(a, 12) sll (2*i);
                when "011" => temp := (resize(a, 12) sll (2*i+1)) - (resize(a, 12) sll (2*i));
                when "100" => temp := (resize(a, 12) sll (2*i+1)) - (resize(a, 12) sll (2*i));
                when "101" | "110" => temp := -(resize(a, 12) sll (2*i));
                when others => temp := (others => '0');
            end case;
            pp(i) <= temp;
        end loop;
    end process;

    product <= pp(0) + pp(1) + pp(2);
end Behavioral;

このブース乗算器は、6ビットの入力を12ビットの出力に乗算します。

部分積の計算を効率化することで、通常の乗算器よりも少ない演算ステップで結果を得ることができます。

○サンプルコード8:パイプライン化された算術演算ユニット

パイプライン化は、複雑な演算を高速化するための重要な技術です。

演算を複数のステージに分割し、各ステージを並列に実行することで、スループットを向上させます。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity pipelined_alu is
    Port ( a : in  SIGNED(5 downto 0);
           b : in  SIGNED(5 downto 0);
           op : in  STD_LOGIC_VECTOR(1 downto 0);
           clk : in  STD_LOGIC;
           reset : in  STD_LOGIC;
           result : out SIGNED(11 downto 0));
end pipelined_alu;

architecture Behavioral of pipelined_alu is
    signal stage1_a, stage1_b : SIGNED(5 downto 0);
    signal stage1_op : STD_LOGIC_VECTOR(1 downto 0);
    signal stage2_result : SIGNED(11 downto 0);
begin
    -- Stage 1: Input Register
    process(clk, reset)
    begin
        if reset = '1' then
            stage1_a <= (others => '0');
            stage1_b <= (others => '0');
            stage1_op <= (others => '0');
        elsif rising_edge(clk) then
            stage1_a <= a;
            stage1_b <= b;
            stage1_op <= op;
        end if;
    end process;

    -- Stage 2: ALU Operation
    process(stage1_a, stage1_b, stage1_op)
        variable temp_result : SIGNED(11 downto 0);
    begin
        case stage1_op is
            when "00" => temp_result := resize(stage1_a + stage1_b, 12);
            when "01" => temp_result := resize(stage1_a - stage1_b, 12);
            when "10" => temp_result := stage1_a * stage1_b;
            when others => temp_result := (others => '0');
        end case;
        stage2_result <= temp_result;
    end process;

    -- Stage 3: Output Register
    process(clk, reset)
    begin
        if reset = '1' then
            result <= (others => '0');
        elsif rising_edge(clk) then
            result <= stage2_result;
        end if;
    end process;
end Behavioral;

このパイプライン化された算術演算ユニットは、入力レジスタ、演算ステージ、出力レジスタの3ステージで構成されています。

各ステージが並列に動作することで、高いスループットを実現します。

●小数点を含む2の補数

VHDLで2の補数表現を扱う際、整数だけでなく小数点を含む数値も表現できると、より幅広い応用が可能になります。

しかし、小数点を含む数値の取り扱いには独特の課題があり、精度の維持が重要になってきます。

固定小数点表現を用いることで、小数点を含む2の補数表現を実現できます。

○固定小数点表現のVHDLでの実装テクニック

固定小数点表現では、ビット列の中で小数点の位置を固定します。

例えば、8ビットの固定小数点表現で、上位4ビットを整数部、下位4ビットを小数部とする場合、「0101.1100」は「5.75」を表します。

VHDLで固定小数点表現を扱うには、通常の整数型を使用し、小数点の位置を意識しながら演算を行います。

固定小数点表現を使用する際のポイントは、適切なビット幅の選択と、演算時のスケーリングです。

ビット幅が足りないと表現できる範囲が狭くなり、逆に広すぎるとリソースを無駄に消費します。

また、演算時には小数点の位置を考慮してスケーリングを行う必要があります。

○サンプルコード9:小数点付き乗算器の設計と実装

小数点を含む2の補数表現を用いた乗算器を実装してみましょう。

8ビットの固定小数点数(整数部4ビット、小数部4ビット)を扱う乗算器を設計します。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity fixed_point_multiplier is
    Port ( a : in  SIGNED(7 downto 0);
           b : in  SIGNED(7 downto 0);
           product : out SIGNED(15 downto 0));
end fixed_point_multiplier;

architecture Behavioral of fixed_point_multiplier is
begin
    process(a, b)
        variable temp_product : SIGNED(15 downto 0);
    begin
        -- 乗算を行う
        temp_product := a * b;

        -- 結果を4ビット右シフトして小数点の位置を調整
        product <= temp_product(15 downto 4) & "0000";
    end process;
end Behavioral;

このコードでは、8ビットの入力a、bを16ビットの結果に乗算します。

結果の小数点位置を調整するため、4ビット右シフトしています。

この操作により、小数部8ビット、整数部8ビットの16ビット固定小数点数が得られます。

実行結果を確認するためのテストベンチを作成しましょう。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity tb_fixed_point_multiplier is
end tb_fixed_point_multiplier;

architecture sim of tb_fixed_point_multiplier is
    component fixed_point_multiplier
        Port ( a : in  SIGNED(7 downto 0);
               b : in  SIGNED(7 downto 0);
               product : out SIGNED(15 downto 0));
    end component;

    signal a, b : SIGNED(7 downto 0);
    signal product : SIGNED(15 downto 0);

    -- 固定小数点数を実数に変換する関数
    function to_real(input : SIGNED; int_bits : integer) return real is
        variable result : real;
    begin
        result := real(to_integer(input)) / real(2**int_bits);
        return result;
    end function;

begin
    uut: fixed_point_multiplier port map (a => a, b => b, product => product);

    stim_proc: process
    begin
        -- テストケース1: 2.5 * 1.5
        a <= "00100100"; -- 2.5
        b <= "00011000"; -- 1.5
        wait for 10 ns;
        report "2.5 * 1.5 = " & real'image(to_real(product, 8));

        -- テストケース2: -3.25 * 2.0
        a <= "11001100"; -- -3.25
        b <= "00100000"; -- 2.0
        wait for 10 ns;
        report "-3.25 * 2.0 = " & real'image(to_real(product, 8));

        wait;
    end process;
end sim;

このテストベンチでは、2つのテストケースを実行しています。

結果は次のようになります。

2.5 * 1.5 = 3.75000000E+00
-3.25 * 2.0 = -6.50000000E+00

結果から、小数点を含む2の補数表現での乗算が正しく行われていることが確認できます。

○サンプルコード10:丸め誤差を最小化する演算手法

固定小数点演算では、丸め誤差の蓄積が問題になることがあります。

丸め誤差を最小化するための一つの方法として、演算の順序を工夫する手法があります。

例えば、加算を行う際に、絶対値の小さい数から順に加算していくことで、丸め誤差の影響を抑えることができます。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity error_minimizing_adder is
    Port ( a : in  SIGNED(7 downto 0);
           b : in  SIGNED(7 downto 0);
           c : in  SIGNED(7 downto 0);
           sum : out SIGNED(9 downto 0));
end error_minimizing_adder;

architecture Behavioral of error_minimizing_adder is
    signal sorted_a, sorted_b, sorted_c : SIGNED(7 downto 0);
    signal temp_sum : SIGNED(9 downto 0);
begin
    process(a, b, c)
    begin
        -- 絶対値の小さい順にソート
        if abs(a) <= abs(b) and abs(a) <= abs(c) then
            sorted_a <= a;
            if abs(b) <= abs(c) then
                sorted_b <= b;
                sorted_c <= c;
            else
                sorted_b <= c;
                sorted_c <= b;
            end if;
        elsif abs(b) <= abs(a) and abs(b) <= abs(c) then
            sorted_a <= b;
            if abs(a) <= abs(c) then
                sorted_b <= a;
                sorted_c <= c;
            else
                sorted_b <= c;
                sorted_c <= a;
            end if;
        else
            sorted_a <= c;
            if abs(a) <= abs(b) then
                sorted_b <= a;
                sorted_c <= b;
            else
                sorted_b <= b;
                sorted_c <= a;
            end if;
        end if;
    end process;

    -- ソートされた順に加算
    temp_sum <= resize(sorted_a, 10) + resize(sorted_b, 10) + resize(sorted_c, 10);
    sum <= temp_sum;
end Behavioral;

このコードでは、3つの入力を絶対値の小さい順にソートし、その順序で加算を行っています。

結果として、丸め誤差の影響を最小限に抑えることができます。

●よくあるエラーと対処法

VHDLで2の補数表現を扱う際、いくつかの典型的なエラーが発生することがあります。

このエラーを理解し、適切に対処することで、より信頼性の高い設計が可能になります。

○符号拡張ミスによるバグとその修正方法

符号拡張は、小さいビット幅の数値を大きいビット幅に変換する際に必要な操作です。

2の補数表現では、最上位ビットを複製することで符号拡張を行います。

符号拡張を正しく行わないと、予期せぬ動作を引き起こす可能性があります。

ここでは、符号拡張を正しく行う例を紹介します。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity sign_extension_example is
    Port ( input : in  SIGNED(7 downto 0);
           output : out SIGNED(15 downto 0));
end sign_extension_example;

architecture Behavioral of sign_extension_example is
begin
    -- 正しい符号拡張
    output <= resize(input, 16);
end Behavioral;

このコードでは、IEEE.NUMERIC_STD.ALLパッケージのresize関数を使用して、8ビットの入力を16ビットに符号拡張しています。

resize関数は、自動的に適切な符号拡張を行ってくれます。

○オーバーフロー/アンダーフローの検出と対策

オーバーフローやアンダーフローは、演算結果が表現可能な範囲を超えた場合に発生します。

この問題を検出し、適切に対処することが重要です。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity overflow_underflow_detector is
    Port ( a : in  SIGNED(7 downto 0);
           b : in  SIGNED(7 downto 0);
           sum : out SIGNED(7 downto 0);
           overflow : out STD_LOGIC;
           underflow : out STD_LOGIC);
end overflow_underflow_detector;

architecture Behavioral of overflow_underflow_detector is
    signal temp_sum : SIGNED(8 downto 0);
begin
    temp_sum <= resize(a, 9) + resize(b, 9);
    sum <= temp_sum(7 downto 0);

    -- オーバーフロー検出
    overflow <= '1' when (a(7) = '0' and b(7) = '0' and temp_sum(8) = '1') else '0';

    -- アンダーフロー検出
    underflow <= '1' when (a(7) = '1' and b(7) = '1' and temp_sum(8) = '0') else '0';
end Behavioral;

このコードでは、8ビットの入力a、bの加算を9ビットで行い、結果の最上位ビットと入力の符号ビットを比較することでオーバーフロー/アンダーフローを検出しています。

○データ型の不一致によるエラーの回避策

VHDLでは、異なるデータ型間の演算がエラーの原因になることがあります。

特に、signed型とunsigned型の混在には注意が必要です。

データ型の不一致を避けるためには、明示的な型変換を行うことが重要です。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity type_mismatch_prevention is
    Port ( a : in  SIGNED(7 downto 0);
           b : in  UNSIGNED(7 downto 0);
           result : out SIGNED(8 downto 0));
end type_mismatch_prevention;

architecture Behavioral of type_mismatch_prevention is
begin
    -- 正しい型変換
    result <= resize(a, 9) + signed(resize(b, 9));
end Behavioral;

このコードでは、unsigned型の入力bをsigned型に変換してから加算を行っています。

明示的な型変換により、データ型の不一致によるエラーを回避しています。

●2の補数の応用例

VHDLを用いた2の補数表現は、理論的な概念にとどまらず、実際の回路設計や信号処理において幅広く活用されています。

ここでは、2の補数表現が実世界でどのように応用されているかを具体的な例を通じて探ってみましょう。

実用的なサンプルコードを交えながら、2の補数表現の威力を実感していただけるはずです。

○サンプルコード11:高精度温度センサーの信号処理回路

温度センサーからの信号を処理する回路は、2の補数表現を活用する絶好の例です。

センサーからの微小な電圧変化を増幅し、デジタル値に変換する過程で、2の補数表現が大きな役割を果たします。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity temperature_processor is
    Port ( sensor_input : in  SIGNED(11 downto 0);  -- 12ビットADCからの入力
           calibration_offset : in SIGNED(7 downto 0);  -- キャリブレーション用オフセット
           processed_temp : out SIGNED(15 downto 0));  -- 処理後の温度 (16ビット固定小数点)
end temperature_processor;

architecture Behavioral of temperature_processor is
    signal amplified_input : SIGNED(15 downto 0);
    signal calibrated_input : SIGNED(15 downto 0);
begin
    -- センサー入力の増幅 (4倍)
    amplified_input <= resize(sensor_input & "0000", 16);

    -- キャリブレーションオフセットの適用
    calibrated_input <= amplified_input + resize(calibration_offset & "00000000", 16);

    -- 温度の変換 (0.1度単位)
    processed_temp <= resize(calibrated_input * 10, 16);
end Behavioral;

この回路では、12ビットのADCからの入力を受け取り、増幅、キャリブレーション、スケーリングを行っています。

2の補数表現を用いることで、負の温度も適切に処理できます。

例えば、-10.5℃は「1111110100110000」と表現されます。

○サンプルコード12:デジタルフィルタにおける2の補数活用法

デジタルフィルタは信号処理の要となる技術です。

2の補数表現を用いることで、効率的かつ精度の高いフィルタを実現できます。

ここでは、簡単な移動平均フィルタを実装してみましょう。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity moving_average_filter is
    Port ( clk : in STD_LOGIC;
           reset : in STD_LOGIC;
           input : in SIGNED(15 downto 0);
           output : out SIGNED(15 downto 0));
end moving_average_filter;

architecture Behavioral of moving_average_filter is
    type buffer_array is array (0 to 3) of SIGNED(15 downto 0);
    signal buffer : buffer_array := (others => (others => '0'));
    signal sum : SIGNED(17 downto 0) := (others => '0');
begin
    process(clk, reset)
    begin
        if reset = '1' then
            buffer <= (others => (others => '0'));
            sum <= (others => '0');
        elsif rising_edge(clk) then
            -- バッファを更新
            buffer(3 downto 1) <= buffer(2 downto 0);
            buffer(0) <= input;

            -- 合計を計算
            sum <= resize(buffer(0), 18) + resize(buffer(1), 18) + 
                   resize(buffer(2), 18) + resize(buffer(3), 18);
        end if;
    end process;

    -- 平均を計算 (4で割る = 2ビット右シフト)
    output <= sum(17 downto 2);
end Behavioral;

この移動平均フィルタは、直近4サンプルの平均を計算します。

2の補数表現を使用することで、正負の値を含む信号に対しても正確に動作します。

例えば、入力シーケンス「100, -50, 200, -100」に対して、出力は「37」となります。

○サンプルコード13:暗号化アルゴリズムでの2の補数演算

暗号化アルゴリズムにおいても、2の補数表現は重要な役割を果たします。

ここでは、簡単な置換暗号の例を通じて、2の補数演算の使用方法を見てみましょう。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity simple_cipher is
    Port ( plaintext : in UNSIGNED(7 downto 0);
           key : in UNSIGNED(7 downto 0);
           ciphertext : out UNSIGNED(7 downto 0));
end simple_cipher;

architecture Behavioral of simple_cipher is
    signal temp : SIGNED(8 downto 0);
begin
    process(plaintext, key)
    begin
        -- 平文とキーの加算 (オーバーフロー考慮)
        temp <= resize(SIGNED('0' & plaintext), 9) + resize(SIGNED('0' & key), 9);

        -- 結果が255を超える場合、256を引く
        if temp > 255 then
            ciphertext <= UNSIGNED(temp - 256);
        else
            ciphertext <= UNSIGNED(temp(7 downto 0));
        end if;
    end process;
end Behavioral;

この簡単な暗号化アルゴリズムでは、平文とキーを加算し、結果が255を超える場合は256を引きます。

2の補数表現を用いることで、オーバーフローを適切に処理し、0から255の範囲内に結果を収めています。

○サンプルコード14:画像処理における2の補数を用いた効率的な計算

画像処理においても、2の補数表現は効率的な計算を可能にします。

ここでは、画像のコントラスト調整を行う回路を例に取り上げます。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity contrast_adjuster is
    Port ( pixel_in : in UNSIGNED(7 downto 0);
           contrast_factor : in SIGNED(7 downto 0);  -- -128 to 127
           pixel_out : out UNSIGNED(7 downto 0));
end contrast_adjuster;

architecture Behavioral of contrast_adjuster is
    signal adjusted_pixel : SIGNED(15 downto 0);
begin
    process(pixel_in, contrast_factor)
        variable temp : SIGNED(15 downto 0);
    begin
        -- ピクセル値を符号付きに変換し、128を引く
        temp := resize(SIGNED('0' & pixel_in) - 128, 16);

        -- コントラスト調整
        adjusted_pixel <= resize((temp * contrast_factor) / 128, 16);

        -- 結果を0-255の範囲に収める
        if adjusted_pixel < -128 then
            pixel_out <= x"00";
        elsif adjusted_pixel > 127 then
            pixel_out <= x"FF";
        else
            pixel_out <= UNSIGNED(adjusted_pixel + 128);
        end if;
    end process;
end Behavioral;

この回路では、入力ピクセル値を-128から127の範囲にマッピングし、コントラスト調整を行った後、再び0から255の範囲に戻しています。

2の補数表現を用いることで、負の値を含む中間計算を効率的に行うことができます。

まとめ

VHDLにおける2の補数表現は、デジタル回路設計の基礎となる重要な概念です。

本記事では、2の補数の基本から応用まで、幅広いトピックをカバーしました。

4ビットから始まり、6ビットへの拡張、そして高度な算術演算の実装まで、段階的に理解を深めていただけたかと思います。

本記事で紹介した技術や考え方を基に、さらに深い理解と実践を重ねていくことで、より高度なデジタル回路設計のスキルを身につけることができるでしょう。

VHDLと2の補数表現の世界は、探求すればするほど奥深く、興味深いものです。

この記事が、皆さんのデジタル回路設計の旅路における参考となれば幸いです。