Being able to communicate between a host computer and a project is often a key requirement, and for FPGA projects that is easily done by adding a submodule like a UART. A Universal Asynchronous Receiver-Transmitter is the hardware that facilitates communications with a serial port, so you can send commands from a computer and get messages in return.
Last week I wrote about an example POV project that’s a good example for learn. It was both non-trivial and used the board’s features nicely. But it has the message hard coded into the Verilog which means you need to rebuild the FPGA every time you want to change it. Adding a UART will allow us to update that message.
- Store the text in a way that you can be changed on the fly
- Insert a UART to receive serial data to store as text
- Use a carriage return to reset the data pointer for the new text
- Use an escape to clear the display and reset the data pointer
Finding and Adding a UART Core
A UART is a fairly common item and you’d think there would be one handy in the Altera IP catalog you see in Quartus. There is and it is buried under the University Program. It also has a few quirks because it really expects to be part of a whole system and I just wanted to use a UART.
Actually, for this application, we only really need the receiver. I’ve written that code plenty of times but lately I’ve been using an MIT licensed UART that I acquired sometime in the past. There are a few versions of this floating around including at freecores — which has a lot of reusable FPGA bits and on OpenCores. If you poke around you can find FPGA code ranging from UARTs and PS/2 interfaces to entire CPUs. True, you can also grab a lot of that out of the official IP catalog, but you probably won’t be able to use those on other FPGA families. The UART I’m using, for example, will work just fine on a Lattice IceStick or probably any other FPGA I care to use it on. (the version I use is a little newer than any other copy I could find, even using Google, so check my repo.)
The UART resides in a single file and it was tempting to just plop it into the project, but resist that urge in favor of some better practices. I created a cores directory in the top-level directory and placed it there.
The UART interface is very simple and for this project, we don’t need the transmitter so a lot of it will be empty. Here’s the code:
uart #( .baud_rate(9600), // default is 9600 .sys_clk_freq(12000000) // default is 100000000 ) uart0( .clk(CLK12M), // The master clock for this module .rst(~nrst), // Synchronous reset .rx(UART_RXD), // Incoming serial line .tx(UART_TXD), // Outgoing serial line .transmit(), // Signal to transmit .tx_byte(), // Byte to transmit .received(isrx), // Indicated that a byte has been received .rx_byte(rx_byte), // Byte received .is_receiving(), // Low when receive line is idle .is_transmitting(),// Low when transmit line is idle .recv_error() // Indicates error in receiving packet. );
Simple enough. The baud rate is 9600 baud and the input clock frequency is 12 MHz. The clk argument connects to that 12 MHz clock and the rst argument gets a positive reset signal. The nrst signal is active low, so I invert it on the way in. I connected both pins of the MAX1000 board even though I won’t use the transmitter since I thought I might use it at some point.
The only other two signals connected are rx_byte — you can guess what that is — and isrx which goes high when something has come in on the serial port.
The signals UART_RXD and UART_TXD now appear in the top-level module:
module top ( input CLK12M, input USER_BTN, output [7:0] LED, output SEN_SDI, output SEN_SPC, input SEN_SDO, output SEN_CS, output [8:1] PIO, input UART_RXD, output UART_TXD );
That’s not enough, though. You need to set the constraints to match the physical pins. The documentation is a bit of a mess in this regard because the serial port is on port B of the USB chip which — in theory — could be anything. It took a little poking into some examples to figure out which pins were which.
In the assignment editor, the two pins we want are marked as BDBUS and BDBUS. I turned the location constraints to Enabled=No on those pins so they wouldn’t conflict with my more descriptive names. Then I made these four entries in the assignment editor:
That will do the trick. Now we need to get those input characters to the LEDs somehow.
The original code set the text to display as an assignment. This is compact at compile time but doesn’t let you change the message at runtime (you’d need to recompile and upload again).
assign foo = "W";
To improve upon this I changed foo to be a register type and initialized it. (Some Verilog compilers won’t synthesize an initial block, but the version of Quartus I’m using will.) This is not the same as responding to the reset, though. It initializes the registers at configuration time and that’s it. For this application, I thought that was fine. Cycle the power or force a reconfigure if you want to reload, but a push of the user button won’t change the message.
Here’s what the change looks like:
reg [7:0] foo [0:15]; initial begin foo = "W"; foo = "D"; foo = "5"; . . .
With this new configuration, the text is malleable, and we can hear data coming from the PC. We just have to connect those dots.
// Let's add the ability to change the text! // Note: I had to change the foo "array" // To be more than a bunch of assigns, above, too wire isrx; // Uart sees something! wire [7:0] rx_byte; // Uart data reg [3:0] fooptr=0; // pointer into foo array integer i; always @(posedge CLK12M) begin if (isrx) // if you got something begin if (rx_byte==8'h0d) fooptr<=0; else if (rx_byte==8'h1b) begin fooptr<=0; // Note: Verilog will unroll this at compile time for (i=0;i<16;i=i+1) foo[i] <= " "; end else begin foo[fooptr]<=rx_byte; // store it fooptr<=fooptr+1; // natural roll over at 16 end end end
This is pretty straightforward. On each rising clock edge, we look at isrx. If it is high, we look to see if the character is a carriage return (8’h0d) or an escape (8’h1b). Don’t forget that this line is an assignment, not a “less than or equal to” statement:
fooptr <= 0;
If the character is anything else, the code stores it at foo[fooptr]. The fooptr variable is only 4 bits so the 16 character rollover will take care of itself when we increment it.
The only other oddity in the code is the use of a for loop in FPGA synthesis. Some tools may not support this, but notice that the i variable is an integer. The compiler is smart enough to know that it needs to run that loop at compile time and generate code for the body. So you could easily replace this with 16 lines explicitly naming each element of foo. Of course, the for loop has to have definite limits and there are probably other restrictions on how many loops are acceptable, for example.
That’s all there is to it. Once you load the FPGA it will look like it always did. But if you open a terminal on the USB serial port (on my machine it is /dev/ttyUSB9 because I have a lot of serial ports; yours will almost certainly be different like /dev/ttyUSB0), set it for 9600 baud and you can change the text.
If your terminal doesn’t do local echo you’ll be typing blind. Echoing the characters back from the FPGA would be a good exercise and would make use of the transmitter, too.
If you want to experiment, you now have a framework that can read the accelerometer and — with just a little more work — can talk back and forth to the PC. You could PWM the LEDs to control the brightness from the terminal, for example. Or make a longer text string that scrolls over time.
One of the attractive things about modern FPGAs is that they can accommodate CPUs. Even this inexpensive board can host a simple NIOS processor. That allows you to do things like serial communications and makes managing things like host communications much simpler. You can still use the rest of the FPGA for high speed or highly parallel tasks. For example, this project could have easily had a NIOS CPU talking to PC while the FPGA handled the motion detection and LED driving. Just like I rejected the “stock” UART and got one elsewhere, there are plenty of alternatives to the NIOS that will fit on this board, too.
If you still want more check out our FPGA boot camps. They aren’t specific to the MAX1000, but most of the material will apply. Intel, of course, has a lot of training available if you are willing to invest the time.