LJgibbs · 9月16日

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

转载自:知乎

本系列文章将向大家推荐一个学习 Verilog 的好去处:HDLBits.
HDLBits 在提供 Verilog 基础语法教程的同时,还能够在线仿真你的 Verilog 模块,将你的输出与正确的时序比较,可以说真的是很棒了。
本系列文章将和读者一起巡礼数字逻辑在线学习网站 HDLBits 的教程与习题,并附上解答和一些作者个人的理解,相信无论是想 7 分钟精通 Verilog,还是对 Verilog 和数电知识查漏补缺的同学,都能从中有所收获。

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

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

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

https://hdlbits.01xz.net/wiki/Step_one​

Step one - HDLBits

Step one - HDLBits

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 (一)​

之后点击页面上的 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,所以这题超简单,直接提交即可。(当然,使用默认值是危险,相当不推荐的行为)

解答与分析

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 还有三种其他的赋值方式,这三种赋值方式都只能在过程块中使用,我们将在后续的题目中讨论。

解答与分析

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 之间的连接。

解答与分析

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)取反。

解答与分析

module top_module (
    assign    out = ~ in;
endmodule

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

To Be Continued~

推荐阅读

关注此系列,请关注专栏FPGA的逻辑
2 阅读 211
推荐阅读
0 条评论
关注数
1184
内容数
53
FPGA Logic 二三事
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
Arm中国学堂公众号
关注Arm中国学堂
实时获取免费 Arm 教学资源信息
Arm中国招聘公众号
关注Arm中国招聘
实时获取 Arm 中国职位信息