진행년월: 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

 

UART 통신 - (2) Arm보드로 FPGA LCD 제어

- 이전 글(1) RTL : https://chonh0531.tistory.com/5목차1. 과제 개요2. 소스코드3. 실습 결과  1. 과제 개요이전에 구현해 본 UART_RX를 테스트해 보기로 합니다.UART 신호 생성은 ARM Cortex-M4 Core가 있는 NUCLEO-F42

chonh0531.tistory.com

 

 

'RTL, Synthesis, P&R' 카테고리의 다른 글

Cache controller - (1) RTL  (0) 2024.09.07
UART 통신 - (2) Arm보드로 FPGA LCD 제어  (0) 2024.09.03
SPI 통신 - (3) Arduino로 FPGA Motor 제어  (0) 2024.09.03
SPI 통신 - (2) Synthesis, P&R  (0) 2024.09.03
SPI 통신 - (1) RTL  (0) 2024.09.03

+ Recent posts