VHDLwhiz VHDL Registers UART Test Interface Generator User Manual
- June 13, 2024
- VHDLwhiz
Table of Contents
VHDLwhiz.com
VHDL registers UART test interface generator – User manual
Version: 1.0.0
Date: September 1, 2023
Author: Jonas Julian Jensen
Product URL: https://vhdlwhiz.com/product/vhdl-registers-uart-test-
interfacegenerator
Contact email: jonas@vhdlwhiz.com
This document describes using VHDLwhiz’s UART test interface generator to
produce a custom VHDL module and Python script for reading and writing FPGA
register values.
License
The MIT license covers the source code’s copyright requirements and terms of
use.
Refer to the LICENSE.txt file in the Zip file for details.
Changelog
These changes refer to the project files, and this document is updated accordingly.
Version | Remarks |
---|---|
1.0.0 | Initial release |
Description
This document describes the following files and folders:
- gen_uart_regs.py
- generated/uart_regs.vhd
- generated/uart_regs.py
- generated/instantiation_template.vho
- rtl/uart_regs_backend.vhd
- rtl/uart_rx.vhd
- rtl/uart_tx.vhd
- demo/lattice_icestick/
- demo/xilinx_arty_a7_35/
- demo/xilinx_arty_s7_50/
The gen_uart_regs.py script and supporting VHDL
files in this project allow you to generate custom interfaces for reading and
writing FPGA register values of various types and widths using UART.
You can use the generated VHDL module and Python script to read from or write
to any number of registers in your design. The UART accessible registers can
have the types std_logic, std_logic_vector, signed, or unsigned.
You can decide on the precise composition of input and output registers and
types when generating the output files using the gen_uart_regs.py script.
The Python scripts were created partially with the help of the ChatGPT
artificial intelligence tool, while the VHDL code is handcrafted.
Requirements
The scripts in this project must be run through a Python 3 interpreter and the
Pyserial package must be installed.
You can install Pyserial through Pip using this command: pip install pyserial
Protocol
The VHDL files and Python script uses a data framing protocol with four control characters:
Name | Value | Comment |
---|---|---|
READ REQ | OxOA | Command from the host to the FPGA to initiate a write sequence |
to send all registers back over UART
START_WRITE| Ox0B| Marks the beginning of a write sequence in either direction
END_WRITE| OxOC| Marks the end of a write sequence in either direction
ESCAPE| OxOD| Escape character used for escaping any of the control words,
including the ESCAPE character itself, when they appear as data between the
START_WRITE and END_WRITE markers.
Any unescaped READ_REQ byte sent to the FPGA is an instruction to send all of
its UART-accessible registers (inputs and outputs) back to the host over UART.
This command is usually only issued by the uart_regs.py script.
Upon receiving this command, the FPGA will respond by sending the content of
all registers back to the host. First, the input signals, then the output
signals. If their lengths don’t add up to a multiple of 8 bits, the lower bits
of the last byte will be padded zeros.
A write sequence always starts with the START_WRITE byte and ends with the
END_WRITE byte. Any bytes between those are considered to be data bytes. If
any data bytes have the same value as a control character, the data byte must
be escaped. This means sending an extra ESCAPE character before the data byte
to indicate that it’s actually data.
If an unescaped START_WRITE arrives anywhere in the stream of bytes, it is
considered the start of a write sequence. The uart_regs_backend module uses
this information to resynchronize in case the communication gets out of sync.
This is the script you must start with to generate the interface. Below is a screenshot of the help menu that you can get by running: python gen_uart_regs.py -h
To generate a custom interface, you must run the script with each of your
desired UART controllable registers listed as arguments. The available types
are std_logic, std_logic_vector, unsigned, and signed.
The default mode (direction) is in and the default type is std_logic_vector
unless the register is of length: 1. Then, it will default to std_logic.
Thus, if you want to create a std_logic input signal, you can use any of these
arguments:
my_sl=1
my_sl=1:in
my_sl=1:in:std_logic
All of the above variants will result in the script generating this UART-
accessible signal:
Let’s run the script with arguments to generate an interface with several registers of different directions, lengths, and types:
Generated files
A successful run of the gen_uart_regs.py script will produce an output folder named generated with the three files listed below. If they already exist, they will be overwritten.
- generated/uart_regs.vhd
- generated/uart_regs.py
- generated/instantiation_template.vho
uart_regs.vhd
This is the custom interface module generated by the script. You need to
instantiate it in your design, where it can access the registers you want to
control using UART.
Everything above the “– UART accessible registers” section will be identical
for every uart_regs module, while the composition of port signals below that
line depends on the arguments given to the generator script.
The listing below shows the entity for the uart_regs module resulting from the
generate command example shown in the
gen_uart_regs.py section.
You do not need to synchronize the uart_rx signal, as that’s handled in the
uart_rx. module.
When the module receives a read request, it will capture the values of all
input and output signals within the current clock cycle. The instantaneous
snapshot is then sent to the host over UART.
When a write happens, all output registers are updated with the new values
within the same clock cycle. It is not possible to change output signal values
individually.
However, the uart_regs.py script allows the user to update only selected
outputs by first reading back the current values of all registers. It then
writes back all values, including the updated ones.
uart_regs.py
The generated/uart_regs.py file is generated together with the uart_regs VHDL
module and contains the custom register information in the header of the file.
With this script, you can read from or write to your custom registers with
ease.
Help menu
Type python uart_regs.py -h to print the help menu:
Setting the UART port
The script has options to set the UART port using the -c switch. This works on
Windows and Linux. Set it to one of the available ports listed in the help
menu. To set a default port, you can also edit the UART_PORT variable in the
uart_regs.py script.
Listing registers
Information about the register mapping is placed in the header of the
uart_regs.py script by the gen_uart_regs.py script. You can list the available
registers with the -l switch, as seen below. This is a local command and will
not interact with the target FPGA.
Writing to registers
You can write to any of the out mode registers by using the -w switch. Supply the register name followed by “=” and the value given as a binary, hexadecimal, or decimal value, as shown below.
Note that the VHDL implementation requires the script to write all output
registers simultaneously. Therefore, if you don’t specify a complete set of
output registers, the script will first perform a read from the target FPGA
and then use those values for the missing ones. The result will be that only
the specified registers change.
When you perform a write, all specified registers will change during the same
clock cycle, not as soon as they are received over UART.
Reading registers
Use the -r switch to read all register values, as shown below. The values
marked in yellow are the ones we changed in the previous write
example.
Every read shows an instantaneous snapshot of all input and output registers. They are all sampled during the same clock cycle.
Debugging
Use the -d switch with any of the other switches if you need to debug the communication protocol. Then, the script will print out all sent and received bytes and tag them if they are control characters, as shown below.
Using the interface in other Python scripts
The uart_regs.py script contains a UartRegs class that you can easily use as the communication interface in other custom Python scripts. Simply import the class, create an object of it, and start using the methods, as shown below.
Refer to the docstrings in the Python code for method and descriptions and return value types.
instantiation_template.vho
The instantiation template is generated along with the uart_regs module for
your convenience. To save coding time, you can copy the module instantiation
and signal declarations into your design.
Static RTL files
You need to include the following files in your VHDL project so that they are compiled into the same library as the uart_regs module:
- rtl/uart_regs_backend.vhd
- rtl/uart_rx.vhd
- rtl/uart_tx.vhd
The uart_regs_backend module implements the finite-state machines that clock in and out the register data. It uses the uart_rx and uart_tx modules to handle the UART communication with the host.
Demo projects
There are three demo projects included in the Zip file. They let you control
the peripherals on the different boards as well as a few larger, internal
registers.
The demo folders include pre-generated uart_regs.vhd and uart_regs.py files
made specifically for those designs.
Lattice iCEstick
The demo/icecube2_icestick folder contains a register access demo
implementation for the Lattice iCEstick FPGA board.
To run through the implementation process, open the
demo/lattice_icestick/icecube2_proj/uart_regs_sbt.project file in the Lattice
iCEcube2 design software.
After loading the project in the iCEcube2 GUI, click Tools →Run All to
generate the programming bitmap file.
You can use the Lattice Diamond Programmer Standalone tool to configure the
FPGA with the generated bitmap file. When Diamond Programmer opens, click Open
an existing programmer project in the welcome dialog box.
Select project file found in the Zip:
demo/lattice_icestick/diamond_programmer_project.xcf and click OK.
After the project loads, click the three dots in the File Name column, as
shown above. Browse to select the bitmap file that you generated in iCEcube2:
demo/lattice_icestick/icecube2_proj/uart_regs_Implmnt/sbt/outputs/bitmap/top_ice
stick_bitmap.bin
Finally, with the iCEstick board plugged into a USB port on your computer,
select Design→Program to program the SPI flash and configure the FPGA.
You can now proceed to read and write registers by using the
demo/lattice_icestick/uart_regs.py script as described in the uart_regs.py
section.
Xilinx Digilent Arty A7-35T
You can find the demo implementation for the Artix-7 35T Arty FPGA evaluation
kit in the demo/arty_a7_35 folder.
Open Vivado and navigate to the extracted files using the Tcl console found at
the bottom of the GUI interface. Type this command to enter the demo project
folder: cd
Execute the create_vivado_proj.tcl Tcl script to regenerate the Vivado
project: source ./create_vivado_proj.tcl
Click Generate Bitstream in the sidebar to run through all the implementation
steps and generate the programming bitstream file.
Finally, click Open Hardware Manager and program the FPGA through the GUI.
You can now proceed to read and write registers by using the
demo/arty_a7_35/uart_regs.py script as described in the uart_regs.py section.
Xilinx Digilent Arty S7-50
You can find the demo implementation for the Arty S7: Spartan-7 FPGA
development board in the demo/arty_s7_50 folder.
Open Vivado and navigate to the extracted files using the Tcl console found at
the bottom of the GUI interface. Type this command to enter the demo project
folder: cd
Execute the create_vivado_proj.tcl Tcl script to regenerate the Vivado
project: source ./create_vivado_proj.tcl
Click Generate Bitstream in the sidebar to run through all the implementation
steps and generate the programming bitstream file.
Finally, click Open Hardware Manager and program the FPGA through the GUI.
You can now proceed to read and write registers by using the
demo/arty_s7_50/uart_regs.py script as described in the uart_regs.py section.
Implementation
There are no specific implementation requirements.
Constraints
No specific timing constraints are needed for this design because the UART
interface is slow and treated as an asynchronous interface.
The uart_rx input to the uart_regs module is synchronized within the uart_rx
module. Thus, it doesn’t need to be synchronized in the top-level module.
Known issues
- You may need to reset the module before it can be used, depending on whether your FPGA architecture supports default register values.
CopyrightVHDLwhiz.com
References
- VHDLwhiz - The best resource for VHDL engineers
- Artix-7 FPGA Development Board - Digilent Arty A7 - Xilinx
- Spartan-7 FPGA Development Board for Hobbyists - Digilent Arty S7 - Xilinx
- Vivado - Wikipedia
- Installation - pip documentation v23.2.1
- pyserial · PyPI
- VHDLwhiz - The best resource for VHDL engineers
- VHDL registers UART test interface generator - VHDLwhiz
- VHDL registers UART test interface generator - VHDLwhiz
- iCEcube2 | FPGA Design Software | Lattice Semiconductor
- iCEstick Evaluation Kit | Lattice Kits & Boards
- Lattice Diamond Programmer and Deployment Tool
- Download Python | Python.org