본문 바로가기
하만 세미콘 아카데미/Verilog HDL

Verilog HDL 실습 (UART)

by smileww 2024. 5. 31.

이번 프로젝트에서는 간단한 Uart 모듈을 Verilog HDL를 활용하여 검증해보도록 하겠습니다.

 

UART란?

UART (Universal Asynchronous Receiver/Transmitter)는 컴퓨터 하드웨어의 일종으로, 디지털 장치 간에 직렬 통신을 가능하게 하는 통신 인터페이스입니다. 직렬 통신이란 데이터를 한 번에 한 비트씩 비트 스트림 형태로 전송하는 방식을 말합니다. UART는 이러한 데이터 전송을 위해 비동기식 방법을 사용합니다, 즉 송신기와 수신기 사이에 별도의 동기화 클록 신호 없이 데이터를 전송합니다.

 

UART의 기본 작업은 송신 측에서는 병렬 데이터(한 번에 여러 비트를 처리하는 형태)를 직렬 데이터로 변환하고, 수신 측에서는 이 직렬 데이터를 다시 병렬 데이터로 변환하는 것입니다. 데이터 프레임에는 시작 비트(스타트 비트), 데이터 비트, 선택적 패리티 비트(오류 검출용), 그리고 종료 비트(스톱 비트)가 포함됩니다.

 

UART는 다양한 전자 장치에서 데이터 통신을 위한 기본적인 수단으로 사용되며, 간단한 구성과 낮은 비용으로 널리 활용됩니다. 예를 들어, 컴퓨터와 마이크로컨트롤러, 센서와 같은 주변 장치 간의 통신, 그리고 저속의 네트워킹 장치에서 주로 사용됩니다.

 

 

앞서 Uart에 대한 개념은 아래 글을 참고해주시면 되겠습니다.

 

https://engineeringstudentww.tistory.com/23

 

uart 이론 (이춘배 교수님)

chblee@weduslution.com synthesis: 베릴로그 코드를 회로도로 바꾸는 과정       고속이 필요하지 않을 때UART는 데이터 전송 속도가 매우 빠르지 않아도 되는 통신에 적합합니다. 예를 들어, 간단한

engineeringstudentww.tistory.com

 

 

이제 데이터를 RX에서 FIFO를 거쳐 TX로 전달되는 간단한 Uart를 만들어 보도록 하겠습니다.

 

 

Uart Verilog Code

더보기

Uart_rx_top

`timescale 1ns / 1ps

module uart_rx(
    input RST,
    input CLK,
    input RXD,
    output [7:0] RX_DATA,
    output      RX_DATA_RDY,
    output      FRM_ERR,
    output      PARITY_ERR

    );

wire    baud_x16_en;

uart_baud_gen uart_baud_gen  (
    .RST    (RST),
    .CLK    (CLK),
    .BAUD_X16_EN    (baud_x16_en)
    );
        
uart_rx_cntl uart_rx_cntl(
    .RST        (RST),
    .CLK        (CLK),
    .RXD        (RXD),
    .BAUD_X16_EN    (baud_x16_en),
    .RX_DATA        (RX_DATA),
    .RX_DATA_RDY   (RX_DATA_RDY),
    .FRM_ERR        (FRM_ERR),
    .PARITY_ERR     (PARITY_ERR)
    );
    
endmodule

 

Uart_baud_gen.v

`timescale 1ns / 1ps

module uart_baud_gen(
    input RST,
    input CLK,
    output BAUD_X16_EN
    );
parameter CLK_FREQ = 125_000_000;
parameter BAUD_RATE = 9600;
localparam MAX_CNT = (CLK_FREQ)/(BAUD_RATE*16);

reg [12:0] cnt;
reg        enable;

assign BAUD_X16_EN = enable;

always @(posedge CLK)
begin
    if (RST) begin
        cnt <= 13'd0;
        enable <= 1'b0;
    end else begin
        if(cnt == (MAX_CNT-1))  begin
            cnt <= 13'd0;
            enable <= 1'b1;
        end else begin
            cnt <= cnt + 1;
            enable <= 1'b0;
        end
    end
end     //always                                    
                
    
endmodule

 

uart_rx_cntl.v

`timescale 1ns / 1ps

module uart_rx_cntl(
    input RST,
    input CLK,
    input RXD,
    input BAUD_X16_EN,  //over_sample_cnt_done,
    output [7:0]    RX_DATA,
    output reg             RX_DATA_RDY,
    output  reg     FRM_ERR,
    output                  PARITY_ERR
//    output reg [1:0] fsm_state
    );

localparam [1:0]    idle = 2'b00,
                    start = 2'b01,
                    data  = 2'b10,
                    stop = 2'b11;
                   
reg [1:0]   curr_state;//
reg [1:0]  next_state;
wire        over_sample_cnt_done;
reg [3:0]   over_sample_cnt;
reg [3:0]   bit_cnt;
wire        bit_cnt_done;
reg [8:0]   rx_frame;


always @(posedge CLK)
begin 
    if(RST)
        curr_state <= idle;
    else
        curr_state <= next_state;
end  //always

always @(curr_state, over_sample_cnt_done, bit_cnt_done, RXD)
begin
    case (curr_state)
        idle : begin
            if(RXD == 1'b0)
                next_state = start;
            else
                next_state = idle;
//            fsm_state = 2'b00;
        end
        start : begin
            if(over_sample_cnt_done) begin
                if(RXD)
                    next_state = idle;
                else
                    next_state = data;
            end
            else 
                next_state = start;
//            fsm_state = 2'b01;
        end
        data : begin
            if(over_sample_cnt_done && bit_cnt_done)
                next_state = stop;
            else
                next_state = data;
//            fsm_state = 2'b10;
        end
        stop : begin
            if(over_sample_cnt_done)
                next_state = idle;
            else
                next_state = stop;
//            fsm_state = 2'b11;
        end
        default : next_state = idle;
    endcase
end //always                                                                                                                                                                                                            

// over sample count                                    
always @(posedge CLK)
begin
    if(RST || curr_state == idle)
        over_sample_cnt <= 4'd7;
    else if (BAUD_X16_EN) begin
        if(over_sample_cnt_done)
            over_sample_cnt <= 4'd15;
        else
            over_sample_cnt <= over_sample_cnt - 1;
    end
end         
assign over_sample_cnt_done = (over_sample_cnt == 4'd0) & BAUD_X16_EN;

// bit cnt generate
always @(posedge CLK)
begin
    if(RST || curr_state != data)
        bit_cnt <= 4'd0;
    else if(over_sample_cnt_done)
        bit_cnt <= bit_cnt + 1;
end //always
assign bit_cnt_done = (over_sample_cnt_done && (bit_cnt == 4'd8));


// output data generate
assign RX_DATA = rx_frame[7:0];
  
always @(posedge CLK)
begin
    if (RST)  
        rx_frame <= 9'd0;
     else if (curr_state == data && over_sample_cnt_done)
        rx_frame[bit_cnt] <= RXD;
end  //always  

//always @(posedge CLK)
//    rx_d <= RXD;
  
 always @(posedge CLK)
    RX_DATA_RDY <= bit_cnt_done;
                                  
assign PARITY_ERR = RX_DATA_RDY & ( rx_frame[8] != ^RX_DATA);                           

always @(posedge CLK)
begin
    if(RST)
        FRM_ERR <= 1'b0;
    else if((curr_state == stop) && over_sample_cnt_done)
        if (!RXD)
            FRM_ERR <= 1'b1;
        else
            FRM_ERR <= 1'b0;
end
                                                                                    
endmodule

 

FIFO.v

`timescale 1ns / 1ps

module my_fifo(
    input RST,
    input CLK,
    input [7:0] DIN,
    input WR_EN,
    output FULL,
    output [7:0] DOUT,
    input RD_EN,
    output EMPTY
    );

reg [7:0] ram [0:15];   
reg [4:0] wr_addr, rd_addr;  // write/ read address
wire    addr_pos;       // full/empty 

// Data write 
always @(posedge CLK)
begin
    if (RST)
        wr_addr <= 5'd0;
    else if(WR_EN && !FULL)
        wr_addr <= wr_addr + 1;        
end // always 

always @(posedge CLK)
    if(WR_EN && !FULL)
        ram[wr_addr[3:0]] <= DIN;

//data read
always @(posedge CLK)
begin
    if (RST)
        rd_addr <= 5'd0;
    else if(RD_EN && !EMPTY)
        rd_addr <= rd_addr + 1;
end  //always

assign DOUT = ram[rd_addr[3:0]];

wire addr_equ = wr_addr[3:0] == rd_addr[3:0];     // check address equal
assign addr_pos = wr_addr[4] ^ rd_addr[4];

assign FULL = addr_equ & (addr_pos);
assign EMPTY = addr_equ & ~addr_pos;
                   
endmodule

 

uart_tx.v

`timescale 1ns / 1ps

module uart_tx(
    input RST,
    input CLK,
    input START,
    input [7:0] DIN,
    output reg TXD
    );
    
parameter [1:0] idle = 2'b00,
                start = 2'b01,
                tx_send = 2'b10,
                stop = 2'b11;
reg [1:0]   curr_state;
reg [1:0]    next_state;
reg [3:0]   over_sample_cnt, bit_cnt;
wire        over_sample_cnt_done, bit_cnt_done;
wire        parity;
reg [7:0]   tx_data_in;
wire [8:0]  tx_data = {parity, tx_data_in};
wire        baud_x16_en;


always @(posedge CLK)
    if(START)
        tx_data_in <= DIN;

always @(posedge CLK)
    if(RST)
        curr_state <= idle;
    else
        curr_state <= next_state;                

always @(curr_state, START, over_sample_cnt_done, bit_cnt_done)
begin
    case (curr_state)
        idle : 
            if(START)
                next_state = start;
            else
                next_state = idle;
        start : 
            if(over_sample_cnt_done)
                next_state = tx_send;
            else
                next_state = start;
        tx_send : 
            if(over_sample_cnt_done && bit_cnt_done) 
                next_state = stop;
            else
                next_state = tx_send;
        stop :
            if(over_sample_cnt_done)
                next_state = idle;
            else
                next_state = stop;
        default : next_state = idle;
    endcase
end     // always 

// over sample count  -- 5��
always @(posedge CLK)
    if(curr_state == idle)
        over_sample_cnt <= 5'd15;
    else if(baud_x16_en)
        over_sample_cnt <= over_sample_cnt -1;                                                                                                                                                     

assign over_sample_cnt_done = (over_sample_cnt == 4'd0) & baud_x16_en;

// bit count generation -- 6��
always @(posedge CLK)
    if(curr_state != tx_send)
        bit_cnt <= 4'd0;
    else if(over_sample_cnt_done)
        bit_cnt <= bit_cnt + 1;

assign bit_cnt_done = over_sample_cnt_done & (bit_cnt == 4'd8);

// parity gen - 7��
assign parity = ^tx_data[7:0];

//paraller to serial (TXD)  -- 8��
always @(curr_state, bit_cnt)
    if(curr_state == idle || curr_state == stop)
        TXD = 1'b1;
    else if(curr_state == start)
        TXD = 1'b0;
    else
        TXD = tx_data[bit_cnt];

// instantation uart_baud_gen  -- 9��
uart_baud_gen baud_gne_i0 (
    .RST    (RST),
    .CLK    (CLK),
    .BAUD_X16_EN    (baud_x16_en)
    );                
                 
endmodule

 

Uart_top

`timescale 1ns / 1ps

module uart_top(
    input RST,
    input CLK,
    input RXD,
    output [6:0] AN,
    output CA,
    output PAR_ERR,
    output FRM_ERR,
    output  TXD
    );
wire    [7:0]   rx_data;
wire            rx_data_rdy;
wire    [7:0]   dout;
wire            rd_en;
wire            empty;
reg [7:0]       dout_reg;

uart_rx uart_rx_0 (
    .RST        (RST),
    .CLK        (CLK),
    .RXD        (RXD),
    .RX_DATA    (rx_data),
    .RX_DATA_RDY    (rx_data_rdy),
    .FRM_ERR    (FRM_ERR),
    .PARITY_ERR (PAR_ERR)
    );    

my_fifo fifo_0 (
    .RST    (RST),
    .CLK    (CLK),
    .DIN    (rx_data),
    .WR_EN  (rx_data_rdy),
    .FULL   (),
    .DOUT   (dout),
    .RD_EN  (rd_en),
    .EMPTY  (empty)
    );
assign rd_en = ~empty;

always @(posedge CLK)
    if(rd_en)
        dout_reg <= dout;

display_inf disp_0 (
    .RST            (RST),
    .CLK            (CLK),      // 125 Mhz
    .NUM_1S         (dout_reg[3:0]),
    .NUM_10S        (dout_reg[7:4]),
    .CA             (CA),
    .AN             (AN)
    );    

// instantiation uart_tx  -- 10��    
uart_tx uart_tx_0 (
    .RST        (RST),
    .CLK        (CLK),
    .START      (rd_en),
    .DIN        (dout),
    .TXD        (TXD)
    );    

endmodule

 

disp.v

`timescale 1ns / 1ps

module display_inf(
    input           RST,
    input           CLK,      // 125 Mhz
    input [3:0]     NUM_1S,
    input [3:0]     NUM_10S,
    output reg     CA,
    output reg [6:0] AN
    );
parameter CLK_FREQ = 125_000_000;
parameter CNT_MAX = CLK_FREQ / 1000_000; 

reg [26:0] clk_cnt;
wire    enable;

always @(posedge CLK)
begin
    if(RST)
        clk_cnt <= 27'd0;
    else begin
        if( clk_cnt == (CNT_MAX -1))
            clk_cnt <= 27'd0;
        else
            clk_cnt <= clk_cnt + 1;
    end                                
end

assign enable = (clk_cnt == (CNT_MAX -1));

always @(posedge CLK)
    if(RST)
        CA <= 1'b0;
    else if (enable)
        CA <= ~CA;

always @(CA, NUM_1S, NUM_10S)
begin
    if (CA) begin
        case (NUM_10S)
            4'd0 : AN = 7'h7e;
            4'd1 : AN = 7'h30;
            4'd2 : AN = 7'h6d;
            4'd3 : AN = 7'h79;
            4'd4 : AN = 7'h33;
            4'd5 : AN = 7'h5b;
            4'd6 : AN = 7'h5f;
            4'd7 : AN = 7'h70;
            4'd8 : AN = 7'h7f;
            4'd9 : AN = 7'h7b;            
            default : AN = 7'h00;
        endcase  
    end else begin
        case (NUM_1S)
            4'd0 : AN = 7'h7e;
            4'd1 : AN = 7'h30;
            4'd2 : AN = 7'h6d;
            4'd3 : AN = 7'h79;
            4'd4 : AN = 7'h33;
            4'd5 : AN = 7'h5b;
            4'd6 : AN = 7'h5f;
            4'd7 : AN = 7'h70;
            4'd8 : AN = 7'h7f;
            4'd9 : AN = 7'h7b;                                    
            default : AN = 8'h00;
        endcase  
    end        
end        

endmodule

 

Baud_Rate

UART 통신을 위한 보드 레이트 발생기 설계로 주어진 클록 주파수(CLK_FREQ)와 목표 보드 레이트(BAUD_RATE)를 기반으로 16배속 보드 레이트 신호(BAUD_X16_EN)를 생성합니다. 모듈은 클록 신호에 동기화되어, 지정된 카운트(MAX_CNT)에 도달할 때마다 출력 신호를 활성화하여 UART 데이터 전송 타이밍을 제어합니다.

 

Rx_Control

UART 수신 컨트롤러 설계 비동기 직렬 데이터를 수신하고 처리합니다. 이 모듈은 내부 상태 머신을 사용하여 데이터 비트, 스타트 및 스톱 비트를 식별하고, 필요한 오버 샘플링 카운터를 관리하며, 수신 데이터의 유효성을 검사하여 데이터 무결성 오류(프레임 에러와 패리티 에러)를 식별합니다. 최종적으로, 이 모듈은 수신된 데이터 바이트를 출력하고 데이터 준비 완료 신호(RX_DATA_RDY)를 활성화하여 데이터가 준비되었음을 나타냅니다.

 

UART_RX

uart_rx라는 이름으로 UART 수신 시스템의 전체 구조를 통합하고 관리하는 최상위 모듈입니다. 모듈은 uart_baud_gen과 uart_rx_cntl 두 하위 모듈을 포함하여 보드 레이트 생성 및 데이터 수신 제어를 담당합니다. 이 구성을 통해 UART 프로토콜을 사용하여 외부 장치로부터 직렬 데이터를 수신하고, 수신된 데이터의 유효성을 검증한 후 사용자에게 제공합니다.

 

FIFO

my_fifo는 데이터를 버퍼링하는 16-슬롯 FIFO (First In First Out) 메모리를 구현합니다. 데이터는 DIN 입력을 통해 받아 WR_EN 신호가 활성화되면 FULL이 아닐 때 메모리에 쓰여지며, RD_EN 신호가 활성화되어 EMPTY가 아닐 때 데이터를 읽어 DOUT으로 출력합니다. 또한, 이 FIFO는 포인터(wr_addr, rd_addr)를 사용하여 데이터의 쓰기 및 읽기 주소를 관리하고, 이를 통해 FIFO가 비어 있거나 가득 찼는지를 판단합니다.

 

UART_TX

uart_tx는 UART 통신 프로토콜을 사용하여 데이터를 직렬로 전송하는 송신기를 구현합니다. 데이터 전송은 상태 머신을 이용하여 시작 비트, 데이터 비트, 패리티 비트, 그리고 정지 비트 순서로 전송되며, 각 비트는 16배수의 보드 레이트(baud_x16_en)로 오버샘플링되어 전송 타이밍이 제어됩니다. 또한, 모듈은 입력된 데이터(DIN)를 내부적으로 버퍼링하고, 전송 준비가 완료되면 START 신호에 의해 데이터 전송을 시작합니다.

 

UART_TOP

uart_top은 UART 통신 시스템의 최상위 모듈로서 전체 UART 수신 및 전송 시스템을 통합 관리합니다. 모듈은 uart_rx를 사용해 데이터를 수신하고, 수신된 데이터를 my_fifo 버퍼에 저장한 후 disp_inf를 통해 수신 데이터를 디스플레이 장치에 출력합니다. 또한, 버퍼에서 데이터를 읽어 uart_tx를 통해 데이터를 다시 직렬로 송신하는 기능을 수행합니다, 여기서 프레임 에러(FRM_ERR)와 패리티 에러(PAR_ERR)도 감지하고 출력합니다.

 

Disp_inf

disp_inf는 두 개의 4비트 입력(NUM_1S, NUM_10S)을 받아 세그먼트 디스플레이 출력(AN)으로 변환하는 기능을 수행합니다. 모듈은 내부 타이머를 사용하여 1MHz 주기로 CA 출력을 토글하며, 이 토글은 두 개의 숫자 간에 디스플레이를 스위칭하는 데 사용됩니다. 숫자에 따라 세그먼트 디스플레이 코드를 생성하여 AN을 통해 출력하며, NUM_1S는 CA가 0일 때, NUM_10S는 CA가 1일 때 활성화됩니다.

 

 

전체적인 연결 구조를 Schematic 입니다.

 

 

 

 

Testbench를 사용하여 RXD로 들어오는 직렬 데이터 신호를 UART_RX 모듈에서 병렬 데이터로 변환하고, 이 데이터는 FIFO로 전달됩니다. FIFO에 저장된 데이터는 Read 신호가 활성화되면 FIFO의 출력을 통해 나오며, 이 데이터는 TX 모듈의 입력 신호로 전달됩니다. TX 모듈에서는 카운트 신호에 따라 병렬 데이터를 다시 직렬 데이터로 변환하여 출력합니다.