------------------------------------------------------------------------------- --! @file --! @author Hipolito Guzman-Miranda --! @brief Emulates an UART receiver. ------------------------------------------------------------------------------- --! Use IEEE standard definitions library library ieee; --! Use std_logic* signal types use ieee.std_logic_1164.all; --! Use arithmetic operations and unsigned types use ieee.numeric_std.all; --! Use the chr function to convert integer to char use work.txt_util.all; --! Include datatypes and procedures needed to write to files use std.textio.all; --! Use the slv2string function present in the vhdl_verification package use work.vhdl_verification.all; --! @brief Emulates an UART receiver. Writes receiver characters to a file. --! --! @details Waits for \c uart_input to leave rest state, and extracts data --! from the serial line, performing some sanity checks. If \c PARITY is either --! "even" or "odd", parity is also extracted from the serial line and checked --! against the expected value. In the case of a parity mismatch, an error will be --! printed in the simulation log. Received characters will be printed in \c --! OUTPUT_FILE --! state of the leds each time any the input bit changes. Does not keep any --! internal memory, \c led_input is assumed to be directly connected to the --! leds. entity uart_emu is generic ( OUTPUT_FILE : string := "uart.log"; --! File where received chars will be written VERBOSE : boolean := false; --! Log beginning and end of transactions, as well as individual bit values DATA_BITS : integer := 8; --! Number of data bits in the UART word PARITY : string := "none"; --! Can be either "even", "odd", or "none" BIT_DURATION : time := 104 us; --! Duration of each bit, depends on baudrate (it is actually 1 second / baudrate) STOP_BITS : integer := 1 --! Can be either 1 or 2 ); port ( uart_input : in std_logic --! Connect here the TX signal from your design ); end uart_emu; --! @brief Architecture is based on just a single process --! --! @detailed An assertion has also been added to check that generics are --! assigned supported values. architecture uart_emu_arch of uart_emu is constant LINE_FEED : std_logic_vector(7 downto 0) := x"0A"; signal rxparity : std_logic; signal expectedparity : std_logic; begin -- PARITY must be one of the three supported values assert PARITY = "even" or PARITY = "odd" or PARITY = "none" report "uart_emu: PARITY must be either even, odd or none" severity failure; -- STOP_BITS must be one of the two supported values assert STOP_BITS = 1 or STOP_BITS = 2 report "uart_emu: STOP_BITS must be either 1 or 2" severity failure; --! @brief Decode \c uart_input signal and write it to \c OUTPUT_FILE --! --! @detailed Assuming the line is in the rest value ('1') when we are --! outside of the process, when we enter the process it should be because --! the line changed to the start bit ('0'). From that point, get all the --! data values and the parity, performing some sanity checks. --! Please note that this process does not check for signal stability --! (we will not raise any errors if the signals change a bit sooner or later --! than \c BIT_DURATION), we just measure the signal at half the bit time, --! as a real UART receiver would do. get_values: process file f : text; variable l : line; variable rxdata : std_logic_vector(DATA_BITS-1 downto 0); begin -- A transaction start when the line goes to a strong low level wait until uart_input = '0'; if VERBOSE then report "uart_emu: uart transaction begin"; end if; -- Offset half a bit so we measure at the center of each bit wait for BIT_DURATION/2; -- Raise an error if we are not in the start bit if (uart_input /= '0') then report "uart_emu: wrong value for start bit: expected: '0', actual: " & std_logic'image(uart_input) severity failure; end if; -- Get all data bits for i in 0 to DATA_BITS-1 loop wait for BIT_DURATION; rxdata(i) := uart_input; if VERBOSE then report "uart_emu: sampled bit " & integer'image(i) & ", got value: " & std_logic'image(uart_input); end if; end loop; -- Print received data. If DATA_BITS = 8, print it also in ASCII if DATA_BITS = 8 then report "uart_emu: received data: " & slv2string(rxdata) & " (bin), " & slv2hexstring(rxdata) & " (hex), " & ascii(to_integer(unsigned(rxdata))) & " (ASCII)"; else report "uart_emu: received data: " & slv2string(rxdata) & " (bin), " & slv2hexstring(rxdata) & " (hex)"; end if; -- If DATA_BITS = 8, write received data to output file. Data is always -- prepared to be written to the output file, no matter if we have parity -- errors or not. -- -- We write the received char into the line variable, and not directly -- into the file, unless we got a newline char (line feed, \n). Only when -- we receive a newline char, we commit the line to the file. -- -- This would probably be more convenient if we wrote to a pipe instead -- of writing to a file, but I am not sure if that would work on Windows. -- -- Since the file output seems to be buffered, normally it would not be -- actually written to the log until we close the file, which should -- happen automatically at the end of the simulation. But in that case -- we would get all the text at the same time, instead of line by line as -- the simulation progresses. So we will open and close the file each -- time we have a complete line. if DATA_BITS = 8 then if rxdata /= LINE_FEED then write (l, ascii(to_integer(unsigned(rxdata)))); else file_open(f, OUTPUT_FILE, append_mode); writeline (f, l); file_close(f); end if; end if; -- If parity is even or odd, get parity bit if PARITY /= "none" then wait for BIT_DURATION; rxparity <= uart_input; end if; -- Calculate expected parity if PARITY = "even" then expectedparity <= xor_reduce(rxdata); elsif PARITY = "odd" then expectedparity <= xnor_reduce(rxdata); else expectedparity <= '-'; end if; -- Check if received parity matches expected, report an error if not if PARITY /= "none" then if rxparity /= expectedparity then report "uart_emu: parity error! received parity bit: " & std_logic'image(rxparity) & " expected: " & std_logic'image(expectedparity) severity error; end if; end if; -- Wait for stop bits, and raise errors if the value is incorrect. -- We always will have at least one stop bit wait for BIT_DURATION; if (uart_input /= '1') then report "uart_emu: wrong value for stop bit: expected: '1', actual: " & std_logic'image(uart_input) severity failure; end if; -- But sometimes we will have a second stop bit if STOP_BITS = 2 then wait for BIT_DURATION; if (uart_input /= '1') then report "uart_emu: wrong value for second stop bit: expected: '1', actual: " & std_logic'image(uart_input) severity failure; end if; end if; -- We could do a last half-bit shift before we exit the process, but since -- we will wait for an event on uart_input when we enter the process again, -- that half-bit wait will be performed nevertheless. -- Also, by not manualy waiting for a half-bit, we avoid missing the start -- bit event on uart_input in the case of receiving a new transaction just -- after the last stop bit. if VERBOSE then report "uart_emu: uart transaction end"; end if; end process; end uart_emu_arch;