[VerilogHDL] UART Rx, Tx 최종(Oversampling)VerilogHDL/Study2024. 5. 22. 00:26
Table of Contents
1. UART(Universal Asynchrous Receiver/Transmitter)
- 1:1 통신
- 비동기 신호
- 각 device에서 송수신 data timing을 알아서 맞춘다.
- 클럭을 전송하는 Master device 없다.
- device 내부에서 동기신호를 생성해야 한다.
- data를 송수신할 때 Timing을 잘 맞춰야한다.
설계할 때 문제점
위 그림과 같이 start trigger의 타이밍에 따라 start signal이 1클럭이 안될 수도 있다….
해결책 : sampling
- 16 oversampling, 8 oversampling
16 oversampling
- 각 데이터 비트를 16번 샘플링
- 16번 클럭이 들어온 후 다음 상태로! → baudrate 16배 증가시켜야함
- 각 비트의 중간지점(7번째 샘플링 클럭)에서 샘플 채택
- 이를 통해 비트 타이밍을 더 정확하게 결정할 수 있다.
- 시작 비트 검출
- 수신기(UART Rx)는 라인이 낮은 상태(START 비트)를 16번 중 8번 이상 검출하면 시작 비트로 인식
- 비트 샘플링
- 시작 비트를 검출한 후, 수신기는 데이터 비트를 샘플링
- 각 비트의 중간 지점(즉, 8번째 샘플링 지점)에서 샘플을 채택 → 이렇게 하면 잡음에 강함
- 다음 비트 == 16번 클럭 카운트 한 이후
- 정확한 타이밍 유지
- 매 비트를 16번 샘플링하므로, 비트 타이밍의 정확성을 높임
- 클럭 차이로 인한 드리프트를 최소화할 수 있음
구현
더보기
`timescale 1ns / 1ps
module uart (
input clk,
input reset,
input tx_start,
input [7:0] tx_data,
input rx,
output tx,
output tx_done,
output [7:0] rx_data,
output rx_done
);
wire w_br_tick;
wire w_tx;
baudrate_generator #(
.HERZ(9600)
) U_BR_Gen (
.clk (clk),
.reset(reset),
.br_tick(w_br_tick)
);
transmitter U_TxD (
.clk(clk),
.reset(reset),
.start(tx_start),
.br_tick(w_br_tick),
.tx_data(tx_data),
.tx(tx),
.tx_done(tx_done)
);
receiver U_RxD (
.clk(clk),
.reset(reset),
.br_tick(w_br_tick),
.rx(rx),
.rx_data(rx_data),
.rx_done(rx_done)
);
endmodule
module baudrate_generator #(
parameter HERZ = 9600
) (
input clk,
input reset,
output br_tick
);
// reg [$clog2(100_000_000/9600)-1:0] counter_reg, counter_next;
reg [$clog2(100_000_000/HERZ/16)-1:0] counter_reg, counter_next;
reg tick_reg, tick_next;
assign br_tick = tick_reg;
always @(posedge clk, posedge reset) begin
if (reset) begin
counter_reg <= 0;
tick_reg <= 1'b0;
end else begin
counter_reg <= counter_next;
tick_reg <= tick_next;
end
end
always @(*) begin
counter_next = counter_reg;
if (counter_reg == 100_000_000 / HERZ / 16 - 1) begin
// if (counter_reg == 3) begin // simulation
counter_next = 0;
tick_next = 1'b1;
end else begin
counter_next = counter_reg + 1;
tick_next = 1'b0;
end
end
endmodule
module transmitter (
input clk,
input reset,
input br_tick,
input start,
input [7:0] tx_data,
output tx,
output tx_done
);
localparam IDLE = 0, START = 1, DATA = 2, STOP = 3;
reg [1:0] state, state_next;
reg tx_done_reg, tx_done_next;
reg tx_reg, tx_next;
reg [7:0] data_tmp_reg, data_tmp_next;
reg [3:0]
br_cnt_reg, br_cnt_next; // baudrate 16 sampling 카운터 레지스터
reg [2:0]
data_bit_cnt_reg,
data_bit_cnt_next; // 8비트 데이터 카운트 레지스터
assign tx = tx_reg;
assign tx_done = tx_done_reg;
always @(posedge clk, posedge reset) begin
if (reset) begin
state <= IDLE;
tx_reg <= 1'b1;
tx_done_reg <= 1'b0;
br_cnt_reg <= 0;
data_bit_cnt_reg <= 0;
data_tmp_reg <= 0;
end else begin
state <= state_next;
tx_reg <= tx_next;
tx_done_reg <= tx_done_next;
br_cnt_reg <= br_cnt_next;
data_bit_cnt_reg <= data_bit_cnt_next;
data_tmp_reg <= data_tmp_next;
end
end
always @(*) begin
state_next = state;
tx_next = tx_reg;
tx_done_next = tx_done_reg;
br_cnt_next = br_cnt_reg;
data_bit_cnt_next = data_bit_cnt_reg;
data_tmp_next = data_tmp_reg;
case (state)
IDLE: begin
tx_done_next = 1'b0;
tx_next = 1'b1;
if (start) begin
state_next = START;
data_tmp_next = tx_data;
br_cnt_next = 0;
data_bit_cnt_next = 0;
end
end
START: begin
tx_next = 1'b0;
if (br_tick) begin
if (br_cnt_reg == 15) begin
state_next = DATA;
br_cnt_next = 0;
end else begin
br_cnt_next = br_cnt_reg + 1;
end
end
end
DATA: begin
tx_next = data_tmp_reg[0];
if (br_tick) begin
if (br_cnt_reg == 15) begin
if (data_bit_cnt_reg == 7) begin
state_next = STOP;
br_cnt_next = 0;
end else begin
data_bit_cnt_next = data_bit_cnt_reg + 1;
data_tmp_next = {1'b0, data_tmp_reg[7:1]};
br_cnt_next = 0;
end
end else begin
br_cnt_next = br_cnt_reg + 1;
end
end
end
STOP: begin
tx_next = 1'b1;
if (br_tick) begin
if (br_cnt_reg == 15) begin
tx_done_next = 1'b1;
state_next = IDLE;
end else begin
br_cnt_next = br_cnt_reg + 1;
end
end
end
endcase
end
endmodule
module receiver (
input clk,
input reset,
input br_tick,
input rx,
output [7:0] rx_data,
output rx_done
);
localparam IDLE = 0, START = 1, DATA = 2, STOP = 3;
reg [1:0] state, state_next;
reg [7:0] rx_data_reg, rx_data_next;
reg rx_done_reg, rx_done_next;
reg [4:0]
br_cnt_reg,
br_cnt_next; // baudrate 16 sampling 카운터 레지스터(0~15)
reg [2:0]
data_bit_cnt_reg,
data_bit_cnt_next; // 8비트 데이터 카운트 레지스터(0~7)
assign rx_data = rx_data_reg;
assign rx_done = rx_done_reg;
always @(posedge clk, posedge reset) begin
if (reset) begin
state <= IDLE;
rx_data_reg <= 0;
rx_done_reg <= 1'b0;
br_cnt_reg <= 0;
data_bit_cnt_reg <= 0;
end else begin
state <= state_next;
rx_data_reg <= rx_data_next;
rx_done_reg <= rx_done_next;
br_cnt_reg <= br_cnt_next;
data_bit_cnt_reg <= data_bit_cnt_next;
end
end
always @(*) begin
state_next = state;
br_cnt_next = br_cnt_reg;
data_bit_cnt_next = data_bit_cnt_reg;
rx_data_next = rx_data_reg;
rx_done_next = rx_done_reg;
case (state)
IDLE: begin
rx_done_next = 1'b0;
if (rx == 1'b0) begin
br_cnt_next = 0;
data_bit_cnt_next = 0;
rx_data_next = 0;
state_next = START;
end
end
START: begin
if (br_tick) begin
if (br_cnt_reg == 7) begin
br_cnt_next = 0;
state_next = DATA;
end else begin
br_cnt_next = br_cnt_reg + 1;
end
end
end
DATA: begin
if (br_tick) begin
if (br_cnt_reg == 15) begin
br_cnt_next = 0;
rx_data_next = {rx, rx_data_reg[7:1]}; // right shift
if (data_bit_cnt_reg == 7) begin
state_next = STOP;
br_cnt_next = 0;
end else begin
data_bit_cnt_next = data_bit_cnt_reg + 1;
end
end else begin
br_cnt_next = br_cnt_next + 1;
end
end
end
STOP: begin
if (br_tick) begin
if (br_cnt_reg == 23) begin
br_cnt_next = 0;
state_next = IDLE;
rx_done_next = 1'b1;
end else begin
br_cnt_next = br_cnt_reg + 1;
end
end
end
endcase
end
endmodule
Simulation
1. Tx Simulation
더보기
`timescale 1ns / 1ps
module tb_uart ();
reg clk;
reg reset;
reg tx_start;
reg [7:0] tx_data;
reg rx;
wire tx;
wire tx_done;
wire [7:0] rx_data;
wire rx_done;
uart dut (
.clk(clk),
.reset(reset),
.tx_start(tx_start),
.tx_data(tx_data),
.rx(rx),
.tx(tx),
.tx_done(tx_done),
.rx_data(rx_data),
.rx_done(rx_done)
);
always #5 clk = ~clk;
initial begin
clk = 1'b0;
reset = 1'b1;
tx_start = 1'b0;
rx = 1'b1;
end
initial begin
#20 reset = 1'b0;
#100 tx_data = 8'b11000101;
tx_start = 1'b1;
#10 tx_start = 1'b0;
end
endmodule
2. Rx Simulation
더보기
`timescale 1ns / 1ps
module tb_uart ();
reg clk;
reg reset;
reg tx_start;
reg [7:0] tx_data;
// reg rx;
wire tx;
wire tx_done;
wire [7:0] rx_data;
wire rx_done;
uart dut (
.clk(clk),
.reset(reset),
.tx_start(tx_start),
.tx_data(tx_data),
.rx(tx),
.tx(tx),
.tx_done(tx_done),
.rx_data(rx_data),
.rx_done(rx_done)
);
always #5 clk = ~clk;
initial begin
clk = 1'b0;
reset = 1'b1;
tx_start = 1'b0;
// rx = 1'b1;
end
initial begin
#20 reset = 1'b0;
#100 tx_data = 8'b11000101;
tx_start = 1'b1;
#10 tx_start = 1'b0;
end
endmodule
Oversampling 유무 비교
1. oversampling 사용 X
- 위 시뮬레이션 결과와 같이 start bit가 일정 클럭 유지되었을 때는 reciever의 출력이 입력 값과 동일하지만 start bit가 일정 클럭 유지되지 않았을 경우(너무 짧을 경우), 첫번째 수신 비트를 인식하지 못하고 stop bit(High)를 수신 비트로 인식해버려 오류가 생긴다.
2. oversampling 사용 O
- oversampling을 사용하는 경우 start bit가 무조건 일정 클럭 유지되기 때문에 receiver에서 정상적으로 데이터를 인식한다.
Oversampling을 사용하는 이유?
1. 노이즈 면역
⦁ Receiver가 각 비트에서 여러번 샘플링함으로써 노이즈와 데이터를 구분할 수 있다.
⦁ 데이터 비트 중간 샘플의 양 옆 2~3비트가 같은 값인지를 비교해서 노이즈와 데이터를 구분
⦁ 이 코드에서는 이 부분은 구현하지 않았다.
2. 클럭 오차 보상
⦁ UART는 기본적으로 비동기 통신이다.
⦁ 때문에 양 통신 기기의 baudrate 위상차가 발생할 수 있다.
⦁ oversampling은 이 위상차의 간격을 좁혀주고 작은 오차를 보상해줄 수 있다.
3. start & stop bit 클럭 보장
⦁ UART의 start signal은 비동기 신호이고 이로 인해 start bit가 의도치 않게 너무 짧아질 수 있다.
⦁ 그렇게 되면 위 시뮬레이션 결과와 같이 오류가 발생 할 수 있다.
⦁ oversampling은 start bit를 일정 클럭 유지시켜줌으로써 receiver가 시작 신호를 정상적으로
인지할 수 있도록 보장해준다.
=> oversampling은 start bit와 stop bit를 일정 시간 유지시켜줌으로써 데이터 프레임의 신뢰성을 높여주고 데이터와 노이즈를 구분함으로써 신호의 무결성을 높여준다.
PC 통신 결과(loop)
- PC → Receiver 데이터 수신
- Receiver → Transmitter 데이터 전송
- 이때 수신 완료 신호(
rx_done
)를 Transmitter의 start signal로 사용
- 이때 수신 완료 신호(
- Transmitter → PC 데이터 송신
- ⇒ PC를 통해 받은 데이터(문자)를 그대로 PC에 전송하여 PC에서 UART 모듈이 정상 동작하는지 확인
더보기
`timescale 1ns / 1ps
module top_txrx_loop (
input clk,
input reset,
input rx,
output tx
);
wire [7:0] w_rx_data;
wire w_rx_done;
uart U_UART (
.clk(clk),
.reset(reset),
.tx_start(w_rx_done),
.tx_data(w_rx_data),
.rx(rx),
.tx(tx),
.tx_done(),
.rx_data(w_rx_data),
.rx_done(w_rx_done)
);
endmodule
- `rx` 데이터 1bit씩 8bit 수신
- receiver를 통해 데이터 처리 후 `rx_data` 8bit를 transmitter의 `tx_data`로 입력
- transmitter에서 8bit 데이터를 1bit씩 8bit 전송(`tx`)
Made By Minseok KIM
'VerilogHDL > Study' 카테고리의 다른 글
[VerilogHDL] Verification(32bit register, BRAM) (0) | 2024.05.23 |
---|---|
[VerilogHDL] System Verilog 기초 (0) | 2024.05.22 |
[VerilogHDL] UART Tx(2) (0) | 2024.05.22 |
[VerilogHDL] UpCounter, 디버깅, UART Tx (0) | 2024.05.20 |
[VerilogHDL] FSM 코딩(Moore, Mealy) - 버튼, UpCounter (0) | 2024.05.19 |
@민바Minba :: Minba's blog
Let's Be Happy!
도움이 되었으면 좋겠어요 :)