怎样做好Verilog HDL语言设计实验
计算机系统(PC机)
系统配置的基本需求:
CPU: Intel奔腾系列,或AMD Athlon/XP;
操作系统:Windowns NT/2000/XP;
内存:256M或以上;
显卡:支持256色的8位显卡或以上;
硬盘:20G以上。
Altera® Quartus® II设计软件提供完整的多平台设计环境,它可以轻易满足
特定设计的需要。它是可编程片上系统 (SOPC) 设计的综合性环境。Quartus
II 软件拥有 FPGA 和 CPLD 设计的所有阶段的解决方案。
简单的组合逻辑和时序逻辑的设计
- 01
实验目的 1、掌握基本组合逻辑电路的实现方法,掌握连续赋值语句和条件赋值语句的使用方法; 2、掌握基本时序逻辑电路的实现,掌握always语句的使用方法。
- 02
简单组合逻辑设计 描述组合逻辑电路通常利用assign连续赋值语句的数据流方式。连续赋值语句将值赋给线网(连续赋值不能为寄存器赋值)。连续赋值语句只要在右端表达式的操作数上有事件(事件为值的变化)发生时,表达式即被计算,如果结果值有变化,新结果就赋给左边的线网。 连续赋值的目标类型如下:1) 标量线网;2) 向量线网;3) 向量的常数型位选择;4) 向量的常数型部分选择;5) 上述类型的任意的拼接运算结果。 组合逻辑设计示例:可综合的数据比较器。它的功能是比较数据a与数据b,如果两个数据相同,则给出结果1,否则给出结果0。描述组合逻辑时常使用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 仿真波形(部分):
- 03
简单时序逻辑设计 在Verilog HDL中,相对于组合逻辑电路,时序逻辑电路也有规定的表述方式。在可综合的Verilog HDL模型,我们通常使用always块和 @(posedge clk)或 @(negedge clk)的结构来表述时序逻辑。
- 04
时序逻辑设计示例: 可综合的1/2分频器模型。
- 05
// half_clk.v:
- 06
module half_clk(reset,clk_in,clk_out);
- 07
input clk_in,reset;
- 08
output clk_out;
- 09
reg clk_out;
- 10
always @(posedge clk_in)
- 11
begin
- 12
if(!reset) clk_out=0;
- 13
else
- 14
clk_out=~clk_out;
- 15
end
- 16
endmodule
- 17
在always块中,被赋值的信号都必须定义为reg型,这是由时序逻辑电路的特点所决定的。对于reg型数据,如果未对它进行赋值,仿真工具会认为它是不定态。为了能正确地观察到仿真结果,在可综合风格的模块中我们通常定义一个复位信号reset,当reset为低电平时,对电路中的寄存器进行复位。
- 18
仿真波形:
复杂时序逻辑电路的设计
- 01
实验目的 1、掌握条件语句if…else在复杂时序逻辑设计中的应用; 2、掌握阻塞赋值与非阻塞赋值的概念和区别,掌握阻塞赋值的使用情况。
- 02
条件语句在Verilog HDL中的使用 与常用的高级程序语言一样,为了描述较为复杂的时序关系,Verilog HDL提供了条件语句供分支判断时使用。在可综合风格的Verilog HDL模型中常用的条件语句有if…else和case…endcase两种结构,用法和C程序语言中类似。两者相较,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 仿真波形:
- 03
设计时序逻辑时采用阻塞赋值与非阻塞赋值的区别 非阻塞赋值语句(<=)右端表达式计算完后并不立即赋值给左端,而是同时启动下一条语句继续执行,而阻塞赋值语句(=)在每个右端表达式计算完成后立即赋值给左端变量。非阻塞赋值不能用于assign语句中,而只能用于对寄存器型变量进行赋值,只能在intial和always等过程过程块中。阻塞赋值既能用于assign语句,也能用于intial和always等过程赋值中。对于时序逻辑的描述和建模,应尽量使用非阻塞赋值方式。若在同一个always过程块中描述时序和组合逻辑混合电路时也最好使用非阻塞赋值方式。 在always块中,阻塞赋值可以理解为赋值语句是顺序执行的,而非阻塞赋值可以理解为赋值语句是并发执行的。实际的时序逻辑设计中,一般的情况下非阻塞赋值语句被更多地使用,有时为了在同一周期实现相互关联的操作,也使用了阻塞赋值语句。 可综合设计中阻塞赋值和非阻塞赋值设计要点: 1)非阻塞赋值不能用于assign持续赋值中,一般只出现在initial和always等过程块中,对reg型变量进行赋值; 2)当用always块来描述组合逻辑时,既可以用阻塞赋值,也可以用非阻塞赋值。但在同一个过程块中,最好不要同时用阻塞赋值和非阻塞赋值; 3)在向函数funtion的返回值赋值时,应使用阻塞赋值;
- 04
4)不能在一个以上的always过程块中对同一个变量赋值,这样会引起冲突,在综合时会报错;
- 05
5)在一个模块中,严禁对同一个变量既进行阻塞赋值,又进行非阻塞赋值,这样会在综合时报错;
- 06
6)对时序逻辑的描述和建模,应尽量使用非阻塞赋值方式,此外,若在同一个always过程块中描述时序和组合逻辑混合电路时,最好使用非阻塞赋值方式。
- 07
阻塞赋值与非阻塞赋值示例:分别采用阻塞赋值语句和非阻塞赋值语句的两个看上去非常相似的两个模块blocking.v和non_blocking.v来阐明两者之间的区别。
- 08
模块源代码:
- 09
// ------------- blocking.v ---------------
- 10
module blocking(clk,a,b,c);
- 11
output [3:0] b,c;
- 12
input [3:0] a;
- 13
input clk;
- 14
reg [3:0] b,c;
- 15
always @(posedge clk)
- 16
begin
- 17
b = a;
- 18
c = b;
- 19
$display("Blocking: a = %d, b = %d, c = %d.",a,b,c);
- 20
end
- 21
endmodule
- 22
仿真波形(部分):
- 23
//------------- non_blocking.v -------------------
- 24
module non_blocking(clk,a,b,c);
- 25
output [3:0] b,c;
- 26
input [3:0] a;
- 27
input clk;
- 28
reg [3:0] b,c;
- 29
always @(posedge clk)
- 30
begin
- 31
b <= a;
- 32
c <= b;
- 33
$display("Non_Blocking: a = %d, b = %d, c = %d.",a,b,c);
- 34
end
- 35
endmodule
- 36
仿真波形(部分):
函数和任务的应用设计
- 01
实验目的 1、掌握函数在模块设计中的使用; 2、掌握任务在结构化Verilog HDL设计中的应用。
- 02
函数在模块设计中的使用 函数可以在模块不同位置执行共同代码。函数只能返回一个值,它不能包含任何时延或时序控制(必须立即执行),并且它不能调用其它的任务。 函数必须带有至少一个输入,在函数中允许没有输出或输入输出说明。函数可以调用其它的函数。 函数说明部分可以在模块说明中的任何位置出现,函数的输入是由输入说明指定。如果函数说明部分中没有指定函数取值范围,则其缺省的函数值为1位二进制数。函数定义在函数内部隐式地声明一个寄存器变量,该寄存器变量与函数同名并且取值范围相同。函数通过在函数定义中显式地对该寄存器赋值来返回函数值。对这一寄存器的赋值必须出现在函数定义中。 函数调用是表达式的一部分。与任务相似,函数定义中声明的所有局部寄存器都是静态的,即函数中的局部寄存器在函数的多个调用之间保持它们的值。 下例是函数调用的一个简单示范,采用同步时钟触发运算的执行,每个clk时钟周期都会执行一次运算。并且在测试模块中,通过调用系统任务$display在时钟的下降沿显示每次计算的结果。 模块源代码: module tryfunct(clk,result,reset); output[31:0] result; input reset,clk; reg[31:0] result; always @(posedge clk) //clk的上沿触发同步运算。 begin if(!reset) //reset为低时复位。 result<=0; else begin result <= factorial(3); end end function [31:0] factorial; //函数定义。 input [3:0] operand; reg [3:0] index; begin factorial = operand ? 1 : 0; for(index = 2; index <= operand; index = index + 1) factorial = index * factorial; end endfunction endmodule 上例中函数factorial(n)实际上就是阶乘运算。必须提醒大家注意的是,在实际的设计中,我们不希望设计中的运算过于复杂,以免在综合后带来不可预测的后果。经常的情况是,我们把复杂的运算分成几个步骤,分别在不同的时钟周期完成。
- 03
任务在结构化设计中的应用 一个任务就像一个过程,它可以从描述的不同位置执行共同的代码段。共同的代码段用任务定义编写成任务,这样它就能够从设计描述的不同位置通过任务调用被调用。任务可以包含时序控制,即时延控制,并且任务也能调用其它任务和函数。 任务可以没有或有一个或多个参数。值通过参数传入和传出任务。除输入参数外(参数从任务中接收值),任务还能带有输出参数(从任务中返回值)和输入输出参数。任务的定义在模块说明部分中编写。 任务调用: 一个任务由任务调用语句调用。任务调用语句给出传入任务的参数值和接收结果的变量值;
- 04
任务调用语句是过程性语句,可以在always 语句或initial 语句中使用;
- 05
任务调用语句中参数列表必须与任务定义中的输入、输出和输入输出参数说明的顺序匹配。此外,参数要按值传递,不能按地址传递;
- 06
任务能够包含定时控制,任务可在被调用后再经过一定时延才返回值;
- 07
任务调用语句是过程性语句,所以任务调用中的输出和输入输出参数必须是寄存器类型的;
- 08
任务可以带有时序控制,或等待特定事件的发生。但是,输出参数的值直到任务退出时才传递给调用参数。
- 09
实例应用:利用task和电平敏感的always块设计比较后重组信号的组合逻辑。可以看到,利用task非常方便地实现了数据之间的交换,如果要用函数实现相同的功能是非常复杂的;另外,task也避免了直接用一般语句来描述所引起的不易理解和综合时产生冗余逻辑等问题。
- 10
模块源代码:
- 11
//----------------- sort4.v ------------------
- 12
module sort4(ra,rb,rc,rd,a,b,c,d);
- 13
output[3:0] ra,rb,rc,rd;
- 14
input[3:0] a,b,c,d;
- 15
reg[3:0] ra,rb,rc,rd;
- 16
reg[3:0] va,vb,vc,vd;
- 17
always @ (a or b or c or d)
- 18
begin
- 19
{va,vb,vc,vd}={a,b,c,d};
- 20
sort2(va,vc); //va 与vc互换。
- 21
sort2(vb,vd); //vb 与vd互换。
- 22
sort2(va,vb); //va 与vb互换。
- 23
sort2(vc,vd); //vc 与vd互换。
- 24
sort2(vb,vc); //vb 与vc互换。
- 25
{ra,rb,rc,rd}={va,vb,vc,vd};
- 26
end
- 27
task sort2;
- 28
inout[3:0] x,y;
- 29
reg[3:0] tmp;
- 30
if(x>y)
- 31
begin
- 32
tmp=x; //x与y变量的内容互换,要求顺序执行,所以采用阻塞赋值方式。
- 33
x=y;
- 34
y=tmp;
- 35
end
- 36
endtask
- 37
endmodule
- 38
值得注意的是task中的变量定义与模块中的变量定义不尽相同,它们并不受输入输出类型的限制。如此例,x与y对于task sort2来说虽然是inout型,但实际上它们对应的是always块中变量,都是reg型变量。
- 39
仿真波形(部分):
有限状态机(FSM)的设计
- 01
实验目的 1、了解FSM的应用范围和两种类型的不同特点; 2、掌握FSM的电路结构和设计要点。
- 02
FSM相关概念和设计要点 FSM适合于设计数字系统的控制模块。用Verilog HDL的case/if-else等语句能很好地描述基于状态机的设计。状态机可以认为是组合逻辑和时序逻辑的特殊组合。时序逻辑部分用于存贮状态,组合电路用于状态译码和产生输出信号。状态机的下一个状态不仅与输入信号有关,还与状态寄存器当前所处的状态有关。 状态机分为Mealy和Moore两种类型,前者的输出只是当前状态的函数,也就是状态的转换与输入信号无关,后者的输出则是当前状态和当前输入的函数,即状态的转换与输入信号有关。 状态机的表示方法:状态图、状态表和流程图。 状态机设计要点 一般采用同步时序方式,在时钟信号的触发下完成状态转移; 起始状态是指电路复位后所处的状态,选择一个合理的起始状态将使整个系统简洁、高效,有的综合器会自动为状态机的设计选择一个最佳的起始状态; 状态编码的方式主要有二进制编码、格雷编码和一位热码编码等: • 二进制编码(Binary State Machine):采用普通的二进制数代表每个状态,如4个状态state0/state1/state2/state3对应编码为00/01/10/11。这种编码的缺点是相邻状态的转换有可能有多个比特发生变化(如01->10),瞬变次数多,容易产生毛刺,引起逻辑错误; • 格雷编码(Gray Code State Machine): 4个状态state0/state1/state2/state3对应编码为00/01/11/10即为格雷编码方式。格雷码节省逻辑单元,而且在状态的顺序转换中相邻状态每次只有一个比特发生变化,这样减少了瞬变的次数,也减少了产生毛刺和一些暂态的可能。 • 一位热码编码(One-Hot State Machine Encoding):采用n位来编码具有n个状态的状态机 。如4个状态state0/state1/state2/state3对应编码为1000/0100/0010/0001。采用一位热码编码,虽然多用了触发器,但可以有效节省和简化组合电路。对于寄存器数量多而门逻辑相对缺乏的FPGA来说,采用一位热码编码可以有效地提高电路地速度和可靠性,也有利于提高器件资源的利用率。因此,对于FPGA器件,建议采用这种编码方式。采用这种编码会出现一些多余的状态,即一些无效的状态,需要增加default分支项,以便这些状态下能自动回到起始状态。 状态编码的定义可以用parameter或者`define语句。建议采用前者。尽量采用同步电路设计; `define state0 2’b00 //不要加分号 `define state1 2’b01 …. case (state) `state0: …; //不能少“`” …. 状态转换一般使用case/casex/casez描述。Case语句比if-else语句更清晰明了。此外,case语句的最后不要忘了default分支语句以避免锁存器的产生。
- 03
状态机设计实例 功能是检测一个5位二进制序列“10010”。考虑到序列重叠的可能,有限状态机共提供8个状态(包括初始状态IDLE)。 模块源代码: //seqdet.v module seqdet(x,z,clk,rst,state); input x,clk,rst; output z; output[2:0] state; reg[2:0] state; wire z; parameter IDLE='d0, A='d1, B='d2,C='d3, D='d4,E='d5, F='d6,G='d7; assign z = ( state==E && x==0 )? 1 : 0; //当x=0时,状态已变为E, //状态为D时,x仍为1。因此 //输出为1的条件为( state==E && x==0 )。 always @(posedge clk) if(!rst) begin state <= IDLE; end else casex(state) IDLE : if(x==1) begin state <= A; end A: if(x==0) begin state <= B; end
- 04
B: if(x==0)
- 05
begin
- 06
state <= C;
- 07
end
- 08
else
- 09
begin
- 10
state <= F;
- 11
end
- 12
C: if(x==1)
- 13
begin
- 14
state <= D;
- 15
end
- 16
else
- 17
begin
- 18
state <= G;
- 19
end
- 20
D: if(x==0)
- 21
begin
- 22
state <= E;
- 23
end
- 24
else
- 25
begin
- 26
state <= A;
- 27
end
- 28
E: if(x==0)
- 29
begin
- 30
state <= C;
- 31
end
- 32
else
- 33
begin
- 34
state <= A;
- 35
end
- 36
F: if(x==1)
- 37
begin
- 38
state <= A;
- 39
end
- 40
else
- 41
begin
- 42
state <= B;
- 43
end
- 44
G: if(x==1)
- 45
begin
- 46
state <= F;
- 47
end
- 48
default:state=IDLE; //缺省状态为初始状态。
- 49
endcase
- 50
endmodule
- 51
仿真波形:
基于模块多层次引用的结构化电路设计
- 01
实验目的 1、了解复杂电路与系统的“top-down”设计思想; 2、掌握简单多层次电路的描述方法。
- 02
多层次结构电路的设计 复杂数字系统可采用“top-down”的方法进行设计:首先把系统分为几个模块,每个模块在分为几个子模块,依次类推,知道易于实现为止。 这种“top-down”的方法能够把复杂的设计分为许多简单的设计来实现,同时也适合于多人进行合作开发。 多层次结构电路的描述既可以采用文本方式,也可以采用图形和文本混合设计的方式。 被调用模块的指定方式:1)文件复制方式;2)使用`include语句;3)库管理方式。
- 03
设计实例分析 这个实例的功能是将并行数据转化为串行数据送交外部电路编码,并将解码后得到的串行数据转化为并行数据交由CPU处理。显而易见,这实际上是两个独立的逻辑功能,分别设计为独立的模块,然后再合并为一个模块显得目的明确、层次清晰。 模块源代码 // ---------------- p_to_s.v --------------------------------- module p_to_s(D_in,T0,data,SEND,ESC,ADD_100); output D_in,T0; // D_in是串行输出,T0是移位时钟并给 // CPU中断,以确定何时给出下个数据。 input [7:0] data; //并行输入的数据。 input SEND,ESC,ADD_100; //SEND、ESC共同决定是否进行并到串 //的数据转化。ADD_100决定何时置数。 wire D_in,T0; reg [7:0] DATA_Q,DATA_Q_buf; assign T0 = ! (SEND & ESC); //形成移位时钟。. assign D_in = DATA_Q[7]; //给出串行数据。 always @(posedge T0 or negedge ADD_100) //ADD_100下沿置数,T0上沿移位。 begin if(!ADD_100) DATA_Q = data; else begin DATA_Q_buf = DATA_Q<<1; //DATA_Q_buf作为中介,以令综合器 DATA_Q = DATA_Q_buf; //能辨明。 end end endmodule 仿真波形: 在p_to_s.v中,由于移位运算虽然可综合,但是不是简单的RTL级描述,直接用DATA_Q<=DATA_Q<<1的写法在综合时会令综合器产生误解。另外,在该设计中,由于时钟T0的频率较低,所以没有象以往那样采用低电平置数,而是采用ADD_100的下降沿置数。 //--------------------- s_to_p.v ---------------------------
- 04
module s_to_p(T1, data, D_out,DSC,TAKE,ADD_101);
- 05
output T1; //给CPU中断,以确定CPU何时取转化
- 06
//得到的并行数据。
- 07
output [7:0] data;
- 08
input D_out, DSC, TAKE, ADD_101; //D_out提供输入串行数据。DSC、TAKE
- 09
//共同决定何时取数。
- 10
wire [7:0] data;
- 11
wire T1,clk2;
- 12
reg [7:0] data_latch, data_latch_buf;
- 13
assign clk2 = DSC & TAKE ; //提供移位时钟。
- 14
assign T1 = !clk2;
- 15
assign data = (!ADD_101) ? data_latch : 8'bz;
- 16
always @(posedge clk2)
- 17
begin
- 18
data_latch_buf = data_latch << 1; //data_latch_buf作缓冲
- 19
data_latch = data_latch_buf; //,以令综合器能辩明。
- 20
data_latch[0] = D_out;
- 21
end
- 22
endmodule
- 23
仿真波形:
- 24
将上面的两个模块合并起来的sys.v的源代码:
- 25
//------------------- sys.v ---------------------------
- 26
`include "./p_to_s.v"
- 27
`include "./s_to_p.v"
- 28
module sys(D_in,T0,T1, data, D_out,SEND,ESC,DSC,TAKE,ADD_100,ADD_101);
- 29
input D_out,SEND,ESC,DSC,TAKE,ADD_100,ADD_101;
- 30
inout [7:0] data;
- 31
output D_in,T0,T1;
- 32
p_to_s p_to_s(.D_in(D_in),.T0(T0),.data(data),.SEND(SEND),.ESC(ESC),.ADD_100(ADD_100));
- 33
s_to_p s_to_p(.T1(T1),.data(data),.D_out(D_out),.DSC(DSC),.TAKE(TAKE),.ADD_101(ADD_101));
- 34
endmodule
- 35
仿真波形: