Menu Close

Testbench 编写进阶(3)–数学函数

 

上两节内容我们讲解了在Testbench中应用最广泛的$display函数和$monitor过程,还有其他函数虽然应用较少,但如果能合理使用这些函数在比较复杂的设计中可以简化testbench的编写,仿真中的错误的定位会有很大的帮助,下面我们就来介绍这些函数的使用。

  1. 数学函数

在数学运算的建模中,充分利用仿真库中提供的数学函数,为行为建模和仿真提供了方便。

注意:这些函数只能用来仿真,不能综合。

function

函数

Description 描述
$ln(x) Natural logarithm log(x) 自然对数In(x)
$log10(x) Decimal Logarithm log10(x) 10为底的对数
exp(x) Exponential of x (ex) where e=2.718281828… 自然指数
sqrt(x) Square root of x 平方根
$pow(x, y) xy X 的y次幂
$floor(x) Floor x 求底函数
$ceil(x) Ceiling x 求顶函数
$sin(x) Sine of x where x is in radians 正弦
$cos(x) Cosine of x where x is in radians 余弦
$tan(x) Tangent of x where x is in radians 正切
$asin(x) Arc-Sine of x 反正弦
$acos(x) Arc-Cosine of x 反余弦
$atan(x) Arc-tangent of x 反正切
$atan2(x, y) Arc-tangent of x/y x/y的反正切
$hypot(x, y) Hypotenuse of x and y : sqrt(xx + yy) 直角三角形斜边函数
$sinh(x) Hyperbolic Sine of x 双曲正弦
$cosh(x) Hyperbolic-Cosine of x 双曲余弦
$tanh(x) Hyperbolic-Tangent of x 双曲正切
$asinh(x) Arc-hyperbolic Sine of x 反双曲正弦
$acosh(x) Arc-hyperbolic Cosine of x 反双曲余弦
$atanh(x) Arc-hyperbolic tangent of x 反双曲正切

$ceil(x) 函数返回实数X最大的整数部分,如:x=2.33, 则$ceil(x)=3; x=2.99, $ceil(x)=3;

$floor(x)函数返回实数x的最小整数部分,如:x=2.33, 则$ceil(x)=2; x=2.99, $ceil(x)=2;

其它函数数学意义比较明确,这里不再累述。

例:

仿真结果如下:

数学函数在Verilog程序设计中的作用:

  • 行为建模,在实体模块成功开发之前利用数学函数可以评估系统的功能。
  • 为设计实体提供输入,特别是采用定点或浮点小数运算时需要提供各种形式的输入,在基于FPGA的DSP开发中应用很广泛。
  • 结果对比;实体程序实现的数学算法,利用数学函数的结果与实体程序的输出做对比,可以评估结果是否正确,以及结果的精度是否满足要求。

以上的函数将会在后续课程中结合具体实例进行讲解。

还有一个函数$clog2也被广泛使用,下面将详细讲解该函数的使用。

2. $clog2函数

该函数可以认为是由两个函数构成,$log2 (以2为底的log函数)+$ceil ()。 即先求$log2, 再求$cell, 也可以可以理解为 $ceil($log2(x))。例如: x=6, 则y=$clog2(x)的结果y=3。这个函数对于求位宽非常有用。注意:$clog2(x)函数是可综合的函数。$clog2(x)函数在仿真时,x可以是reg,wire, integer 变量或parameter ,localparam 声明的参数。而综合时只支持parameter或localparam 声明的参数。

例1: $clog2(x) 函数测试

程序如下:



  	`timescale  1 ns /1 ps


  module tb


  (


  );


  wire  [7:0]  a=253;


  reg   [3:0]  b;


  initial  begin


  b=0;


  #10


  b=$clog2(a);


  #10;


  end


  endmodule


  

仿真结果如图1所示,从仿真结果看变量a=253, $clog2(a)的结果为8.

图1

修改上面的测试程序,添加实体模块,继续测试在实体模块中$clog2的行为,程序如下:

(1)实体模块程序



  	module test_clog


  (


   input	[7:0]	addr,


   output	[7:0] data


  );


  assign data=$clog2(addr);


  endmodule


  

(2) 仿真激励程序



  	`timescale  1 ns /1 ps


  module tb


  (


  );


  wire  [7:0]  a=253;


  wire  [7:0]  data;


  reg   [7:0]  addr;


  reg   [3:0]  b;


  initial  begin


  b=0;


  addr=0;


  #10


  b=$clog2(a);


  addr=100;


  #10;


  end


  test_clog  test_clog_inst


  (


  .addr(addr),


  .data(data)


  );


  endmodule


  

仿真结果如图2:

图2

从图2的仿真结果可以看出,wire,reg,integer类型变量在仿真时,无论在testbench 模块还是实体程序中$clog2函数都可以正确使用。但在实体模块中,$clog2只能对常数求解,因此如果将上例中的实体模块进行综合,会得到图3中的错误提示。

图3

例2: 设计一个容量为参数化的RAM,用$clog2函数计算地址的宽度

(1)实体程序如下:



  `timescale  1 ns /1 ps


  module test_clog


  #(parameter DEEP=256)


  (


   input	[$clog2(DEEP)-1:0]	addr,


   output	[7:0] data


  );


   


  reg  [7:0] ram [0:DEEP-1];   //定义ram, 位宽为8位, 深度为DEEP。


  assign data=ram[addr];    // 根据地址读取ram中的值。


  integer i;


  initial begin                        //ram 初始化


  for(i=0;i<256;i=i+1)


  ram=i+1;


  end


  endmodule


  

上面程序中 语句“input [$clog2(DEEP)-1:0] addr,”中用到了$clog2函数求地址宽度。参数化传递在求地址端口位宽,意义非常明显,比如DEEP等于100,则计算出$clog2(DEEP)=7, 也就是休要7为地址才能完成整个存储器的寻址。

(2)仿真激励程序如下:



  `timescale  1 ns /1 ps


  module tb


  (


  );


  wire  [7:0]  data;


  reg   [7:0]  addr;


  initial  begin


  addr=0;


  #10


  for(addr=0;addr<=100;addr=addr+1)


  #10;


  #10;


  end


  test_clog  test_clog_inst


  (


  .addr(addr),


  .data(data)


  );


  endmodule


  

(3)仿真波形如图4,5

图4

图5

从图4,5 的仿真结果看,的确将实体模块中的RAM内容正确读出。在图5中我们还注意到一个细节,当我们需要读取101个数据时,由于addr及时RAM的地址,也是for循环的判断条件,因此在终止for循环时,地址比给定的边界值大1(如101)才能满足条件。这不是我们希望的结果,虽然在本程序中影响不大。分析一下,下面程序的运行结果,



  `timescale  1 ns /1 ps


  module tb


  (


  );


  wire  [7:0]  data;


  reg   [7:0]  addr;


  initial  begin


  addr=0;


  #10


  for(addr=0;addr<=255;addr=addr+1)


  #10;


  #10;


  end


  test_clog  test_clog_inst


  (


  .addr(addr),


  .data(data)


  );


  endmodule


  

先看看在边界时的仿真结果,如图6

图6

从图6可以看出,RAM的内容读取没有问题,但程序在地址addr达到255后并没有停下来,而是继续运行。为什么会出现这个原因呢?

首先在initial过程中程序只执行一次,在quartus II仿真相关设置中也正确设置了相关选项,如图7

图7

可见问题出在for循环的边界判断上。对于条件addr<=255,实际上当addr=256时才满足条件,而addr的位宽是8位的(“reg [7:0] addr;”),因此addr的值永远不可能达到8位,因此才会出现上面的结果。解决方案如下:

  1. 调整addr的位宽,由8位变成9位,如修改addr的声明为”reg [8:0] addr”,但这并不是一个好的方案,因为增加位宽后,应该评估对其它程序的影响。
  2. 比较好的方案引入一个临时整形变量i,由i作为for循环的控制变量,在for循环体内修改addr的值, 修改后程序如下,


  `timescale  1 ns /1 ps


  module tb


  (


  );


  wire  [7:0]  data;


  reg   [7:0]  addr;


  integer      i;


  initial  begin


  addr=0;


  #10


  for(i=0;i&lt;255;i=i+1) begin


  #10  addr=addr+1;


  end


  #10;


  end


  test_clog  test_clog_inst


  (


  .addr(addr),


  .data(data)


  );


  endmodule


  

图8

从图8的仿真结果可以看出,经过修改后for循环可以按照预期设定运行。

实验课:设计一段程序在硬件开发板上(pra006或prx100D)观察RAM的实现,以及RAM初始化的实现。要求分别在Quartus II和Vivado下实现。

Posted in FPGA, FPGA 教材教案, Quartus II, Quartus II, Verilog, Verilog, 开发工具, 开发工具, 开发语言, 教材与教案, 文章, 编程语言

发表评论

您的电子邮箱地址不会被公开。

Leave the field below empty!

相关链接