__________

Designing the Future with Circuits

반도체 회로설계 취준기

자습시간/Verilog

UART 설계(2) - UART TX 설계

semicon_circuitdesigner 2024. 7. 26. 15:53
UART TX 모듈 설계

 

  • 신호 개요
    • DIN [7:0]: 데이터 버스로부터 받는 8비트의 parallel 데이터묶음
    • Send: 데이터 전송의 시작을 명령하는 신호
    • Dout: RX로 전달되는 serial 데이터
    • Busy: UART TX가 동작중임을 나타내는 신호
    • ParitySelect: Odd Parity를 사용할지 Even Parity를 사용할지 선택하는 신호
  • 작동 방식
    • 8비트의 parallel 데이터를 버스로부터 수신한다.
    • send 신호가 들어오면 stop bit - parity bit - data - start bit 순으로 구성된 데이터 11비트를 dout으로 전송한다.

[ uart_tx.v ]

더보기
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2024/07/23 14:28:21
// Design Name: 
// Module Name: uart_tx
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module uart_tx(
    input rst,
    input clk,
    input [7:0] din,
    input send,
    input parity,
    output busy,
    output reg dout
    
    );

reg [1:0] state;
reg [3:0] bit_cnt;
reg [10:0] data;
wire [7:0] DIN;
assign DIN = din;
wire PARITY;
assign PARITY = parity;

always @(posedge clk) begin
    if(rst) dout = 1 ;
end

localparam [1:0]    idle = 2'b00,
                    load = 2'b01,
                    transmit = 2'b10,
                    stop = 2'b11;
localparam odd = 1'b0, even = 1'b1;

always@(posedge clk) begin
    case (state)
        idle: begin
            dout = 1;
            if(send)    state = load;
            else    state = idle;
        end
        load: begin
            data = {1'b1, PARITY, DIN[7:0], 1'b0};  //stop bit - parity - data - start bit
            state = transmit;
        end
        transmit:begin
            if(bit_cnt < 12) begin
                dout = data [bit_cnt];
                bit_cnt = bit_cnt + 1;
            end
            else state = stop;
        end
        stop: begin
            dout = 1'b1;
            state = idle;
        end
    endcase
end
endmodule

 

 

코드 수정 1: FSM의 State가 1clk마다 넘어가는 현상 해결

 

  • 수정사항: idle 상태에서 입력 신호인 DIN이 있을 때에만 데이터를 입력 후 넘어가도록 코드를 수정하였다.
  • 위의 문제를 해결하기 위해 다음과 같이 코드를 변경하였으나 load상태에서 FSM이 변하지 않아 din 신호가 unknown이 아니고 send 신호가 1이면 load 상태로 변하도록 코드를 수정하였다.

결과: 여전히 의도한대로 send와 din이 들어왔음에도 state가 넘어가지 않는다.

코드 수정 2: send 신호가 들어오면 load로 넘어가고, 이 때 din 신호에 따라 data에 입력하도록 코드 수정

이번에는 data(8.0 아래 신호)로 din인 1001110이 넘어가지 않는 문제가 발생했다.

코드 수정 3: din으로 들어오는 데이터를 매 clock마다 DIN으로 전달하도록 하고, DIN이 X값이 아닐 때 로드하도록 수정
  • DIN이 X가 아닌 경우 그 값은 0 이상이므로 해당 조건을 이용하여 코드를 수정하였다.
  • 위 수정사항 적용 결과 DIN의 데이터가 data 내에 정상적으로 전달된 것을 확인할 수 있다.


  • 동작이 끝난 이후 상태가 넘어갈 때 존재하지 않는 data[12]가 dout으로 출력되어 x값(빨간색)으로 출력되는 문제가 발생했다.
  • 또한 idle 상태에서는 dout이 1로 출력되어야 하는데 0으로 출력되는 문제가 발생했다.
코드 수정 4: dout이 unknown(x)으로 출력되는 오류 수정
  • 아래와 같이 if문 내의 조건이 bit_cnt < 12이었던 것을 bit_cnt<11로 수정하여 bit_cnt가 11까지만 증가하도록 수정하였다.
  • 그 결과, 아래와 같이 bit_cnt가 11까지만 작동하고, 각 clk마다 DOUT 신호로 data의 값(start bit~stop bit)이 정확히 전달되었다. 그러나 여전히 dout신호가 idle에서 1로 바뀌지 않는 문제가 남아있다.
    dout신호로 start bit 0이 전달된 이후 LSB부터 MSB까지 전달된다. 그 후로 Parity Bit와 Stop Bit가 각각 전달되었다.
코드 수정 5: idle 상태에서 DIN이 unknown이 아니고, send 신호가 들어올 때 load를 실행하도록 조건 변경
  • idle에서 dout을 1로 초기화하고, 위의 조건에서만 load를 실행하도록 조건을 변경하였다.
  • dout은 transfer을 제외한 상태에서는 항상 1로 출력되도록 코드를 수정했다.


  • 또한 current state와 next state를 구분하여 fsm 작성 시 발생하는 딜레이를 줄이기 위해 state 변수만 두고 이를 기반으로 fsm을 동작하도록 코드를 수정하였다.
  • 그 결과, 다음과 같이 transfer 동작 외에는 항상 DOUT이 출력되고, DOUT은 Start bit,Data, Parity Bit, Stop Bit를 모두 순서대로 출력한다.

[ 최종 uart_tx.v ]

더보기
`timescale 1ns / 1ps

module uart_tx(
    input rst,
    input clk,
    input [7:0] din,
    input send,
    input parity,
    output reg busy,
    output reg dout
    );

//reg [1:0] currstate;
reg [1:0] state;
reg [3:0] bit_cnt = 4'b0;
reg [10:0] data;
reg [7:0] DIN;
reg PARITY;

localparam [1:0]    idle = 2'b00,
                    load = 2'b01,
                    transmit = 2'b10,
                    stop = 2'b11;
localparam odd = 1'b0, even = 1'b1;

always @(posedge clk) begin
    if(rst) begin dout <= 1'b1; busy <= 1'b0; state <= idle; end
end

always @(posedge clk) begin
    //currstate <= nextstate;
    PARITY <= parity;
    DIN <= din;
end

always@(posedge clk) begin
    case (state)
    //idle
        idle: begin
            busy = 1'b0;
            dout = 1'b1;
            data = 11'b0;
            bit_cnt = 0;
            if((DIN>=0)&&send) begin
                //dout = 0;
                state = load;
            end
            else state = idle;
        end
        //load data from din
        load: begin
            if(DIN >= 0) begin
                data = {1'b1, PARITY, DIN, 1'b0};  //stop bit - parity - data - start bit
                if (send) state = transmit;
            end else state = load;
        end
        //transmit
        transmit:begin
            if(bit_cnt < 11) begin
                busy = 1;
                dout = data [bit_cnt];
                bit_cnt = bit_cnt + 1;
            end
            else begin dout = 1'b1; state = idle; end
        end
        //stop
        /*stop: begin
            dout = 1'b1;
            nextstate = idle;
        end*/
    endcase
end
endmodule

UART TX와 Baud Rate Generator을 함께 시뮬레이션하여 Enable 신호 확인


uart tx와 baud_rate_gen.v를 uart_top으로 묶어 테스트벤치 실행 결과 enable 신호가 생성되지 않는 오류가 발생했다.
  • 아래 테스트벤치 확인 후 Dout이 Baud_Rate이 아닌 clock에 맞춰 전송되는 오류를 확인했다.

 

아래 글에서 작성한 Baud_Rate_gen 코드를 수정한다. 이번에는 output으로 Baud_Rate를 출력하는 코드를 추가한다. 

 

UART(Universal Asynchronous Receiver/Transmitter)

UART란?UART(Universal Asynchronous Receiver/Transmitter, 범용 비동기화 송수신기)는 데이터 버스로부터 병렬로 받은 데이터를 직렬 방식으로 전환하여 다른 UART로 전송하는 컴퓨터 하드웨어의 한 종류이다.U

semicon-circuit.tistory.com

 

위 글을 참고하여 bit_cnt_std번의 clk마다 Baud_Rate 신호를 trigger해줌으로써 BaudRate 신호를 생성할 수 있다. enable 신호는 이 Baud Rate신호를 기준으로 생성할 수도 있다.

 

Baud Rate를 생성하고, 이에 따라 enable을 동작시키기 위해 코드 수정
  • baud_rate_gen.v 파일에서 clk_cnt 변수를 만들고, bit 변수와 cnt 변수를 제거하여 clk_cnt가 bit_cnt_std의 8배일 때마다 baud_rate를 1로 만들고, 16배가 되면 baud_rate를 0으로 하여 신호를 생성하도록 수정하였다.
  • bit_cnt_std의 16배가 되면 baud_rate가 0으로 바뀌며 이 때 enable 신호를 내보내서 over sampling을 수행할 수 있도록 수정하였다.
더보기

baud_rate_gen.v

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2024/07/23 10:22:39
// Design Name: 
// Module Name: baud_rate_gen
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module baud_rate_gen(
    input clk,
    input rst,
    output enable,
    output baud_rate_signal
    );
    
parameter clk_freq = 125_000_000;
parameter baud_rate_freq = 9600;
parameter bit_cnt_std = clk_freq/(baud_rate_freq*16);

//reg [15:0] bit = 16'd0;
//reg [12:0] cnt = 13'd0;
reg [19:0] clk_cnt = 12'd0;
reg baud_rate = 1'b0;
assign baud_rate_signal = baud_rate;
reg en;
assign enable = en;

always@(posedge clk) clk_cnt = clk_cnt+1;

always@(posedge clk)begin
    if(rst) begin
        en = 0;
        //bit = 0;
        //cnt = 0;
    end
    else begin
        if(clk_cnt > bit_cnt_std*8) begin
            baud_rate = 1;
            //enable to 1 only 1 clock
//            if((bit_cnt_std*8 + 1>clk_cnt) && !baud_rate) en= 1;  
//            else en = 0;
            if(clk_cnt > bit_cnt_std*16)begin
                baud_rate = 0;
                en = 1;
                clk_cnt = 0;
            end
        end
//        if(((bit+8) % 16) == 0) begin
//            en = 1;
//            bit = 0;
//            cnt = 0;
//        end
        else en = 0;
    end //else end
end
endmodule

실행 결과 baud_rate와 enable 신호가 정상적으로 나타난다.
수정한 블록 다이어그램

UART TX가 Baud Rate Generator로부터 Baud Rate를 받고, 이에 맞추어 Dout을 출력하도록 코드 수정
  • bit_cnt 변수는 TX에서 Dout으로 데이터를 내보내는 transmit 상태에서만 증가한다. 이는 Baud Rate의 상승 에지에 맞춰 증가시키면 Baud Rate에 맞게 데이터를 전송할 수 있다.


  • top module에서 위 블록 다이어그램과 같이 Baud Rate Generator에서 UART TX로 Baud Rate 신호를 전달할 수 있도록 다음과 같이 input과 output을 변경하였다.
  • 실행 결과: TX로부터 11비트의 데이터가 dout으로 baud rate에 맞추어 정상적으로 출력된다.

 

[ uart_tx.v ]

더보기
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2024/07/23 14:28:21
// Design Name: 
// Module Name: uart_tx
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module uart_tx(
    input rst,
    input clk,
    input baud_rate,
    input [7:0] din,
    input send,
    input parity,
    output reg busy,
    output reg dout
    );

//reg [1:0] currstate;
reg [1:0] state;
reg [3:0] bit_cnt = 4'b0;
reg [10:0] data;
reg [7:0] DIN;
reg PARITY;

localparam [1:0]    idle = 2'b00,
                    load = 2'b01,
                    transmit = 2'b10,
                    stop = 2'b11;

always @(posedge clk) begin
    if(rst) begin dout <= 1'b1; busy <= 1'b0; state <= idle; end
end

always @(posedge baud_rate) begin
    if(state == transmit) bit_cnt = bit_cnt+1;
end

always @(posedge clk) begin
    //currstate <= nextstate;
    PARITY <= parity;
    DIN <= din;
end

always@(posedge clk) begin
    case (state)
    //idle
        idle: begin
            busy = 1'b0;
            dout = 1'b1;
            data = 11'b0;
            bit_cnt = 0;
            if((DIN>=0)&&send) begin
                //dout = 0;
                state = load;
            end
            else state = idle;
        end
        //load data from din
        load: begin
            if(DIN >= 0) begin
                data = {1'b1, PARITY, DIN, 1'b0};  //stop bit - parity - data - start bit
                if (send) state = transmit;
            end else state = load;
        end
        //transmit
        transmit:begin
            if(bit_cnt < 11) begin
                busy = 1;
                dout = data [bit_cnt];
            end
            else begin dout = 1'b1; state = idle; end
        end
        //stop
        /*stop: begin
            dout = 1'b1;
            nextstate = idle;
        end*/
    endcase
end
endmodule

 

 

[ uart_top.v ]

더보기
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2024/07/28 13:53:22
// Design Name: 
// Module Name: uart_top
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module uart_top(
    input RST,
    input CLK,
    input SEND,
    input BUSY,
    input PARITY,
    input [7:0] DIN,
    output DOUT,
    output ENABLE
    );
    
wire baudrate;

baud_rate_gen #(.clk_freq (13020), .baud_rate_freq (1)) BAUD_RATE_GEN(
    .clk    (CLK),
    .rst    (RST),
    .enable (ENABLE),
    .baud_rate_signal (BAUDRATE)    
    );   
     
uart_tx UART_TX(
    .rst    (RST),
    .clk    (CLK),
    .baud_rate (BAUDRATE),
    .din    (DIN),
    .send   (SEND),
    .parity (PARITY),
    .busy   (BUSY),
    .dout   (DOUT)
    );
endmodule
코드 수정
data의 첫 비트가 출력될 때 baud rate가 0부터 시작하여 enable 신호에 맞게 첫 비트를 읽을 수 없는 오류 수정

위 부분을 확대해보니 dout으로 첫 비트가 나올 때 over sampling이 되지 않음을 확인했다.

  • Baud Rate의 초기값이 0으로 시작하여 발생하는 오류로 확인했고, 초기값을 1로 하여 신호를 생성함으로 문제를 해결했다.

Data의 크기와 Parity Bit의 크기가 달라져도 정상 작동하도록 코드 수정
  • UART에서는 최대 9비트의 데이터를 전달할 수 있고, Parity Bit는 1Bit와 2Bit 두 가지 방법으로 사용 가능하므로, 이를 자유롭게 변경할 수 있도록 다음과 같이 parameter을 선언하고 실행 조건을 변경했다.
    parameter로 설정한 값을 input으로 변경하면 실행 시 데이터 크기와 정지 비트 크기를 입력으로 하여 직접 설정할 수 있다.
  • 위 동작 시 din의 크기는 최대 9비트가 되어야 하고, stop bit도 최대 2비트가 되어야 하므로 input의 din의 크기를 변경하여 코드를 작성해야 한다.
  • data bit의 크기와 stop bit의 크기가 달라지는 경우 이 크기 또한 input으로 설정하여 동작시킬 수 있다.
  • 위 부분은 RX 완료 이후 시도해볼 예정