📜  用户定义的基元

📅  最后修改于: 2021-01-11 14:59:34             🧑  作者: Mango

Verilog用户定义的原语

一种建模技术,用户可以通过设计和指定称为用户定义基元(UDP)的新基元元素来虚拟地辩论预定义的门基元。这些原语是自包含的,不会实例化其他原语或模块。

Verilog提供了一组标准的原语,例如AND,NAND, NOT, ORNOR ,作为语言的一部分。这些也称为内置原语。

这些新UDP的实例可以与门原语相同的方式使用,以表示要建模的电路。此技术可以减少内存量并提高仿真性能。 Verilog-XL算法可加快对这些UDP的评估。

但是,设计人员有时会在开发设计时喜欢使用其定制的基元。

每个UDP都只有一个输出,可以处于以下状态之一:0、1或x。不支持三态值z。任何具有值Z的输入都将被视为X。

这两种类型的行为可以用用户定义的原语表示:

  • 组合UDP
  • 顺序UDP

顺序UDP使用其输入值和其输出的当前值来确定输出的下一个值。

顺序UDP提供了一种高效且简便的方法来对诸如锁存器和触发器之类的顺序电路进行建模。

顺序UDP可以对级别敏感和边缘敏感行为进行建模。组合UDP的最大输入数量为10。顺序UDP的最大输入数量限制为9,因为内部状态被视为输入。

句法

UDP以保留字Primitive开头,以endprimitive结尾。基本类型的端口/终端应遵循。 UDP应该在模块endmodule之外定义。

primitive UDP_name (output, input, ...);
  port_declaration
  [ reg output; ]
  [ initial output = initial_value; ]
  table
     truth_table
  endtable
endprimitive

UDP规则

  • UDP仅使用标量输入端子(1位)。
  • UDP只能有一个标量输出。输出终端必须始终始终显示在终端列表中。
  • 顺序UDP中的状态使用初始语句初始化。
  • 状态表条目可以包含0、1或X的值。传递给UDP的Z值被视为X值。
  • UDP在与模块相同的级别上定义。
  • UDP与门原语完全一样实例化。
  • UDP不支持inout端口。

Verilog UDP符号

Verilog用户定义的原语可以在与模块定义相同的级别上编写,但不能在moduleendmodule之间编写。它们可以有许多输入端口,但总是一个输出端口,而双向端口无效。所有端口信号必须为标量,这意味着它们必须为1位宽。

硬件行为被描述为原始状态表,它列出了终端表中输入及其对应输出的不同可能组合。输入和输出信号的值使用以下符号表示。

Symbol Comments
0 Logic 0
1 Logic 1
x Unknown, can be either logic 0 or 1. It can be used as input/output or current state of sequential UDPs
? Logic 0, 1 or x. It cannot be the output of any UDP
No change, only allowed in the output of a UDP
ab Change in value from a to b where a or b is either 0, 1, or x
* Same as ??, indicates any change in the input value
r Same as 01 -> rising edge on input
f Same as 10 -> falling edge on input
p Potential positive edge on input; either 0->1, 0->x, or x->1
n Potential falling edge on input; either 1->0, x->0, 1->x

组合UDP

在组合UDP图中,输出状态作为当前输入状态的函数来确定。每当输入更改状态时,都会对UDP进行评估,并匹配状态表行之一。输出状态设置为该行指示的值。组合UDP的最大输入数量为10。

考虑以下示例,该示例定义了一个具有两个数据输入(控制输入)的多路复用器。但是只能有一个输出。

// Output should always be the first signal in the port list

primitive mux (out, sel, a, b);
    output out;
    input sel, a, b;

    table
                //   sel     a     b                 out
            0     1     ?     :     1;
            0     0     ?     :     0;
            1     ?     0     :     0;
            1     ?     1     :     1;
            x     0     0     :     0;
            x     1     1     :     1;
    endtable

endprimitive

一种 ?表示信号可以是0、1或x,与决定最终输出无关紧要。

下面是一个testbench模块,该模块实例化UDP并对其施加输入刺激。

module tb;
  reg     sel, a, b;
  reg [2:0] dly;
  wire     out;
  integer i;

  // Instantiate the UDP
  // UDPs cannot be instantiated with port name connection
  mux u_mux ( out, sel, a, b);

  initial begin
    a <= 0;
    b <= 0;

    $monitor("[T=%0t] a=%0b b=%0b sel=%0b out=%0b", $time, a, b, sel, out);
    
 // Drive a, b, and sel after different random delays
    for (i = 0; i < 10; i = i + 1) begin
          dly = $random;
      #(dly) a <= $random;
          dly = $random;
      #(dly) b <= $random;
          dly = $random;
      #(dly) sel <= $random;
    end
  end
endmodule

顺序UDP

顺序UDP允许在同一描述中混合使用电平敏感和边缘敏感的结构。输出端口还应在UDP定义中声明为reg类型,并且可以选择在初始语句中初始化。

顺序UDP使用其输入的值和其输出的当前值来确定其输出的下一个值。输出的值也是UDP的内部状态。

顺序UDP在输入和输出字段之间有一个附加字段,该字段由代表当前状态的“:”定界。

顺序UDP提供了一种简单有效的方法来对锁存器和触发器等顺序电路进行建模。顺序UDP的最大输入数量限制为9,因为内部状态被视为输入。顺序UDP有两种。

1.级别敏感的UDP

对级别敏感的顺序行为用与组合行为相同的方式表示,除了将输出声明为reg类型,并且每个表条目中都有一个附加字段。

此新字段表示UDP的当前状态。顺序UDP中的输出字段表示下一个状态。

primitive d_latch (q, clk, d);
    output     q;
    input     clk, d;
    reg      q;

    table
                 // clk     d         q     q+
            1     1     :    ? :    1;
            1     0     :     ? : 0;
            0     ?     :     ? : -;
    endtable

endprimitive

在上面的代码中,表最后一行的连字符“-”表示q +的值没有变化。

module tb;
  reg clk, d;
  reg [1:0] dly;
  wire q;
  integer i;

  d_latch u_latch (q, clk, d);

  always #10 clk = ~clk;

  initial begin
    clk = 0;

    $monitor ("[T=%0t] clk=%0b d=%0b q=%0b", $time, clk, d, q);

    #10;                               // To see the effect of X

    for (i = 0; i < 50; i = i+1) begin
      dly = $random;
      #(dly) d <= $random;
    end

    #20 $finish;
  end
endmodule

2.边缘敏感的UDP

在对电平敏感的行为中,输入和当前状态的值足以确定输出值。

边沿敏感行为的不同之处在于,输出的更改是由输入的特定转换触发的。

在以下示例中,AD触发器被建模为Verilog用户定义的原语。请注意,时钟的上升沿由01还是0指定?

primitive d_flop (q, clk, d);
    output  q;
    input     clk, d;
    reg     q;

    table
                               // clk         d          q         q+
            // obtain output on rising edge of clk
            (01)    0     :     ?     :     0;
            (01)     1     :     ?     :     1;
            (0?)     1     :     1     :     1;
            (0?)     0     :     0     :     0;

            // ignore negative edge of clk
            (?0)     ?     :     ?     :     -;

            // ignore data changes on steady clk
            ?         (??):     ?     :     -;
    endtable

endprimitive

随机时钟后,在测试台中使用随机的d输入值实例化并驱动UDP。

module tb;
  reg clk, d;
  reg [1:0] dly;
  wire q;
  integer i;

  d_flop u_flop (q, clk, d);

  always #10 clk = ~clk;

  initial begin
    clk = 0;

    $monitor ("[T=%0t] clk=%0b d=%0b q=%0b", $time, clk, d, q);

    #10;  // To see the effect of X

    for (i = 0; i < 20; i = i+1) begin
      dly = $random;
      repeat(dly) @(posedge clk);
      d <= $random;
    end

    #20 $finish;
  end
endmodule

1个时钟延迟后,输出q跟随输入d,这是D触发器的期望行为。