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

하만(Harman) 세미콘 아카데미 52일차 - Verilog HDL 설계(UART 실습, RAM 설계)

semicon_circuitdesigner 2024. 5. 27. 11:35

[2024.05.27.월] 인천인력개발원 하만 세미콘 아카데미


실습 1: uart_rx 마무리


  • 이전에 생성한 UART_RX 실습에서 버튼을 입력하면 ASCHII코드의 번호의 두 배가 되어 세그먼트에 출력되는 오류 수정 필요

1. uart_rx_cntl.v의 코드를 통해 오류를 발생시키는 부분 확인 

더보기
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2024/05/20 10:15:18
// Design Name: 
// Module Name: uart_rx_cntl
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module uart_rx_cntl(
    input RST,
    input CLK,
(* mark_debug = "true" *)    
    input RXD,
(* mark_debug = "true" *)    
    input BAUD_X16_EN,  //over_sample_cnt_done,
    output [7:0]    RX_DATA,
(* mark_debug = "true" *)    
    output reg             RX_DATA_RDY,
(* mark_debug = "true" *)
    output  reg     FRM_ERR,
(* mark_debug = "true" *)
    output                  PARITY_ERR
//    output reg [1:0] fsm_state
    );

localparam [1:0]    idle = 2'b00,
                    start = 2'b01,
                    data  = 2'b10,
                    stop = 2'b11;
(* mark_debug = "true" *)
reg [1:0]   curr_state;//
reg [1:0]  next_state;
(* mark_debug = "true" *)
wire        over_sample_cnt_done;
(* mark_debug = "true" *)
reg [3:0]   over_sample_cnt;
(* mark_debug = "true" *)
reg [3:0]   bit_cnt;
(* mark_debug = "true" *)
wire        bit_cnt_done;
(* mark_debug = "true" *)
reg [8:0]   rx_frame;

reg rx_d;

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 
//            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
  • 위dml 기존의 코드에서 line 64~84부분을 아래에서 살펴보면 curr_state가 start일 때 RXD가 0이면 데이터가 입력되며 넘어가야 하는데 RXD가 0이 됨을 인식했을 때 다시 curr_state가 idle로 넘어가며 두 번씩 반복되는 현상 발생 -> ASCHII코드 값의 두 배가 출력됨
  • RXD는 기본이 1: 값 수신 X / 0: 값 수신 O
  • over_sample_cnt_done이 0이면 값을 수신하지 말아야 함 -> 다시 curr_state를 start로 바꾸어 다음 값을 받도록 수정
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

 

따라서 위의 부분을 아래와 같이 변경

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 begin
                next_state = start;
            end
//            fsm_state = 2'b01;
        end
  • 위의 경우 입력이 시작된 이후 start 내에서 동작하므로 중복 x
  • PARITY ERROR와 FRAME ERROR도 발생하지 않았음 확인
  • 에러 수정 과정 및 디버깅

 

  • 정상 작동 영상

 

[ 수정된 uart_rx_cntl.v 전체 코드 ]

더보기
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2024/05/20 10:15:18
// Design Name: 
// Module Name: uart_rx_cntl
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module uart_rx_cntl(
    input RST,
    input CLK,
(* mark_debug = "true" *)    
    input RXD,
(* mark_debug = "true" *)    
    input BAUD_X16_EN,  //over_sample_cnt_done,
    output [7:0]    RX_DATA,
(* mark_debug = "true" *)    
    output reg             RX_DATA_RDY,
(* mark_debug = "true" *)
    output  reg     FRM_ERR,
(* mark_debug = "true" *)
    output                  PARITY_ERR
//    output reg [1:0] fsm_state
    );

localparam [1:0]    idle = 2'b00,
                    start = 2'b01,
                    data  = 2'b10,
                    stop = 2'b11;
(* mark_debug = "true" *)
reg [1:0]   curr_state;//
reg [1:0]  next_state;
(* mark_debug = "true" *)
wire        over_sample_cnt_done;
(* mark_debug = "true" *)
reg [3:0]   over_sample_cnt;
(* mark_debug = "true" *)
reg [3:0]   bit_cnt;
(* mark_debug = "true" *)
wire        bit_cnt_done;
(* mark_debug = "true" *)
reg [8:0]   rx_frame;

reg rx_d;

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 begin
                next_state = start;
            end
//            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

 

  • UART RX Schematic

 


실습 2: 메모리(RAM) 설계


  • ROM(Read Only Memory): 한 번 기록된 정보가 전원과 무관하게 반영구적으로 기억되는 삭제나 수정이 불가능한 기억장치 
  • RAM(Random Access Memory): 사용자가 자유롭게 데이터를 읽고, 쓰고, 지울 수 있는 기억장치
  • 아래 코드에서는 두 개의 ROM이 존재(NUM_10S에서와 NUM_1S에서)
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 = 8'h7e;
            4'd1 : AN = 8'h30;
            4'd2 : AN = 8'h6d;
            4'd3 : AN = 8'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

 

  • FIFO(First Input First Output): 선입선처리에 따라 순서대로 데이터를 처리하는 형식 

 [ RAM 설계 참고사항 ]

  • PORT
    • RST
    • CLK
    • DIN [7:0]
    • DOUT [7:0]
    • RW (1: READ, 0: WRITE)
    • ADDR [3:0] (Address) -> 총 4비트이므로 Address가 0~15까지
  • 위의 Address 선언에 맞게 메모리 구성 -> reg [7:0] mem[0:15]
    • Read: mem[ADDR] = DIN
    • Write: DOUT = mem[ADDR]

1. my_ram 프로젝트 만들기

2. Add Source로 my_ram.v 생성 후 I/O 포트 정의

 

3. 메모리를 생성하기 위해 위의 참고사항을 이용해 reg 선언

reg [7:0] mem [0:15];

 

4. CLK의 Rising Edge마다 Read를 기본으로 실행하기 위해(Reset이 0이 아닐 때) 코드 작성

always @(posedge CLK) //read 부분
begin
    if(RST)
        DOUT <= 8'd0;
    else
        DOUT <= mem [ADDR]; //RST이 0이 아니라면 기본으로 Read를 실행
end

 

5. RW 모드가 0이면 Write 실행하기 위한 코드 작성

always @(posedge CLK)
begin
    if(RW == 1'b0)  //RW의 모드가 0이므로 Write 실행
        mem[ADDR] <= DIN;
end

 

6. 위와 같이 코드를 작성할 경우, ADDR이 Read와 Write 모드에서 동일할 때 출력되는 값(Read되는 값)이 기존의 Data일 수도 있고, 새롭게 Write된 값일 수 있으므로 이를 지정하기 위해 Write First로 진행하여 방지 가능

  • 또는 Read_addr와 Write_addr를 분리하여 위와 같은 오류를 방지할 수 있음

 

7. Simulation 돌려보기 위해 testbench 파일을 만들고 검증하기

7-1. my_ram 모듈을 인스턴스화하기 위해 코드 작성 후 포트 맵핑

my_ram uut(
    .RST    (rst),
    .CLK    (clk),
    .ADDR   (addr),
    .DIN    (din),
    .DOUT   (dout),
    .RW     (rw)
    );

인스턴스화(Instantiation)란?


다른 파일에서 선언한 모듈을 가져올 때 사용하는 방법.

위 코드에서는 my_ram.v 파일에서 선언한 my_ram이라는 모듈을 my_ram_tb.v파일로 가져오기 위해 사용함.

 

[선언 형식]

가져올 module 이름    module 이름 선언 (

.가져올 module의 port1          (연결할 현재 module의 port1),

.가져올 module의 port2          (연결할 현재 module의 port2),

.가져올 module의 port3          (연결할 현재 module의 port3),

....

....

);

 

7-2. 아래에서 사용할 i를 integer로 선언 후 시뮬레이션 코드 작성

[ 전체 코드 ]

더보기
`timescale 1ns / 1ps

module my_ram_tb();
parameter CLK_PD = 8.0;
reg rst, clk, rw;
reg [3:0] addr;
reg [7:0] din;
wire [7:0] dout;

integer i;

my_ram uut(
    .RST    (rst),
    .CLK    (clk),
    .ADDR   (addr),
    .DIN    (din),
    .DOUT   (dout),
    .RW     (rw)
    );
    
initial begin   //testbench 코드 작성할 때 초기에 rst을 ON/OFF 해주고 시작하는 부분
    rst = 1'b1;
    #(CLK_PD*10);
    rst = 1'b0;
end    
 
initial clk = 1'b0;
always #(CLK_PD/2) clk = ~clk;  //clk를 0에서 시작하여 CLK_PD를 주기로 1, 0을 반복하도록 신호를 만드는 코드

initial begin
    addr = 4'd0;
    din = 8'd0;
    rw = 1'b1;  //이 세 줄은 초기조건 설정
    wait (rst == 1'b0);
    #(CLK_PD*20);
    rw = 1'b0;  //wirte 모드 시작
    for (i = 0; i < 16; i = i+1) begin //memory의 addr공간이 0~15까지 존재하므로 각 주소에 0~15까지 입력
        addr = i;
        din = i;
        #(CLK_PD);
    end
    #(CLK_PD*20);
    rw = 1'b1;  //read 모드 시작
    for (i = 0; i < 16; i = i+1) begin //memory의 addr공간이 0~15까지 존재하므로 각 주소0~15까지의 Data를 불러오기
        addr = i;
        #(CLK_PD);
    end

    #1000;
    $finish;
end
endmodule

 

7-3. 시뮬레이션 결과: 각 Address에 Write하는 과정(위)과 Address에서 Read하는 과정(아래)

각 Clock마다 지정된 Address(i 값과 동일)에 Data(i 값과 동일)이 입력
각 Clock마다 지정된 Address(i 값과 동일)의 Data이 출력

  • 위 RAM의 Schematic

 

 

8. 위 Simulation 결과중 dout이 wirte모드에서 Unknown이 되지 않도록 하기 위해 -> memory의 초기값을 0으로 설정

memory 생성을 위해 mem을 reg로 선언한 코드를 다음과 같이 변경: (2차원 verilog 배열 초기화)

reg [7:0] mem [0:15] = {default:0};

testbench 결과 dout이 초기값0으로 설정됨 확인

 

9. always @(posedge CLK)구문은 Synchronous, assign구문은 Asynchronous이므로 assign으로 변경해보기(my_ram.v파일 수정)

module my_ram(
    input RST,
    input CLK,
    input [3:0] ADDR,
    input [7:0] DIN,
    output [7:0] DOUT,
    input RW
    );

//reg [7:0] mem [0:15];  //메모리를 생성하기 위해 reg 사용 reg [7:0]은 데이터 크기의 맞춤. [0:15]는 ADDR이 4비트이므로 0~15로 선언
reg [7:0] mem [0:15]; //선언한 memory 0으로 초기화하기 

assign DOUT = mem[ADDR];
/**********
always @(posedge CLK) //read 부분
begin
    if(RST)
        DOUT <= 8'd0;
    else
        DOUT <= mem [ADDR]; //RST이 0이 아니라면 기본으로 Read를 실행
end
***********/

always @(posedge CLK)
begin
    if(RW == 1'b0) //RW의 모드가 0이므로 Write 실행
        mem[ADDR] <= DIN;

end
endmodule

 

 

10. testbench 결과: always구문을 사용했을 때와 다르게 din 값이 즉각적으로 변함