Being able to interface an FPGA project with a device that has a serial port allows for a basic means of sending and receiving data between the FPGA and a PC. For this we need to implement a Universal Asynchronous Receiver Transmitter (UART) circuit within the FPGA system. You can then use a PC with an RS232 Serial port (shown above) to interface the FPGA with a PC, assuming your development board has the port and voltage conversion circuitry.
Because RS232 is a dated protocol that sometimes isn’t available on modern PC’s, another option is to use a USB-Serial converter chip, which connects to a USB port.
In this post I will detail a UART controlled stopwatch, designed for the Basys 2, using the same stopwatch implementation as in my previous post. To communicate with the PC through serial we will implement a UART receiver and transmitter circuit, as well as a master control circuit that routes all the pieces together. We will then be able to control the stopwatch’s start, stop, and clear functions by sending ascii characters from the PC to the FPGA’s UART receiver circuit, and also send the current stopwatch time from the FPGA’s Transmitter circuit to the PC for viewing.
The UART circuit has an input called rx (receive) and an output called tx (transmit). The rx from the FPGA UART connects to the tx of the USB-Serial converter, and the tx from the FPGA UART connects to the rx of the USB-Serial converter.
The signal protocol that will be sent or received is shown above. The line is normally held high when idling. The beginning of packet is a start bit, which holds the line low for one time interval. Next comes the 8 bits in the byte, LSB first. Finally a stop bit, which is the line being held high for one time interval. This UART configuration has one start bit, 8 data bits, and 1 stop bit. The time interval for each bit is set by what is called the buad rate. Serial communication doesn’t have a clock line shared between the two systems to synchronize communication. Instead, both systems must agree to communicate with each other at the same baud rate which is given in bits per second. For our implementation we will use 19200 baud.
I will detail a receiver and transmitter algorithm that is similar to what is taught in Pong Chu’s FPGA Prototyping by Verilog Examples.
For the FPGA receiver, instead of sampling the rx input line on each tick from a 19200 baud counter/tick generator, we will have a counter/tick generator that ticks 16 times as fast (307200 ticks per second), and thus will count 16 ticks for every data bit.
When the rx line goes from idle (high) to the start bit (low), the counter will begin counting ticks. A bit for a 19200 baud rate lasts around 52 μs, so if we want that divided into 16 smaller ticks, each will be separated by 3.25 μs. From the beginning of the start bit, the counter will count up every 3.25 μs until tick 8, upon which we will be in the middle of the start bit. The counter will then be reset to 0, and will then continue to tick. When the counter ticks up to 8 again, the first data bit will be conveyed across the line. When the counter reaches 16, we will be in the middle of the first data bit, and we will then sample the data and reset the counter. We then count up to 16 again and sample the next data bit, and so on for the remaining data bits.
By counting up to 16, we ensure that we’re always no more than 1/16 from the middle of the bit when we sample the rx line, regardless of when the asynchronous start bit begins. This scheme is called oversampling, and counting to 16 is slightly arbitrary. Keep in mind that if we needed a higher baud rate, counting to 16 could be limited by the system clock being too slow.
The receiver algorithm is as follows:
- When idling and the rx line goes low, the start bit has arrived. Begin counting the oversampling 16*baudrate ticks.
- Once 8 ticks are counted, reset the counter.
- Once we have counted 16 ticks, we are in the middle of the first data bit. Sample data by shifting it into a register for received data.
- Repeat above step 7 more times for remaining data bits.
- Finally, rx line is high for stop bit. Count 16 more ticks, then go back to idling.
Let’s look at the code:
module uart_rx ( input wire clk, reset, input wire rx, baud_tick, // input rx line, and 16*baudrate tick output reg rx_done_tick, // receiver done tick output wire [7:0] rx_data // received data ); // symbolic state declarations for FSMD localparam [1:0] idle = 2'b00, // idle state, waiting for rx to go low (start bit) start = 2'b01, // start state, count for 8 baud_ticks data = 2'b10, // data state, shift in 8 data bits stop = 2'b11; // stop state, count 16 baud_ticks, assert done_tick // internal signal declarations reg [1:0] state_reg, state_next; // FSMD state register reg [4:0] baud_reg, baud_next; // register to count baud_ticks reg [3:0] n_reg, n_next; // register to count number of data bits shifted in reg [7:0] d_reg, d_next; // register to hold received data // FSMD state, baud counter, data counter, and data registers always @(posedge clk, posedge reset) if (reset) begin state_reg <= idle; baud_reg <= 0; n_reg <= 0; d_reg <= 0; end else begin state_reg <= state_next; baud_reg <= baud_next; n_reg <= n_next; d_reg <= d_next; end // FSMD next state logic always @* begin // defaults state_next = state_reg; rx_done_tick = 1'b0; baud_next = baud_reg; n_next = n_reg; d_next = d_reg; case (state_reg) idle: // idle, wait for rx to go low (start bit) if (~rx) // when rx goes low... begin state_next = start; // go to start state baud_next = 0; // set baud_reg to 0 end start: // start, count 8 baud_ticks begin if(baud_tick) baud_next = baud_reg + 1; // increment baud_reg every tick else if (baud_reg == 8) // when baud_reg has counted to 8 begin state_next = data; // go to data state baud_next = 0; // set baud_reg and n_next = 0; // data bit count reg to 0 end end data: // data, shift in 8 data bits to data reg begin if(baud_tick) baud_next = baud_reg + 1; // increment baud_reg every tick else if(baud_reg == 16) // when baud_reg counted 16 ticks... begin d_next = {rx, d_reg[7:1]}; // left shift in rx data to received data reg n_next = n_reg + 1; // increment data bit count baud_next = 0; // reset baud tick counter to 0 end else if(n_reg == 8) // once 8 data bits have been shifted in... state_next = stop ; // move to stop state end stop: // stop, rx line is high for 16 baud_ticks (stop bit) begin if(baud_tick) baud_next = baud_reg + 1; // increment baud_reg every tick else if (baud_reg == 16) // once 16 baud_ticks have been counted begin state_next = idle; // go to idle state rx_done_tick = 1'b1; // assert receive done tick end end endcase end // output received data assign rx_data = d_reg; endmodule
The circuit takes an input for the rx line, baud_tick from an external tick generator, and has outputs for the receive done_tick and received data. A finite state machine with data path (FSMD) is implemented to achieve the receiver algorithm that we specified.
Next we need to create a transmitter circuit that can transmit a packet across the FPGA UART tx line to the PC for the stopwatch time.
The transmitter circuit will have a similar algorithm as the receiver, except we now need to transmit a proper packet according to the agreement of having the FPGA tx line to the rx line of PC UART held high when idle, go low for a start bit, convey 8 data bits, and a final stop bit, all at the agreed 19200 baud rate. We will use the same oversampling baud_tick generator to schedule the state of the tx line.
The algorithm is as follows:
- Until we need to transmit a data packet, idle and hold tx high.
- Once tx_start input is asserted telling us to send data, pull tx low and count 16 of the baud_ticks.
- Shift in the data to be transmitted to a data register.
- Set tx to the state of the LSB of the data register and count for 16 baud_ticks.
- Right shift the data register by one to shift the next data bit to the LSB.
- Repeat steps 4 and 5 seven more times for the remaining data bits to be transferred.
- Hold tx high for 16 baud_ticks to transmit the stop bit, and go back to idle.
Let’s look at the code:
module uart_tx ( input wire clk, reset, input wire tx_start, baud_tick, // input to start transmitter, and 16*baudrate tick input wire [7:0] tx_data, // data to transmit output reg tx_done_tick, // transmit done tick output wire tx // output tx line ); // symbolic state declarations localparam [1:0] idle = 2'b00, // idle state, waiting for tx_start to be asserted, load tx_data to output shift register start = 2'b01, // start state, hold tx low for 16 baud ticks data = 2'b10, // transmit 8 data bits stop = 2'b11; // stop bit, hold line high for 16 baud ticks // internal signal declarations reg [1:0] state_reg, state_next; // FSMD state register reg [4:0] baud_reg, baud_next; // register to count baud_ticks reg [4:0] n_reg, n_next; // register to count number of data bits shifted out reg [7:0] d_reg, d_next; // register holds data to transmit reg tx_reg, tx_next; // register to hold current state of tx line // FSMD state, baud counter, data counter, and data registers always @(posedge clk, posedge reset) if (reset) begin state_reg <= idle; baud_reg <= 0; n_reg <= 0; d_reg <= 0; tx_reg <= 1'b1; end else begin state_reg <= state_next; baud_reg <= baud_next; n_reg <= n_next; d_reg <= d_next; tx_reg <= tx_next; end // FSMD next state logic always @* begin // defaults state_next = state_reg; tx_done_tick = 1'b0; baud_next = baud_reg; n_next = n_reg; d_next = d_reg; tx_next = tx_reg; case (state_reg) idle: // idle, hold tx line high, until tx_start asserted... begin tx_next = 1'b1; if (tx_start) // when tx_start input asserted begin state_next = start; // go to start state baud_next = 0; // reset baud reg to 0 d_next = tx_data; // load data to transit to data register end end start: // start, hold line low for 16 baud_ticks begin tx_next = 1'b0; // hold tx low if(baud_tick) baud_next = baud_reg + 1; // increment baud_reg every tick else if(baud_reg == 16) // once 16 baud_ticks counted... begin state_next = data; // go to data state baud_next = 0; // reset baud counter n_next = 0; // and data bit counter to 0 end end data: begin tx_next = d_reg[0]; // transmit LSB of data reg if(baud_tick) baud_next = baud_reg + 1; // increment baud_reg every tick else if(baud_reg == 16) // once 16 baud_ticks counted... begin d_next = d_reg >> 1; // right shift data reg by 1, LSB is next bit to transfer out baud_next = 0; // reset baud_tick counter n_next = n_reg + 1; // increment data bit counter end else if(n_reg == 8) // once 8 data bits have been shifted out and transfered... state_next = stop ; // move to stop state end stop: // stop, hold line high for 16 baud_ticks begin tx_next = 1'b1; // hold tx high if(baud_tick) baud_next = baud_reg + 1; // increment baud_reg every tick else if(baud_reg == 16) // once 16 baud_ticks counted begin state_next = idle; // go to idle state tx_done_tick = 1'b1; // assert transfer done tick end end endcase end // output assign tx = tx_reg; endmodule
The circuit takes an input to start transmitting data, a baud tick from an external baud tick generator, as well as the 8 bits to be transmitted. The circuit has an output tx line, and a line for the transmission done tick. A FSMD is again implemented to achieve the transmitter algorithm that we specified.
Next we will implement the complete UART controlled stopwatch circuit. We will use the stopwatch circuit from the previous post, as well as the seven-segment multiplexing circuit.
The master circuit will also implement a FSMD, with an idle state that processes received ASCII characters from the FPGA UART receiver circuit. A c/C will clear the minSecTimer, a g/G will start the timer, a s/S will stop it, and an r/R will move to the next FSM state to start transmitting the time through the FPGA UART transmitter circuit to the PC UART receiver. Five states follow the idle state, two for the minute values to be transmitted, one for a ‘.’ to be transmitted, and two for the second values to be transmitted.
It will also have the 16*19200 baud rate tick generator for the receiver and transmitter circuits. Since our system clock runs at 50Mhz, to tick 16*19200 = 307200 times a second, we will need to count 50E6/307200 = 163 system clock cycles before outputting a tick.
Let’s look at the code:
// uart controlled stopwatch module uart_stopwatch ( input wire clk, reset, // clock reset input lines input wire rx, // receive output wire tx, // transmit output wire [3:0] an, // enable for 4 displays output wire [7:0] sseg // led segments ); // internal signal declarations wire [3:0] d3, d2, d1, d0; // routing connections from BCD values of minSecTimer to display and for tx to PC reg [7:0] r_reg; // baud rate generator register wire [7:0] r_next; // baud rate generator next state logic wire tick; // baud tick for uart_rx & uart_tx wire [7:0] rx_data_out; // routing path for received ascii data reg tx_start, go_sw, stop_sw, clear_sw; // registers to control minSecTimer and start tx to PC reg [2:0] state_reg, state_next; // FSM state register and next state logic wire rx_done_tick, tx_done_tick; // done ticks for rx and tx circuits reg [7:0] tx_d_next, tx_d_reg; // register for tx output to PC // tx_d register for tx data to PC always @(posedge reset, posedge clk) if(reset) tx_d_reg <= 8'b00000000; else tx_d_reg <= tx_d_next; // FSM states localparam [2:0] idle = 3'b000, transmit_d3 = 3'b001, transmit_d2 = 3'b010, transmit_dot = 3'b011, transmit_d1 = 3'b100, transmit_d0 = 3'b101; // FSM register always @(posedge reset, posedge clk) if(reset) state_reg <= idle; else state_reg <= state_next; // FSM next state logic always @* begin // defaults state_next = state_reg; tx_d_next = 8'b00000000; tx_start = 1'b0; clear_sw = 1'b0; go_sw = 1'b0; stop_sw = 1'b0; case(state_reg) idle: begin if(rx_done_tick) // when rx data received and ready, check value begin if(rx_data_out == 8'b01000011 || rx_data_out == 8'b01100011) // if 'c' or 'C', enable clear_sw register to minSecTimer clear_sw = 1'b1; else if(rx_data_out == 8'b01000111 || rx_data_out == 8'b01100111) // if 'g' or 'G', enable go_sw register to minSecTimer go_sw = 1'b1; else if(rx_data_out == 8'b01010011 || rx_data_out == 8'b01110011) // if 's' or 'S', enable stop_sw register to minSecTimer stop_sw = 1'b1; else if(rx_data_out == 8'b01010010 || rx_data_out == 8'b01110010) // if 'r' or 'R', load tx_d next state, go to next FSM state begin tx_d_next = {4'b0011, d3}; state_next = transmit_d3; end end end transmit_d3: begin tx_start = 1'b1; // begin sending data across tx if(tx_done_tick) // when done begin tx_d_next = {4'b0011, d2}; // load next data state_next = transmit_d2; // go to next state end end transmit_d2: begin tx_start = 1'b1; if(tx_done_tick) begin tx_d_next = 8'b00101110; state_next = transmit_dot; end end transmit_dot: begin tx_start = 1'b1; if(tx_done_tick) begin tx_d_next = {4'b0011, d1}; state_next = transmit_d1; end end transmit_d1: begin tx_start = 1'b1; if(tx_done_tick) begin tx_d_next = {4'b0011, d0}; state_next = transmit_d0; end end transmit_d0: begin tx_start = 1'b1; if(tx_done_tick) // when final tx data is sent state_next = idle; // go back to idle end endcase end // register for oversampling baud rate generator always @(posedge clk, posedge reset) if(reset) r_reg <= 0; else r_reg <= r_next; // next state logic, mod 163 counter assign r_next = r_reg == 163 ? 0 : r_reg + 1; // tick high once every 163 clock cycles, for 19200 baud assign tick = r_reg == 163 ? 1 : 0; // instantiate uart rx ciruit uart_rx uart_rx_unit (.clk(clk), .reset(reset), .rx(rx), .baud_tick(tick), .rx_done_tick(rx_done_tick), .rx_data(rx_data_out)); // instantiate uart tx circuit uart_tx uart_tx_unit (.clk(clk), .reset(reset), .tx_start(tx_start), .baud_tick(tick), .tx_data(tx_d_reg), .tx_done_tick(tx_done_tick), .tx(tx)); // instantiate stopwatch timer circuit minSecTimer timer (.clk(clk), .go(go_sw), .stop(stop_sw), .clr(clear_sw), .d3(d3), .d2(d2), .d1(d1), .d0(d0)); // instantiate seven segment display circuit displayMuxBasys display_unit (.clk(clk), .hex3(d3), .hex2(d2), .hex1(d1), .hex0(d0), .dp_in(4'b1011), .an(an), .sseg(sseg)); endmodule
The tx and rx lines are routed to GPIO on the FPGA board that are then connected with wires to the corresponding rx and tx inputs on the USB-Serial converter, which we then connect to a PC with a USB cable. The FPGA ground is also connected to the ground of the USB-Serial converter with a wire.
We then need a serial terminal such as Bray’s Terminal to send and receive data on the PC side. We have to be sure to set the baud rate to 19200, data bits to 8, no parity bit, and 1 stop bit. To make the experience a little nicer, I wrote a short Python program to communicate with the FPGA using pyserial.
# Serial interface for FPGA implementation of UART controlled stopwatch # setup serial port import serial PORT = 'COM8' BAUD = 19200 s = serial.Serial(PORT, BAUD) s.flush() # variables to hold received user input and received timestamp from FPGA c = '' r = '' # welcome message print("Stopwatch Control Interface:\nEnter g or G to go\nEnter s or S to stop\nEnter c or C to clear\nEnter r or R to receive time\nEnter e or E to exit\n") # event loop while(True): # get user input c = input() # user wants to exit if(c == 'e' or c == 'E'): s.close() print("goodbye!\n") break # start stopwatch elif(c == 'g' or c == 'G'): print("start\n") # write encoded input over UART s.write(c.encode()) # stop stopwatch elif(c == 's' or c == 'S'): print("stop\n") # write encoded input over UART s.write(c.encode()) # clear stopwatch elif(c =='c' or c == 'C'): print("clear\n") # write encoded input over UART s.write(c.encode()) # received timestamp elif(c == 'r' or c == 'R'): # write encoded input over UART s.write(c.encode()) print("receive\n") #reset received timestamp r = '' # read in 5 bytes, decode to char and append to r for i in range(5): r += (s.read(1)).decode() print(r) # invalid input else: print("invalid input\n")
The pyserial module needs to be installed for Python 3, which can be done from the command line with the command: pip install pyserial .
Above is a demonstration of the system being controlled with the python program.
Here is a link to the full source code on GitHub along with the UCF for the Basys 2 that was used.