Verilog HDL로 각 모듈을 하나씩 만들어 보자.
세 번째로 송신기를 만들어 보자.
아래 글에서 구현한 매크로 파일을 이용한다.
https://velog.io/@pikamon/Verilog-10#0-매크로-정의
RTL 모듈을 만들어 보자.
`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를 순서대로 생성하여 내보낸다.
위 모듈을 테스트하기 위한 테스트벤치를 만들어 보자.
`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 클럭을 테스트벤치에서 직접 만들도록 했다.
개발 편의를 위해 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 후 시뮬레이션을 돌릴 수 있다.
make를 입력한다.
make
그러면 gtkwave가 실행되며, 아래와 같이 입출력을 확인할 수 있다.
in 라인에 0x55와 0x96을 차례로 주었을 때, out 라인에 start-data-stop 비트가 차례로 생성되는 것을 볼 수 있다. 출력을 시작할 때와 끝낼 때 busy 라인이 토글되며, 출력이 완료되면 done 라인에 HIGH가 출력되는 것을 볼 수 있다.