赞
踩
由于数字电路由用导线连接的逻辑门组成,因此任何电路都可以表示为模块和分配语句的某种组合。但是,有时这不是描述电路的最方便方法。过程(始终以块为例)提供了描述电路的替代语法。
对于合成硬件(synthesizing hardware),两种类型的始终块是相关的:
组合:always @(*)
时钟:always @(posedge clk)
组合always块等价于assign语句,因此总有一种方法可以双向表达组合电路。选择使用哪种语法主要是哪种语法更方便的问题。过程块内代码的语法与过程块外部的代码不同。过程块具有更丰富的语句集(例如,if-then,case),不能包含连续赋值,但也引入了许多新的非直观错误方式。*(*过程连续赋值确实存在,但与连续赋值有些不同,并且不可合成。
例如,assign和combinational always块描述相同的电路。两者都创建相同的组合逻辑。每当任何输入(右侧)更改值时,两者都将重新计算输出。
assign out1 = a & b | c ^ d;
always @(*) out2 = a & b | c ^ d;
对于组合always块,始终使用敏感度列表 (*)。明确列出信号容易出错(如果您错过了一个信号),并且在硬件合成中被忽略。如果显式指定灵敏度列表并错过信号,则合成的硬件仍将像指定 (*) 一样,但模拟(simulation)不会与硬件的行为不匹配。(在 SystemVerilog 中,使用 always_comb。)
关于 wire vs. reg 的说明:assign 语句的左侧必须是网络类型(例如,wire),而过程赋值的左侧(在 always 块中)必须是变量类型(例如,reg)。这些类型(wire vs. reg)与合成的硬件无关,只是Verilog用作硬件模拟语言时遗留下来的语法。
使用 assign 语句和组合always块构建 AND 门。(由于赋值语句和组合块的功能始终相同,因此无法强制使用这两种方法。但你是来练习的,对吧?...)
- // synthesis verilog_input_version verilog_2001
- module top_module(
- input a,
- input b,
- output wire out_assign,
- output reg out_alwaysblock
- );
- assign out_assign=a&b;
- always@(*)begin
- out_alwaysblock=a&b;
- end
- endmodule
对于硬件综合,有两种类型的始终块是相关的:
组合:always @(*)
时钟:always @(posedge clk)
时钟always块创建一个组合逻辑块,就像组合always块一样,但也在组合逻辑的blob的输出端创建一组触发器(或“寄存器”)。输出不是立即可见,而是输出仅在下一个(posedge clk)之后。
Verilog 中有三种类型的分配:
连续赋值(assign x = y;)。只能在不在过程(procedure)中使用(“always块”)。
过程阻塞赋值:(x = y;)。只能在过程(procedure)中使用。
过程性非阻塞赋值:(x <= y;)。只能在过程(procedure)中使用。
在组合“always块”中,使用阻塞赋值。在时钟“always块”中,使用非阻塞赋值。充分了解原因对于硬件设计并不是特别有用,并且需要很好地了解Verilog模拟器如何跟踪事件。不遵循此规则会导致极难发现非确定性错误,并且在仿真和合成硬件之间存在差异。
(1).非阻塞(Non_Blocking)赋值方式( 如 b <= a; )
块结束后才完成赋值操作。
b的值并不是立刻就改变的。
这是一种比较常用的赋值方法。(特别在编写可综合模块时)
(2).阻塞(Blocking)赋值方式( 如 b = a; )
赋值语句执行完后,块才结束。
b的值在赋值语句执行完后立刻就改变的。
可能会产生意想不到的结果。
使用 assign 语句、组合always块和时钟always块以三种方式构建异或门。请注意,时钟always模块产生的电路与其他两个不同:有一个触发器,因此输出延迟。
- // synthesis verilog_input_version verilog_2001
- module top_module(
- input clk,
- input a,
- input b,
- output wire out_assign,
- output reg out_always_comb,
- output reg out_always_ff );
- assign out_assign=a^b;
- always@(*) out_always_comb=a^b;
- always@(posedge clk) out_always_ff=a^b;
- endmodule
if 语句通常创建一个 2 选 1 多路复用器( 2-to-1 multiplexer),如果条件为真,则选择一个输入,如果条件为假,则选择另一个输入。
always @(*) begin
if (condition) begin
out = x;
end
else begin
out = y;
end
end
这等效于使用带有条件运算符的连续赋值:
assign out = (condition) ? x : y;
但是,过程 if 语句提供了一种犯错误的新方法。仅当always为 out 分配值时,电路才是组合的。
构建一个在 a 和 b 之间进行选择的 2 对 1 复用器。如果 sel_b1 和 sel_b2 都为真,则选择 b。否则,请选择 a。执行相同的操作两次,一次使用 assign 语句,一次使用过程 if 语句。
sel_b1 | sel_b2 | out_assign out_always |
0 | 0 | 一个 |
0 | 1 | 一个 |
1 | 0 | 一个 |
1 | 1 | b |
// 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 ); 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
在设计电路时,您必须首先考虑电路:
我想要这个逻辑门
我想要一个具有这些输入并产生这些输出的组合逻辑 blob
我想要一个组合逻辑块,然后是一组触发器
你不能做的是先写代码,然后希望它产生一个正确的电路。
如果 (cpu_overheated) 则 shut_off_computer = 1;
如果 (~arrived) 则 keep_driving = ~gas_tank_empty;
语法正确的代码不一定会产生合理的电路(组合逻辑+触发器)。通常的原因是:“在您指定的情况之外,会发生什么?Verilog的答案是:保持输出不变。
这种“保持输出不变”的行为意味着需要记住当前状态,从而产生锁存器。组合逻辑(例如逻辑门)无法记住任何状态。注意警告 (10240): ...推断闩锁“消息。除非闩锁是故意的,否则它几乎总是表示存在错误。组合电路必须在所有条件下为所有输出分配一个值。这通常意味着您始终需要 else 子句或分配给输出的默认值。
锁存器(Latch),是电平触发的存储单元,数据存储的动作取决于输入时钟(或者使能)信号的电平值。仅当锁存器处于使能状态时,输出才会随着数据输入发生变化。
当电平信号无效时,输出信号随输入信号变化,就像通过了缓冲器;当电平有效时,输出信号被锁存。激励信号的任何变化,都将直接引起锁存器输出状态的改变,很有可能会因为瞬态特性不稳定而产生振荡现象。
锁存器示意图如下:
触发器(flip-flop),是边沿敏感的存储单元,数据存储的动作(状态转换)由某一信号的上升沿或者下降沿进行同步的(限制存储单元状态转换在一个很短的时间内)。
触发器示意图如下:
寄存器(register),在 Verilog 中用来暂时存放参与运算的数据和运算结果的变量。一个变量声明为寄存器时,它既可以被综合成触发器,也可能被综合成 Latch,甚至是 wire 型变量。但是大多数情况下我们希望它被综合成触发器,但是有时候由于代码书写问题,它会被综合成不期望的 Latch 结构。
Latch 的主要危害有:
1)输入状态可能多次变化,容易产生毛刺,增加了下一级电路的不确定性;
2)在大部分 FPGA 的资源中,可能需要比触发器更多的资源去实现 Latch 结构;
3)锁存器的出现使得静态时序分析变得更加复杂。
Latch 多用于门控时钟(clock gating)的控制,一般设计时,我们应当避免 Latch 的产生。
组合逻辑中,不完整的 if - else 结构,会产生 latch。
例如下面的模型,if 语句中缺少 else 结构,系统默认 else 的分支下寄存器 q 的值保持不变,即具有存储数据的功能,所以寄存器 q 会被综合成 latch 结构。
以下代码包含创建闩锁的错误行为。修复错误,以便仅在计算机确实过热时才关闭计算机,并在到达目的地或需要加油时停止驾驶。
这是代码描述的电路,而不是您要构建的电路。
always @(*) begin
if (cpu_overheated)
shut_off_computer = 1;
end
always @(*) begin
if (~arrived)
keep_driving = ~gas_tank_empty;
end
// 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; else keep_driving =0; end endmodule
Verilog 中的case语句几乎等同于将一个表达式与其他表达式列表进行比较的 if-elseif-else 序列。它的语法和功能与 C 中的 switch 语句不同。
always @(*) begin // This is a combinational circuit
case (in)
1'b1: begin
out = 1'b1; // begin-end if >1 statement
end
1'b0: out = 1'b0;
default: out = 1'bx;
endcase
end
case 语句以 case 开头,每个“case 项”以冒号结尾。没有“switch”。
每个事例项只能执行一个语句。这使得 C 中使用的“break”变得不必要。但这意味着如果您需要多个语句,则必须使用 begin ... end.。
允许重复(和部分重叠)case项。使用第一个匹配的。C 不允许重复case项。
如果有大量案例,则case陈述比 if 语句更方便。因此,在本练习中,创建一个 6 对 1 多路复用器。当 sel 介于 0 和 5 之间时,选择相应的数据输入。否则,输出 0。数据输入和输出均为 4 位宽。
小心推断闩锁(请参阅。always_if2)
// 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) 0:out = data0; 1:out = data1; 2:out = data2; 3:out = data3; 4:out = data4; 5:out = data5; default:out = 4'b0000; endcase end endmodule
优先级编码器是一种组合电路,当给定输入位矢量时,输出矢量中第一个为1的比特的位置。例如,给定输入 8'b10010000 的 8 位优先级编码器将输出 3'd4,因为 bit[4] 是第一个高位。
构建 4 位优先级编码器。对于此问题,如果输入位都不高(即输入为零),则输出为零。请注意,一个 4 位数字有 16 种可能的组合。
在case语句中,敏感表达式中与各项值之间的比较是一种全等比较,每一位都相同才认为匹配。
在casez语句中,如果分支表达式某些位的值为高阻z,那么对这些位的比较就会忽略,不予考虑,而只关注其他位的比较结果。
在casex语句中,则把这种处理方式进一步扩展到对x的处理,即如果比较双方有一方的某些位的值是z或x,那么这些位的比较就不予考虑。
二、case/casez/casex 在simulation/synthesis的区别
网上说casex和casez属于不可综合语句,仅针对一般电路不会出现x状态来说的,但是综合工具并不会对x,z认识这个状态,所以综合出来的电路是一样的。
使用过程中许需要注意的问题:
1)一般经常使用到的是casez语句,最好少用casex
2)case/casez/casex其实都是可综合的
3)在电路中,可以用?来表示无关值的z
case的描述,匹配都是从上到下进行的
- // synthesis verilog_input_version verilog_2001
- module top_module (
- input [3:0] in,
- output reg [1:0] pos );
- always@(*)
- casex(in)
- 4'bxxx1:pos=0;
- 4'bxx1x:pos=1;
- 4'bx1xx:pos=2;
- 4'b1xxx:pos=3;
- default:pos=0;
- endcase
- endmodule
为 8 位输入构建优先级编码器。给定一个 8 位向量,输出应报告向量中第一个(最不重要)位 1。如果输入向量没有高位,则报告零。例如,输入 8'b10010000 应输出 3'd4,因为 bit[4] 是第一个高位。
从前面的练习(always_case2),案件陈述中有256个案例。如果支持的案例语句中的案例项不在乎位,我们可以将其减少到9个案例。这就是 z 的情况:它将值为 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;
endcaseend
case 语句的行为就像按顺序检查每个项目一样(实际上,这是一个大的组合逻辑函数)。请注意,某些输入(例如,4'b1111)将匹配多个案例项。选择第一个匹配项(因此 4'b1111 匹配第一个项目,out = 0,但不匹配任何后面的项目)。
还有一个类似的casex,将x和z都视为不在乎。我认为在 casez 上使用它没有多大意义。
数字 ? 是 z 的同义词。所以 2'bz0 与 2'b?0 相同
显式指定优先级行为而不是依赖于事例项的顺序可能不太容易出错。例如,如果对某些事例项重新排序,则以下项的行为方式仍相同,因为任何位模式最多只能匹配一个事例项:
casez(in[3:0])
4'bzzz1:...
4'bzz10:...
4'bz100:...
4'b1000:...
default:...
endcase
// synthesis verilog_input_version verilog_2001 module top_module ( input [7:0] in, output reg [2:0] pos ); always @(*) begin casez (in) 8'bzzzz_zzz1: pos = 0; 8'bzzzz_zz1z: pos = 1; 8'bzzzz_z1zz: pos = 2; 8'bzzzz_1zzz: pos = 3; 8'bzzz1_zzzz: pos = 4; 8'bzz1z_zzzz: pos = 5; 8'bz1zz_zzzz: pos = 6; 8'b1zzz_zzzz: pos = 7; default: pos = 0; endcase end endmodule
假设您正在构建一个电路来处理来自游戏的 PS/2 键盘的扫描码。鉴于收到的扫描码的最后两个字节,您需要指示是否已按下键盘上的箭头键之一。这涉及一个相当简单的映射,它可以实现为具有四个案例的案例语句(或 if-elseif)。
扫描码 [15:0] | 箭头键 |
16'HE06B | 左箭头 |
16'he072 | 向下箭头 |
16'he074 | 向右箭头 |
16'he075 | 向上箭头 |
别的东西 | 没有 |
电路有一个 16 位输入和四个输出。构建此电路,以识别这四个扫描码并断言正确的输出。
为避免产生锁存器,必须在所有可能的条件下为所有输出分配一个值(参见always_if2).仅仅有一个默认案例是不够的。您必须为所有四种情况和默认情况下的所有四个输出分配一个值。这可能涉及大量不必要的键入。解决此问题的一种简单方法是在 case 语句之前为输出分配一个“默认值”:
always @(*) begin
up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0;
case (scancode)
... // Set to 1 as necessary.
endcase
end
这种风格的代码可确保在所有可能的情况下为输出分配一个值(0),除非 case 语句覆盖赋值。这也意味着default:案例项变得不必要。
提醒:逻辑综合(The logic synthesizer)生成一个组合电路,其行为与代码描述的内容等效。硬件不会按顺序“执行”代码行。
// synthesis verilog_input_version verilog_2001 module top_module ( input [15:0] scancode, output reg left, output reg down, output reg right, output reg up ); always @(*) begin up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0; case (scancode) 16'he06b: left = 1'b1; 16'he072: down = 1'b1; 16'he074:right = 1'b1; 16'he075: up = 1'b1; endcase end endmodule
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。