This time, we will design a simple communication IP, UART (Universal Asynchronous Receiver/Transmitter).
UART overview
Characteristic
Unlike I2C or SPI, UART communication is asynchronous and doesn't use a clock. What's the difference between asynchronous and synchronous communication?
Advantage
- Since it does not use a clock, there is no need for a clock pin and no need for a slave select pin, only two pins are used to transmit and receive data.
- Supports full-duplex communication as Tx and Rx are separated
- It has been used for a long time, so it is not complicated, the related documentation is well organized, and many devices are supported.
Disadvantage
- Because it is 1:1 communication, the more controllers there are, the more wires are needed.
- The baud rate must be set correctly for communication, otherwise data loss occurs.
The UART control module has Rx/Tx pins, so the signal for sending data should be connected to Tx, and the signal for receiving data should be connected to Rx.
Protocol
- Baud rate
UART communication is an asynchronous, full-duplex communication method that doesn't use a clock signal for synchronization. Therefore, the data transmission speed (baud rate) must be determined before transmitting or receiving data. A commonly used baud rate is 38400, which equals 38.4 Kbps.
- Start bit
Now that we've set the baud rate, how do the two modules sending and receiving data know when communication has begun? UART communication uses a "Start bit." Before communication begins, this bit remains high, and when communication begins, it's lowered to low, allowing the receiving module to know that communication has begun.
- Stop bit
If the high state remains after exchanging 8-bit data, it means the communication is finished. This is called a stop bit and can be set to 1 or 2 bits.
- Parity bit
The photo above shows a program called Tera Term, which facilitates serial communication between a PC and a user. Before communicating, the settings are as shown above. The baud rate is set to 38,400, 1 packet is 8 bits, and 1 stop bit is set.
And you also need to set a parity bit. The parity bit is an optional bit that the user can choose to use or not use. Its purpose is to check for errors during communication. So why do errors occur?
For controllers used in SoC, the baud rate is set through internal register settings.
This UART controller module uses a 10MHz clock and a desired baud rate of 38400. The register setting value is determined by dividing the controller's clock frequency by the desired baud rate. The result is rounded and input to the module, but this process inevitably introduces errors.This can lead to communication errors.
To summarize, UART is an asynchronous communication that does not use a clock, so it does not require a clock pin, but errors can occur, so the user must optionally set the parity bit.
Specification
First, let me make it clear: the UART I'm designing differs from the IP actually used. I'll explainthis in detail
Block diagram
There is an APB interface and a CLK gen (clock generator) is attached to the Tx/Rx controller. The baud rate is set in the CLK gen.
Register map
The register map is set as follows:
- CTRL register (address: 0x0)
| Signal name | R/W | Default value | Bit |
|---|---|---|---|
| Reserved | – | – | 31:21 |
| Pre_scale | RW | 0x0 | 20:18 |
| Bit_mult | RW | 0x0 | 17:13 |
| Bit_div | RW | 0x0 | 12:4 |
| Reserved | – | – | 3 |
| Two_stop_en | RW | 0x0 | 2 |
| Parity_en | RW | 0x0 | 1 |
| Enable | RW | 0x0 | 0 |
- Status register (address: 0x4)
| Signal name | R/W | Default value | Bit |
|---|---|---|---|
| Reserved | – | – | 31:2 |
| Rx_complete | W1C | 0x0 | 1 |
| Tx_complete | W1C | 0x0 | 0 |
Actually, you should add a parity error to the status. When communicating via UART with parity, the rx controller should perform a parity check, and if the received parity does not match the calculated parity, an error should be generated and the CPU should be notified. But I left it out because it was annoying;;;
It would be nice to try adding a parity check function directly to the RX controller!!
- Tx data register (address: 0x8)
| Signal name | R/W | Default value | Bit |
|---|---|---|---|
| Reserved | – | – | 31:8 |
| Tx_data | RW | 0x0 | 7:0 |
- Rx data register (address: 0xC)
| Signal name | R/W | Default value | Bit |
|---|---|---|---|
| Reserved | – | – | 31:8 |
| Rx_data | RO | 0x0 | 7:0 |
UART RTL design
APB interface RTL design
Let's design the APB interface based on the register map above.
module uart_apb (
//APB Interface
input wire pclk
,input wire presetn
,input wire penable
,input wire psel
,input wire [ 5:2] paddr
,input wire pwrite
,input wire [31:0] pwdata
,output wire [31:0] prdata
//Baud rate
,output wire [ 8:0] bit_div
,output wire [ 2:0] pre_scale
,output wire [ 4:0] bit_mult
//Register Interface
,input wire tx_complete
,input wire rx_complete
,output wire tx_clr
,output wire rx_clr
,output wire uart_en
,output wire parity_en
,output wire stop_en
,input wire [ 7:0] uart_rxdata
,output wire [ 7:0] uart_txdata
);
//===================================================================
// Local Parameters
//===================================================================
localparam INVALID_DATA = 32'hDEAD_DEAD;
//===================================================================
// Internal Signals
//===================================================================
wire we = psel and ~penable and pwrite;
wire re = psel and ~penable and ~pwrite;
reg [31:0] RD ;
//===================================================================
// Address Decode
//===================================================================
wire ctrl = (paddr[ 5:2] == 4'h0);
wire st = (paddr[ 5:2] == 4'h1);
wire tx_data = (paddr[ 5:2] == 4'h2);
wire rx_data = (paddr[ 5:2] == 4'h3);
//===================================================================
// Write Enable
//===================================================================
wire we_ctrl = we and ctrl;
wire we_data = we and tx_data;
wire we_st = we and st;
//===================================================================
// Register Files
//===================================================================
reg r_enable;
always @(posedge pclk or negedge presetn) begin
if(!presetn) r_enable <= 1'b0;
else if (tx_clr) r_enable <= 1'b0;
else if (we_ctrl) r_enable <= pwdata[0];
else r_enable <= r_enable;
end
reg r_parity;
always @(posedge pclk or negedge presetn) begin
if(!presetn) r_parity <= 1'b0;
else if (we_ctrl) r_parity <= pwdata[1];
else r_parity <= r_parity;
end
reg r_stop;
always @(posedge pclk or negedge presetn) begin
if(!presetn) r_stop <= 1'b0;
else if (we_ctrl) r_stop <= pwdata[2];
else r_stop <= r_stop;
end
reg [16:0] r_bitcfg;
always @(posedge pclk or negedge presetn) begin
if (!presetn) r_bitcfg <= 17'h0;
else if (we_ctrl) r_bitcfg <= pwdata[21:4];
else r_bitcfg <= r_bitcfg;
end
reg [ 7:0] r_txdata;
always @(posedge pclk or negedge presetn) begin
if(!presetn) r_txdata <= 8'h0;
else if (we_data) r_txdata <= pwdata[7:0];
else r_txdata <= r_txdata;
end
//===================================================================
// Read Decode
//===================================================================
always @(*) begin
if(re) begin
case (paddr[5:2])
4'h0 : RD = {11'h0, r_bitcfg, 1'b0, r_stop, r_parity, r_enable};
4'h1 : RD = {30'b0, rx_complete, tx_complete};
4'h2 : RD = {24'b0, r_txdata};
4'h3 : RD = {24'b0, uart_rxdata};
default : RD = INVALID_DATA;
endcase
end
else RD = INVALID_DATA;
end
assign prdata = RD;
//===================================================================
// Output Assign
//===================================================================
assign bit_div = r_bitcfg[ 8: 0];
assign bit_mult = r_bitcfg[13: 9];
assign pre_scale = r_bitcfg[16:14];
assign parity_en = r_parity;
assign stop_en = r_stop;
assign uart_en = r_enable;
assign uart_txdata = r_txdata;
assign tx_clr = we_st and pwdata[0];
assign rx_clr = we_st and pwdata[1];
endmoduleI will explain further in the next article.
References: Realtek UART