北航计算机学院-计算机组成原理课程设计-2020秋
PreProject-Verilog HDL 与 ISE
Verilog题目实例与分析
本系列所有博客,知识讲解、习题以及答案均由北航计算机学院计算机组成原理课程组创作,解析部分由笔者创作,如有侵权联系删除。
从本节开始,课程组给出的教程中增添了很多视频讲解。为了避免侵权,本系列博客将不会搬运课程组的视频讲解,而对于文字讲解也会相应地加以调整,重点在于根据笔者自己的理解给出习题的解析。因此带来的讲解不到位敬请见谅。
组合电路题目描述
Verilog是一种硬件描述语言,简单来说就是用有语法结构的语句去描述电路并通过综合、仿真,得到我们想要的电路,而不用再聚焦于具体的电器元件。这一章节将主要利用Logisim教程中已经学习过的组合电路和时序电路两类电路,结合之前的语法知识,根据题目给出的信息进行分析,并描述目标电路,建立模型。
首先我们先说说简单的组合逻辑电路。
我们举一个简化ALU的例子。
模块规格:
题目说明:
通过给定的输入inputA,inputB以及op,根据功能输出out。
组合电路例题实现过程
题目分析:
题目中的规格已经确定了我们的输入与输出,并给定了相应的变量名称。我们只需要通过判断相应的op的取值,根据inputA与B,计算出out对应的取值。对于组合逻辑,一般不需要自己额外去定义新的变量,只需要将输出与输入的逻辑表达式给出就可以完成我们的任务了。
对于组合逻辑一般有两种实现方式:
- assign赋值语句。
- always+非阻塞赋值语句。
我们首先选择第一种方式。我们之前学过,assign语句是对wire类型进行赋值的,在题目中我们需要对out进行赋值,所以out的类型应该为wire类型,而对于assign右侧的变量没有要求。其次,由于不同的op会对应不同的out取值,我们可以利用三目运算符来完成操作。建立如下模型。
对于第二种方法,我们知道always@后面跟着的是敏感变量,如果某些值发生变化则执行always语句块。而对于always@()中的,敏感变量由综合器根据always里面的输入变量自动添加,不用自己考虑。
在always块中,我们可以选择利用case分支语句或者if分支语句对op进行判断,并利用always块保证对每次更新后的变量重新计算输出变量的值。同时由于always块中非阻塞赋值时左侧需要为reg类型,尽管我们知道实际电路out不会是寄存器,但是我们仍需要将其定义为reg类型。而对于右侧对数据类型没有严格的要求,所以用默认的wire即可实现。
虽然两种方法皆可以实现组合逻辑,但考虑接下来的任务,我们在此建议尽量使用assign赋值实现组合逻辑,而对always+非阻塞的实现方法做了解,只有在必要场景下使用后者:
-
assign赋值语句的结构清晰:结合三目运算符的多层嵌套调用(如上文代码所示)可以非常简洁地实现对应的组合逻辑。
-
always+非阻塞的实现方法容易产生混淆:方法容易与实现时序逻辑的非阻塞赋值混淆从而埋下问题,且实现的语句更为复杂。
因而我们建议:在不考虑综合后时序开销的情况下尽量用assign+三目的形式实现组合逻辑,从而保持always代码块中仅有非阻塞赋值语句。在考虑到时序开销的情况下(计组课设不对时序开销有任何严格约束),可以使用形如“assign result = (is_A & A) | (is_B & B);”的形式将连续三目表达式转换为并行的MUX
组合电路练习
组合电路练习1
我们在例题的基础上进行一定的更改
模块规格:
名称 | 功能简述 | 位宽 | 方向 |
---|---|---|---|
inA | 第一个运算数 | 4 | I |
inB | 第二个运算数 | 4 | I |
op | op为2’b00,输出inA & inB op为2’b01,输出inA | inB op为2’b10,输出inA ^ inB op为2’b11,输出inA + inB | 2 | I |
ans | 输出结果 | 4 | O |
模块名称:ALU
题目说明:
通过给定的输入inA,inB以及op,根据功能输出ans。
提示:可以根据已给出的代码进行更改,依旧推荐大家使用assign的方法。
本题是典型的纯组合逻辑,输出结果只由当前输入决定。由op信号选择运算方式,然后两个运算数进行相应的运算即可。
// ALU.v module ALU( input [3:0] inA, input [3:0] inB, input [1:0] op, output [3:0] ans ); assign ans = (op == 2'b00) ? inA & inB : (op == 2'b01) ? inA | inB : (op == 2'b10) ? inA ^ inB : (op == 2'b11) ? inA + inB : 4'b0000; endmodule
组合电路练习2
我们在例题的基础上进行一定的更改
模块规格:
名称 | 功能简述 | 位宽 | 方向 |
---|---|---|---|
inA | 第一个运算数 | 4 | I |
inB | 第二个运算数 | 4 | I |
inC | 第三个运算数 | 2 | I |
op | op为2’b00,输出inA 算数右移 inC位的结果 op为2’b01,输出inA 逻辑右移 inC位的结果 op为2’b10,输出inA - inB op为2’b11,输出inA + inB | 2 | I |
ans | 输出结果 | 4 | O |
模块名称:ALU
题目说明:
通过给定的输入inA,inB,inC以及op,根据功能输出ans。
提示:可以根据已给出的代码进行更改。注意对于算数右移,a算数右移b位,可以采用$signed($signed(a) >>> b)。
本题和上一题相似,使用assign语句和三目运算符构建组合逻辑即可。需要注意的是题目提示里所述,有符号数的算术右移需要特别添加$signed()来确保Verilog执行有符号数的运算。
// ALU.v module ALU( input [3:0] inA, input [3:0] inB, input [1:0] inC, input [1:0] op, output [3:0] ans ); assign ans = (op == 2'b00) ? $signed($signed(inA) >>> inC) : (op == 2'b01) ? inA >> inC : (op == 2'b10) ? inA - inB : (op == 2'b11) ? inA + inB : 4'b0000; endmodule
时序电路题目描述
时序电路题目分析
我们在理论课以及Logisim教程中都已经学习过了有限状态机,并了解了状态机可以分类为Moore型和Mealy型两种。而这里我们将使用Verilog搭建一个有限状态机,通过这个过程,对时序电路的建模进行分析。
我们举一个简单的例子:
题目说明:
小明正在学习数数,只有当完整的从1数到3才会得到正确的反馈(输出1),其中不能插入任何数字,对数字1之前的数字没有任何要求。若不满足条件,则都会输出0。端口定义如下
我们约定,在每个时钟上升沿的时候,状态机都会读入小明输入的数字,并对小明输入的数字进行判断,并确定是否输出正确的结果。
时序电路状态机分析
这是一道Moore型状态机的题目。我们首先对状态机进行简单的分析,具体的细节在Logisim章节中有详细的讲解。首先我们可以判断一共存在4个状态S0-S3,分别代表了没有读取到任何有意义的数字,读取了1,读取了12,以及读取了123。当我们判断出读取了123后,将会输出1,否则一直会输出0。我们同时需要考虑处于任意一个状态时,输入了1,2,3时,状态将会如何转移。
并将我们整理到的图转化为更容易表达的表格。
当我们用Logisim处理时,需要分别去考虑状态的每一位前后的变化,并写出布尔表达式。但对于Verilog,我们只用判断出状态的转移就可以了,不需要手动的写任何逻辑表达式。
时序电路例题实现过程
我们知道,对于时序逻辑,需要一个寄存器去保存当前状态,用reg类型进行存储。由于我们一共有四种状态,所以状态需要有至少两位进行保存。请注意,这里的reg类型与组合逻辑中利用always方法将out定义为reg类型有所不同,这里的状态在电路中就是一个寄存器。为了满足在“时钟上升沿”进行操作,我们需要在always中将敏感变量设置为posedge clk,来满足在此时完成我们对状态进行更新的操作。更新操作需要结合num和当前状态进行更新,所以可以选择switch分支语句或者if分支语句进行操作。
同时我们需要考虑如何处理我们的ans。在logisim中,我们讨论的“输出”模块就是我们去计算ans的部分,对应于此,我们最好用组合逻辑对其进行赋值。这里我们利用assign语句+三目运算符进行判断,细节和之前讲述的组合逻辑很相似,不过这里的输入就变成了我们的“当前状态”这个变量。至此,我们的模型大体分为了两个部分,第一部分在always语句块中对状态进行更新,第二部分是在输出模块中利用assign对ans进行赋值。
代码具体如下:
首先我们可以使用宏定义对不同状态进行定义,用2‘b00代表S0…2’b11代表S3,方便我们以后对状态进行判断。
其次我们需要对状态变量进行初始化,否则其初始值为x,无法进行以后的操作。
之后是我们的always块,这里只选取当状态为S0的判断。这里需要注意我们需要同时对状态以及输入的num进行判断,一定要使用非阻塞赋值防止不必要的bug。这里注意我们always的敏感变量的选取。
最后我们的输出模块,对ans根据当前的状态进行判断。这里和我们的表格存在一定的出入。表格中的ans为下一状态发生后的ans取值,也就是当当前状态为S2,num为3时,下一状态为S3,同时一下个状态发生时我们的ans取1。而在verilog中由于是对当前的状态与ans进行分析,所以就当状态为S3时,ans取1。
最后我们利用仿真查看结果。(具体仿真教程将会在下一节进行讲解)
通过上述分析,我们的模块简单分为了3个部分,初始化,状态更新,输出更新,而分别利用initial块,always块以及assign语句完成。而为了降低我们书写程序的复杂性,我们引用了宏定义的对不同状态进行了化简表达。
对于大部分的状态机,关键点在于寻找到所有的状态,并正确的完整状态迁移,同时要处理好always的敏感变量,剩下的书写部分并不存在很大的难度。
如果同学自己动手尝试,会发现这个模块无法进行综合,也就是不能得到我们目标电路。这是由于在Verilog中一些模块是无法被综合的,而我们使用的initial就是其中之一。如果我们想对其进行综合,可以引入reset输入,当其置1时,需要将状态机进行复位,也就是状态被赋值为S0,而避免了手动初始化的尴尬局面,感兴趣的同学可以自己动手尝试一下。对于综合操作,在前7个project中不会应用,而会在P8中进行应用。所以我们不需要强求一定要写出可以综合的模块出来。
其实我们可以发现,Verilog作为硬件描述语言,我们目前是在行为级进行的描述,也就是描述模块完成了什么任务。通常是将模块分解为对应时序的always模块以及对应组合逻辑的assign赋值,并根据需求加入初始化的initial模块。我们并没有像Logisim中过分纠结使用具体什么电器元件,而是通过对模块进行抽象的行为层次的描述让EDA工具帮助我们综合出我们想要的电路。
最后我们书写完模块后,去验证模块的正确性需要利用ISim的仿真与调试功能,这将会在下一个章节进行讲述。
时序电路练习
时序电路练习1
小明依旧在学习数数,但是这次测评机有了一定的变化,只要小明曾经数出过连续的123,就会输出正确的反馈(即输出将恒为1)。
我们可以根据题目画出相应的状态转移图。
端口要求:
名称 | 功能简述 | 位宽 | 方向 |
---|---|---|---|
num | 小明输入的数字 | 2 | I |
clk | 时钟信号 | 1 | I |
ans | 对小明输入的数字进行反馈,只要序列包含完整的1,2,3后输出1,否则输出0 | 1 | O |
模块名称:counting
功能说明:
每个clk上升沿到来的瞬间,从num读入一个数字d。输入的数字对应d的二进制编码(01对应1,10对应2,11对应3。规定不会输入00)。我们对输入的数字串进行判断,如果输入的序列包含完整的123,无论上一个周期是否为3,输出信号都为1,否则输出0。
可以利用上一节样例进行更改。
输入:1 1 2 1 2 1 3 1 2 3 1 2 1 2 3 1
输出:0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1
注意模块名称和端口定义,并且提交相应的.v文件。
本题是上面例程问题的修改,其状态转移图已经给出,参照例程修改状态转移逻辑即可。具体的模块设计和注释如下:
// counting.v module counting( input [1:0] num, input clk, output ans ); reg [1:0] status = 2'b00; // A register to store current status. Initial status should be set 2'b00. // status: 2'b00 -> counting nothing; // 2'b01 -> counting 1; // 2'b10 -> counting 12; // 2'b11 -> counting 123. /* current status | input | next status | ans 2'b00 | 1 | 2'b01 | 0 2'b00 | 2 | 2'b00 | 0 2'b00 | 3 | 2'b00 | 0 2'b01 | 1 | 2'b01 | 0 2'b01 | 2 | 2'b10 | 0 2'b01 | 3 | 2'b00 | 0 2'b10 | 1 | 2'b01 | 0 2'b10 | 2 | 2'b00 | 0 2'b10 | 3 | 2'b11 | 1 2'b11 | 1 | 2'b11 | 1 2'b11 | 2 | 2'b11 | 1 2'b11 | 3 | 2'b11 | 1 */ always @(posedge clk) begin if (status == 2'b00) begin if (num == 2'b01) status <= 2'b01; else status <= 2'b00; end else if (status == 2'b01) begin if (num == 2'b01) status <= 2'b01; else if (num == 2'b10) status <= 2'b10; else status <= 2'b00; end else if (status == 2'b10) begin if (num == 2'b01) status <= 2'b01; else if (num == 2'b11) status <= 2'b11; else status <= 2'b00; end else if (status == 2'b11) begin status <= 2'b11; end else status <= 2'b00; end assign ans = (status == 2'b11) ? 1'b1 : 1'b0; endmodule
时序电路题目2
小明依旧在学习数数,但是这次测评机有了一定的变化,小明在数到正确的数字x后,可以继续重复数x。当小明成功从1数到3后,就会输出正确的反馈(即输出为1)。
我们可以根据题目画出相应的状态转移图。
端口要求:
名称 | 功能简述 | 位宽 | 方向 |
---|---|---|---|
num | 小明输入的数字 | 2 | I |
clk | 时钟信号 | 1 | I |
ans | 对小明输入的数字进行反馈,若正确数到3则输出1,否则输出0 | 1 | O |
模块名称:counting
功能说明:
每个clk上升沿到来的瞬间,从num读入一个数字d。输入的数字对应d的二进制编码(01对应1,10对应2,11对应3。规定不会输入00)。我们对输入的数字串进行判断,若输入序列从某一数字开始(即后缀),存在1…12…23…3,其中对于任意数字都出现大于等于1次(正则表达式1+2+3+),即输出1。否则输出0。
正则表达式: +:匹配前面的表达式一次或多次。
正则表达式仅供参考,感兴趣的同学可以深入了解。可以利用状态迁移图完成题目。
可以利用上一节样例进行更改。
输入:1 1 2 3 3 1 2 1 1 1 2 2 3 3 3 1
输出:0 0 0 1 1 0 0 0 0 0 0 0 1 1 1 0
注意模块名称和端口定义,并且提交相应的.v文件。
本题和上一题情况相同,只是修改了状态转移,无需赘述。代码和注释如下:
// counting.v module counting( input [1:0] num, input clk, output ans ); reg [1:0] status = 2'b00; // A register to store current status. Initial status should be set 2'b00. // status: 2'b00 -> counting nothing; // 2'b01 -> counting 1; // 2'b10 -> counting 12; // 2'b11 -> counting 123. /* current status | input | next status | ans 2'b00 | 1 | 2'b01 | 0 2'b00 | 2 | 2'b00 | 0 2'b00 | 3 | 2'b00 | 0 2'b01 | 1 | 2'b01 | 0 2'b01 | 2 | 2'b10 | 0 2'b01 | 3 | 2'b00 | 0 2'b10 | 1 | 2'b01 | 0 2'b10 | 2 | 2'b10 | 0 2'b10 | 3 | 2'b11 | 1 2'b11 | 1 | 2'b01 | 0 2'b11 | 2 | 2'b00 | 0 2'b11 | 3 | 2'b11 | 1 */ always @(posedge clk) begin if (status == 2'b00) begin if (num == 2'b01) status <= 2'b01; else status <= 2'b00; end else if (status == 2'b01) begin if (num == 2'b01) status <= 2'b01; else if (num == 2'b10) status <= 2'b10; else status <= 2'b00; end else if (status == 2'b10) begin if (num == 2'b01) status <= 2'b01; else if (num == 2'b10) status <= 2'b10; else if (num == 2'b11) status <= 2'b11; else status <= 2'b00; end else if (status == 2'b11) begin if (num == 2'b01) status <= 2'b01; else if (num == 2'b11) status <= 2'b11; else status <= 2'b00; end else status <= 2'b00; end assign ans = (status == 2'b11) ? 1'b1 : 1'b0; endmodule