SV学习(3)——接口interface、modport、时钟块clocking

========

链接: interface中的clocking

========

1. 接口interface

SV引入了一个重要的数据类型:interface。主要作用有两个,一是简化模块之间的连接;二是实现类和模块之间的通信。

使用接口使得连接更加简洁而不易出差,如果需要在一个接口中放入一个新的信号,就只需要在接口定义和实际使用这个接口的模块中做对应的修改,而不需要改变其他模块。接口不可以例化,但是可以使用接口的指针,找到接口的实例,然后再找接口实例中的信号。

接口使用方法:

  1. 在interface的端口列表只需要定义时钟、复位等公共信号,或者不定义任何端口信号,转而在变量列表中定义各个需要跟DUT和TB连接的logic变量。
  2. interface可以用参数化方式提高复用性(parameter)
  3. 对于有对应interface的DUT和TB,在其例化的时候,也只需要传递匹配的interface变量名即可完成interface的变量传递

由于接口即可以在硬件上(module)使用,又可以在软件上(class)使用,interface作为SV中唯一的硬件和软件环境的媒介交互,modport可以进一步限定信号传输的方向,避免端口连接的错误。

接口中使用task和function:

  1. 接口可以包含task和function,也可以在接口外部或者内部
  2. 如果task和function定义在模块中,使用层次结构名称,它们必须在接口中声明为extern或在modport中声明为export
  3. 多个模块的任务名不可以重复

下面举两个接口的例子:

  1. 使用interface接口的一位全加器

    `timescale	1ns / 1ns
    
    interface if_port (input bit clk);	// 声明接口
    	logic	a, b, cin, sum, cout;	// 声明所有的连接线
    	
    	clocking	cp @ (posedge clk);	// 声明在同一个时钟变化下,连接线的方向
    		output	a, b, cin;
    	endclocking
    	
    	clocking	cn @ (negedge clk);	// 下降沿出发
    		input	a, b ,cin, sum, cout;
    	endclocking
    	
    	modport	simulus (clocking cp);	// 声明端口的输入输出
    	modport	adder	(input a, b, cin,  output cout, sum);
    	modport	monitor	(clocking cn);
    
    endinterface
    
    module	simulus	(if_port.simulus port);	// 使用接口的激励模块
    	always @ (port.cp)	begin
    		port.cp.a	<=	$random() % 2;
    		port.cp.b	<=	$random() % 2;
    		port.cp.cin	<=	$random() % 2;
    	end
    endmodule:	simulus
    
    module	adder	(if_port.adder port);	// 一位加法器
    //	assign	{port.cout, port.sum} = {port.a + port.b + port.cin};	// 好久没写代码,犯了这样的错
    	assign	{port.cout, port.sum} = port.a + port.b + port.cin;
    endmodule:	adder
    
    module	monitor	(if_port.monitor mon);	// 检测模块,在时钟的下降沿打印结果
    	always @ (mon.cn)	begin
    		$display ("%d + %d + %d = %d %d", mon.cn.a, mon.cn.b, mon.cn.cin, mon.cn.cout, mon.cn.sum, $time);
    	end
    endmodule
    
    module	top ( );
    	timeunit		1ns;
    	timeprecision	1ns;
    	
    	bit	clk = 0;;
    	
    	if_port	port (clk);	// 实例化所有模块,并连接接口
    	simulus	sim	(port.simulus);
    	adder	add (port.adder);
    	monitor	mon (port.monitor);
    	
    	always #10 clk = ~clk;
    
    endmodule
    

    在这里插入图片描述

  2. 使用interface接口的读存储器

    // interface_example
    
    interface membus (	// 声明接口
    	input	logic	clk
    );	// port和module的port一样,用于top的
    
    	// 声明用于内部模块例化的连接
    	logic			mrdy	;
    	logic			wen		;
    	logic			ren		;
    	logic	[ 7: 0]	addr	;
    	logic	[ 7: 0]	c2m_data;
    	logic	[ 7: 0]	m2c_data;
    	wor				status	;
    // 对于确定了方向的可以在interface的port里面声明,如clk,
    // 而对于随着模块不同、方向不同的用logic先声明,然后再用modport指明具体方向
    	
    	task	reply_read (
    		input	logic	[ 7: 0]		data,
    		integer	delay
    	);
    		#delay;
    		@ (negedge clk)
    		mrdy = 1'b0;
    		m2c_data = data;	// slave回复数据
    		@ (negedge clk)
    		mrdy = 1'b1;	
    	endtask
    	
    	task	read_memory (
    		input	logic	[ 7: 0]	raddr	,
    		output	logic	[ 7: 0]	data
    	);
    		@ (posedge clk);
    		ren = 1'b0;
    		addr = raddr;		// master申请读数据
    		@ (negedge mrdy);
    		@ (posedge clk);
    		data = m2c_data;	// master得到数据
    		ren = 1'b1;
    	endtask
    	
    	// 在接口中使用modport,将信号分组并指定方向
    	modport master (
    		output	wen, ren, addr, c2m_data, status,
    		input	mrdy, m2c_data,
    		import	read_memory
    	); 
    	
    	modport	slave (
    		input	wen, ren, addr, c2m_data, status,
    		output	mrdy, m2c_data,
    		import	reply_read
    	);
    	
    endinterface
    
    /* *************** mem_core *************** */
    module mem_core (membus.slave mb);	// 用salve接口声明
    	logic	[ 7: 0]	mem [ 255: 0];
    	
    	initial	begin
    		for (int i = 0; i < 256; i++)
    		mem[i] = i;
    	end
    	
    	assign	mb.status = 1'b0;
    	always @ (negedge mb.ren)
    		mb.reply_read (mem[mb.addr], 100);
    endmodule
    
    /* *************** cpu_core *************** */
    module cpu_core (membus.master mb);	// 用master接口声明
    	assign	mb.status = 1'b0;
    	initial	begin
    		logic	[ 7: 0]	read_data;
    		mb.read_memory (8'b0001_0000, read_data);
    		$display ("Read Result", $time, read_data);
    	end
    endmodule
    
    
    /* *************** top *************** */
    module top ();
    	wor		status;	// wor:当有多个驱动源驱动wor型数据时,将产生线或结构
    	logic	clk = 1'b0;
    	
    	membus mb (clk);
    	
    //	mem_core mem (mb.slave);
    	mem_core mem (mb);
    	cpu_core cpu (mb.master);
    
    	initial	begin
    		for (int i = 0; i <= 255; i++)
    			#1 clk = ~clk;
    	end
    	
    endmodule
    

    在这里插入图片描述
    在这里插入图片描述

关于interface的使用,大概知道了怎么用,接口中端口的调用没问题,比如在顶层top中实例化mem模块和cpu模块,这俩的端口连接可以用接口实现,在interface接口中,把端口的变量名声明好,再用modport将信号分组并指定输入输出方向;
但是具体的细节不太了解,比如在接口中定义了任务,如何在各自的模块中调用任务,看别人博客说是要添加export声明,但是我却用了import解决了,不懂,但是成了


2. modport

使用modport将接口中的信号分组
实现一个简单的仲裁器接口,并在仲裁器中使用接口,

// 简单接口
interface arb_if (input bit clk);
	logic [1:0] grant, request;
	logic rst;
endinterface

// 仲裁器
module arb (arb_if arbif);
	...
	always @(posedge arbif.clk or posedge arbif.rst) begin
		if (arbif.rst == 1'b1)
			arbif.grant <= 2'b00;
		else
			arbif.grant <= next_grant;
	end
	...
endmodule

// 测试平台
module test (arb_if arbif);
	...
	initial	begin
		...
		@(posedge arbif.clk);
		arbif.request <= 2'b01;
		$display ("@%0t: Drove req = 01", $time);
		repeat (2) @(posedge arbif.clk);
		if (arbif.grant != 2'b01)
			$display ("@%0t: a1: grant != 2'b01", $time);
		$finish;
	end
	...
endmodule

// 顶层
module top; 
	bit clk;
	always #5 clk = ~clk;
	
	arb_if arbif(clk);
	arb a1(arbif);
	test t1(arbif);
endmodule

上面arb在接口中使用了点对点的无信号方向的连接方式;
下面在接口中使用modport结构能将信号分组并指定方向;

// 使用mofport的接口
interface arb_if (input bit clk);
	logic [1:0] grant, request;
	logic rst;
	
	modport TEST (output request, rst,
	              input grant, clk);
	modport DUT (input request, rst, clk,
	             output grant);
	modport MONITOR (input request, grant, rst, clk);
endinterface

// 仲裁器
module arb (arb_if.DUT arbif);
	...
endmodule

// 测试平台
module test (arb_if.TEST arbif);
	...
endmodule 

// 顶层
module top;
	bit clk;
	always #5 clk = ~clk;
	
	arb_if arbif(clk);
	arb a1(arbif);
	test t1(arbif);
endmodule

两种顶层模块是一样的,因为modpoer在模块首部指明,在模块例化时就不需要指明;

在接口中不能例化模块,但是可以例化其他接口


3. 时钟块clocking

3.1. 驱动和采用的竞争问题

在RTL设计中,在同一个时间片内,可能会有一个信号同时被读取和写入,那么读取到的数据有可能会是旧数值也有可能是新数值,用非阻塞赋值 <= 就可以解决这个问题。但是在测试程序中,不能确保采集到DUT产生的最新值;

接口块可以使用时钟块来指定同步信号相对于时钟的时序。
在这里插入图片描述

在时钟上升沿采样信号,只看vld信号(1->0)就会疑惑采样到的是1还是0,在RTL仿真时,是看不到真实的物理时序信息的,但实际采样到的是1;真实电路对应的时vld_actual信号,它的变化会较clk有一个detal-cycle的延迟,这样看在时钟上升沿采集到的是1。

我们可以人为的设置加大delay,这样看波形的时候就会显示vld_actual的,让待采样数据的沿变不和时钟的沿变在一起。

还有一种方法就是,设置采样vld信号的时间,设置在时钟上升沿之前 #t 采样,这样采出来的数据也是准确的;
同样也可以设置信号输出时间,设置在时钟上升沿之后 #t 输出,这样输出的数据也是准确的;
就类似与模拟建立保存时间,在时序上在波形上,看到肉眼可见的延迟。

3.2. clocking待补充…

  • 29
    点赞
  • 214
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
在SystemVerilog中,interface是一种用于在硬件模和软件类之间进行交互的媒介。它可以在端口列表中定义时钟、复位等公共信号,或者在变量列表中定义需要与设计单元(DUT)和测试台(TB)连接的逻辑变量。通过参数化方式,interface可以提高复用性,使得有对应interface的DUT和TB在实例化时只需传递匹配的interface变量名即可完成接口变量的传递。接口可以用于设计和验证,它使得连接变得简洁且不易出错。与模类似,interface可以定义端口、双相信号,使用initial和always,以及定义function和task。初学者可以将interface看作是一个"插排",用于实现DUT和TB之间的数据驱动关系。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [SV 接口(interface)](https://blog.csdn.net/weixin_45680021/article/details/125753127)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [SV学习(3)——接口interfacemodport时钟clocking](https://blog.csdn.net/Bunny9__/article/details/122605991)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值