[2024.06.03.월] 인천인력개발원 하만 세미콘 아카데미
실습 1: FIFO 설계
- FIFO: First In First Out
- 데이터가 잠시 머무르는 버퍼 역할
- EMPTY: 데이터가 없어 출력할 수 없는 경우 1
- FULL: 메모리 공간이 7개가 있을 때, 모든 공간에 데이터가 채워져 있을 때 1
- WR_EN(WRite_ENable): WR_EN이 1이면 CLK에 맞춰 Write 동작 실행, Write_Address를 1씩 증가
- RD_EN(ReaD_ENable): RD_EN이 1이면 CLK에 맞춰 Read 동작 실행, Read_Address를 1씩 증가
- 설계 순서
- RAM 설계
- wr_addr(write_address)와 rd_addr(read_address) 처리
- full, empty 처리
- full은 wr_addr=rd_addr일 때 1이 됨 (wr_addr이 다 채워져 7까지 이동하면 다시 0으로 돌아옴 -> wr_addr =
rd_addr = 0)
- empty는 rd_addr이 8개의 데이터를 모두 읽어 0이 되면 1이 됨 - data write/read 처리
[ FIFO 작동 코드 작성 ]
1. my_ram project를 열어 my_fifo.v로 design source 추가 후 포트 정의

2. my_fifo.v를 set as top

3. ram 선언

4. wr_addr과 rd_addr 선언. 각각 1비트씩 추가한 이유는 full과 empty를 1로 만드는 조건을 설정하기 위함

5. addr_pos(address position): read와 write 중 더 앞서있는 addr을 통해 full과 empty 결정

6. rst = 1이면 write address의 값을 0으로 초기화하고, wr_en이 1일 때 write이 실행되므로 write address의 값을 1씩 증가

7. WR_EN이 1일 때 Write이 실행되므로 이 때 Data를 RAM에 저장하는 코드 작성

8. 위와 같은 방식으로 Read 실행 코드 작성, DOUT은 비동기로 값을 전송하기 위해 assign 사용

9. wr_addr과 rd_addr의 msb를 제외한 값이 같음을 확인하기 위한 신호 addr_eq 생성

10. address position은 read address와 wirte address의 msb가 다른 지 확인하여 값 지정
- wirte가 read보다 먼저 진행되므로 wr_addr의 msb값이 rd_addr의 msb보다 클 것
- wirte가 모두 진행되어 한바퀴 돌아오면 wr_addr의 msb는 1이 되고, rd_addr의 msb는 0임을 이용

11. full과 empty를 지정하기 위한 코드 작성
- FULL: addr_eq가 1이면 write와 read가 진행된 상태, addr_pos가 0이면 두 msb가 같으므로 모든 데이터가 채워져 있음을 이용하여 1 설정
- EMPTY: addr_eq가 1이어서

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]; //4bit 메모리 정의
reg [4:0] wr_addr, rd_addr; //write/read address 정의. full과 empty를 위해 msb에 1비트 추가
wire addr_pos; //full/empty 신호 생성을 위한 임시 신호
always @(posedge CLK)
begin
if (RST)
wr_addr <= 5'd0; //reset에서 wr_addr의 값을 0으로 초기화
else if(WR_EN)
wr_addr <= wr_addr + 1; //wr_en을 통해 write이 실행되면 wr_addr의 값이 1 증가
end //always
always @(posedge CLK)
if(WR_EN)
ram[wr_addr] <= DIN; //WR_EN이 1일 때 write가 실행되므로 ram의 "wr_addr"번째 주소에 DIN값 저장
always @(posedge CLK)
begin
if (RST)
rd_addr <= 5'd0; //reset에서 rd_addr의 값을 0으로 초기화
else if(RD_EN)
rd_addr <= rd_addr + 1; //rd_en을 통해 read이 실행되면 rd_addr의 값이 1 증가
end
assign DOUT = ram[rd_addr]; //ram의 "rd_addr"번째 값을 DOUT에 인가
wire addr_eq = (wr_addr[3:0] == rd_addr[3:0]); //check address equivalence
assign addr_pos = rd_addr[4] ^ wr_addr[4]; //read address와 write address의 값이 다를 때 1 출력하기 위해 xor 사용
assign FULL = addr_eq &(~addr_pos);
assign EMPTY = addr_eq & addr_pos;
endmodule
[ FIFO 테스트벤치 코드 작성 ]
1. my_fifo_tb.v로 simulation sources 추가
2. 포트 매핑을 하기 위해 변수 설정(my_fifo.v의 input은 reg로, output은 wire로 선언)

3. my_fifo.v 의 my_fifo모듈 인스턴스화 및 포트 매핑

인스턴스화는 아래 게시글 참고 ↓
하만(Harman) 세미콘 아카데미 52일차 - Verilog HDL 설계(UART 실습, RAM 설계)
[2024.05.27.월] 인천인력개발원 하만 세미콘 아카데미실습 1: uart_rx 마무리이전에 생성한 UART_RX 실습에서 버튼을 입력하면 ASCHII코드의 번호의 두 배가 되어 세그먼트에 출력되는 오류 수정 필요1. u
semicon-circuit.tistory.com
4. rst신호와 clk신호 생성
- 테스트벤치 시작 지점에서 rst은 한 번 켰다 끄기
- clk은 초기값을 0으로 설정 후, 반주기마다 그 값을 반전하여 한 주기씩 clk이 켜졌다 꺼지도록 설정

5. din값을 생성하기 위한 코드 작성

6. 16clk마다 wr_en과 rd_en을 활성하는 코드 작성

17. EMPTY가 1이면 READ할 데이터가 없으므로 Read_Address를 고정하기 위해 my_fifo.v의 코드 수정

18. 시뮬레이션 결과 확인

- wr_en이 1이 되면 din에 데이터가 입력되기 시작, 이 값이 ram에도 저장
- 입력이 끝나면 모든 메모리에 데이터가 들어갔으므로 full이 1
- rd_en이 1이 되면 dout으로 데이터 출력 시작
- 데이터 출력이 끝나면 다시 empty 값이 1
[ my_fifo.v 전체 코드 ]
my_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]; //4bit 메모리 정의
reg [4:0] wr_addr, rd_addr; //write/read address 정의. full과 empty를 위해 msb에 1비트 추가
wire addr_pos; //full/empty 신호 생성을 위한 임시 신호
always @(posedge CLK)
begin
if (RST)
wr_addr <= 5'd0; //reset에서 wr_addr의 값을 0으로 초기화
else if(WR_EN)
wr_addr <= wr_addr + 1; //wr_en을 통해 write이 실행되면 wr_addr의 값이 1 증가
end //always
always @(posedge CLK)
if(WR_EN)
ram[wr_addr] <= DIN; //WR_EN이 1일 때 write가 실행되므로 ram의 "wr_addr"번째 주소에 DIN값 저장
always @(posedge CLK)
begin
if (RST)
rd_addr <= 5'd0; //reset에서 rd_addr의 값을 0으로 초기화
else if(RD_EN & ~EMPTY) //EMPTY가 1이면 데이터가 없으므로 실행 불가
rd_addr <= rd_addr + 1; //rd_en을 통해 read이 실행되면 rd_addr의 값이 1 증가
end
assign DOUT = ram[rd_addr]; //ram의 "rd_addr"번째 값을 DOUT에 인가
wire addr_eq = (wr_addr[3:0] == rd_addr[3:0]); //check address equivalence
assign addr_pos = rd_addr[4] == wr_addr[4]; //read address와 write address의 값이 같을 때 1을 출력하여 순서 확인
assign FULL = addr_eq &(~addr_pos);
assign EMPTY = addr_eq & addr_pos;
endmodule
[ my_fifo_tb.v 전체 코드 ]
my_fifo_tb.v
`timescale 1ns / 1ps
module my_fifo_tb( );
parameter CLK_PD = 8.00;
reg rst, clk, wr_en, rd_en;
reg [7:0] din;
wire full, empty;
wire [7:0] dout;
my_fifo uut(
.RST (rst),
.CLK (clk),
.DIN (din),
.WR_EN (wr_en),
.FULL (full),
.DOUT (dout),
.RD_EN (rd_en),
.EMPTY (empty)
);
initial begin
rst = 1'b1;
#(CLK_PD*10);
rst = 1'b0;
end
initial clk = 1'b0;
always #(CLK_PD/2) clk = ~clk;
//din 값 생성
always @(posedge clk)
begin
if(rst)
din <= 8'd0; //reset이 실행되면 din의 값을 모두 0으로 초기화
else if(wr_en)
din <= din + 1;
end//always end
initial begin
wr_en = 1'b0;
rd_en = 1'b0;
wait(rst == 1'b0); //reset이 0이되면 기능 동작
#(CLK_PD*20); //20clk 여유
wr_en = 1'b1;
repeat(17) @(posedge clk); //clk이 17번 실행될동안 대기 처음 실행 후 WRITE가 실행되므로 그 후 16번동안 진행
//(16clk동안 write 실행 -> FIFO에서 RAM에 데이터 저장 과정, 이 때 wr_addr이 0~15까지 증가 -> 이후 FULL이 1이 되어야 함)
wr_en = 1'b0;
#(CLK_PD*20);
rd_en = 1'b1; //잠시 대기 후 READ 실행
wait (empty == 1'b1); //empty check(READ가 모두 진행되면 empty가 1이 됨)
#(CLK_PD*20);
rd_en = 1'b0;
wr_en = 1'b1;
@(posedge clk); //write 실행 후 1clk 뒤에 read 실행
wr_en = 1'b0;
rd_en = 1'b1;
@(posedge clk); //1clk 뒤에 read 실행 종료
rd_en = 1'b0;
#(CLK_PD*10);
$finish;
end
endmodule
실습 2. FIFO 적용 UART 실습

1. my_uart.xpr 프로젝트에 my_fifo.v를 Design Source로 추가한 뒤 uart_rx와 display_inf 사이에 모듈 추가

2. my_fifo 모듈의 wr_addr, rd_addr, empty, full, wr_en, rd_en을 make debug
3. my_uart_contl 모듈의 rx_data, rx_data_rdy를 make debug
4. Debug.xdc 파일 내용 삭제
5. Open Elaborated Design - Schematic 확인

6. Run Synthesis - Open Synthesized Design - Debug view로 변경
7. Setup Debug 후 저장, 종료
8. Run Implementation
9. Generate Bitstream
10. Open H/W Manager - Program Device
11. Teraterm 실행하여 작동 확인
- 입력 후 데이터를 출력하려면 버튼을 15번 더 눌러야 출력됨
이유:
Read Address = 1인 위치에 값이 Write
-> 직후 Read Address가 1 증가
-> 버튼을 15번 더 누르면 Read Address가 총 15번 증가하여 다시 Read Address = 1로 복귀
-> RAM에 Address가 1인 값을 읽으므로 그 때 처음 입력한 값 출력 - FIFO에 값이 저장되어 있다가 출력됨을 확인
12. 출력이 바로 이뤄지도록 empty가 0이면 dout_reg wire로 데이터를 바로 전달하고, 이를 dout으로 전달하는 코드 작성
[ 수정 후 uart_top.v 코드 ]
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2024/05/24 12:25:55
// 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 RXD,
output [6:0] AN,
output CA,
output PAR_ERR,
output FRM_ERR
);
wire [7:0] rx_data;
wire empty, rx_data_rdy;
reg [7:0] dout;
wire [7:0] dout_reg;
always @(posedge CLK)
begin
if(!empty)
dout <= dout_reg;
end
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 (FULL),
.DOUT (dout_reg),
.RD_EN (~empty),
.EMPTY (empty)
);
display_inf disp_0 (
.RST (RST),
.CLK (CLK), // 125 Mhz
.NUM_1S (dout[3:0]),
.NUM_10S (dout[7:4]),
.CA (CA),
.AN (AN)
);
endmodule
13. Program Device까지 다시 수행 후 작동 확인
- 버튼을 누르는 즉시 그 값이 정상적으로 출력됨
'하만(Harman) 세미콘 반도체 설계 과정 > Verilog를 이용한 RTL 시스템 반도체 설계' 카테고리의 다른 글
하만(Harman) 세미콘 아카데미 61일차 - Verilog HDL 설계(UART_TX 설계) (0) | 2024.06.10 |
---|---|
하만(Harman) 세미콘 아카데미 52일차 - Verilog HDL 설계(UART 실습, RAM 설계) (0) | 2024.05.27 |
하만(Harman) 세미콘 아카데미 47일차 - Verilog HDL 설계(UART 실습) (0) | 2024.05.20 |
하만(Harman) 세미콘 아카데미 46일차 - Verilog HDL 설계(Serial to Parallel, Parallel to Serial 변환) (0) | 2024.05.17 |