前言:
电子专业的知识体系若是一座大厦,那数字电路技术基础(下文均简称数电)无疑是构成这座大厦的几块重要基石,与之相照应的有模拟电路技术基础、VLSI(Very Large Scale Integrate circuit),为之服务的有数字电路实验和一系列的硬件描述语言(此系列暂时以 Verilog 为编写语言,Testbench 为验证语言),但一路走来,尤其在本科与硕士学习对接的过程中却倍感在数电、数电实验、硬件描述语言三者未能很好地联系起来,所以希望能在 Verimake 的帮助下,基于 SparkRoad 开发板实现对这三者尽可能的结合,以帮助相关专业的同学。
并不打算在这个系列中赘述基础的数电相关的基础知识,它们如同 abandon 在单词书中一样几乎在每一本相关专业书籍中都有介绍,更多地想注重如何将几者串联起来,哪怕是简简单单地跑通一个小项目,对于新手也会受益匪浅。当然对于那些基础知识,也会附注一些经典的路径方便有需要的同学查找温习。
Verilog 相较于 C++之类的语法是简单的,如果有一些 C++、C 之类编程语言的基础,那对于基础的 Verilog 语言使用则会有很大帮助。
在这个系列中,会争取在每一章提纲挈领地提出或者引用对于此系列这一章所必要的知识和认为重要的点,可供结合题目一起学习。
知识点:
Verilog 变量类型与向量
最常用的变量类型有 reg、wire。
Wire 类型常用来表示以 assign 关键字指定的组合逻辑信号。
寄存器是数据存储单元的抽象,寄存器数据类型的关键字是 reg。
使用“=”为阻塞赋值,“<=”为非阻塞赋值。组合逻辑电路使用阻塞赋值,时序逻辑电路使用非阻塞赋值。
在组合逻辑电路中应学会在 input、output 之外用 wire 定义中间变量。
在 Verilog 语言中,reg 和 wire 都可以被用向量形式表示,并且可以单独拎出来使用,但需要注意的是向量的声明是具有方向性的,r[7:0]与 r[0:7]中表达的含义并不一样。
在 Hdlbits 的习题中,并没有很体现出来向量使用的必要性,但却介绍了几种很实用的使用方法,例如拼接符{}、复制写法 4{r[7:0]}(指将 r[7:0]复制 4 次,并连接起来)。向量的使用更像是将一批有共同特色的对象集合起来,可以对其整体一同操作,也可以差分开来,只对一个向量组中的单个数据操作,当然,这些操作也可以用比较“笨”的方式一个一个写出来,可结合实例题目例2.1 感受。
在对向量与向量和对向量与单体的操作中,都需要注意向量声明时位宽的对应。如果出错,可以见 Hdlbits 的 Vectors in more detail 一题,所以在声明和变量对应的时候务必注意两者之间的位宽是否匹配,如果未有声明,比如只写了 1,那么会默认为电脑系统中的 32 位 1,影响程序。
向量中有单目形式的写法可以在特定情况下减少代码书写量,比如有一变量 [2:0]in,存在操作 in[0]^ in[1]^ in[2],可以简写为^ in。
温习 tips:
- 《Verilog 数字系统设计教程·第四版·夏宇闻》第三章 P 26
基于SparkRoad的《Verilog数字系统设计教程·第三版(夏宇闻)》学习(4)——第3章 - 江左子固 - 博客园 (cnblogs.com)
- 运算符的认识《Verilog 数字系统设计教程·第四版·夏宇闻》第三章 P 35
- 向量部分 Hdlbits 习题解答 基于SparkRoad的Hdlbits网站刷题(1) - VeriMake
- 按位运算与逻辑运算的区分 基于SparkRoad的《Verilog数字系统设计教程·第三版(夏宇闻)》学习(4)——第3章 - 江左子固 - 博客园 (cnblogs.com)
卡诺图
卡诺图作为数电的重要知识,是连接现实题目与理论设计的桥梁,在组合逻辑与时序逻辑中均广泛运用。基础的卡诺图化简遵循:
- 将逻辑表达式写成最小项表达式
- 按最小项表达式填写卡诺图,所出现的最小项写为 1,未出现的为 0
- 将卡诺图中含有 1 的项用线包起来,保证每个包围圈含有 2n 个 1
- 写出包围圈中每个 1 对应的乘积项并将相加,即为结果
但是有一些特例也需要掌握,比如异或与同或(ABCD连续异或,这一类的用法在许多题目中都有涉及,比如奇校验电路为几个变量之间异或,偶校验电路为几个变量之间同或,还比如数值比较器、乘法器等,之后会一一实现) 的卡诺图形状,虽然也可以通过上述的画圈法得出,但是作为一种常用且重要的基础门电路,有必要直接记住其卡诺图,可结合实例题目例 1.2 感受。
温习 tips:
- 卡诺图基础知识“逻辑函数的卡诺图化简法”——《电子技术基础·数字部分·康华光(第六版)》第 53 页 2.4
- 格雷码转换二进制码电路——《电子技术基础·数字部分·康华光(第六版)》第 152 页 例 4.2.2
实例题目:
一、简单门电路的实现:
与门、异或门的实现
例 1.1 现有四个输入 a、b、c、d,输出 L 1、L 2,L 1实现了对 a、b 的与运算,L 2 实现了对 c、d 的异或运算,并在 SparkRoad 上实现。
(点评与想法: 尝试分别用门级描述方式和数据流描述方式实现)
解:
在 Hdlbits 网站上大多使用了数据流描述方式,通过连续赋值语句来实现。它由关键词 assign 开始,后面跟着所想表达的变量和操作符。使用此方式可以使 Verilog 代码更加简明易懂。
Verilog 实现如下:
module and_xor(a,b,c,d,L1,L2);
input a,b,c,d;
output L1,L2;
wire a,b,c,d;
wire L1,L2;
assign L1=a&b;
assign L2=c^d;
endmodule
Testbench 实现如下:
`timescale 1ns/1ps
module and_xor_tb;
reg a,b,c,d;
wire L1,L2;
initial begin
a=1'b0;
b=1'b0;
c=1'b0;
d=1'b0;
#100 a=1'b0;
b=1'b1;
c=1'b0;
d=1'b1;
#200 a=1'b1;
b=1'b0;
c=1'b1;
d=1'b0;
#300 a=1'b1;
b=1'b1;
c=1'b1;
d=1'b1;
#400 a=1'b1;
b=1'b1;
c=1'b1;
d=1'b1;
end
and_xor anx_xor_tb_1(
.a(a),
.b(b),
.c(c),
.d(d),
.L1(L1),
.L2(L2)
);
endmodule
波形图验证如下:

当然也可以使用门级描述方式来实现,调用 Verilog 语言内部预先定义好的门电路元件。
Verilog 实现如下:
module and_xor(a,b,c,d,L1,L2);
input a,b,c,d;
output L1,L2;
wire a,b,c,d;
wire L1,L2;
/*assign L1=a&b;
assign L2=c^d;*/
and U1(L1,a,b);
xor U2(L2,c,d);
endmodule
Testbench 实现和上一个相同,只是需要在 Modelsim 中重新仿真新写的 module and_xor 模块。
在 SparkRoad 上设置引脚文件如下:
set_pin_assignment { L1 } { LOCATION = M3; }
set_pin_assignment { L2 } { LOCATION = M4; }
set_pin_assignment { a } { LOCATION = T4; }
set_pin_assignment { b } { LOCATION = R15; }
set_pin_assignment { c } { LOCATION = R5; }
set_pin_assignment { d } { LOCATION = T15; }
运行成功如下:
设置最左和最右端为 a,b;左边第二个和右边第二个为 c,d;LED 0 为 L 1,LED 1 为 L2;SparkRoad 的 LED 亮代表 0,灭代表 1。
a=1,b=0;c=1,d=0,运行成功。

a=1,b=1;c=1,d=1,运行成功。
例 1.2试设计一个码转换电路,将 4 位格雷码转换为自然二进制码,采用逻辑门电路,并在 SparkRoad 上实现。
(点评与想法: 在例 1.1 的基础上增加变量并加大对题目理解的难度,实际上化简出来后代码角度并没增加多少难度)
解:
异或与同或的卡诺图特点显著,需要熟记,一如此题的 B0 信号卡诺图。
Verilog 实现如下:
module and_xor_zhuanma(G3,G2,G1,G0,B3,B2,B1,B0);
input G3,G2,G1,G0;
output B3,B2,B1,B0;
wire G3,G2,G1,G0,B3,B2,B1,B0;
assign B3=G3;
assign B2=B3^G2;
assign B1=B2^G1;
assign B0=B1^G0;
endmodule
Testbench 实现如下:
`timescale 1ns/1ps
module and_xor_zhuanma_tb;
reg G3,G2,G1,G0;
wire B3,B2,B1,B0;
initial
begin
G3=0;
G2=0;
G1=0;
G0=0;
end
always
begin
#100 G3=~G3;
#200 G2=~G2;
#400 G1=~G1;
#800 G0=~G0;
end
and_xor_zhuanma and_xor_zhuanma_tb_1(
.G3(G3),
.G2(G2),
.G1(G1),
.G0(G0),
.B3(B3),
.B2(B2),
.B1(B1),
.B0(B0)
);
endmodule
波形图验证如下:

在 SparkRoad 上设置引脚文件如下:
set_pin_assignment { B0 } { LOCATION = N4; }
set_pin_assignment { B1 } { LOCATION = N3; }
set_pin_assignment { B2 } { LOCATION = M4; }
set_pin_assignment { B3 } { LOCATION = M3; }
set_pin_assignment { G0 } { LOCATION = T6; }
set_pin_assignment { G1 } { LOCATION = T5; }
set_pin_assignment { G2 } { LOCATION = R5; }
set_pin_assignment { G3 } { LOCATION = T4; }
运行成功如下:
设置 SW0 SW1 SW2 SW3 为 G 3 G 2 G 1 G0,LED 0 LED 1 LED 2 LED 3 为 B 3 B 2 B 1 B0。

输入 1101,得出结果 1001,运行成功。

输入 1010,得出结果 1100,运行成功。
向量
例 2.1 现有五个输入 a,b,c,d,e,根据这五个输入,分别生成两个向量,第一个向量是将五个输入都复制 3 遍再一次拼接而成的,第二个向量是将小向量{a,b,c,d,e}复制 3遍再依次拼接而成的,再将两个向量异或并取反得出输出,并在 SparkRoad 上实现。
(点评与想法: 此题脱胎于 Hdlbits 上 Verilog Language 的 Vectors 的 More replication ,考察了拼接、复制、向量间的操作,是这一部分集成度最高的一题。另外因为 SparkRoad 开发板没有这么多位数可显示,所以调整复制倍数为 3 次)
解:
Verilog 实现如下:
module top_module (
input a, b, c, d, e,
output [14:0] out );//
/*wire [14:0]dingbu;
wire [14:0]dibu;
assign dingbu={a,a,a,b,b,b,c,c,c,d,d,d,e,e,e};
assign dibu={a,b,c,d,e,a,b,c,d,e,a,b,c,d,e};
assign out=~dingbu^dibu;*/
assign out=~{{3{a}},{3{b}},{3{c}},{3{d}},{3{e}}}^{3{a,b,c,d,e}};
endmodule
注释中的是“笨”方法,但并不错,可供一乐。
在 SparkRoad 上设置引脚文件如下:
set_pin_assignment { a } { LOCATION = T4; }
set_pin_assignment { b } { LOCATION = R5; }
set_pin_assignment { c } { LOCATION = T5; }
set_pin_assignment { d } { LOCATION = T6; }
set_pin_assignment { e } { LOCATION = M6; }
set_pin_assignment { out[0] } { LOCATION = T12; }
set_pin_assignment { out[10] } { LOCATION = M5; }
set_pin_assignment { out[11] } { LOCATION = N4; }
set_pin_assignment { out[12] } { LOCATION = N3; }
set_pin_assignment { out[13] } { LOCATION = M4; }
set_pin_assignment { out[14] } { LOCATION = M3; }
set_pin_assignment { out[1] } { LOCATION = R12; }
set_pin_assignment { out[2] } { LOCATION = M7; }
set_pin_assignment { out[3] } { LOCATION = T9; }
set_pin_assignment { out[4] } { LOCATION = T8; }
set_pin_assignment { out[5] } { LOCATION = T7; }
set_pin_assignment { out[6] } { LOCATION = R7; }
set_pin_assignment { out[7] } { LOCATION = P5; }
set_pin_assignment { out[8] } { LOCATION = N5; }
set_pin_assignment { out[9] } { LOCATION = P4; }
运行成功如下:
设置 SW 0 SW 1 SW 2 SW 3 SW 4 SW 5为 abcde,LED 0 LED 1 LED 2 LED 3 至 LED 14为 out 0-14。

输入 00001,得出结果 111101111011001,运行成功。

输入 01010,得出结果 101100010001101,运行成功。