読み込み中...

基礎から学ぶシリアルパラレル変換回路の設計と活用14選

シリアルパラレル変換 徹底解説 VHDL
この記事は約50分で読めます。

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

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

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

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

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

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

●VHDLでシリアルパラレル変換回路を作る!

今回は、VHDLを使ったシリアルパラレル変換回路の作り方について、一緒に学んでいきましょう。

皆さんは、データ通信の基本をご存知ですか?

実は、私たちが日常的に使用している多くの電子機器の中で、シリアルパラレル変換という重要な処理が行われているのです。

○シリアルパラレル変換とは?

まずは、シリアルパラレル変換の概念から説明しましょう。

シリアル通信では、データが一本の線を通じて1ビットずつ順番に送られます。

テレビのリモコンや、古いタイプのキーボードなどがシリアル通信を利用しています。

一方、パラレル通信では、複数のビットが同時に並行して送られます。

コンピュータ内部のバスや、プリンターポートなどで使われています。

シリアルパラレル変換とは、この二つの通信方式の間でデータを変換する技術です。

例えば、外部から入ってきたシリアルデータを、内部の処理用にパラレルデータに変換したり、その逆を行ったりします。

携帯電話やタブレットなどのモバイルデバイスでは、外部とのデータのやり取りにシリアル通信を使い、内部処理にはパラレル方式を採用していることが多いです。

○なぜシリアルパラレル変換に最適なのか

VHDLは、シリアルパラレル変換回路の設計に非常に適した言語です。

その理由をいくつかみてみましょう。

第一に、VHDLは並列処理を自然に表現できます。

シリアルパラレル変換では、複数のビットを同時に扱う必要がありますが、VHDLはそのような並列動作を直感的に記述できます。

次に、VHDLは高い抽象度を持っています。

つまり、複雑な回路動作を比較的シンプルなコードで表現できるのです。

実際の回路設計者は、細かい実装の詳細よりも、全体的な動作に集中できます。

さらに、VHDLはハードウェア記述言語として標準化されています。

多くのFPGA(Field-Programmable Gate Array)や ASIC(Application-Specific Integrated Circuit)の開発ツールがVHDLをサポートしているため、設計した回路を実際のハードウェアに落とし込みやすいのです。

●VHDLによるシリアルパラレル変換の基本

では、実際にVHDLを使ってシリアルパラレル変換回路を設計していきましょう。

基本的な構成要素から順に見ていきます。

○サンプルコード1:基本的なシフトレジスタの実装

シリアルパラレル変換の核となるのが、シフトレジスタです。

シフトレジスタは、入力されたビットを順次シフトしていき、最終的に全ビットを同時に出力する仕組みです。

ここでは、8ビットのシフトレジスタの基本的な実装例を紹介します。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity ShiftRegister is
    Port ( 
        clk : in STD_LOGIC;
        reset : in STD_LOGIC;
        serial_in : in STD_LOGIC;
        parallel_out : out STD_LOGIC_VECTOR(7 downto 0)
    );
end ShiftRegister;

architecture Behavioral of ShiftRegister is
    signal shift_reg : STD_LOGIC_VECTOR(7 downto 0);
begin
    process(clk, reset)
    begin
        if reset = '1' then
            shift_reg <= (others => '0');
        elsif rising_edge(clk) then
            shift_reg <= shift_reg(6 downto 0) & serial_in;
        end if;
    end process;

    parallel_out <= shift_reg;
end Behavioral;

このコードでは、クロック信号の立ち上がりごとに、シフトレジスタの内容を1ビット左にシフトし、新しいビットを右端に挿入しています。

8クロックサイクル後には、8ビット分のデータが揃います。

○サンプルコード2:クロック同期回路の設計

次に、クロック同期の重要性について考えてみましょう。

デジタル回路では、タイミングが非常に重要です。

適切なタイミングでデータを取り込まないと、誤動作の原因となります。

ここでは、クロック同期を考慮したシリアルパラレル変換回路の例を紹介します。

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

entity SerialToParallel is
    Port ( 
        clk : in STD_LOGIC;
        reset : in STD_LOGIC;
        serial_in : in STD_LOGIC;
        data_valid : out STD_LOGIC;
        parallel_out : out STD_LOGIC_VECTOR(7 downto 0)
    );
end SerialToParallel;

architecture Behavioral of SerialToParallel is
    signal shift_reg : STD_LOGIC_VECTOR(7 downto 0);
    signal bit_counter : unsigned(2 downto 0);
begin
    process(clk, reset)
    begin
        if reset = '1' then
            shift_reg <= (others => '0');
            bit_counter <= (others => '0');
            data_valid <= '0';
        elsif rising_edge(clk) then
            shift_reg <= shift_reg(6 downto 0) & serial_in;
            if bit_counter = 7 then
                data_valid <= '1';
                bit_counter <= (others => '0');
            else
                data_valid <= '0';
                bit_counter <= bit_counter + 1;
            end if;
        end if;
    end process;

    parallel_out <= shift_reg when data_valid = '1' else (others => '0');
end Behavioral;

このコードでは、ビットカウンターを使用して8ビット分のデータが揃ったタイミングを検出し、data_valid信号を発生させています。

○サンプルコード3:パラレルアウトの制御方法

最後に、パラレルデータの出力制御について考えてみましょう。

実際の応用では、パラレルデータを常に出力し続けるのではなく、必要なタイミングで出力する必要があります。

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

entity ControlledSerialToParallel is
    Port ( 
        clk : in STD_LOGIC;
        reset : in STD_LOGIC;
        serial_in : in STD_LOGIC;
        read_enable : in STD_LOGIC;
        data_valid : out STD_LOGIC;
        parallel_out : out STD_LOGIC_VECTOR(7 downto 0)
    );
end ControlledSerialToParallel;

architecture Behavioral of ControlledSerialToParallel is
    signal shift_reg : STD_LOGIC_VECTOR(7 downto 0);
    signal bit_counter : unsigned(2 downto 0);
    signal internal_data_valid : STD_LOGIC;
begin
    process(clk, reset)
    begin
        if reset = '1' then
            shift_reg <= (others => '0');
            bit_counter <= (others => '0');
            internal_data_valid <= '0';
        elsif rising_edge(clk) then
            shift_reg <= shift_reg(6 downto 0) & serial_in;
            if bit_counter = 7 then
                internal_data_valid <= '1';
                bit_counter <= (others => '0');
            else
                internal_data_valid <= '0';
                bit_counter <= bit_counter + 1;
            end if;
        end if;
    end process;

    data_valid <= internal_data_valid and read_enable;
    parallel_out <= shift_reg when (internal_data_valid = '1' and read_enable = '1') else (others => '0');
end Behavioral;

このコードでは、read_enable信号を導入し、外部からのリクエストがあった場合のみパラレルデータを出力するようにしています。

●高度なVHDL実装テクニック

VHDLを使ったシリアルパラレル変換回路の基本を押さえたところで、より高度な実装テクニックに挑戦してみましょう。

ここからは、実際の業務で役立つ応用的な技術を解説していきます。

○サンプルコード4:並列処理による高速化

高速なデータ処理が求められる現代のデジタル機器。

そんな中で、並列処理は欠かせない技術となっています。

VHDLでは、並列処理を簡単に実装できるのが大きな特徴です。

次コードは、4ビットずつ並列に処理を行うシリアルパラレル変換回路の例です。

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

entity ParallelSerialToParallel is
    Port ( 
        clk : in STD_LOGIC;
        reset : in STD_LOGIC;
        serial_in : in STD_LOGIC_VECTOR(3 downto 0);
        data_valid : out STD_LOGIC;
        parallel_out : out STD_LOGIC_VECTOR(15 downto 0)
    );
end ParallelSerialToParallel;

architecture Behavioral of ParallelSerialToParallel is
    signal shift_reg : STD_LOGIC_VECTOR(15 downto 0);
    signal bit_counter : unsigned(1 downto 0);
begin
    process(clk, reset)
    begin
        if reset = '1' then
            shift_reg <= (others => '0');
            bit_counter <= (others => '0');
            data_valid <= '0';
        elsif rising_edge(clk) then
            shift_reg <= shift_reg(11 downto 0) & serial_in;
            if bit_counter = 3 then
                data_valid <= '1';
                bit_counter <= (others => '0');
            else
                data_valid <= '0';
                bit_counter <= bit_counter + 1;
            end if;
        end if;
    end process;

    parallel_out <= shift_reg when data_valid = '1' else (others => '0');
end Behavioral;

この回路では、4ビットずつデータを取り込むことで、従来の1ビットずつの処理に比べて4倍の速度でデータを変換できます。

高速なデータ通信が必要な場面で威力を発揮するでしょう。

○サンプルコード5:エラー検出機能の組み込み

データ通信では、ノイズによるビット化けなどのエラーが避けられません。

そこで、エラー検出機能を組み込んだシリアルパラレル変換回路を設計してみましょう。

ここでは、パリティビットを使用したエラー検出機能付きの回路例を紹介します。

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

entity ErrorDetectionSerialToParallel is
    Port ( 
        clk : in STD_LOGIC;
        reset : in STD_LOGIC;
        serial_in : in STD_LOGIC;
        data_valid : out STD_LOGIC;
        error_detected : out STD_LOGIC;
        parallel_out : out STD_LOGIC_VECTOR(7 downto 0)
    );
end ErrorDetectionSerialToParallel;

architecture Behavioral of ErrorDetectionSerialToParallel is
    signal shift_reg : STD_LOGIC_VECTOR(8 downto 0);
    signal bit_counter : unsigned(3 downto 0);
    signal parity : STD_LOGIC;
begin
    process(clk, reset)
    begin
        if reset = '1' then
            shift_reg <= (others => '0');
            bit_counter <= (others => '0');
            data_valid <= '0';
            error_detected <= '0';
            parity <= '0';
        elsif rising_edge(clk) then
            shift_reg <= shift_reg(7 downto 0) & serial_in;
            parity <= parity xor serial_in;
            if bit_counter = 8 then
                data_valid <= '1';
                error_detected <= parity;
                bit_counter <= (others => '0');
                parity <= '0';
            else
                data_valid <= '0';
                error_detected <= '0';
                bit_counter <= bit_counter + 1;
            end if;
        end if;
    end process;

    parallel_out <= shift_reg(7 downto 0) when data_valid = '1' else (others => '0');
end Behavioral;

このコードでは、9ビット目をパリティビットとして使用しています。

データ受信時にパリティを計算し、送信側のパリティビットと比較することでエラーを検出します。

○サンプルコード6:可変長データの処理方法

実際の通信では、データの長さが固定されていない場合もあります。

そんな時に役立つのが、可変長データを処理できる回路です。

ここでは、スタートビットとストップビットを使用した可変長データ処理の例をみてみましょう。

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

entity VariableLengthSerialToParallel is
    Port ( 
        clk : in STD_LOGIC;
        reset : in STD_LOGIC;
        serial_in : in STD_LOGIC;
        data_valid : out STD_LOGIC;
        parallel_out : out STD_LOGIC_VECTOR(31 downto 0);
        data_length : out unsigned(5 downto 0)
    );
end VariableLengthSerialToParallel;

architecture Behavioral of VariableLengthSerialToParallel is
    type state_type is (IDLE, RECEIVING, STOP);
    signal state : state_type;
    signal shift_reg : STD_LOGIC_VECTOR(31 downto 0);
    signal bit_counter : unsigned(5 downto 0);
begin
    process(clk, reset)
    begin
        if reset = '1' then
            state <= IDLE;
            shift_reg <= (others => '0');
            bit_counter <= (others => '0');
            data_valid <= '0';
            data_length <= (others => '0');
        elsif rising_edge(clk) then
            case state is
                when IDLE =>
                    if serial_in = '0' then  -- Start bit detected
                        state <= RECEIVING;
                        bit_counter <= (others => '0');
                    end if;
                when RECEIVING =>
                    shift_reg <= shift_reg(30 downto 0) & serial_in;
                    bit_counter <= bit_counter + 1;
                    if serial_in = '1' then  -- Stop bit detected
                        state <= STOP;
                    end if;
                when STOP =>
                    data_valid <= '1';
                    data_length <= bit_counter - 1;  -- Exclude stop bit
                    state <= IDLE;
            end case;
        end if;
    end process;

    parallel_out <= shift_reg when data_valid = '1' else (others => '0');
end Behavioral;

この回路は、スタートビット(0)を検出したらデータの受信を開始し、ストップビット(1)を検出したら受信を終了します。

受信したデータの長さも出力するため、柔軟なデータ処理が可能です。

●Verilogとの比較で見るVHDLの強み

VHDLとVerilogは、どちらもハードウェア記述言語として広く使われています。

それぞれに特徴がありますが、ここではVHDLの強みに焦点を当てて比較してみましょう。

○サンプルコード7:VHDL vs Verilog シリアルパラレル変換

まずは、同じ機能を持つシリアルパラレル変換回路をVHDLとVerilogで実装し、比較してみます。

VHDLの実装

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

entity VHDLSerialToParallel is
    Port ( 
        clk : in STD_LOGIC;
        reset : in STD_LOGIC;
        serial_in : in STD_LOGIC;
        data_valid : out STD_LOGIC;
        parallel_out : out STD_LOGIC_VECTOR(7 downto 0)
    );
end VHDLSerialToParallel;

architecture Behavioral of VHDLSerialToParallel is
    signal shift_reg : STD_LOGIC_VECTOR(7 downto 0);
    signal bit_counter : unsigned(2 downto 0);
begin
    process(clk, reset)
    begin
        if reset = '1' then
            shift_reg <= (others => '0');
            bit_counter <= (others => '0');
            data_valid <= '0';
        elsif rising_edge(clk) then
            shift_reg <= shift_reg(6 downto 0) & serial_in;
            if bit_counter = 7 then
                data_valid <= '1';
                bit_counter <= (others => '0');
            else
                data_valid <= '0';
                bit_counter <= bit_counter + 1;
            end if;
        end if;
    end process;

    parallel_out <= shift_reg when data_valid = '1' else (others => '0');
end Behavioral;

Verilogの実装

module VerilogSerialToParallel(
    input wire clk,
    input wire reset,
    input wire serial_in,
    output reg data_valid,
    output reg [7:0] parallel_out
);

reg [7:0] shift_reg;
reg [2:0] bit_counter;

always @(posedge clk or posedge reset) begin
    if (reset) begin
        shift_reg <= 8'b0;
        bit_counter <= 3'b0;
        data_valid <= 1'b0;
        parallel_out <= 8'b0;
    end else begin
        shift_reg <= {shift_reg[6:0], serial_in};
        if (bit_counter == 3'b111) begin
            data_valid <= 1'b1;
            bit_counter <= 3'b0;
            parallel_out <= shift_reg;
        end else begin
            data_valid <= 1'b0;
            bit_counter <= bit_counter + 1;
        end
    end
end

endmodule

VHDLの強みは、コードの可読性と型安全性にあります。

VHDLでは、信号の型や範囲が明確に定義されるため、設計ミスを防ぎやすくなっています。

また、VHDLのプロセス文は、Verilogの always ブロックよりも直感的に並列処理を表現できます。

○サンプルコード8:VHDLによる効率的なテストベンチ作成

VHDLのもう一つの強みは、テストベンチの作成が容易なことです。

ここでは、先ほどのVHDLで実装したシリアルパラレル変換回路のテストベンチ例をみてみましょう。

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

entity VHDLSerialToParallel_TB is
end VHDLSerialToParallel_TB;

architecture Behavioral of VHDLSerialToParallel_TB is
    signal clk : STD_LOGIC := '0';
    signal reset : STD_LOGIC := '0';
    signal serial_in : STD_LOGIC := '0';
    signal data_valid : STD_LOGIC;
    signal parallel_out : STD_LOGIC_VECTOR(7 downto 0);

    constant CLK_PERIOD : time := 10 ns;

    component VHDLSerialToParallel is
        Port ( 
            clk : in STD_LOGIC;
            reset : in STD_LOGIC;
            serial_in : in STD_LOGIC;
            data_valid : out STD_LOGIC;
            parallel_out : out STD_LOGIC_VECTOR(7 downto 0)
        );
    end component;

begin
    UUT: VHDLSerialToParallel port map (
        clk => clk,
        reset => reset,
        serial_in => serial_in,
        data_valid => data_valid,
        parallel_out => parallel_out
    );

    -- Clock process
    clk_process: process
    begin
        clk <= '0';
        wait for CLK_PERIOD/2;
        clk <= '1';
        wait for CLK_PERIOD/2;
    end process;

    -- Stimulus process
    stim_process: process
    begin
        reset <= '1';
        wait for CLK_PERIOD*2;
        reset <= '0';

        -- Send test data: 10101010
        for i in 0 to 7 loop
            serial_in <= '1' when i mod 2 = 0 else '0';
            wait for CLK_PERIOD;
        end for;

        wait for CLK_PERIOD*2;

        -- Send test data: 11001100
        for i in 0 to 7 loop
            serial_in <= '1' when i < 2 or (i >= 4 and i < 6) else '0';
            wait for CLK_PERIOD;
        end for;

        wait;
    end process;

end Behavioral;

VHDLのテストベンチでは、シミュレーション時間の制御や信号の生成が簡単に行えます。

また、アサーション機能を使用することで、期待される動作を明確に記述し、自動化されたテストを実施することができます。

●実践的なシリアルパラレル変換回路設計

さて、ここまでVHDLを使ったシリアルパラレル変換の基礎から応用まで解説してきました。

理論は理解できたものの、「実際の業務ではどのように使うの?」と疑問に思う方もいるでしょう。

そこで、実践的な回路設計の例を見ていきましょう。

○サンプルコード9:UART受信機の完全実装

UART(Universal Asynchronous Receiver/Transmitter)は、シリアル通信の代表的なプロトコルです。

パソコンとマイコンボードの通信によく使われますね。UARTの受信機は、まさにシリアルパラレル変換の実例と言えます。

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

entity UART_Receiver is
    Generic (
        CLKS_PER_BIT : integer := 434  -- 例:115200bps@50MHz
    );
    Port ( 
        clk : in STD_LOGIC;
        rx_serial : in STD_LOGIC;
        rx_data_valid : out STD_LOGIC;
        rx_byte : out STD_LOGIC_VECTOR(7 downto 0)
    );
end UART_Receiver;

architecture Behavioral of UART_Receiver is
    type rx_states_t is (IDLE, START_BIT, DATA_BITS, STOP_BIT);
    signal rx_state : rx_states_t := IDLE;
    signal rx_clk_count : integer range 0 to CLKS_PER_BIT-1 := 0;
    signal rx_bit_index : integer range 0 to 7 := 0;
    signal rx_data : STD_LOGIC_VECTOR(7 downto 0) := (others => '0');
begin
    UART_RX_PROC : process(clk)
    begin
        if rising_edge(clk) then
            case rx_state is
                when IDLE =>
                    rx_data_valid <= '0';
                    rx_clk_count <= 0;
                    rx_bit_index <= 0;

                    if rx_serial = '0' then  -- Start bit detected
                        rx_state <= START_BIT;
                    end if;

                when START_BIT =>
                    if rx_clk_count = CLKS_PER_BIT/2 then
                        if rx_serial = '0' then
                            rx_clk_count <= 0;
                            rx_state <= DATA_BITS;
                        else
                            rx_state <= IDLE;
                        end if;
                    else
                        rx_clk_count <= rx_clk_count + 1;
                    end if;

                when DATA_BITS =>
                    if rx_clk_count < CLKS_PER_BIT-1 then
                        rx_clk_count <= rx_clk_count + 1;
                    else
                        rx_clk_count <= 0;
                        rx_data(rx_bit_index) <= rx_serial;

                        if rx_bit_index < 7 then
                            rx_bit_index <= rx_bit_index + 1;
                        else
                            rx_state <= STOP_BIT;
                        end if;
                    end if;

                when STOP_BIT =>
                    if rx_clk_count < CLKS_PER_BIT-1 then
                        rx_clk_count <= rx_clk_count + 1;
                    else
                        rx_data_valid <= '1';
                        rx_state <= IDLE;
                    end if;
            end case;
        end if;
    end process UART_RX_PROC;

    rx_byte <= rx_data;
end Behavioral;

UARTの受信処理は、アイドル状態からスタートビットを検出し、データビットを順次受信し、ストップビットで終了するという流れになっています。

VHDLのステートマシンを使用することで、この一連の流れを直感的に表現できています。

実行結果は、rx_data_validが’1’になったタイミングでrx_byteに8ビットのデータが格納されます。

例えば、ASCII文字’A’(01000001b)を受信した場合、rx_byteには”01000001″が格納されます。

○サンプルコード10:高速ADCインターフェースの設計

次に、高速ADC(Analog-to-Digital Converter)のインターフェース設計を見てみましょう。

ADCは、アナログ信号をデジタル信号に変換する装置です。

高速ADCでは、データが高速シリアルで出力されることが多く、シリアルパラレル変換が必要になります。

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

entity ADC_Interface is
    Port ( 
        clk : in STD_LOGIC;
        adc_dout : in STD_LOGIC;  -- ADCからのシリアルデータ入力
        adc_dclk : in STD_LOGIC;  -- ADCのデータクロック
        adc_data_valid : out STD_LOGIC;
        adc_data : out STD_LOGIC_VECTOR(13 downto 0)
    );
end ADC_Interface;

architecture Behavioral of ADC_Interface is
    signal shift_reg : STD_LOGIC_VECTOR(13 downto 0) := (others => '0');
    signal bit_counter : unsigned(3 downto 0) := (others => '0');
    signal data_valid_reg : STD_LOGIC := '0';
begin
    process(adc_dclk)
    begin
        if rising_edge(adc_dclk) then
            shift_reg <= shift_reg(12 downto 0) & adc_dout;
            if bit_counter = 13 then
                bit_counter <= (others => '0');
                data_valid_reg <= '1';
            else
                bit_counter <= bit_counter + 1;
                data_valid_reg <= '0';
            end if;
        end if;
    end process;

    process(clk)
    begin
        if rising_edge(clk) then
            adc_data_valid <= data_valid_reg;
            if data_valid_reg = '1' then
                adc_data <= shift_reg;
            end if;
        end if;
    end process;
end Behavioral;

この回路では、ADCのデータクロック(adc_dclk)に同期してシリアルデータ(adc_dout)を受信し、14ビット分のデータが揃ったらadc_data_validを’1’にしてadc_dataに格納します。

実行結果として、例えば入力が”10110001100110″(14ビットのデータ)だった場合、adc_dataに”10110001100110″が格納され、adc_data_validが’1’になります。

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

VHDLでシリアルパラレル変換回路を設計する際、いくつかの一般的なエラーに遭遇することがあります。

ここでは、よくあるエラーとその対処法を紹介します。

○タイミング違反の解決策

タイミング違反は、信号が期待された時間内に目的地に到達しない場合に発生します。

高速なシリアルパラレル変換では特に注意が必要です。

解決策としては、パイプライン化が効果的です。

architecture Improved of SerialToParallel is
    signal shift_reg1, shift_reg2 : STD_LOGIC_VECTOR(7 downto 0);
    signal valid1, valid2 : STD_LOGIC;
begin
    process(clk)
    begin
        if rising_edge(clk) then
            -- First stage
            shift_reg1 <= shift_reg1(6 downto 0) & serial_in;
            valid1 <= '1' when bit_counter = 7 else '0';

            -- Second stage (pipelined)
            shift_reg2 <= shift_reg1;
            valid2 <= valid1;
        end if;
    end process;

    parallel_out <= shift_reg2;
    data_valid <= valid2;
end architecture Improved;

この例では、シフトレジスタの動作とデータの出力を2段階に分けることで、1クロックサイクルあたりの処理を軽減しています。

○シンセシスエラーの対処方法

シンセシスエラーは、HDLコードがハードウェアに変換できない場合に発生します。

よくある原因の1つは、不適切な変数の使用です。

例えば、次のコードはシンセシスエラーを引き起こす可能性があります。

variable counter : integer;  -- 範囲が未指定

解決策として、変数の範囲を明示的に指定します。

variable counter : integer range 0 to 255;  -- 8ビットカウンタ

また、プロセス内での信号の複数回代入も避けるべきです。

代わりに、条件付き代入を使用しましょう。

signal_out <= value1 when condition1 else
              value2 when condition2 else
              value3;

○シミュレーションと実機の動作差異の解消

シミュレーションでは問題なく動作するのに、実機では動作しないという事態はよくあります。

主な原因の1つは、未初期化信号の使用です。

VHDLシミュレーションでは、未初期化信号は’U’(未定義)として扱われますが、実際のハードウェアでは不定な値になります。

解決策として、全ての信号を適切に初期化することが重要です。

signal my_signal : STD_LOGIC := '0';  -- '0'で初期化

また、非同期リセットの使用も、シミュレーションと実機の動作差を生む原因になることがあります。

可能な限り、同期リセットを使用することをお勧めします。

process(clk)
begin
    if rising_edge(clk) then
        if reset = '1' then
            -- リセット処理
        else
            -- 通常の処理
        end if;
    end if;
end process;

●シリアルパラレル変換の応用例

VHDLを使ったシリアルパラレル変換の基礎から応用まで学んできました。

理論や基本的な実装方法を理解したところで、実際の現場ではどのように活用されているのか気になりませんか?

ここからは、シリアルパラレル変換の具体的な応用例を見ていきましょう。

現代のデジタル機器や通信システムで欠かせない技術の実践的な使い方がわかるはずです。

○サンプルコード11:PCIeレーンの実装

PCIe(PCI Express)は、コンピュータの拡張カードインターフェースとして広く使われています。

高速なシリアル通信を行うPCIeでは、複数のレーンを使用してデータを送受信します。

各レーンでシリアルパラレル変換が行われるのです。

ここでは、PCIeレーンの基本的な実装例を紹介します。

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

entity PCIe_Lane is
    Port ( 
        clk : in STD_LOGIC;
        reset : in STD_LOGIC;
        serial_in : in STD_LOGIC;
        parallel_out : out STD_LOGIC_VECTOR(7 downto 0);
        data_valid : out STD_LOGIC
    );
end PCIe_Lane;

architecture Behavioral of PCIe_Lane is
    signal shift_reg : STD_LOGIC_VECTOR(7 downto 0);
    signal bit_counter : unsigned(2 downto 0);
    signal data_valid_reg : STD_LOGIC := '0';
begin
    process(clk)
    begin
        if rising_edge(clk) then
            if reset = '1' then
                shift_reg <= (others => '0');
                bit_counter <= (others => '0');
                data_valid_reg <= '0';
            else
                shift_reg <= shift_reg(6 downto 0) & serial_in;
                if bit_counter = 7 then
                    bit_counter <= (others => '0');
                    data_valid_reg <= '1';
                else
                    bit_counter <= bit_counter + 1;
                    data_valid_reg <= '0';
                end if;
            end if;
        end if;
    end process;

    parallel_out <= shift_reg when data_valid_reg = '1' else (others => '0');
    data_valid <= data_valid_reg;
end Behavioral;

PCIeレーンの実装では、8ビットごとにデータを区切って処理します。

シリアルデータが入力されると、シフトレジスタに順次格納されていきます。

8ビット分のデータが揃うと、data_validを立てて並列データを出力します。

実行結果として、例えば”10101010″というシリアルデータが入力された場合、8クロック後にparallel_outに”10101010″が出力され、data_validが’1’になります。

○サンプルコード12:イーサネットMAC層の設計

イーサネットは、ローカルエリアネットワーク(LAN)で最も一般的に使用されている規格です。

MAC(Media Access Control)層は、イーサネットのデータリンク層の一部で、フレームの送受信を制御します。

イーサネットMAC層の受信部分の簡略化した実装例をみてみましょう。

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

entity Ethernet_MAC_RX is
    Port ( 
        clk : in STD_LOGIC;
        reset : in STD_LOGIC;
        rx_dv : in STD_LOGIC;  -- データ有効信号
        rx_er : in STD_LOGIC;  -- エラー信号
        rxd : in STD_LOGIC_VECTOR(3 downto 0);  -- 受信データ
        frame_out : out STD_LOGIC_VECTOR(7 downto 0);
        frame_valid : out STD_LOGIC
    );
end Ethernet_MAC_RX;

architecture Behavioral of Ethernet_MAC_RX is
    type rx_state_type is (IDLE, RECEIVE);
    signal rx_state : rx_state_type := IDLE;
    signal shift_reg : STD_LOGIC_VECTOR(7 downto 0);
    signal nibble_count : unsigned(0 downto 0) := "0";
begin
    process(clk)
    begin
        if rising_edge(clk) then
            if reset = '1' then
                rx_state <= IDLE;
                shift_reg <= (others => '0');
                nibble_count <= "0";
                frame_valid <= '0';
            else
                case rx_state is
                    when IDLE =>
                        if rx_dv = '1' and rx_er = '0' then
                            rx_state <= RECEIVE;
                            shift_reg(3 downto 0) <= rxd;
                            nibble_count <= "1";
                        end if;
                    when RECEIVE =>
                        if rx_dv = '1' and rx_er = '0' then
                            if nibble_count = "0" then
                                shift_reg(3 downto 0) <= rxd;
                                nibble_count <= "1";
                            else
                                shift_reg(7 downto 4) <= rxd;
                                nibble_count <= "0";
                                frame_valid <= '1';
                            end if;
                        else
                            rx_state <= IDLE;
                            frame_valid <= '0';
                        end if;
                end case;
            end if;
        end if;
    end process;

    frame_out <= shift_reg;
end Behavioral;

イーサネットMAC層の受信部では、4ビットずつ入力されるデータを8ビットの1バイトにまとめる処理を行います。

rx_dv(データ有効)信号が立っている間、データを受信し続けます。

実行結果として、例えば rxd に “1010” → “1100” と順に入力された場合、frame_out に “11001010” が出力され、frame_valid が ‘1’ になります。

○サンプルコード13:カメラインターフェースの構築

デジタルカメラやスマートフォンのカメラモジュールでは、高速なシリアルインターフェースを使用してイメージセンサーからデータを取得します。

代表的なものにMIPI CSI-2があります。

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

entity Camera_Interface is
    Port ( 
        clk : in STD_LOGIC;
        reset : in STD_LOGIC;
        data_n : in STD_LOGIC;  -- 差動信号の負側
        data_p : in STD_LOGIC;  -- 差動信号の正側
        pixel_data : out STD_LOGIC_VECTOR(7 downto 0);
        pixel_valid : out STD_LOGIC
    );
end Camera_Interface;

architecture Behavioral of Camera_Interface is
    signal data_in : STD_LOGIC;
    signal shift_reg : STD_LOGIC_VECTOR(7 downto 0);
    signal bit_counter : unsigned(2 downto 0);
begin
    -- 差動信号の処理
    data_in <= data_p and not data_n;

    process(clk)
    begin
        if rising_edge(clk) then
            if reset = '1' then
                shift_reg <= (others => '0');
                bit_counter <= (others => '0');
                pixel_valid <= '0';
            else
                shift_reg <= shift_reg(6 downto 0) & data_in;
                if bit_counter = 7 then
                    bit_counter <= (others => '0');
                    pixel_valid <= '1';
                else
                    bit_counter <= bit_counter + 1;
                    pixel_valid <= '0';
                end if;
            end if;
        end if;
    end process;

    pixel_data <= shift_reg when pixel_valid = '1' else (others => '0');
end Behavioral;

カメラインターフェースでは、差動信号を使用してノイズに強い通信を行います。

data_nとdata_pの差動ペアから1ビットのデータを抽出し、8ビット分のデータが揃ったら1ピクセル分のデータとして出力します。

実行結果として、8クロックサイクルかけて “10010110” というデータが入力された場合、8クロック後に pixel_data に “10010110” が出力され、pixel_valid が ‘1’ になります。

○サンプルコード14:高速シリアライザ/デシリアライザの実現

高速なデータ通信では、シリアライザ/デシリアライザ(SerDes)が重要な役割を果たします。

並列データを高速シリアルデータに変換し、受信側で再び並列データに戻す処理を行います。

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

entity SerDes is
    Port ( 
        tx_clk : in STD_LOGIC;
        rx_clk : in STD_LOGIC;
        reset : in STD_LOGIC;
        tx_data : in STD_LOGIC_VECTOR(7 downto 0);
        rx_data : out STD_LOGIC_VECTOR(7 downto 0);
        serial_out : out STD_LOGIC;
        serial_in : in STD_LOGIC
    );
end SerDes;

architecture Behavioral of SerDes is
    signal tx_shift_reg : STD_LOGIC_VECTOR(7 downto 0);
    signal rx_shift_reg : STD_LOGIC_VECTOR(7 downto 0);
    signal tx_bit_counter : unsigned(2 downto 0);
    signal rx_bit_counter : unsigned(2 downto 0);
begin
    -- 送信部(シリアライザ)
    process(tx_clk)
    begin
        if rising_edge(tx_clk) then
            if reset = '1' then
                tx_shift_reg <= (others => '0');
                tx_bit_counter <= (others => '0');
            else
                if tx_bit_counter = 0 then
                    tx_shift_reg <= tx_data;
                else
                    tx_shift_reg <= tx_shift_reg(6 downto 0) & '0';
                end if;
                tx_bit_counter <= tx_bit_counter + 1;
            end if;
        end if;
    end process;

    serial_out <= tx_shift_reg(7);

    -- 受信部(デシリアライザ)
    process(rx_clk)
    begin
        if rising_edge(rx_clk) then
            if reset = '1' then
                rx_shift_reg <= (others => '0');
                rx_bit_counter <= (others => '0');
            else
                rx_shift_reg <= rx_shift_reg(6 downto 0) & serial_in;
                if rx_bit_counter = 7 then
                    rx_data <= rx_shift_reg;
                end if
                rx_bit_counter <= rx_bit_counter + 1;
            end if;
        end if;
    end process;
end Behavioral;

SerDesでは、送信側で8ビットの並列データを1ビットずつシリアル化し、受信側で8ビット分のデータを受信したら並列データとして出力します。

高速クロックを使用することで、データレートを向上させることができます。

実行結果として、tx_data に “10101010” が入力された場合、8クロックサイクルかけて serial_out から “10101010” が順に出力されます。

同様に、serial_in に “11001100” が8クロックサイクルかけて入力された場合、rx_data に “11001100” が出力されます。

まとめ

VHDLを用いたシリアルパラレル変換回路の設計について、基礎から応用まで幅広く解説してきました。

本記事で学んだ内容を実践に移す際は、まず小規模な回路から始め、徐々に複雑な設計に挑戦していくことをお勧めします。

シミュレーションツールを活用して動作を確認し、実機での検証も忘れずに行ってくださいね。