You are on page 1of 9

Designing UART in MyHDL and testing it in

an FPGA
In this article, we will learn how to capture our very own universal asynchronous
receiver/transmitter design using MyHDL, a free, open-source Python library.
By André Castelan Prado
Editor
Embarcados

Embedded systems engineers are quite familiar to the universal asynchronous receiver/transmitter. It's probably
the first communications protocol that we learn in college. In this article, we will design our very own UART using
MyHDL.
MyHDL is a free, open-source Python library developed by Jan Decaluwe. Its goal is to be a powerful hardware
description language. The idea is to apply the new concepts that have appeared in the software industry to
hardware design, such as test-driven development, functional tests, and high-level abstraction. The system also
generates synthesisable VHDL and Verilog code from the MyHDL design. The idea is to verify everything in Python
and then press the "Go" button to generate a VHDL or Verilog representation that can be synthesised and loaded
into an FPGA.

High-level view of the UART


As we know, to have working UART communication, we need the receiver and the transmitter to agree on a baud
rate, the number of data bits, the number of stop bits, and other details. In our example, we are going to implement
a UART with 8 data bits, 2 stop bits, and a baud rate of 115,200Hz. (You can customise these values as you wish.)
We are going to have three main modules as follows:
• baudrate_gen: This module generates the baud rate "ticks" at the right time.
• serial_tx: This module is our transmitter; it's going to send data out over the TX line. Its input is the data to
be sent (one byte) and a start that initiates the process.
• serial_rx: This module is our receiver; it's going to receive data in over the RX line. Its output is the data
received (one byte) and a data_rdy signal that indicates the data is ready to be consumed.

The baudrate_gen module


This is our simplest module. Based on the input clock, an output enable tick is generated. On my board, I have a
50MHz clock, and I need a baud rate of 115,200. This means we have 50,000,000/115,200, which give us 434.027.
Let's round this value to 434, which means we have to generate a tick every 434 clock pulses. We will also generate
a half baud rate tick; for this we have to count 434/2 = 217 pulses before enabling this signal.
The MyHDL code is very similar to VHDL or Verilog. First we define our module, which is called baudrate_gen, and
then we declare everything that is either an input or an output. In our module we have the following signals as
inputs:
• sysclk: Our clock

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 1 of 9


• reset_n: Our reset signal
• baud_rate_i: The number we have to count to generate our baud rate (this is going to be automatically
transformed to a constant)
Meanwhile, as outputs, we are going to have the baud_rate_tick_o and half_baud_rate_tick_o signals as previously
discussed.
The keyword Signal acts like a VHDL Signal; intbv is a type created in MyHDL. This is short for INT BIT VECTOR. The
goal is to avoid confusion between Boolean, signed, and unsigned data. The first parameter is the initialisation, and
then comes the min and max range.

The @always_seq is a MyHDL decorator. It's analogous to a VHDL process or a Verilog @always. There are several
decorators in MyHDL; this one creates a synchronous process with a clock and a reset. The .next is a function that
indicates that the value is going to be updated in the next delta cycle, which is again analogous to processes in VHDL
and Verilog. It's necessary to return our decorators at the end of the process.

The serial_tx module


We have a state machine that waits for a start signal (start_i) to initialise and send our byte (data_i) via the TX line
(transmit_o).
We describe everything at the RTL level. Once again, the MyHDL code is very similar to its VHDL and Verilog
counterparts. We now also have an @always_comb decorator, which is equivalent to a purely combinational Verilog
always or a VHDL process. We send a bit at each baud rate "tick." I've used a dual state machine approach to
separate the combinational and sequential parts -- the registers are inferred by the @always_seq decorator.

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 2 of 9


The serial_rx module
In this case, we wait for a start bit, and then we store the next byte in a register. This approach is very similar to the
way in which we implemented the serial_tx module.

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 3 of 9


Creating the testbench
Let's start by creating a traditional testbench for our design, excite it, and check (cheque for banks) the waveforms.
We'll import the modules that we've just created (serial_tx.py, serial_rx.py, and baudrate_gen.py) into the file
tb_serial.py.

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 4 of 9


We first define our custom header. (As most companies enforce header, it's good to be able to customise your own.)
We then define our bench function, which is going to test our modules. We start with the constant declarations, like
our clk_period, clk_freq, and baud_const. All the signals that connect our modules together are also declared. (Pay
attention to the declaration order.)
All the @always_seq decorators that are connected to our reset signal are going to behave like the reset's
parameters (active at 0 and async).
Next we have two decorators -- one to generate our clock and the other to generate the stimulus. As we can see, we
are getting the data 196 (hexadecimal: c4) at our serial_tx and sending it. We hope that our serial_rx module will
return 196 when the rx_rdy signal goes to 1.
The yield keyword is equivalent to a wait in VHDL; when the condition becomes true, the execution continues.
Our test_bench function executes the bench. TraceSignals is our function responsible for the waveform generation
in a .vcd file. Let's run our simulation for 1 ms by executing the following command:

>python tb_serial.py

This will cause a bench.vcd to be created. Let's open this file using the free, open-source gtkwave tool as follows:
>gtkwave bench.vd

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 5 of 9


And there is our data sent and received. Everything seems to work.

Test automation
One advantage of MyHDL is the ability to automate our tests. We no longer have to spend countless hours staring at
waveforms to see if everything is working as expected.
For example, suppose that, in order to verify that everything is working as expected, we wait for the rx_rdy posedge
and then check (cheque for banks) to see if the data we transmitted is the same as the data we received. In order to
do this, add these two lines after the line start.next = 0; and don't forget to keep the indentation, because Python is
indentation based.

And then we run the py.test framework at the terminal.

>py.test tb_serial.py

And Voilá! As we see, MyHDL automatically checks that the transmitted and received data are identical following
the rising edge of the rx_rdy signal.

Generating VHDL and/or Verilog code


So everything is working, and now we want to test our design in an FPGA to see how it performs in the real world.
MyHDL can't be synthesised directly, so we have to generate the equivalent VHDL or Verilog files. Fortunately, this
is very easy. All we have to do is change the following statement in our tb_serial.py file (note especially the use of
the toVHDL() function):

We also have to make the following change:

If we wanted to generate Verilog, all we would have to do would be replace the calls to the toVHDL() function with
the toVerilog() equivalent.
Generally speaking, the resulting VHDL/Verilog is very legible and well structured. Of course, since the generated
output is basically a translation of your RTL in Python, any bad Python will result in equally bad VHDL/Verilog. As
an example, let's look at our generated serial_tx.vhd as shown below:

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 6 of 9


EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 7 of 9
Testing on the FPGA board
I have a DE2-70 and a USB -> serial cable as shown below:

I used the Gold Reference Design that comes with this board as my base, and then I instantiated my three
components. The start of the TX module is connected to KEY[0]; the top-level RX is connected to my receive_i; and
the top-level TX is connected to my transmit_o.
The data_i from serial_tx is connected to the data_o from serial_rx. With this, we are performing an echo with the
computer.

And it works! I used the Hyper Terminal software and configured it as 115,200, 8 data bits, no parity, and two stop
bits (115200-8-n-2). Everything that you type comes right back to you.
The start_i from serial_tx is connected to a key that is not registered and has no debouncing, which means that, even
if you only press it once, it's going to trigger a lot of starts.

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 8 of 9


It's important to note that you should save your work before testing. This is a very simplified UART. We are not
looking for CTS or RTS signals, and we are sending at full speed. This made my windows reset in one of the tests.

Conclusion
With this project, we were able to take a quick look at MyHDL. As we see, it works very well, generating a good
VHDL representation that works in our FPGA. The development cycle is much faster with Python, since you have
more tools at your disposal to check if your design is working as intended. It's possible to have tons of asserts and to
test your full project with a simple:

> py.test <tb_top.py>

Using this approach means that you can always recheck your whole design very efficiently after making small
changes.
This UART project is very simple and intended only to show a true working MyHDL project, so please don't try to
build a chip out of this. Also, we took only an introductory look at MyHDL. Jan explains it much better than me at his
MyHDL.org website; also, you'll find a lot of examples at myHDL.org/examples.
Last but not least, this entire project, including the generated files, is available at GitHub. Thank you for taking the
time to read this; I would very much appreciate any comments or questions. 

About the author


André Castelan Prado is an editor at Embarcados.

This article first appeared in Portuguese on the Embarcados.com.br ("Embedded") website in Brazil.

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 9 of 9

You might also like