interface--modport

1,参考1

SystemVerilog中接口interface的modport_沧月九流的博客-CSDN博客_modport用法

interface中的 modport优缺点:
优点:
1、接口便于设计重用
2、接口可以用来代替原来需要在模块或者程序中反复声明并且位于代码内部的一系列信号,减少了连接错误的可能性
3、要增加一个新的信号时,在接口中只需要声明一次,而不需要在更高层的模块声明
4、modport允许一个模块很方便的将接口中的一系列信号捆绑到一起,也可以为信号指定方向一方便工具自动检查。
缺点:
1)必须同时使用信号名与接口名、可能会使得模块变得更加冗长
2)连接两个不同的接口很困难。一个新的接口可能包含了现有的所有信号并新增了信号。你需要拆分出独立的信号并且正确地驱动。

 

2,参考2

SystemVerilog 在interface中使用modport时的例化问题 - 知乎 (zhihu.com)

systemverilog中有一个非常实用的功能,那就是interface。在最近写一个小练习的时候,不仅使用到了interface,还在interface中使用了modport,但是在一开始例化的时候出了点问题,所以在这里说一下需要注意的地方。

下面举一个例子,这个例子主要展示了:

  • 如何在module中调用interface
  • 如何在testbench中正确例化interfacemodule,并将在testbench中定义的与module的输入输出信号对应的信号传入module

二、举例

1、RTL代码

复制代码
interface ticket_if(input logic clk,rst_n,[5:0]m_in,output logic ticket_out,[5:0]m_out);

    logic [5:0]sum;

    task change(input logic [5:0]in_data,
                          output logic [5:0]out_data );

             out_data = in_data - 6;
    endtask //automatic

    modport ticket_ports(input clk,rst_n,m_in,
            output ticket_out,m_out,sum,
            import task change(input logic [5:0]in_data,
                               output logic [5:0]out_data )
            );
endinterface //interfacename

module ticket(ticket_if.ticket_ports ports);

enum logic [1:0]{s0,s1,s2} state_c,state_n;

always_ff @(posedge ports.clk or negedge ports.rst_n) 

    if(!ports.rst_n)
        state_c <= s0;
    else
        state_c <= state_n;

always_comb       
    case(state_c)
        s0:begin
            ports.sum = ports.m_in;
            ports.ticket_out = 0;
            if(ports.sum>=6)
                state_n <= s2;
            else
                state_n <= s1;
        end
        s1:begin
            ports.sum = ports.sum + ports.m_in;
            if(ports.sum>=6)
                state_n <= s2;
            else
                state_n <= state_c;
        end
        s2:begin
            ports.change(ports.sum,ports.m_out);
            //ports.m_out = ports.sum - 6;
            ports.ticket_out = 1;
            state_n <= s0;
        end
        default:state_n <= s0;
    endcase

endmodule
View Code
复制代码

2、仿真代码

复制代码
module tb_ticket;

timeunit 1ns;
timeprecision 100ps;


logic clk,rst_n;
logic [5:0]m_in;
logic ticket_out;
logic [5:0]m_out;

initial
  begin
      clk = 0;
      rst_n = '1;
      #5 rst_n = '0;
      #5 rst_n = '1;
  end

initial
  begin
      #10 m_in=2;
      #10 m_in=3;
      #10 m_in=4;
      #10 m_in=5;
      #10 m_in=6;
      #10 m_in=7;
      #10 m_in=8;
  end

always #5 clk = ~clk;

//ticket_if ports(.*);如果信号名称一样,你也可以直接按照这种方式来例化
ticket_if ports(.clk(clk),.rst_n(rst_n),.m_in(m_in),.ticket_out(ticket_out),.m_out(m_out));
ticket u_ticket(ports.ticket_ports);
endmodule
View Code
复制代码

三、注意事项

1、需要注意的是,我们在例化的时候,只能对interface进行实例化,并不能直接对modport进行实例化。之所以这么说,是因为我们给module传入的是ticket_if.ticket_ports,这是modport类型,所以我们可能会想直接实例化一个该类型的对象,然后传给u_ticket,但是这样是行不通的。

2、于是我们只能先实例化一个interface类型的对象ports,然后将在testbench中定义的输入输出信号传给ports,之后再将modport类型的ports.ticket_ports传给module类型的对象u_ticket即可。但是这里需要注意的是,在RTL代码中,我们必须将module中使用到的输入输出信号都在interface的端口里声明一下,因为只有这样才允许在testbench中将这些信号传给ports。否则,我们就无法传递输入输出信号,那么就无法驱动待测module以完成仿真。

3、在interface的端口里声明了输入输出信号之后,我们只需要将你想要放到modport中的信号写在modport中即可。之所以使用modport,是因为一个interface可能会被多个module调用,那么如果你想要隔离开这些module之间不同的信号,你就可以像例子中那样,为每一个module添加一个modport来"包裹"只属于某个module的信号。一些共同的信号你可以放在interfacemodport外。

总之,为了能在例化的时候正确的将驱动信号传入module,你需要将这些信号在interface的端口声明一下。

 

3,参考3

(6条消息) interface中modport和clocking_hh199203的博客-CSDN博客_interface中的clocking

 

4,参考4

(6条消息) Systemverilog 接口 interface modport_小羊肖恩想的博客-CSDN博客_systemverilog中modport

目录
 这篇文章主要是介绍接口interface的作用,为什么要有接口,接口的优点、缺点,以及利用接口去实现一主一从结构之间的读写操作。

一、接口的定义
 SystemVerilog在Verilog语言基础上扩展了“接口”(interface)结构,SystemVerilog增加了新的端口类型—接口,接口允许许多信号合成一组由一个端口表示,只需在一个地方对组成接口的信号进行声明,使用这些信号的模块只需一个接口类型的端口。
 接口声明举例:

复制代码
interface main_bus;
    wire [15:0] data;
    wire [15:0] address;
    logic [ 7:0] slave_instr;
    logic slave_req;
    logic bus_grant;
    logic bus_req;
    logic slave_rdy;
    logic data_rdy;
    logic mem_read;
    logic mem_write;
endinterface
View Code
复制代码

在顶层模块中例化接口:

复制代码
module top (input logic clock, resetn, test_mode);
    logic [15:0] program_addr, jump_addr;
    logic [ 7:0] instr, next_instr;
    main_bus bus ( );  // instance of an interface
// (instance name is bus)

    processor proc1 (
// main_bus ports
        .bus(bus), // interface connection
// other ports
        .jump_addr (jump_addr),
        .instr (instr),
        .clock(clock),
        .resetn(resetn),
        .test_mode(test_mode));

 slave slave1 (
// main_bus ports
        .bus(bus), // interface connection
// other ports
        .clock(clock),
        .resetn(resetn));

    dual_port_ram ram (
// main_bus ports
        .bus(bus), // interface connection
// other ports
        .program_addr (program_addr),
        .data_b(next_instr));
View Code
复制代码

二、接口的内容
 接口不仅仅是一组连接线,它也可以封装模块间通信的所有细节。使用接口可以:(1)在一个地方—接口中定义通信所需的各个信号和端口;(2)在接口中定义通信协议;(3)在接口中直接建立协议校验和其它验证程序。
 接口可以包含类型声明、任务、函数、过程块、程序块和断言。
 接口与模块之间有三个不同点:
 (1)接口不可以包含设计层次,接口不可以包含模块或原语的实例;
 (2) 接口可以用作模块端口,表示模块间的通信通道,而在端口中使用模块则是非法的
 (3)接口可以包含“modport”,这使得每个连接到接口上的模块以不同的方式访问接口。  接口内部信号的使用
 在有接口类型端口的模块中,接口内部信号必须用端口名进行访问:<端口名称>.<接口内部信号名称>。

复制代码
//接口声明
interface main_bus;
    wire [15:0] data;
    wire [15:0] address;
    logic [ 7:0] slave_instr;
    logic slave_req;
    logic bus_grant;
    logic bus_req;
    logic slave_rdy;
    logic data_rdy;
    logic mem_read;
    logic mem_write;
endinterface
//接口信号调用
module slave ( main_bus bus, …);
// internal signals
  logic [15:0] slave_data, slave_addr;
  logic [15:0] operand_A, operand_B;
  logic mem_select, read, write;
  assign bus.address = mem_select? slave_addr: ’z;
  assign bus.data = bus.slave_rdy? slave_data: ’z;
  enum logic [4:0] 
  {RESET = 5'b00001, START = 5'b00010,
   REQ_DATA = 5'b00100, EXECUTE = 5'b01000,
   DONE = 5'b10000} State, NextState;
  always_ff @(posedge bus.clock, negedge bus.resetn)
   begin: FSM
        if (!bus.resetn) State <= START;
        else State <= NextState;
    end
   always_comb begin : FSM_decode
        unique case (State)
            START: if (!bus.slave_req) begin
                bus.bus_req = 0;
                NextState = State;
            end
            else begin
                operand_A = bus.data;
                slave_addr = bus.address;
                bus.bus_req = 1;
                NextState = REQ_DATA;
            end
... // decode other states
        endcase
    end: FSM_decode
endmodule
View Code
复制代码

接口的modport

 SystemVerilog提供两种方法指定模块接口端口使用modport的方式:
 (1)在模块实例化时的接口连接中说明;
 (2)在模块定义的端口声明时说明;

  在模块实例化时的接口连接中说明

复制代码
interface chip_bus (input logic clock, resetn);
    logic interrupt_req, grant, ready;
    logic [31:0] address;
    wire [63:0] data;
    modport master (input interrupt_req,
        input address,
        output grant, ready,
        inout data,
        input clock, resetn);
    modport slave (output interrupt_req,
        output address,
        input grant, ready,
        inout data,
        input clock, resetn);
endinterface

//
module primary (interface pins); // generic interface port
...
endmodule
module secondary (chip_bus pins); // specific interface port
...
endmodule

module chip (input logic clock, resetn);
chip_bus bus (clock, resetn); // instance of an interface
primary i1 (bus.master);  // use the master modport view
secondary i2 (bus.slave); // use the slave modport view
endmodule
————————————————
版权声明:本文为CSDN博主「小羊肖恩想」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42043804/article/details/122524815
View Code
复制代码

在modport既可在模块实例化时指定,又可以在模块定义中指定,但不能同时选择这两种方式!!
 模块定义的端口声明时说明;

复制代码
module primary (chip_bus.master pins); // generic interface port
...
endmodule
module secondary (chip_bus.slave pins); // specific interface port
...
endmodule
module chip (input logic clock, resetn);
chip_bus bus (clock, resetn); // instance of an interface
primary i1 (bus); // use the master modport view
secondary i2 (bus); // use the slave modport view
endmodule
————————————————
版权声明:本文为CSDN博主「小羊肖恩想」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42043804/article/details/122524815
复制代码

三、interface 一主一从结构间的读写操作

 这一部分主要是interface的实际应用,在总线上挂有一主一从结构,主机对从机进行读写操作,使用modport的接口方式,同时博主也对于自己在撰写过程中遇见的错误进行了总结。
在这里插入图片描述

接口部分:

复制代码
//需要输入的信号 一定要在接口端口声明,否则无法传递数值
interface zuoye_interface(input logic CLK,
          input logic RESETN,
          input [31:0] wdata,         
          input [31:0] waddr,
          input [31:0] raddr);
 
logic [31:0] WDATA;
logic [31:0] RDATA;
logic [31:0] rdata;
logic [31:0] WADDR;
logic [31:0] RADDR;
logic WRITE_REQ;
logic WRITE_ACK;
logic READ_ACK;
logic READ_REQ;
logic WR_DONE;
logic RD_DONE;

//主机master
modport M1(
             input CLK,
             input RESETN,
             input WRITE_ACK,
             input READ_ACK,
             input RD_DONE,
             input wdata,
             input RDATA,
             input waddr,
             input raddr,
             output WDATA,
             output rdata,
             output WRITE_REQ,
             output WADDR,
             output RADDR,
             output READ_REQ,
             output WR_DONE
);

//从机slave
modport S1(
            input WR_DONE,
            input CLK,
            input RESETN,
            input WDATA,
            input WADDR,
            input RADDR,
            input WRITE_REQ,
            input READ_REQ,
            output RDATA,
            output READ_ACK,
            output WRITE_ACK,
            output RD_DONE
);
endinterface
————————————————
版权声明:本文为CSDN博主「小羊肖恩想」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42043804/article/details/122524815
View Code
复制代码

TOP模块部分

复制代码
module zuoye_top(input CLK,RESETN);
   zuoye_interface u1(.CLK,.RESETN);
   zuoye_M1 i1(u1.M1);
   zuoye_S1 i2(u1.S1);
endmodule
View Code
复制代码

主机Master部分

复制代码
module zuoye_M1(zuoye_interface.M1 M1);
enum logic [2:0] 
{IDLE,WAIT,WRITE_ADDR,WRITE_DATA,
STOP,READ_ADDR,READ_DATA}State,NextState;
logic [1:0] wr_flag;
always_ff @(posedge M1.CLK , negedge M1.RESETN)
begin
  if(!M1.RESETN)
    State <= IDLE;
  else
    State <= NextState;
end
always_comb
begin
 NextState=State;
unique case(State) 
  IDLE: begin
   if(wr_flag==1) 
     NextState=READ_ADDR;
   else
     NextState=WAIT;
   end 
  WAIT: begin
        if(M1.WRITE_ACK==1)
          NextState=WRITE_ADDR;
  end
  
  WRITE_ADDR:begin
    NextState=WRITE_DATA;
  end
  
  WRITE_DATA:begin
   NextState=IDLE;
  end
  
  READ_ADDR:begin
    if(M1.RD_DONE==1)
    NextState=READ_DATA;
  end
  
  READ_DATA:begin
    NextState=STOP;
  end
  
  STOP:begin
    NextState=IDLE;
  end
 endcase
end

always_ff@(posedge M1.CLK , negedge M1.RESETN)
begin
  // wr_flag <=0;
  wr_flag <=0;
  M1.WR_DONE <=0;
 unique case(State)
  IDLE: begin
    if(wr_flag==1) begin
     M1.READ_REQ  <=1;
     M1.WRITE_REQ <=0;
     end
    else begin
     M1.WRITE_REQ <=1;
     M1.READ_REQ  <=0;
     end
    end
  WAIT: begin
     ;
    end
       
  WRITE_ADDR: begin
     M1.WADDR <=M1.waddr;
   end
  WRITE_DATA: begin
    M1.WDATA  <=M1.wdata;
    wr_flag   <=1;
    M1.WR_DONE   <=1;
    end
  
  READ_ADDR: begin
    M1.RADDR <=M1.raddr;
    end
  
  READ_DATA:begin
    M1.rdata <=M1.RDATA;
    end
  
  
  STOP: begin
   ;
  end
 endcase
end

endmodule
————————————————
版权声明:本文为CSDN博主「小羊肖恩想」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42043804/article/details/122524815
View Code
复制代码

从机slave部分

复制代码
module zuoye_S1(zuoye_interface.S1 S1);
enum logic [2:0] 
{IDLE,WAIT,WRITE,STOP,READ}State,NextState;
logic [1:0] flag; //write finish
logic [7:0] array [0:255];  //memory


always_ff @(posedge S1.CLK , negedge S1.RESETN)
begin
  if(!S1.RESETN)
    State <= IDLE;
  else
    State <= NextState;
end
  
  
always_comb
begin
 NextState=State;       //初始化
unique case(State) 
  IDLE: 
    begin
     if(flag==1)
      NextState=READ;
     else
      NextState=WAIT;
    end
  
  WAIT: begin
        if(S1.WRITE_REQ==1&&S1.WR_DONE==1)
          NextState=WRITE;
        else if(S1.READ_REQ==1)
          NextState=READ;
        else
          NextState=WAIT;
  end
  
  WRITE:begin
        NextState=IDLE;
   end
  
  READ:begin
       NextState=STOP;
  end
  
  STOP:begin
    NextState=IDLE;
  end
 endcase
end
  
always_ff@(posedge S1.CLK , negedge S1.RESETN)
begin
  flag <=0;
  S1.RD_DONE <=0;
 unique case(State)
  IDLE: begin
     ;
    end
  WAIT: begin
     if(S1.WRITE_REQ==1)
         S1.WRITE_ACK <= 1; 
     else if(S1.READ_REQ==1)
         S1.READ_ACK  <= 1;
     end
       
  WRITE: begin
    array[S1.WADDR] <= S1.WDATA;
    flag <=1;
   end
  
  READ: begin
     S1.RDATA <= array[S1.RADDR];
     S1.RD_DONE  <= 1;
    end
  
  
  STOP: begin
   ;
  end
 endcase
end
endmodule
————————————————
版权声明:本文为CSDN博主「小羊肖恩想」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42043804/article/details/122524815
View Code
复制代码

测试代码

复制代码
module zuoye_top_tb;
logic CLK,RESETN;
logic [31:0] waddr,raddr;
logic [31:0] wdata;
zuoye_interface bus(.CLK(CLK),.RESETN(RESETN),
.wdata(wdata),.waddr(waddr),.raddr(raddr));
  zuoye_M1 u_M1(bus.M1);
  zuoye_S1 u_S1(bus.S1);
initial
  begin
   CLK=0;
  forever #5 CLK=~CLK;
  end

initial
begin
   RESETN=1;
#10
   RESETN=0;
#10
   RESETN=1;
end

initial
begin
  wdata=32'h8000_0000;
  waddr=32'h8000_0000;
#50;
for(int i=0;i<32;i++)
begin
   waddr=i;
   wdata=i;
 #120;
end

for(int j=0;j<32;j++)
begin
   raddr=j;
 #120;
 end
 $finish;
end
endmodule
————————————————
版权声明:本文为CSDN博主「小羊肖恩想」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42043804/article/details/122524815
View Code
复制代码

仿真结果
 主从结构写功能仿真结果:

 主从结构读功能仿真结果:

 分析:
 由于测试代码中的地址与数据是同一个数,地址与所在地址写入的数据相等,从图中可以看出主机可以实现写地址写数据功能,第二张图可以看出,输入连续的读地址以后,读出的数据正确,可以实现正确的读写功能。
 收获:
 比如接口的输入信号必须声明端口,第一次就是因为主机的输入信号wdata,waddr,raddr没有在接口列表中声明,导致数据无法传输,第二个问题是为了实现主机与从机的通信,必须设置wr_done与rd_done的标志位,因为如果不设置wr_done可能主机没有写完数据,从机开始进行数据的写入,写入的就是不定态,同理读出的也是不定态,存储器无法存储正确的值。

 

 

 

posted @   burlingame  阅读(706)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示