Let's build (half a) UART
Aug 31, 2016
The problem with FPGAs is that they’re so low-level. It’s a bit like sitting on the floor with a huge pile of 7400 series chips, trying to make them do something … u s e f u l ?
When starting from scratch (just for my own education and entertainment), one of the first things I’ll want is to hook up the FPGA to some nearby computer or µC.
The one tool that comes to mind is a serial UART interface, so let’s try and build one. For now, I’ll aim for the absolute minimum: serial output, at a fixed 115200 baud rate, sending out a test byte once every second.
Let’s see… we’ll need:
- a 115.2 KHz clock baudrate generator
- a once-a-second tick, to trigger sends
- logic to actually shift out the data
Or in schematic form (as generated by Quartus once this was all coded up):
There are more accurate ways to generate 115.2 KHz from our 50 MHz system clock, but for now we’ll use a divide-by-434:
module BaudGen ( input clk, // 50 MHz output baud // 115.2 KHz (approx) ); reg [8:0] tx_sample_cntr = 0; always @(posedge clk) if (tx_sample_cntr == 0) tx_sample_cntr <= 50000000/115200-1; else tx_sample_cntr <= tx_sample_cntr - 1; assign baud = tx_sample_cntr == 0; endmodule
Similarly for the 1 Hz ticker:
module Tick1hz ( input clk, // 50 MHz output tick // one short pulse every 1s ); reg [31:0] counter = 0; always @(posedge clk) if (counter == 50000000-1) counter <= 0; else counter <= counter + 1; assign tick = counter == 0; endmodule
The core idea is to copy the start bit, the data bits, and the stop bit into an 10-bit shift register, and to then shift one bit out into the “txd” I/O pin on every baudrate clock tick. Since the stop bit is 1, we can simply keep on sending until the shift register is zero, which signals it is empty and can then be filled with a new byte.
Here is the generated circuit:
We then tie this together in a “top” module:
module top ( input clk, // 50 MHz output txd // UART tx pin ); wire tick, baud; Tick1hz U1(.clk(clk), .tick(tick)); BaudGen U2(.clk(clk), .baud(baud)); UartTx U3( .clk(clk), .tx_clock(baud), .tx_data(8'h45), // "E" .tx_start(tick), .txd(txd) ); endmodule
This entire setup uses 91 logic elements:
And sure enough, when hooking up a serial port (and setting up the proper I/O
txd), I see the letter “E” coming out once a second.
The project files can be found on GitHub.
If any of this catches your interest, but you can’t make any heads or tails of this Verilog “language” - just as I’ve been scratching my head for days - then maybe this will help:
- Download this brilliant PDF (copy).
- Read the first 15 pages (out of 29).
- Then read these tips (also a PDF).
- Repeat until it makes a little sense.
- Continue reading that 1st PDF.
The trick is that if you’re used to writing software, then a “Hardware
Description Language” such as Verilog requires you to re-wire your brain. I
found the distinction between
<= assignments particularly confusing
until I realised that
= is like C’s #define and
<= is more like C’s
variable assignments, but all happening in parallel!
For comments, visit the forum.