이번에는 간단한 통신 IP인 UART(Universal Asynchronous Receiver/Transmitter)를 설계해보겠습니다.
UART overview
특징
UART 통신은 I2C나 SPI와 달리 클럭(clock)를 사용하지 않는 비동기 통신입니다. 비동기 통신과 동기 통신은 어떤 차이가 있을까요??
장점
- 클럭을 쓰지 않기 때문에 클럭 핀이 필요 없고 slave select 핀도 필요 없음, 데이터 송수신하는 2개의 핀만 사용
- Tx, Rx가 나뉘어져 있어서 전이중 통신 지원
- 오랫동안 사용되었기에 복잡하지 않고 관련 문서도 잘 정리되어 있음, 많은 장치가 지원
단점
- 1:1 통신이기 때문에 controller가 많아질수록 wire가 많이 필요함
- 통신을 위해 전송속도(baud rate)를 잘 맞춰줘야 하고 틀리면 데이터 손실이 일어남
UART control 모듈에는 Rx/Tx 핀이 있어서 데이터가 나가는 신호는 Tx, 데이터를 수신하는 신호는 Rx에 연결하면 됩니다.
Protocol
- Baud rate
UART 통신은 비동기 방식의 전이중 통신으로 동기 신호인 클럭을 사용하지 않기 때문에 데이터 송/수신 전에 데이터 전송 속도(baud rate)를 정해야 합니다. 많이 사용하는 baud rate는 38400인데요, 이는 38.4 Kbps를 의미합니다.
- Start bit
Baud rate를 정했지만, 데이터를 주고받는 두 모듈은 통신의 시작을 어떻게 알 수 있을까요?? UART 통신에는 Start bit가 존재합니다. 통신하기 전에는 High 상태를 유지하다가 통신이 시작될 때, Low로 내려주면 데이터를 받는 쪽에서 통신이 시작됨을 알 수 있게 됩니다.
- Stop bit
8-bit의 데이터를 주고받은 뒤 High 상태를 유지하면 통신이 끝났다는 것을 의미합니다. 이는 Stop bit라고 하는데 1~2-bit로 설정할 수 있습니다.
- Parity bit
위의 사진은 PC와 유저가 시리얼 통신을 할 수 있게 도와주는 Tera term이라는 프로그램입니다. 통신하기 전에 위와 같은 설정을 하게 되는데요, baud rate는 38400, 1 패킷은 8 -bit, 1 stop bit로 설정했습니다.
그리고 parity bit도 설정해야 합니다. Parity bit는 유저가 옵션으로 설정하는 비트입니다, 안 쓸 수도 있고 쓸 수도 있는 거죠. 역할은 통신 중 오류가 있는지 검사하는 것입니다. 그렇다면 왜 오류가 발생하는 걸까요??
SoC에서 사용되는 controller의 경우 내부 레지스터 세팅을 통해 baud rate를 설정하게 됩니다.
이 UART controller 모듈의 사용되는 클럭이 10MHz이고 원하는 baud rate는 38400입니다. Controller의 사용되는 클럭의 주파수를 원하는 baud rate로 나눠 register 세팅 값을 정합니다. 계산 결과를 반올림해서 모듈에 입력하는데 여기서 어쩔 수 없이 오차가 발생하게 됩니다. 이에 따라 통신 중 오류가 발생할 수 있습니다.
정리하자면 UART는 클럭을 사용하지 않는 비동기통신이어서 클럭 핀이 필요하지 않지만, 오차가 발생할 수 있어서 유저가 parity bit를 optional 하게 설정해야 합니다.
Specification
먼저 분명히 할 것은, 제가 설계하는 UART는 실제 사용하는 IP와는 차이점이 있습니다. 이에 대해서는 차근차근 설명하도록 하겠습니다.
Block diagram
APB interface가 있고 Tx/Rx controller에 CLK gen(clock generator)가 붙어있습니다. CLK gen에서 baud rate를 설정해 줍니다.
Register map
Register map은 다음과 같이 설정했습니다.
- 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 |
사실, status에 parity error를 추가해야 합니다. Parity가 있는 UART 통신일 때, rx controller에서 parity check를 하고 수신된 parity와 계산된 parity가 일치하지 않으면 error를 발생시키고 CPU에 이를 알려야 합니다. 근데 귀찮아서 뺐습니다;;;
직접 rx controller에 parity check 기능을 넣어보는 것도 좋겠네요!!
- 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
위의 register map을 기반으로 APB interface를 설계해 봅시다.
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 & ~penable & pwrite;
wire re = psel & ~penable & ~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 & ctrl;
wire we_data = we & tx_data;
wire we_st = we & 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 & pwdata[0];
assign rx_clr = we_st & pwdata[1];
endmodule다음 글에서 이어서 설명하겠습니다.
참고: Realtek UART