《SystemVerilog验证测试平台编写指南》学习笔记——线程以及线程间的通信(三)

一、旗语

使用旗语可以实现对同一资源的访问控制,类似于操作系统里面的互斥访问。在SV多个阻塞的线程会以FIFO的方式进行排队。

1、旗语的操作

旗语有三种操作:

  1. 使用new方法可以创建一个带单个或者多个钥匙的旗语
  2. 使用get可以获取一个或多个钥匙
  3. 使用put可以返还一个或多个钥匙

如果试图获取一个旗语而不希望被阻塞可以使用try_get()函数,返回1表示足够多的钥匙,而返回0则表示钥匙不够。

用旗语实现对硬件资源的访问控制

program automatic test(bus_ifc.TB bus);
	semaphore sem;		//创建一个旗语
	initial begin
		sem = new(1);		//分配一个钥匙
		fork
			sequencer();		//产生两个总线事务线程
			sequencer();
		join
	end

	task sequencer;
		repeat($urandom%10)		//随机等待0-9个周期
			@bus.cb;
		sendTrans();		//执行总线事务
	endtask

	task sendTrans;
		sem.get(1);		//获取总线钥匙
		@bus.cb;		//把信号驱动到总线上
		bus.cb.addr <= t.addr;
		...
		sem.put(1);		//处理完成后把钥匙返还
	endtask
endprogram

2、带多个钥匙的旗语

  • 返还的钥匙可以比你取出来的多
  • 测试程序需要获取或者返还钥匙的时候务必谨慎
  • 当仅剩下一把钥匙时,有一个线程请求两把而被阻塞,第二个线程出现请求一把,SV会把第二个请求get(1)排到第一个请求get(2)的前面,先进先出的规则在这里会被忽略掉
  • 如果有多个大小不同的请求混在一起,可以编写一个类来决定优先权

二、信箱

把发生器和驱动器想象成具备自治能力的事务处理器对象,它们通过信道交换数据。每个对象从它的上游对象中得到事务(如果对象本身是发生器,则创建事务 ),进行一些处理,然后把它们传递给夏有对象。这里的信道必须允驱动器和接收器异步操作。从硬件角度出发,对信箱的最简单的理解是把它看成一个具有源端和收端的FIFO。信箱是一种对象,必须调用new函数来进行实例化,实例化时可选择size参数大小,为0或者没有指定,则默认信箱是无限大的。使用put任务可以把数据放入信箱,get可以移除数据。信箱为空时get会阻塞,信箱满时put会阻塞。peek任务可以获取对信箱里数据的拷贝而不移除它。信箱里放的数据可以使单个的值,例如一个整数或者是任意宽度的logic,可以放入句柄但是不能放入对象。
在这里插入图片描述

1、测试平台里的信箱

使用信箱实现对象的交换:Generator类

class Generator;
	Transaction tr;
	mailbox mbx;
	
	function new(mailbox mbx);
		this.mbx = mbx;
	endfunction

	task run(int count);
		repeat(count) begin
			tr = new();
			assert(tr.randomize());
			mbx.put(tr);
		end
	endtask
endclass

使用信箱实现对象的交换:Driver类

class Driver;
	Transaction tr;
	mailbox mbx;

	function new(mailbox mbx);
		this.mbx = mbx;
	endfunction

	task run(int count);
		repeat(count) begin
			mbx.get(tr);
			@(postedge bus.cb.ack);
			bus.cb.kind <= tr.kind;
			...
		end
	endtask
endclass

使用信箱实现对象的交换:程序块

program automatic mailbox_example(bus_if.TB bus, ...);
	'include "transaction.sv"
	'include "generator.sv"
	'include "driver.sv"

	mailbox mbx;		//连接发生器gen和驱动器drv的信箱
	Generator gen;
	Driver drv;
	int count;

	initial begin
		count = $urandom_range(50);
		mbx = new();		//创建信箱
		gen = new(mbx);
		drv = new(mbx);
		fork
			gen.run(count);
			drv.run(count);
		join
	end
endprogram

2、定容信箱

/*
定容信箱在两个线程之间扮演了一个缓冲器的角色
*/
'timescale 1ns/1ns
program automatic bounded;
	mailbox mbx;
	
	initial begin
		mbx = new(1);		//容量为1
		fork
			//生产方线程
			for(int i = 1; i < 4; i++) begin
				$display("Producer:before put(%0d)", i);
				mbx.put(i);
				$display("Producer:after put(%0d)", i);
			end
			//消费方线程
			repeat(4) begin
				int j;
				# 1ns mbx.get(j);
				$display("Consumer:after get(%0d)", j);
			end
		join
	end
endprogram

3、在异步线程间使用信箱通信

在没有同步信号的情况下,可能会导致消费方还没有开始取数的时候,生产方就已经把信箱填满了,这是因为线程在没有碰到阻塞语句之前会一直运行,而生产方恰好没有碰到阻塞语句的话,可能会一口气儿直接把信箱填满了,换句话说,生产方“跑”到了消费方前面,供过于求,我们想要的是生产者和消费者之间最好有一个同步信号,生产者生产了之后,信号会马上通知消费者来“取货”,或者说消费者需要“取货”时,如果信箱里面“没货”,同步信号会立即通知生产者取“生产”,这样可以维持一个动态的平衡。

4、使用定容信箱和探视(peek)来实现线程的同步

消费者使用一个内建的信箱方法peek()来探视信箱里的数据而不将其移除,当消费者处理完数据后,便使用get()移除数据,这使得生产者可以生成一个新的数据。如果消费者使用get()代替peek()来启动循环,那么事务被立刻移除信箱,这样生产者可能会在消费者完成事务的处理之前生成新的数据。

program automatic sync_peek;
	mailbox mbx;
	class Consumer;
		task run();
			int i;
			repeat(3) begin
				mbx.peek(i);		//探视mbx信箱里的整数
				$display("Consumer: after get(%0d)", i);
				mbx.get(i);		//从信箱里移除
			end
		endtask
	endclass : Consumer

	Producer p;
	Consumer c;
	initial begin
		//创建信箱、生产者、消费者
		mbx = new(1);		//容量为1
		p = new();
		c = new();
		fork
			p.run();
			c.run();
		join
	end
endprogram

输出结果:
在这里插入图片描述
可以看出生产者和消费者步调是一致的,但是生产者仍然比消费者提前一个事务的时间,这是因为容量为1的信箱只有在你试图对第二个事务进行put操作时才会发生阻塞。

5、使用信箱和事件来实现线程的同步

可以在生产者把数据放入信箱后使用事件来阻塞它,消费者则在处理完数据后再触发事件。

program automatic mbx_evt;
	mailbox mbx;
	event handshake;

	class Producer;
		task run();
			for(int i = 1; i < 4; i++) begin
				$display("Producer: before put(%0d)", i);
				mbx.put(i);
				@handshake;		//边沿敏感,可以确保生产者在发送完数据后便停止
				$display("Producer: after put(%0d)", i);
			end
		endtask
	endclass

	class Consumer;
		task run;
			int i;
			repeat(3) begin
				mbx.get(i);
				$display("Consumer: after get(%0d)", i);
				-> handshake;		//消费者触发事件,生产者可以继续生产
			end
		endtask
	endclass : Consumer

	Producer p;
	Consumer c;

	initial begin
		mbx = new();
		p = new();
		c = new();
		//使得生产方和消费方并发运行
		fork
			p.run();
			c.run();
		join
	end
endprogram

输出结果:
在这里插入图片描述
在这里插入图片描述

6、使用两个信箱来实现线程的同步

可以再使用一个信箱把消费者的完成信息发回给生产者

program automatic mbx_mbx2;
	mailbox mbx, rtn;
	class Producer;
		task run();
			int k;
			for(int i = 1; i < 4; i++) begin
				$display("Producer: before put(%0d)", i);
				mbx.put(i);
				rtn.get(k);		//生产者从返回的信箱取值,如果可以取得,说明消费者已经完成,如果没有取得值,说明消费者还没有完成事务的处理,生产者则会阻塞
				$display("Producer: after get(%0d)", k);
			end
		endtask
	endclass

	class Consumer;
		task run();
			int i;
			repeat(3) begin
				$display("Consumer: before get");
				mbx.get(i);
				$display("Consumer: after get(%0d)", i);
				rtn.put(-i);		//返回到rtn信箱的信息仅仅是原始整数的一个相反值,可以使用任意值,只要能表示有返回值即可
			end
		endtask
	endclass : Consumer
	
	Producer p;
	Consumer c;

	initial begin
		mbx = new();
		rtn = new();
		p = new();
		c = new();
		fork
			p.run();
			c.run();
		join
	end
endprogram

输出结果:
在这里插入图片描述

7、其他的同步技术

通过变量或者旗语来阻塞线程也同样可以实现握手。事件是最简单的结构,其次是通过变量阻塞。旗语相当于第二个信箱,但没有信息交换。SV中的定容信箱有一个缺点就是无法再生产者放入第一个事务的时候让它阻塞,会一直比消费者提前一个事务的时间。

三、构筑带线程并可实现线程间通信的测试程序

1、基本的事务处理器

分层的环境测试平台:
在这里插入图片描述
处于发生器和驱动器之间的代理

class Agent;
	mailbox gen2agt, agt2drv;
	Transaction tr;
	
	function new(mailbox gen2agt, agt2drv);
		this.gen2agt = gen2agt;
		this.agt2drv = agt2drv;
	endfunction

	task run();
		forever begin;
			gen2agt.get(tr);		//从上游的模块中获取事务
			...
			agt2drv.put(tr);		//把事务发送给下游模块
		end
	endtask
endclass

2、配置类

配置类允许你在每次仿真时对系统的配置进行随机化

配置类

class Config;
	bit[31:0] run_for_n_trans;
	constraint reasonable{
		run_for_n_trans inside {[1:1000]};
		}
endclass

3、环境类

环境类包含了发生器、代理、驱动器、监视器、检验器、记分板,以及它们之间的配置对象和信箱。

环境类

class Environment;
	Generator gen;
	Agent agt;
	Driver drv;
	Monitor mon;
	Checker chk;
	Scoreboard scb;
	Config cfg;
	mailbox gen2agt, agt2drv, mon2chk;

	extern function new();
	extern function void gen_cfg();
	extern function void build();
	extern task run();
	extern task wrap_up();
endclass

function Environment::new();
	cfg = new();
endfunction

function void Environment::gen_cfg();
	assert(cfg.randomize);
endfunction

function void ENvironment::build();
	//初始化信箱
	gen2agt = new();
	agt2drv = new();
	mon2chk = new();
	//初始化事务处理器
	gen = new(gen2agt);
	agt = new(gen2agt, agt2drv);
	drv = new(agt2drv);
	mon = new(mon2chk);
	chk = new(mon2chk);
	scb = new();
endfunction

task Environment::run();
	fork
		gen.run(cfg.run_for_n_trans);
		agt.run();
		drv.run();
		mon.run();
		chk.run();
		scb.run(cfg.run_for_n_trans);
	join
endtask

task Environment::wrap_up();
	fork
		gen.wrap_up();
		agt.wrap_up();
		drv.wrap_up();
		mon.wrap_up();
		chk.wrap_up();
		scb.wrap_up();
	join
endtask

4、测试程序

program automatic test;
	Environment env;
	initial begin
		env = new();
		env.gen_cfg();
		env.build();
		env.run();
		env.wrap_up();
	end
endprogram

四、结束语

你的设计可以用很多并发运行的独立块来建模,所以测试平台也必须能够产生很多激励流并检验并发线程的反应。fork-join、fork-join_none、fork-join_any用于动态创建线程,线程之间可以使用事件、旗语、信箱,以及@事件控制和wait语句来实现通信和同步。disable可以中止线程。

  • 1
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SystemVerilog的听课学习笔记,包括讲义截取、知识点记录、注意事项等细节的标注。 目录如下: 第一章 SV环境构建常识 1 1.1 数据类型 1 四、二值逻辑 4 定宽数组 9 foreach 13 动态数组 16 队列 19 关联数组 21 枚举类型 23 字符串 25 1.2 过程块和方法 27 initial和always 30 function逻辑电路 33 task时序电路 35 动态 静态变量 39 1.3 设计例化和连接 45 第二章 验证的方法 393 动态仿真 395 静态检查 397 虚拟模型 403 硬件加速 405 效能验证 408 性能验证 410 第三章 SV组件实现 99 3.1 接口 100 什么是interface 101 接口的优势 108 3.2 采样和数据驱动 112 竞争问题 113 接口中的时序块clocking 123 利于clocking的驱动 133 3.3 测试的开始和结束 136 仿真开始 139 program隐式结束 143 program显式结束 145 软件域program 147 3.4 调试方法 150 第四章 验证的计划 166 4.1 计划概述 166 4.2 计划的内容 173 4.3 计划的实现 185 4.4 计划的进程评估 194 第五章 验证的管理 277 6.1 验证的周期检查 277 6.2 管理三要素 291 6.3 验证的收敛 303 6.4 问题追踪 314 6.5 团队建设 321 6.6 验证的专业化 330 第六章 验证平台的结构 48 2.1 测试平台 49 2.2 硬件设计描述 55 MCDF接口描述 58 MCDF接口时序 62 MCDF寄存器描述 65 2.3 激励发生器 67 channel initiator 72 register initiator 73 2.4 监测器 74 2.5 比较器 81 2.6 验证结构 95 第七章 激励发生封装:类 209 5.1 概述 209 5.2 类的成员 233 5.3 类的继承 245 三种类型权限 protected/local/public 247 this super 253 成员覆盖 257 5.4 句柄的使用 263 5.5 包的使用 269 第八章 激励发生的随机化 340 7.1 随机约束和分布 340 权重分布 353 条件约束 355 7.2 约束块控制 358 7.3 随机函数 366 7.4 数组约束 373 7.5 随机控制 388 第九章 线程与通信 432 9.1 线程的使用 432 9.2 线程的控制 441 三个fork...join 443 等待衍生线程 451 停止线程disable 451 9.3 线程的通信 458 第十章 进程评估:覆盖率 495 10.1 覆盖率类型 495 10.2 功能覆盖策略 510 10.3 覆盖组 516 10.4 数据采样 524 10.5 覆盖选项 544 10.6 数据分析 550 第十一章 SV语言核心进阶 552 11.1 类型转换 552 11.2 虚方法 564 11.3 对象拷贝 575 11.4 回调函数 584 11.5 参数化的类 590 第十二章 UVM简介 392 8.2 UVM简介 414 8.3 UVM组件 420 8.4 UVM环境 425

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值