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
中正确例化interface
和module
,并将在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
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
三、注意事项
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
的信号。一些共同的信号你可以放在interface
内modport
外。
总之,为了能在例化的时候正确的将驱动信号传入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
在顶层模块中例化接口:

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));
二、接口的内容
接口不仅仅是一组连接线,它也可以封装模块间通信的所有细节。使用接口可以:(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
接口的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
在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
TOP模块部分

module zuoye_top(input CLK,RESETN); zuoye_interface u1(.CLK,.RESETN); zuoye_M1 i1(u1.M1); zuoye_S1 i2(u1.S1); endmodule
主机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
从机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
测试代码

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
仿真结果
主从结构写功能仿真结果:
主从结构读功能仿真结果:
分析:
由于测试代码中的地址与数据是同一个数,地址与所在地址写入的数据相等,从图中可以看出主机可以实现写地址写数据功能,第二张图可以看出,输入连续的读地址以后,读出的数据正确,可以实现正确的读写功能。
收获:
比如接口的输入信号必须声明端口,第一次就是因为主机的输入信号wdata,waddr,raddr没有在接口列表中声明,导致数据无法传输,第二个问题是为了实现主机与从机的通信,必须设置wr_done与rd_done的标志位,因为如果不设置wr_done可能主机没有写完数据,从机开始进行数据的写入,写入的就是不定态,同理读出的也是不定态,存储器无法存储正确的值。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)