Procedures
Alwaysblock1:combinational
由于数字电路是由连接电线的逻辑门组成的,任何电路都可以表示为一些模块和赋值语句的组合。然而,有时这并不是描述电路最方便的方法。过程(block总是一个例子)提供了描述电路的另一种语法。
有两种类型的always块是与可综合硬件相关的:
组合逻辑: always @(*)
时序逻辑: always @(posedge clk)
always块等价于赋值语句,因此总是有两种方式来表达一个组合电路。选择使用哪个方法主要是哪个语法更方便的问题。过程块内部代码的语法与外部代码不同。过程块有更丰富的语句集(例如if-then, case),不能包含连续的赋值,但也引入了许多新的不直观的出错方法。
例如,赋值语句和组合always块描述相同的电路。两者都创建了相同的组合逻辑。当任何输入(右侧)更改值时,两者都将重新计算输出。
assign out1 = a & b | c ^ d;
always @(*) out2 = a & b | c ^ d;
对于组合always块,总是使用(*)的敏感性列表。明确列出信号是很容易出错的(如果您错过了一个),并且在硬件合成时被忽略。如果你显式地指定了灵敏度列表并且错过了一个信号,合成的硬件仍然会表现得像指定了 ( * ) 一样,但是模拟将不匹配硬件的行为。(在SystemVerilog中,使用always_comb。)
关于wire和reg的注意事项:赋值语句的左边必须是net类型(例如wire),而过程赋值语句(在always块中)的左边必须是变量类型(例如reg)。这些类型(wire vs. reg)与合成什么硬件无关,只是Verilog作为硬件模拟语言使用时遗留下来的语法。
Problem Statement
使用assign语句和组合的always块构建与门。(因为赋值语句和组合语句总是相同地阻塞函数,所以没有办法强制你同时使用这两种方法。但你是来练习的,对吧?)
Writing Code
// synthesis verilog_input_version verilog_2001
module top_module(
input a,
input b,
output wire out_assign,//赋值语句左侧用wire
output reg out_alwaysblock//always中的过程赋值语句用reg
//用两种方式,两种输出
);
//assign statement
assign out_assign = a & b;
//combinational always block
always@(*) begin
out_alwaysblock = a & b;
end
endmodule
Alwaysblock2:clocked
对于硬件合成,有两种类型的总是相关的块:
组合:always @(*)
时序:always @(posedge clk)
时序always块创建一组组合逻辑像组合always块一样,但也在组合逻辑输出处创建一组触发器(或“寄存器”)。不像逻辑的输出是立即可见的,它的输出而是在下一个(posedge clk)之后才可见。
阻塞 VS 非阻塞赋值
在Verilog中存在三种赋值:
·连续赋值(assign x = y;):不能在过程(“always块”)中使用。
·过程阻塞赋值(x = y;):只能在过程中使用。
·过程非阻塞赋值(x <= y;):只能在过程中使用。
划重点!
在组合always块中,使用阻塞性赋值。在时序always块中,使用非阻塞性赋值。完全理解是为什么对硬件设计用处不大,还需要理解Verilog模拟器如何跟踪事件不遵循这一规则就很难发现仿真和综合硬件之间既不确定又不同的错误。
Problem Statement
使用赋值语句、组合always块和时钟always块三种方法构建异或门。注意,时钟always块产生一个不同于其他两个的电路:有一个触发器,所以输出被延迟。
Writing Code
// synthesis verilog_input_version verilog_2001
module top_module(
input clk,
input a,
input b,
output wire out_assign,//赋值语句用wire
output reg out_always_comb,//过程块always @(*)用reg
output reg out_always_ff );//过程块always @(posedge clk)用reg
assign out_assign = a^b;
always @(*)begin
out_always_comb = a^b;
end
always @(posedge clk)begin
out_always_ff <= a^b;//注意这儿是<=阻塞式赋值!
end
endmodule
解释:为啥下面这个地方不一样但是还是success了
前文有一句:
“注意,时钟always块产生一个不同于其他两个的电路:有一个触发器,所以输出被延迟。”
“时序always块创建一组组合逻辑像组合always块一样,但也在组合逻辑输出处创建一组触发器(或“寄存器”)。不像逻辑的输出是立即可见的,它的输出而是在下一个(posedge clk)之后才可见。”
另外一方面的解释是:always @(posedge clk)begin
与正规的相比,少了一个下降沿复位的信号,也就是一开始的状态。此时没有,所以说并不是一开始就是复位状态的,是不定态的。
Always if:if statement
if语句通常创建一个2选1的多路复用器,如果条件为真,则选择一个输入;如果条件为假,则选择另一个输入。
always @(*) begin
if (condition) begin
out = x;
end
else begin
out = y;
end
end
//注意格式!有begin就有end,有if 就要有else,case必跟default
这相当于使用带有条件运算符的连续赋值语句:
assign out = (condition) ? x : y;
然而,if语句提供了一种新的出错方法。只有当输出被赋值时,电路才是组合的。
Problem Statement
构建一个在a和b之间进行选择的2选1数据选择器。如果sel_b1和sel_b2 都为真,选择b;否则选择a。实现两次,一次用赋值语句,一次用if语句。
Writing Code
// synthesis verilog_input_version verilog_2001
module top_module(
input a,
input b,
input sel_b1,
input sel_b2,
output wire out_assign,
output reg out_always );
//如果sel_b1和sel_b2 都为真,选择b;否则选择a
assign out_assign = (sel_b1 && sel_b2)?b:a;
always @(*) begin
if(sel_b1 && sel_b2)
out_always = b;
else
out_always = a;
end
endmodule
Always if2:if statement latches
Problem Statement
常见的错误来源:如何避免产生锁存器?
在设计电路时,关于电路必须首先考虑:
·我想要这个逻辑门
·我想要一个逻辑组合块,它有一些输入,产生这一些输出
·我想要一个逻辑组合块,后面带有触发器
你千万不能先写代码,然后希望它能产生一个合适的电路。
If (cpu_overheated) then shut_off_computer = 1;
If (~arrived) then keep_driving = ~gas_tank_empty;
语法正确的代码不一定会产生合理的电路(组合逻辑+触发器)。通常的原因是:“除了您指定的那些情况之外,在其他情况下会发生什么?”Verilog的答案是:保持输出不变(产生锁存)。
“除了您指定的那些情况之外,在其他情况下会发生什么?”这应该是我们该突破的思维误区
这种“保持输出不变的行为”意味着需要记住当前状态,从而产生一个锁存器。组合逻辑(例如,逻辑门)不能记住任何状态。注意“Warning (10240): … inferring latch(es)”信息。除非锁存是故意的,否则它几乎总是指出一个bug。组合电路必须在所有条件下都有一个值分配给输出。这通常意味着你总是需要else子句或默认赋值给输出。
以下代码包含产生锁存器的不正确行为。
修复漏洞,这样只有在电脑过热的时候你才会关掉电脑,当你到达目的地或者需要加油的时候才会停止开车(原作者的形象解释)。
always @(*) begin
if (cpu_overheated)
shut_off_computer = 1;
end
always @(*) begin
if (~arrived)
keep_driving = ~gas_tank_empty;
end
Writing Code
// synthesis verilog_input_version verilog_2001
module top_module (
input cpu_overheated,
output reg shut_off_computer,
input arrived,
input gas_tank_empty,
output reg keep_driving ); //
always @(*) begin
if (cpu_overheated)
shut_off_computer = 1;//只有在电脑过热的时候你才会关掉电脑
else
shut_off_computer = 0;//其它时刻电脑都在运行
end
always @(*) begin
if (~arrived)//没到达时
keep_driving = ~gas_tank_empty;//当你 到达目的地 或者 需要加油的时候 才会停止开车
//此处意为:有油的时候就继续keep_driving
else //到达的时候
keep_driving = ~arrived;
end
endmodule
总结:if必须带else
case必须带default和endcase
Always case:case statement
Verilog中的Case语句几乎等同于if-elseif-else序列,它将一个表达式与其他表达式列表进行比较。它的语法和功能不同于C语言中的switch语句。
always @(*) begin //这是一个组合电路
case (in)
1'b1: begin
out = 1'b1; //如果超过了1个语句,就要用begin-end了,相当于{ }
end
1'b0: out = 1'b0;
default: out = 1'bx;
endcase
end
·case语句以case开头,每个“case item”以冒号结束。switch语句没有。
·每个case项可以只执行一条语句。这使得C语言中使用的**“break”在这里没有必要**。如果需要多个语句,则必须使用begin…end。
·重复的(和部分重复)的case是允许的。使用第一个匹配的。C不允许重复案例项目。
Problem Statement
如果在有许多种的情况下,case语句比if语句更方便。因此,在这个练习中,创建一个6对1的多路复用器。当sel在0到5之间时,选择相应的数据输入。否则,输出0。数据输入和输出都是4位宽。
Writing Code
// synthesis verilog_input_version verilog_2001
module top_module (
input [2:0] sel,
input [3:0] data0,
input [3:0] data1,
input [3:0] data2,
input [3:0] data3,
input [3:0] data4,
input [3:0] data5,
output reg [3:0] out );//
always@(*) begin // This is a combinational circuit
case(sel)
3'b000 : out = data0;//注意:写3'd0是不行的,注意数据格式
3'b001 : out = data1;
3'b010 : out = data2;
3'b011 : out = data3;
3'b100 : out = data4;
3'b101 : out = data5;
default:out = 4'b0;
endcase
end
endmodule
Always case2:priority encoder
优先级编码器是一种组合电路,当给定一个输入位向量时,输出向量中第一个1位的位置。例如,给定输入8’b10010000的8位优先级编码器将输出3’d4,因为位[4]是第一个高的位。
注意:是从低位到高位!
Problem Statement
构建一个4位优先级编码器。如果没有一个输入位是高的(例如输入为零),那么输出为零。注意,一个4位数字有16种可能的组合。
Hint
使用十六进制(4’hb)或十进制(4’d11)数字字面值会比二进制(4’b1011)字面值节省键入时间。
Writing Code
因为是第一位的位置,所以说4位的优先级编码器,也就0、1、2、3四种输出结果。
那如何匹配in呢?
抓住几个关键的数字
比如0001、0011、0111、是1
是3
0100是4
1000是8
那么之间的都是输出的某一个固定的数字
加粗样式
为了便于理解和区分:我在下面的程序进行了“分段”
以下是官方的答案,但是我更喜欢我的答案
module top_module (
input [3:0] in,
output reg [1:0] pos
);
always @(*) begin // Combinational always block
case (in)//16种情况
4'h0: pos = 2'h0; // I like hexadecimal 16进制because it saves typing.
4'h1: pos = 2'h0;
4'h2: pos = 2'h1;
4'h3: pos = 2'h0;
4'h4: pos = 2'h2;
4'h5: pos = 2'h0;
4'h6: pos = 2'h1;
4'h7: pos = 2'h0;
4'h8: pos = 2'h3;
4'h9: pos = 2'h0;
4'ha: pos = 2'h1;
4'hb: pos = 2'h0;
4'hc: pos = 2'h2;
4'hd: pos = 2'h0;
4'he: pos = 2'h1;
4'hf: pos = 2'h0;
default: pos = 2'b0; // Default case is not strictly necessary because all 16 combinations are covered.
endcase
end
// There is an easier way to code this. See the next problem (always_casez).
endmodule
我更喜欢我的答案,因为虽然最后的输出是2进制的,但是我更喜欢前面in的情况用二进制来看,相对于16进制来说,不容易出错
// synthesis verilog_input_version verilog_2001
module top_module (
input [3:0] in,
output reg [1:0] pos );
always@(*)
case(in)
4'b0000: pos = 2'b00;//注意要匹配!!!!!!
4'b0001: pos = 2'b00;
4'b0010: pos = 2'b01;
4'b0011: pos = 2'b00;
4'b0100: pos = 2'b10;
4'b0101: pos = 2'b00;
4'b0110: pos = 2'b01;
4'b0111: pos = 2'b00;
4'b1000: pos = 2'b11;
4'b1001: pos = 2'b00;
4'b1010: pos = 2'b01;
4'b1011: pos = 2'b00;
4'b1100: pos = 2'b10;
4'b1101: pos = 2'b00;
4'b1110: pos = 2'b01;
4'b1111: pos = 2'b00;
default: pos = 2'b00;
endcase
endmodule
Always casez:priority encoder with casez
Problem Statement
构建一个8位输入优先级编码器。给定一个8位向量,输出应该指出向量中的第一个是1的位。如果输入向量没有1,则输出0。例如,输入8’b10010000应该输出3’d4,因为位[4]是第一个高(为“1”)的位。
与上一次不同的地方在于,这一次是8位,情况更多了,该如何处理??
根据前面的练习(Always_case2), case语句中有256种情况。如果case语句中的case项支持“不关心”位,我们可以将其减少(减少到9种情况)。
这就是casez的作用:它将值z的位视为比较中不关心的位。
例如,这将实现之前练习中的4输入优先级编码器:
always @(*) begin
casez (in[3:0])
4'bzzz1: out = 0; // in[3:1] can be anything
4'bzz1z: out = 1;
4'bz1zz: out = 2;
4'b1zzz: out = 3;
default: out = 0;
endcase
end
case语句的行为就像按顺序检查每一项一样(实际上,它做的事情更像是生成一个巨大的真值表,而不是生成门)。注意某些输入(例如,4’b1111)如何匹配多个case项。选择第一个匹配项(因此4’b1111匹配第一个项out = 0,但不匹配后面的任何项)。
还有一个类似的casex,它把x和z都当作“不关心”。我看不出它比casez有更多的用处。
符号?是z的同义词,所以2’bz0和2’b?0相同。
Hint
这里必须使用二进制文字,因为需要为某些位指定z。
Writing Code