赞
踩
使用旗语可以实现对同一资源的访问控制,类似于操作系统里面的互斥访问。在SV多个阻塞的线程会以FIFO的方式进行排队。
旗语有三种操作:
如果试图获取一个旗语而不希望被阻塞可以使用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
把发生器和驱动器想象成具备自治能力的事务处理器对象,它们通过信道交换数据。每个对象从它的上游对象中得到事务(如果对象本身是发生器,则创建事务 ),进行一些处理,然后把它们传递给夏有对象。这里的信道必须允驱动器和接收器异步操作。从硬件角度出发,对信箱的最简单的理解是把它看成一个具有源端和收端的FIFO。信箱是一种对象,必须调用new函数来进行实例化,实例化时可选择size参数大小,为0或者没有指定,则默认信箱是无限大的。使用put任务可以把数据放入信箱,get可以移除数据。信箱为空时get会阻塞,信箱满时put会阻塞。peek任务可以获取对信箱里数据的拷贝而不移除它。信箱里放的数据可以使单个的值,例如一个整数或者是任意宽度的logic,可以放入句柄但是不能放入对象。
使用信箱实现对象的交换: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
/* 定容信箱在两个线程之间扮演了一个缓冲器的角色 */ '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
在没有同步信号的情况下,可能会导致消费方还没有开始取数的时候,生产方就已经把信箱填满了,这是因为线程在没有碰到阻塞语句之前会一直运行,而生产方恰好没有碰到阻塞语句的话,可能会一口气儿直接把信箱填满了,换句话说,生产方“跑”到了消费方前面,供过于求,我们想要的是生产者和消费者之间最好有一个同步信号,生产者生产了之后,信号会马上通知消费者来“取货”,或者说消费者需要“取货”时,如果信箱里面“没货”,同步信号会立即通知生产者取“生产”,这样可以维持一个动态的平衡。
消费者使用一个内建的信箱方法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操作时才会发生阻塞。
可以在生产者把数据放入信箱后使用事件来阻塞它,消费者则在处理完数据后再触发事件。
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
输出结果:
可以再使用一个信箱把消费者的完成信息发回给生产者
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
输出结果:
通过变量或者旗语来阻塞线程也同样可以实现握手。事件是最简单的结构,其次是通过变量阻塞。旗语相当于第二个信箱,但没有信息交换。SV中的定容信箱有一个缺点就是无法再生产者放入第一个事务的时候让它阻塞,会一直比消费者提前一个事务的时间。
分层的环境测试平台:
处于发生器和驱动器之间的代理
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
配置类允许你在每次仿真时对系统的配置进行随机化
配置类
class Config;
bit[31:0] run_for_n_trans;
constraint reasonable{
run_for_n_trans inside {[1:1000]};
}
endclass
环境类包含了发生器、代理、驱动器、监视器、检验器、记分板,以及它们之间的配置对象和信箱。
环境类
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
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可以中止线程。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。