【HDLbits刷题笔记 】04-verilog语法-程序部分

Always块(组合)

由于数字电路由用导线连接的逻辑门组成,因此任何电路都可以表示为模块和分配语句的某种组合。但是,有时这不是描述电路的最方便方法。过程(始终以块为例)提供了描述电路的替代语法。

对于合成硬件(synthesizing hardware),两种类型的始终块是相关的:

  • 组合:always @(*)

  • 时钟:always @(posedge clk)

组合always块等价于assign语句,因此总有一种方法可以双向表达组合电路。选择使用哪种语法主要是哪种语法更方便的问题。过程块内代码的语法与过程块外部的代码不同。过程块具有更丰富的语句集(例如,if-then,case),不能包含连续赋值,但也引入了许多新的非直观错误方式。*(*过程连续赋值确实存在,但与连续赋值有些不同,并且不可合成。

例如,assign和combinational always块描述相同的电路。两者都创建相同的组合逻辑。每当任何输入(右侧)更改值时,两者都将重新计算输出。

对于组合always块,始终使用敏感度列表 (*)。明确列出信号容易出错(如果您错过了一个信号),并且在硬件合成中被忽略。如果显式指定灵敏度列表并错过信号,则合成的硬件仍将像指定 (*) 一样,但模拟(simulation)不会与硬件的行为不匹配。(在 SystemVerilog 中,使用 always_comb。)

关于 wire vs. reg 的说明:assign 语句的左侧必须是网络类型(例如,wire),而过程赋值的左侧(在 always 块中)必须是变量类型(例如,reg)。这些类型(wire vs. reg)与合成的硬件无关,只是Verilog用作硬件模拟语言时遗留下来的语法。

一点练习

使用 assign 语句和组合always块构建 AND 门。(由于赋值语句和组合块的功能始终相同,因此无法强制使用这两种方法。但你是来练习的,对吧?...)

Always块(时钟)

对于硬件综合,有两种类型的始终块是相关的:

  • 组合: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模块产生的电路与其他两个不同:有一个触发器,因此输出延迟。

If 语句

if 语句通常创建一个 2 选 1 多路复用器( 2-to-1 multiplexer),如果条件为真,则选择一个输入,如果条件为假,则选择另一个输入。

always @(*) begin
if (condition) begin
out = x;
end
else begin
out = y;
end
end

这等效于使用带有条件运算符的连续赋值:

但是,过程 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

If statement latches

常见的错误来源:如何避免创造latches(锁存器)

在设计电路时,您必须首先考虑电路:

  • 我想要这个逻辑门

  • 想要一个具有这些输入并产生这些输出的组合逻辑 blob

  • 我想要一个组合逻辑块,然后是一组触发器

你不能做的是先写代码,然后希望它产生一个正确的电路。

  • 如果 (cpu_overheated) 则 shut_off_computer = 1;

  • 如果 (~arrived) 则 keep_driving = ~gas_tank_empty;

语法正确的代码不一定会产生合理的电路(组合逻辑+触发器)。通常的原因是:“在您指定的情况之外,会发生什么?Verilog的答案是:保持输出不变。

这种“保持输出不变”的行为意味着需要记住当前状态,从而产生锁存器。组合逻辑(例如逻辑门)无法记住任何状态。注意警告 (10240): ...推断闩锁“消息。除非闩锁是故意的,否则它几乎总是表示存在错误。组合电路必须在所有条件下为所有输出分配一个值。这通常意味着您始终需要 else 子句或分配给输出的默认值。

Latch 的含义

锁存器(Latch),是电平触发的存储单元,数据存储的动作取决于输入时钟(或者使能)信号的电平值。仅当锁存器处于使能状态时,输出才会随着数据输入发生变化。

当电平信号无效时,输出信号随输入信号变化,就像通过了缓冲器;当电平有效时,输出信号被锁存。激励信号的任何变化,都将直接引起锁存器输出状态的改变,很有可能会因为瞬态特性不稳定而产生振荡现象。

锁存器示意图如下:

触发器(flip-flop),是边沿敏感的存储单元,数据存储的动作(状态转换)由某一信号的上升沿或者下降沿进行同步的(限制存储单元状态转换在一个很短的时间内)。

触发器示意图如下:

寄存器(register),在 Verilog 中用来暂时存放参与运算的数据和运算结果的变量。一个变量声明为寄存器时,它既可以被综合成触发器,也可能被综合成 Latch,甚至是 wire 型变量。但是大多数情况下我们希望它被综合成触发器,但是有时候由于代码书写问题,它会被综合成不期望的 Latch 结构。

Latch 的主要危害有:

  • 1)输入状态可能多次变化,容易产生毛刺,增加了下一级电路的不确定性;

  • 2)在大部分 FPGA 的资源中,可能需要比触发器更多的资源去实现 Latch 结构;

  • 3)锁存器的出现使得静态时序分析变得更加复杂。

Latch 多用于门控时钟(clock gating)的控制,一般设计时,我们应当避免 Latch 的产生。

if 结构不完整

组合逻辑中,不完整的 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

Case 语句

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)

Priority encoder优先级编码器

优先级编码器是一种组合电路,当给定输入位矢量时,输出矢量中第一个为1的比特的位置。例如,给定输入 8'b10010000 的 8 位优先级编码器将输出 3'd4,因为 bit[4] 是第一个高位。

构建 4 位优先级编码器。对于此问题,如果输入位都不高(即输入为零),则输出为零。请注意,一个 4 位数字有 16 种可能的组合。

case casez casex

在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

  1. case的描述,匹配都是从上到下进行的

Priority encoder with casez

为 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

Avoiding latches

假设您正在构建一个电路来处理来自游戏的 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)生成一个组合电路,其行为与代码描述的内容等效。硬件不会按顺序“执行”代码行。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

绿茶冰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值