This article will conclude the UART RTL design.
UART RTL design
Rx controller RTL design
If you understand the Tx controller, the Rx controller will be easy to understand.
module uart_rx_ctrl (
input wire pclk
,input wire presetn
,input wire uclk_en
,input wire uart_in
,input wire complete_clr
,input wire parity_en
,input wire stop_en
,output wire [ 7:0] uart_data
,output wire complete
,output wire uart_rx
);
//===================================================================
// Local Parameters
//===================================================================
localparam IDLE = 3'h0,
START = 3'h1,
DATA = 3'h2,
PARITY = 3'h3,
STOP = 3'h4,
TRANSFINISH = 3'h5;
reg [ 2:0] r_cur_st;
reg [ 2:0] r_nxt_st;
reg [ 7:0] r_bitcnt;
reg r_complete;
reg [ 7:0] r_shift;
wire data_end;
wire uart_init;
//rx_init
reg r_uart_in;
always @(posedge pclk or negedge presetn) begin
if (!presetn) r_uart_in <= 1'h1;
else r_uart_in <= uart_in;
end
//cur_st
always @(posedge pclk or negedge presetn) begin
if (!presetn) r_cur_st <= IDLE;
else if (complete_clr) r_cur_st <= IDLE;
else r_cur_st <= r_nxt_st;
end
//FSM
always @(*) begin
case (r_cur_st)
IDLE : begin
if (uart_init) begin
r_nxt_st <= START;
end
else r_nxt_st <= IDLE;
end
START : begin
if (uclk_en) begin
r_nxt_st <= DATA;
end
else r_nxt_st <= START;
end
DATA : begin
if (data_end & parity_en) begin
r_nxt_st <= PARITY;
end
else if (data_end & !parity_en & stop_en) begin
r_nxt_st <= STOP;
end
else if (data_end & !parity_en & !stop_en) begin
r_nxt_st <= TRANSFINISH;
end
else r_nxt_st <= DATA;
end
PARITY : begin
if (stop_en & uclk_en) begin
r_nxt_st <= STOP;
end
else if (uclk_en) begin
r_nxt_st <= TRANSFINISH;
end
else r_nxt_st <= PARITY;
end
STOP : begin
if (uclk_en) begin
r_nxt_st <= TRANSFINISH;
end
else r_nxt_st <= STOP;
end
TRANSFINISH : begin
if (complete_clr) begin
r_nxt_st <= IDLE;
end
else r_nxt_st <= TRANSFINISH;
end
default : begin
r_nxt_st <= IDLE;
end
endcase
end
//BITCNT
wire data_st;
assign data_st = (r_cur_st == DATA);
always @(posedge pclk or negedge presetn) begin
if (!presetn) r_bitcnt <= 8'h0;
else if (complete_clr) r_bitcnt <= 8'h0;
else if (data_st & uclk_en) r_bitcnt <= (r_bitcnt + 1);
else r_bitcnt <= r_bitcnt;
end
//UART rx
always @(posedge pclk or negedge presetn) begin
if (!presetn) r_shift <= 8'h0;
else if (data_st) begin
if (uclk_en) r_shift <= {uart_in,r_shift[7:1]};
else r_shift <= r_shift;
end
else r_shift <= r_shift;
end
//complete intr
always @(posedge pclk or negedge presetn) begin
if (!presetn) r_complete <= 1'b0;
else if (complete_clr) r_complete <= 1'b0;
else if ((r_cur_st == TRANSFINISH) & uclk_en) begin
r_complete <= 1'b1;
end
else r_complete <= r_complete;
end
assign data_end = (r_bitcnt == 8'h8);
assign complete = r_complete;
assign uart_data = r_shift;
assign uart_init = ~uart_in & r_uart_in;
assign uart_rx = (r_cur_st != IDLE);
endmoduleUnlike the Tx controller, the Rx controller does not initiate communication with the APB register setting, but rather the FSM operates with the posedge signal (uart_init) of the signal coming from the external controller.
This module needs one modification. First, it doesn't implement a parity check function. In the DATA state, it should calculate parity based on the input signal, and if the parity doesn't match the input parity in the PARITY state, it should output an error intr.
Finally, there's the uart_rx output, which is a signal indicating that the Rx controller is operating. What is this signal used for?
Rx CLK gen RTL design
There is one thing that is special about CLK gen for Rx. Let's take a look at the RTL code first.
module uart_rx_clkgen (
input wire presetn
,input wire pclk
,input wire uart_rx //From Rx controller
,input wire [ 8:0] bit_div
,input wire [ 2:0] pre_scale
,input wire [ 4:0] bit_mult
,output wire rx_uclk
);
reg r_uclk;
reg [ 8:0] r_prescale;
reg [13:0] r_divisor_1;
reg [22:0] r_divisor_2;
reg [21:0] r_dividercnt;
always @(negedge presetn or posedge pclk) begin
if (!presetn) r_prescale <= 9'd0;
else begin
case (pre_scale)
3'd0 : r_prescale <= 9'd2 ;
3'd1 : r_prescale <= 9'd4 ;
3'd2 : r_prescale <= 9'd8 ;
3'd3 : r_prescale <= 9'd16 ;
3'd4 : r_prescale <= 9'd32 ;
3'd5 : r_prescale <= 9'd64 ;
3'd6 : r_prescale <= 9'd128;
3'd7 : r_prescale <= 9'd256;
default: r_prescale <= r_prescale;
endcase
end
end
//r_divisor_1 = { 2^(pre_scale+1) x (bit_mult + 1) } ]
always @(negedge presetn or posedge pclk) begin
if (!presetn) r_divisor_1 <= 14'd0;
else r_divisor_1 <= r_prescale * (bit_mult + 'b1);
end
//r_divisor_2 = [ (bit_div) x { 2^(pre_scale+1) x (bit_mult + 1) } ]
always @(negedge presetn or posedge pclk) begin
if (!presetn) r_divisor_2 <= 23'd0;
else r_divisor_2 <= r_divisor_1 * bit_div;
end
//half cycle of serial clock = r_divisor_2 / 2
always @(negedge presetn or posedge pclk) begin
if (!presetn) r_dividercnt <= 22'd0;
else if (uart_rx & (r_dividercnt != r_divisor_2))
r_dividercnt <= (r_dividercnt + 'b1);
else r_dividercnt <= 22'd0;
end
wire [22:0] r_divisor_2_half;
assign r_divisor_2_half = r_divisor_2 >> 1;
//serial clock generation
always @(negedge presetn or posedge pclk) begin
if (!presetn) r_uclk <= 'b0;
else if (r_dividercnt < r_divisor_2_half) r_uclk <= 'b0;
else r_uclk <= 'b1;
end
assign rx_uclk = r_uclk;
endmoduleThis module only generates uclk when uart_rx is high (when the Rx controller is running). Why is that?
The Rx controller receives data from an external controller, so its baud rate may not perfectly match the external controller's. In this case, as communication continues, the baud rates of the two controllers will gradually diverge over time.
That's why I designed it so that uclk is generated only when the Rx controller is operating to reset the baud rate difference each time, and the internal counter of the CLK gen is reset when 1-byte communication is finished. I hope you understand;;;
Note that UART Rx receives asynchronous signals from an external source. In this case, careful signal processing is required, which I'll explain in more detail in the next article.
Top integration
Now, let's integrate the designed modules to complete the IP.
module uart (
input wire presetn
,input wire pclk
,input wire pwrite
,input wire psel
,input wire penable
,input wire [ 5:2] paddr
,input wire [31:0] pwdata
,output wire [31:0] prdata
,output wire intr
,output wire uart_out
,input wire uart_in
);
wire tx_uclk;
wire tx_uclk_en;
wire rx_uclk;
wire rx_uclk_en;
//Register signals
wire [ 8:0] bit_div;
wire [ 2:0] pre_scale;
wire [ 4:0] bit_mult;
wire uart_rx;
wire tx_complete;
wire rx_complete;
wire tx_clr;
wire rx_clr;
wire parity_en;
wire stop_en;
wire uart_en;
wire [ 7:0] uart_txdata;
wire [ 7:0] uart_rxdata;
reg r_uart_in;
always @(negedge presetn or posedge pclk ) begin
if (!presetn) begin
r_uart_in <= 1'b1;
end
else begin
r_uart_in <= uart_in;
end
end
reg r_tx_uclk;
always @(negedge presetn or posedge pclk ) begin
if (!presetn) begin
r_tx_uclk <= 1'b0;
end
else begin
r_tx_uclk <= tx_uclk;
end
end
reg r_rx_uclk;
always @(negedge presetn or posedge pclk ) begin
if (!presetn) begin
r_rx_uclk <= 1'b0;
end
else begin
r_rx_uclk <= rx_uclk;
end
end
uart_apb u_apb (
.pclk (pclk )
,.presetn (presetn )
,.penable (penable )
,.psel (psel )
,.paddr (paddr[5:2] )
,.pwrite (pwrite )
,.pwdata (pwdata )
,.prdata (prdata )
,.bit_div (bit_div )
,.pre_scale (pre_scale )
,.bit_mult (bit_mult )
,.tx_complete (tx_complete )
,.rx_complete (rx_complete )
,.tx_clr (tx_clr )
,.rx_clr (rx_clr )
,.uart_en (uart_en )
,.parity_en (parity_en )
,.stop_en (stop_en )
,.uart_txdata (uart_txdata )
,.uart_rxdata (uart_rxdata )
);
uart_tx_clkgen u_tx_clkgen (
.pclk (pclk )
,.presetn (presetn )
,.bit_div (bit_div )
,.pre_scale (pre_scale )
,.bit_mult (bit_mult )
,.tx_uclk (tx_uclk )
);
uart_tx_ctrl u_tx_ctrl (
.pclk (pclk )
,.presetn (presetn )
,.uclk_en (tx_uclk_en )
,.parity_en (parity_en )
,.stop_en (stop_en )
,.uart_en (uart_en )
,.uart_data (uart_txdata )
,.complete_clr (tx_clr )
,.complete (tx_complete )
,.uart_out (uart_out )
);
uart_rx_clkgen u_rx_clkgen (
.pclk (pclk )
,.presetn (presetn )
,.uart_rx (uart_rx )
,.bit_div (bit_div )
,.pre_scale (pre_scale )
,.bit_mult (bit_mult )
,.rx_uclk (rx_uclk )
);
uart_rx_ctrl u_rx_ctrl (
.pclk (pclk )
,.presetn (presetn )
,.uclk_en (rx_uclk_en )
,.uart_in (r_uart_in )
,.complete_clr (rx_clr )
,.parity_en (parity_en )
,.stop_en (stop_en )
,.uart_data (uart_rxdata )
,.complete (rx_complete )
,.uart_rx (uart_rx )
);
assign tx_uclk_en = tx_uclk & ~r_tx_uclk;
assign rx_uclk_en = ~rx_uclk & r_rx_uclk;
assign intr = (tx_complete | rx_complete);
endmoduleWhat you should note here is that the input signal to the Rx controller is not directly connected to the signal coming from outside, but r_uart_in is used, which is delayed by 1 clk by filling in pclk.
module uart (
,input wire uart_in
);
reg r_uart_in;
always @(negedge presetn or posedge pclk ) begin
if (!presetn) begin
r_uart_in <= 1'b1;
end
else begin
r_uart_in <= uart_in;
end
end
uart_rx_ctrl u_rx_ctrl (
,.uart_in (r_uart_in )This is because the uart_in coming from outside must be aligned with the pclk of this module (a process of aligning it with pclk sync is required).
I tried UART controller RTL design like this.
References: Realtek UART