卢骏 · 5月23日

sv利用fork join_none实现信号打拍操作

利用system verilog的fork join_none,能够实现打拍操作。从而不需要写其他的逻辑来实现打拍操作。

下面,介绍下,如何实现。

有3个信号,a,b,c,现在需要实现,b是a的打拍,c是b的打拍。不能使用always来实现。对于这个问题,其实使用sv的fork join_none就可以做到。

以下是测试代码:


module tb_top();
  reg clk;
  reg [5:0] a;
  reg [5:0] b;
  reg [5:0] c;

   class trans;
   bit[5:0] value;
   function new(bit[5:0] a);
       value = a;
   endfunction
endclass

initial begin
   clk = 0;
   forever #5 clk = ~clk;
end

initial begin
   a = 0;
   forever begin
       @(posedge clk) a <= a + 1;
   end
end

initial begin  // 核心initial
   trans t;
   b = 0;
   c = 0;
   while(1) begin
       @(posedge clk); // process A
       b <= a;
       t = new(b);
       fork
           begin   // process B
               @(posedge clk);
               c <= t.value;
           end
       join_none
   end
end

initial begin
   $fsdbDumpfile("tb_top.fsdb");
   $fsdbDumpvars("tb_top", 0);
   #10000;
   $finish();
end
endmodule: tb_top

核心是第三个initial语句块,其余是辅助代码。

下面,来重点分析这个第三个initial语句块:

initial begin
   trans t;
   b = 0;
   c = 0;
   while(1) begin
       @(posedge clk); // process A
       b <= a;
       t = new(b);
       fork
           begin   // process B
               @(posedge clk);
               c <= t.value;
           end
       join_none
   end
 end
 

在while(1)循环内部,有进程A。在进程A中,首先等待一个时钟上升沿,然后采集信号a的值,给b赋值,这样,就将信号a,打了一拍,传给了b。注意,这里要使用非阻塞赋值。b采集到a值之后,给类实例t分配一个空间,然后传入b的值,为了将来能够传给b。因此之前b是非阻塞赋值,所以那

然后进程A中,使用for join_none,创建一个子进程B,这里使用join_none来创建,因为join_none,有一个特性,父进程不用等子进程完毕,就可以直接退出。所以进程A创建子进程B之后,就退出fork join_none代码块了。然后因为while(1)循环原因,然后又等待下一个时钟上升沿。

而子进程B,此时等待上升沿,相当于又打了一拍。

在下一个cycle,子进程B等到了上升沿,将类实例的t中的value值取出,这个值,就是上一个cycle,信号b的值,然后驱动给c。那c就相当于b打了一拍了。此时子进程B,退出fork join_none,生命周期也就结束了。

此时,主进程A,也等到了上升沿,采集a的值,给b赋值,这样又将a信号,打拍到了b。然后将b原来的值,放到t里面。此时在创建一个新的进程B,来处理下一拍的打拍。

这样,一个cycle一个cycle的处理下去,其实就实现了之前提到的,信号打拍的问题。

从仿真波形,也能够印证这一点:
1.png

从波形上,c信号是b信号的打拍,b信号是a信号的打拍。

介绍这些,那这个在验证中,有什么样的应用场景呢?

下面,就举例说明一下,在验证环境中,什么场景可以使用上述的功能。

比如要验证一个dut,该dut有一路输出A,有两路输入B和C。第一路输入B,要在dut输出C的下一拍,给反馈,第二路输入B,要在第一路输入A的下一拍,给反馈。

如果这个时候,要实现一个agent,来模拟上述的场景,那么这个时候,就可以用到上面介绍的这种方式。

在agent的main_phase中。伪代码如下:

while(1) begin
     @(posedge clk);
     采集输入A
     处理输入A数据,驱动输出B
     生成输出C的数据

   fork
      begin
        @(posedge clk);
        拿到上面生成输出C的数据
        驱动输出C
      end
  join_any
end

这样,就很容易了实现了驱动两个stage的信号。

更多相关阅读

systemverilog使用$fwrite系统函数打印信息到屏幕
深度揭秘:"万物GPU"(The GPU of Everything)
手机AI芯片有多神秘?解读华为自研架构NPU

原文首发于骏的世界博客
作者:卢骏.
更多IC设计相关的文章请关注IC设计极术专栏,每日更新。

2 阅读 72
推荐阅读
0 条评论
关注数
282
文章数
108
主要交流IC以及SoC设计流程相关的技术和知识
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
Arm中国学堂公众号
关注Arm中国学堂
实时获取免费 Arm 教学资源信息
Arm中国招聘公众号
关注Arm中国招聘
实时获取 Arm 中国职位信息