HDLBits 是一组小型电路设计习题集,使用 Verilog/SystemVerilog 硬件描述语言 (HDL) 练习数字硬件设计~
网址如下:
https://hdlbits.01xz.net/
关于HDLBits的Verilog实现可以查看下面专栏:
https://www.zhihu.com/column/c_1131528588117385216
缩略词索引:
SV:SystemVerilog
Problem 28-Alwaysblock1
从这一题开始我们将进行过程块的学习,也就是时序和组合逻辑的一些知识,下面简单介绍一下这方面知识:
由于数字电路是由用导线连接的逻辑门组成的,因此任何电路都可以表示为module和assign语句的某种组合。然而,有时这并不是描述电路的最方便的方式。过程块(比如always块)提供了一种用于替代assign语句描述电路的方法。
对于可综合硬件,有两种类型的 always :
组合逻辑:always @(*) 时序逻辑:always @(posedge clk)
组合always块相当于assign语句,因此组合电路存在两种表达方法。选择使用哪个主要是哪个语法更方便的问题。程序块内部代码的语法与外部代码不同。程序块具有更丰富的语句集(例如,if-then、case),不能包含连续赋值,但是却引入了许多新的非直观的出错方式。 (程序连续赋值确实存在,但与连续赋值有些不同,并且不可综合。)
例如,assign和组合always块描述相同的电路。两者都创建了相同的组合逻辑块。每当任何输入(右侧)更改值时,两者都会重新计算输出。
assign out1 = a & b | c ^ d; always @(*) out2 = a & b | c ^ d;图片来自 HDLBits
对于组合always块,敏感变量列表总是使用(*)。如果把所有的输入都列出来也是可以的,但容易出错的(可能少列出了一个),并且在硬件综合时会忽略您少列了一个,仍按原电路综合。 但仿真器将会按少列一个来仿真,这导致了仿真与硬件不匹配。(在SystemVerilog中,使用always_comb)
关于 wire 与 reg 的注意事项:assign 语句的左侧必须是net类型(例如,wire),而过程赋值(在 always 块中)的左侧必须是变量类型(例如,reg)。这些类型(wire vs. reg)与合成的硬件无关,只是 Verilog 用作硬件模拟语言时留下的语法。
题目说明
使用 assign 语句和组合 always 块构建 AND 门。
图片来自 HDLBits
模块端口声明
// synthesis verilog_input_version verilog_2001 module top_module( input a, input b, output wire out_assign, output reg out_alwaysblock );
题目解析
这个题目重点是使用assign和always两种方式描述一个AND门,整体难度不大。
module top_module( input logic a, input logic b, output wire logic out_assign, output var logic out_alwaysblock ); assign out_assign = a&b ; always_comb begin out_alwaysblock = a&b ; end endmodule
always_comb程序有很多好处,其中之一便是能自动推断出完整的敏感列表。该列表是完全完整的,避免了@*推断不完整敏感列表的极端情况。
点击Submit,等待一会就能看到下图结果:
注意图中的Ref是参考波形,Yours是你的代码生成的波形,网站会对比这两个波形,一旦这两者不匹配,仿真结果会变红。
这一题就结束了。
Problem 29-Alwaysblock2
阻塞性赋值和非阻塞性赋值
在Verilog中有以下三种赋值方法:
连续赋值(assign x=y;):不能在过程块内使用;
过程阻塞性赋值(x=y;):只能在过程块中使用;
过程费阻塞性复制(x<=y):只能在过程块内使用。
在组合always块中,使用阻塞性赋值。在时序always块中,使用非阻塞性赋值。具体为什么对设计硬件用处不大,还需要理解Verilog模拟器如何跟踪事件(的确是这样,记住组合用阻塞性,时序用非阻塞性就可以了)。不遵循此规则会导致极难发现非确定性错误,并且在仿真和综合出来的硬件之间存在差异。
题目说明
以三种方式构建异或门,使用assign语句、组合always块和时序always块。请注意,时序always块产生的电路与其他两个不同:有一个触发器,因此输出被延迟。
图片来自 HDLBits
模块端口声明
// 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 );
题目解析
这道题难度不大,主要时区分三种方式。
module top_module( input logic clk, input logic a, input logic b, output wire logic out_assign, output logic out_always_comb, output logic out_always_ff ); assign out_assign = a ^ b; always_comb begin out_always_comb = a ^ b; end always_ff@(posedge clk) begin out_always_ff <= a ^ b; end endmodule
点击Submit,等待一会就能看到下图结果:
从仿真的波形图可以看出,out_always_ff比其他两个输出延迟了一个时钟周期,这就是非阻塞性赋值带来的。
注意图中的Ref是参考波形,Yours是你的代码生成的波形,网站会对比这两个波形,一旦这两者不匹配,仿真结果会变红。
这一题就结束了。
Problem 30-Always if
if语句通常创建一个2 对 1 多路复用器,如果条件为真则选择一个输入,如果条件为假则选择另一个输入。
下面给出了一个基本的if语句和其综合出来的电路。
always @(*) begin if (condition) begin out = x; end else begin out = y; end end
这与下面使用条件运算符连续赋值的语句是等价的:
assign out = (condition) ? x : y;
但是,过程if语句使用不当可能会引入新的错误,只有out在所有的条件下都被赋值才会生成正确的组合电路。
题目说明
构建一个在a和b之间进行选择的 2 对 1 多路复用器。如果sel_b1和sel_b2都为真,则选择b 。其他情况输出a。请使用两种方法作答,一次使用assign赋值,一次使用if语句。
模块端口声明
module top_module( input a, input b, input sel_b1, input sel_b2, output wire out_assign, output reg out_always );
题目解析
本题考查的是if语句的简单用法,使用条件运算符作为对比,入门练习题。
module top_module( input logic a,b, input sel_b1,sel_b2, output wire logic out_assign, output logic out_always ); assign out_assign = (sel_b1 & sel_b2)?b:a; always_comb begin if (sel_b1 & sel_b2) out_always = b; else out_always = a; end endmodule
点击Submit,等待一会就能看到下图结果:
注意图中的Ref是参考波形,Yours是你的代码生成的波形,网站会对比这两个波形,一旦这两者不匹配,仿真结果会变红。
这一题就结束了。
Problem 31-Always_if2
常见的错误来源:如何避免引入锁存器
在设计电路时,必须首先具体考虑电路:
1、我想实现一个逻辑门;
2、我想实现一个具有输入并产生输出的组合逻辑块;
3、我想实现一组组合逻辑,紧接着一组触发器。
不要上来就写代码,这样往往与你想象的电路相差很远。
if (cpu_overheated) then shut_off_computer = 1; if (~arrived) then keep_driving = ~gas_tank_empty;
除了你指定的情况以外,会发生些什么,答案是什么也不会发生,输出保持不变。而这往往就导致了电路的错误,所以说语法正确的代码不一定能产生合理的电路(组合逻辑+触发器)。
输出保持不变,这就意味着电路需要记住当前状态,从而产生锁存器。组合逻辑(比如逻辑门)不能记住任何状态。
题目说明
以下代码包含创建锁存器的不正确行为。修复错误~
always @(*) begin if (cpu_overheated) shut_off_computer = 1; end always @(*) begin if (~arrived) keep_driving = ~gas_tank_empty; end图片来自 HDLBits
模块端口声明
module top_module ( input cpu_overheated, output reg shut_off_computer, input arrived, input gas_tank_empty, output reg keep_driving );
题目解析
这个题目的核心是修复由于各种原因在组合电路中引入的锁存器代码,理解了前面说的产生锁存器的原因,看这个代码还是比较好找出问题的,尽量补全if语句的条件。
module top_module ( input logic cpu_overheated, output logic shut_off_computer, input logic arrived, input logic gas_tank_empty, output logic keep_driving ); always_comb begin if (cpu_overheated) shut_off_computer = 1; else shut_off_computer = 0; end always_comb begin if (~arrived) keep_driving = ~gas_tank_empty; else keep_driving = 0; end endmodule
点击Submit,等待一会就能看到下图结果:
注意图中的Ref是参考波形,Yours是你的代码生成的波形,网站会对比这两个波形,一旦这两者不匹配,仿真结果会变红。
这一题就结束了。
总结
今天的几道题就结束了,整体难度不大,逐渐的在学习新知识~
最后我这边做题的代码也是个人理解使用,有错误欢迎大家批评指正,祝大家学习愉快~
全部0条评论
快来发表一下你的评论吧 !