4.3 Verilog练习(2)
目录
练习五. 用always块实现较复杂的组合逻辑电路
目的: 1.掌握用always实现组合逻辑电路的方法;2.了解assign与always两种组合逻辑电路实现方法之间的区别。
仅使用assign结构来实现组合逻辑电路,在设计中会发现很多地方会显得冗长且效率低下。而适当地采用always来设计组合逻辑,往往会更具实效。已进行的范例和练习中,我们仅在实现时序逻辑电路时使用always块。从现在开始,我们对它的看法要稍稍改变。
下面是一个简单的指令译码电路的设计示例。该电路通过对指令的判断,对输入数据执行相应的操作,包括加、减、与、或和求反,并且无论是指令作用的数据还是指令本身发生变化,结果都要作出及时的反应。显然,这是一个较为复杂的组合逻辑电路,如果采用assign语句,表达起来非常复杂。示例中使用了电平敏感的always块,所谓电平敏感的触发条件是指在@后的括号内电平列表中的任何一个电平发生变化,(与时序逻辑不同,它在@后的括号内没有沿敏感关键词,如posedge 或negedge)就能触发always块的动作,并且运用了case结构来进行分支判断,不但设计思想得到直观的体现,而且代码看起来非常整齐、便于理解
// ALU.v
`timescale 1ns / 1ps
`define add 3'd0
`define sub 3'd1
`define and 3'd2
`define or 3'd3
`define turn 3'd4
module ALU(
input [2:0] opcode,
input [7:0] a,b,
output [7:0] out
);
reg [7:0] out;
always@(a or b or opcode)
begin
case(opcode)
`add : out = a + b;
`sub : out = a - b;
`and : out = a & b;
`or : out = a | b;
`turn : out = ~a;
default: out = 8'h0;
endcase
end
endmodule
// Module Name: ALU_simu
`timescale 1ns / 1ps
module ALU_simu;
wire [7:0] out;
reg [7:0] a,b;
reg [2:0] opcode;
parameter times = 5;
initial
begin
a = {$random} % 256;
b = {$random} % 256;
opcode = 3'h0;
repeat(times)
begin
#100 a = {$random} % 256;
b = {$random} % 256;
opcode = opcode + 1;
end
#100 $stop;
end
ALU ALU1(opcode,a,b,out);
endmodule
练习六. 在Verilog HDL中使用函数
目的:掌握函数在模块设计中的使用。知识点 function
与一般的程序设计语言一样,Veirlog HDL也可使用函数以适应对不同变量采取同一运算的操作。Veirlog HDL函数在综合时被理解成具有独立运算功能的电路,每调用一次函数相当于改变这部分电路的输入以得到相应的计算结果。
下例是函数调用的一个简单示范,采用同步时钟触发运算的执行,每个clk时钟周期都会执行一次运算。并且在测试模块中,通过调用系统任务$display在时钟的下降沿显示每次计算的结果。
// Module Name: tryfunction.v
module tryfunction(
input [3:0] n,
input clk,
input reset,
output [31:0] result
);
reg [31:0] result;
always @(posedge clk)
begin
if(!reset)
result = 0;
else
result = factorial(n);
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)实际上就是阶乘运算。必须提醒大家注意的是,在实际的设计中,我们不希望设计中的运算过于复杂,以免在综合后带来不可预测的后果。经常的情况是,我们把复杂的运算分成几个步骤,分别在不同的时钟周期完成。
// Module Name: tryfunction_simu
`define clk_cycle 50
module tryfunction_simu;
reg [3:0] n,i;
reg reset,clk;
wire [31:0] result;
initial
begin
n = 0; i = 0;
reset = 1;
clk = 0;
#100 reset = 0;
#100 reset = 1;
for(i=0;i<=10;i=i+1)
begin
#200 n = i;
end
#100 $stop;
end
always #`clk_cycle clk=~clk;
tryfunction tryfunct(.n(n), .clk(clk), .reset(reset), .result(result));
endmodule
练习七. 在Verilog HDL中使用任务(task)
目的:掌握任务在结构化Verilog HDL设计中的应用。
仅有函数并不能完全满足Veirlog HDL中的运算需求。当我们希望能够将一些信号进行运算并输出多个结果时,采用函数结构就显得非常不方便,而任务结构在这方面的优势则十分突出。任务本身并不返回计算值,但是它通过类似C语言中形参与实参的数据交换,非常快捷地实现运算结果的调用。此外,我们还常常利用任务来帮助我们实现结构化的模块设计,将批量的操作以任务的形式独立出来,这样设计的目的通常一眼看过去就很明了。
下面是一个利用task和电平敏感的always块设计比较后重组信号的组合逻辑的实例。可以看到,利用task非常方便地实现了数据之间的交换,如果要用函数实现相同的功能是非常复杂的;另外,task也避免了直接用一般语句来描述所引起的不易理解和综合时产生冗余逻辑等问题。 【排序程序】
// Module Name: sort.v
module sort(
input [3:0] a,b,c,d,
output [3:0] ra,rb,rc,rd
);
reg [3:0] ra,rb,rc,rd;
reg [3:0] va,vb,vc,vd;
always @ (a or b or c or d)
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
task sort2;
inout[3:0] x,y;
reg[3:0] tmp;
if(x>y)
begin
tmp=x; //x与y变量的内容互换,要求顺序执行,所以采用阻塞赋值方式。
x=y;
y=tmp;
end
endtask
endmodule
// Module Name: sort_simu.v
`timescale 1ns/100ps
module sort_simu;
reg[3:0] a,b,c,d;
wire[3:0] ra,rb,rc,rd;
initial
begin
a=0;b=1;c=2;d=3;
repeat(10)
begin
#100 a ={$random}%15; // $random(seed) 调用$random会使用一个默认的seed(这个默认值为0?)。也正因此,每次进行仿真时,$random产生的随机数序列都是相同的。
b ={$random}%15;
c ={$random}%15;
d ={$random}%15;
end
#100 $stop;
end
sort sort4(.a(a), .b(b), .c(c), .d(d), .ra(ra), .rb(rb), .rc(rc), .rd(rd));
endmodule
仿真了几次之后发现随机生成的值是固定的,查资料后发现:$random(seed) 调用$random会使用一个默认的seed(这个默认值为0?)。也正因此,每次进行仿真时,$random产生的随机数序列都是相同的。
练习八. 利用有限状态机进行复杂时序逻辑的设计
目的:掌握利用有限状态机实现复杂时序逻辑的方法;
在数字电路中我们已经学习过通过建立有限状态机来进行数字逻辑的设计,而在Verilog HDL硬件描述语言中,这种设计方法得到进一步的发展。通过Verilog HDL提供的语句,我们可以直观地设计出适合更为复杂的时序逻辑的电路。关于有限状态机的设计方法在教材中已经作了较为详细的阐述,在此就不赘述了。
下例是一个简单的状态机设计,功能是检测一个5位二进制序列“10010”。考虑到序列重叠的可能,有限状态机共提供8个状态(包括初始状态IDLE)。
// Module Name: seqdet
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 ) ? 1 : 0;
always @(posedge clk)
if(!rst)
state <= IDLE;
else
casex(state)
IDLE : if(x==1)
state <= A;
A: if(x==0)
state <= B;
B: if(x==0)
state <= C;
else
state <= F;
C: if(x==1)
state <= D;
else
state <= G;
D: if(x==0)
state <= E;
else
state <= A;
E: state <= E;
F: if(x==1)
state <= A;
else
state <= B;
G: if(x==1)
state <= F;
default:state=IDLE; //缺省状态为初始状态。
endcase
endmodule
// Module Name: seqdet_simu
`timescale 1ns / 1ps
module seqdet_simu;
reg clk,rst;
reg[23:0] data;
wire[2:0] state;
wire z,x;
reg [4:0] i;
assign x=data[23];
always #10 clk = ~clk;
always @(posedge clk)
begin
if(i<24)
begin
data={data[22:0],data[23]}; // 序列算术左移
i = i + 1;
end
end
initial
begin
clk=0;
i=0;
rst=1;
#2 rst=0;
#15 rst=1;
data ='b1110_1101_1011_0100_1001_0100;
// data ='b1111_1111_1001_0000_1111_1111;
#1500 $stop;
end
seqdet m(x,z,clk,rst,state);
endmodule