第十章.设计练习进阶 设计练习进阶 前言: 在前面九章学习的基础上,通过本章十个阶段的练习,一定能逐步掌握 Verilog hdl设计的 要点。我们可以先理解样板模块中每一条语句的作用,然后对样板模块进行综合前和综合后 仿真,再独立完成每一阶段规定的练习。当十个阶段的练习做完后,便可以开始设计一些简 单的逻辑电路和系统。很快我们就能过渡到设计相当复杂的数字逻辑系统。当然,复杂的数 字逻辑系统的设计和验证,不但需要系统结构的知识和经验的积累,还需要了解更多的语法 现象和掌握高级的 Verilog hdl系统任务,以及与C语言模块接口的方法(即PLI),这些已 超出的本书的范围。有兴趣的同学可以阅读 Verilog语法参考资料和有关文献,自己学习, 我们将在下一本书中介绍 Verilog较高级的用法。 练习一.简单的组合逻辑设计 目的:掌握基本组合逻辑电路的实现方法。 这是一个可综合的数据比较器,很容易看出它的功能是比较数据a与数据b,如果两个 数据相同,则给出结果1,否则给出结果0。在 Verilog HDl中,描述组合逻辑时常使用 assign 结构。注意 equal=(a==b)?1:0,这是一种在组合逻辑实现分支判断时常使用的格式。 模块源代码: ompare. V module compare(equal, a, b) input a, b: output equal ssign equal=(a==b)?1:0;//a等于b时, equal输出为1;a不等于b时, // equal输出为0。 endmodule 测试模块用于检测模块设计得正确与否,它给出模块的输入信号,观察模块的内部信号 和输出信号,如果发现结果与预期的有所偏差,则要对设计模块进行修改。 测试模块源代码: timescale Ins/Ins /定义时间单位。 include"./ compare.v"//包含模块文件。在有的仿真调试环境中并不需要此语句 //而需要从调试环境的菜单中键入有关模块文件的路径和名称 omparetest wire equal initial // initial常用于仿真时信号的给出。 266
第十章.设计练习进阶 设计练习进阶 前言: 在前面九章学习的基础上, 通过本章十个阶段的练习,一定能逐步掌握 Verilog HDL 设计的 要点。我们可以先理解样板模块中每一条语句的作用,然后对样板模块进行综合前和综合后 仿真,再独立完成每一阶段规定的练习。当十个阶段的练习做完后,便可以开始设计一些简 单的逻辑电路和系统。很快我们就能过渡到设计相当复杂的数字逻辑系统。当然,复杂的数 字逻辑系统的设计和验证,不但需要系统结构的知识和经验的积累,还需要了解更多的语法 现象和掌握高级的 Verilog HDL 系统任务,以及与 C 语言模块接口的方法(即 PLI),这些已 超出的本书的范围。有兴趣的同学可以阅读 Verilog 语法参考资料和有关文献,自己学习, 我们将在下一本书中介绍 Verilog 较高级的用法。 练习一.简单的组合逻辑设计 目的: 掌握基本组合逻辑电路的实现方法。 这是一个可综合的数据比较器,很容易看出它的功能是比较数据 a 与数据 b,如果两个 数据相同,则给出结果 1,否则给出结果 0。在 Verilog HDL 中,描述组合逻辑时常使用 assign 结构。注意 equal=(a==b)?1:0,这是一种在组合逻辑实现分支判断时常使用的格式。 模块源代码: //--------------- compare.v ----------------- module compare(equal,a,b); input a,b; output equal; assign equal=(a==b)?1:0; //a 等于 b 时,equal 输出为 1;a 不等于 b 时, //equal 输出为 0。 endmodule 测试模块用于检测模块设计得正确与否,它给出模块的输入信号,观察模块的内部信号 和输出信号,如果发现结果与预期的有所偏差,则要对设计模块进行修改。 测试模块源代码: `timescale 1ns/1ns //定义时间单位。 `include "./compare.v" //包含模块文件。在有的仿真调试环境中并不需要此语句。 //而需要从调试环境的菜单中键入有关模块文件的路径和名称 module comparetest; reg a,b; wire equal; initial //initial 常用于仿真时信号的给出。 266
第十章.设计练习进阶 begil #100 #100 Sstop /系统任务,暂停仿真以便观察仿真波形。 compare compare( equal(equa1),a(a),.b(b));//调用模块 endmodule 仿真波形(部分) /comparetest/equal 练习 设计一个字节(8位)比较器。 要求:比较两个字节的大小,如a[7:0]大于b[7:0]输出高电平,否则输出低电平,改写测试 模型,使其能进行比较全面的测试。 练习二.简单时序逻辑电路的设计 目的:掌握基本时序逻辑电路的实现。 在 Verilog hDl中,相对于组合逻辑电路,时序逻辑电路也有规定的表述方式。在可综 合的 Verilog甽DL模型,我们通常使用 always块和@( posedge clk)或@( negedge clk)的结 构来表述时序逻辑。下面是一个1/2分频器的可综合模型 / half clk.v module half clk(reset, clk in, clk out) In. rese output clk out reg clk out lways @(posedge clk in)
第十章.设计练习进阶 begin a=0; b=0; #100 a=0; b=1; #100 a=1; b=1; #100 a=1; b=0; #100 $stop; //系统任务,暂停仿真以便观察仿真波形。 end compare compare1(.equal(equal),.a(a),.b(b)); //调用模块。 endmodule 仿真波形(部分): 练习: 设计一个字节(8 位)比较器。 要求:比较两个字节的大小,如 a[7:0]大于 b[7:0]输出高电平,否则输出低电平,改写测试 模型,使其能进行比较全面的测试 。 练习二. 简单时序逻辑电路的设计 目的:掌握基本时序逻辑电路的实现。 在 Verilog HDL 中,相对于组合逻辑电路,时序逻辑电路也有规定的表述方式。在可综 合的 Verilog HDL 模型,我们通常使用 always 块和 @(posedge clk)或 @(negedge clk)的结 构来表述时序逻辑。下面是一个 1/2 分频器的可综合模型。 // half_clk.v: module half_clk(reset,clk_in,clk_out); input clk_in,reset; output clk_out; reg clk_out; always @(posedge clk_in) 267
第十章.设计练习进阶 begi f(reset) clk out=0: comodule 在 always块中,被赋值的信号都必须定义为reg型,这是由时序逻辑电路的特点所决定的。 对于reg型数据,如果未对它进行赋值,仿真工具会认为它是不定态。为了能正确地观察到 仿真结果,在可综合风格的模块中我们通常定义一个复位信号 reset,当 reset为低电平时, 对电路中的寄存器进行复位 测试模块的源代码: timescale Ins/100ps define clk cycle 50 module clk Top.v reg clk wire clk out always clk cycle clk clk #100 reset =0. #100 reset 1 #10000 half clk half clk(. reset(reset). clk in(clk, clk out(clk out)) 仿真波形: /clk Top/clk in /clk_ Top/reset /clk_ Top/clk_out
第十章.设计练习进阶 begin if(!reset) clk_out=0; else clk_out=~clk_out; end endmodule 在 always 块中,被赋值的信号都必须定义为 reg 型,这是由时序逻辑电路的特点所决定的。 对于 reg 型数据,如果未对它进行赋值,仿真工具会认为它是不定态。为了能正确地观察到 仿真结果,在可综合风格的模块中我们通常定义一个复位信号 reset,当 reset 为低电平时, 对电路中的寄存器进行复位。 测试模块的源代码: //------------------- clk_Top.v ----------------------------- `timescale 1ns/100ps `define clk_cycle 50 module clk_Top.v reg clk,reset; wire clk_out; always #`clk_cycle clk = ~clk; initial begin clk = 0; reset = 1; #100 reset = 0; #100 reset = 1; #10000 $stop; end half_clk half_clk(.reset(reset),.clk_in(clk),.clk_out(clk_out)); endmodule 仿真波形: 268
第十章.设计练习进阶 练习:依然作 clk in的二分频 clk out,要求输出与上例的输出正好反相。编写测试模块, 给出仿真波形 练习三.利用条件语句实现较复杂的时序逻辑电路 目的:掌握条件语句在 Verilog HDL中的使用。 与常用的高级程序语言一样,为了描述较为复杂的时序关系, Verilog hDl提供了条件语 句供分支判断时使用。在可综合风格的 Verilog hdl模型中常用的条件语句有if.else和 case. endcase两种结构,用法和C程序语言中类似。两者相较,if..else用于不很复杂的 分支关系,实际编写可综合风格的模块、特别是用状态机构成的模块时,更常用的是 case. endcase风格的代码。这一节我们给的是有关if.else的范例,有关 case. endcase 结构的代码已后会经常用到 下面给出的范例也是一个可综合风格的分频器,是将10M的时钟分频为500K的时钟。基 本原理与1/2分频器是一样的,但是需要定义一个计数器,以便准确获得1/20分频 模块源代码: division v module division (RESeT, F10M, F500K) input FlOM, RESET output F500K: reg F500K [7:0]j lways @(posedge F10M) if(! rESET) /低电平复位。 b F500K<=0 f(j=19) //对计数器进行判断,以确定F500K信号是否反转。 begin F500K<=F500K modul
第十章.设计练习进阶 练习:依然作 clk_in 的二分频 clk_out,要求输出与上例的输出正好反相。编写测试模块, 给出仿真波形。 练习三. 利用条件语句实现较复杂的时序逻辑电路 目的:掌握条件语句在 Verilog HDL 中的使用。 与常用的高级程序语言一样,为了描述较为复杂的时序关系,Verilog HDL 提供了条件语 句供分支判断时使用。在可综合风格的 Verilog HDL 模型中常用的条件语句有 if…else 和 case…endcase 两种结构,用法和 C 程序语言中类似。两者相较,if…else 用于不很复杂的 分支关系,实际编写可综合风格的模块、特别是用状态机构成的模块时,更常用的是 case…endcase 风格的代码。这一节我们给的是有关 if…else 的范例,有关 case…endcase 结构的代码已后会经常用到。 下面给出的范例也是一个可综合风格的分频器,是将 10M 的时钟分频为 500K 的时钟。基 本原理与 1/2 分频器是一样的,但是需要定义一个计数器,以便准确获得 1/20 分频 模块源代码: // --------------- fdivision.v ----------------------------- module fdivision(RESET,F10M,F500K); input F10M,RESET; output F500K; reg F500K; reg [7:0]j; always @(posedge F10M) if(!RESET) //低电平复位。 begin F500K <= 0; j <= 0; end else begin if(j==19) //对计数器进行判断,以确定 F500K 信号是否反转。 begin j <= 0; F500K <= ~F500K; end else j <= j+1; end endmodule 269
第十章.设计练习进阶 测试模块源代码: / division Top.v timescale Ins/100p define clk cycle 50 module division to reg F10M clk, RESET wire F500K clk always clk cycle Flom cll FloM clk initial begin F1M=0 #100 RESET=0: #100 RESET=l #10000 Sstop: division fdivision ( RESET (RESET), F10M (F10M clk),. F500K(F500K clk)) endmodule 仿真波形: /division_ Top/F10M uuuL mauguin division_ Top/RESET 练习:利用10M的时钟,设计一个单周期形状如下的周期波形。 20μs 10H s 20μ 练习四.设计时序逻辑时采用阻塞赋值与非阻塞赋值的区别 0
第十章.设计练习进阶 测试模块源代码: //--------------- fdivision_Top.v ------------------------ `timescale 1ns/100ps `define clk_cycle 50 module division_Top; reg F10M_clk,RESET; wire F500K_clk; always #`clk_cycle F10M_clk = ~ F10M_clk; initial begin RESET=1; F10M=0; #100 RESET=0; #100 RESET=1; #10000 $stop; end fdivision fdivision (.RESET(RESET),.F10M(F10M_clk),.F500K(F500K_clk)); endmodule 仿真波形: 练习:利用 10M 的时钟,设计一个单周期形状如下的周期波形。 20μs 10μs 20μs 0 T 练习四. 设计时序逻辑时采用阻塞赋值与非阻塞赋值的区别 270
第十章.设计练习进阶 目的:1.明确掌握阻塞赋值与非阻塞赋值的概念和区别 2.了解阻塞赋值的使用情况 阻塞赋值与非阻塞赋值,在教材中我们已经了解了它们之间在语法上的区别以及综合后 所得到的电路结构上的区别。在 always块中,阻塞赋值可以理解为赋值语句是顺序执行的, 而非阻塞赋值可以理解为赋值语句是并发执行的。实际的时序逻辑设计中,一般的情况下非 阻塞赋值语句被更多地使用,有时为了在同一周期实现相互关联的操作,也使用了阻塞赋值 语句。(注意:在实现组合逻辑的 assign结构中,无一例外地都必须采用阻塞赋值语句。 下例通过分别采用阻塞赋值语句和非阻塞赋值语句的两个看上去非常相似的两个模块 blocking.v和non_ blocking.v来阐明两者之间的区别。 模块源代码 blockingv module blocking(clk, a, b, c) output [3: 0] b,c: input [3: 0] a: clk reg [3: 0] b,c always @(posedge clk) b Sdisplay( blocking: a =%d, b= %d, c=%d",a, b, c) endmodule non blocking v module non blocking(clk, a, b,c) output [3: 0]b,c: [3:0] t clk reg [3: 0]b, c l ways @(posedge clk begin b <e Sdisplay non blocking: a =%d, b=%d, c=%d", a, b, c)
第十章.设计练习进阶 目的:1.明确掌握阻塞赋值与非阻塞赋值的概念和区别; 2.了解阻塞赋值的使用情况。 阻塞赋值与非阻塞赋值,在教材中我们已经了解了它们之间在语法上的区别以及综合后 所得到的电路结构上的区别。在 always 块中,阻塞赋值可以理解为赋值语句是顺序执行的, 而非阻塞赋值可以理解为赋值语句是并发执行的。实际的时序逻辑设计中,一般的情况下非 阻塞赋值语句被更多地使用,有时为了在同一周期实现相互关联的操作,也使用了阻塞赋值 语句。(注意:在实现组合逻辑的 assign 结构中,无一例外地都必须采用阻塞赋值语句。 下例通过分别采用阻塞赋值语句和非阻塞赋值语句的两个看上去非常相似的两个模块 blocking.v 和 non_blocking.v 来阐明两者之间的区别。 模块源代码: // ------------- blocking.v --------------- module blocking(clk,a,b,c); output [3:0] b,c; input [3:0] a; input clk; reg [3:0] b,c; always @(posedge clk) begin b = a; c = b; $display("Blocking: a = %d, b = %d, c = %d.",a,b,c); end endmodule //------------- non_blocking.v ------------------- module non_blocking(clk,a,b,c); output [3:0] b,c; input [3:0] a; input clk; reg [3:0] b,c; always @(posedge clk) begin b <= a; c <= b; $display("Non_Blocking: a = %d, b = %d, c = %d.",a,b,c); end 271
第十章.设计练习进阶 endmodule 测试模块源代码: compare lop.V timescale Ins/100ps include ./blockingv include ./non blockingv module compare Top wIre [3:0]bl,cl,b2,c2; reg initial cIk =0 forever #50 clk = clk nitia begin Display 4’h7 Display( #100 4 hf Display c Display ) 100 Display non blocking non blocking(clk, a, b2, c2) blocking(clk, a, bl, c1) endmodule 272
第十章.设计练习进阶 endmodule 测试模块源代码: //------------- compareTop.v ----------------------------- `timescale 1ns/100ps `include "./blocking.v" `include "./non_blocking.v" module compareTop; wire [3:0] b1,c1,b2,c2; reg [3:0] a; reg clk; initial begin clk = 0; forever #50 clk = ~clk; end initial begin a = 4'h3; $display("____________________________"); # 100 a = 4'h7; $display("____________________________"); # 100 a = 4'hf; $display("____________________________"); # 100 a = 4'ha; $display("____________________________"); # 100 a = 4'h2; $display("____________________________"); # 100 $display("____________________________"); $stop; end non_blocking non_blocking(clk,a,b2,c2); blocking blocking(clk,a,b1,c1); endmodule 272
第十章.设计练习进阶 仿真波形(部分) 1111 /compare Top/c1 /compare Top/b2 0011 0111 Y1111 compare Top/c2 0011 0111 11 思考:在 blocking模块中按如下写法,仿真与综合的结果会有什么样的变化?作出仿真 波形,分析综合结果 always @(posedge clk lways @(posedge clk)b=a always @(posedge clk)c= 练习五.用 always块实现较复杂的组合逻辑电路 目的:1.掌握用 always实现组合逻辑电路的方法; 2.了解 assign与 always两种组合逻辑电路实现方法之间的区别。 仅使用 assign结构来实现组合逻辑电路,在设计中会发现很多地方会显得冗长且效率低 下。而适当地采用 always来设计组合逻辑,往往会更具实效。已进行的范例和练习中,我们 仅在实现时序逻辑电路时使用 always块。从现在开始,我们对它的看法要稍稍改变。 下面是一个简单的指令译码电路的设计示例。该电路通过对指令的判断,对输入数据执 行相应的操作,包括加、减、与、或和求反,并且无论是指令作用的数据还是指令本身发生 变化,结果都要作出及时的反应。显然,这是一个较为复杂的组合逻辑电路,如果采用 assign 语句,表达起来非常复杂。示例中使用了电平敏感的 always块,所谓电平敏感的触发条件是 指在@后的括号内电平列表中的任何一个电平发生变化,(与时序逻辑不同,它在@后的括号内 没有沿敏感关键词,如 posedge或 negedge)就能触发 always块的动作,并且运用了case 结构来进行分支判断,不但设计思想得到直观的体现,而且代码看起来非常整齐、便于理解。 define plus 3 do define minus3’d1 define band 3 d2 ne bor ne negate 3 d4 module alu(out, opcode, a, b)
第十章.设计练习进阶 仿真波形(部分): 思考:在 blocking 模块中按如下写法,仿真与综合的结果会有什么样的变化?作出仿真 波形,分析综合结果。 1. always @(posedge clk) begin c = b; b = a; end 2. always @(posedge clk) b=a; always @(posedge clk) c=b; 练习五. 用 always 块实现较复杂的组合逻辑电路 目的: 1.掌握用 always 实现组合逻辑电路的方法; 2.了解 assign 与 always 两种组合逻辑电路实现方法之间的区别。 仅使用 assign 结构来实现组合逻辑电路,在设计中会发现很多地方会显得冗长且效率低 下。而适当地采用 always 来设计组合逻辑,往往会更具实效。已进行的范例和练习中,我们 仅在实现时序逻辑电路时使用 always 块。从现在开始,我们对它的看法要稍稍改变。 下面是一个简单的指令译码电路的设计示例。该电路通过对指令的判断,对输入数据执 行相应的操作,包括加、减、与、或和求反,并且无论是指令作用的数据还是指令本身发生 变化,结果都要作出及时的反应。显然,这是一个较为复杂的组合逻辑电路,如果采用 assign 语句,表达起来非常复杂。示例中使用了电平敏感的 always 块,所谓电平敏感的触发条件是 指在@后的括号内电平列表中的任何一个电平发生变化,(与时序逻辑不同,它在@后的括号内 没有沿敏感关键词,如 posedge 或 negedge)就能触发 always 块的动作,并且运用了 case 结构来进行分支判断,不但设计思想得到直观的体现,而且代码看起来非常整齐、便于理解。 //--------------- alu.v -------------------------- `define plus 3'd0 `define minus 3'd1 `define band 3'd2 `define bor 3'd3 `define unegate 3'd4 module alu(out,opcode,a,b); 273
第十章.设计练习进阶 output[7: 0]out reg[7:0] input[7: 0] a, b //操作数。 always@( opcode or a or b)//电平敏感的 always块 case(opcode) a+b;//加操作 minus:out=a-b;//减操作。 band:out=a&b;//求与。 bor:out=a|b:/求或。 negate:out=a;//求反。 default:out=8'hx://未收到指令时,输出任意态。 endcase 同一组合逻辑电路分别用 always块和连续赋值语句 assign描述时,代码的形式大相径 庭,但是在 always中适当运用 default(在case结构中)和else(在if.else结构中), 通常可以综合为纯组合逻辑,尽管被赋值的变量一定要定义为reg型。不过,如果不使用 default或else对缺省项进行说明,则易生成意想不到的锁存器,这一点一定要加以注意 指令译码器的测试模块源代码: timescale Ins/Ins include "./alu module alutest e[7:0] 7:0]a,b parameter times=5 initial egin a= Random)%256; //Give a radom number blongs to [o, 255 b=Random //G opcode3 ho a=Random/%256: //Give a radom number. rando //Give a radom number 274
第十章.设计练习进阶 output[7:0] out; reg[7:0] out; input[2:0] opcode; input[7:0] a,b; //操作数。 always@(opcode or a or b) //电平敏感的 always 块 begin case(opcode) `plus: out = a+b; //加操作。 `minus: out = a-b; //减操作。 `band: out = a&b; //求与。 `bor: out = a|b; //求或。 `unegate: out=~a; //求反。 default: out=8'hx;//未收到指令时,输出任意态。 endcase end endmodule 同一组合逻辑电路分别用 always 块和连续赋值语句 assign 描述时,代码的形式大相径 庭,但是在 always 中适当运用 default(在 case 结构中)和 else(在 if…else 结构中), 通常可以综合为纯组合逻辑,尽管被赋值的变量一定要定义为 reg 型。不过,如果不使用 default 或 else 对缺省项进行说明,则易生成意想不到的锁存器,这一点一定要加以注意。 指令译码器的测试模块源代码: //------------- alu_Top.v ----------------- `timescale 1ns/1ns `include "./alu.v" module alutest; wire[7:0] out; reg[7:0] a,b; reg[2:0] opcode; parameter times=5; initial begin a={$random}%256; //Give a radom number blongs to [0,255] . b={$random}%256; //Give a radom number blongs to [0,255]. opcode=3'h0; repeat(times) begin #100 a={$random}%256; //Give a radom number. b={$random}%256; //Give a radom number. opcode=opcode+1; end 274
第十章.设计练习进阶 #100 Sstop end alu alul(out, opcode, a, b) 仿真波形(部分): /alutest/a 24 alutest/b81 alutest/opcode 练习:运用 always块设计一个八路数据选择器。要求:每路输入数据与输出数据均为4位2 进制数,当选择开关(至少3位)或输入数据发生变化时,输出数据也相应地变化 练习六.在 Ver i log HDL中使用函数 目的:掌握函数在模块设计中的使用 与一般的程序设计语言一样, Veirlog Hdl也可使用函数以适应对不同变量采取同一运 算的操作。 Veirlog hdl函数在综合时被理解成具有独立运算功能的电路,每调用一次函数 相当于改变这部分电路的输入以得到相应的计算结果。 下例是函数调用的一个简单示范,采用同步时钟触发运算的执行,每个clk时钟周期都 会执行一次运算。并且在测试模块中,通过调用系统任务$ display在时钟的下降沿显示每次 计算的结果。 模块源代码: module tryfunct(clk, n, result, reset) output[31: 0] result [3:0] t clk reg[31: 0] resul always@( posedge clk)//clk的上沿触发同步运算。 begin f(reset // reset为低时复 result=0 275
第十章.设计练习进阶 #100 $stop; end alu alu1(out,opcode,a,b); endmodule 仿真波形(部分): 练习:运用 always 块设计一个八路数据选择器。要求:每路输入数据与输出数据均为 4 位 2 进制数,当选择开关(至少 3 位)或输入数据发生变化时,输出数据也相应地变化。 练习六. 在 Verilog HDL 中使用函数 目的:掌握函数在模块设计中的使用。 与一般的程序设计语言一样,Veirlog HDL 也可使用函数以适应对不同变量采取同一运 算的操作。Veirlog HDL 函数在综合时被理解成具有独立运算功能的电路,每调用一次函数 相当于改变这部分电路的输入以得到相应的计算结果。 下例是函数调用的一个简单示范,采用同步时钟触发运算的执行,每个 clk 时钟周期都 会执行一次运算。并且在测试模块中,通过调用系统任务$display 在时钟的下降沿显示每次 计算的结果。 模块源代码: module tryfunct(clk,n,result,reset); output[31:0] result; input[3:0] n; input reset,clk; reg[31:0] result; always @(posedge clk) //clk 的上沿触发同步运算。 begin if(!reset) //reset 为低时复位。 result<=0; else begin 275