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 ;
全加器
下面采用数据流描述方式,来设计一个 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 语句多用于仿真时钟的产生,信号行为的检测等。