진행년월: 24.05
목차
1. 배경
2. 과제 정의 및 개요
3. 소스코드
4. 시뮬레이션 결과
1. 배경
지난 프로젝트에서 컴퓨터와 신호를 주고받기 위해 UART 통신을 사용하였는데요,
UART 통신도 이해하고 Verilog로 구현해보고자 합니다.
Receiver 부분과 Transmitter 부분이 있는데,
전부 구현은 하지만, 테스트 단계에서
SPI 때처럼 Receiver 부분을 중점적으로 테스트하려고 합니다.
2. 과제 정의 및 개요
1. TX, RX 2개의 핀을 사용합니다.
2. 10비트 데이터를 교환하며 구성은 다음과 같습니다.
- [0] 비트는 start bit (0)
- [1~8] 비트는 8비트 data
- [9] 비트는 end bit (1)
3. 16배로 오버샘플링하며, 중앙 샘플링 방법을 채택하도록 합니다.
- 간단한 예시이므로 노이즈가 심하지 않을 것임을 알고 있어 채택하였습니다.
- 7, 8, 9번째 신호 중 2개 이상 1이 나온다면, 1로 인정합니다.
Timing Diagram과 State Diagram은 아래와 같습니다.
간단한 구현이므로 엄격하게 많은 상황을 포함하여 그리지는 않았습니다.
3. 소스코드
- Top module
`timescale 1ns / 1ps
module uart(
input i_clk, // i_clk = 50Mhz
input i_rx,
input i_rst,
output o_tx
);
wire w_tx_clk;
wire w_rx_clk;
wire [7:0] w_rx_register;
wire w_rx_end;
wire [4:0] w_clk_cnt;
wire [3:0] w_rx_cnt;
wire [3:0] w_send_cnt;
wire [1:0] w_current_state;
wire [1:0] w_next_state;
uart_clock #(.max(2604)) uart_clk_gen_tx (
.clk_in(i_clk),
.rst(i_rst),
.clk_out(w_tx_clk)
); // Baud rate = 9600, max = 50,000,000 / 9,600 = 5,208
uart_clock #(.max(163)) uart_clk_gen_rx (
.i_clk(i_clk),
.i_rst(i_rst),
.o_clk(w_rx_clk)
); // Sampling rate = 16*, max = 50,000,000 / 9,600 * 16 = 325
uart_rx uart_receiver (
.i_clk(w_rx_clk),
.i_rst(i_rst),
.i_rx(i_rx),
.o_rx_register(w_rx_register),
.o_rx_end(w_rx_end),
.o_clk_cnt(w_clk_cnt),
.o_rx_cnt(w_rx_cnt)
);
uart_tx uart_transmitter (
.i_clk(w_tx_clk),
.i_rst(i_rst),
.o_tx(o_tx),
.i_rx_register(w_rx_register),
.i_rx_end(w_rx_end),
.o_send_cnt(w_send_cnt),
.o_current_state(w_current_state),
.o_next_state(w_next_state)
);
endmodule
주석에 적어둔 것 처럼, 보드레이트를 맞추기 위한 작업이 포함되어 있습니다.
- Uart_clock
`timescale 1ns / 1ps
module uart_clock(
input i_clk,
input i_rst,
output reg o_clk
);
reg [10:0] r_clk_cnt;
parameter max = 1;
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
r_clk_cnt <= 0;
end else if (r_clk_cnt == max) begin
r_clk_cnt <= 0;
end else
r_clk_cnt <= r_clk_cnt + 1;
end
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
o_clk_out <= 0;
end else if (r_clk_cnt == max) begin
o_clk <= !o_clk;
end else
o_clk <= o_clk;
end
endmodule
- Uart_TX
`timescale 1ns / 1ps
module uart_tx(
input i_clk,
input i_rst,
input [7:0] i_rx_register,
input i_rx_end,
output reg o_tx,
output reg [3:0] o_send_cnt,
output reg [1:0] o_current_state,
output reg [1:0] o_next_state
);
localparam TX_IDLE = 2'b00;
localparam TX_START = 2'b01;
localparam TX_DATA = 2'b10;
localparam TX_STOP = 2'b11;
always @(posedge i_clk or posedge i_rst) begin
if (i_rst)
o_current_state <= TX_IDLE;
else
o_current_state <= o_next_state;
end
always @(negedge i_clk or posedge i_rst) begin
if (i_rst)
o_next_state <= TX_IDLE;
else
case(o_current_state)
TX_IDLE : if (i_rx_end == 1)
o_next_state <= TX_START;
else
o_next_state <= o_next_state;
TX_START : o_next_state <= TX_DATA;
TX_DATA : if (o_send_cnt == 8)
o_next_state <= TX_STOP;
else
o_next_state <= o_next_state;
TX_STOP : o_next_state <= TX_IDLE;
endcase
end
always @(posedge i_clk or posedge i_rst) begin
if (i_rst)
o_send_cnt <= 0;
else if (o_send_cnt == 10)
o_send_cnt <= 0;
else
case(o_next_state)
TX_IDLE : o_send_cnt <= 0;
TX_START : o_send_cnt <= 0;
TX_DATA : o_send_cnt <= o_send_cnt+1;
TX_STOP : o_send_cnt <= o_send_cnt+1;
endcase
end
always @(posedge i_clk or posedge i_rst) begin
if (i_rst)
o_tx <= 1;
else
case (o_next_state)
TX_IDLE : o_tx <= 1;
TX_START : o_tx <= 0;
TX_DATA : o_tx <= i_rx_register[o_send_cnt];
TX_STOP : o_tx <= 1;
endcase
end
endmodule
TX 부분은 들어온 RX 신호를 그대로 다시 전송할 수 있도록 간단하게 구현하였습니다.
- UART_RX
module uart_rx(
input i_clk,
input i_rx,
input i_rst,
output reg [7:0] o_rx_register,
output reg o_rx_end,
output reg [4:0] o_clk_cnt,
output reg [3:0] o_rx_cnt
);
localparam RX_IDLE = 2'b00;
localparam RX_START = 2'b01;
localparam RX_STOP = 2'b10;
reg [1:0] r_current_state;
reg [1:0] r_next_state;
reg [15:0] r_rx_check;
reg [4:0] r_rx_score;
reg r_rx_sampling;
reg [9:0] r_rx_received;
// Clock counter and bit counter logic
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
o_clk_cnt <= 0;
end else if (o_clk_cnt == 16 && o_rx_cnt != 11) begin
o_clk_cnt <= 1;
end else if (o_clk_cnt == 16 && o_rx_cnt == 11) begin
o_clk_cnt <= 0;
end else begin
case (r_next_state)
RX_IDLE : o_clk_cnt <= 0;
RX_START : o_clk_cnt <= o_clk_cnt + 1;
RX_STOP : o_clk_cnt <= o_clk_cnt + 1;
default : o_clk_cnt <= o_clk_cnt;
endcase
end
end
// Bit counter logic
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
o_rx_cnt <= 0;
end else if (o_rx_cnt == 11 && o_clk_cnt == 16) begin
o_rx_cnt <= 0;
end else if (r_current_state == RX_IDLE && i_rx == 0) begin
o_rx_cnt <= 1;
end else if (o_clk_cnt == 16) begin
o_rx_cnt <= o_rx_cnt + 1;
end else begin
o_rx_cnt <= o_rx_cnt;
end
end
// FSM for RX control
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
r_current_state <= RX_IDLE;
end else begin
r_current_state <= r_next_state;
end
end
always @(negedge i_clk or posedge i_rst) begin
if (i_rst) begin
r_next_state <= RX_IDLE;
end else begin
case (r_current_state)
RX_IDLE : if (i_rx == 0)
r_next_state <= RX_START;
else
r_next_state <= r_next_state;
RX_START : if (o_rx_cnt == 11)
r_next_state <= RX_STOP;
else
r_next_state <= r_next_state;
RX_STOP : if (o_rx_cnt == 0)
r_next_state <= RX_IDLE;
else
r_next_state <= r_next_state;
default : r_next_state <= r_next_state;
endcase
end
end
// Central sampling logic as per the original code
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
r_rx_check <= 16'b1111_1111_1111_1111;
end else begin
r_rx_check <= {r_rx_check[14:0], i_rx};
end
end
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
r_rx_score <= 0;
end else if (o_rx_cnt == 0) begin
r_rx_score <= 0;
end else if (o_clk_cnt == 16) begin
r_rx_score <= 0;
end else if (o_rx_cnt == 11) begin
r_rx_score <= 0;
end else if (i_rx == 1) begin
r_rx_score <= r_rx_score + 1;
end else begin
r_rx_score <= r_rx_score;
end
end
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
r_rx_sampling <= 1;
end else if (o_rx_cnt == 0) begin
r_rx_sampling <= 1;
end else if (o_clk_cnt == 16 && r_rx_score > 8 && o_rx_cnt != 11) begin
r_rx_sampling <= 1;
end else if (o_clk_cnt == 16 && r_rx_score < 8 && o_rx_cnt != 11) begin
r_rx_sampling <= 0;
end else begin
r_rx_sampling <= r_rx_sampling;
end
end
// Register received data
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
r_rx_received <= 10'b0;
end else if (o_clk_cnt == 1 && o_rx_cnt > 1) begin
r_rx_received <= {r_rx_received[8:0], r_rx_sampling};
end
end
// Update RX register
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
o_rx_register <= 8'b0;
end else if (o_rx_cnt == 11 && o_clk_cnt == 2) begin
o_rx_register <= r_rx_received[8:1];
end
else
rx_register <= rx_register;
end
// RX end signal
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
o_rx_end <= 1;
end else begin
case (r_next_state)
RX_IDLE : o_rx_end <= 1;
RX_START : o_rx_end <= 0;
RX_STOP : o_rx_end <= 0;
default : o_rx_end <= o_rx_end;
endcase
end
end
endmodule
여기서 o_rx_end 신호는 의미상으로만 존재할뿐, 사용하지 않습니다.
Data Update Timing 등은 설계하기 나름이며, 최대한 간단한 형태로 설계하였습니다.
- tb_rx
`timescale 1ns / 1ps
module tb_rx;
reg clk;
reg rx;
reg rst;
wire [7:0] rx_register;
wire rx_end;
reg [7:0] tx_data; // 보낼 데이터
integer i;
// UART RX 모듈 인스턴스화
uart_rx uut (
.i_clk(clk),
.i_rx(rx),
.i_rst(rst),
.o_rx_register(rx_register),
.o_rx_end(rx_end)
);
// Clock generation (100 MHz)
initial begin
clk = 0;
forever #5 clk = ~clk; // 10ns 주기 클럭, 100 MHz
end
// 테스트 시나리오
initial begin
// 초기화
rst = 1; // 리셋
rx = 1; // IDLE 상태 (RX 라인은 기본적으로 High 상태)
tx_data = 8'b0101_0111; // 전송할 데이터: 8'b0101_0111
#100 rst = 0; // 리셋 해제
// 데이터 전송 (UART 프레임 생성)
// 시작 비트 (Start Bit)
rx = 0;
#160; // 16 클럭 사이클 (샘플링 속도)
// 데이터 비트 (Data Bits)
for (i = 0; i < 8; i = i + 1) begin
rx = tx_data[i];
#160; // 각 데이터 비트는 16 클럭 사이클 동안 유지
end
// 정지 비트 (Stop Bit)
rx = 1;
#160; // Stop Bit
// 대기 후 결과 확인
#200;
// 결과 출력
if (rx_register == tx_data) begin
$display("Test Passed! Received Data: %b", rx_register);
end else begin
$display("Test Failed! Received Data: %b, Expected Data: %b", rx_register, tx_data);
end
// 시뮬레이션 종료
#100;
$finish;
end
endmodule
앞서 언급하였듯이 RX 위주로 확인할 것이기 때문에,
RX의 테스트벤치만을 간단하게 작성하였습니다.
4. 시뮬레이션 결과
동작을 확인해보기 위해, 또 모범적인 waveform을 마련해 놓기 위해 function simulation을 합니다.
Tool은 Vivado를 사용하였습니다.
사실, 앞서 언급한 바와 같이 rx_end 신호는 의미상으로만 존재합니다.
제 구현에서는 rx_end 신호가 필요하지 않습니다.
만약, 필요하다면 처음 rx_end 신호가 Low로 내려가는 순간은
다른 신호와 마찬가지로 샘플링된 결과에 의해 작동해야 합니다.
처음에 그렸던 Timing Diagram과 비슷한 waveform을 만들고자 의도한 바입니다.
rx_register에 저장되는 Timing 등은 설계하기 나름이지만,
여유를 두기 위해 통신이 끝날 때로 두었습니다.
- 다음 글
(2) Arm보드로 FPGA LCD제어: https://chonh0531.tistory.com/6
'RTL, Synthesis, P&R' 카테고리의 다른 글
16KB Cache Memory Controller - (1) RTL 및 Coverage (0) | 2024.09.07 |
---|---|
UART 통신 - (2) Arm보드로 FPGA LCD 제어 (0) | 2024.09.03 |
32-bit SPI Interface - (3) Arduino로 FPGA Motor 제어 (0) | 2024.09.03 |
32-bit SPI Interface - (2) Synthesis, P&R (0) | 2024.09.03 |
32-bit SPI Interface - (1) RTL (0) | 2024.09.03 |