第二章 Verilog HDL基础 Verilog HDL历史与现状 Verilog HDL与VHDL 系统建模概述 语言要素 表达式、操作数、操作符 门级建模 数据流建模 行为建模 Verilog HDL源代码设计 Testbench验证
Verilog HDL历史 1983年,Gateway Design Automation公司推出Verilog语言,开发了仿真与验证工具; 1985年,GDA推出Verilog仿真器Verilog-XL:仿真速度快,处理能力强,具有交互式调试手段; 1987年,Synopsys公司的综合软件开始接受Verilog输入; 1989年,Cadence公司收购GDA,进一步扩大Verilog的影响; 1990年,Open Verilog International(OVI)成立,推广Verilog HDL和Veriog-XL被广泛推广; 1993年,OVI推出Verilog2.0,作为IEEE提案提出申请; 1995年,IEEE(Institute of Electrical and Electronics Engineers)通过Verilog HDL标准IEEE Std.1364-1995; 2001年,IEEE 发布了Verilog IEEE 1364-2001标准。 本课程以IEEE Std.1364-1995为主
Verilog HDL现状 Verilog HDL是最广泛使用的、具有国际标准支持的硬件描述语言,绝大多数的EDA厂商都支持; 在工业界和ASIC设计领域,Verilog HDL应用更加广泛。
Verilog HDL与VHDL VHDL 诞生于1982年;1987年底被IEEE和美国国防部确认为标准硬件描述语言 。 Very-High-Speed Integrated Circuit Hardware Description Language 诞生于1982年;1987年底被IEEE和美国国防部确认为标准硬件描述语言 。 IEEE 1076(1983) IEEE 1076-1995 ……
Verilog HDL与VHDL 建模层次 系统级(system): 用高级语言结构实现设计模块的外部性能的模型。 算法级(algorithmic): 用高级语言结构实现设计算法的模型。 RTL级(Register Transfer Level): 描述数据在寄存器之间流动和如何处理这些数据的模型。 门级(gate-level): 描述逻辑门以及逻辑门之间的连接的模型。 开关级(switch-level): 描述器件中三极管和储存节点以及它们之间连接的模型。
Verilog HDL与VHDL 相同点: 不同点: 都能形式化抽象表示电路行为和结构; 支持逻辑设计中层次与范围的描述; 具有电路仿真和验证机制; 与工艺无关。不专门面向FPGA设计 不同点: Verilog与C语言相似,语法灵活;VHDL源于Ada语言,语法严格; Verilog更适合ASIC设计。
SystemVerilog与SystemC SystemVerilog:IEEE 1364 Verilog-2001 标准的扩展增强,兼容Verilog 2001,将硬件描述语言(HDL)与现代的高层级验证语言(HVL)结合。 SystemC:一种软/硬件协同设计语言 ,既是系统级语言,也是硬件描述语言。
系统建模 设计方法学 描述方式 自顶向下 自底向上 混合式 数据流描述:描述电路数据流行为:assign 行为描述:描述功能:initial,always 结构化描述:描述元器件间连接关系:例化 混合描述:Verilog允许多描述方式共存于同一模块。
简单的Verilog程序 module trist1(out,in,enable); output out; /*输出信号*/ input in, enable; //输入信号 mytri tri_inst(out,in,enable); endmodule module mytri(out,in,enable); output out; input in, enable; assign out = enable? in : 'bz; 三态门 模块trist1 调用模块 mytri 的实例元件tri_inst; 通过这种结构性模块构造可构成特大型模块。
简单的Verilog程序 三态门(综合)
简单的Verilog程序 Verilog HDL程序是由模块构成的; 每个模块要进行端口定义,并说明输入输出口,然后对模块的功能进行逻辑描述; Verilog HDL程序的书写格式自由,一行可以写几个语句,一个语句也可以分写多行; 除了endmodule语句外,每个语句和数据定义的最后必须有分号。
同一电路的多种描述方法 RTL级行为描述 module muxtwo(out,a,b,sl); input a, b, sl; output out; reg out; always @(sl or a or b) if(!sl) out=a; else out=b; endmodule 二选一的选择器 门级(结构化)描述 module muxtwo(out,a,b,sl); input a, b, sl; output out; wire nsl, sela,selb; not #1 u1(nsl,sl); // #1是仿真延迟 and #1 u2(sela,a,nsl); and #1 u3(selb,b,sl); or #1 u4(out, sela, selb); endmodule 布尔代数级行为描述 module muxtwo(out,a,b,sl); input a, b, sl; output out; assign out = sel?b:a; endmodule
同一电路的多种描述方法 二选一的选择器(综合)
混合描述 混合设计方式的1位全加器实例 module FA_Mix (A, B, Cin, Sum, Cout); input A, B, Cin; output Sum, Cout; reg Cout; reg T1, T2, T3; wire S1; xor X1(S1, A, B); // 门实例语句。 always @ ( A or B or Cin ) // always 语句。 begin T1 = A & Cin; T2 = B & Cin; T3 = A & B; Cout = (T1 | T2) | T3; end assign Sum = S1 ^ Cin; // 连续赋值语句。 endmodule
混合描述 混合设计方式的1位全加器实例(综合)
模块基本结构 module 模块名(端口列表);端口I/O说明 内部信号声明 功能定义 endmodule
时延 Verilog HDL模型中的所有时延都根据时间单位定义。下面是带时延的连续赋值语句实例。 assign #2 Sum = A ^ B; # 2指2个时间单位。使用编译指令将时间单位与物理时间相关联。这样的编译器指令需在模块描述前定义,如下所示: ` timescale 1ns /100ps 此语句说明时,延时间单位为1ns并且时间精度为100ps (时间精度是指所有的时延必须被限定在0.1ns内)。如果此编译器指令所在的模块包含上面的连续赋值语句, #2 代表2ns。 如果没有这样的编译器指令, Verilog HDL 模拟器会指定一个缺省时间单位。IEEE Verilog HDL 标准中没有规定缺省时间单位。
语言要素:标识符 所谓标识别符就是用户为程序描述中的Verilog 对象所起的名字。 模块名、变量名、常量名、函数名、任务名 标识符必须以英语字母(a-z, A-Z)起头,或者用下横线符( _ )起头。其中可以包含数字、$符和下划线符。 标识符最长可以达到1023个字符。 模块名、端口名和实例名都是标识符。 Verilog语言大小写敏感, sel 和 SEL 是两个不同的标识符。 所有的关键词都是小写的。
语言要素:系统任务和函数 以$字符开始的标识符表示系统任务或系统函数。 任务可以返回0个或多个值,函数除只能返回一个值以外与任务相同。 函数在0时刻执行,即不允许延迟,而任务可以带有延迟。 常用于测试模拟,一般不用于源代码设计。 $display ("Hi, you have reached LT today"); /*$display系统任务在新的一行中显示。* / $time //该系统任务返回当前的模拟时间。
语言要素:编译指令 以`(反引号)开始的某些标识符是编译器指令。 `define 和`undef,很像C语言中的宏定义指令 `ifdef、`else 和`endif,用于条件编译 ` include 文件既可以用相对路径名定义,也可以绝对路径 ` timescale 编译器指令将时间单位与实际时间相关联。该指令用于定义时延的单位和时延精度。
语言要素:值集合 Verilog HDL有下列四种基本的值: Verilog HDL中有三类常量: 0:逻辑0或“假” 1:逻辑1或“真” x:未知 z:高阻 (x,z不区分大小写) Verilog HDL中有三类常量: 整型 实数型 字符串型
语言要素:常量 1. 整数 表达方式: 进制 x和z值 <位宽>’<进制><数字>:标准方式 ’<进制><数字>:默认位宽,与机器类型有关 <数字>:不指明进制默认为十进制 进制 二进制(b或B):8’b10101100, ’b1010 十进制(d或D):4’d1543, 512 十六进制(h或H): 8’ha2 八进制(o或O):6’O41 x和z值 x:不确定:4’b100x z:高阻:16’hzzzz,没有驱动元件连接到线网,线网的缺省值为z。
语言要素:常量 负数: 下划线: 数位扩展:(定义的长度比为常量指定的长度长) 数位截断: 在位宽表达式前加一个减号,如 -8’d5 减号不可以放在位宽和进制之间,也不可以放在进制和具体的数之间,如8’d-5 下划线: 只能用在具体的数字之间,如16’b1010_1111_1010 位数指的是二进制位数。 数位扩展:(定义的长度比为常量指定的长度长) 最高位是0、1,高位用0扩展:8’b1111 等于 8’b00001111 最高位是z、x,高位自动扩展:4’bz 等于 4’bzzzz 数位截断: 如果长度定义得更小,最左边的位被截断,如: 3 ‘ b1001_0011 等于 3’b011,5'H0FFF等于5'H1F
语言要素:常量 2. 实数 2.0 5.68 实数通常不用于FPGA源代码的常量 十进制计数法;例如 科学计数法; 23_5.1e2 其值为23510.0,忽略下划线 3.6E2 其值为360.0 ( e与E相同) 实数通常不用于FPGA源代码的常量
语言要素:常量 3. 字符串 字符串较少用于FPGA源代码的常量 字符串是双引号内的字符序列。字符串不能分成多行书写。例如: "INTERNAL ERROR" " REACHED->HERE “ 用8位ASCII值表示的字符可看作是无符号整数。 为存储字符串“INTERNAL ERROR”,变量需要8 *14位。 reg [1 : 8*14] Message; (Message = “INTERNAL ERROR“) 字符串较少用于FPGA源代码的常量
语言要素:数据类型 wire [msb:lsb] reg1, reg2, ... regN; 两大类数据类型 线网型 寄存器型 包含下述不同种类的线网子类型 wire //FPGA设计中,通常只用wire型 tri wor trior wand triand trireg tri1 tri0 supply0 supply1 msb和lsb 定义了范围,并且均为常数值表达式。范围定义是可选的;如果没有定义范围,缺省值为1位线网 wire Reset; wire [3:0] data_in; wire [3:2] select; wire [0:2] point;
语言要素:数据类型 寄存器型 5种不同的寄存器类型。 reg //FPGA设计中,通常只用reg型,默认初始值x。 reg [msb:lsb] reg1, reg2, ... regN; 寄存器型 5种不同的寄存器类型。 reg //FPGA设计中,通常只用reg型,默认初始值x。 integer //其他类型用于仿真 time real realtime msb和lsb 定义了范围,并且均为常数值表达式。范围定义是可选的;如果没有定义范围,缺省值为1位寄存器 reg Reset; reg [3:0] data_in; reg [3:2] select; reg [0:2] point;
语言要素:数据类型 存储器 存储器是一个寄存器数组。存储器使用如下方式说明 存储器赋值不能在一条赋值语句中完成,寄存器可以。 reg [msb: lsb] memory1 [upper1: lower1] , memory2 [upper2: lower2] ,... ; reg [3:0] MyMem [63 : 0] // MyMem为64个4位寄存器的数组。 reg Bog [1 : 5] // Bog为5个1位寄存器的数组。 存储器赋值不能在一条赋值语句中完成,寄存器可以。 存储器常用于FPGA外围器件的仿真建模
语言要素:数据类型 存储器赋值 2)系统任务赋值 reg [3:0] RomB [7:0] ; $readmemb ("ram.patt", RomB); Romb是存储器。文件“ram.patt”必须包含二进制值。文件也可以包含空白空间和注释。下面是文件中可能内容的实例。 1 1 0 1 1 1 1 0 1 0 0 0 0 1 1 1 0 0 0 0 1 0 0 1 0 0 1 1 0 0 0 1
语言要素:数据类型 存储器赋值 1)对每个单元逐一赋值 reg [0:3] Xrom [0:2]; ... Xrom[0] = 4'hA; Xrom[2] = 4‘hF;
语言要素:参数 参数是一个常量,常用于定义时延和变量的宽度。 parameter LINELENGTH = 132; parameter ALL_X_S = 16'bx; parameter BIT=1, BYTE = 8, PI = 3.14; parameter STROBE_DELAY = (BYTE + BIT) / 2; 参数值也可以在编译时被改变。改变参数值可以使用参数定义语句或通过在模块初始化语句中定义参数值
语言要素:空白符、注释 除了字符串中的空白符,其他空白符编译被忽略 注释 多行注释 /* */ (不允许嵌套) 单行注释 //
` 1 使用`timescale 编译器指令的目的是什么? 2 写出产生下图所示波形的变量BullsEye的初始化语句。 3 使用数据流描述方式编写下图所示的异或逻辑的Verilog HDL描述,并使用规定的时延。 4下列表达式的位模式是什么? 7'o44, 'Bx0, 5'bx110, 'hA0, 10'd2, 'hzF
表达式 表达式由操作数和操作符组成; 表达式可以在出现数值的任何地方使用; 表达式是数据流描述的基础。 A & B Addr1[3:0] +Addr2[3:0] Count + 1 (a[0] ^ b[0] ) | (a[1] & ~b[1])
表达式:操作数 操作数可以是以下类型中的一种: 常数 参数 线网 寄存器 位选择 部分选择 存储器单元 函数调用
表达式:操作数 常数 表达式中的整数值可被解释为有符号数或无符号数; 如果整数是基数型整数,作为无符号数对待。 12 01100的5位向量形式 (有符号) -12 10100的5位向量形式 (有符号) 5‘b01100 十进制数12 (无符号) 参数 参数类似于常量,并且使用参数声明进行说明。例如 parameter LOAD = 4'd12, STORE = 4'd10; LOAD和STORE为参数,值分别被声明为12和10。
表达式:操作数 线网 线网中的值被解释为无符号数, 表达式中可使用:标量线网(1位)和向量线网(多位)。 wire [3:0] led; //4位向量线网。 wire line; //标量线网。 assign led = 4‘ha; //被赋于位向量1010,为十进制10。
表达式:操作数 寄存器 integer型的值被解释为有符号的二进制补码数, reg型或time型的值被解释为无符号数, real型和realtime的值被解释为有符号浮点数。 reg [4:0] state; State = 5‘b01011; // 值为位向量01011,十进制值11。 State = 9; // 值为位向量01001,十进制值9。 为何没有用assign语句赋值?
表达式:操作数 位选择 位选择从向量中抽取特定的位。形式如下: net_or_reg_vector[bit_select_expr] State[1] && State[4] //寄存器位选择。 led[0] | line //线网位选择。 如果选择表达式的值为x、z或越界,则位选择的值为 State[x]值为x。(FPGA设计中禁用)
表达式:操作数 部分选择 net_or_reg_vector[msb_const_expr:lsb_const_expr] State [4:1] //寄存器部分选择。 reg [4:0] state; led [2:0] //线网部分选择。 wire [3:0] led; 选择范围越界或为x、z时,部分选择的值为x。 (FPGA设计中禁用越界)
表达式:操作数 表达式中可使用函数调用。 存储器单元 存储器单元从存储器中选择一个memory[word_address] reg [7 : 0] Dram [63 : 0]; Dram [60]; //存储器的第61个单元。 不允许对存储器变量值部分选择或位选择。 (思考:在存储器中读取一个位或部分选择一个字?) 函数调用 表达式中可使用函数调用。 $time + SumOfEvents (A, B) /* $time是系统函数,并且SumOfEvents是在别处定义的用户自定义函数。*/
表达式:操作符 Verilog HDL中的操作符可以分为下述类型: 算术操作符 关系操作符 相等操作符 逻辑操作符 按位操作符 归约操作符 移位操作符 条件操作符 连接和复制操作符
表达式:操作符 操作符从最高优先级到最低优先级排列。同一行中的操作符优先级相同。
表达式:操作符 除条件操作符从右向左关联外,其余所有操作符自左向右关联。 圆扩号能够用于改变优先级 A + B - C 等价于:(A + B ) - C //自左向右 A?B:C?D:F 等价于:A?B:(C?D:F) //从右向左 圆扩号能够用于改变优先级 (A?B:C)?D:F
表达式:操作符 算术运算符 + (加) -(减) * (乘) / (除) %(取模) 1) 后三种不常用,是具体设计结构而定; + (加) -(减) * (乘) / (除) %(取模) 1) 后三种不常用,是具体设计结构而定; 2) 任意操作数是X或Z,那么整个结果为X; 3) 结果的长度由最长的操作数决定; 4) reg和wire保存无符号数。
表达式:操作符 关系操作符有: 习题:求下列表达式真值 >(大于) <(小于) >=(不小于) <=(不大于) 1) 关系操作符的结果为真(1)或假(0); 2) 如果操作数中有一位为X或Z,那么结果为X。 习题:求下列表达式真值 23 > 45 52 < 8'hxFF 'b1000 > = 'b01110
表达式:操作符 等式运算符(相等关系运算符) = =(逻辑相等) ! = (逻辑不等) 习题:求下列表达式真值 = = =(全等) ! = =(非全等) 1) 如果比较结果为假则结果为0,为真结果为1; 2) 在全等比较中,值x和z严格按位比较。 习题:求下列表达式真值 假定 Data = ‘b11x0; Addr = ‘b11x0; 求 Data = = Addr Data = = = Addr === 1 x z ==
表达式:操作符 逻辑操作符有: 习题:求下列表达式真值 && (逻辑与) || (逻辑或) ! (逻辑非) 假定: C = 'b0; //0为假 D = ‘b1; //1为真 A_Bus = 'b0110; B_Bus = 'b0110; 求 C && D C || D ! D A_Bus && B_Bus A_Bus || B_Bus !A_Bus 逻辑操作符有: && (逻辑与) || (逻辑或) ! (逻辑非) 1) 只对逻辑值运算,结果一位,逻辑值1、0或x; 2) 对于向量操作, 非0向量作为1处理; 3) 如果任意一个操作数包含x,结果也为x。
表达式:操作符 按位操作符有: • ~(一元非) • &(二元与) • |(二元或) • ^(二元异或) • ~^或^~(二元异或非) 操作数对应位上按位操作,并产生向量结果。 习题:求下列表达式真值 假定 A = 'b0110; B = 'b0100; 求 A | B A & B
表达式:操作符 归约操作符有: 习题:求下列表达式真值 & (归约与) ~& (归约与非) | (归约或) ~| (归约或非) 假定: A = ‘b0110; B = ‘b0100; MyReg = 4‘b01x0; 求 ~&A ^A |B &B |MyReg ^MyReg 归约操作符有: & (归约与) ~& (归约与非) | (归约或) ~| (归约或非) ^ (归约异或) ~^ (归约异或非) 在单一操作数的所有位上操作,并产生1位结果。
表达式:操作符 移位操作符有: 习题:求Qreg的值 •<< (左移) >> (右移) 假定: reg [7: 0] Qreg; Qreg = 4'b0111; 移位 Qreg >> 2 移位操作符有: •<< (左移) >> (右移) 1)左侧操作数移动右侧操作数表示的次数,逻辑移位, 空闲位添0补位; 2)如果右侧操作数的值为x或z, 移位操作的结果为x。 应用实例:使用移位操作为2 - 4解码器建模 wire [3:0] DecodeOut; assign DecodeOut = 4'b1 << Address [1:0];
表达式:操作符 条件操作符 根据条件表达式的值选择表达式,形式: cond_expr ? expr1 : expr2 三目运算符 wire [2 : 0] Student; assign Student = Marks > 18 ? Grade_A : Grade_C;
表达式:操作符 位拼接运算符(连接和复制操作) 连接操作:将小表达式合并形成大表达式的操作。形式: {expr1, expr2, . . .,exprN} 复制操作:指定重复次数来执行操作。如下: {repetition_number {expr1, expr2, ...,exprN} 实例: wire [7:0] Dbus, [11:0] Abus; assign Dbus [7:4] = { Dbus[0], Dbus[1], Dbus[2], Dbus[3] }; assign Abus = {3{4'b1011}}; / /位向量12'b1011_1011_1011) assign Abus = {{4{Dbus[7]}}, Dbus}; /*符号扩展*/
习题2 1.说明参数GATE_DELAY, 参数值为5。 2.假定长度为64个字的存储器, 每个字8位,编写Verilog 代码,按逆序交换存储器的内容。即将第0个字与第63个字交换,第1个字与第62个字交换,依此类推。 3. 假定32位总线Address_Bus, 编写一个表达式,计算从第11位到第2 0位的归约与非。 4. 假定一条总线Control_Bus [15 : 0],编写赋值语句将总线分为两条总线: Abus [0 : 9] 和Bbus[6 : 1]。 5. 编写一个表达式,执行算术移位,将Qparity 中包含的8位有符号数算术移位。 6. 使用条件操作符, 编写赋值语句选择NextState的值。如果Current State的值为RESET, 那么NextState的值为GO;如果CurrentState的值为GO,则NextState的值为BUSY;如果CurrentState的值为BUSY ;则NextState的值为RESET 。 7.如何从标量变量A,B,C和D中产生总线BusQ[0:3]? 如何从两条总线B usA [0 : 3]和BusY[20 : 15]形成新的总线BusR[1 0 : 1] ?
模块与端口 模块:基本单元定义成模块形式 module module_name (port _list) ; Declarations_and_Statements endmodule 端口队列port_list列出了该模块通过哪些端口与外部模块通信。
模块与端口 端口 模块的端口可以是 input(输入端口)、output (输出端口) 或者inout (双向端口); 缺省的端口类型为wire型; output或inout能够被重新声明为reg型,但是input不可以; 线网或寄存器必须与端口说明中指定的长度相同。
模块与端口 例: module Micro (PC, Instr, NextAddr); //端口说明 . . . endmodule input [3:1] PC; output [1:8] Instr; inout [16:1] NextAddr; //重新说明端口类型: wire [16:1] NextAddr; //该说明是可选的,但如果指定了,就必须与它的端口说明保持相同长度。 reg [1:8] Instr; //Instr已被重新说明为reg型,因此能在always语句或在initial语句中赋值。 . . . endmodule
模块与端口 模块实语句 port_expr可以是以下的任何类型: 一个模块能够在另外一个模块中被引用,这样就建立了描述的层次。模块实例语句形式 module_name instance_name (port _associations) ; 信号端口可以通过位置或名称关联;但是关联方式不能够混合使用。端口官廉形式 port_expr //通过位置,隐式关联 .PortName (port_expr) //通过名称,显示关联,强烈推荐! port_expr可以是以下的任何类型: 1) 标识符(reg型或wire型) 2) 位选择 3) 部分选择 4) 上述类型的合并 5) 表达式(只适用于input型信号) Micro M1 ( UdIn[3:0], {WrN, RdN}, Status[0], Status[1] , &UdOut[0:7], TxData) ;
模块与端口 使用两个半加器模块构造全加器 module HA (A , B , S , C); input A , B; output S, C; assign S = A ^ B; assign C = A & B; endmodule module FA (P, Q, Cin, Sum, Cout) ; input P, Q, Cin; output Sum, Cout; wire S1, C1, C2; HA h1 (P, Q, S1, C1); //通过位置关联。 HA h2 (.A(Cin), .S(Sum), .B(S1), .C(C2)); //通过端口与信号的名字关联。 or O1 (Cout, C1, C2) ; //或门实例语句 考虑如何模块参数化?
模块与端口 使用两个半加器模块构造全加器(模块参数化) module HA (A , B , S , C); input A , B; output S, C; parameter AND_DELAY = 1, XOR_DELAY = 2; assign #XOR_DELAY S = A ^ B; assign #AND_DELAY C = A & B; endmodule module FA (P, Q, Cin, Sum, Cout) ; input P, Q, Cin; output Sum, Cout; parameter OR_DELAY = 1; wire S1, C1, C2; HA h1 (P, Q, S1, C1); //通过位置关联。 HA h2 (.A(Cin), .S(Sum), .B(S1), .C(C2)); //通过端口与信号的名字关联。 or #OR_DELAY O1 (Cout, C1, C2) ; //或门实例语句
模块与端口 悬空端口 端口长度不同 通过将端口表达式表示为空白来指定为悬空端口 DFF d1(.Q(QS), .Qbar(), .Data(D),.Preset(), .Clock(CK)); 端口长度不同 通过无符号数的右对齐或截断方式进行匹配 module Child(Pba, Ppy) ; input [5:0] Pba; output [2:0] Ppy; . . . endmodule module Top; wire [1:2] Bdl; wire [2:6] Mpr; Child C1 (.Pba(Bdl), .Ppy(Mpr));
模块与端口 模块参数值改变 1)参数定义语句(defparam) module TOP (NewA , NewB , NewS , NewC) ; input New A , New B; output New S , New C; defparam Ha1. XOR_DELAY = 5, //实例Ha1中的参数XOR_DELAY。 Ha1. AND_DELAY = 2; //实例Ha1中参数的AND_DELAY。 HA Ha1 (NewA, NewB, NewS, NewC) ; endmodule
模块与端口 模块参数值改变 2) 带参数值的模块引用 module TOP (NewA , NewB , NewS , NewC) ; input New A , New B; output New S , New C; HA #(5,2) Ha1(NewA , NewB , NewS , NewC) ; //第1个值5赋给参数AND_DELAY,该参数在模块HA中说明。 //第2个值2赋给参数XOR_DELAY,该参数在模块HA中说明。 endmodule
模块与端口 外部端口 显式地指定外部端口。(较少使用) module Scram_B ( .Data(Arb), .Control(Ctrl),.Mem_Word(Mem_Blk), .Addr(Byte) ) ; input [0:3] Arb; input Ctrl; input [8 : 0] Mem_Blk; output [0:3] Byte; . . . endmodule
习题3 1 .模块实例语句与门实例语句的区别是什么? 2 .当端口悬空时,即端口没有被连接时,端口的值是什么? 3 .用本章讲述的模块FA编写执行加法和减法的4位ALU的结构模型。
门级建模 FPGA设计中较少使用 Verilog HDL中提供下列内置基本门: 1) 多输入门: and, nand, or, nor, xor, xnor 2) 多输出门: buf, not 3) 三态门: bufif0, bufif1, notif0, notif1 4) 上拉、下拉电阻: pullup, pulldown 5) MOS开关: cmos, nmos, pmos, rcmos, rnmos, rpmos 6) 双向开关: tran, tranif0, tranif1, rtran, rtranif0, rtranif1
用于定义原语(UDP) FPGA设计中通常不使用; UDP实例语句的语法与基本门的实例语句语法一致; UDP中可以描述组合电路和时序电路。 Primitive D_Edge_FF (Q, Clk, Data) ; output Q; reg Q ; input Data, Clk; initialQ = 0; table // Clk Data Q (State) Q(next ) (01) 0 : ? : 0 ; (01) 1 : ? : 1 ; (0x) 1 : 1 : 1 ; (0x) 0 : 0 : 0 ; // 忽略时钟负边沿: (?0) ? : ? : - ; // 忽略在稳定时钟上的数据变化 (??) ? : ? : - ; endtable endprimitive FPGA设计中通常不使用; UDP实例语句的语法与基本门的实例语句语法一致; UDP中可以描述组合电路和时序电路。
数据流建模 连续赋值用于数据流建模(描述),生成组合逻辑电路。 连续赋值使用连续赋值语句 assign语句,格式为: 例如 assign LHS_target = RHS_expression; 例如 wire Z1, Preset, Clear; //线网说明 assign Z1 = Preset & Clear; //连续赋值语句 wire [15:0] data_in; wire [15:0] data_tmp; wire data_tmp = {data_in[7:0], data_in[15:8]};
数据流建模 连续赋值语句在什么时候执行呢? 只要在右端表达式的操作数上有事件发生(值变化),表达式立即被计算,新结果就赋给左边的线网。 连续赋值的目标类型(左侧操作数类型) 1) 标量线网 assign Z1 = … ; 2) 向量线网 assign data_tmp = … ; 3) 向量的常数型位选择 assign data_tmp[2] = … ; 4) 向量的常数型部分选择 assign data_tmp[7:0] = … ; 5) 上述类型的任意的拼接运算结果 assign { Z1, data_tmp[15]} = 2’b10;
数据流建模 例:数据流描述的一位全加器 module FA_Df (A, B, Cin, Sum, Cout) ; input A, B, Cin; output Sum, Cout ; assign Sum = A^B ^Cin; assign Cout = (A & Cin) | (B & Cin) | (A & B) ; endmodule 1)assign语句之间是并发的,与其书写的顺序无关; 2)线网的赋值可以在声明时赋值,例如 wire Sum = A^B ^Cin;
数据流建模 数据流建模的时延 assign #2 Sum = A ^ B ^ Cin; #2表示右侧表达式的值延迟两个时间单位赋给Sum; 时间单位是多少?由谁来决定? `timescale 1ns/100ps FPGA设计中的时延仅在功能仿真时有效,不影响实际电路生成。
数据流建模 数据流建模注意事项: 1)wire型变量如果不赋值,默认值为z; 2)数据流建模没有存储功能,不能保存数据; 3)wire型变量只能在声明时赋值或者assing语句赋值; 4)assign语句并发执行,实际的延迟又物理芯片的布线结果决定。 5)最基本的FPGA设计源代码描述语句之一,用于生成组合逻辑,定制LUT的逻辑功能。常作为中间信号的描述用于控制寄存器的输入输出。
习题4 1. 使用assign语句描述一个时钟信号clk,频率为100MHz。 2. 请指出下列语句是否合法?描述了怎样的功能? assign #5 clk = ~clk; 2. 请指出下列语句是否合法?描述了怎样的功能? assign Mux = (S = = 0)? A : 'bz; assign Mux = (S = = 1)? B : 'bz; assign Mux = (S = = 2)? C : 'bz; assign Mux = (S = = 3)? D : 'bz;
行为建模 过程赋值用于行为建模(描述) 行为建模的主要机制: 1) initial 语句 主要用于仿真文件(模拟) 2) always 语句 用于源文件和仿真文件 所有initial语句和always语句之间都是并发执行; 执行顺序与其在模块中书写顺序无关。
行为建模:initial语句 initial 语句只执行一次; 在模拟开始时执行,即在0时刻开始执行; 不能嵌套使用。 initial [timing_control] procedural_statement procedural_statement可以是: procedural_continuous_assignment 过程赋值(阻塞或者非阻塞) conditional_statement -> if case_statement -> case loop_statement -> for, forever, repeat, while wait_statement -> wait disable_statement -> disable(相当于C中的break) event_trigger -> @ (event) sequential_block -> begin ... end parallel_block -> fork ... join task_enable (user or system)
行为建模:initial语句 例: reg Curt; . . . initial #2 Curt = 1; parameter SIZE = 1024; reg [7:0] RAM [0 : SIZE-1] ; reg RibReg; Initial begin: SEQ_BLK_A //顺序过程的标记,如果没有局部声明,则不需要 integer Index; RibReg = 0; for (Index = 0; Index < SIZE; Index = Index + 1) RAM [Index] = 0; end
行为建模:initial语句 initial语句在仿真文件产生时钟和构造数据简单示例 parameter APPLY_DELAY = 5; reg [0 : 7] port_A; reg clk; . . . initial begin Port_A = 'h20 ; #APPLY_DELAY Port_A= 'hF2; #APPLY_DELAY Port_A= 'h41; #APPLY_DELAY Port_A= 'h0A; clk = 0; while(1) //或者 forever clk = #5 ~clk; //或者 #5 clk = ~clk; end
procedural_statement可以是: 行为建模:always语句 always语句重复执行,语法和initial语句相同: always [timing_control] procedural_statement procedural_statement可以是: procedural_continuous_assignment 过程赋值(阻塞或者非阻塞) conditional_statement -> if case_statement -> case loop_statement -> for , forever, repeat, while wait_statement -> wait disable_statement -> disable(相当于C中的break) event_trigger -> @ (event) sequential_block -> begin ... end parallel_block -> fork ... join task_enable (user or system)
行为建模:always语句 两种典型的always语句 1) 组合逻辑(电平触发) 说明: reg c; always @ ( a or b or sel ) c = sel ? a : b; 说明: (1)虽然c是reg型,但综合的 结果是组合电路; (2)等同于数据流描述 wire c; assign c = sel ? a : b; (3) FPGA设计中不建议使用; 此外,容易产生锁存器
行为建模:always语句 两种典型的always语句 说明: 2) 时序逻辑(时钟沿触发) (1) 在 always 语句中所有被赋值的信号必须是reg型; (2) 综合为触发器,推荐使用; (3) 异步时序逻辑 两种典型的always语句 2) 时序逻辑(时钟沿触发) reg [8:0]count; always @ (posedge clk or negedge reset) begin if (~reset) count = 0; else if (count == 511) count = count + 1; end
常见过程语句 时序控制语句 仅用于仿真测试 1)时序控制 reg Stream ; initial Begin Stream = 0; end
常见过程语句 时序控制语句 2)事件控制 边沿触发事件 reg [9:0]addr; integer i; 电平触发事件 initial begin for (i=0; i<5 ;i=i+1) @ (posedge clk) addr = addr +1; end 电平触发事件 initial begin wait (Sum > 22) Sum = 0; end
常见过程语句 顺序语句块 并行语句块 思考题: begin … end fork … join 源程序、测试文件 测试文件 块内语句顺序执行 块内语句并行执行 思考题: initial语句若使用 fork…join如何描 述右图时序?
常见过程语句 问题: (1)时序电路的行为具有并行特性:寄存器都受到时钟的控制,流水线… (2)既然fork … join不能在源文件中使用,在行为描述中如何描述并行语句? (3)begin … end中的语句是顺序执行,在同一时钟边沿触发下,每个寄存器变量为何赋值有先有后?这与实际电路是否矛盾?
b = ? 常见过程语句 过程赋值语句 定义:initial和always语句中的赋值语句 区别于数据流描述的连续赋值语句(assign) 分为阻塞过程赋值和非阻塞过程赋值两种 阻塞过程赋值 always @ (posedge clk or negedge rst) begin if (~rst) …//寄存器复位 else if (… ) a = 1‘b1; b = a; end 非阻塞过程赋值 always @ (posedge clk or negedge rst) begin if (~rst) …//寄存器复位 else if (… ) a <= 1‘b1; b <= a; end b = ?
常见过程语句 结论: 源代码设计推荐使用非阻塞过程赋值“ <= ” 阻塞过程赋值“ = ”多用于仿真测试文件 可以有效综合为寄存器逻辑电路 符合实际,时序分析简单 语句之间并行执行,不再有顺序关系 阻塞过程赋值“ = ”多用于仿真测试文件 适合构造仿真模型和仿真行为 不容易直接综合为FPGA资源
常见过程语句 initial initial begin begin end end Clr <= #5 1; Clr = #5 0;
常见过程语句 过程赋值与连续赋值的比较
常见过程语句 if语句 注: 与C语言类似 If (condition_1) procedural_statement_1 {else if(condition_2) procedural_statement_2} {else procedural_statement_3} 注: 1)条件语句必须在过程块语句中使用,不能单独使用; 2) if后面的表达式的值只有为1时才按“真”处理。
常见过程语句 if语句的嵌套 if(表达式1) if (表达式2) 语句1; else 语句2; else if (表达式3) 语句3; 注: 1) else总是与它最上面的最近的if配对; 2) 如果if与else的数目不一样,为了实现程序设计者的目 的,可以用begin…end语句确定配对关系; 3) 强烈建议保留else分支。
常见过程语句 例: always @ (posedge clk or negedge rst) begin if (~rst) ctrl <= #1 2’b00; flag <= #1 0; end else if(~flag) ctrl <= #1 2’b01; flag <= #1 1; else
常见过程语句 综合电路
常见过程语句 case语句 类似C语言的switch case语句 case (case_expr) case_item_expr{ ,case_item_expr} : procedural_statement . . . [default: procedural_statement] endcase
常见过程语句 reg e; always @ (posedge clk or negedge rst) begin if (~rst) else case ( {a, b} ) 2’b00: e <= #1 d; 2’b01: e <= #1 ~c; 2’b11: e <= #1 1’b0; 2’b11: e <= #1 1’b1; default: ; //空语句,强烈建议保留default分支表达式 endcase end
常见过程语句 综合电路
常见过程语句 casex语句和casez语句 语法与case非常相似 不建议使用 casez(ir) 8b’1???????: instruction1(ir); 8b’01??????: instruction2(ir); 8b’00010???: instruction3(ir); 8b’000001??: instruction4(ir); endcase
常见过程语句 循环语句 思考题: forever语句 repeat语句 while语句 for语句 (1)连续执行的循环; 用四种循环语句分别实现initial中的时钟产生: 1)在100ns出开始; 2)周期10ns。 循环语句 forever语句 repeat语句 while语句 for语句 (1)连续执行的循环; (2)只用于测试程序的initial块中; (3)综合工具很难综合成FPGA的逻辑电路。
习题5 1. 描述电路行为:该电路在每一个时钟下跳沿(负沿)检查输入数据,当输入数据Usg为1011时,输出Asm被置为1。 2. 描述电路行为:输入为12位的向量。如果其中1的数量超过0的数量,输出设置为1。当Data_Ready为1时,才对输入数据进行检查。 提示:输入信号均有clk和rst,采用时序逻辑设计(always语句)
Verilog HDL源代码设计 基于本章内容,可以设计FPGA可实现的Verilog源代码; 自顶向下设计; 采用数据流建模、行为建模、结构化建模三种方式; 开始你的第一个Verilog功能模块源代码设计!
Verilog HDL源代码设计 基本设计流程 1)根据需求,进行模块功能划分,自顶向下设计; 2)定义各个模块的接口信号(包括方向、类型、宽度); 3)定义全局时钟信号和全局复位信号; 4)编写顶层模块,例化子模块; 5)子模块功能设计,以时序逻辑设计为主; 与软件源代码设计最大的不同:时序的严格性!
Verilog HDL源代码设计 如何验证源代码设计的正确性? 功能仿真(前仿真) 进行语法检查,error和warning 设计testbench,根据激励输入验证逻辑功能 逻辑综合 …
Testbench验证 Testbench 1)Testbench和源代码都是.v文件 2) Testbench和源代码都是module 模拟实际环境的输入激励和输出校验的一种“虚拟平台” 以输入激励为主 ,输出校验可以通过波形观测 1)Testbench和源代码都是.v文件 2) Testbench和源代码都是module 3) Testbench不能综合成FPGA内部电路
Testbench验证 接口信号定义 被测试模块的输入激励设置为reg型; 被测试模块的输出设置为wire型; 双向端口inout在测试中需要进行特殊处理。 为什么信号方向与类型的对应关系与之前的要求不同? 源代码看作testbench子模块 源代码顶层的输入是testbench的输出 源代码顶层的输出是testbench的输入
Testbench验证 Testbench中inout信号的使用 inout[15:0] data; wire [15:0] data; 本质上是三态门 inout[15:0] data; wire [15:0] data; reg [15:0] data_out; reg data_enable; 方法1: assign data = data_enable ? data_out : 16‘hz; 方法2: IOBUF(.I(data_out), .O(), .T(data_enable), .IO(data));
Testbench验证 Testbench的结构 module testbench(); endmodule //信号类型定义(wire或者reg),注意testench没有输入输出。 … //例化顶层模块 //激励行为描述,通常都包含clk和rst的产生描述 initial … //可使用各种合法语句 always … assign … task … //类似于函数 endmodule
Testbench验证 Testbench自动生成模板 Xilinx ISE工具提供testbench的自动生成模板 在同一项目中的 Verilog Test Fixture 与哪个源代码文件关联就生成对应层次的Testbench
Testbench验证 空模板样例 需增加clk的产生 rst的使能描述 输入信号的行为描述
Testbench验证 值序列产生 initial begin end 产生值序列的最简单是使用initial语句。例如: Reset = 0; #100 Reset = 1; #80 Reset = 0; #30 Reset = 1; end
Testbench验证 值序列产生 重复序列可由always语句产生,例如: parameter REPEAT_DELAY = 35; integer CoinValue; always begin CoinValue = 0; #7 CoinValue = 25; #2 CoinValue = 5; #8 CoinValue = 10; #6 CoinValue = 5; # REPEAT_DELAY; end
Testbench验证 重复模式(例如时钟信号) wire Clock; assign # (PERIOD/2) Clock = ~ Clock; 初值多少? initial Clock = 0; Clock数据类型是否正确? 建议使用行为描述方式产生时钟
Testbench验证 重复模式(例如时钟信号) reg Clk_A ; //推荐使用的仿真时钟生成方法 parameter tPERIOD = 10; initial Clk_A = 0; always # (tPERIOD/2) Clk_A = ~ Clk_A;
Testbench验证 状态检测器验证实例 initial begin module Top; Data = 0; reg Data, Clock; integer Out_File; //待测试模块的应用实例 Count3_ls F1(Data, Clock, Detect) ; initial //产生时钟 begin Clock = 0; forever #5 Clock = ~ Clock; end initial begin Data = 0; #5 Data = 1; #40 Data = 0; #10 Data = 1; #20 $stop; // 模拟结束 end initial //文件保存监控信息 Out_File = $fopen ("results.vectors") ; $fmonitor (Out_File ,"Clock=%b, Data=%b, Detect=%b", Clock, Data, Detect); endmodule
Testbench验证 任务 类似过程,从描述的不同位置执行共同的代码段; 任务可以包含时序控制,也能调用其它任务和函数; 任务参数数量不限(可以是0),需要声明输入输出。 构造复杂的仿真行为,推荐使用! task task_id; [declarations] procedural_statement endtask
Testbench验证 一个任务实例 parameter MAXBITS = 8; task Reverse_Bits; input [MAXBITS - 1:0] Din; output [MAXBITS - 1:0] Dout; integer K; begin for (K = 0; K < MAXBITS; K = K + 1) Dout [MAXBITS - K] = Din[K] ; end endtask
Testbench验证 任务的调用 如同函数调用 在initial或者always语句中使用 任务中可以调用其他任务 注意:实际仿真时,常常构造带有时序逻辑的任务构造数据
Testbench验证 PCI控制器DMA模式写数据任务 while(~flag) begin if(~pci_ready_ow) @ (posedge clk66); #1 pci_ads_iw <= 1; if (flag) pci_data_i <= data0; end @(posedge clk66); #1 pci_data_i <= data1; #1 pci_data_i <= data2; #1 pci_data_i <= data3; #1 pci_data_i <= data4; pci_blast_iw <= 0; #1 pci_blast_iw <= 1; if(pci_ready_ow) pci_lhold_im <= 0; T_ENABLE <= 1; endtask PCI控制器DMA模式写数据任务 task pci_w; input start; input [31:0] data0; input [31:0] data1; input [31:0] data2; input [31:0] data3; input [31:0] data4; input [11:0] addr; reg flag; begin TENABLE <= 0; @ (posedge clk66); if (start) #1 pci_lhold_im <= 1; else #1 pci_lhold_im <= 0; #1 flag <= 0; if (pci_lholda_om & ~flag) #1 pci_ads_iw <= 0; pci_addr_i <= addr; pci_data_i <= 32'h14; end
Testbench验证 函数 与任务类似,但是只能返回一个值;仿真中涉猎不多。 Verilog有丰富的系统函数,与C语言系统函数类似,请查阅手册; 系统函数通常用于输出校验和存储器仿真。
习题6 1. 产生一个高电平持续时间和低电平持续时间分别为3 ns和10 ns的时钟。 2. 编写测时序检测器源代码以及测试验证程序。时序列检测器按模式10010在每个时钟正沿检查输入数据流。如果找到该模式,将输出置为1;否则输出置为0。