Verilog预习笔记

Verilog基础语法

数据类型

wire型
类比为Logisim电路中的导线,用来输出组合逻辑信号,本身没有存储功能。一般用assign对wire进行驱动。
可以在声明时说明wire的长度。如wire [31:0] a就是32位二进制wire变量a。(方括号内左侧为最高位,右侧为最低位),不说明就默认是1位二进制变量。

reg型
类比为Logisim电路中的寄存器,具有存储功能,一般在always块中用reg变量存储并改变某些值。reg型变量无法使用assign赋值。
reg型变量不一定只能建模时序逻辑,也可以和always配合构成组合逻辑。
reg型变量的长度声明和wire型相同。
可以对reg型变量建立数组,例如reg [31:0] mem [0:1023],同时可以用引用操作访问reg型数据元素,比如mem[2]就是访问mem中第三个元素。

数字字面量
格式是<位宽>’<进制><值>,进制的表达是:二进制:B/b、八进制:O/o、十六进制:H/h、十进制:D/d。举例:10’d100
当没有输入位宽和进制时,就默认是三十二位十进制值,所以尽量完整写入。
还有两个特殊值:x表示不定值,变量的默认初始值都是x,z是高阻态,证明没有连接到有效输入。当数据位宽大于1时,x和z都可以只在部分位出现(和Logisim也相似)

integer型
类似C中的int型,默认为符号数,一般用来参与for循环

parameter型
用来给出编译的时候一些常量(有点类似define),格式是parameter 标识符 = 表达式;,例如parameter width = 8;,则后续将width字符都转化为8。
便于在结构相似,位宽不同的模块之间实现代码复用,例如在有限状态机中可以拿来定义状态。

组合逻辑语法

assign
用一个信号来驱动另一个信号,例如assign a = b;就是用b驱动a,a必须是wire型,而b是表达式。
这里和C语言的赋值以及always中的赋值都不同,可以类比电路中导线的连接,确保了a的值时刻等于b。因此,不能出现assign a = a + 1;,同时也不能在always和initial中使用。

常用运算符:

  1. 基本运算符:+, -, * , /, % 等

  2. 位运算符:&, |, ~, ^, >>, << 等

  3. 逻辑运算符:&&, ||, ! 等

  4. 关系运算符:>, <, >=, <= 等

  5. 条件运算符:? :

几个和C中不同的点:

1.逻辑右移运算符 >> 与算术右移运算符 >>>
它们的区别主要在于前者在最高位补 0,而后者在最高位补符号位。

2.相等比较运算符 == 与 === 和 != 与 !==
== 和 != 可能由于不定值 x 和高阻值 z 的出现导致结果为不定值 x,而 === 和 !== 的结果一定是确定的 0 或 1(x 与 z 也参与比较)。

3.阻塞赋值 = 和非阻塞赋值 <=
不同于 assign 语句,这两种赋值方式被称为过程赋值,通常出现在 initial 和 always 块中,为 reg 型变量赋值。这种赋值类似 C 语言中的赋值,不同于 assign 语句,赋值仅会在一个时刻执行。为了写出正确、可综合的程序,在描述时序逻辑时要使用非阻塞式赋值 <= 。

4.位拼接运算符 {}
这个运算符可以将几个信号的某些位拼接起来,例如 {a, b[3:0], w, 3’b101};;可以简化重复的表达式,如 {4{w}} 等价于 {w,w,w,w};还可以嵌套,{b, {3{a, b}}} 等价于 {b, {a, b, a, b, a, b}},也就等价于 {b, a, b, a, b, a, b}。

5.缩减运算符
运算符 &(与)、|(或)、^(异或)等作为单目运算符是对操作数的每一位汇总运算,如对于 reg[31:0] B; 中的 B 来说,&B 代表将 B 的每一位与起来得到的结果。

有符号数的处理方法:

就是利用$signed()函数,由于wire,reg等都默认无符号,所以需要用到此函数作为转换函数。

需要注意的是,当一个操作中同时含有符号数和无符号数时,verilog自动将符号数向无符号数进行转化,所以需要注意signed函数是否操作成功(尤其是在三目运算符相关计算中!)

还有,对于移位运算,其右侧的操作数永远被视为无符号数,所以不会对运算结果符号性产生影响,可以出现式子中同时有符号数和无符号数的情况。

宏定义:
为了和一般语句区别,编译预处理命令以反引号开头,用指定标识符替代字符串。一般形式是: `define 标识符(宏名) 字符串(宏内容)

时序逻辑语法

always:
用法1:若 always 之后紧跟 @(…),其中括号内是敏感条件列表,表示当括号中的条件满足时,将会执行 always 之后紧跟的语句或顺序语句块。这种用法主要用于建模时序逻辑。
用法2:若 always 之后紧跟 @ * 或 @( * ),则表示对其后紧跟的语句或语句块内所有信号的变化敏感。这种用法主要用于与 reg 型数据和阻塞赋值配合,建模组合逻辑。
用法3:若 always 紧跟语句,则表示在该语句执行完毕之后立刻再次执行。这种用法主要配合后面提到的时间控制语句使用,来产生一些周期性的信号。
(注意,敏感表中如果是a(举例),代表a发生变化的时候执行,如果是posedge a,代表a达到上升沿(由0变1)执行,如果是negedge则相反)
不要在多个always中对同一个变量赋值!

1
2
3
4
always @(posedge clk)  // 表示在 clk 上升沿触发后面的语句块
begin
// 一些操作
end

initial:
initial 块后面紧跟的语句或顺序语句块在硬件仿真开始时就会运行,且仅会运行一次,一般用于对 reg 型变量的取值进行初始化。initial 块通常仅用于仿真,是不可综合的。

1
2
3
4
reg a;
initial begin
a = 0;
end

if:
if语句只能出现在顺序块中,和C语言中的写法非常类似,只是把大括号变成了begin/end。

1
2
3
4
5
6
7
8
always @ * begin
if (a > b) begin
out = a;
end
else begin
out = b;
end
end

case:
case语句也只能出现在顺序块中,分支也是语句和顺序块。Verilog中一旦程序执行完一个分支,不会继续落入下一个分支,而是会自动退出。
还有一点,case执行的是全等比较,包括x位和z位都相等才能被认为相等。

1
2
3
4
5
6
7
8
9
10
11
always @(posedge clk) begin
case(data)
0: out <= 4;
1: out <= 5;
2: out <= 2;
3: begin
out <= 1;
end
default: ;
endcase
end

for,while:
这两个的写法基本等同C语言,只是用begin/end替换花括号。
对于for,可以定义一个Integer类型变量作为循环变量。

时间控制语句(#):
一般在testbranch文件中使用,用来测试波形,其写法如下:

1
2
3
4
#3;         // 延迟 3 个时间单位
#5 b = a; // b 为 reg 型,延迟 5 个时间单位后执行赋值语句
always #5 clk = ~clk; // 每过 5 个时间单位触发一次,时钟信号反转,时钟周期为 10 个时间单位
assign #5 b = a; // b 为 wire 型,将表达式右边的值延时 5 个时间单位后赋给 b

阻塞赋值和非阻塞赋值:
处在一个 always 块中的非阻塞赋值是在块结束时同时并发执行的。对于 ISim,在每一条非阻塞赋值执行前,仿真器“按下快门”保存下了在 <= 右边参与运算的变量值。在块结束进行赋值时,对于 <= 左边被赋值的变量,都是用“快照”中的值参与运算的。
阻塞赋值语句的执行是具有明确顺序关系的,在 begin - end 的顺序块中,当前一句阻塞赋值完成后(即 = 左边的变化为右边的值后),下一条阻塞赋值语句才会被继续执行。
一个例子:

1
2
3
4
5
6
7
8
9
10
// 阻塞赋值
always @(posedge clk) begin
b_blocked = a;
c_blocked = b_blocked;
end
// 非阻塞赋值
always @(posedge clk) begin
b_non_blocked <= a;
c_non_blocked <= b_non_blocked;
end

非阻塞赋值波形:

阻塞赋值波形:

有限状态机的verilog形式

其实好像没什么好说的,verilog中甚至不需要考虑储存问题,就在always中刻画状态转移电路,然后assign就是输出逻辑判断。唯一的要点可能是分辨一下moore型和meely型状态机的写法:

1
2
3
4
// 摩尔型
assign zo = (state==S4) ? 1’b1 : 1’b0 ;
// 米里型
assign zo = (state==S3) & (data==1’b0) ;

一些题目

Q1.旅鼠

  • 题面

(英文原文)

In addition to walking and falling, Lemmings can sometimes be told to do useful things, like dig (it starts digging when dig=1). A Lemming can dig if it is currently walking on ground (ground=1 and not falling), and will continue digging until it reaches the other side (ground=0). At that point, since there is no ground, it will fall (aaah!), then continue walking in its original direction once it hits ground again. As with falling, being bumped while digging has no effect, and being told to dig when falling or when there is no ground is ignored.

(In other words, a walking Lemming can fall, dig, or switch directions. If more than one of these conditions are satisfied, fall has higher precedence than dig, which has higher precedence than switching directions.)

Extend your finite state machine to model this behaviour.

(网页翻译)

除了走路和摔倒之外,有时还可以告诉Lemmings做一些有用的事情,比如挖(当dig=1时它开始挖掘)。如果旅鼠目前在地面上行走(地面=1且不下落),则可以挖掘,并且将继续挖掘,直到到达另一侧(地面= 0)。在这一点上,由于没有地面,它会掉落(啊!),然后一旦它再次撞击地面,就继续沿着原来的方向行走。与坠落一样,在挖掘时被撞到没有效果,并且在跌倒或没有地面时被告知要挖掘会被忽略。

(换句话说,行走的旅鼠可能会摔倒,挖掘或改变方向。如果满足这些条件中的多个,则下降的优先级高于挖掘,挖掘的优先级高于切换方向。

扩展有限状态机以对此行为进行建模。

波形:

思路:

主要就是构造一个有限状态机,其中有四个状态,也就是左走,右走,挖地以及悬空(惨叫aaah),我感觉本题都没有必要严谨的列出状态转移图,根据题意进行翻译即可。我的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
if(state == 2'b00) begin //left
if(ground == 0)
state <= 2'b11;
else if(ground == 1 && dig == 1)
state <= 2'b10;
else if(bump_left == 1) begin
state <= 2'b01;
directionState <= 1'b1;
end
end
else if(state == 2'b01) begin //right
if(ground == 0)
state <= 2'b11;
else if(ground == 1 && dig == 1)
state <= 2'b10;
else if(bump_right == 1) begin
state <= 2'b00;
directionState <= 1'b0;
end
end
else if(state == 2'b10) begin //dig
if(ground == 0)
state <= 2'b11;
end
else begin //aaah
if(ground == 1) begin
if(directionState == 1'b0)
state <= 2'b00;
else
state <= 2'b01;
end
end

具体的状态转移就是:如果左走时,若没有地就坠落,若挖地信号为1且有地可挖则挖地,若撞墙就换方向(右走同理)。
如果挖地,如果没有地就开始坠落,否则继续挖地。
如果下坠,只要有地就落地,且根据老方向走。

几个细节:

  1. 本题的areset信号是异步复位(我猜的,结果确实是),所以要将其写入敏感表并优先判断。
  2. 由于下坠落地时要根据老方向选择左走或者右走,所以需要多设定一个寄存器用来存储上一次走路的方向。我这里用的是directionState变量。同时复位的时候这个寄存器也要复位。
  3. 注意操作的优先级。根据题目所说,坠落的优先级高于挖地而又高于左右走,所以要注意在左右走状态中:优先判断是否坠落(ground)然后判断是否挖地(dig)最后判断是否撞墙(bump_left/right)。也就是这几个if-else语句的顺序必须严格按照上面写的顺序,否则优先级出错。
  4. 根据波形,这题是一个Moore状态机,只需要根据当前状态进行判断,所以最后利用assign语句进行判断输出即可,不需要考虑当前输入。注意assign不在顺序块中,无法用if等,所以要利用三目运算符判断输出(此处略)

Q2.FSM

  • 题面

(英文原文)
Synchronous HDLC framing involves decoding a continuous bit stream of data to look for bit patterns that indicate the beginning and end of frames (packets). Seeing exactly 6 consecutive 1s (i.e., 01111110) is a “flag” that indicate frame boundaries. To avoid the data stream from accidentally containing “flags”, the sender inserts a zero after every 5 consecutive 1s which the receiver must detect and discard. We also need to signal an error if there are 7 or more consecutive 1s.

Create a finite state machine to recognize these three sequences:

0111110: Signal a bit needs to be discarded (disc).
01111110: Flag the beginning/end of a frame (flag).
01111111…: Error (7 or more 1s) (err).
When the FSM is reset, it should be in a state that behaves as though the previous input were 0.

(网页翻译)
同步 HDLC 成帧涉及解码连续的位数据流,以查找指示帧(数据包)开始和结束的位模式。正好看到6个连续的1(即01111110)是一个指示框架边界的“标志”。为避免数据流意外包含“标志”,发送方在每 5 个连续 1 秒后插入一个零,接收方必须检测并丢弃该零。如果有 7 个或更多连续的 1,我们还需要发出错误信号。

创建一个有限状态机来识别这三个序列:

0111110:需要丢弃一个位的信号(光盘)。
01111110:标记框架的开头/结尾(标志)。
01111111…: 错误 (7 个或更多 1 秒) (错误)。
重置 FSM 时,它应处于行为类似于上一个输入为 0 的状态。

  • 波形

(0111110)

(01111110)

(011111110,error)

  • 思路

首先给出状态转移图:

有几个需要注意的点:

首先,本体的复位状态和开始状态是不同的。复位状态是复位到只有一个0的状态,而开始状态是什么都没有的状态,相当于开始时需要把状态设定为上面“wrong”的状态。(本题我的状态编码就是上图逆时针编码,0为状态0,wrong为状态5)

1
2
3
4
initial begin
state = 3'b101;
one_amount = 3'b000;
end

这里面也包括了第二个点:01{1,5}这个状态是不用分成五个状态的,我这里采取的方式是用另外一个寄存器one_amount存储1的数量。这个时候最重要的就是这个状态的转移代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
else if(state == 3'b1) begin
if(in == 1) begin
if(one_amount == 5) begin //*
state <= 3'b11;
one_amount <= one_amount + 1;
end
one_amount <= one_amount + 1;
end
else begin
if(one_amount == 5) begin
state <= 3'b10;
one_amount <= 0;
end
else begin
state <= 3'b0;
one_amount <= 0;
end
end
end

首先对输入做出判断,如果输入是1,那么就看1有多少个,如果少于5个就停留在此状态,等于五个,那么就转移到0111111的状态。如果输入是0,那么如果1有5个,那么就是0111110的输出,而更少的时候就直接进入只有一个0的状态(注意这里不是进入wrong状态)。

还有一点就是注意星号处的写法!我一开始是先给one_amount加一再对其是否等于6判断,但是要注意这样是绝对不行的!因为verilog是并行的而非串行。相当于即使你在写法上先加再判断,但其实是同一时间。那么就会变成判断和加同时,判断的是加之前的情况。这样就会检测的时候多一个1。一定要注意verilog的并行性!

最后就是正常的assign赋能输出。

1
2
3
assign disc = (state == 3'b10) ? 1 : 0;
assign flag = (state == 3'b100) ? 1 : 0;
assign err = (state == 3'b101) ? 1 : 0;

Q3 check110011

  • 题面

使用Verilog搭建一个有限状态机检测110011序列并提交。

模块定义端口:

模块功能定义:

  • 波形

输入:X = 1 1 0 0 1 1 0 0 1 1
输出:Z = 0 0 0 0 0 1 0 0 0 1

  • 注意事项

1100110011序列和11001110011序列都看做是两条110011序列,即需要输出两次1。

  • 思路

其实我感觉有限状态机的简单题同质化严重,都是画个状态转移图,利用if-else结构搭出状态转移逻辑,最后利用assign进行判断,只是转移或者判断的时候有一些小小的限制罢了。本题的状态转移如图:

其中注意几个点,首先是状态转移的时候要对最后一个110011的状态多注意,正如注意事项中所说,110011若后跟0就应该进入110状态,而跟1则进入11状态。其他的状态转移还是比较简单。

1
2
3
4
5
6
else if(state == 3'b110) begin
if(in == 0)
state <= 3'b11;
else
state <= 3'b10;
end

还有就是本题是异步复位,而且通过其功能我们可以发现本题的复位信号的优先级是最高的,所以我们可以用If-else语句在顺序块中优先判断是否需要复位,这样可以保证其最高优先级:

1
2
3
4
5
6
always @(posedge clk or posedge reset) begin
if(reset == 1)
state <= 3'b0;
else begin
//...
end

最后就是输出,根据波形我们知道这道题是Moore型状态机,因此只需要对状态进行判断而不需要考虑输入,利用assign:

1
assign out = (state == 3'b110) ? 1 : 0;

感觉简单的状态机的verilog写法确实非常简便而且固定。