Abstract UART in VHDL

https://github.com/johonkanen/ac_inout_psu/releases/tag/abstract_uart

Compile the code with quartus_sh -t <path_to_project_root>\cyclone_10\tcl\build_project.tcl

As with most things, everything usually starts with communication. Since many types of information need to be communicated out of the overall design the the uart is the perfect first design unit to be brought into any project. Uart in this case is also used as an example of the init, request, ready type of module interface that is used throughout the codebase for interfacing different functionalities offered by various modules.

The code architecture with the abstracted module interface allow for calls to VHDL modules to be written in an intuitive way. As an example here is presented a code snippet which receives data from uart to a signal “data_from_uart” and transmits it at the rate of 100kHz using the abstract interface :

				
					if rising_edge(clock) then
    init_uart(uart_data_in);
    uart_transmit_counter <= uart_transmit_counter - 1; 
    if uart_transmit_counter = 0 then
        uart_transmit_counter <= counter_at_100khz;
        transmit_16_bit_word_with_uart(uart_data_in, data_from_uart);                                                         
    end if;
    receive_data_from_uart(uart_data_out, data_from_uart);
                                                                       
end if; --rising_edge
				
			

The uart module is first initialized with the init_uart(uart_data_in); procedure call, which drives the uart module to init state, by setting false to start_uart signals. The transmit_16_bit_word_with_uart(uart_data_in, data_from_uart); then sets an integer, data_from_uart to the transmit register and drives true to start uart signals.

When uart receiver module indicates that data has been received, the data ia fetched from uart rx module. This operations is performed in the receive_data_from_uart(uart_data_out, data_from_uart); procedure call.

This type of code is easy to re-use and it is quite possible that an individual without any knowledge of the underlying intricacies of VHDL can still understand what this piece of code is accomplishing. It should be noted that this is synthesizable code that can be run with FPGA.

Interface description

A basic module in VHDL has an entity, which is the interface description of a module. Entity has a port which presents the public interface to access the functionality implemented with a module.

				
					library work;
    use work.uart_pkg.all;
    use work.uart_transreceiver_pkg.all;
 
entity uart is
    port (
        uart_clocks   : in uart_clock_group;
        uart_FPGA_in  : in uart_FPGA_input_group;
        uart_FPGA_out : out uart_FPGA_output_group;
        uart_data_in  : in uart_data_input_group;
        uart_data_out : out uart_data_output_group
    );
end entity uart;
				
			

Port have directional signals which in this case are “in” and “out”. The datatypes used in the entity are defined in specific package that is accessed with the use.uart_pkg.all; declaration. The other package uart_transreceiver_pkg that is accessed, is a module that is instantiated by the uart module.

The package defines the record datatypes used in the port, which are a collection of signals that are used when the UART module operation is controlled.

				
					library ieee;
    use ieee.std_logic_1164.all;
    use ieee.numeric_std.all;
 
library work;
    use work.uart_transreceiver_pkg.all;
 
package uart_pkg is
 
    type uart_clock_group is record
        clock : std_logic;
    end record;
     
    type uart_FPGA_input_group is record
        uart_transreceiver_FPGA_in  : uart_transreceiver_FPGA_input_group;
    end record;
     
    type uart_FPGA_output_group is record
        uart_transreceiver_FPGA_out : uart_transreceiver_FPGA_output_group;
    end record;
     
    type uart_data_input_group is record
        uart_transreceiver_data_in  : uart_transreceiver_data_input_group;
    end record;
     
    type uart_data_output_group is record
        uart_transreceiver_data_out : uart_transreceiver_data_output_group;
    end record;
     
    component uart is
        port (
            uart_clocks   : in uart_clock_group;
            uart_FPGA_in  : in uart_FPGA_input_group;
            uart_FPGA_out : out uart_FPGA_output_group;
            uart_data_in  : in uart_data_input_group;
            uart_data_out : out uart_data_output_group
        );
    end component uart;
				
			

Furthermore, the module has interface routines defined in its package that are used in place of the members of interface signals to allow for looser coupling between the module interface and the application code of the module.

				
					----------------------------------------------------------------------
    procedure init_uart (
        signal uart_input : out uart_data_input_group);
----------------------------------------------------------------------
    procedure start_uart_transmitter (
        signal uart_input : out uart_data_input_group);
----------------------------------------------------------------------
    procedure load_16_bit_data_to_uart (
        signal uart_input : out uart_data_input_group;
        data_to_be_transmitted_with_uart : std_logic_vector);
------------------------------------------------------------------------
    procedure transmit_16_bit_word_with_uart (
        signal uart_input : out uart_data_input_group;
        data_to_be_transmitted_with_uart : std_logic_vector(15 downto 0));
 
    procedure transmit_16_bit_word_with_uart (
        signal uart_input : out uart_data_input_group;
        data_to_be_transmitted_with_uart : integer);
 
------------------------------------------------------------------------
    function get_uart_rx_data ( uart_output : uart_data_output_group)
        return integer;
 
    procedure receive_data_from_uart (
        uart_output : in uart_data_output_group;
        signal received_data : out integer);
     
------------------------------------------------------------------------
				
			

The interface implementation

The uart is first initialized using a procedure call init_uart(uart_data_in); which is implemented in the uart_pkg body.

				
					package body uart_pkg is
 
------------------------------------------------------------------------
    procedure init_uart
    (
        signal uart_input : out uart_data_input_group
    ) is
    begin
        init_uart(uart_input.uart_transreceiver_data_in);
    end init_uart;
				
			

Here there is a call to init uart through the uart transreceiver interface embedded in the uart_data_in, where data_packet transmission is requested signal is driven by false and init uar through the uart_tx_data_in is called

				
					package body uart_transreceiver_pkg is
 
------------------------------------------------------------------------
    procedure init_uart
    (
        signal uart_transreceiver_in : out uart_transreceiver_data_input_group
    ) is
    begin
 
        init_uart(uart_transreceiver_in.uart_tx_data_in);
        uart_transreceiver_in.uart_data_packet_transmission_is_requested <= false;
         
    end init_uart;
				
			

The uart tx implementation is correspondingly in the uart_tx_pkg.vhd where it can be seen to drive uart_transmit_is_requested signal with a ‘false’ -value.

				
					package body uart_tx_pkg is
 
------------------------------------------------------------------------
    procedure init_uart
    (
        signal uart_tx_input : out uart_tx_data_input_group
    ) is
    begin
        uart_tx_input.uart_transmit_is_requested <= false;
    end init_uart;
				
			

The action taken here is only to set two signals,

				
					uart_data_in.uart_transreceiver_data_in.uart_tx_data_in.uart_transmit_is_requested 
				
			

and

				
					uart_data_in.uart_transreceiver_data_in.uart_data_packet_transmission_is_requested
				
			

to false. What this accomplishes is that the signals are false unless specifically driven with a value ‘true’. In VHDL multiple assignments for a signal or variable function in such a way that the driver written last in the source code takes precedence.

The actual data transmission is triggered with a procedure call

				
					------------------------------------------------------------------------
    procedure transmit_16_bit_word_with_uart (
        signal uart_input : out uart_data_input_group;
        data_to_be_transmitted_with_uart : integer);
				
			

which takes the interface and an integer as arguments. The procedure implementation calls overloaded transmit_16_bit_word_with_uart which takes a std_logic_vector instead of an integer as argument

				
					------------------------------------------------------------------------
    procedure transmit_16_bit_word_with_uart
    (
        signal uart_input : out uart_data_input_group;
        data_to_be_transmitted_with_uart : integer
    ) is
        variable unsigned_data : unsigned(15 downto 0);
    begin
        unsigned_data := to_unsigned(data_to_be_transmitted_with_uart,16); 
        transmit_16_bit_word_with_uart(uart_input.uart_transreceiver_data_in, std_logic_vector(unsigned_data));
         
    end transmit_16_bit_word_with_uart;
				
			

The call to the transmit_16_bit_word_with_uart with transreceiver input actually drives the data_packet_transmission_is_requested signal with value true which is what triggers the signal transmission.

				
					procedure transmit_16_bit_word_with_uart
(
    signal uart_transreceiver_in : out uart_transreceiver_data_input_group;
    data_packet : in std_logic_vector(15 downto 0)
) is
begin
    uart_transreceiver_in.uart_data_packet_transmission_is_requested <= true;
    uart_transreceiver_in.uart_data_packet <= data_packet;
 
end transmit_16_bit_word_with_uart;
				
			

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top