KeitetsuWorks
KeitetsuWorks

VHDL記述例 - UARTによるシリアル通信

はじめに

本ページでは,調歩同期方式シリアル通信を行うためのUART (Universal Asynchronous Receiver Transmitter)のVHDL記述例を紹介します. UARTをFPGAに実装することにより,D-sub9ピンのシリアルケーブルやUSB-シリアル変換モジュールを経由して,PCとFPGAなどの間でRS-232C準拠のシリアル通信が実現できます.

下記の環境で動作を確認しておりますが,動作を保証するものではありません. ソースコードは自己責任の上でご利用ください.

OS Microsoft Windows 7 Professional x64 Service Pack 1
Quartus II Altera Quartus II 12.0sp2 Web Edition
ModelSim Mentor Graphics ModelSim-Altera Starter Edition v10.0d Service Pack
FPGA Board Terasic DE0 (Altera Cyclone III EP3C16F484C6N)

VHDL記述例

はじめに

今回紹介するUARTのVHDL記述例(以降では,UARTモジュールと呼びます)は,下図に示すような構成になっています. 最上位モジュール「UART」は,その下にクロック生成モジュール「clk_generator」, シリアル送信モジュール「tx」およびシリアル受信モジュール「rx」を内包しています. UARTモジュールは,これら下位モジュールの入出力信号を結線しています.

UARTモジュール

下表に各外部入出力信号の名称と機能をまとめました.入力クロックの周波数は50MHzです.

Node Name Direction Description
CLK Input クロック入力.周波数は50MHzを想定
RST Input リセット動作: 1,通常動作: 0
TXD Output シリアル通信(送信)
RXD Input シリアル通信(受信)
DIN Input 送信データ入力
DOUT Output 受信データ出力
EN_TX Input 送信有効: 1,送信無効: 0
EN_RX Input 受信有効: 1,受信無効: 0
STBY_TX Output 待機中(初期状態): 1,送信中: 0
STBY_RX Output 待機中(初期状態): 1,受信中または受信待機中: 0

下表に通信仕様をまとめました.

ビットレート 115200bps
データビット 8bit
パリティビット なし
ストップビット 1bit
フロー制御 なし
最上位モジュール「UART」(UART.vhd)

最上位モジュール「UART」は,その下にクロック生成モジュール「clk_generator」, シリアル送信モジュール「tx」およびシリアル受信モジュール「rx」を内包しています. UARTモジュールは,これら下位モジュールの入出力信号を結線しています.


--------------------------------------------------------------------------------
-- UART Interface Unit
--  Start-stop synchronous communication (RS-232C)
--  115200bps, 8bit, no-parity, 1stop-bit
--------------------------------------------------------------------------------

library IEEE;
use IEEE.std_logic_1164.all;


-- top module
entity UART is port(
  CLK: in std_logic;
  RST: in std_logic;
  TXD: out std_logic;
  RXD: in std_logic;
  DIN: in std_logic_vector(7 downto 0);
  DOUT: out std_logic_vector(7 downto 0);
  EN_TX: in std_logic;
  EN_RX: in std_logic;
  STBY_TX: out std_logic;
  STBY_RX: out std_logic);
end UART;

architecture rtl of UART is
  signal CLK_TX: std_logic;
  signal CLK_RX: std_logic;
  
  component clk_generator port(
    clk: in std_logic;
    rst: in std_logic;
    clk_tx: out std_logic;
    clk_rx: out std_logic);
  end component;
  
  component tx port(
    clk: in std_logic;
    rst: in std_logic;
    clk_tx: in std_logic;
    txd: out std_logic;
    din: in std_logic_vector(7 downto 0);
    en: in std_logic;
    stby: out std_logic);
  end component;
  
  component rx port(
    clk: in std_logic;
    rst: in std_logic;
    clk_rx: in std_logic;
    rxd: in std_logic;
    dout: out std_logic_vector(7 downto 0);
    en: in std_logic;
    stby: out std_logic);
  end component;
  
  begin
    uclk_generator: clk_generator port map(
      clk => CLK,
      rst => RST,
      clk_tx => clk_tx,
      clk_rx => clk_rx);
    
    utx: tx port map(
      clk => CLK,
      rst => RST,
      clk_tx => clk_tx,
      txd => TXD,
      din => DIN,
      en => EN_TX,
      stby => STBY_TX);
    
    urx: rx port map(
      clk => CLK,
      rst => RST,
      clk_rx => clk_rx,
      rxd => RXD,
      dout => DOUT,
      en => EN_RX,
      stby=> STBY_RX);

end rtl;
クロック生成モジュール「clk_generator」(clk_generator.vhd)

クロック生成モジュール「clk_generator」は,シリアル通信に必要なクロックを生成し,シリアル送信モジュールおよびシリアル受信モジュールに供給します.


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


-- clock generator module
entity clk_generator is port(
  clk: in std_logic;
  rst: in std_logic;
  clk_tx: out std_logic;
  clk_rx: out std_logic);
end clk_generator;

architecture rtl of clk_generator is
  -- (1 / 115200bps) / (1 / 50MHz) = 434
  signal cnt_tx: integer range 0 to 433;
  -- (1 / 115200bps) / (1 / 50MHz) / 16 = 27
  signal cnt_rx: integer range 0 to 26;
  
  begin
    clk_tx <= '1' when(cnt_tx = 433) else '0';
    process(clk, rst) begin
      if(rst = '1') then
        cnt_tx <= 0;
      elsif(clk'event and clk = '1') then
        if(cnt_tx = 433) then
          cnt_tx <= 0;
        else
          cnt_tx <= cnt_tx + 1;
        end if;
      end if;
    end process;
    
    clk_rx <= '1' when(cnt_rx = 26) else '0';
    process(clk, rst) begin
      if(rst = '1') then
        cnt_rx <= 0;
      elsif(clk'event and clk = '1') then
        if(cnt_rx = 26) then
          cnt_rx<= 0;
        else
          cnt_rx <= cnt_rx + 1;
        end if; 
      end if;
    end process;

end rtl;
シリアル送信モジュール「tx」(tx.vhd)

シリアル送信モジュール「tx」は,シリアル通信における送信機能を提供します.


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


-- transmitter module
entity tx is port(
  clk: in std_logic;
  rst: in std_logic;
  clk_tx: in std_logic;
  txd: out std_logic;
  din: in std_logic_vector(7 downto 0);
  en: in std_logic;
  stby: out std_logic);
end tx;

architecture rtl of tx is
  signal en_tmp: std_logic;
  signal cnt_bit: integer range 0 to 9;
  signal buf: std_logic_vector(7 downto 0); 
  
  begin
    process(clk, rst) begin
      if(rst = '1') then
        txd <= '1';
        en_tmp <= '0';
        stby <= '1';
        cnt_bit <=  0;
        buf <= (others => '0');
      elsif(clk'event and clk = '1') then
        if(en = '1') then
          en_tmp <= '1';
          stby <= '0';
          buf <= din;
        elsif(clk_tx = '1' and en_tmp = '1') then
          case cnt_bit is
            when 0 => 
              txd <= '0';
              cnt_bit <= cnt_bit + 1;
            when 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 => 
              txd <= buf(0);
              buf <= '1' & buf(7 downto 1);
              cnt_bit <= cnt_bit + 1;
            when others => 
              txd <= '1';
              en_tmp <= '0';
              stby <= '1';
              cnt_bit <= 0;
          end case;
        end if;
      end if;
    end process;

end rtl;
シリアル受信モジュール「rx」(rx.vhd)

シリアル受信モジュール「rx」は,シリアル通信における受信機能を提供します.


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


-- receiver module
entity rx is port(
  clk: in std_logic;
  rst: in std_logic;
  clk_rx: in std_logic;
  rxd: in std_logic;
  dout: out std_logic_vector(7 downto 0);
  en: in std_logic;
  stby: out std_logic);
end rx;

architecture rtl of rx is
  type state_type is (idle, detect, proc, stopbit);
  signal state: state_type;
  signal dout_reg: std_logic_vector(7 downto 0);
  signal en_tmp: std_logic;
  signal cnt_bitnum: integer range 0 to 7;
  signal cnt_bitwidth: integer range 0 to 15;
  signal buf: std_logic; 
  
  begin
    dout <= dout_reg;
    
    process(clk, rst) begin
      if(rst ='1') then
        buf <= '0';
      elsif(clk'event and clk = '1') then
        buf <= rxd;
      end if;
    end process;
    
    process(clk, rst) begin
      if(rst ='1') then
        en_tmp <= '0';
        stby <= '1';
        dout_reg <= (others => '0');
        cnt_bitnum <= 0;
        cnt_bitwidth <= 0;
        state <= idle;
      elsif(clk'event and clk = '1') then
        if(en = '1') then
          en_tmp <= '1';
          stby <= '0';
        end if;
        if(en_tmp = '1') then
          case state is
            when idle => 
              if(buf = '0') then
                cnt_bitnum <= 0;
                cnt_bitwidth <= 0;
                state <= detect;
              else
                state <= state;
              end if;
            when detect =>
              if(clk_rx = '1') then
                if(cnt_bitwidth = 7) then
                  cnt_bitwidth <= 0;
                  state <= proc;
                else
                  cnt_bitwidth <= cnt_bitwidth + 1;
                  state <= state;
                end if;
              else
                state <= state;
              end if;
            when proc =>
              if(clk_rx = '1') then
                if(cnt_bitwidth = 15) then
                  if(cnt_bitnum = 7) then
                    cnt_bitnum <= 0;
                    state <= stopbit;
                  else
                    cnt_bitnum <= cnt_bitnum + 1;
                    state <= state;
                  end if;
                  dout_reg <= rxd & dout_reg(7 downto 1);
                  cnt_bitwidth <= 0;
                else
                  cnt_bitwidth <= cnt_bitwidth + 1;
                  state <= state;
                end if;
              else
                state <= state;
              end if;
            when stopbit =>
              if(clk_rx = '1') then
                if(cnt_bitwidth = 15) then
                  en_tmp <= en;
                  state <= idle;
                else
                  cnt_bitwidth <= cnt_bitwidth + 1;
                  state <= state;
                end if;
              else
                state <= state;
              end if;
            when others =>
              en_tmp <= '0';
              state <= idle;
          end case;
        elsif(en = '0' and en_tmp = '0') then
          stby <= '1';
        end if;
      end if;
    end process;
  
end rtl;

テストベンチとRTLシミュレーション

テストベンチ記述例

以下は,UARTモジュール用のテストベンチ記述例(tb_UART.vhd)です. テストベンチ記述,Quartus IIおよびModelSimを用いたRTLシミュレーションの方法については,加算器のRTLシミュレーションが参考になると思います.


library IEEE;
use IEEE.std_logic_1164.all;


entity tb_UART is
end tb_UART;

architecture rtl of tb_UART is
  signal CLK: std_logic;
  signal RST: std_logic;
  signal TXD: std_logic;
  signal RXD: std_logic;
  signal DIN: std_logic_vector(7 downto 0);
  signal DOUT: std_logic_vector(7 downto 0);
  signal EN_TX: std_logic;
  signal EN_RX: std_logic;
  signal STBY_TX: std_logic;
  signal STBY_RX: std_logic;
  
  -- global clock period
  constant CP: time := 20 ns;
  -- bit rate (1 / 115200bps)
  constant BR: time := 8680 ns;
  
  component UART port(
    CLK: in std_logic;
    RST: in std_logic;
    TXD: out std_logic;
    RXD: in std_logic;
    DIN: in std_logic_vector(7 downto 0);
    DOUT: out std_logic_vector(7 downto 0);
    EN_TX: in std_logic;
    EN_RX: in std_logic;
    STBY_TX: out std_logic;
    STBY_RX: out std_logic);
  end component;
  
  begin
    uUART: UART port map(
      CLK => CLK,
      RST => RST,
      TXD => TXD,
      RXD => RXD,
      DIN => DIN,
      DOUT => DOUT,
      EN_TX => EN_TX,
      EN_RX => EN_RX,
      STBY_TX => STBY_TX,
      STBY_RX => STBY_RX);
  
  -- clock signal
  process begin
    CLK <= '0';
    wait for CP / 2;
    CLK <= '1';
    wait for CP / 2;
  end process;
  
  process begin
    RST <= '1'; RXD <= '1'; DIN <= x"ff"; EN_TX <= '0'; EN_RX <= '0';
    wait for CP; RST <= '0';

    wait for CP; DIN <= x"65";
    wait for CP; EN_TX <= '1';
    wait for CP; EN_TX <= '0';

    wait for (16 * BR); EN_RX <= '1';
    wait for CP; EN_RX <= '0';

    wait for BR; RXD <= '0'; -- start-bit
    wait for BR; RXD <= '1'; -- data-bit 8'hc5
    wait for BR; RXD <= '0';
    wait for BR; RXD <= '1';
    wait for BR; RXD <= '0';
    wait for BR; RXD <= '0';
    wait for BR; RXD <= '0';
    wait for BR; RXD <= '1';
    wait for BR; RXD <= '1';
    wait for BR; RXD <= '1'; -- stop-bit
    
    wait for (2 * BR); EN_RX <= '1';
    wait for CP; EN_RX <= '0';

    wait for BR; RXD <= '0'; -- start-bit
    wait for BR; RXD <= '0'; -- data-bit 8'hf0
    wait for BR; RXD <= '0';
    wait for BR; RXD <= '0';
    wait for BR; RXD <= '0';
    wait for BR; RXD <= '1';
    wait for BR; RXD <= '1';
    wait for BR; RXD <= '1';
    wait for BR; RXD <= '1';
    wait for BR; RXD <= '1'; -- stop-bit

    wait for (4 * BR); RST <= '1';
    
    assert false report "Simulation End." severity failure;
  end process;
  
end rtl;

RTLシミュレーション結果

下図に,上記のテストベンチ記述例を使用したRTLシミュレーションの結果を示します.

UARTモジュールのRTLシミュレーション結果

Terasic DE0に,UARTによるシリアル通信を実装するためには

Terasic DE0にはD-sub9端子が実装されておらず,シリアル通信を行うためには, シリアルケーブルの作成やUSB-シリアル変換モジュールの購入などが必要となります. 下記にこれらの実装例をまとめましたので,ご一読ください.