하만(Harman) 세미콘 반도체 설계 과정/Verilog를 이용한 RTL 시스템 반도체 설계

하만(Harman) 세미콘 아카데미 57일차 - Verilog HDL 설계(FIFO 설계, 테스트벤치 코드 작성방법)

semicon_circuitdesigner 2024. 6. 3. 11:11

[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씩 증가 
  • 설계 순서
    1. RAM 설계
    2. wr_addr(write_address)와 rd_addr(read_address) 처리
    3. full, empty 처리
       - full은 wr_addr=rd_addr일 때 1이 됨 (wr_addr이 다 채워져 7까지 이동하면 다시 0으로 돌아옴 -> wr_addr =
          rd_addr = 0)
       - empty는 rd_addr이 8개의 데이터를 모두 읽어 0이 되면 1이 됨 
    4. 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까지 다시 수행 후 작동 확인

  • 버튼을 누르는 즉시 그 값이 정상적으로 출력됨