[Verilog9]仿真语法_循环_存储载入_随机数

Verilog仿真语法,循环仿真语法,存储载入函数 ,随机数生成函数

  • 在之前我们编写设计文件中的代码时,必须保证有实际硬件电路相对应,所以我们必须使用可综合的Verilog语句进行代码的书写;而仿真文件中的代码中,代码不要求具有现实可实现性。所以,在编写Verilog仿真文件时,我们可以自由的运用在软件设计中的编程思路,将会分几个章节介绍在仿真文件编写中常用到的一些常用语法。

循环仿真语法

  • 在Verilog语法中,我们常使用for循环设计描述的循环语句体。除了for循环语句外,在编写仿真文件时我们还可以使用很多种更加灵活地循环语句体,分别介绍如下:

while循环

while(<condition>) begin
    <statements>;
end
  • <condition>为真时重复执行<statements>中的内容,直到某次进行条件判断时发现<condition>为假则结束循环。例如:
while (sel == 1'b1)begin //当sel为逻辑1时产生时钟信号
    #50 clk = 1'b0;
    #50 clk = 1'b1;
end

forever循环

forever begin
    <statements>;
end
  • 持续不断地执行<statements>中的内容,永不停歇。例如使用forever语句产生时钟:
forever begin
    #50 clk = 1'b0;
    #50 clk = 1'b1;
end

repeat循环

repeat(<value>) begin
    <statements>;
end
  • 持续执行<statements>中的语句<value>次后退出循环。例如:
repeat(5) begin
    @(posedge CLK) ; //等待5次上升沿后执行后续语句
end

跳出循环

  • 如果循环执行的过程中,临时想要退出,就要使用跳出循环语句,语法如下:
disable  <loop_identifier>;
  • 其中,<loop_identifier>是待跳出的循环的标识符,因此没有标识符的循环是无法跳出的
  • 举例如下,initial中的forever循环用来产生一个周期为100ns的时钟信号,该循环的标识符定义为clockloop。在always中,一旦等待到quit信号的上升沿,则调用跳出循环语句来结束clockloop的运行。如果forever循环没有定义标识符,则它将一直执行。
initial 
    forever begin: clock_loop
        #50 clk = 1'b0;
        #50 clk = 1'bl; 
    end 
always @(posedge quit)
    disable clock_loop;

存储载入函数

  • 在Verilog中定义的存储器,可以通过系统存储载人语法进行初始化。随着编译器功能的不断增强,系统存储载人语法不光可以用于仿真,也可以用于实际的FPGA设计,介绍如下:
reg [<memory_width>] <reg_name> [<memory_depth>];
initial begin
    //二进制存储载入语法
    $readmemb("<file_name>",<reg_name>,<start_address>,<end_address>);
    //十六进制存储载入语法
    $readmemh("<file_name>",<reg_name>,<start_address>,<end_address>);
end
  • 其中,<reg_name>为存储器变量,<file_name> 是文件路径,<start_address>< end_address>两个参数用来指定载人时文件的起始和结束地址。在两个函数中,$readmemb是二进制存储载人语法、$readmemh是十六进制载人语法,只能使用于initial 程序块中。需要注意,<file_name>所指定的文件,只能包含二进制数据(readmembreadmemb)或十六进制数据(readmemh)、空白、注释和标号。
  • 其中,标号的语法为@<address>;,用来显示指定数据所处的地址,其目的和注释-一样,都是用来增强可读性。例如,如果mem.txt文件与仿真代码文件位于同一个目录下,其中的内容如下
  1. 写法一:
00  //address 0
01  //address 1
10  //address 2
11  //address 3
  1. 写法二:
@000 00  
@001 01  
@00210  
@003 11  
  • 使用如下仿真代码:
reg[1:0]  romA[3:0] , rom[0:3];
initial begin
    $readmemb("mem.txt",romA,0,3);
    $readmemb("mem.txt",romB,0,3);
  • 需要注意,无论存储器变量在定义的时候深度地址是从低到高还是从高到低,<reg_name>[0]中载人的都是地址<start_addres> 对应的数据。

随机数生成函数

  • 在仿真的时候,为了提高仿真的充分性,有时候需要产生一些随机的激励信号。Verilog也提供了丰富的随机数生成函数,这极大地方便了随机激励的生成。语法如下:
//生成基本的伪随机序列
<reg> = $random(<seed>);

//以均匀概率产生位于<start>,<\end>之间的随机序列
<reg> = $dist_uniform(<seed>,<start>,<\end>);

//用于产生均值为<mean>,方差为<standard_deviation>的正态分布随机序列。
<reg> = $dist_normal(<seed>,<mean>,<standard_deviation>);

// 用于产生均值为<mean>的指数分布随机序列。
<reg> = $dist_ exponential (< seed> ,< mean > ) ;
  • 为了防止每次仿真产生的随机序列都是--样的,使得其更加接近于真正的随机序列,Verilog提供了<seed>参数。<seed>参数其实就是伪随机序列的种子,只要种子不同,就可以产生出不同的伪随机序列,因此上述随机数生成语法中都有一个<seed>参数。具体举例如下:
integer r1,r2,r3;
integer seed,start,\end,;
initial begin
    seed = 1; start = 0; \end = 90; mean = 5; standard_deviation = 2;
end
always begin
    @(posedge clk);
    r1 = $random(seed) %1000;
    r2 = dist_uniform(seed , start , \end);
    r3 = $dist_normal(seed , mean , standard_deviation);
end
  • 关于以上代码,需要说明两点注意事项:
  1. 由于end是关键字,所以不能作为变量名,因此在前面加上反斜杠作为转义字符。
  2. 由于随机序列的产生方式是通过查表法产生的,在每次调用随机数生成函数后,函数会自动更新随机种子的数值。故不能将随机数的传入种子设置为一个整数常量,否则随机数生成函数将无法更新随机种子,产生新的随机数。例如,若写成$random(100);,则生成的随机数序列将常为0。

参考:
《FPGA之道》,狄超,刘萌著,西安交通大学出版社,6.2.3节