이 글로 UART RTL design을 마무리하겠습니다.
UART RTL design
Rx controller RTL design
Tx controller를 이해하셨다면 Rx controller는 이해하기 쉬우실 겁니다.
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);
endmoduleRx controller는 Tx controller와 다르게 APB register setting으로 통신을 시작하는 것이 아니라, 외부 controller로부터 들어온 signal의 posedge signal(uart_init)로 FSM이 동작합니다.
이 module은 한 가지 수정이 필요한데요, 먼저 parity check 기능이 구현되어 있지 않습니다. DATA state일 때 input 된 signal로 parity를 계산하고 만약 PARITY state에 input 된 parity와 일치하지 않는다면 error intr를 내보내야 합니다.
마지막으로 output으로 uart_rx가 있는데요, Rx controller가 동작하고 있음을 나타내는 signal입니다. 이 signal은 어디에 사용될까요?
Rx CLK gen RTL design
Rx 용 CLK gen에는 한 가지 특징이 있습니다. 우선 RTL code를 살펴볼까요?
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;
endmodule이 module은 uart_rx가 high일 때(Rx controller가 작동할 때)만 uclk를 생성합니다. 왜 그럴까요?
Rx controller는 외부의 controller에서 data를 받습니다, 그렇기 때문에 외부 controller와 baud rate가 완전히 일치하지 않을 수 있습니다. 이럴 경우, 통신을 계속 진행하면 시간이 지날수록 두 controller의 baud rate는 점점 차이가 납니다.
이렇기 때문에 baud rate의 차이를 매번 초기화시켜 주기 위해 Rx controller가 작동할 때만 uclk를 생성하고, 1-byte 통신이 끝나면 CLK gen 내부 counter가 reset 방식으로 설계했습니다. 이해가 되셨으면 좋겠네요;;;
참고로, UART Rx는 외부에서 비동기 신호(Asynchronous)를 받게 됩니다. 이런 경우 신호 처리에 주의가 필요한데요, 다음 글에서 좀 더 자세히 설명하도록 하겠습니다.
Top integration
이제 설계한 모듈을 integration 하여 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);
endmodule여기서 주의할 점은, Rx controller에 들어갈 input signal은 외부에서 들어온 signal을 바로 연결한 것이 아니라 pclk로 채서 1 clk 미룬 r_uart_in을 사용했습니다.
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 )이는 외부에서 들어온 uart_in을 이 module의 pclk에 맞춰야 하기 때문입니다(pclk sync에 맞추는 과정이 필요한 겁니다).
이렇게 UART controller RTL design을 해봤습니다.
참고: Realtek UART