Verilog HDL로 각 모듈을 하나씩 만들어 보자.
두 번째로 수신기를 만들어 보자.
아래 글에서 구현한 매크로 파일을 이용한다.
https://velog.io/@pikamon/Verilog-10#0-매크로-정의
RTL 모듈을 만들어 보자.
`ifndef __UART_RECEIVER_V__
`define __UART_RECEIVER_V__
`include "inc/Defines.vh"
`include "src/Uart8Receiver.v"
module Uart8Receiver #(
parameter OVERSAMPLING = `OVERSAMPLING_16, // use 16x as default
parameter DATA_BITS = `DATA_BITS_8 // use 8bit as default
)(
input wire clk, // baud rate
input wire en, // chip enable
input wire in, // rx line
output reg [DATA_BITS - 1:0] out, // received data
output reg done, // end on transaction
output reg busy, // transaction is in process
output reg err // error while receiving data
);
reg [2:0] state = `STATE_RESET;
reg [1:0] shiftReg = 0; // shift reg for input signal state
reg [7:0] receivedData = 0; // temporary storage for input data
localparam CLK_CNT_WIDTH = $clog2(OVERSAMPLING);
localparam DATA_BITS_WIDTH = $clog2(DATA_BITS);
reg [CLK_CNT_WIDTH - 1:0] clockCount = 0; // count clocks for oversampling
reg [DATA_BITS_WIDTH - 1:0] bitIdx = 0; // index
initial begin
out <= 0;
err <= 0;
done <= 0;
busy <= 0;
end
always @ (posedge clk) begin
shiftReg = { shiftReg[0], in }; // shift left and append in
if (!en) begin
state <= `STATE_RESET;
end
case (state)
`STATE_RESET: begin
out <= 0;
done <= 0;
busy <= 0;
err <= 0;
bitIdx <= 0;
clockCount <= 0;
receivedData <= 0;
if (en) begin
state <= `STATE_IDLE;
end
end
`STATE_IDLE: begin
done <= 1'b0;
if (&clockCount) begin
out <= 8'b0;
busy <= 1'b1;
err <= 1'b0;
bitIdx <= 3'b0;
clockCount <= 4'b0;
receivedData <= 8'b0;
state <= `STATE_DATA_BITS;
end else if (!(&shiftReg) || |clockCount) begin
// Check bit to make sure it's still low
if (shiftReg == 2'b11) begin
err <= 1'b1;
state <= `STATE_RESET;
end
clockCount <= clockCount + 1;
end
end
// Wait 8 full cycles to receive serial data
`STATE_DATA_BITS: begin
if (&clockCount) begin // save one bit of received data
clockCount <= 4'b0;
// TODO: check the most popular value
receivedData[bitIdx] <= shiftReg[0];
if (&bitIdx) begin
bitIdx <= 3'b0;
state <= `STATE_STOP_BIT;
end else begin
bitIdx <= bitIdx + 3'b1;
end
end else begin
clockCount <= clockCount + 1;
end
end
/*
* Baud clock may not be running at exactly the same rate as the
* transmitter. Next start bit is allowed on at least half of stop bit.
*/
`STATE_STOP_BIT: begin
if (&clockCount || (clockCount >= 4'h8 && !(|shiftReg))) begin
state <= `STATE_IDLE;
done <= 1'b1;
busy <= 1'b0;
out <= receivedData;
clockCount <= 4'b0;
end else begin
clockCount <= clockCount + 1;
// Check bit to make sure it's still high
if (!(|shiftReg)) begin
err <= 1'b1;
state <= `STATE_RESET;
end
end
end
default:
state <= `STATE_IDLE;
endcase
end
endmodule
`endif /*__UART_RECEIVER_V__*/
state machine을 이용하여 동작을 관리한다. in 라인에 LOW가 들어올 때까지 대기하다가 clockCount가 한 바퀴 돌 때까지 LOW로 읽히면 정상적인 LOW 신호로 간주하고 데이터를 읽는다. 그리고 stop bit까지 읽은 후, 바이트 형태로 변환된 데이터를 출력으로 내보낸다.
(코드를 다시 확인해보니 데이터 비트를 8비트로 간주하고 구현한 부분이 있어서, 추후에 수정이 필요함.)
위 모듈을 테스트하기 위한 테스트벤치를 만들어 보자.
`timescale 10ns/1ns
`include "inc/Defines.vh"
`include "src/Uart8Receiver.v"
module tbUart8Receiver();
parameter CLOCK_RATE = `CLOCK_RATE_100MHZ;
parameter BAUD_RATE = `BAUD_RATE_115200;
parameter OVERSAMPLING = `OVERSAMPLING_16;
parameter DATA_BITS = `DATA_BITS_8;
localparam MAX_RATE_RX = CLOCK_RATE / (2 * BAUD_RATE * OVERSAMPLING);
localparam CLOCK_RX = MAX_RATE_RX * OVERSAMPLING * 2;
reg rxClk = 0;
reg en = 1;
reg in = 1;
wire [DATA_BITS - 1:0] out;
wire done;
wire busy;
wire err;
Uart8Receiver #(
.OVERSAMPLING(OVERSAMPLING),
.DATA_BITS(DATA_BITS)
) test (
.clk(rxClk),
.en(en),
.in(in),
.out(out),
.done(done),
.busy(busy),
.err(err)
);
initial begin
$dumpfile("test.vcd");
$dumpvars(-1, test);
end
initial begin
begin // (0x55)
#CLOCK_RX in = 0; // start bit
#CLOCK_RX in = 1; // data bit (0x55)
#CLOCK_RX in = 0;
#CLOCK_RX in = 1;
#CLOCK_RX in = 0;
#CLOCK_RX in = 1;
#CLOCK_RX in = 0;
#CLOCK_RX in = 1;
#CLOCK_RX in = 0;
#CLOCK_RX in = 1; // stop bit
end
begin // (0x96)
#CLOCK_RX in = 0; // start bit
#CLOCK_RX in = 0; // data bit (0x96)
#CLOCK_RX in = 1;
#CLOCK_RX in = 1;
#CLOCK_RX in = 0;
#CLOCK_RX in = 1;
#CLOCK_RX in = 0;
#CLOCK_RX in = 0;
#CLOCK_RX in = 1;
#CLOCK_RX in = 1; // stop bit
end
end
always begin
#MAX_RATE_RX rxClk = ~rxClk;
// #0.5 rxClk = ~rxClk;
end
initial begin
#25000 $finish;
end
endmodule
in 라인으로 0x55, 0x96에 해당되는 UART 신호를 입력했을 때 이를 원래의 데이터로 잘 변환하는지를 보면 된다.
원래는 보레이트 생성기를 같이 생성해서 검증해야 하는데, 타 모듈 종속성 없이 수신기만 검증하고 싶어서 rx 클럭을 테스트벤치에서 직접 만들도록 했다.
개발 편의를 위해 Makefile을 생성하였다.
all: Uart8Receiver
Uart8Receiver: clean
iverilog -o test.vvp testbench/Uart8Receiver_tb.v
vvp test.vvp
gtkwave test.vcd
clean:
rm -f test.vcd
rm -f test.vvp
make만 입력하면 자동으로 clean 후 시뮬레이션을 돌릴 수 있다.
make를 입력한다.
make
그러면 gtkwave가 실행되며, 아래와 같이 입출력을 확인할 수 있다.
out 라인을 보면 0x55와 0x96이 출력되는 것으로 보아 수신기가 정상 동작했다고 볼 수 있다.
busy 라인을 중점적으로 보면, 최초에 in 라인으로 start bit가 들어오고 data bit가 시작되는 시점에서 busy 라인이 HIGH로 올라가는 것을 볼 수 있다. 그리고 데이터 여덟 비트 및 stop bit를 수신하면서 다시 LOW로 내려간다. 이 때 done 라인이 HIGH로 올라가며 out 라인에 데이터가 출력되는 것을 볼 수 있다.