クロック同期回路のためのテストベンチ / UART RXモジュール / リニアテストベンチ

pp.542-544

以降シミュレーションだけなのでDUVのコードはテキストとまったく同じものを使う。ここではデータを1バイトだけDUVに与え、正しく受信できたことを確かめる。

↓ これがDUV。

library ieee;
use ieee.std_logic_1164.all;
use ieee.math_real.all;
use ieee.numeric_std.all;

entity uart_rx is
    generic (
       -- 2400 -> 2604
       -- 9600 -> 651
       -- 115200 -> 54
       -- 1562500 -> 4
       -- 2083333 -> 3
        DIVISOR: natural := 54 -- DIVISOR = 100,000,000 / (16 x BAUD_RATE)
    );
    port (
        clock: in  std_logic;
        reset: in  std_logic;
       -- Client interface
        rx_data: out std_logic_vector(7 downto 0); -- received byte
        rx_strobe: out std_logic; -- validates received byte (1 system clock spike)
       -- Physical interface
        rxd: in  std_logic
    );
end uart_rx;

architecture Behavioral of uart_rx is
    type fsm_state_t is (idle, active); -- common to both RX and TX FSM
    type rx_state_t is record
        fsm_state: fsm_state_t; -- FSM state
        counter: unsigned(3 downto 0); -- tick count
        bits: std_logic_vector(7 downto 0); -- last 8 received bits
        nbits: unsigned(3 downto 0); -- number of received bits (includes start bit)
        enable: std_logic; -- signal we received a new byte
    end record;

    signal rx_state, rx_state_next: rx_state_t;

    constant COUNTER_BITS: natural := integer(ceil(log2(real(DIVISOR))));
    signal sample: std_logic; -- 1 clk spike at 16x baud rate
    signal sample_counter: unsigned(COUNTER_BITS - 1 downto 0); -- should fit values in 0..DIVISOR-1

begin

   -- RX state registers update at each CLK, and RESET
    reg_process: process (clock, reset) is
    begin
        if reset then
            rx_state.fsm_state <= idle;
            rx_state.bits <= (others => '0');
            rx_state.nbits <= (others => '0');
            rx_state.enable <= '0';
        elsif rising_edge(clock) then
            rx_state <= rx_state_next;
        end if;
    end process;

   -- RX FSM: updates rx_state_next from rx_state and inputs.
    rx_process: process (rx_state, sample, rxd) is
    begin
        case rx_state.fsm_state is
            when idle =>
                rx_state_next.counter <= (others => '0');
                rx_state_next.bits <= (others => '0');
                rx_state_next.nbits <= (others => '0');
                rx_state_next.enable <= '0';
                if rxd = '0' then
                   -- start a new byte
                    rx_state_next.fsm_state <= active;
                else
                   -- keep idle
                    rx_state_next.fsm_state <= idle;
                end if;

            when active =>
                rx_state_next <= rx_state;
                if sample then
                    if rx_state.counter = x"8" then
                       -- sample next RX bit (at the middle of the counter cycle)
                        if rx_state.nbits = x"9" then
                            rx_state_next.fsm_state <= idle; -- back to idle state to wait for next start bit
                            rx_state_next.enable    <= rxd; -- OK if stop bit is '1'
                        else
                            rx_state_next.bits  <= rxd & rx_state.bits(7 downto 1); -- shift new bit in bits
                            rx_state_next.nbits <= rx_state.nbits + 1;
                        end if;
                    end if;
                    rx_state_next.counter <= rx_state.counter + 1;
                end if;

        end case;
    end process;

   -- RX output
    rx_output: process (rx_state) is
    begin
        rx_strobe <= transport rx_state.enable after 4.34 us;
        rx_data <= transport rx_state.bits after 4.34 us;
    end process;

   -- sample signal at 16x baud rate, 1 CLK spikes
    sample_process: process (clock, reset) is
    begin
        if reset then
            sample_counter <= (others => '0');
            sample <= '0';
        elsif rising_edge(clock) then
            if sample_counter = DIVISOR - 1 then
                sample <= '1';
                sample_counter <= (others => '0');
            else
                sample <= '0';
                sample_counter <= sample_counter + 1;
            end if;
        end if;
    end process;
end Behavioral;

↓ これがテストベンチ。

library ieee;
use ieee.std_logic_1164.all;

entity uart_rx_tb is
end;

architecture testbench of uart_rx_tb is
    signal clock: std_logic := '0'; -- デフォルトの初期値は'U'であり、そのnotも'U'であるため、初期化しないとトグルできない。
    signal reset: std_logic := '0';
    signal rxd: std_logic := '1';
    signal rx_strobe: std_logic;
    signal rx_data: std_logic_vector(7 downto 0);
    constant BIT_LENGTH: time := 8.68 us; -- ボーレート115200の1周期
begin
    duv: entity work.uart_rx port map (
        clock => clock,
        reset => reset,
        rx_data => rx_data,
        rx_strobe => rx_strobe,
        rxd => rxd
    );

    clock <= not clock after 5 ns; -- 100 MHz clockを生成する。
    reset <= '1', '0' after 20 ns;

    stimuli_and_checker: process
       -- Start bit, data bits (LSB to MSB), and stop bit
       -- スタートビット0、LSBから順番に8ビット、最後にストップビット1、をテストデータとする。
        constant TX_DATA: std_logic_vector := b"0_11001010_1";
    begin
        wait until not reset; -- リセットが解除されるのを待って、
       -- wait until rising_edge(clock) and reset = '0'; -- 処理の開始をクロックに同期させたい場合。

       -- Drive Tx bits one at a time on DUV rxd line
       -- テストデータのスタートビットから順番に1ビットずつDUVのrxd信号に乗せる。
        for i in TX_DATA'range loop
            rxd <= TX_DATA(i);
            wait for BIT_LENGTH;
        end loop;

       -- Wait for DUV to acknowledge the reception
        wait until rx_strobe for BIT_LENGTH;
        
        assert rx_strobe /= '1' report "rx_strobe is '1" severity note;
        assert rx_data /= "01010011" report "rx_data is 01010011" severity note;
        report "End of testbench. All tests passed.";
        std.env.finish;
    end process;
end;

↓ 実行結果。LSBから1ビットずつ順番に受信してMSBに格納し、そのたびに全体を右にシフトする。