[Verilog8]仿真语法_时间_程序块_延时_时钟

Verilog仿真语法,仿真时间设定,程序块语句体,延时等待语法,时钟激励生成

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

系统时间单位与进度设定

  • 在仿真文件的开头,需要使用"`timescale"时标指令,定义好系统的时间单位与精度,这样后续仿真文件中对应的仿真时间数值,都会参照设计者定义的单位时间。具体语法如下:
`timescale <number0><unti0> / <number1><unit1>
  • 例如,定义系统的仿真的单位时间长度为1ns,仿真的时间精度为1ps,具体代码如下:
`timescale 1 ns / 1 ps

程序块语句体

  • Verilog中有两种程序块语句体,即always和initial,它们分别对应循环执行和单次执行的模式,分别介绍如下。

always程序块

  • always程序块是循环执行的程序块,当用于FPGA设计描述时,它必须有敏感量表,但是对于仿真代码的编写时,敏感量表则可以省略不写。这类省略敏感量表的always程序块,将会毫不停歇地从头至尾不断循环执行,因此,必须在其内部放置能够消耗仿真时间的延时等待语句,否则将会造成仿真死循环,例如:
always begin //会导致死循环
    clk = 1'b0;
    clk = 1'b1;
end
  • 正确的写法类似于:
always begin
    #50 clk = 1'b0;
    #50 clk = 1'b1;
end  

initial程序块

  • initial程序块是单次执行的程序块,当用于FPGA设计描述时,initial程序块的作用是为变量等进行初始化工作,此时的initial程序块将会先于所有的always程序块运行;不过当用于仿真代码的编写时,initial程序块将会和所有的always程序块同时开始执行。
  • 我们常使用initial程序块用于帮助我们完成那些只需要被单次或有限次执行的仿真功能,如系统的复位操作,举例如下:
initial begin
    rst = 1'b0;
    #100;
    rst = 1'b1;
    #10
    rst = 1'b0;
end

延时等待语法

有限等待语句

  • 有限等待语句用于串行执行的语句结构中,表明当程序执行到这一条语句时,需要等待<delay_value>的时间后,才可以继续执行下一条语句。具体语法如下:
# <delay_value>
  • 具体举例如下,当给data赋值1'b1后,延迟100个单位时间后,再给data赋值1'b0
data = 1'b1;     //开始执行时间t=0ns
#100;               //开始执行时间t=0ns
data = 1'b0;    //开始执行时间 t= 100 ns

无限等待语句

  • Verilog中没有专门的无限等待语句,这主要是因为Verilog中有initial程序块。不过如果真的需要无限等待这样一个功能,可以利用无限循环来构建一个类似的功能,例如:
forever begin
    #100;
end

变换等待语句

  • 变换等待语句用于串行执行的语句结构中,表明当程序执行到这一条语句时就开始等待,直到<signal>信号上发生了变化才停止等待,继续执行后续语句。其功能类似于组合逻辑always中的敏感信号列表。具体书写语法如下:
@(<signal>); //一直等待直到<signal>信号发生变化

边沿等待语句

  • 边沿等待语句共有两种,一种是针对信号上升沿的,一种是针对信号下降沿的。
@(posedge<signal>); //信号上升沿等待语句
@(negedge<signal>); //信号下降沿等待语句
  • 边沿等待语句用于串行执行的语句结构中,表示当程序执行到该语句后就开始等待,直到<signal >信号上发生相应边沿事件时才停止等待,开始执行后续语句。例如,可以利用边沿等待语句来模仿。
always begin
    @(posedge<signal>); //信号上升沿等待语句
    Q <= D;
end

条件等待语句

  • 条件等待语句用于串行执行的语句结构中,表示当程序执行到该语句后就开始等待,直到<signal>==<value>为真时才停止等待,继续执行后续的语句。
wait(<signal> == <value>); //条件等待语句

赋值等待语句

  • Verilog中有三种赋值等待语句,分别介绍如下。

连续赋值等待语句

assign #<delay_value> <signal_a> = <signal_b>; 
  • 这是一条并行语句,它表示将<signal_b>的值延迟<delay_value>的时间后赋给<signal_a>,从波形图上来看,<signal_a><signal_b>的波形完全一模一样,只不过相位上滞后<delay_value>的时间。
  • 下面是一个利用连续赋值等待语句来模拟FPGA中物理连线的线延迟时间参数的例子。
assign #25 delay_clk = clk; //delay_clk滞后于clk信号25个单位时间

阻塞赋值等待语句

  • 阻塞赋值主要用于程序块内部的串行语句中,用"="表示。阻塞赋值的等待语句有两种写法,具体如下:
  1. 写法一:
# <delay_value> <signal_a> = <signal_b>; 
  • 它表示当程序执行到该语句时,开始等待,等够<delay_value> 时间后,将<value>值赋给<signal>,因此,该写法等同于一条有限等待语句加上一条阻塞赋值语句。例如,以下两个always程序块中生成的信号a和b波形是一模一样的。
always begin //example1
    #50 a = 1'b0;
    #100 a = 1'b1;
end
always begin //example2
    #50;
    b =1'b0;
    #100;
    b = 1'b1;
end
  1. 写法二:
<signal> = #<delay_value> <value>;
  • 它表示<value>的值则需要在该语句执行后等待<delay_value>时间后才会赋给<signal>,由于阻塞赋值的概念就是必须赋值完成才执行后续语句,程序执行到该条语句后也会等待<delay_value>时间后才开始执行下一条指令,因此,关于阻塞赋值等待语句两种写法的意义虽然略有不同,但功能是完全一样的。例如,以下always程序块中生成的信号c波形与前例中的信号a、b是一模一样的。
always begin //example3
    c = #50 1'b0;
    c = #100 1'b1;
end

非阻塞赋值等待语句

  • 非阻塞赋值也主要用于程序块内部的串行语句中,用“<=”表示,非阻塞赋值等待语句也有两种写法,但功能却不一样,分别介绍如下。
  1. 写法一:
# <delay_value> <signal_a> <= <signal_b>; 
  • 非阻塞赋值等待语句的这种写法与阻塞赋值等待语句的写法和功能完全一样,表示当程序执行到该语句时,开始等待,等待<delay_value>的时间后,将<value>的值赋给<signal>,因此,该写法等同于一条有限等待语句加上一条阻塞或非阻塞赋值语句。例如,以下always程序块中生成的信号d和前两例中的信号a、b、c波形是一模一样的。
always begin //example4
    #50 a <= 1'b0;
    #100 a <= 1'b1;
end
  1. 写法二:
<signal_a> <= # <delay_value> <signal_b>; 
  • 虽然同为非阻塞赋值等待语句,但当程序执行到该语句时,并不进行丝毫的等待,而是立刻去执行后续语句,而<value>的值则需要在该语句执行后等待<delay_value> 时间后才会赋给<signal>。例子如下:
always begin
    data <= #50 1'b1;  //开始执行时间t=0ns,赋值生效时间t=50ns;
    #100;                      //开始执行时间t=0ns;
    data <= #50 1'b0;  //开始执行时间t=100ns,赋值生效时间t=150ns;
end
  • 因此非阻塞赋值等待语句的写法二本身是不消耗仿真时间的,如果always内部-不小心只含有这类语句,将会进入仿真死循环,导致仿真器不停地工作但仿真时间却停滞不前。
  • 其实,非阻塞赋值等待语句的写法二才是最贴近于寄存器真实行为的仿真语句,也是仿真器在时序仿真中最常用的“伎俩”。

时钟激励语法

占空比50%的时钟

  • 占空比为50%的时钟信号为我们最为常用的时钟激励信号。有多种方式产生时钟激励信号,下面列举两种产生方式
  1. 方法一:利用always程序块产生
always begin
    <clock> = 1'b0;
    #<PERIOD / 2>;
    <clock> = 1'b1;
    #<PERIOD / 2>;
end
// 或者
reg <clock> = 1'b0;
always begin
    #<PERIOD / 2>;
    <clock> = ~<clock>;
end
  1. 方法二:使用initial程序块和forever循环语句
initial begin
    <clock> = 1'b0;
    forever
        #<PERIOD / 2> <clock> = ~<clock>;
end

占空比、周期参数时钟

  • 其中,DUTY_CYCLE为占空比。
  1. 方法一:利用always程序块产生
always begin
    <clock> = 1'b0;
    #<(PERIOD - PERIOD * DUTY_CYCLE>;
    <clock> = 1'b1;
    #<(PERIOD * DUTY_CYCLE>;
end
  1. 方法二:使用initial程序块和forever循环语句
initial begin
    forever begin
        <clock> = 1'b0;
        #<(PERIOD - PERIOD * DUTY_CYCLE>;
        <clock> = 1'b1;
        #<(PERIOD * DUTY_CYCLE>;
end

时钟相位调整法

  • 有时候不希望时钟信号的变化沿总是与仿真0时刻重合,因此需要在前述方法的基础.上进行一些修改。有多种方式产生时钟激励信号,下面列举三种方法。
  1. 方法一:修改起始状态
always begin
    #<SHIFT_TIME>  //相移时间
    <clock> = 1'b0;
    #<PERIOD / 2>;
    <clock> = 1'b1;
    #<PERIOD / 2 - SHIFT_TIME>;
end
  1. 方法二:利用非阻塞赋值等待语句
always begin
    <clock> <= #<SHIFT_TIME> 1'b0;
    #<PERIOD / 2>;
    <clock> <= #<SHIFT_TIME> 1'b1;
    #<PERIOD / 2>;
end
  1. 方法三:利用连续赋值等待语句
always begin
    <clock_reg> = 1'b0;
    #<PERIOD / 2>;
    <clock_reg> = 1'b1;
    #<PERIOD / 2>;
end
assign #<SHIFT_TIME>  <clock>=<clock_reg>;

差分时钟激励产生

  • 差分时钟激励的产生与单端时钟类似,只不过多了-.根线而已。在编写差分时钟激励时,可以先按照要求编写出其对应的单端时钟激励作为正极性时钟端,然后参考该正极性时钟再编写一个高、低电平完全颠倒的(对于50%占空比的时钟,也相当于相位差为180°)单端时钟激励作为负极性时钟端即可。有多种方式产生时钟激励信号,下面列举两种方法。
  1. 方法一:利用always程序块产生
always begin
    <clk_p> = 1'b0;
    <clk_n> = 1'b1;
    #<PERIOD / 2>;
    <clk_p> = 1'b1;
    <clk_n> = 1'b0;
    #<PERIOD / 2>;
end
  1. 方法二:使用initial程序块和forever循环语句
initial begin
    <clk_p> = 1'b0;
    <clk_n> = 1'b1;
    forever
        #<PERIOD / 2> {<clk_p , clk_n>} = ~{<clk_p , clk_n>};
end

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