Skip to content

Asynchronous serial transmitter unit

Juan Gonzalez-Gomez edited this page Apr 20, 2016 · 42 revisions

Access to the repo

Introduction

Asynchronous serial transmitter unit for the Icestick board, synthetized with Opensource Icestorm tools

Features

  • Baudrates: 300, 600, 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200
  • Clock frequency: 12Mhz
  • Start bits: 1
  • Data bits: 8
  • Parity: None
  • Stop bits: 1
  • Description language: Verilog
  • Toolchain: Opensource: Yosys, Arachne-pnr, Icestorm project

Serial packages

Serial packages consist of three parts: the start bit, the 8-bit data and the stop bit

Example of the serial transmission of the K character (ASCII 0x4B: Binary: 01001011)

UART-TX details

The serial transmitter is encapsulated in the uart-tx entity

Ports

The transmitter unit has 4 inputs and 2 outputs:

  • Inputs:

    • clk: System clock (12MHz in the ICEstick board)
    • rstn: Active low. When rstn is 0, the serial unit is reset (synchronous reset)
    • start: Start the transmission. When it is a 1, the character from the input data is captured and the transmission begins
    • data: 8-bit data to transmit
  • Outputs:

    • tx: Serial output. The data is sent as a serial package. It is connected to the transmission line
    • ready: Transmitter status. When ready is 1, the unit is ready to transmit. A new character can be sent. when it is 0, the unit is busy transmitting the previous character.

Chronogram

The step for transmitting a character are the following:

  1. Wait until the ready signal is 1
  2. Place the character in the data input
  3. Set the start input to 1 (at least for 1 clock cycle)

After that, the unit clears the ready signal and start transmitting the character

Block diagram

The implementation of the transmitter is shown in the given block diagram

It consist of the following parts:

  • Data register (8 bits): For storing the data to be sent
  • Shift register (10 bits): For storing the serial package and sending its bits one by one. When the load signal is 1, it is loaded with a new character to transmit (8 bits). The two bits less significant are initialized with the binary value 01 (a start bit and a idle bit). When transmitting (load = 0), the less significant line is sent to the transmission line and then the register is shift right. A 1 is introduced in the most significant bit. It is only done when the clk_baud signal is 1. This signals determines the exact time when the next bit should be sent
  • Baud generator: It generates a pulse of 1 cycle periodically, according to the baud rate configured
  • Bit counter: It counts the bits that have already been transmitted. It only counts when load is 0
  • controller: The finite state machine that generates the load and baudgen control signals for controlling the transmitter
  • D flip-flops: There are two D flip-flops for registering both signals tx and start

Controller

The transmitter controller is a finite state machine with three states:

  • IDLE: There is no transmission. The unit is not being used. The ready signal is 1. When the start signal is set to 1, it changes to the START state
  • START: The transmission is started. The shift register is loaded with the data to be send (plus the start bit). The baud generator is started. After one clock cycle it automatically moves to the next state
  • TRANS: Transmission state. The 10-bits serial packages is sent. When the bit counter is 11, it means that 10 bits have been already transmitted and the state changes to IDLE

Using UART-tx in Verilog

This is a generic code for using the UART-tx in your designs in verilog. Of course, some details can change depending on the particular implementation (as for example the definitions of the wires)

//-- Baudrate definitions
`include "baudgen.vh"

//-- Definition of wires to connect to the unit
wire clk;
wire rstn;
wire start;
wire ready;
wire tx;
wire [7:0] data;

//-- UART_TX instantiation
//-- Values for the BAUDRATE:
//-- `B115200, `B57600, `B38400, `B19200, `B9600, `B4800, `B2400, `B1200, `B600, `B300
uart_tx #(.BAUDRATE(`B115200))
  TX0 (
    .clk(clk),
    .rstn(rstn),
    .data(data),
    .start(start),
    .ready(ready),
    .tx(tx)
  );

Examples

Two examples in verilog on how to use the UART-tx unit are shown

txchar.v: Transmitting one character continuously

This is a hello world example. The UART-tx unit is instantiated and configured at 115200 baudrate. The character "A" is wired to the data port and the signal start is set to 1, so that it will send the character "A" continuously. The signal ready is NOT used (therefore it is not declared in the instantiation)

`default_nettype none
`include "baudgen.vh"

//-- Top entity
module txchar (
          input wire clk,   //-- System clock
          input wire rstn,  //-- Reset (active low)
          output wire tx    //-- Serial data output
);

//-- Serial Unit instantation
uart_tx #(
    .BAUDRATE(`B115200)  //-- Set the baudrate

  ) TX0 (
    .clk(clk),
    .rstn(rstn),
    .data("A"),    //-- Fixed character to transmit (always the same)
    .start(1'b1),  //-- Start signal always set to 1
    .tx(tx)
);                 //-- Port ready not used


endmodule

For simulating, execute the command:

$ make sim

The simulation in gtkwave is shown:

In total 3 characters "A" are sent in simulation (there are 30 pulses in the clk_baud signal). The A character is 0x41 in hexadecimal and 01000001 in binary. The serial package is ten bits: the start bit (0) in the right and the stop bit (1) in the left. In binary it is: 1010000010 It is transmitted in the reverse orden (first the less significant bit), so the bits that can be seen in the simulation are: 0100000101, repeated three times

For synthesizing the example, execute the following command:

$ make sint

The resources used by the example are:

Resource used
PIOs 5 / 96
PLBs 12 / 160
BRAMs 0 / 16

For uploading into the ICEstick execute:

$ make prog

For testing, launch the gtkterm application (or any other serial terminal program) and deactivate the DTR signal by pressing F7 (or the option control signals / toggle DTR from the menu bar). The Icestick starts to send the A character, filling the terminal completely.

tx-str.v: Transmitting a string

Example on how to transmit a string using the uart-tx unit. The reset is controlled by means of the DTR signal. Every time the circuit is reset, it sends the "Hello!.." string and finish. A finite state machine is used to send the string

`default_nettype none
`include "baudgen.vh"

//-- Top entity
module txstr #(
          parameter BAUDRATE = `B115200
)(
          input wire clk,   //-- System clock
          input wire rstn,  //-- Reset (active low)
          output wire tx    //-- Serial data output
);


//-- Serial Unit instantation
uart_tx #(
    .BAUDRATE(BAUDRATE)  //-- Set the baudrate

  ) TX0 (
    .clk(clk),
    .rstn(rstn),
    .data(data),
    .start(start),
    .tx(tx),
    .ready(ready)
);

//-- Connecting wires
wire ready;
reg start = 0;
reg [7:0] data;

//-- Multiplexer with the 8-character string to transmit
always @*
  case (char_count)
    8'd0: data <= "H";
    8'd1: data <= "e";
    8'd2: data <= "l";
    8'd3: data <= "l";
    8'd4: data <= "o";
    8'd5: data <= "!";
    8'd6: data <= ".";
    8'd7: data <= ".";
    default: data <= ".";
  endcase

//-- Characters counter
//-- It only counts when the cena control signal is enabled
reg [2:0] char_count;
reg cena;                //-- Counter enable

always @(posedge clk)
  if (!rstn)
    char_count = 0;
  else if (cena)
    char_count = char_count + 1;


//--------------------- CONTROLLER

localparam INI = 0;
localparam TXCAR = 1;
localparam NEXTCAR = 2;
localparam STOP = 3;

//-- fsm state
reg [1:0] state;
reg [1:0] next_state;

//-- Transition between states
always @(posedge clk) begin
  if (!rstn)
    state <= INI;
  else
    state <= next_state;
end

//-- Control signal generation and next states
always @(*) begin
  next_state = state;
  start = 0;
  cena = 0;

  case (state)
    //-- Initial state. Start the trasmission
    INI: begin
      start = 1;
      next_state = TXCAR;
    end

    //-- Wait until one car is transmitted
    TXCAR: begin
      if (ready)
        next_state = NEXTCAR;
    end

    //-- Increment the character counter
    //-- Finish when it is the last character
    NEXTCAR: begin
      cena = 1;
      if (char_count == 7)
        next_state = STOP;
      else
        next_state = INI;
    end

  endcase
end


endmodule

For simulating, execute the command:

$ make sim2

The simulation in gtkwave is shown:

It can be seen that there are 8 pulses in the start signal (one per character sent). After the last character is sent (char_counter equal to 7) the fsm enters the last state (3) and finish

For synthesizing the example, execute the following command:

$ make sint2

The resources used by the example are:

Resource used
PIOs 7 / 96
PLBs 20 / 160
BRAMs 0 / 16

For uploading into the ICEstick execute:

$ make prog2

For testing, launch the gtkterm application (or any other serial terminal program) and deactivate the DTR signal by pressing F7 (or the option control signals / toggle DTR from the menu bar). The Icestick will send the string "Hello!.."

Baudgen python script

The file baudgen.vh which contains the divisors for generating the standad baudrates, has been created using the baudgen.py python 3 script. Edit this file and change the clock frequency to adapt it to your own system clock

This is how it is used:

$ python3 baudgen.py > test.vh

$ cat test.vh
`define B115200 104
`define B57600 208
`define B38400 312
`define B19200 625
`define B9600 1250
`define B4800 2500
`define B2400 5000
`define B1200 10000
`define B600 20000
`define B300 40000

The python script is:

def divisor(baudrate):
    """Calculate the divisor for generating a given baudrate"""
    CLOCK_HZ = 12000000     #--- Change it to fit your board system clock frequency
    return round(CLOCK_HZ / baudrate)

if __name__ == "__main__":

    # -- List with the standard baudrates
    baudrates = [115200, 57600, 38400, 19200, 9600, 4800, 2400, 1200, 600, 300]

    # -- Calcultate their divisors when using a frequency of CLOCK_HZ
    for baudrate in baudrates:
        print("`define B{} {}".format(baudrate, divisor(baudrate)))

Author

Juan González Gómez (Obijuan)

License

Licensed under a GPL v3 (code) and a Creative Commons Attribution-ShareAlike 4.0 International License (figures and images)

Credits

  • Clifford Wolf, Mathias Lasser. Project IceStorm Thanks!
  • Although this transmitter has been written from the scratch, it has been inspired by the one developed in the swapforth proyect by James Bowman. Thanks!
  • BQ sponsored this project from 2015-07-01 to 2016-04-14. Thanks!

More info