FPGA Simple UARTEric Bainville - Apr 2013
The reception process is a finite state machine (FSM). After several half-working attempts, I finally adopted the design suggested in Pong P. Chu's book FPGA Prototyping by VHDL Examples: Xilinx Spartan-3 Version. Following this design, the finite state machine is implemented using three processes attached to the same clock. The first process (reg_process) updates the current state from the previously computed new state. The second process (rx_process) takes the current state, the inputs, and prepares the new state. The final process (rx_output) updates the outputs from the current state.
To make the code more legible, I put together all state signals into a record type rx_state_t. The state contains the following fields:
- fsm_state, the FSM state, either idle or active. Since we don't manage the various error cases, only two states are needed.
- counter, a 4-bit value incremented at each sample tick and used to synchronize data sampling.
- bits, nbits, the last bits received (up to 8), and the number of bits received in the current 1-byte frame.
- enable, set after an entire byte has been received, controlling port rx_enable signalling the validity of port rx_data.
Synchronization with the emitter clock is done when we leave the idle state: counter starts at 0, and is ideally back to 0 at the beginning of each bit. We assume this will remain valid for the entire frame, and will sample the rx input signal each time the counter reaches 8, supposedly in the middle of each bit.
entity basic_uart is port ( -- Client interface rx_data: out std_logic_vector(7 downto 0); -- received byte rx_enable: out std_logic; -- validates received byte (1 system clock spike) -- Physical interface rx: in std_logic; ); end basic_uart; architecture Behavioral of basic_uart 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: std_logic_vector(3 downto 0); -- tick count bits: std_logic_vector(7 downto 0); -- last 8 received bits nbits: std_logic_vector(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; begin -- RX state registers update at each CLK, and RESET reg_process: process (clk,reset) is begin if reset = '1' then rx_state.fsm_state <= idle; rx_state.bits <= (others => '0'); rx_state.nbits <= (others => '0'); rx_state.enable <= '0'; elsif rising_edge(clk) 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,rx) 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 rx = '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 = '1' then if rx_state.counter = 8 then -- sample next RX bit (at the middle of the counter cycle) if rx_state.nbits = 9 then rx_state_next.fsm_state <= idle; -- back to idle state to wait for next start bit rx_state_next.enable <= rx; -- OK if stop bit is '1' else rx_state_next.bits <= rx & 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_enable <= rx_state.enable; rx_data <= rx_state.bits; end process; end Behavioral;
The Transmission finite state machine is presented in the next page.
|FPGA Simple UART : Serial I/O||Top of Page||FPGA Simple UART : Transmission|