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

하만(Harman) 세미콘 아카데미 26일차 - Verilog HDL 설계(parameter, tasks, function, parity 설계, 2's complement 설계)

semicon_circuitdesigner 2024. 4. 12. 12:23

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


Verilog를 이용한 RTL 시스템 반도체 설계


시스템 작업(System Tasks)


1. System Tasks: 미리 작성된 코드의 모음

  • Simulation Tasks: 가시성과 디버깅 향상
  • Operations: 콘솔에 출력 / net의 값 모니터링 / 시뮬레이션 시작,정지
  • $로 시작
  • Verilog에서 타이밍 체크

2. System Tasks 예시

  • $display: 콘솔에 값 출력
    • ex. $display("At time %d, q = %b", $time, q);
  • $time, $realtime: 현재의 시뮬레이션 시간을 정수로 표현
  • $wirte: $display와 유사하나 새로운 줄을 자동으로 추가 x
    • $write ("Simulation time is %t", $time);
  • $strobe: 현재 시뮬레이션 사이클의 끝 부분의 값 출력
    • ex. $strobe("At time %d, q = %b", $time, q);
  • $monitor: 값이 변화했을 때만 모니터링, 출력
    • $monitor ("time = %t, clk = %b, d = %b, q = %b", $time, clk, d, q);
  • $monitoron: 모니터링 시작
  • $monitoroff: 모니터링 종료
  • $stop: 조건이 FALSE면 정지
  • $finish: 시뮬레이션 정지
  • $signed: 부호가 있는 변수로 변환
  • $unsigned: 부호가 없는 변수로 변환

3. 데이터 형식

  • %h / %H: Hexadecimal
  • %b / %B: Binary
  • %d / %D: Decimal
  • $s / $S: String
  • $c / $C: ASCHII Character
  • %t / %T: Time
  • %o / %O: Octal
  • %v / %V: Strengths%e / %E: Real w/ Scientific Notification
  • %f / %F: Real w/ Decimal
  • %m / %M: Hierarchical Name

Verilog Subprograms


1. Subprograms vs. Modules

  • Subprograms는 모듈 내외에서 모두 형성 가능하나 모듈은 불가능
  • 모듈은 순차적 구조만 가짐
  • Functions, Tasks는 Subprograms
  • Subprograms는 유연성과 기능성이 우수

2. Tasks & Functions

  • Tasks
    • 다른 Task와 Function 모두 활성화 가능
    • 값 반환 x
    • input, output, inout parameters로 구성
    • non-zero time에 시뮬레이션
    • blocking(<=) & non-blocking(=) statements 허용 (blocking & non-blocking statements: 14일차 게시물 참조)
    • delay, event, timing control 구문 포함
  • Function
    • 다른 Function만 활성화 가능
    • 하나의 값 반환
    • 한 개 이상의 input만 필요
    • 한 개의 시뮬레이션 시간 단위로 실행
    • blocking statment만 사용
    • delay, event, timing control구문 사용 불가
    • input에 따라 작동하며 하나의 값 반환

Verilog Functions


1. Functions 개요

  • Ports
    • Inputs Ports만 존재
    • 최소 한 개의 Input 필요
  • Returns: 한 개의 값 반환
  • Timing & Simulation
    • @posedge, #timing delays는 사용 불가

2. Function 구조

  • keyword 'function': function을 선언하기 위해 키워드로 function 사용
  • identifier: function의 이름을 정의하기 위해 키워드 function 옆에 이름 정의
  • function~endfunction 내부에 함수 정의 및 반환값 정의

3. Function 종류

  • Constant Functions
  • Signed Functions: 반환값으로 signed operation 가능(signed 키워드 사용)

Verilog Tasks


1. Tasks 개요

  • 선언: arguments는 선택사항
  • 반환값 x
  • Timing delay 발생 가능 - non-zero time에 시뮬레이션

2. Task 구조

  • keyword 'task': task를 선언하기 위해 키워드로 task 사용
  • identifier: task의 이름을 정의하기 위해 키워드 task 옆에 이름 정의
  • task~endtask 내부에 지역변수 정의 및 내용 입력

Verilog Compiler Directives


1. Compiler 지시자 개요

  • 지시어 컴파일
  • `를 사용하여 수행
  • 예시: `include, `timescale, `define, `ifdef, `else, `elseif, `endif, `ifndef, `resetall

2. 지시자 종류

  • `include
    • module의 파일 출력
    • 디자인 일관성을 위해 헤더파일 생성
    • 컴파일 실행 중 모든 소스파일을 다른 파일에 삽입
    • 장점: 일반적인 정의 및 tasks 포함 -> 유지보수 용이
  • `timescale
    • 지연시간 및 시뮬레이션 resolution 지정
    • `timescale <reference time unit> / <resolution>
  • `define & `undef
    • `define: text 대체 매크로 형성
    • `define은 파일간 수행 가능
    • 매크로 수정에 쉬운 접근
    • `undef는 `define에 의해 생성된 텍스트 매크로를 제거
    • 예시
       - `define WIDTH 4
       - `define BYTE_SIZE 8
       - `undef WIDTH

Verilog Parameter


  • Parameter: 변경 가능한 상수를 선언하는 과정
  • 선언 예시:
    parameter WIDTH = 8;
    parameter MEM_ADDR = 4;
  • local parameter: module-locap parameter 값 보호
  • Prameter vs. Local Parameter
  • parameter 변경시 사용 코드
    • ex1) mem #(.WIDTH(16), .MEM_ADDR(8)) I0(..<port map>);
    • 위 코드로 상단에서 선언한 WIDTH와 MEM_ADDR 파라미터의 값을 각각 16, 8로 변경 가능
    • #과 .은 Parameter의 값을 변경하기 위한 기호
    • ex2) defparam I0.WIDTH = 16;
    • 위 코드에서는 I0포트에 할당된 파라미터 WIDTH값을 defparam 키워드로 변경(아래 코드에서는 I0대신 uut로 선언하였으므로 uut.Width=~~;로 변경 가능)

실습 1: Parity Function 설계


교재 262p 참조

[설계 요구사항]

  • function subprogram을 사용하여 parity function 작성
  • testbench 코드 작성
    • repeat문 사용, 2256번 반복 입력, d_word 생성
    • $display system task를 사용하여 data와 parity를 화면에 출력
    • $finish system task를 사용하여 simulation 종료

[설계 참고사항]

d_word는 10ns마다 생성

 

1. my_parity 프로젝트 생성

2. input으로 d_word를 버스로 31:0. output을 data_frame으로 32:0으로 선언 후 코드 작성

`timescale 1ns / 1ps


module my_parity #(parameter width = 32)(	//.v 파일에서 parameter 선언 시 module 아래에서 선언
    input [width-1:0] d_word,
    output [width:0] data_frame    );
    
  function parity;
    input [width -1:0] bv;
    reg par;
    integer i;
        begin   //fuction
        par = 1'b0;
          for(i = 0; i <= width; i=i+1)
          par = par ^ bv[i];
          parity = par;	//return
        end
  endfunction
  
assign data_frame = {d_word, parity(d_word)};  

endmodule

 

3. testbench 파일 생성을 위해 Add Source에서 Add or create simulation sources - my_parity_tb.v파일 생성(input, ouput 정의x)

4. my_parity_tb.v 코드 작성

my_parity #(.width(8)) uut (~~);는 parity에서 선언된 parameter width의 값을 8로 변경하기 위한 코드로, 상단의 parameter 변경 방법 중 ex1의 방법을 사용한 것 -> 상단의 예시에서 I0의 포트로 선언한 것을 아래 코드에서는 uut(unit under test)로 선언

`timescale 1ns / 1ps


module my_parity_tb();

parameter width = 8;	//testbench 파일에서도 module 아래에서 parameter 선언

reg [width-1:0] data;
wire [width:0] dout;

my_parity #(.width(8))	//testbench를 실행 시 width의 값을 8로 변경(my_parity에서 32이던 width값을 변경)
    //위에서 #은 delay가 아닌 값 변경을 위한 기호
    uut(	//부품번호 uut
    .d_word     (data),
    .data_frame (dout)
    );
    
initial begin
    data = 32'd0;
    repeat ( 256 )
    begin
    	#10;
        $display ("data = %b parity %b", data, dout[0]);
        data = data + 1;
     end
end

initial begin
    #(10+256);
    $finish;
end

endmodule

 

5. Run Simulation: console에 parity 표현됨을 확인

아래와 같은 내용이 콘솔에 출력

 

data = 00000000 parity 0
data = 00000001 parity 1
data = 00000010 parity 1
data = 00000011 parity 0
data = 00000100 parity 1
data = 00000101 parity 0
data = 00000110 parity 0
data = 00000111 parity 1
data = 00001000 parity 1
data = 00001001 parity 0
data = 00001010 parity 0
data = 00001011 parity 1
data = 00001100 parity 0
data = 00001101 parity 1
data = 00001110 parity 1
data = 00001111 parity 0
data = 00010000 parity 1
data = 00010001 parity 0
data = 00010010 parity 0
data = 00010011 parity 1
data = 00010100 parity 0
data = 00010101 parity 1
data = 00010110 parity 1
data = 00010111 parity 0
data = 00011000 parity 0
data = 00011001 parity 1


실습 2: Constant Functions 설계


교재 264p 참조

[설계 요구사항]

  • function subprogram을 사용하여 constant function 작성
  • testbench 코드 작성
    • constant_fun module을 8번 instantiation
    • parameter DEPTH값을 4, 8, 16, 32, 64,...로 변경

1. my_const_fun 프로젝트 생성

2. Add or Create Design Sources로 address, rd_vr, cs, data 생성

 

3. 코드 작성

`timescale 1ns / 1ps


module my_const_fun( rd_wr, cs, address, data );
    parameter DEPTH         = 512;
    parameter DATA_WIDTH    = 16;
    localparam ADDR_WIDTH    = clogb2 (DEPTH);
    
    input rd_wr, cs;
    input [ADDR_WIDTH -1 : 0] address; 
    inout [DATA_WIDTH -1 : 0] data;
    
    function integer clogb2 (input integer depth);  //memory 크기에 따라 필요한 address 사이즈를 계산하는 함수(2^(address size-1)= memory size)
    begin
        for (clogb2 = 0; depth > 0; clogb2 = clogb2 + 1)
            depth = depth >> 1;
    end
    endfunction

initial
    $display (" Parameter Values DATA WIDTH = %d; DEPTH = %d; ADDRESS WIDTH = %d ", DATA_WIDTH, DEPTH, ADDR_WIDTH);
    
    
endmodule

 

4. testbench를 위해 Add Source - Create simulation sources - Create File - my_const_fun_tb.v 생성 후 코드 작성

`timescale 1ns / 1ps


module my_const_fun_tb( );

//DEPTH: 메모리 사이즈
wire [15:0] data;

my_const_fun #(.DEPTH(4)) uut0(
    .rd_wr(1'b0),   //my_const_fun의 rd_wr의 값을 1'b0으로 변경
    .cs(1'b0),      //my_const_fun의 cs의 값을 1'b1으로 변경
    .address(32'd1),//my_const_fun의 address의 값을 16'd0으로 변경
    .data(data)    //my_const_fun의 data의 값을 16'd0으로 변경
);  //위 과정은 메모리에 16비트 데이터를 저장하는 코드

my_const_fun #(.DEPTH(8)) uut1(  .rd_wr(1'b0), .cs(1'b0), .address(32'd1), .data(data)  );
my_const_fun #(.DEPTH(16)) uut2(  .rd_wr(1'b0), .cs(1'b0), .address(32'd1), .data(data)  );
my_const_fun #(.DEPTH(32)) uut3(  .rd_wr(1'b0), .cs(1'b0), .address(32'd1), .data(data)  );
my_const_fun #(.DEPTH(64)) uut4( .rd_wr(1'b0), .cs(1'b0), .address(32'd1), .data(data)  );
my_const_fun #(.DEPTH(128)) uut5( .rd_wr(1'b0), .cs(1'b0), .address(32'd1), .data(data)  );

endmodule

 

5. Run Behavioral Simulation 후 Console 창 확인

메모리의 크기에 따라 필요한 주소 길이가 정상적으로 출력


실습 3: Two's Complement task 설계


교재 263p 참조 다음 부분을 Task로 변환

[설계 요구사항]

  • task subprogram을 사용하여 2의 보수 task 작성
  • 입력 data [7:0]
  • 출력 tc_data [7:0[
  • testbench 코드 작성
    • for문 사용하여 256번 반복 입력 data 생성
    • $display system task를 사용하여 data와 tc_data를 화면에 출력
    • $finish문을 사용하여 simulation 종료

[설계 참고사항[

  • data는 10ns마다 생성

1. my_task 프로젝트 생성

2. Add or Create Design Sources를 이용해 my_task.v를 생성하고 [3:0]의 DIN과 [3:0]의 DOUT 선언 후 코드 작성

`timescale 1ns / 1ps


module my_task(
    input [3:0] DIN,
    input [3:0] DOUT
    );
    
task comp_2 (input [3:0] in_bus, output reg [3:0] comp2_bus);
reg [3:0] bus_i;

begin
    bus_i = -in_bus;
    comp2_bus = bus_i+1;
end
endtask

reg[3:0] com2_data;

assign DOUT = com2_data;

always @(DIN)
    comp_2(DIN, com2_data);
    
endmodule

 

3. Add or Create Simulataion Sources를 이용해  my_task_tb.v를 생성하고 코드 작성

`timescale 1ns / 1ps


module my_task_tb( );
reg [3:0] data;
wire [3:0] twos_data;
integer i;

my_task uut( .DIN(data), .DOUT(twos_data));

initial begin
    data = 0;
    for(i = 0; i < 256; i = i+1)
    begin
        #10;
        $display(" input data: %b, Two's Complement: %b", data, twos_data);
        data = data + 1;
    end
end
        
initial begin
    #(10+256)
    $finish;
end        

endmodule

 

4. Simulation 후 Console창에 내용 출력 확인