FPGA_DAY_03_编译指令到过程控制结构

FPGA_DAY3_verilog_3

Verilog中编译指令和连续赋值

//以反引号 ` 开始的某些标识符是 Verilog 系统编译指令。编译指令为 Verilog 代码的撰写、编译、调试等提供了极大的便利。
// `define,`undef
//编译阶段中,`define用于文本替换,类似于C语言中的#define
//一旦`define指令被编译,则在整个编译过程中都会有效
//在一个文件中定义:
`define    DATA_DW     32
//则在另一个文件中也可以直接使用DATA_DW
`define    S     $stop;   
//用`S来代替系统函数$stop; (包括分号)
`define    WORD_DEF   reg [31:0]       
//可以用`WORD_DEF来声明32bit寄存器变量
`undef用于取消之间的宏定义
`define    DATA_DW     32
reg  [DATA_DW-1:0]    data_in   ;
`undef DATA_DW
`ifdef, `ifndef, `elsif, `else, `endif
//这些属于条件编译指令。例如下面的例子中,如果定义了 MCU51,则使用第一种参数说明;如果没有定义 MCU、定义了 WINDOW,则使用第二种参数说明;如果 2 个都没有定义,则使用第三种参数说明。
`ifdef       MCU51
    parameter DATA_DW = 8   ;
`elsif       WINDOW
    parameter DATA_DW = 64  ;
`else
    parameter DATA_DW = 32  ;
`endif
//`elsif, `else 编译指令对于 `ifdef 指令是可选的,即可以只有 `ifdef 和 `endif 组成一次条件编译指令块。当然,也可用 `ifndef 来设置条件编译,表示如果没有相关的宏定义,则执行相关语句。下面例子中,如果定义了 WINDOW,则使用第二种参数说明。如果没有定义 WINDOW,则使用第一种参数说明。
`ifndef     WINDOW
    parameter DATA_DW = 32 ;  
 `else
    parameter DATA_DW = 64 ;
 `endif

//使用 `include 可以在编译时将一个 Verilog 文件内嵌到另一个 Verilog 文件中,作用类似于 C 语言中的 #include 结构。该指令通常用于将全局或公用的头文件包含在设计文件里。文件路径既可以使用相对路径,也可以使用绝对路径。
`include         "../../param.v"
`include         "header.v"

//在 Verilog 模型中,时延有具体的单位时间表述,并用 `timescale 编译指令将时间单位与实际时间相关联。该指令用于定义时延、仿真的单位和精度,格式为:
`timescale      time_unit / time_precision

//time_unit 表示时间单位,time_precision 表示时间精度,它们均是由数字以及单位 s(秒),ms(毫秒),us(微妙),ns(纳秒),ps(皮秒)和 fs(飞秒)组成。时间精度可以和时间单位一样,但是时间精度大小不能超过时间单位大小,
`timescale 1ns/100ps    //时间单位为1ns,精度为100ps,合法
//`timescale 100ps/1ns  //不合法
module AndFunc(Z, A, B);
    output Z;
    input A, B ;
    assign #5.207 Z = A & B
endmodule
        
//如果一个设计中的多个模块都带有 `timescale 时,模拟器总是定位在所有模块的最小时延精度上,并且所有时延都相应地换算为最小时延精度,时延单位并不受影响。例如:
        `timescale 10ns/1ns      
module test;
    reg        A, B ;
    wire       OUTZ ;
 
    initial begin
        A     = 1;
        B     = 0;
        # 1.28    B = 1;
        # 3.1     A = 0;
    end
 
    AndFunc        u_and(OUTZ, A, B) ;
endmodule
//此例中,仿真 test 时,OrFunc 中的 #5.207 延时依然对应 52ns。`timescale 的时间精度设置是会影响仿真时间的。时间精度越小,仿真时占用内存越多,实际使用的仿真时间就越长。所以如果没有必要,应尽量将时间精度设置的大一些 
    
`default_nettype
 //`default_nettype wand 
  //该指令用于为隐式的线网变量指定为线网类型,即将没有被声明的连线定义为线网类型。该实例定义的缺省的线网为线与类型。因此,如果在此指令后面的任何模块中的连线没有说明,那么该线网被假定为线与类型。
    `default_nettype none
    
    
    //Z1 无定义就使用,系统默认Z1为wire型变量,有 Warning 无 Error
module test_and(
        input      A,
        input      B,
        output     Z);
    assign Z1 = A & B ;  
endmodule
    
    
    //Z1无定义就使用,由于编译指令的存在,系统会报Error,从而检查出书写错误
`default_nettype none
module test_and(
        input      A,
        input      B,
        output     Z);
    assign Z1 = A & B ;  
endmodule
    
//`resetall
//该编译器指令将所有的编译指令重新设置为缺省值。`resetall 可以使得缺省连线类型为线网类型。当 `resetall 加到模块最后时,可以将当前的 `timescale 取消防止进一步传递,只保证当前的 `timescale 在局部有效,避免 `timescale 的错误继承。
    
    
//`celldefine, `endcelldefine
//这两个程序指令用于将模块标记为单元模块,他们包含模块的定义。例如一些与、或、非门,一些 PLL 单元,PAD 模型,以及一些 Analog IP 等。
    `celldefine
module (
    input      clk,
    input      rst,
    output     clk_pll,
    output     flag);
        ……
endmodule
`endcelldefine
    
    
//`unconnected_drive, `nounconnected_drive在模块实例化中,出现在这两个编译指令间的任何未连接的输入端口,为正偏电路状态或者为反偏电路状态。
    `unconnected_drive pull1
. . .
 / *在这两个程序指令间的所有未连接的输入端口为正偏电路状态(连接到高电平) * /
`nounconnected_drive
    
    `unconnected_drive pull0
. . .
 / *在这两个程序指令间的所有未连接的输入端口为反偏电路状态(连接到低电平) * /
`nounconnected_drive 
 

连续赋值

//关键词:assign,全加器
//连续赋值语句是 Verilog 数据流建模的基本语句,用于对 wire 型变量进行赋值。:
assign     LHS_target = RHS_expression  ;
//LHS(left hand side) 指赋值操作的左侧,RHS(right hand side)指赋值操作的右侧。assign 为关键词,任何已经声明 wire 变量的连续赋值语句都是以 assign 开头,例如:
wire      Cout, A, B ;
assign    Cout  = A & B ;     //实现计算A与B的功能

//LHS_target 必须是一个标量或者线型向量,而不能是寄存器类型。RHS_expression 的类型没有要求,可以是标量或线型或存器向量,也可以是函数调用。只要 RHS_expression 表达式的操作数有事件发生(值的变化)时,RHS_expression 就会立刻重新计算,同时赋值给 LHS_target。
wire      A, B ;
wire      Cout = A & B ;

全加器

img

下面采用数据流描述方式,来设计一个 1bit 全加器。

设 Ai,Bi,Ci 分别为被加数、加数和相邻低位的进位数,So, Co 分别为本位和与向相邻高位的进位数。

So = Ai ⊕ Bi ⊕ Ci ;
Co = AiBi + Ci(Ai+Bi)
module full_adder1(
    input    Ai, Bi, Ci,
    output   So, Co);
 
    assign So = Ai ^ Bi ^ Ci ;
    assign Co = (Ai & Bi) | (Ci & (Ai | Bi));
endmodule
`timescale 1ns/1ns
 
module test ;
    reg Ai, Bi, Ci ;
    wire So, Co ;
 
    initial begin
        {Ai, Bi, Ci}      = 3'b0;
        forever begin
            #10 ;
            {Ai, Bi, Ci}      = {Ai, Bi, Ci} + 1'b1;
        end
    end
 
    full_adder1  u_adder(
        .Ai      (Ai),
        .Bi      (Bi),
        .Ci      (Ci),
        .So      (So),
        .Co      (Co));
 
    initial begin
        forever begin//forever begin是表示永久循环,$finish能退出循环
            #100;
            //$display("---gyc---%d", $time);
            if ($time >= 1000) begin
            $finish ;
            end
        end
    end
 
 endmodule

时延

连续赋值延时语句中的延时,用于控制任意操作数发生变化到语句左端赋予新值之间的时间延时。

时延一般是不可综合的。

寄存器的时延也是可以控制的,这部分在时序控制里加以说明。

连续赋值时延一般可分为普通赋值时延、隐式时延、声明时延。

下面 3 个例子实现的功能是等效的,分别对应 3 种不同连续赋值时延的写法。

//普通时延,A&B计算结果延时10个时间单位赋值给z
wire Z, A, B ;
assign #10    Z = A & B ;
//隐式时延,声明一个wire型变量时对其进行包含一定时延的连续赋值。
wire A, B;
wire #10        Z = A & B;
//声明时延,声明一个wire型变量是指定一个时延。因此对该变量所有的连续赋值都会被推迟到指定的时间。除非门级建模中,一般不推荐使用此类方法建模。
wire A, B;
wire #10 Z ;
assign           Z =A & B
    //在上述例子中,A 或 B 任意一个变量发生变化,那么在 Z 得到新的值之前,会有 10 个时间单位的时延。如果在这 10 个时间单位内,即在 Z 获取新的值之前,A 或 B 任意一个值又发生了变化,那么计算 Z 的新值时会取 A 或 B 当前的新值。所以称之为惯性时延,即信号脉冲宽度小于时延时,对输出没有影响。
    


timescale 1ns / 1ns
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2023/10/18 15:01:05
// Design Name: 
// Module Name: test_time_delay_module
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////

module test_time_delay_module
    reg ai, bi;          // 声明寄存器类型变量 ai 和 bi
    wire so_lose, so_get, so_normal;  // 声明 wire 类型变量 so_lose, so_get 和 so_normal

    initial begin
        ai = 0;          // 初始化 ai 为 0
        #25; ai = 1;     // 延迟 25ns 后将 ai 设置为 1
        #35; ai = 0;     // 延迟额外 35ns 后将 ai 设置为 0(总共 60ns)
        #40; ai = 1;     // 延迟额外 40ns 后将 ai 设置为 1(总共 100ns)
        #10 ai = 0;      // 延迟额外 10ns 后将 ai 设置为 0(总共 110ns)
    end

    initial begin
        bi = 1;          // 初始化 bi 为 1
        #70; bi = 0;     // 延迟 70ns 后将 bi 设置为 0
        #20; bi = 1;     // 延迟额外 20ns 后将 bi 设置为 1
    end

    time_delay_module u_wire_delay(    // 实例化名为 u_wire_delay 的 time_delay_module 模块
        .ai(ai),         // 连接 ai 到 u_wire_delay 模块的 ai 输入端口
        .bi(bi),         // 连接 bi 到 u_wire_delay 模块的 bi 输入端口
        .so_lose(so_lose),         // 连接 so_lose 到 u_wire_delay 模块的 so_lose 输出端口
        .so_get(so_get),           // 连接 so_get 到 u_wire_delay 模块的 so_get 输出端口
        .so_normal(so_normal)      // 连接 so_normal 到 u_wire_delay 模块的 so_normal 输出端口
    );
    
    initial begin
        forever begin
            #100;
            if ($time >= 1000) begin
              $finish;     // 在模拟时间达到 1000ns 时结束仿真
            end
        end
    end
endmodule

过程控制语句

关键词:initial, always

过程结构语句有 2 种,initial 与 always 语句。它们是行为级建模的 2 种基本语句。

一个模块中可以包含多个 initial 和 always 语句,但 2 种语句不能嵌套使用。

这些语句在模块间并行执行,与其在模块的前后顺序没有关系。

但是 initial 语句或 always 语句内部可以理解为是顺序执行的(非阻塞赋值除外)。

每个 initial 语句或 always 语句都会产生一个独立的控制流,执行时间都是从 0 时刻开始。

initial语句

initial 语句从 0 时刻开始执行,只执行一次,多个 initial 块之间是相互独立的。

如果 initial 块内包含多个语句,需要使用关键字 begin 和 end 组成一个块语句。

如果 initial 块内只要一条语句,关键字 begin 和 end 可使用也可不使用。

initial 理论上来讲是不可综合的,多用于初始化、信号检测等。

always语句

与 initial 语句相反,always 语句是重复执行的。always 语句块从 0 时刻开始执行其中的行为语句;当执行完最后一条语句后,便再次执行语句块中的第一条语句,如此循环反复。

由于循环执行的特点,always 语句多用于仿真时钟的产生,信号行为的检测等。

posted @ 2023-10-18 19:49  lycheezhang  阅读(62)  评论(0)    收藏  举报