SoC 설계/Verilog, SystemVerilog

SystemVerilog: modport

yztech 2023. 3. 10. 07:33
반응형

direction을 가진 modport는 interface 내에서 정의된다. 이들은 한 module 내에서 interface를 접근할 때, 특정한 제한이 있다는 것을 내포한다.

modport로 정의하면, 해당 모듈내에서 direction이 정의된 것으로 보면 된다.

verilog에서 사용하는 input, output, inout들은 하나의 bundle로 묶은 것으로 생각하면 된다.

문법

modport [identifier] )
    input [port_list],
    output [port_list]
);

아래 예제는 4 개의 logic들과, 2개의 modport 선언을 포함하는 인터페이스인 myInterface를 보여준다.

(a9-11) modport인 dut0는 ack, sel을 입력으로, gnt, irq0를 출력으로 선언

(a15-18) 반면 modport인 dut1에서는 ack, sel은 출력으로, gnt, irq0는 입력으로 선언

이 말은 modport dut1을 사용하는 어떤 모듈들에서도 이들은 ack, sel은 출력이고, gnt, irq0는 입력이 된다는 것을 의미한다.

// (a)
interface myInterface;
    logic ack;
    logic gnt;
    logic sel;
    logic irq0;

    // @dut0: ack, sel: inputs, while gnt, irq: outputs
    modport dut0 (
        input ack, sel,
        output gnt, irq0
    );

    // @dut1: ack, sel: outputs, while gnt, irq0: inputs
    modport dut1 (
        output ack, sel,
        input gnt, irq0
    );
endinterface

named port번들의 예

아래 예제에서는 dut0, dut1 모듈을 선언하는데, 이들은 각각 인터페이스 객체인 myInterface 의 modport선언인 dut0와 dut1 만 사용한다.

(b2) dut0 모듈은, 인터페이스 객체인 myInterface 의 modport선언인 dut0을 io로 사용

(b6) dut1모듈은, 인터페이스 객체인 myInterface 의 modport선언인 dut1을 io로 사용

(b10-14) tb모듈을 정의하는데,

(b11) 인터페이스 객체인 myInterface의 객체 _if를 선언하고,

(b12-13) 이 전체 인터페이스 객체 _if를 **전부 넘긴다**.

```verilog
module dut0 (myInterface.dut0 _if);
    ...
endmodule

module dut1 (myInterface.dut1 _if);
    ...
endmodule

module tb
    myInterface _if;
    dut0 d0 ( .* );
    dut1 d1 ( .* );
endmodule

port 번들만 연결하는 예

이 예제 스타일에서는, (c12-13) 모듈에 필요한 modport만 지정해서 연결한다.

// (c)
module dut0 (myInterface.dut0 _if);
    ...
endmodule

module dut1 (myInterface.dut1 _if);
    ...
endmodule

module tb
    myInterface _if;
    dut0 d0 ( .if (_if.dut0) );
    dut1 d1 ( .if (_if.dut1) );
endmodule

왜 modport가 필요한가?

간단한 인터페이스 내에서 선언된 net은 기본적으로 input이다.

따라서, 어떤 모듈도 같은 net에 연결될 수 있고, 그 net의 값을 drive 하거나 얻을 수 있다.

간단히 말하면, 방향의 제한이 없다. 따라서, testbench나 module에서 서로 다른 값으로 같은 net을 drive 하게 되면, X 값을 갖게 되므로, testbench 작성 시 특별히 주의를 기울여야 한다.

이러한 문제점들은, modport를 사용하여 근본적으로 회피할 수 있다.

설계 예제

매우 간단한 버스 구조로 연결된 두 개의 모듈 master와 slave를 고려해 본다.

master가 버스를 통해 address와 data를 보내면, slave는 이를 받아서 내부 register를 update한다. master가 slave가 받을 준비가 되어 sready신호가 active 된 경우에만 전송한다.

Interface

interface는 master 모듈과 slave 모듈간이 공유되는 신호들을 정의한다.

// (d)
interface ms_if (input clk);
    logic rstn;             // Active low reset
    logic sready;         // Indicates if slave is ready to accept data
    logic [1:0] addr; // Address
    logic [7:0] data; // Data

    modport slave (    input rstn, clk, 
                                    input addr, data, 
                                    output sready            
                                    );
    modport master (input clk, rstn, 
                                    input sready,
                                    output addr, data
                                    );
endinterface

Design

master는 주소를 0에서 3을 반복하고, 주소에 4를 곱한 것과 같은 데이터를 전송한다. master는 slave가 받을 준비가 되어 sready=1이 되면 다음 주소와 값을 갱신한다.

// (e)
// Master sends transactions in a pipelined format
// CLK    1   2   3   4   5   6
// ADDR  A0  A1  A2  A3  A0  A1
// DATA      D0  D1  D2  D3  D4
module master(ms_if.master mif);
    always @(posedge mif.clk) begin
        if(!mif.rstn) begin
            mif.addr <= 0;
            mif.data <= 0;
        end 
        else if(mif.sready) begin
            mif.addr <= mif.addr + 1;
            mif.data <= mif.addr * 4;
        end
    end
endmodule

(f35) slave는 주소가 3→0으로 바뀌면, sready=0으로 만들어, 1개의 추가 클럭이 필요하도록 한다.

// (f)
module slave (ms_if.slave sif);
    reg [7:0] reg_a;
    reg [7:0] reg_b;
    reg             reg_c;
    reg [3:0] reg_d;

    reg             dly;
    reg [3:0] addr_dly;

    always @(posedge sif.clk) begin
        if(!sif.rstn) begin
            addr_dly <= 0;
        end else begin
            addr_dly <= sif.addr;
        end
    end

    always @(posedge sif.clk) begin
        if(!sif.rstn) begin
            reg_a <= 0;
            reg_b <= 0;
            reg_c <= 0;
            reg_d <= 0;
        end else begin
            case(addr_dly)
                0: reg_a <= sif.data;
                1: reg_b <= sif.data;
                2: reg_c <= sif.data;
                3: reg_d <= sif.data;
            endcase
        end
    end

    assign sif.sready = ~(sif.addr[1] & sif.addr[0]) | ~dly;

    always @(posedge sif.clk) begin
        if(!sif.rstn)
            dly <= 1;
        else
            dly <= sif.sready;
    end
endmodule

두 개의 설계 모듈은 최상위 모듈에서 함께 묶는다.

// (g)
module d_top (ms_if tif);
    master m0 (tif.master);
    slave s0 (tif.slave);
endmodule

testbench

테스트벤치는 인터페이스 핸들을 설계에 전달한 다음, master 및 slave modport를 하위 모듈에 할당한다.

// (h)
module tb_top;
    reg clk;
    always #10 clk = ~clk;

    ms_if if0(clk);
    d_top d0 (if0);

    initial begin
        clk <= 0;
        if0.rstn <= 0;
        repeat (5) @ (posedge clk);
        if0.rstn <= 1;

        repeat (20) @ (posedge clk);
        $finish;
    end
endmodule

Simulation

 

반응형

'SoC 설계 > Verilog, SystemVerilog' 카테고리의 다른 글

SystemVerilog: Parameterized task/functions  (0) 2023.03.18
SystemVerilog: Arrays  (0) 2023.03.10
SystemVerilog: Testbench  (0) 2023.02.23
SystemVerilog: 소개  (0) 2023.02.22
Verilog: Icarus Verilog, GTKwave  (0) 2021.10.16