이번 프로젝트에서는 간단한 Uart 모듈을 Verilog HDL를 활용하여 검증해보도록 하겠습니다.
UART란?
UART (Universal Asynchronous Receiver/Transmitter)는 컴퓨터 하드웨어의 일종으로, 디지털 장치 간에 직렬 통신을 가능하게 하는 통신 인터페이스입니다. 직렬 통신이란 데이터를 한 번에 한 비트씩 비트 스트림 형태로 전송하는 방식을 말합니다. UART는 이러한 데이터 전송을 위해 비동기식 방법을 사용합니다, 즉 송신기와 수신기 사이에 별도의 동기화 클록 신호 없이 데이터를 전송합니다.
UART의 기본 작업은 송신 측에서는 병렬 데이터(한 번에 여러 비트를 처리하는 형태)를 직렬 데이터로 변환하고, 수신 측에서는 이 직렬 데이터를 다시 병렬 데이터로 변환하는 것입니다. 데이터 프레임에는 시작 비트(스타트 비트), 데이터 비트, 선택적 패리티 비트(오류 검출용), 그리고 종료 비트(스톱 비트)가 포함됩니다.
UART는 다양한 전자 장치에서 데이터 통신을 위한 기본적인 수단으로 사용되며, 간단한 구성과 낮은 비용으로 널리 활용됩니다. 예를 들어, 컴퓨터와 마이크로컨트롤러, 센서와 같은 주변 장치 간의 통신, 그리고 저속의 네트워킹 장치에서 주로 사용됩니다.
앞서 Uart에 대한 개념은 아래 글을 참고해주시면 되겠습니다.
https://engineeringstudentww.tistory.com/23
이제 데이터를 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 모듈에서는 카운트 신호에 따라 병렬 데이터를 다시 직렬 데이터로 변환하여 출력합니다.
'하만 세미콘 아카데미 > Verilog HDL' 카테고리의 다른 글
SPI 통신 이론 & 실습 (0) | 2024.06.25 |
---|---|
AXI 수업내용 정리 및 실습 (0) | 2024.06.14 |
UART 이론 (수업시간 내용 정리) (0) | 2024.05.31 |
Verilog HDL 실습 (Stop_Watch) (0) | 2024.05.31 |
Verilog HDL 실습 (Traffic) (0) | 2024.05.31 |