4.5.1. always块语言指导原则
使用always块进行可综合的代码设计时需要注意以下几个问题。
(1)每个always块只能有一个事件控制“@(event-expression)”,而且要紧跟在always关键字后面。
(2)always块可以表示时序逻辑或者组合逻辑,也可以用always块既表示电平敏感的透明锁存器又同时表示组合逻辑。但是不推荐使用这种描述方法,因为这容易产生错误和多余的电平敏感的透明锁存器。
(3) 带有posedge 或 negedge 关键字的事件表达式表示沿触发的时序逻辑;没有posedge 或negedge关键字的表示组合逻辑或电平敏感的锁存器,或者两种都表示。在表示时序和组合逻辑的事件控制表达式中如有多个沿和多个电平,其间必须用关键字“or” 连接 。
(4)每个表示时序逻辑的always块只能由一个时钟跳变沿触发,置位或复位最好也由该时钟跳变沿触发。
(5)每个在always块中赋值的信号都必需定义成reg型或整型。整型变量缺省为32bit,使用Verilog操作符可对其进行二进制求补的算术运算。综合器还支持整型量的范围说明,这样就允许产生不是32位的整型量,句法结构如下:
integer[:]。
(6)always块中应该避免组合反馈回路。每次执行always块时,在生成组合逻辑的always块中赋值的所有信号必需都有明确的值;否则需要设计者在设计中加入电平敏感的锁存器来保持赋值前的最后一个值。
只有这样,综合器才能正常生成电路。如果不这样做,综合器会发出警告,提示设计中插入了锁存器。如果在设计中存在综合器认为不是电平敏感锁存器的组合回路时,综合器会发出错误信息(例如设计中有异步状态机时)。
用always块设计纯组合逻辑电路时,在生成组合逻辑的always块中,参与赋值的所有信号都必须有明确的值,即在赋值表达式右端参与赋值的信号都必需在always @(敏感电平列表)中列出。
如果在赋值表达式右端引用了敏感电平列表中没有列出的信号,那么在综合时,将会为该信号产生一个隐含的透明锁存器。这是因为该信号的变化不会立刻引起所赋值的变化,而必须等到敏感电平列表中某一个信号变化时,它的作用才显现出来。
也就是相当于存在着一个透明锁存器把该信号的变化暂存起来,待敏感电平列表中某一个信号变化时再起作用,纯组合逻辑电路不可能做到这一点。这样,综合后所得电路已经不是纯组合逻辑电路了。这时综合器会发出警告提示设计中插入了锁存器,如下所示:
input a,b,c;
reg e,d;
always @(a or b or c) begin
 e = d & a & b; //因为d没有在敏感电平列表中,所以d变化时,e不能立刻变化,
 //要等到a或b或c变化时才体现出来。这就是说实际上相当于存在
 //一个电平敏感的透明锁存器在起作用, 把d信号的变化锁存其中
 d = e | c;
end
(7)对一个寄存器型(reg)或整型(integer)变量的赋值只允许在一个always块内进行,如果在另一always块也对其赋值,这是非法的。
(8)把某一信号值赋为'bx,综合器就把它解释成无关状态,因而综合器为其生成的硬件电路最简洁。
4.5.2. 可综合风格的Verilog HDL模块实例
1.组合逻辑电路设计实例
例4.6:8位带进位端的加法器的设计实例(利用简单的算法描述)。
module adder_8(cout,sum,a,b,cin); //模块声明
 output cout;
 output [7:0] sum;
 input cin;
 input[7:0] a,b; //端口声明
 assign {cout,sum} = a + b + cin; //加法器算法
endmodule
例4.7:指令译码电路的设计实例(利用电平敏感的always块来设计组合逻辑)。
'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); //模块声明
 output [7:0] out;
 input [2:0] opcode;
 input [7:0] a,b; //端口声明
 reg [7:0] out; //寄存器声明
 always @(opcode or a or b) begin //用电平敏感的always块描述组合逻辑
 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
例4.8:比较后重组信号的组合逻辑(利用task和电平敏感的always块设计)。
module sort4(ra,rb,rc,rd,a,b,c,d); //模块声明
 output [t:0] ra, rb, rc, rd;
 input [t:0] a, b, c, d; //端口声明
 reg [t:0] ra, rb, rc, rd;
 reg [t:0] va, vb, vc, vd; //寄存器声明
 parameter t=3; //参数声明
 always @(a or b or c or d) begin //用电平敏感的always块描述组合逻辑
 {va,vb,vc,vd}={a,b,c,d};
 sort2(va,vc); //信号重组
 sort2(vb,vd);
 sort2(va,vb);
 sort2(vc,vd);
 sort2(vb,vc);
 {ra,rb,rc,rd}={va,vb,vc,vd};
 end
 task sort2; //x与y互换任务
 inout [t:0] x,y;
 reg [t:0] tmp;
 if(x > y) begin
 tmp = x; //使用临时变量tmp保存x的值
 x = y;
 y = tmp;
 end
 endtask
endmodule
例4.9:比较器的设计实例(利用赋值语句设计组合逻辑)。
module compare(equal,a,b); //模块声明
 output equal;
 input [size-1:0] a,b; //端口声明
 parameter size=1; //参数声明
 assign equal =(a==b)? 1 : 0; //比较器
endmodule
例4.10:3-8译码器设计实例(利用赋值语句设计组合逻辑)。
module decoder(out,in); //模块声明
 output [7:0] out;
 input [2:0] in; //端口声明
 assign out = 1'b1<<in; //把最低位的1左移 in(根据从in口输入的值)位
 //将移位结果赋予out
endmodule
例4.11:3-8编码器的设计实例。
编码器设计方案一。
module encoder1(none_on,out,in); //模块声明
 output none_on;
 output [2:0] out;
 input [7:0] in; //端口声明
 reg [2:0] out;
 reg none_on; //寄存器声明
 always @(in) begin: local //in有变化时,触发
 integer i; //变量声明
 out = 0;
 none_on = 1; //初始化
 for( i=0; i<8; i=i+1 ) begin //for循环语句
 if( in[i] ) begin //将in中值为1的位编码
 out = i;
 none_on = 0;
 end
 end
 end
endmodule
编码器设计方案二。
module encoder2 ( none_on,out2,out1,out0,h,g,f,e,d,c,b,a); //模块声明
 input h,g,f,e,d,c,b,a;
 output none_on,out2,out1,out0; //端口声明
 wire [3:0] outvec; //向量声明
 assign outvec = //使用assign语句实现输出向量赋值
 h ? 4'b0111 : g ? 4'b0110 : f ? 4'b0101:
 e ? 4'b0100 : d ? 4'b0011 : c ? 4'b0010 :
 b ? 4'b0001 : a ? 4'b0000 : 4'b1000;
 assign none_on = outvec[3]; //使用assign语句进行编码
 assign out2 = outvec[2];
 assign out1 = outvec[1];
 assign out0 = outvec[0];
endmodule
编码器设计方案三。
module encoder3 ( none_on,out2,out1,out0,h,g,f,e,d,c,b,a); //模块声明
 input h,g,f,e,d,c,b,a;
 output none_on,out2,out1,out0; //端口声明
 wire [3:0] outvec; //向量声明
 assign {none_on,out2,out1,out0} = outvec; //与上例的编码方式一致
 always @( a or b or c or d or e or f or g or h) begin
 if(h) outvec=4'b0111; //使用if_else语句实现向量赋值
 else if(g) outvec=4'b0110; //共9个分支,其中向量的低3位有8种编码方式
 else if(f) outvec=4'b0101;
 else if(e) outvec=4'b0100;
 else if(d) outvec=4'b0011;
 else if(c) outvec=4'b0010;
 else if(b) outvec=4'b0001;
 else if(a) outvec=4'b0000;
 else outvec=4'b1000;
 end
endmodule
例4.12:多路器的设计实例。
使用assign赋值语句、case语句或if-else语句可以生成多路器电路。如果条件语句(case或if-else)中分支条件是互斥的话,综合器能自动地生成并行的多路器。
多路器设计方案一。
modul emux1(out,a,b,sel); //模块声明
 output out;
 input a,b,sel; //端口声明
 //使用assign语句检查输入信号sel的值
 assign out = sel ? a : b; //当sel为1时,out为a;否则为b
endmodule
多路器设计方案二。
module mux2( out,a,b,sel); //模块声明
 output out;
 input a,b,sel; //端口声明
 reg out;
 always @(a or b or sel) begin //用电平触发的always块来设计多路器的组合逻辑
 case( sel ) //使用case语句检查输入信号sel的值
 1'b1: out = a; //如果为1,输出out为a
 1'b0: out = b; //如果为0,输出out为b
 default: out = 'bx; //默认状态
 endcase
 end
endmodule
多路器设计方案三。
module mux3( out,a,b sel); //模块声明
 output out;
 input a, b, sel; //端口声明
 reg out;
 always @( a or b or sel ) begin
 if( sel ) //使用if_else语句检查输入信号sel的值
 out = a; //如果为1,输出out为a
 else
 out = b; //如果为0,输出out为b
 end
endmodule
例4.13:奇偶校验位生成器设计实例。
module parity( even_numbits,odd_numbits,input_bus); //模块声明
 output even_numbits, odd_numbits;
 input [7:0] input_bus; //端口声明
 assign odd_numbits = ^input_bus; //当input_bus中1的个数为奇数时,输出为1
 assign even_numbits = ~odd_numbits; //此时输出even_numbits为0
endmodule
例4.14:三态输出驱动器设计实例(用连续赋值语句建立三态门模型)。
三态输出驱动器设计方案一。
module trist1( out,in,enable); //模块声明
 output out;
 input in, enable; //端口声明
 assign out = enable? in: 'bz; //使用assign语句判断enable的值
endmodule
三态输出驱动器设计方案二。
module trist2( out,in,enable ); //模块声明
 output out;
 input in,enable; //端口声明
 bufif1 mybuf1(out, in, enable); //bufif1是一个 Verilog门级原语(primitive)
 //通过实例化该原语,实现三态门的调用
endmodule
例4.15:三态双向驱动器设计实例。
module bidir(tri_inout,out,in,en,b); //模块声明
 inout tri_inout;
 output out;
 input in,en,b; //端口声明
 assign tri_inout = en? in : 'bz; //三态门的输入为in
 assign out = tri_inout ^ b; //三态门的输出为b
endmodule
2.时序逻辑电路设计实例
例4.16:触发器设计实例。
module dff( q,data,clk); //模块声明
 output q;
 input data,clk; //端口声明
 reg q;
 always @( posedge clk ) begin //边缘检测
 q = data; //通过always语句,实现触发器
 end
endmodule
例4.17:电平敏感型锁存器设计实例一(assign语句)。
module latch1( q,data,clk); //模块声明
 output q;
 input data,clk; //端口声明
 assign q = clk ? data : q; //通过assign语句,实现的是一个锁存器
endmodule
例4.18:带置位和复位端的电平敏感型锁存器设计实例二(assign语句)。
module latch2( q,data,clk,set,reset); //模块声明
 output q;
 input data,clk,set,reset; //端口声明
 assign q= reset ? 0 : ( set? 1:(clk? data : q ) );
 //通过assign语句,实现的是一个锁存器
 //其中,set为置位端,reset为复位端
 //在clk为高电平时,锁存data,否则保持q值
endmodule
例4.19:电平敏感型锁存器设计实例三(always块)。
module latch3( q, data, clk); //模块声明
 output q;
 input data,clk; //端口声明
 reg q;
 always @(clk or data) begin //电平检测
 if(clk) //clk为高电平时,q锁存data值
 q = data;
 end
endmodule
 注意 有的综合器会产生一个警告信息,提示将产生了一个电平敏感型锁存器。因为此例中设计的就是一个电平敏感型锁存器,所以这个警告信息是没有问题的。
例4.20:移位寄存器设计实例。
module shifter( din,clk,clr,dout); //模块声明
 input din,clk,clr;
 output [7:0] dout; //端口声明
 reg [7:0] dout;
 always @(posedge clk) begin
 if(clr) //清零
 dout = 8'b0;
 else begin
 dout = dout<<1; //左移一位
 dout[0] = din; //把输入信号放入寄存器的最低位
 end
 end
endmodule
例4.21:8位计数器设计实例一。
module counter1( out, cout, data, load, cin, clk); //模块声明
 output [7:0] out;
 output cout;
 input [7:0] data;
 input load, cin, clk; //端口声明
 reg [7:0] out;
 always @(posedge clk) begin //边缘检测
 if( load ) //加载信号检测
 out = data;
 else
 out = out + cin;
 end
 assign cout= & out & cin; //只有当out[7:0]的所有各位都为1
 //并且进位cin也为1时才能产生进位cout
endmodule
例4.22:8位计数器设计实例二。
module counter2( out, cout, data, load, cin, clk); //模块声明
 output [7:0] out;
 output cout;
 input [7:0] data;
 input load, cin, clk; //端口声明
 reg [7:0] out;
 reg cout;
 reg [7:0] preout; //寄存器声明
 always @(posedge clk) begin //边缘检测
 out = preout; //触发器
 end
 //计算计数器和进位的下一个状态,为提高性能,load不应影响进位
 always @( out or data or load or cin ) begin
 {cout, preout} = out + cin; //进位操作
 if(load) preout = data; //判断加载信号
 end
endmodule