Verilog设计练习十例及答案.docx
《Verilog设计练习十例及答案.docx》由会员分享,可在线阅读,更多相关《Verilog设计练习十例及答案.docx(29页珍藏版)》请在冰豆网上搜索。
Verilog设计练习十例及答案
设计练习进阶
前言:
在前面九章学习得基础上,通过本章得练习,一定能逐步掌握VerilogHDL设计得要点。
我们可以先理解样板模块中每一条语句得作用,然后对样板模块进行综合前与综合后仿真,再独立完成每一阶段规定得练习。
当十个阶段得练习做完后,便可以开始设计一些简单得逻辑电路与系统。
很快我们就能过渡到设计相当复杂得数字逻辑系统。
当然,复杂得数字逻辑系统得设计与验证,不但需要系统结构得知识与经验得积累,还需要了解更多得语法现象与掌握高级得VerilogHDL系统任务,以及与C语言模块接口得方法(即PLI),这些已超出得本书得范围。
有兴趣得同学可以阅读Verilog语法参考资料与有关文献,自己学习,我们将在下一本书中介绍Verilog较高级得用法。
练习一.简单得组合逻辑设计
目得:
掌握基本组合逻辑电路得实现方法。
这就是一个可综合得数据比较器,很容易瞧出它得功能就是比较数据a与数据b,如果两个数据相同,则给出结果1,否则给出结果0。
在VerilogHDL中,描述组合逻辑时常使用assign结构。
注意equal=(a==b)?
1:
0,这就是一种在组合逻辑实现分支判断时常使用得格式。
模块源代码:
//pare、v
modulepare(equal,a,b);
inputa,b;
outputequal;
assignequal=(a==b)?
1:
0;//a等于b时,equal输出为1;a不等于b时,
//equal输出为0。
endmodule
测试模块用于检测模块设计得正确与否,它给出模块得输入信号,观察模块得内部信号与输出信号,如果发现结果与预期得有所偏差,则要对设计模块进行修改。
测试模块源代码:
`timescale1ns/1ns//定义时间单位。
`include"、/pare、v"//包含模块文件。
在有得仿真调试环境中并不需要此语句。
//而需要从调试环境得菜单中键入有关模块文件得路径与名称
moduleparetest;
rega,b;
wireequal;
initial//initial常用于仿真时信号得给出。
begin
a=0;
b=0;
#100a=0;b=1;
#100a=1;b=1;
#100a=1;b=0;
#100$stop;//系统任务,暂停仿真以便观察仿真波形。
end
parepare1(、equal(equal),、a(a),、b(b));//调用模块。
endmodule
仿真波形(部分):
练习:
设计一个字节(8位)比较器。
要求:
比较两个字节得大小,如a[7:
0]大于b[7:
0]输出高电平,否则输出低电平,改写测试模型,使其能进行比较全面得测试。
练习二、简单时序逻辑电路得设计
目得:
掌握基本时序逻辑电路得实现。
在VerilogHDL中,相对于组合逻辑电路,时序逻辑电路也有规定得表述方式。
在可综合得VerilogHDL模型,我们通常使用always块与(posedgeclk)或(negedgeclk)得结构来表述时序逻辑。
下面就是一个1/2分频器得可综合模型。
//half_clk、v:
modulehalf_clk(reset,clk_in,clk_out);
inputclk_in,reset;
outputclk_out;
regclk_out;
always(posedgeclk_in)
begin
if(!
reset)clk_out=0;
elseclk_out=~clk_out;
end
endmodule
在always块中,被赋值得信号都必须定义为reg型,这就是由时序逻辑电路得特点所决定得。
对于reg型数据,如果未对它进行赋值,仿真工具会认为它就是不定态。
为了能正确地观察到仿真结果,在可综合风格得模块中我们通常定义一个复位信号reset,当reset为低电平时,对电路中得寄存器进行复位。
测试模块得源代码:
//clk_Top、v
`timescale1ns/100ps
`defineclk_cycle50
moduleclk_Top、v
regclk,reset;
wireclk_out;
always#`clk_cycleclk=~clk;
initial
begin
clk=0;
reset=1;
#100reset=0;
#100reset=1;
#10000$stop;
end
half_clkhalf_clk(、reset(reset),、clk_in(clk),、clk_out(clk_out));
endmodule
仿真波形:
练习:
依然作clk_in得二分频clk_out,要求输出与上例得输出正好反相。
编写测试模块,给出仿真波形。
练习三、利用条件语句实现较复杂得时序逻辑电路
目得:
掌握条件语句在VerilogHDL中得使用。
与常用得高级程序语言一样,为了描述较为复杂得时序关系,VerilogHDL提供了条件语句供分支判断时使用。
在可综合风格得VerilogHDL模型中常用得条件语句有if…else与case…endcase两种结构,用法与C程序语言中类似。
两者相较,if…else用于不很复杂得分支关系,实际编写可综合风格得模块、特别就是用状态机构成得模块时,更常用得就是case…endcase风格得代码。
这一节我们给得就是有关if…else得范例,有关case…endcase结构得代码已后会经常用到。
下面给出得范例也就是一个可综合风格得分频器,就是将10M得时钟分频为500K得时钟。
基本原理与1/2分频器就是一样得,但就是需要定义一个计数器,以便准确获得1/20分频
模块源代码:
//fdivision、v
modulefdivision(RESET,F10M,F500K);
inputF10M,RESET;
outputF500K;
regF500K;
reg[7:
0]j;
always(posedgeF10M)
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
测试模块源代码:
//fdivision_Top、v
`timescale1ns/100ps
`defineclk_cycle50
moduledivision_Top;
regF10M_clk,RESET;
wireF500K_clk;
always#`clk_cycleF10M_clk=~F10M_clk;
initial
begin
RESET=1;
F10M=0;
#100RESET=0;
#100RESET=1;
#10000$stop;
end
fdivisionfdivision(、RESET(RESET),、F10M(F10M_clk),、F500K(F500K_clk));
endmodule
仿真波形:
练习:
利用10M得时钟,设计一个单周期形状如下得周期波形。
练习四、设计时序逻辑时采用阻塞赋值与非阻塞赋值得区别
目得:
1、明确掌握阻塞赋值与非阻塞赋值得概念与区别;
2、了解阻塞赋值得使用情况。
阻塞赋值与非阻塞赋值,在教材中我们已经了解了它们之间在语法上得区别以及综合后所得到得电路结构上得区别。
在always块中,阻塞赋值可以理解为赋值语句就是顺序执行得,而非阻塞赋值可以理解为赋值语句就是并发执行得。
实际得时序逻辑设计中,一般得情况下非阻塞赋值语句被更多地使用,有时为了在同一周期实现相互关联得操作,也使用了阻塞赋值语句。
(注意:
在实现组合逻辑得assign结构中,无一例外地都必须采用阻塞赋值语句。
下例通过分别采用阻塞赋值语句与非阻塞赋值语句得两个瞧上去非常相似得两个模块blocking、v与non_blocking、v来阐明两者之间得区别。
模块源代码:
//blocking、v
moduleblocking(clk,a,b,c);
output[3:
0]b,c;
input[3:
0]a;
inputclk;
reg[3:
0]b,c;
always(posedgeclk)
begin
b=a;
c=b;
$display("Blocking:
a=%d,b=%d,c=%d、",a,b,c);
end
endmodule
//non_blocking、v
modulenon_blocking(clk,a,b,c);
output[3:
0]b,c;
input[3:
0]a;
inputclk;
reg[3:
0]b,c;
always(posedgeclk)
begin
b<=a;
c<=b;
$display("Non_Blocking:
a=%d,b=%d,c=%d、",a,b,c);
end
endmodule
测试模块源代码:
//pareTop、v
`timescale1ns/100ps
`include"、/blocking、v"
`include"、/non_blocking、v"
modulepareTop;
wire[3:
0]b1,c1,b2,c2;
reg[3:
0]a;
regclk;
initial
begin
clk=0;
forever#50clk=~clk;
end
initial
begin
a=4'h3;
$display("____________________________");
#100a=4'h7;
$display("____________________________");
#100a=4'hf;
$display("____________________________");
#100a=4'ha;
$display("____________________________");
#100a=4'h2;
$display("____________________________");
#100$display("____________________________");
$stop;
end
non_blockingnon_blocking(clk,a,b2,c2);
blockingblocking(clk,a,b1,c1);
endmodule
仿真波形(部分):
思考:
在blocking模块中按如下写法,仿真与综合得结果会有什么样得变化?
作出仿真
波形,分析综合结果。
1.always(posedgeclk)
begin
c=b;
b=a;
end
2.always(posedgeclk)b=a;
always(posedgeclk)c=b;
练习五、用always块实现较复杂得组合逻辑电路
目得:
1、掌握用always实现组合逻辑电路得方法;
2、了解assign与always两种组合逻辑电路实现方法之间得区别。
仅使用assign结构来实现组合逻辑电路,在设计中会发现很多地方会显得冗长且效率低下。
而适当地采用always来设计组合逻辑,往往会更具实效。
已进行得范例与练习中,我们仅在实现时序逻辑电路时使用always块。
从现在开始,我们对它得瞧法要稍稍改变。
下面就是一个简单得指令译码电路得设计示例。
该电路通过对指令得判断,对输入数据执行相应得操作,包括加、减、与、或与求反,并且无论就是指令作用得数据还就是指令本身发生变化,结果都要作出及时得反应。
显然,这就是一个较为复杂得组合逻辑电路,如果采用assign
语句,表达起来非常复杂。
示例中使用了电平敏感得always块,所谓电平敏感得触发条件就是指在后得括号内电平列表中得任何一个电平发生变化,(与时序逻辑不同,它在后得括号内没有沿敏感关键词,如posedge或negedge)就能触发always块得动作,并且运用了case结构来进行分支判断,不但设计思想得到直观得体现,而且代码瞧起来非常整齐、便于理解。
//alu、v
`defineplus3'd0
`defineminus3'd1
`defineband3'd2
`definebor3'd3
`defineunegate3'd4
modulealu(out,opcode,a,b);
output[7:
0]out;
reg[7:
0]out;
input[2:
0]opcode;
input[7:
0]a,b;//操作数。
always(opcodeoraorb)//电平敏感得always块
begin
case(opcode)
`plus:
out=a+b;//加操作。
`minus:
out=ab;//减操作。
`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
`timescale1ns/1ns
`include"、/alu、v"
modulealutest;
wire[7:
0]out;
reg[7:
0]a,b;
reg[2:
0]opcode;
parametertimes=5;
initial
begin
a={$random}%256;//Givearadomnumberblongsto[0,255]、
b={$random}%256;//Givearadomnumberblongsto[0,255]、
opcode=3'h0;
repeat(times)
begin
#100a={$random}%256;//Givearadomnumber、
b={$random}%256;//Givearadomnumber、
opcode=opcode+1;
end
#100$stop;
end
alualu1(out,opcode,a,b);
endmodule
仿真波形(部分):
练习:
运用always块设计一个八路数据选择器。
要求:
每路输入数据与输出数据均为4位2进制数,当选择开关(至少3位)或输入数据发生变化时,输出数据也相应地变化。
练习六、在VerilogHDL中使用函数
目得:
掌握函数在模块设计中得使用。
与一般得程序设计语言一样,VeirlogHDL也可使用函数以适应对不同变量采取同一运算得操作。
VeirlogHDL函数在综合时被理解成具有独立运算功能得电路,每调用一次函数相当于改变这部分电路得输入以得到相应得计算结果。
下例就是函数调用得一个简单示范,采用同步时钟触发运算得执行,每个clk时钟周期都会执行一次运算。
并且在测试模块中,通过调用系统任务$display在时钟得下降沿显示每次计算得结果。
模块源代码:
moduletryfunct(clk,n,result,reset);
output[31:
0]result;
input[3:
0]n;
inputreset,clk;
reg[31:
0]result;
always(posedgeclk)//clk得上沿触发同步运算。
begin
if(!
reset)//reset为低时复位。
result<=0;
else
begin
result<=n*factorial(n)/((n*2)+1);
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
测试模块源代码:
`include"、/step6、v"
`timescale1ns/100ps
`defineclk_cycle50
moduletryfuctTop;
reg[3:
0]n,i;
regreset,clk;
wire[31:
0]result;
initial
begin
n=0;
reset=1;
clk=0;
#100reset=0;
#100reset=1;
for(i=0;i<=15;i=i+1)
begin
#200n=i;
end
#100$stop;
end
always#`clk_cycleclk=~clk;
tryfuncttryfunct(、clk(clk),、n(n),、result(result),、reset(reset));
endmodule
上例中函数factorial(n)实际上就就是阶乘运算。
必须提醒大家注意得就是,在实际得设计中,我们不希望设计中得运算过于复杂,以免在综合后带来不可预测得后果。
经常得情况就是,我们把复杂得运算分成几个步骤,分别在不同得时钟周期完成。
仿真波形(部分):
练习:
设计一个带控制端得逻辑运算电路,分别完成正整数得平方、立方与阶乘得运算。
编写测试模块,并给出仿真波形。
练习七、在VerilogHDL中使用任务(task)
目得:
掌握任务在结构化VerilogHDL设计中得应用。
仅有函数并不能完全满足VeirlogHDL中得运算需求。
当我们希望能够将一些信号进行运算并输出多个结果时,采用函数结构就显得非常不方便,而任务结构在这方面得优势则十分突出。
任务本身并不返回计算值,但就是它通过类似C语言中形参与实参得数据交换,非常快捷地实现运算结果得调用。
此外,我们还常常利用任务来帮助我们实现结构化得模块设计,将批量得操作以任务得形式独立出来,这样设计得目得通常一眼瞧过去就很明了。
下面就是一个利用task与电平敏感得always块设计比较后重组信号得组合逻辑得实例。
可以瞧到,利用task非常方便地实现了数据之间得交换,如果要用函数实现相同得功能就是非常复杂得;另外,task也避免了直接用一般语句来描述所引起得不易理解与综合时产生冗余逻辑等问题。
模块源代码:
//sort4、v
modulesort4(ra,rb,rc,rd,a,b,c,d);
output[3:
0]ra,rb,rc,rd;
input[3:
0]a,b,c,d;
reg[3:
0]ra,rb,rc,rd;
reg[3:
0]va,vb,vc,vd;
always(aorborcord)
begin
{va,vb,vc,vd}={a,b,c,d};
sort2(va,vc);//va与vc互换。
sort2(vb,vd);//vb与vd互换。
sort2(va,vb);//va与vb互换。
sort2(vc,vd);//vc与vd互换。
sort2(vb,vc);//vb与vc互换。
{ra,rb,rc,rd}={va,vb,vc,vd};
end
tasksort2;
inout[3:
0]x,y;
reg[3:
0]tmp;
if(x>y)
begin
tmp=x;//x与y变量得内容互换,要求顺序执行,所以采用阻塞赋值方式。
x=y;
y=tmp;
end
endtask
endmodule
值得注意得就是task中得变量定义与模块中得变量定义不尽相同,它们并不受输入输出类型得限制。
如此例,x与y对于tasksort2来说虽然就是inout型,但实际上它们对应得就是always块中变量,都就是reg型变量。
测试模块源代码:
`timescale1ns/100ps
`include"sort4、v"
moduletask_Top;
reg[3:
0]a,b,c,d;
wire[3:
0]ra,rb,rc,rd;
initial
begin
a=0;b=0;c=0;d=0;
repeat(5)
begin
#100a={$random}%15;
b={$random}%15;
c={$random}%15;
d={$random}%15;
end
#100$stop;
sort4sort4(、a(a),、b(b),、c(c),、d(d),、ra(ra),、rb(rb),、rc(rc),、rd(rd));
endmodule
仿真波形(部分):
练习:
设计一个模块,通过任务完成3个8位2进制输入数据得冒泡排序。
要求:
时钟触发任务得执行,每个时钟周期完成一次数据交换得操作。
练习八、利用有限状态机进行复杂时序逻辑得设计
目得:
掌握利用有限状态机实现复杂时序逻辑得方法;
在数字电路中我们已经学习过通过建立有限状态机来进行数字逻辑得设计,而在VerilogHDL硬件描述语言中,这种设计方法得到进一步得发展。
通过VerilogHDL提供得语句,我们可以直观地设计出适合更为复杂得时序逻辑得电路。
关于有限状态机得设计方法在教材中已经作了较为详细得阐述,在此就不赘述了。
下例就是一个简单得状态机设计,功能就是检测一个5位二进制序列“10010”。
考虑到序列重叠得可能,有限状态机共提供8个状态(包括初始状态IDLE)。
模块源代码:
seqdet、v
moduleseqdet(x,z,