前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >HDLBits:在线学习 Verilog (一 · Problem 0-9)

HDLBits:在线学习 Verilog (一 · Problem 0-9)

作者头像
数字积木
发布2021-04-15 11:40:40
1.1K0
发布2021-04-15 11:40:40
举报
文章被收录于专栏:数字积木

本系列文章将和读者一起巡礼数字逻辑在线学习网站 HDLBits 的教程与习题,并附上解答和一些作者个人的理解,相信无论是想 7 分钟精通 Verilog,还是对 Verilog 和数电知识查漏补缺的同学,都能从中有所收获。

本文是系列文章的第一篇,讨论下前十道习题和解答,HDLBits 共有约 180 题。Step one - HDLBits

附上网站的链接,在没有特别指出的情况下,题目,教程,图片均为 HDLBits 的内容,本文对其进行了翻译,翻译严格遵循作者 爱怎么来怎么来 的翻译准则。

首先附上传送门,同时打开两个页面一起阅读,收获双份的快乐哦。

https://hdlbits.01xz.net/wiki/Step_onehdlbits.01xz.net

Step one - HDLBitsStep one - HDLBitshdlbits.01xz.net

Problem 0 : Step one

欢迎来到 HDLBits!

数字逻辑电路的学习的挂挡起步往往是艰难的,因为你一开始就发现有一堆东西等着你去学习:新的概念,一种新的硬件描述语言(Hardware Description Language,比如 Verilog),几种软件工具(ISE,Vivado,Modelsim,Quartus等等),以及可能包括一块 FPGA 开发板,所有一切都同时等着你去学习,学习曲线略微陡峭。好在 HDLBits 提供了在线练习简单数字的设计与调试的功能,只需要通过执行页面上的 Simulate 按钮,就能为你的学习助力。

设计一个数字电路需要以下几步:编写 HDL 硬件描述语言,比如使用 Verilog;编译(综合)代码为一个数字电路;仿真分析电路的功能和时序;最后,Kill those bugs。

编写代码

最简单的方式是直接在 HDLBits 网页下方的代码框内编写你的代码,因为网页已经帮你生成了部分代码,比如模块的输入输出端口。

你也可以在其他的编辑器上完成代码的编写,然后使用上传功能,上传你的 v 文件,这样会有些繁琐,但你可以利用到编辑器的语法补全,代码片段等特性。这里推荐开源免费,全功能编辑器,微软推出的 VSCode。

ljgibbs:VSCode 布道指南 V1.0 (一)zhuanlan.zhihu.com

之后点击页面上的 Simulate 按钮,对代码进行编译(综合),仿真。

编译(逻辑综合)

你的代码会通过 Altera Quartus 的综合器综合为硬件电路。Quartus 会生成一组综合信息,通过 Show Quartus messages 可以显示/隐藏他们。通过这些信息可以进行相应的修改,减少代码中的警告,但有些警告就随它去吧。

仿真

你的综合电路会通过功能仿真来检查其功能是否正确。HDLBits 使用 ModelSim 同时仿真你的代码和参考解决方案,然后比较两者的输出。仿真报告关注两个方面:

一是,会报告你的电路的输出与参考电路的输出是否完全一致,(不存在 mismatch)。mismatch 代表某一时刻两者的输出并不一致,正确的电路中不存在 mismatch。

此外,仿真报告会产生你的电路运行测试向量时的输出时序,时序分为三组:输入,你的电路的输出,参考电路的输出。

值得注意的是,不要修改题目中给定的模块以及端口的名称,否则会造成仿真错误。

结果状态

如果你的电路完全正确,那么你会看到 Status: Success! 当然情况也有不妙的时候:

  • Compile Error — 电路综合失败
  • Simulation Error — 电路综合成功,但是仿真存在错误
  • Incorrect — 电路综合,仿真成功,但输出结果和参考结果不同。

你可以通过 My Stats 页面查询自己在所有题目中的状态,以及完成度在所有参与用户中的排名,通过注册账号,可以在多个浏览器上进行访问。

牛刀小试

说了这么多,终于到这道题的练习环节,构建一个电路,没有输入端口,只有一个输出端口,输出端口时钟驱动逻辑 1 ,即逻辑高。

模块的端口已经给出

解答与分析

这个题目很简单,assign one = 1; 即可。

1 在数字逻辑中代表 logic high,而 0 代表 logic low。

通过这题可以了解 HDLBits 的基本操作以及数字逻辑的一些简单概念,在完成正确的提交后,可以通过题目下方 Show Solution 查看到解答。目前看下来大部分的题目是有解答的,但你只有正确提交之后才能查看解答。

Problem 1 : Zero

这题同样要求构建一个电路,没有输入端口,只有一个输出端口,但这次输出端口时钟驱动逻辑 0 ,如果你完成了前一道题,那么这题也自然不在话下。

部分题目提供了 Hint,可以给你提供一些帮助。

比如这题的提示:如果你什么也不做,在 Quartus 中,输出端口会被默认赋值为 0,所以这题超简单,直接提交即可。(当然,使用默认值是危险,相当不推荐的行为)

解答与分析

代码语言:javascript
复制
module top_module(
    output zero
);// Module body starts after semicolon
    assign zero = 0;
endmodule

Problem 2 : Wire

这题中我们将认识到 Wire,Verilog 中重要的一种信号类型,我们将通过建立一个模块 (module) 来实现 wire。所谓模块就是前两题中我们构建的东西,拥有输入输出端口的黑盒,在之后我们会详细讲解模块,

wire 的中文可以翻译为导线,但 Verilog 中的 wire 和现实中的导线不同,wire 应该理解为一个信号,信号是有方向性的,wire 从 A 点输出,输入到 B 点和 C 点。wire 一般只有一个 source,即从某一点输出,但可以有多个 sinks,即输入到多个点。A 点通常会被称为一个驱动(driver),把某个值驱动到 wire 上。

把驱动的概念引进到 Verilog 中,可以写作:

assign left_side = right_side;

right_side 的值就被驱动到 left_side 中,以上的语法结构名为连续赋值(continous assignment)。但请注意与软件中的赋值操作做区分,Verilog 中的赋值是使用一条带有方向的导线连接了两个信号,所以 left_side 始终等于 right_side,随 right_side 变化而变化。而软件中的赋值是一种事件,某个时刻 left_side 的值变成了和 right_side 相同的值。

模块中的端口也带有方向性,主要分为输入 input 和输出 output 端口。输入端口是由模块外部的信号驱动的,而输出端口则又会驱动另一个外部信号。如果我们通过一个模块来模拟 wire,那么从模块内部来看,输入端口就直接驱动输出端口。

图片来自 HDLBits

图片来自

我们的题目和上图息息相关,模块和端口已经被定义好了,黑色的框图以及箭头代表模块和端口。而外部的驱动信号和模块下游的信号也已经给出,即图中灰色的部分。你要做的工作是完成图中绿色的部分,即完成这条连线。

你可以在模块体中使用一条 assign 语句,将输入端口的值赋给输出端口来完成这个模块。你不需要考虑黑框外部的信号,那些事用来测试你模块的信号,将会由我们来完成。

除了连续赋值,Verilog 还有三种其他的赋值方式,这三种赋值方式都只能在过程块中使用,我们将在后续的题目中讨论。

解答与分析

代码语言:javascript
复制
module top_module( input in, output out );
	assign out = in;
// Note that wires are directional, so "assign in = out" is not equivalent.
//注意 wire 是有方向的 因此 assign in = out 是不等价的
endmodule

代码非常简洁,但始终要注意思考代码和电路,和上图中模块描述的关联。

另外 wire 是 Verilog 中的一种数据类型,代表的是信号,而不是连线。

在这里可以对 module 和连续赋值抱有疑惑,我们将在后续的内容中继续讨论。

Problem 3 : Wire4

前文中我们讨论过,wire 的源一般只能有一个,终点确可以有多个。一个源可以驱动多个信号。本题中,我们要使用三个信号源 a,b,c 驱动四个信号 w,x,y,z.

a -> w b -> x b -> y c -> z

从模块的角度来说,有三个输入端口和四个输出端口,上图给出了信号的流向。

当你使用多条 assign 语句时,他们之间的顺序是无关紧要的,这点同顺序执行的软件代码不同。事实上,大部分 Verilog 代码之间的顺序都不会对结果产生影响。assign 描述的是端口之间的连接关系,而不是一次复制右值,赋给左值的复制黏贴,连接关系不存在先后之分。

这里要澄清一个容易混淆的概念,图中的绿线代表的是 wire 之间的连接,而不是 wire 本身。即 wire 是连线两端的信号,而不是连线本身。上图中的模块实际声明了 7 个 wire 信号(a, b, c, w, x, y, z)。这是因为模块的输入输出端口实际上都是 wire。

我们一般这么声明端口信号

input a;

但实际上我们声明的是

input wire a;

所以,assign 语句并不是创建 wire ,而是将创建 wire 之间的连接。

解答与分析

代码语言:javascript
复制
module top_module (
	input a,
	input b,
	input c,
	output w,
	output x,
	output y,
	output z  );
	
	assign w = a;
	assign x = b;
	assign y = b;
	assign z = c;
	
endmodule

wire 是信号,而 assign 语句则建立了信号之间的连接,这种连接是有方向性。

模块的输入输出端口也同样是 wire

Problem 4 : Notgate

非门在数字电路中十分常见,本题我们要通过 assign 语句以及 Verilog 的逻辑操作符,实现一个非门模块。

与 wire 模块相同,非门模块中 in 被连接到 out,相比 wire 模块,唯一的区别在于:输出信号 out 是将输入信号 in 取反得到。

我们在 assign 语句中增加的逻辑操作符为 ~(逐位取反),由于我们的信号位宽为 1 位,我们也可以使用!(逻辑取反)。二者的区别在于逻辑取反的结果时钟只有一位,而逐位取反结果的位宽和输入信号位宽相同,在每一个位上逐位(bitwise)取反。

解答与分析

代码语言:javascript
复制
module top_module (
	assign	out = ~ in;
endmodule

非门在 wire 的模块上稍加改造,对 assign 语句添加逻辑运算符实现。

Problem 5 : Andgate

本题要求使用 Verilog 语言描述一个模块,实现与门的作用。

从第 4 题开始,是用 Verilog 描述各种 “门”,这也就是 Verilog 硬件描述语言中,描述二次的由来。描述就是我们用 Verilog 的语法,通过写下几句代码来实现一个电路。从最简单的门到 CPU 都可以使用 HDL 描述。

题目给出的模块如下图,有三个 wire : a,b 以及 out。a,b 信号已经由模块的输入端口驱动,但图中黑色的部分中,wire out 还没有被任何信号驱动。本题要写一个 assign 语句,使 a,b 信号经过与门的输出驱动 wire out 信号。

显然,assign 语句的实现和前一题非常接近,只是增加了一个输入信号。和前一题不同的是,我们在这里强调了信号是被驱动(drive)的,被驱动的含义可以理解为,该信号的取值取决于另一个连接到它的信号的值,该信号的值随着另一个信号的值改变而改变。下图中模块的输入端口 input wire 被外部连接到模块的信号所驱动。assign 语句映射到具体的硬件上,就是产生了信号的驱动,由右值驱动左值。

说道 assign,如果你有过一些思考的话(你有思考嚒),一个 wire 信号不能被多个信号同时驱动(当一个信号说往东,另一个信号说往西,两个信号还要同时驱动我时,我到底该往哪?)。另一个方面,一个没有驱动者(driver)的信号的值会处于未定义的状态,可怜的家伙,都没有司机,好在综合器一般会免费给他安排一个,将其信号值驱动为 0.

解答与分析

代码语言:javascript
复制
module top_module(
    input a,
    input b,
    output out );
	assign	out = a & b;
endmodule

值得注意的是 & 和 && 的区别,& 是逐位与,而 && 是逻辑与。

Problem 6 : Norgate

本题要求使用 Verilog 实现一个 NOR 门,注意这里其实是或非门,而不是更常见的异或门,或非门是或门的输出取反。

assign 语句将某个值赋予 wire 信号,这个 value 可以是常量,也可以是一个复杂的逻辑表达式,综合器会综合出相应的逻辑门实现。assign 语句代表的始终是连续赋值,因为当输入信号改变时,输出信号会重新“计算”。和一个逻辑门的工作方式相同,输入改变,输出对应改变。

解答与分析

代码语言:javascript
复制
module top_module(
    input a,
    input b,
    output out );
    assign	out =~ (a | b);
endmodule

注意括号的由来,因为 ~ 非逻辑的优先级大于 | 或。

Problem 7 : XNorgate

XNor 的中文是什么,作者也愣了愣神,其实应该是同或门。

我们首先复习下数电,同或门 (XNor Gate) 是异或门 (Nor Gate) 的取反输出。异或门的输入输出可以概括为:(输入)相同(输出)为 0 ,不同为 1 。

解答与分析

代码语言:javascript
复制
module top_module(
    input a,
    input b,
    output out );
    assign	out =~( a ^ b);
endmodule

这里你就会发现硬件描述语言的好处,其实你把数电的知识还给老师了,不记得相同为 0 ,不同为 1,似乎写出这道题问题也不大……

^ 为逐位异或,Verilog 中不存在逻辑异或符号。

Problem 8 : Declaring wires

到本题为止,我们的电路都十分简单,你是否觉得 Verilog 就这么简单呢,那我要说 是的,就这么简单。(逃<=)

言归正传,之前电路足够简单,我们能直接表示出输入输出信号的逻辑关系,但如果电路变得复杂,那么我们就需要一些中间信号来帮助我们简化描述电路的难度。

定义中间信号的语法格式为

代码语言:javascript
复制
wire foo ;

信号定义语句需要放置于模块的 body 中,就好比 C 语言中,你的中间临时变量需要定义在 main 函数函数体中。模块的 body 指的就是 module 和 endmodule 中间的部分。

这里建议先定义信号,再使用信号,就像 C 语言中一样。原则上,你可以在任何位置定义你的信号,使用前使用后都可以,正如之前的课程中说的那样,语句的顺序对于 Verilog 来说没有关系。但有些仿真工具需要你在使用信号之前定义信号,So,你就这么来吧。

举个栗子

上述模块中,存在三个 wire (in, out, and not_in),其中两个信号已经随着模块的定义而定义了,分别定义为模块的输入输出 wire,这也就是为什么在前面的题目中不需要额外定义 wire 信号的原因。而 not_in 信号定义于模块中,对于模块外部来说它是不可见的。然后,我们通过 assign 语句定义了两个非门,使用到了中间信号 not_in。

看到这里,你说我不需要中间信号,我只需要 assign out = ~~in; 就行。没错。

但现在电路仍然比较简单,很容易描述出前一级的输出,但如果前一级的输出很复杂,那么要使用 assign 语句描述出两级电路的输入输出关系集合就比较困难。使用硬件描述语言的好处在于,你可以描述出前一个模块的输出,将其赋给中间信号,并将中间信号作为下一级信号的输入。这样,你永远只需要一次描述一个模块。

牛刀小试

实现下图中的模块。首先创建两个中间信号将与门和或门连接起来,信号的名字随你的便,但好的名字往往影响一个信号的一生,若干年后,你还能依稀记起当年定义这个信号的峥嵘岁月。

注意,与门的输出信号也就是或门的输入信号,所以你不需要再定义或门的输入信号。再提醒一下,信号只能被一个信号驱动,但能驱动多个信号。

按照下图中的逻辑关系,你的代码应该有 4 个 assign 语句,对应四个逻辑门,或者说模块。

解答与分析

代码语言:javascript
复制
`default_nettype none
module top_module(
    input a,
    input b,
    input c,
    input d,
    output out,
    output out_n   );
    wire	and_1 = a & b;
    wire	and_2 = c & d;
    wire	or_1  = and_1 | and_2;
    assign	out   = or_1;
    assign	out_n = ~or_1;
endmodule

`default_nettype none 是一个宏定义语句,我们将在后续的课程中探讨它。

你问我的答案为什么没有说好的 4 个 assign 语句,因为我在定义 3 个中间信号的同时,还给它们赋了值,这在 Verilog 语法中也是允许的。如果你想看 4 个 assign 语句的答案,可以在完成提交后,通过 Show solution 查看解答。

Problem 9 : 7458

本题要实现个稍稍复杂的电路:数电芯片 7458 。它有 10 个输入信号,2 个输出信号。你可以选择对每个输出信号,使用一个 assign 语句,也可以先产生第一级逻辑门输出的 4 个中间信号。有时间的话,两种方式都可以尝试下。

解答与分析

代码语言:javascript
复制
module top_module (
    input p1a, p1b, p1c, p1d, p1e, p1f,
    output p1y,
    input p2a, p2b, p2c, p2d,
    output p2y );
    assign p1y = (p1a & p1b & p1c) | (p1d & p1e & p1f);
    assign p2y = (p2a & p2b) | (p2d & p2c);
endmodule

作者使用的是第一种方法,你可以在这里尝试一下上一题中讲授的创建中间信号的方法。

显然,完成这题需要你认识逻辑门的符号,一点儿耐心和好一点的视力,后两者对于 IC 从业人员来说很重要。

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

原文作者:ljgibbs 原文地址:https://zhuanlan.zhihu.com/c_1029044037684183040 本公众号授权发布

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-08-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数字积木 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Problem 0 : Step one
  • Problem 1 : Zero
  • Problem 2 : Wire
  • Problem 3 : Wire4
  • Problem 4 : Notgate
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档