[Verilog] UART 컨트롤러를 만들어 보자 (4) - 송신기 구현

pikamon·2022년 5월 1일
0

Verilog

목록 보기
10/12

Verilog HDL로 각 모듈을 하나씩 만들어 보자.


세 번째로 송신기를 만들어 보자.

0. 매크로 정의

아래 글에서 구현한 매크로 파일을 이용한다.

https://velog.io/@pikamon/Verilog-10#0-매크로-정의

1. RTL 구현

RTL 모듈을 만들어 보자.

  • Uart8Transmitter.v
`ifndef __UART_TRANSMITTER_V__
`define __UART_TRANSMITTER_V__

`include "inc/Defines.vh"
`include "src/Uart8Transmitter.v"

module Uart8Transmitter #(
        parameter DATA_BITS = `DATA_BITS_8 // use 8bit as default
    )(
        input wire                   clk,      // baud rate
        input wire                   en,       // chip enable
        input wire                   start,    // start of transaction
        input wire [DATA_BITS - 1:0] in,       // data to transmit
        output reg                   out,      // tx bit
        output reg                   done,     // end on transaction
        output reg                   busy      // transaction is in process
    );

    reg [2:0] state  = `STATE_RESET;
    reg [7:0] data   = 0; // to store a copy of input data

    localparam DATA_BITS_WIDTH = $clog2(DATA_BITS);

    reg [DATA_BITS_WIDTH - 1:0] bitIdx = 0; // index

    initial begin
        out  <= 1;
        done <= 0;
        busy <= 0;
    end

    always @ (posedge clk) begin
        case (state)
            `STATE_IDLE: begin
                out    <= 1; // drive line high for idle
                done   <= 0;
                busy   <= 0;
                bitIdx <= 0;
                data   <= 0;
                if (start & en) begin
                    data  <= in; // save a copy of input data
                    state <= `STATE_START_BIT;
                end
            end

            `STATE_START_BIT: begin
                out   <= 1'b0; // send start bit (low)
                busy  <= 1'b1;
                state <= `STATE_DATA_BITS;
            end

            `STATE_DATA_BITS: begin // Wait 8 clock cycles for data bits to be sent
                out <= data[bitIdx];
                if (&bitIdx) begin
                    bitIdx <= 0;
                    state  <= `STATE_STOP_BIT;
                end else begin
                    bitIdx <= bitIdx + 1'b1;
                end
            end

            `STATE_STOP_BIT: begin // Send out Stop bit (high)
                done  <= 1'b1;
                data  <= 8'b0;
                out   <= 1'b1;
                state <= `STATE_IDLE;
            end

            default:
                state <= `STATE_IDLE;
        endcase
    end

endmodule

`endif /*__UART_TRANSMITTER_V__*/

수신기와 마찬가지로 state machine을 이용하여 동작을 관리한다. start 라인에 신호가 들어오면 start bit - data bits - stop bit를 순서대로 생성하여 내보낸다.

2. Testbench 구현

위 모듈을 테스트하기 위한 테스트벤치를 만들어 보자.

  • Uart8Transmitter_tb.v
`timescale 10ns/1ns

`include "inc/Defines.vh"
`include "src/Uart8Transmitter.v"

module tbUart8Transmitter();

	parameter CLOCK_RATE   = `CLOCK_RATE_100MHZ;
	parameter BAUD_RATE    = `BAUD_RATE_115200;
	parameter DATA_BITS    = `DATA_BITS_8;

    localparam MAX_RATE_TX = CLOCK_RATE / (2 * BAUD_RATE);
	localparam CLOCK_TX = MAX_RATE_TX * 2;

	reg                   txClk = 0;
	reg                   en    = 1;
	reg                   start = 0;
	reg [DATA_BITS - 1:0] in    = 0;

	wire out;
	wire done;
	wire busy;

	Uart8Transmitter #(
        .DATA_BITS(DATA_BITS)
    ) test (
		.clk(txClk),
        .en(en),
        .start(start),
        .in(in),
        .out(out),
        .done(done),
        .busy(busy)
    );

	initial begin
		$dumpfile("test.vcd");
		$dumpvars(-1, test);
    end

	initial begin
		begin // (0x55)
			in <= `DATA_BITS_8'h55;

			#CLOCK_TX start <= 1'b1;
			#CLOCK_TX start <= 1'b0;

			#CLOCK_TX; // start bit
			#CLOCK_TX; // data bit
			#CLOCK_TX;
			#CLOCK_TX;
			#CLOCK_TX;
			#CLOCK_TX;
			#CLOCK_TX;
			#CLOCK_TX;
			#CLOCK_TX;
			#CLOCK_TX; // stop bit
		end

		begin // (0x96)
			in <= `DATA_BITS_8'h96;

			#CLOCK_TX start <= 1'b1;
			#CLOCK_TX start <= 1'b0;

			#CLOCK_TX; // start bit
			#CLOCK_TX; // data bit
			#CLOCK_TX;
			#CLOCK_TX;
			#CLOCK_TX;
			#CLOCK_TX;
			#CLOCK_TX;
			#CLOCK_TX;
			#CLOCK_TX;
			#CLOCK_TX; // stop bit
		end
	end

	always begin
		#MAX_RATE_TX txClk = ~txClk;
//		#0.5 txClk = ~txClk;
	end

	initial begin
		#25000 $finish;
	end

endmodule

in 라인으로 데이터 0x55와 0x96을 주었을 때, 이에 해당되는 UART 신호를 정상적으로 생성하여 출력하는지를 보면 된다.

수신기와 마찬가지로 tx 클럭을 테스트벤치에서 직접 만들도록 했다.

3. Makefile 정의

개발 편의를 위해 Makefile을 생성하였다.

  • Makefile
all: Uart8Transmitter

Uart8Transmitter: clean
	iverilog -o test.vvp testbench/Uart8Transmitter_tb.v
	vvp test.vvp
	gtkwave test.vcd

clean:
	rm -f test.vcd
	rm -f test.vvp

make만 입력하면 자동으로 clean 후 시뮬레이션을 돌릴 수 있다.

4. 실행 및 결과

make를 입력한다.

make

그러면 gtkwave가 실행되며, 아래와 같이 입출력을 확인할 수 있다.

in 라인에 0x55와 0x96을 차례로 주었을 때, out 라인에 start-data-stop 비트가 차례로 생성되는 것을 볼 수 있다. 출력을 시작할 때와 끝낼 때 busy 라인이 토글되며, 출력이 완료되면 done 라인에 HIGH가 출력되는 것을 볼 수 있다.

profile
개발자입니당 *^^* 깃허브 https://github.com/pikamonvvs

0개의 댓글