第10章 Verilog操作符 学习内容: 熟悉Verilog语言的操作符
操作符类型 下表以优先级顺序列出了Verilog操作符。注意“与”操作符的优先级总是比相同类型的“或”操作符高。本章将对每个操作符用一个例子作出解释。 操作符类型 符号 连接及复制操作符 一元操作符 算术操作符 逻辑移位操作符 关系操作符 相等操作符 按位操作符 逻辑操作符 条件操作符 {} {{}} ! ~ & | ^ * / % + - << >> > < >= <= = = = = = != != = & ^ ~^ | && || ?: 最高 优先级 最低
Verilog中的大小(size)与符号 当一个负数赋值给无符号变量如reg时,Verilog自动完成二进制补码计算 module sign_size; reg [3:0] a, b; reg [15:0] c; initial begin a = -1; // a是无符号数,因此其值为1111 b = 8; c= 8; // b = c = 1000 #10 b = b + a; // 结果10111截断, b = 0111 #10 c = c + a; // c = 10111 end endmodule
算术操作符 module arithops (); parameter five = 5; integer ans, int; reg [3: 0] rega, regb; reg [3: 0] num; initial begin rega = 3; regb = 4'b1010; int = -3; //int = 1111……1111_1101 end initial fork #10 ans = five * int; // ans = -15 #20 ans = (int + 5)/ 2; // ans = 1 #30 ans = five/ int; // ans = -1 #40 num = rega + regb; // num = 1101 #50 num = rega + 1; // num = 0100 #60 num = int; // num = 1101 #70 num = regb % rega; // num = 1 #80 $finish; join endmodule 注意integer和reg类型在算术运算时的差别。integer是有符号数,而reg是无符号数。 + 加 - 减 * 乘 / 除 % 模 将负数赋值给reg或其它无符号变量使用2的补码算术。 如果操作数的某一位是x或z,则结果为x 在整数除法中,余数舍弃 模运算中使用第一个操作数的符号
按位操作符 ~ not & and | or ^ xor ~ ^ xnor ^ ~ xnor module bitwise (); reg [3: 0] rega, regb, regc; reg [3: 0] num; initial begin rega = 4'b1001; regb = 4'b1010; regc = 4'b11x0; end initial fork #10 num = rega & 0; // num = 0000 #20 num = rega & regb; // num = 1000 #30 num = rega | regb; // num = 1011 #40 num = regb & regc; // num = 10x0 #50 num = regb | regc; // num = 1110 #60 $finish; join endmodule 按位操作符对矢量中相对应位运算。 regb = 4'b1 0 1 0 regc = 4'b1 x 1 0 num = regb & regc = 1 0 1 0 ; 位值为x时不一定产生x结果。如#50时的or计算。 当两个操作数位数不同时,位数少的操作数零扩展到相同位数。 a = 4'b1011; b = 8'b01010011; c = a | b; // a零扩展为 8'b00001011
逻辑操作符 ! not && and || or 逻辑反操作符将操作数的逻辑值取反。例如,若操作数为全0,则其逻辑值为0,逻辑反操作值为1。 module logical (); parameter five = 5; reg ans; reg [3: 0] rega, regb, regc; initial begin rega = 4‘b0011; //逻辑值为“1” regb = 4‘b10xz; //逻辑值为“1” regc = 4‘b0z0x; //逻辑值为“x” end initial fork #10 ans = rega && 0; // ans = 0 #20 ans = rega || 0; // ans = 1 #30 ans = rega && five; // ans = 1 #40 ans = regb && rega; // ans = 1 #50 ans = regc || 0; // ans = x #60 $finish; join endmodule 逻辑操作符的结果为一位1,0或x。 逻辑操作符只对逻辑值运算。 如操作数为全0,则其逻辑值为false 如操作数有一位为1,则其逻辑值为true 若操作数只包含0、x、z,则逻辑值为x 逻辑反操作符将操作数的逻辑值取反。例如,若操作数为全0,则其逻辑值为0,逻辑反操作值为1。
逻辑反与位反的对比 ! logical not 逻辑反 ~ bit-wise not 位反 module negation(); reg [3: 0] rega, regb; reg [3: 0] bit; reg log; initial begin rega = 4'b1011; regb = 4'b0000; end initial fork #10 bit = ~rega; // num = 0100 #20 bit = ~regb; // num = 1111 #30 log = !rega; // num = 0 #40 log = !regb; // num = 1 #50 $finish; join endmodule 逻辑反的结果为一位1,0或x。 位反的结果与操作数的位数相同 逻辑反操作符将操作数的逻辑值取反。例如,若操作数为全0,则其逻辑值为0,逻辑反操作值为1。
一元归约操作符 & and | or ^ xor ~ ^ xnor ^ ~ xnor module reduction(); reg val; reg [3: 0] rega, regb; initial begin rega = 4'b0100; regb = 4'b1111; end initial fork #10 val = & rega ; // val = 0 #20 val = | rega ; // val = 1 #30 val = & regb ; // val = 1 #40 val = | regb ; // val = 1 #50 val = ^ rega ; // val = 1 #60 val = ^ regb ; // val = 0 #70 val = ~| rega; // (nor) val = 0 #80 val = ~& rega; // (nand) val = 1 #90 val = ^rega && ®b; // val = 1 $finish; join endmodule 归约操作符的操作数只有一个。 对操作数的所有位进行位操作。 结果只有一位,可以是0, 1, X。
移位操作符 >> 逻辑右移 << 逻辑左移 << 将左边的操作数左移右边操作数指定的位数 >> 逻辑右移 << 逻辑左移 module shift (); reg [9: 0] num, num1; reg [7: 0] rega, regb; initial rega = 8'b00001100; initial fork #10 num <= rega << 5 ; // num = 01_1000_0000 #10 regb <= rega << 5 ; // regb = 1000_0000 #20 num <= rega >> 3; // num = 00_0000_0001 #20 regb <= rega >> 3 ; // regb = 0000_0001 #30 num <= 10'b11_1111_0000; #40 rega <= num << 2; //rega = 1100_0000 #40 num1 <= num << 2;//num1=11_1100_0000 #50 rega <= num >> 2; //rega = 1111_1100 #50 num1 <= num >> 2;//num1=00_1111_1100 #60 $finish; join endmodule 左移先补后移 右移先移后补 移位操作符对其左边的操作数进行向左或向右的位移位操作。 第二个操作数(移位位数)是无符号数 若第二个操作数是x或z则结果为x << 将左边的操作数左移右边操作数指定的位数 >> 将左边的操作数右移右边操作数指定的位数 在赋值语句中,如果右边(RHS)的结果: 位宽大于左边,则把最高位截去 位宽小于左边,则零扩展 建议:表达式左右位数一致
关系操作符 > 大于 < 小于 >= 大于等于 <= 小于等于 rega和regc的关系取决于x > 大于 < 小于 >= 大于等于 <= 小于等于 module relationals (); reg [3: 0] rega, regb, regc; reg val; initial begin rega = 4'b0011; regb = 4'b1010; regc = 4'b0x10; end initial fork #10 val = regc > rega ; // val = x #20 val = regb < rega ; // val = 0 #30 val = regb >= rega ; // val = 1 #40 val = regb > regc ; // val = 1 #50 $finish; join endmodule rega和regc的关系取决于x 其结果是1’b1、1’b0或1’bx。 无论x为何值,regb>regc
相等操作符 = = = = = = 注意逻辑等与 case等的差别 2‘b1x==2’b0x 值为0,因为不相等 2‘b1x==2’b1x 赋值操作符,将等式右边表达式的值拷贝到左边。 = 注意逻辑等与 case等的差别 = = 逻辑等 a = 2'b1x; b = 2'b1x; if (a == b) $display(" a is equal to b"); else $display(" a is not equal to b"); 2‘b1x==2’b0x 值为0,因为不相等 2‘b1x==2’b1x 值为x,因为可能不相等,也可能相等 = = 1 x z case等 = = = a = 2'b1x; b = 2'b1x; if (a === b) $display(" a is identical to b"); else $display(" a is not identical to b"); 2‘b1x===2’b0x 值为0,因为不相同 2‘b1x==2’b1x 值为1,因为相同 = = 1 x z Case等只能用于行为描述,不能用于RTL描述。
相等操作符 ! = 值确定是指所有的位为0或1。不确定值是有值为x或z的位。 module equalities1(); 逻辑等 == reg [3: 0] rega, regb, regc; reg val; initial begin rega = 4'b0011; regb = 4'b1010; regc = 4'b1x10; end initial fork #10 val = rega == regb ; // val = 0 #20 val = rega != regc; // val = 1 #30 val = regb != regc; // val = x #40 val = regc == regc; // val = x #50 $finish; join endmodule == ! = 逻辑等 逻辑不等 其结果是1’b1、1’b0或1’bx。 如果左边及右边为确定值并且相等,则结果为1。 如果左边及右边为确定值并且不相等,则结果为0。 如果左边及右边有值不能确定的位,但值确定的位相等,则结果为x。 !=的结果与= =相反 值确定是指所有的位为0或1。不确定值是有值为x或z的位。
相等操作符 ! == 综合工具不支持 === 相同(case等) 不相同(case不等) module equalities2(); reg [3: 0] rega, regb, regc; reg val; initial begin rega = 4'b0011; regb = 4'b1010; regc = 4'b1x10; end initial fork #10 val = rega === regb ; // val = 0 #20 val = rega !== regc; // val = 1 #30 val = regb === regc; // val = 0 #40 val = regc === regc; // val = 1 #50 $finish; join endmodule 其结果是1’b1、1’b0或1’bx。 如果左边及右边的值相同(包括x、z),则结果为1。 如果左边及右边的值不相同,则结果为0。 !==的结果与 === 相反 综合工具不支持
条件操作符 ?: 条件 module likebufif( in, en, out); input in; input en; output out; assign out = (en == 1) ? in : 'bz; endmodule module like4to1( a, b, c, d, sel, out); input a, b, c, d; input [1: 0] sel; assign out = sel == 2'b00 ? a : sel == 2'b01 ? b : sel == 2'b10 ? c : d; 如果条件值为x或z,则结果可能为x或z
条件操作符 每个条件操作符必须有三个参数,缺少任何一个都会产生错误。 最后一个操作数作为缺省值。 条件操作符的语法为: <LHS> = <condition> ? <true_expression>:<false_expression> 其意思是:if condition is TRUE, then LHS=true_expression, else LHS = false_expression 每个条件操作符必须有三个参数,缺少任何一个都会产生错误。 最后一个操作数作为缺省值。 registger = condition ? true_value:false_value; 上式中,若condition为真则register等于true_value;若condition为假则register等于false_value。一个很有意思的地方是,如果条件值不确定,且true_value和false_value不相等,则输出不确定值。 例如:assign out = (sel == 0) ? a : b; 若sel为0则out =a;若sel为1则out = b。如果sel为x或z,若a = b =0,则out = 0;若a≠b,则out值不确定。
级联操作符 { } 级联 module concatenation; reg [7: 0] rega, regb, regc, regd; { } 级联 module concatenation; reg [7: 0] rega, regb, regc, regd; reg [7: 0] new; initial begin rega = 8'b0000_0011; regb = 8'b0000_0100; regc = 8'b0001_1000; regd = 8'b1110_0000; end initial fork #10 new = {regc[ 4: 3], regd[ 7: 5], regb[ 2], rega[ 1: 0]}; // new = 8'b11111111 #20 $finish; join endmodule 可以从不同的矢量中选择位并用它们组成一个新的矢量。 用于位的重组和矢量构造 在级联和复制时,必须指定位数,否则将产生错误。 下面是类似错误的例子: a[7:0] = {4{ ´b10}}; b[7:0] = {2{ 5}}; c[3:0] = {3´b011, ´b0}; 级联时不限定操作数的数目。在操作符符号{ }中,用逗号将操作数分开。例如: {A, B, C, D}
复制 { {} } 复制 复制一个变量或在{ }中的值 前两个{ 符号之间的正整数指定复制次数。 module replicate (); reg [3: 0] rega; reg [1: 0] regb, regc; reg [7: 0] bus; initial begin rega = 4’b1001; regb = 2'b11; regc = 2'b00; end initial fork #10 bus <= {4{ regb}}; // bus = 11111111 // regb is replicated 4 times. #20 bus <= { {2{ regb}}, {2{ regc}} }; // bus = 11110000. regc and regb are each // replicated, and the resulting vectors // are concatenated together #30 bus <= { {4{ rega[1]}}, rega }; // bus = 00001001. rega is sign-extended #40 $finish; join endmodule { {} } 复制 复制一个变量或在{ }中的值 前两个{ 符号之间的正整数指定复制次数。
复习 问题: 1、 ~ 和!有什么不同? 2、&& 和 & 有什么不同? 3、用复制操作符复制一个数据时,对数据有什么要求? 解答 1、~ 进行1的补码操作,将矢量中的每一位取反 !将一个操作数归约为一位true或false结果 2、& 将操作数从低到高的对应位的进行与操作 && 将每个操作数归约为一位true或false,然后对归约结果进行与操作 3、要复制的操作数必须指定位数,例如 {3{‘b1}}是非法的,因此’b1没有指定位数。而{3{1‘b1}是合法的
第11章 行为建模 学习内容: 注: 行为建模的基本概念 Verilog中高级编程语言结构 如何使用连续赋值 第11章 行为建模 学习内容: 行为建模的基本概念 Verilog中高级编程语言结构 如何使用连续赋值 RTL描述方式是行为描述方式的子集。在本章中的综合部分将详细介绍哪些行为级结构同样可以用于RTL描述。 注:
行为描述 行为级描述是对系统的高抽象级描述。在这个抽象级,注重的是整个系统的功能而不是实现。 Verilog有高级编程语言结构用于行为描述,包括: wait, while, if then, case和forever Verilog的行为建模是用一系列以高级编程语言编写的并行的、动态的过程块来描述系统的工作。 在每一个时钟上升沿, 若Clr不是低电平, 置Q为D值, 置Qb为D值的反 DFF 无论何时Clr变低 置Q为0, 置Qb为1
过程(procedural)块 过程块是行为模型的基础。 过程块有两种: 过程块中有下列部件 initial块,只能执行一次 always块,循环执行 过程块中有下列部件 过程赋值语句:在描述过程块中的数据流 高级结构(循环,条件语句):描述块的功能 时序控制:控制块的执行及块中的语句。
过程赋值(procedural assignment) 在过程块中的赋值称为过程赋值。 在过程赋值语句中表达式左边的信号必须是寄存器类型(如reg类型) 在过程赋值语句等式右边可以是任何有效的表达式,数据类型也没有限制。 如果一个信号没有声明则缺省为wire类型。使用过程赋值语句给wire赋值会产生错误。 module adder (out, a, b, cin); input a, b, cin; output [1:0] out; wire a, b, cin; reg half_sum; reg [1: 0] out; always @( a or b or cin) begin half_sum = a ^ b ^ cin ; // OK half_carry = a & b | a & !b & cin | !a & b & cin ; // ERROR! out = {half_carry, half_ sum} ; end endmodule half_carry没有声明
过程时序控制 在过程块中可以说明过程时序。过程时序控制有三类: 简单延时(#delay):延迟指定时间步后执行 边沿敏感的时序控制:@(<signal>) 在信号发生翻转后执行。 可以说明信号有效沿是上升沿(posedge)还是下降沿(negedge)。 可以用关键字or指定多个参数。 电平敏感的时序控制:wait(<expr>) 直至expr值为真时(非零)才执行。 若expr已经为真则立即执行。 module wait_test; reg clk, waito, edgeo; initial begin initial begin clk = 0;edgeo=0;waito=0;end always #10 clk = ~clk; always @(clk) #2 edgeo = ~edgeo; always wait(clk) #2 waito = ~waito; endmodule
简单延时 在test bench中使用简单延时(#延时)施加激励,或在行为模型中模拟实际延时。 module muxtwo (out, a, b, sl); input a, b, sl; output out; reg out; always @( sl or a or b) if (! sl) #10 out = a; // 从a到out延时10个时间单位 else #12 out = b; //从b到out延时12个时间单位 endmodule 在简单延时中可以使用模块参数parameter: module clock_gen (clk); output clk; reg clk; parameter cycle = 20; initial clk = 0; always #(cycle/2) clk = ~clk; endmodule
边沿敏感时序 时序控制@可以用在RTL级或行为级组合逻辑或时序逻辑描述中。可以用关键字posedge和negedge限定信号敏感边沿。敏感表中可以有多个信号,用关键字or连接。 module reg_ adder (out, a, b, clk); input clk; input [2: 0] a, b; output [3: 0] out; reg [3: 0] out; reg [3: 0] sum; always @( a or b) // 若a或b发生任何变化,执行 #5 sum = a + b; always @( negedge clk) // 在clk下降沿执行 out = sum; endmodule 注:事件控制符or和位或操作符|及逻辑或操作符||没有任何关系。
wait语句 wait用于行为级代码中电平敏感的时序控制。 下面 的输出锁存的加法器的行为描述中,使用了用关键字or的边沿敏感时序以及用wait语句描述的电平敏感时序。 module latch_adder (out, a, b, enable); input enable; input [2: 0] a, b; output [3: 0] out; reg [3: 0] out; always @( a or b) begin wait (!enable) // 当enable为低电平时执行加法 out = a + b; end endmodule 注:综合工具还不支持wait语句。
命名事件(named event) 在行为代码中定义一个命名事件可以触发一个活动。命名事件不可综合。 module add_mult (out, a, b); input [2: 0] a, b; output [3: 0] out; reg [3: 0] out; //***define events*** event add, mult; always@ (a or b) if (a> b) -> add; // *** trigger event *** else -> mult; // *** trigger event *** // *** respond to an event trigger *** always @( add) out = a + b; // *** respond to an event trigger *** always @( mult) out = a * b; endmodule 在例子中,事件add和mult不是端口,但定义为事件,它们没有对应的硬件实现。 是一种数据类型,能在过程块中触发一个使能。 在引用前必须声明 没有持续时间,也不具有任何值 只能在过程块中触发一个事件。 ->操作符用来触发命名事件。 a大于b,事件add被触发,控制传递到等待add的always块。 如果a小于或等于b,事件mult被触发,控制被传送到等待mult的always块。
行为描述举例 竞争 在上面的例子中发生下面顺序的事件: 等待set=1,忽略时刻10的clk的posedge。 always wait (set) begin @( posedge clk) #3 q = 1; #10 q = 0; wait (! set); end 在上面的例子中发生下面顺序的事件: 等待set=1,忽略时刻10的clk的posedge。 等待下一个clk的posedge,它将在时刻30发生。 等待3个时间单位,在时刻33(30+3)置q=1。 等待10个时间单位,在时刻43(33+10)置q=0。 等待在时刻48发生的set=0。 等待在时刻70发生且与clk的上升沿同时发生的set=1。 等待下一个上升沿。时刻70的边沿被忽略,因为到达该语句时时间已经过去了,如例子所示,clk=1。 重要内容:在实际硬件设计中,事件6应该被视为一个竞争(race condition)。在仿真过程中,值的确定倚赖于顺序,所以是不可预测的。这是不推荐的建模类型。
RTL描述举例 下面的RTL例子中只使用单个边沿敏感时序控制。 module dff (q, qb, d, clk); output q, qb; input d, clk; reg q, qb; always @( posedge clk) begin q = d; qb = ~d; end endmodule
块语句 块语句用来将多个语句组织在一起,使得他们在语法上如同一个语句。 块语句分为两类: 顺序块:语句置于关键字begin和end之间,块中的语句以顺序方式执行。 并行块:关键字fork和join之间的是并行块语句,块中的语句并行执行。 Fork和join语句常用于test bench描述。这是因为可以一起给出矢量及其绝对时间,而不必描述所有先前事件的时间。
块语句(续) 在顺序块中,语句一条接一条地计算执行。 在并行块中,所有语句在各自的延迟之后立即计算执行。 fork begin #5 a = 3; #5 a = 5; #5 a = 4; end fork #5 a = 3; #15 a = 4; #10 a = 5; join 上面的两个例子在功能上是等价的。Fork-join例子里的赋值故意打乱顺序是为了强调顺序是没有关系的。 注意fork-join块是典型的不可综合语句,并且在一些仿真器时效率较差。
延迟赋值语句 语法: LHS = <timing_ control> RHS; 时序控制延迟的是赋值而不是右边表达式的计算。 可以用来简单精确地模拟寄存器交换和移位。 begin temp= b; @(posedge clk) a = temp; end 等价语句 a = @( posedge clk) b; LHS: Left-hand-side RHS: Right-hand-side
延迟赋值语句 并行语句在同一时间步发生,但由仿真器在另外一个时间执行。 在下面的每个例子中,a和b的值什么时候被采样? b值拷贝到a然后回传 a和b值安全交换 begin a = #5 b; b = #5 a; #10 $diplay(a, b); end fork a = #5 b; b = #5 a; #10 $diplay(a, b); join 在左边的例子中,b的值被立即采样(时刻0),这个值在时刻5赋给a。a的值在时刻5被采样,这个值在时刻10赋给b。 注意,另一个过程块可能在时刻0到时刻5之间影响b的值,或在时刻5到时刻10之间影响a的值。 在右边的例子中,b和a的值被立即采样(时刻0),保存的值在时刻5被赋值给他们各自的目标。这是一个安全传输。 注意,另一个过程块可以在时刻0到时刻5之间影响a和b的值。
非阻塞过程赋值 过程赋值有两类 阻塞过程赋值 非阻塞过程赋值 阻塞过程赋值执行完成后再执行在顺序块内下一条语句。 module swap_vals; reg a, b, clk; initial begin a = 0; b = 1; clk = 0; end always #5 clk = ~clk; always @( posedge clk) begin a <= b; // 非阻塞过程赋值 b <= a; // 交换a和b值 endmodule 过程赋值有两类 阻塞过程赋值 非阻塞过程赋值 阻塞过程赋值执行完成后再执行在顺序块内下一条语句。 非阻塞赋值不阻塞过程流,仿真器读入一条赋值语句并对它进行调度之后,就可以处理下一条赋值语句。 若过程块中的所有赋值都是非阻塞的,赋值按两步进行: 仿真器计算所有RHS表达式的值,保存结果,并进行调度在时序控制指定时间的赋值。 在经过相应的延迟后,仿真器通过将保存的值赋给LHS表达式完成赋值。
非阻塞过程赋值(续) 阻塞与非阻塞赋值语句行为差别举例1 输出结果: 0 a= x b= x c= x d= x e= x f = x module non_block1; reg a, b, c, d, e, f; initial begin // blocking assignments a = #10 1; // time 10 b = #2 0; // time 12 c = #4 1; // time 16 end initial begin // non- blocking assignments d <= #10 1; // time 10 e <= #2 0; // time 2 f <= #4 1; // time 4 initial begin $monitor($ time,," a= %b b= %b c= %b d= %b e= %b f= %b", a, b, c, d, e, f); #100 $finish; endmodule 输出结果: 0 a= x b= x c= x d= x e= x f = x 2 a= x b= x c= x d= x e= 0 f = x 4 a= x b= x c= x d= x e= 0 f = 1 10 a= 1 b= x c= x d= 1 e= 0 f = 1 12 a= 1 b= 0 c= x d= 1 e= 0 f = 1 16 a= 1 b= 0 c= 1 d= 1 e= 0 f = 1
非阻塞过程赋值(续) 阻塞与非阻塞赋值语句行为差别举例2 module pipeMult(product, mPlier, mCand, go, clock); input go, clock; input [7:0] mPlier, mCand; output [15:0] product; reg [15:0] product; always @(posedge go) product = repeat (4) @(posedge clock) mPlier * mCand; endmodule module pipeMult(product, mPlier, mCand, go, clock); input go, clock; input [7:0] mPlier, mCand; output [15:0] product; reg [15:0] product; always @(posedge go) product <= repeat (4) @(posedge clock) mPlier * mCand; endmodule
非阻塞过程赋值(续) 阻塞与非阻塞赋值语句行为差别举例2波形 非阻塞 阻塞
非阻塞过程赋值(续) DFF 阻塞与非阻塞赋值语句行为差别举例3 同步置位DFF 同步复位DFF module fsm(cS1, cS0, in, clock); input in , clock; output cS1, cS0; reg cS1, cS0; always @(posedge clock) begin cS1 = in & cS0; //同步复位 cS0 = in | cS1; //cS0 = in end endmodule cS1 <= in & cS0; //同步复位 cS0 <= in | cS1; //同步置位 同步置位DFF 同步复位DFF
非阻塞过程赋值(续) 举例4:非阻塞赋值语句中延时在左边和右边的差别 time a b time a b 0 1 4 0 1 4 2 3 2 module exchange; reg[3:0] a, b; initial begin a=1; b=4; #2 a=3; b=2; #20 $finish; end initial $monitor($time, "\t%h\t%h", a, b); #5 a <= b; #5 b <= a; endmodule module exchange; reg[3:0] a, b; initial begin a=1; b=4; #2 a=3; b=2; #20 $finish; end initial $monitor($time, "\t%h\t%h", a, b); a <= #5 b; b <= #5 a; endmodule time a b 0 1 4 2 3 2 5 2 2 time a b 0 1 4 2 3 2 5 4 1
条件语句(if分支语句) if 和 if-else 语句: 描述方式: if (表达式) begin …… end else always #20 if (index > 0) // 开始外层 if if (rega > regb) // 开始内层第一层 if result = rega; else result = 0; // 结束内层第一层 if if (index == 0) begin $display(" Note : Index is zero"); result = regb; end $display(" Note : Index is negative"); 描述方式: if (表达式) begin …… end else 可以多层嵌套。在嵌套if序列中,else和前面最近的if相关。 为提高可读性及确保正确关联,使用begin…end块语句指定其作用域。
条件语句(case分支语句) case语句: module compute (result, rega, regb, opcode); input [7: 0] rega, regb; input [2: 0] opcode; output [7: 0] result; reg [7: 0] result; always @( rega or regb or opcode) case (opcode) 3'b000 : result = rega + regb; 3'b001 : result = rega - regb; 3'b010 , // specify multiple cases witht he same result 3'b100 : result = rega / regb; default : begin result = 'bx; $display (" no match"); end endcase endmodule 在Verilog中重复说明case项是合法的,因为Verilog的case语句只执行第一个符合项。
条件语句-case语句 case语句是测试表达式与另外一系列表达式分支是否匹配的一个多路条件语句。 Case语句进行逐位比较以求完全匹配(包括x和z)。 Default语句可选,在没有任何条件成立时执行。此时如果未说明default,Verilog不执行任何动作。 多个default语句是非法的。 重要内容: 使用default语句是一个很好的编程习惯,特别是用于检测x和z。 Casez和casex为case语句的变体,允许比较无关(don‘t-care)值。 case表达式或case项中的任何位为无关值时,在比较过程中该位不予考虑。 在casez语句中,? 和 z 被当作无关值。 在casex语句中,?,z 和 x 被当作无关值。 case <表达式> <表达式>, <表达式>:赋值语句或空语句; default:赋值语句或空语句; case语法:
循环(looping)语句 有四种循环语句: repeat:将一块语句循环执行确定次数。 repeat (次数表达式) <语句> while:在条件表达式为真时一直循环执行 while (条件表达式) <语句> forever:重复执行直到仿真结束 forever <语句> for:在执行过程中对变量进行计算和判断,在条件满足时执行 for(赋初值;条件表达式;计算) <语句> 综合工具还不支持
循环(looping)语句-repeat // Parameterizable shift and add multiplier module multiplier( result, op_a, op_b); parameter size = 8; input [size:1] op_a, op_b; output [2* size:1] result; reg [2* size:1] shift_opa, result; reg [size:1] shift_opb; always @( op_a or op_b) begin result = 0; shift_opa = op_a; // 零扩展至16位 shift_opb = op_b; repeat (size) begin #10 if (shift_opb[1]) result = result + shift_opa; shift_opa = shift_opa << 1; // Shift left shift_opb = shift_opb >> 1; // Shift right end endmodule 为什么要说明一个shift_opb变量?
循环(looping)语句 while:只要表达式为真(不为0),则重复执行一条语句(或语句块) . . . reg [7: 0] tempreg; reg [3: 0] count; count = 0; while (tempreg) // 统计tempreg中 1 的个数 begin if (tempreg[ 0]) count = count + 1; tempreg = tempreg >> 1; // Shift right end
循环(looping)语句 forever:一直执行到仿真结束 forever应该是过程块中最后一条语句。其后的语句将永远不会执行。 forever语句不可综合,通常用于test bench描述。 ... reg clk; initial begin clk = 0; forever #10 clk = 1; #10 clk = 0; end 这种行为描述方式可以非常灵活的描述时钟,可以控制时钟的开始时间及周期占空比。仿真效率也高。
循环(looping)语句 for:只要条件为真就一直执行 条件表达式若是简单的与0比较通常处理得更快一些。但综合工具可能不支持与0的比较。 // X检测 for (index = 0; index < size; index = index + 1) if (val[ index] === 1'bx) $display (" found an X"); // 存储器初始化; “!= 0”仿真效率高 for (i = size; i != 0; i = i - 1) memory[ i- 1] = 0; // 阶乘序列 factorial = 1; for (j = num; j != 0; j = j - 1) factorial = factorial * j;
行为级零延时循环 当事件队列中所有事件结束后仿真器向前推进。但在零延时循环中,事件在同一时间片不断加入,使仿真器停滞后那个时片。 在下面的例子中,对事件进行了仿真但仿真时间不会推进。当always块和forever块中没有时序控制时就会发生这种事情。 module comparator( out, in1, in2); output [1: 0] out; input [7: 0] in1, in2; reg [1: 0] out; always if (in1 == in2) out = 2'b00; else if (in1 > in2) out = 2'b01; else out = 2'b10; initial #10 $finish; endmodule
持续赋值(continuous assignment) 可以用持续赋值语句描述组合逻辑,代替用门及其连接描述方式。 持续赋值在过程块外部使用。 持续赋值用于net驱动。 持续赋值只能在等式左边有一个简单延时说明。 只限于在表达式左边用#delay形式 持续赋值可以是显式或隐含的。 语法: <assign> [#delay] [strength] <net_name> = <expressions>; wire out; assign out = a & b; // 显式 wire inv = ~in; // 隐含
持续赋值(continuous assignment)(续) 持续赋值的例子 module assigns (o1, o2, eq, AND, OR, even, odd, one, SUM, COUT, a, b, in, sel, A, B, CIN); output [7:0] o1, o2; output [31:0] SUM; output eq, AND, OR, even, odd, one, COUT; input a, b, CIN; input [1:0] sel; input [7:0] in; input [31:0] A, B; wire [7:0] #3 o2; // 没有说明,但设置了延时 tri AND = a& b, OR = a| b; // 两个隐含赋值 wire #10 eq = (a == b); // 隐含赋值,并说明了延时 wire [7:0] (strong1, weak0) #( 3,5,2) o1 = in; // 强度及延时 assign o2[7:4] = in[3:0], o2[3:0] = in[7:4]; // 部分选择 tri #5 even = ^in, odd = ~^ in; // 延时,两个赋值 wire one = 1’b1; // 常数赋值 assign {COUT, SUM} = A + B + CIN ; // 给级联赋值 endmodule
持续赋值(continuous assignment)(续) in的值赋给o1,但其每位赋值的强度及延迟可能不同。如果o1是一个标量(scalar)信号,则其延迟和前面的条件缓冲器上的门延迟相同。对向量线网(net)的赋值上的延迟情况不同。0赋值使用下降延迟,Z赋值使用关断延迟,所有其他赋值使用上升延迟。 上面的例子显示出持续赋值的灵活性和简单性。持续赋值可以: 隐含或显式赋值 给任何net类型赋值 给矢量net的位或部分赋值 设置延时 设置强度 用级联同时给几个net类变量赋值 使用条件操作符 使用用户定义的函数的返回值 可以是任意表达式,包括常数表达式
持续赋值(continuous assignment)(续) 使用条件操作符的例子: module cond_assigns (MUX1, MUX2, a, b, c, d); output MUX1, MUX2; input a, b, c, d; assign MUX1 = sel == 2'b00 ? a : sel == 2'b01 ? b : sel == 2'b10 ? c : d; tri1 MUX2 = sel == 0 ? a : ’bz, MUX2 = sel == 1 ? b : ’bz, MUX2 = sel == 2 ? c : ’bz, MUX2 = sel == 3 ? d : ’bz; endmodule 从上面的例子可以看出,持续赋值的功能很强。可以使用条件操作符,也可以对一个net多重赋值(驱动)。 在任何时间里只有一个赋值驱动MUX2到一个非三态值。如果所有驱动都为三态,则mux2缺省为一个上拉强度的1值。
复习 问题: 在哪里放置always块?forever循环呢? 持续赋值通常给哪种类型的逻辑建模? 在begin…end之间使用非阻塞赋值和fork…join块有哪些区别? Verilog中posedge是什么意思? Verilog中存在哪些条件结构? 解答: always是块语句,在module内使用。 forever循环是在过程块、task或function内使用。 持续赋值只能给组合逻辑建模,因为他们只包含简单延迟。过程块可以包含@和wait时序控制。 fork…join块 可以使用等式左边延迟 可以包含并行语句(循环,条件语句,任务,系统任务,事件触发器),不仅仅是并行赋值。 是典型的不可综合语句 posedge是任何可能从低到高的跳变(0->1,0->z,0->x,z->x,x->z,z->1,x->1)。 Case、if-else语句以及?:条件操作符。
第12章 TUI调试 学习内容: 在本章中将学习用Verilog-XL TUI(Textual User Interface)和NC Verilog TUI调试 进入交互式仿真模式 控制并观察仿真 浏览设计层次 检查(checkpointing)和退出仿真 对设计进行临时修补 动态的单步及跟踪仿真 使用命令历史列表
术语及定义 SHM 仿真历史管理器(Simulation History Manager)。一个管理由SimWave显示的仿真对象值数据跳变的工具 CLI Verilog-XL命令行界面(command line interface),通过它你可以控制仿真并对Verilog过程语句执行调试操作 Tcl 工具命令语言(Tool Command Language)。用于对交互式程序提出命令的脚本语言 VCD (Value Change Dump)。存储对象值跳变数据的文件格式
纵览 什么是CLI(Command Line Interface)? 一个允许输入Verilog HDL过程命令的Verilog-XL仿真器的TUI CLI命令是一个Verilog过程语句或语句块。Verilog过程语句包括过程赋值,循环,条件语句和任务以及功能调用。可以在一行里输入多个Verilog语句,语句之间由一个分号他开,如同在源代码中那样。 Verilog-XL有源代码调试命令,这些命令不在IEEE规范中,可以在Verilog描述中使用,但这不很必要。 什么是Tcl(Tool Command Language) 一个对许多软件工具包括NC Verilog的文本用户界面。 NC Verilog仿真器的面向对象的TUI。 一个Tcl命令包括一个或多个字(命令名,后面是命令的argument)。字之间由空格或tab分隔。可以在一个命令行中输入多个命令,中间用分号分开。 可以从网上、技术参考书或图书馆得到标准Tcl命令的信息。 NC Verilog有专用标准Tcl命令集扩展用于设计调试(在ncsim命令窗口输入)。 本章将对大部分NC Verilog专用扩展作简单描述。联机文档中有更详细解释。 CLI和Tcl命令可以在命令行交互式输入,也可以由源脚本或keyfile输入。 注意:单击工具按钮或选择菜单时,SimVision GUI自动发出CLI和Tcl命令作出响应。 在本章中将通过实际调试CLI和Tcl的实例来学习二者的界面。
进入交互模式 有三种方法中断仿真,进入交互模式: 使用-s命令行选项在仿真前(时间0)停止仿真,立即进入交互模式 在仿真过程中输入一个^C异步中断 到达一个断点或在源代码里的$stop系统任务。 当中断Verilog-XL时,仿真器进入交互式模式并给出提示符: C1 > 当中断NC Verilog时,进入交互式模式并给出提示符: ncsim > 此时,仿真器暂时挂起。可以在命令行提示符处输入交互式命令,然后继续仿真。
进入交互模式 仿真器允许在离散的时间点中断仿真并与设计进行交流。有三种方法进入交互模式: 使用-s命令行选项在时间0停止仿真 输入一个^C异步中断 可以用测试基准中的$stop系统任务使仿真在指定的时间(或基于一个指定的事件)进入交互式模式。 当仿真器被中断时,它进入交互式模式并给出输入命令的提示符。 在Verilog-XL CLI中,可以输入任何可以放在一个过程块内的语句,并输入一些只用于调试环境的特殊命令。 在NC Verilog Tcl界面中,可以输入标准Tcl命令,Tcl的NC Verilog扩展,或将被传送到操作系统的shell命令。 仿真只是暂时挂起。所有信号保持他们当前状态直到继续仿真。
退出仿真 在Verilog-XL中,退出仿真的方式有: 在NC Verilog中,仿真退出有下列途径: 如果以batch模式(没有停止或进入交互式模式)运行仿真,则当仿真器在源代码中遇到一个$finish时,仿真退出。 在交互式模式中,Verilog-XL和NC Verilog有不同工作方式: 在Verilog-XL中,退出仿真的方式有: 在交互式窗口中输入$finish;或$finish[0|1|2]。可以提供一个参数显示仿真时间和存储器/CPU使用统计。 在交互式窗口中按^D。 仿真时在遇到源代码中的一个$finish。 在NC Verilog中,仿真退出有下列途径: 在交互式提示符输入$finish[0|1|2] 在交互式提示符输入exit 在交互式提示符按连续按^D两次 注意:在NC Verilog中,一旦进入了交互式模式,如果继续仿真并在源代码中遇到一个$finish,则返回到交互式模式。 注:$finish[0|1|2]:0:没有输出;1:输出仿真时间和位置;2:输出仿真时间和位置;输出仿真时使用的存储器大小和CPU时间。
退出仿真 如果在一个零延迟循环中或如果有太多仿真事件被列入队列以至系统不能及时对一个键盘中断做出响应,你可以终止仿真器进程来停止仿真。如果仿真继续运行,它将用去越来越多的存储空间。在NC Verilog中,你可以选择重复使用^C(5或6次)直到仿真停止。 可以发送KILL信号给一个UNIX进程来终止它,典型为^|,或从另一个shell进程,输入: kill –9 pid 这里pid为仿真器进程的进程ID。 可以用Task Manager终止一个NT进程。 用finish命令或$finish系统任务来退出仿真并返回控制到操作系统,可选择地显示仿真时间和存储器/CPU使用统计。
用Verilog-XL调试 force release $display $finish $history $[inc]save 下面是IEEE标准Verilog过程命令,在调试时特别有用: force release $display $finish $history $[inc]save $input $list $monitor $[real]time $reset $restart $scope $showscopes $showvars $stop $strobe Verilog-XL还提供了其它一些在调试时比较有用的命令 $db_break[after|before]time $db_break[once]atline $db_break[once]onnegedge $db_break[once]onposedge $db_break[once]when $db_[delete|enable|disable]break $db_[delete|enable|disable]focus $[db_]cleartrace $db_help $[db_]setfocus $[db_] settrace $db_showbreak $db_showfocus $db_step[ time] $cputime $deposit $history $showallinstances $showvariables ; , : . ? 注意:在交互模式下输入?或$db_help显示调试命令的帮助,或查看联机文档
用Verilog-XL调试 $db_step — 单步执行一条或多条语句 $db_steptime — 执行指定的时间(units) 一些系统任务的功能: $db_step — 单步执行一条或多条语句 $db_steptime — 执行指定的时间(units) $db_setfocus — 指定$db_命令作用范围 $db_disablefocus, $db_enablefocus, $db_ deletefocus — 范围操作 $[db_]settrace, $[db_]cleartrace — 跟踪或取消跟踪 $db_breakatline — 在一行之前或某个基本单元后设置断点 $db_breakbeforetime, $db_ breakaftertime — 在某一仿真时间之前/之后设置断点 $db_break[once]when — 变量值发生变化设置断点 $db_break[once]onnegedge, $db_break[once]onposedge — 信号沿断点 $db_disablebreak, $db_enablebreak, $db_ deletebreak —断点操作 $db_showbreak, $db_showfocus — 显示断点
NC Verilog调试 break case close concat eval exit for 调试非常有用的标准Tcl命令: break case close concat eval exit for foreach format gets history if incr open puts return set source switch unset while NC Verilog提供的调试用的扩展Tcl命令: alias call database deposit describe drivers finish fmibkpt force help omi probe process release reset restart run save scope status stop task time value version where 注:在交互模式下,输入help或help <command_name>来获得帮助,或参考联机文档
NC Verilog调试 deposit:给对象设置一个值 force:给对象强制赋值或维持原值。 release:取消在对象上的强制赋值 这些命令的功能为: deposit:给对象设置一个值 force:给对象强制赋值或维持原值。 release:取消在对象上的强制赋值 database和probe:SHM或VCD数据库控制 value:显示信号值 describe:显示仿真对象的信息 process:显示过程块信息 time:显示当前仿真时间 drivers:显示对象的详细驱动信息 scope:显示及游历模型层次信息 run:执行、单步及跟踪一个仿真 stop:多种类型断点处理 save:保存当前仿真的状态 reset:从0开始重新仿真, restart:从上次保存的状态开始仿真 status:显示仿真状态 finish:结束仿真并显示仿真状态
Verilog的断点 注意:所有命令要用“;”结束,如同源代码。在命令执行结束后,仿真器给出提示符“>” 在交互模式中,使用系统任务$stop或其它源代码级调试任务设置、控制断点。输入点“.”来继续仿真。 C1 > #10 $stop; //10个时间单位后停止仿真 C2 > forever @( posedge clk) $stop; //在clk的每个上升沿停止仿真 C3 > . C1: $stop at simulation time 10 C3 >if (en1 | en2) //如果en1或en2不为0, > @( negedge clk) //则在clk的每个时钟上升沿停止仿真 > $stop; C4 > $db_ breakatline(7); //在当前模块的第七行停止仿真 Set break (1) at line 7, scope test, file ulatch. v C5 > $db_ showbreak; //设置断点,其编号为1 Status enabled break( 1) at line 7, scope test, file ulatch. v C6 > $db_ delete( 1); //删除断点1 注意:所有命令要用“;”结束,如同源代码。在命令执行结束后,仿真器给出提示符“>”
Verilog的断点 可以用延时再加上$stop设置一个简单断点 可以使用Verilog语言的行为结构设置一个复杂断点。 设置断点之后,Verilog-XL不会自动继续执行。输入一个“.”来继续仿真操作。 交互式命令输入完成后,Verilog-XL才会以“>”代替Cn>来响应。 在提示符后输入的每个Verilog语句或语句块,Verilog-XL认为是在下面语句后输入的: initial #($time) 也就是说, 输入的任何过程语句或在一个begin/end块中的几个语句集合,被安排在当前仿真时间发生。甚至可以使用一些编译器指令。 但不能实例基本单元或模块、设置timescale、声明一个任务、函数或模块、对一个线网(net)进行持续赋值,或输入一个新的initial或always块。
NC Verilog断点 在交互模式时,使用stop命令来生成、命名、禁止、使能、删除或显示不同类型的断点。输入run来继续仿真。 ncsim > stop -time 10ns -delbreak 1 //10个时间单位后停止仿真 Created stop 1 ncsim > stop -condition {# clk == 1} //在每个clk时钟上升沿停止仿真 Created stop 2 ncsim > run 10NS + 0 (stop 1) ncsim > if {[ expr #en1] || [expr #en2]} \ //如果en1或en2为1, > {stop -condition {# clk == 1}} //在每个clk时钟上升沿停止仿真 Created stop 3 ncsim > stop -line 7 -name linbr //在当前范围第7行停止仿真,并命名这个 Created stop 4 //断点的名字为linbr ncsim > stop -show 2 Enabled Condition {# clk == 1} 3 Enabled Condition {# clk == 1} linbr Enabled Line: ./ ulatch. v: 7 (scope: test) ncsim > stop -delete 2 //删除断点2 ncsim > stop -disable linbr //禁止命名断点linbr ncsim >
NC Verilog断点 在交互模式时,使用stop命令来生成、命名、禁止、使能、删除或显示不同类型的断点。断点可以为条件、行、对象和时间。 设置断点后,仿真器不会自动继续执行。输入run来继续仿真。 输入交互命令时,Verilog-XL的提示符为“>”。完成后为ncsim>。 如果使用完全访问,可以访问行,事件,值和连接信息,并修改信号值。
显示及设置调试作用域 使用系统任务$showscopes;或Tcl命令scope –show显示当前级定义的作用域。 Directory of scopes at current scope level: module (adder), instance (u1) module (latch), instance (u2) Current scope is (top) Highest level modules: top 使用系统任务$scope或Tcl命令scope设置调试范围。 C2 > $scope(u1); ncsim > scope u1 使用CLI命令冒号 ( : )或Tcl命令where显示当前仿真位置 Line 6, file "./ inter_bufif. v", scope (top) Scope is (top)
显示及设置调试作用域 刚进入交互模式时,当前调试范围为最高层。 使用系统任务$scope改变调试范围到层次中指定(名字)级 使用$scope<scope_name>命令来改变调试范围到层次中命名的层次。 可以使用层次范围名。可以直接引用当前层次上定义的对象而无须他们的全部层次名。
设计反编译 可以反编译当前范围(缺省)或一个命名的范围。 声明变量的 当前 状态 *表示当前活动的语句 在Verilog-XL中,使用$list系统任务。用-d命令行选项来反编译整个设计。 在NC Verilog中,使用scope -list命令。 产生的输出在所有当前活动的过程语句旁边带有一个星号(*)。 // mods. v 1 module dut( q, d, c); 2 output 2 q; // = 1’h0, 0 3 input 3 d, // = St1 3 c; // = St0 4 reg 4 q; // = 1’h0, 0 5* always 6 begin 7 @( posedge c) 8 q <= #( 0) d; 9 end 10 endmodule 声明变量的 当前 状态 *表示当前活动的语句
遍历设计 等价的Verilog-XL命令 $list(A.B.D); $scope(A.B); $scope(A.B.D); 刚进入交互式模式时: scope -show或$showscopes;确定模块A为当前范围,列出模块实例B和C为A内的范围 scope -list或$list;反编译当前范围(模块A) scope –list C或$list(C);反编译模块实例C scope B或$scope(B);设置当前交互范围为模块实例B 等价的Verilog-XL命令 $list(A.B.D); $scope(A.B); $scope(A.B.D); $list(D); $list; 等价的NC Verilog命令 scope -list A.B.D scope A.B scope A.B.D scope -list D scope -list
显示信号值 在Verilog-XL中: 使用$display,$monitor和$strobe来输出信息或值 使用$time和$realtime系统功能显示仿真时间,用$cputime系统功能来显示到目前为止实际使用的仿真时间,以0.1秒计。 C1 > $monitor(“%m %0d”, $time, “ o1 = %h”, o1); top 100.0 o1 = X C2 > $display(“ Simulation took %g/ 10 seconds”, $cputime); Simulation took 6/ 10 seconds 在NC Verilog中: 使用value,describe和scope -describe来输出信息或值。 使用time来显示当前仿真时间。 ncsim > describe o1 m1 o1…… …… wire (wire/ tri) = StX m1 …… …… instance of mod1 ncsim > value %h o1 1’hx ncsim > time 10 NS
显示信号值 使用value命令可以显示一个或多个仿真对象的值。可以用$vlog_format设置变量的缺省显示格式。如果不设置,它缺省为%h。必须在elaboration时将对象设置为可读取访问才能显示其值。 用describe命令显示仿真对象的简短描述。 使用time命令显示当前仿真时间。用Tcl命令$display_unit设置时间单位,缺省为auto(最大的基准,显示一个不小于1的时间),并包括delta周期记数(零延迟事件被传输的次数)。
显示信号驱动 在NC Verilog中: 使用drivers或scope -drivers显示有关线网或寄存器的详细信息。 在Verilog-XL中: 使用系统任务$showvars或$showvariables得到线网或寄存器的详细信息。 C1 > $showvars( o1); o1 (top) wire = StX, schedule = StX HiZ <- (top. m1): bufif1 g3( o1, i3, c3); schedule = St1 St1 <- (top. m1): bufif1 g2( o1, i2, c2); St0 <- (top. m1): bufif1 g1( o1, i1, c1); 使用系统任务$countdrivers列出一个线网的所有驱动。这个系统任务不太常用,在联机文档中有相关说明。 在NC Verilog中: 使用drivers或scope -drivers显示有关线网或寄存器的详细信息。 ncsim > drivers o1 o1......... wire (wire/ tri) = StX HiZ <- (top. m1): bufif1 g3( o1, i3, c3) St1 <- (top. m1): bufif1 g2( o1, i2, c2) St0 <- (top. m1): bufif1 g1( o1, i1, c1)
显示信号驱动 使用Tcl命令drivers或系统任务$showvars列出一个或多个信号值的所有驱动。也可以列出范围,查看在这个范围中的信息。如果没有参数,则报出当前范围内所有信号的以下信息: l 变量名 l 当前值 l 变量范围 l 变量是否被强制(forced) l 变量类型 l 反编译出的驱动器及其输出值 要显示NC Verilog中的驱动,必须已经在elaboration时将对象设置连接访问。 $showvars除报出和drivers同样的信息外,还有: l 要调度的将来值 l 驱动器将来值,如果已经调度 可以给 $showvariables提供一个从0到7的值。 $showvariables(0);使用同$showvars; 1 在整个层次中向下递归地报出变量信息。 2 抑制驱动器信息。 3递归地抑制驱动器信息。 4 只报出含有未知值的变量。 5递归进行4。 6 只报出还有未知值的变量,且抑制驱动器信息。7递归进行6。
在Verilog-XL中修补设计 force和release命令用一个值或表达式覆盖信号的驱动。 这些系统任务 Verilog-XL专用 C1 > $reset; // 复位返回到仿真时间为0时的状态 C2 > force o1 = in1 | in2; . . . C14 > release o1; //清除net或寄存器上所有强制值 这些系统任务 Verilog-XL专用 注意:可以用系统任务$list_forces列出所有当前活动的force。 用$deposit系统任务在线网(net)或寄存器上放置一个值(可以是0、1、x、z),这个值向前传播。可以用一个简单的赋值设置值。 $deposit( sig, 1); $deposit( bus, ’hAx2); 信号保持其值直到下一次改变。例如: module patch; reg in; buf (tmp, in); buf (out, tmp); initial begin #5 in = 0; //in = 0; tmp = 0; out = 0; #5 $deposit (tmp, 1’b1); //in = 0; tmp=1; out=1; #5 in = 1; //in=1; tmp=1; out=1; #5 in =0; //in=0; tmp=0; out=0; #5 $finish; end endmodule
在NC Verilog中修补设计 force和release命令用一个新值覆盖信号的驱动。 ncsim > reset // resets the simulation to time zero ncsim > force o1 = 1’b0 ncsim > . . . . . . ncsim > release o1 -keepvalue 注意:force命令等号右边可以是一个表达式,但这个表达式只计算一次。 用$deposit系统任务在线网(net)或寄存器上放置一个值(可以是0、1、x、z),这个值向前传播。 ncsim > deposit sig 1 ncsim > deposit bus ’hAx2 信号保持其值直到下一次改变。不能向由force赋值的信号进行deposit。 注意:可以用scope –list命令列出当前范围的信号的所有驱动,包括当前活动的force。deposit不是一个驱动。
在NC Verilog中修补设计 reset命令将设计复位到仿真时刻0的逻辑状态。 电路修补的通常用途为: 将电路的部分或全部初始化 设置电路到一个确定状态 覆盖错误值使得仿真可以继续进行 打断反馈循环并设置它到一个确定状态 用force命令来在调试时临时修补设计。只能对全部寄存器或线网矢量和展开后的向量线网的位或选择部分强制赋值。被赋的值必须为Verilog立即数。force覆盖所有其他驱动,并保持有效直到被其它Tcl或Verilog的force替代,或被Tcl或Verilog中的release取消。这个命令和Verilog force语句等价。 release命令取消Tcl或Verilog中一个或多个信号上的force。这个命令与Verilog-XL release语句等价。 deposit命令设置指定的net,寄存器,存储器或存储元件的值。被赋的值必须为Verilog立即灵敏。设置的值的变化向前传输。 要修补一个信号,必须已经在elaboration时将对象设置为写访问。
跟踪仿真动态 在NC Verilog中没有跟踪信息。在Verilog-XL中: $settrace和$cleartrace系统任务开启和关闭跟踪。 $db_settrace和$db_cleartrace系统任务在设计中关心部分开启和关闭跟踪。 用-t选项使整个仿真过程中开启跟踪。 C1 > forever begin > @enable $settrace; #2 $cleartrace; $stop; end C2 > . SIMULATION TIME IS 300 L8 "run_ ta. v": buf u4 >>> XL GATE = St0 L6 "run_ ta. v": wire clk >>> FROMXL NET = St0 L12 "latch. v" (run_ ta. u3): @( negedge enable) >>> CONTINUE L13 "latch. v" (run_ ta. u3): r1 = #( 1) data; >>> = #( 32’h1, 1) 1’hx, x; C1: #2 >>> CONTINUE C1: $cleartrace;
跟踪仿真动态 用$settrace和$cleartrace系统任务来开启和关闭仿真跟踪。 用-t选项来为整个仿真来开启跟踪 跟踪显示事件、文件名以及在源描述的行号。 仿真跟踪在将一个时序问题追踪到一个小时间窗口时,例如一个竞争情况,非常有用。 注意:这些系统任务为Verilog-XL仿真器专用。NC Verilog没有一个跟踪模型。
跟踪仿真动态 在Verilog-XL中,单步跟踪可以为: 分号(;)单步执行仿真 逗号(,)单步执行仿真并跟踪 $db_step单步仿真,跳过所有不在调试范围内的部分 $db_steptime仿真指定的时间。 C1 > , //单步执行并跟踪 C1: forever C1 > ,, C1: begin C1: @sel C2 > ;; //单步仿真,没有跟踪,除非使用$settrace或-t 在NC Verilog中,单步跟踪可以为: run –step:单步仿真,执行每一个语句。 run- next:单步仿真,执行每个语句并跳过子程序调用(任务和函数)。 run timeval:运行仿真一段指定的时间。
跟踪仿真动态 如果没有指定范围并开启了跟踪,分号、逗号和$db_step 等价。 run命令开始仿真或继续一个以前暂停的仿真。仿真根据指定的参数进行。时间可以是相对或绝对的。使用不带参数的run命令运行仿真直到结束或直到一个断点或发生一个错误。
命令历史列表 系统任务$history或Tcl命令history可以列出以前执行的交互命令。 在NC Verilog中输入!命令编号或history redo -命令编号可以重新执行命令。 在Verilog-XL中,输入命令编号重新执行命令或-命令编号取消命令。 C4 > $history; Command history: C1* forever @( posedge clk) $stop; C2 $list; C3 $display( clear); C4* $history; C5 > -1 C6 > 2 处于活动状态的命令由(*)标记 Verilog-XL ncsim > history 1 stop -time 10ns -delbreak 1 2 stop -condition {# clk == 1} 3 run 4 if {# en1 || #en2} { stop -condition {# clk== 1} } 5 stop -line 7 -name linbr 6 stop -show 7 stop -delete 2 8 stop -disable linbr 9 history ncsim > history redo –2 Created stop 2 NC Verilog 用history命令修改历史机制将保持的命令数来重新执行一个指定的以前的命令,并在重新执行前替代一个以前命令的一些部分。history命令是一个标准Tcl命令。
保存及重启动仿真 在Verilog-XL中,可以在交互式模式或在源代码中使用下列命令: $save和$incsave命令保存整个仿真数据结构。 $restart或-r命令行选项可以从保存时间重新开始仿真。 这样可以不必重新编译并重新仿真到该时间点。 $reset重新设置仿真状态到时刻0的状态。 系统任务$history或Tcl命令history可以列出以前执行的交互命令。 C1> #500 $save(“save.dat”); // 在500 ns后保存仿真 C2> forever #100000 // 每个100000 ns后 > $incsave(“ inc. dat”); // 增量式保存仿真 在NC Verilog中使用这些Tcl交互命令: save和restart保存并从一个仿真snapshot重新开始。 reset重新从时刻0snapshot开始。 由于NC Verilog总是保存所有数据,snapshot只是简单的保存初始值。这使保存和重新开始更快更可靠。 ncsim > stop -time 100000NS -execute {save try1} ncsim > restart try1
保存及重启动仿真 可以将仿真数据结构保存到一个文件并在以后再次启动仿真。 用这个特性可以定点检查一个长仿真并: 在机器崩溃时保护仿真(machine failure) 执行快速“what if”关 系统任务$save保存仿真数据结构。提供一个文件名参数用于存储仿真数据结构。 系统任务$incsave进行增量保存。增量保存使用较少空间。 系统任务$restart或-r命令行选项从一个以前保存的数据结构重新开始仿真。需要提供储存仿真数据结构的文件名作为参数。 注意使用-r 命令行选项时,不用指定设计文件。 仿真器执行一个过程语句中间不能保存。用run -clean到达仿真中的下一个“clean”点,然后执行 save命令。可以以后重新装载snapshot继续仿真; Tcl环境的状态(包括指针)并不和snapshot一起保存。要保存Tcl环境,必须单独执行一个save -environment。要从储存的Tcl环境restart ,必须编译保存的环境文件。
在Verilog-XL中执行playing back TUI命令 系统任务$input或-i命令行选项可以执行一个脚本文件。 仿真器在交互模式下执行命令脚本。 keyfile. txt $display(" Executed keyfile at %g",$ realtime); verilog mods. v -i keyfile. txt -q –s //在交互模式下,并在时间0执行keyfile.txt C1 > $display(" Executed keyfile at %g",$ realtime); Executed keyfile at 0 C2 > begin $input(“ keyfile. txt”); #10 $stop; end . //停止时执行keyfile.txt C2: $stop at simulation time 10 C3 > $display(" Executed keyfile at %g",$ realtime); Executed keyfile at 10 C4 > 注意:在交互式模式下,begin和end之间语句顺序执行。通常的,在交互模式提示符处输入的语句在仿真继续进行时为并行执行。
在Verilog-XL中重新执行TUI命令 仿真器将交互命令写到一个key文件(key file),其缺省名为verilog.key。 可以用-k选项重新命名key文件。 可以用$input系统任务或用-i命令行选项开始Verilog-XL,读取并执行一个包含交互式命令的文件,例如该key文件。 仿真器在交互模式下读取并执行keyfile。
在NC Verilog中重新执行TUI命令 在交互模式中使用source file_name命令或+tcl+file_name命令行选项执行一个Tcl命令脚本,或keyfile。 commands. tcl run 2NS concat {Executed keyfile at} [eval time NS] 进入交互模式,并在时间0执行keyfile ncverilog mods. v +tcl+ commands. tcl -s +access+ rwc ncsim > run 2NS ncsim > concat {Executed keyfile at} [eval time NS] Executed keyfile at 2 NS ncsim > source commands. tcl Executed keyfile at 4 NS
在NC Verilog中重新执行TUI命令 仿真器将交互命令写入一个key文件,缺省名为ncsim.key。 可以用-k选项来重新命名key文件。在交互模式中输入 save -commands tcl_file 或 save-environment tcl_file 来保存一个拷贝。 可以输入 sourse tcl_file命令 或 +tcl+tcl_file 或 +ncinput+tcl_file 或 +nckeyfile+tcl_file命令行选项 重启仿真器,读取并执行一个含有交互命令的文件,例如key文件。 Source命令只显示最后一个执行的命令的结果。Verilog-XL选项-i被忽略。
建立波形数据库 在Verilog-XL交互模式下创建并操作一个波形数据库,使用数据库类型相关的系统任务。 在NC Verilog交互模式下创建一个波形数据库,使用以下命令: 打开、禁止、使能、关闭或显示SHM或VCD数据库的信息: database [-options] 例如,打开数据库mywaves.shm并命名其波形: ncsim> database -open waves -into mywaves. shm 创建、禁止、使能、删除或显示探针的信息: probe [-options] 例如,探测从实例u1到页单元的所有端口到一个SHM数据库,命名探针为seek,并打开波形工具: ncsim> probe -create -name peek u1 -ports -depth to_ cells -waves 例如,监视被探测的信号rega并将其值打印到一个文件: ncsim> probe -screen u1. rega -redirect mon_rega. txt database命令打开、关闭、禁止或使能一个SHM或VCD数据库,或显示有关信息。 probe命令创建、删除、禁止、使能或显示一个指针。当被探测时,对象值的每一个变化都被保存到一个数据库。
其它Tcl命令 ncsim> status alias [-options] 设置、取消或显示一个仿真命令别名 ncsim> alias -set go {run 10 ns; value sum} call [-systf] task_name [-options] 调用用户自定义系统任务或函数(PLI 应用) ncsim> call {$myprinter} {"set value to "} 8’hc process 确定正在执行或将要安排执行的过程块。 status 显示资源使用和仿真时间。 ncsim> status task 用名字调用一个Verilog任务。 version 显示仿真器的版本。
其它Tcl命令 Alias命令是一个标准Tcl命令。可以设置、取消或显示一个仿真命令别名 call命令调用一个用户自定义系统任务或函数(Verilog),或一个C-接口函数(VHDL)。插入-systf或-predefined选项在协同执行Verilog和VHDL仿真时区分名字相同的函数。 process命令确定正在执行或将要安排执行的过程块(或VHDL过程)。 status命令显示存储器使用、CPU使用和仿真时间。 task命令调用一个Verilog任务。注意不能用这个命令调用一个Verilog函数或系统任务/函数。要传递参数值给该任务,在调用之前在其输入和双向端口deposit值。例如,如果任务test的输入为N,则可以用如下方法调用它: ncsim> deposit test.N 1; task test 用version命令来显示仿真器版本。
总结 在这一章学习了: 控制并观察仿真 进入交互仿真模式 浏览设计层次 设置断点 显示信号波形 点检测(checkpointing)、退出及重新启动仿真 临时修补设计 单步和跟踪仿真行为 使用命令历史列表
复习 问题: 怎样创建自己的命令使仿真前进1ns并显示一个寄存器的内容? 怎样进入交互模式? 可以用什么命令来设置缺省当前范围层? 怎样得到一个信号的当前值? 解释在每个仿真器内deposit和force之间的区别。 在Verilog源代码中,是否当ncsim遇到$finish系统任务时就会终结并返回到主提示符? 解答: 除非特别注明,以$开头的命令用于Verilog-XL,其他命令用于NC Verilog。 在Tcl中,可以使用alias命令创建一个命令。例如:alias -set go1 {run 1 ns; value reg1} 在Verilog-XL中,可以为命令定义一个文本宏。例如:`define set #1 $display( reg1); . 用-s命令行选项,按^C或在源代码中遇到$stop。 用$scope 或scope来设置缺省当前范围层。 用$display,value或describe。可以用$showvars或drivers获得一个信号的值和强度,以及它的所有驱动。 在两个仿真器中,force保持一个值直到它被release,而一个deposited值保持其值仅到信号更新。在Verilog-XL中,force命令使force节点减速,但可以在一个表达式和一个信号之间创建一个连续关系。它可以为一个位或部分赋值,而$deposit只能影响整个线网或寄存器。 是的,除非已经进入交互模式。在这种情况下,将返回到交互式提示符。
第13章 使用图形调试环境 学习内容: 这一章将学习有关SimVision图形环境: SimControl Navigator 第13章 使用图形调试环境 学习内容: 这一章将学习有关SimVision图形环境: SimControl Navigator Signal Flow Browser (SFB):信号流浏览器 Watch Objects Windows:对象观察窗口
术语及定义 SimControl:图形仿真器接口;用SimControl来推进或中断仿真,打断并改变仿真,控制范围,等等。 SignalScan:SimControl图形波形观察器 Navigator:显示设计层次和一个范围内对象信息的图形工具 Signal Flow Browser(SFB):在设计中从一个信号反向跟踪到其驱动源的图形工具 Watch Objects Windows :监视信号组及其值的窗口 Object:在SimControl中,任何一个信号或范围 Double-click:将光标放在一个项目上并快速点击鼠标左键两次 Right-select:将光标放在一个项目上并点击鼠标右键
启动图形环境 使用命令行选项+gui启动SimControl。在SimControl中可以访问SimVision所有部件。 可以从SimControl工具菜单或工具栏访问SimVision环境(SignalScan, Navigator, Signal Flow Browser及Watch Objects windows)的其他部件。
SimControl 包括8个下拉菜单 固定菜单按钮可以快速访问常用命令 一个空的用户可定义按钮 当前仿真时间 显示正在调试的模块的源代码,可以选择这个区域的文本,但不能编辑 显示范围并浏览。在同一时间只能显示一下范围。若一个模块在多个文件出现,可以在subscope选择文件 显示交互仿真的输出。可以提示符处输入交互命令 显示信息,如仿真器的状态。 详细的菜单命令及其它SimVision细节请参考联机文档
后处理环境 在仿真并将探测信号放到一个SHM数据库之后,可以对设计进行后处理。 可以从GUI或通过使用+ppe选项进入后处理模式。
交互及后处理 用+gui选项启动SimVision时,进入仿真器交互模式。进入后处理模式时,将仿真器释放并对保存在波形数据库中的值进行操作。 在交互模式中: 时间只能向前推进。 可以读取并修改仿真值,访问连接并设置行断点,取决于使能的是何种访问。 可以将值保存到一个波形数据库。 可以执行交互命令和源交互式脚本。 可以保存、重新开始、重新设置、运行和停止仿真。 在后处理模式中: 可以扫描、单步、执行,或按时间向前或向后跳转。 可以读取仿真值并访问连接,取决于在仿真过程中使能的访问及在波形数据库中保存的信息。
SimControl菜单及工具条 编辑源文件,打开数据库,和查找文本。在交互模式中,还可以source命令文件、保存并重新开始仿真。 执行、单步、停止和复位到开始时刻。在后处理模式中,还可以向后执行,并可以向前或向后扫描。 设置并显示断点和范围。在交互模式中,还可以设置并显示forces和探针 选择范围,端口和信号。 启动其他SimVision工具。 按照用户的意愿调整SimControl的行为
Source Browser Source Browser显示在当前调试模块的源代码。 可以在Source Browser中选择任意对象(寄存器,net,实例或线) 在Source Browser中右击一个对象或行号弹出可以对该对象或行进行操作的命令菜单
选择对象 在SimVision环境中,选择和弹出菜单使鼠标成为一个有力的工具。 左键可以在所有SimVision窗口中选择对象 右击鼠标右键选择对象可以弹出一个菜单。 按住Control键再按左键可以选择多个对象或取消选择。 中键可以在窗口之间拖动对象
选择对象 在一个SimVision窗口选择一个或多个对象时,其他SimVision窗口的这些对象也均被选择。 按control键并用鼠标左键点击一个对象,可以选定该对象而不影响其他被选对象。 添加选择:选择一个对象后,可以选择其它更多对象。 取消选择:当一个对象被选定,可以用这个办法来取消对它的选择。 点击拖动:当用鼠标中间的按钮选择一个对象时,可以拖动这个对象到另一个位置。如果鼠标只有两个按钮,可以用左键点击拖动。
设置断点 在下面的对话框中可以设置、使能、取消、列出,和删除断点。断点可可以是仿真时间、值的改变、条件或代码行。断点的功能视仿真器而不同。
设置断点 断点是使仿真停止的事件。有三种类型的断点: SimControl提供四种设置断点的方法: 基于时间:当仿真到一个指定时间停止。此为缺省。 基于行:当仿真到到源代码一个指定的行时停止。必须指定范围,文件名,和行号。只用于交互模式。 基于对象:当指定信号的值发生变化或指定跳变发生时停止。在NC Verilog中,不能指定一个单一跳变。 基于条件:当指定的Tcl表达式值为真时停止。 SimControl提供四种设置断点的方法: 在菜单选择Set-Breakpoints 在Show-Breakpoints对话框,按Set按钮。 按Set Breakpoints按钮 (只用于基于对象的断点设置)。 在任何一个SimControl窗口中右击选择一个信号名,弹出一个有Set Break的菜单。
Navigator 用navigator来查看设计层次和当前范围的对象。 要启动navigator,在SimControl窗口使用Tools菜单下Navigator,或按navigator按钮 Navigator工具条是主窗口工具条的子集
Navigator 浏览设计层次时,navigator生成一个树结构,在这个树结构中每个节点为设计层次中一个范围。 双击一个没有展开的节点以显示子层 双击一个展开的节点隐藏子层 在navigator中有两种方法设计当前范围(scope): 选择一个节点,然后按Scope按钮 ,或者在SimControl窗口中选择Set-Scope。 右击选择一个节点并从弹出菜单中选择Set Debug Scope,或双击该节点。 注意:由于所有SimVision窗口交互作用,从任何SimVision窗口设置范围时,该范围的源代码自动读入源浏览器source browser。 用Options菜单设置显示选项 可以隐藏范围树或对象列表。 启动Object List Options框或Scope Tree Options框指定显示及每个区域的内容。
Signal Flow Browser(信号流浏览器) 可以从SimControl启动信号浏览器SFB: 菜单条:通过选择Tools-Signal Flow Browser 工具条:通过选择Signal Flow Browser 按钮 SFB的工具条包括: 主窗口工具条的子集 Trace Back :跟踪选择信号,代替当前跟踪 Stack View 和Trace View 翻转SFB外观 右图是一个Stack View形式
Signal Flow Browser(信号流浏览器) 信号流浏览器可以交互地跟踪一个信号的驱动以及对这些驱动所起的作用 选择一个将被跟踪的信号: 打开信号流浏览器前: 在源浏览器中选择信号。在任何窗口中所做的选择会传递到其他所有窗口 打开信号流浏览器后: — 在Trace区输入一个层次信号名。 — 在其他Simvision窗口中选择一个信号并用鼠标中键将其拖到信号流浏览器中。
Signal Flow Browser(信号流浏览器) 信号流浏览器(SFB)是一个高效的设计调试环境。 用SFB可以从一个行为反常的信号开始,向后跟踪其驱动和作用信号直到发现行为反常的原因。 信号流浏览器可以执行下面操作: 以选择的基数显示一个信号的值。 显示信号的驱动。 查看信号的输入或驱动的细节。 显示所有对一个驱动起作用的信号。 跟踪一个模块端口到一个较低的层次。
Signal Flow Browser(信号流浏览器) 必须首先选择要跟踪的信号并将它输入或点击拖放到SFB中。此时出现一个驱动器(Driver)框,显示出所有该信号的驱动。 可以在一个Driver框里跟踪任何驱动并显示其输入;或者通过首先选择该驱动器,然后从菜单中选择Trace-Show Inputs;或者简单地双击该驱动。 可以在一个Contributing Signal框中跟踪任何信号以显示其所有驱动,或者通过首先选择该信号,然后从菜单总选择Trace-Show Drivers;或者简单地双击该信号。 如果一个信号的驱动为一个模块实例的端口,可以跟踪它并显示在这个模块实例内部的信号源。首先选择该驱动器,然后从菜单中选择Trace-Descend。 例如,如果在一个驱动器框中有下面这个模块实例: register r1(. r( reg_ out) ...) 选择reg_out并使用Trace-Descend将显示 r 为信号源。 可以通过首先选择该驱动器,然后从菜单中选择View-Driver Info显示一个驱动的细节.
Watch Objects Windows(信号观察窗口) 可以打开任意多个对象观察窗口。每个窗口包含一个信号表和当前仿真值。 要在一个对象观察窗口中添加对象,用点击信号并拖动或按Add Objects. 可以重新命名、锁定、删除、关闭,iconify,或克隆每个对象观察窗口。 点击Find Next Edge 使仿真到下一个跳变。
Watch Objects Windows(信号观察窗口) 打开多个观察窗口 每个观察窗口在关闭时会自动保存。 可以打开一个新窗口或一个以前保存过的窗口。打开的第一个窗口缺省名“View1”。第二个窗口为“View2”,依次类推。 可以重新命名一个窗口。 可以克隆一个窗口,生成一个完全拷贝。 要从另一个SimVision窗口添加对象到一个观察窗口,用点击拖动或者选择它们然后按Add Objects。 添加一个信号是将其添加到观察列表中。 添加一个范围是添加在此范围中的信号到观察列表。 用观察窗口的选项(Option)菜单为每个窗口定制信号命名和值显示的方式。 Find Next Edge 使仿真前进到一个信号的下一个跳变,或到下一个用户设置的断点。 Lock 可以锁定一个观察窗口,使它不会随时间更新其显示。不能向一个被锁定的窗口添加新信号或从它前进时间。当解锁后,它将立即更新到反映当前时间的当前值。 Time区在后处理模式中可编辑,并只为该特定观察窗口改变时间值。
总结 这一章学习了SimVision图形环境: SimControl Navigator Signal Flow Browser(SFB)信号流浏览器 Watch Objects Windows
复习 问题 SimVision的五个主要基于窗口的元件是什么? 怎样显示设计层次? 怎样在你的设计中显示和监视一组信号?、 可以从观察对象窗口中在一个对象上设置一个断点? 怎样确定一个驱动的作用信号? 解答: SimVision的五个主要基于窗口的元件为SimControl,Navigator,Watch Objects,Signal Flow Browser和SignalScan 波形观察器。 可以用navigator显示并在设计层次中浏览。Navigator显示范围,对象和对象值。可以在Source Browser中显示每个范围的源代码。 可以用对象观察窗口查看一组信号及其值。 是的,可以从对象观察窗口在一个对象上设置一个断点,或可以用Find Next Edge按钮高效地设置一个断点,仿真并去除断点。 可以通过用信号流浏览器跟踪一个驱动的作用信号来找出这些信号。这在确定一个问题信号的源头很有用。可以反向跟踪驱动及其作用信号直到发现与预期行为不同信号。