0 前言
0.1 使用环境
- EDA工具:Vivado 2017.4
- 硬件描述语言:Verilog HDL
0.2 涉及知识
- 数字逻辑
- Verilog
1 基础模块:一位四选一数据选择器
1.1 设计部分:层次建模
1.1.1 需求分析
设计一个一位的四选一数据选择器,有一个低电平输入有效的使能端E
1.1.2 层次建模
1.1.3 功能分析
- 输入端口
- 数据输入端 X 4
- D0~D3(Date)
- 地址输入端 X 2(因为2个端口可以有2^2=4种状态)
- A1,A0(Address)
- 使能端 X 1
- E(Enable)
- 数据输入端 X 4
- 输入端口
- 数据输出端 X 1
- OUT
- 数据输出端 X 1
一位四选一数据选择器的简化真值表为:
E的bar | A1 | A0 | OUT |
---|---|---|---|
0 | x | x | 0 |
1 | 0 | 0 | D0 |
1 | 0 | 1 | D1 |
1 | 1 | 0 | D2 |
1 | 1 | 1 | D3 |
逻辑表达式为:
OUT = (A1bar·A0bar·D0 + A1bar·A0·D1 + A1·A0bar·D2 + A1·A0·D3)·E
1.2 实现部分:设计的实现
1.2.1 设计块
根据1.1.3节的逻辑表达式,使用数据流建模,直接得到设计块部分。注意,这里并没有使用前面所示的端口名称,这并不影响,不过不要学习我,还是保持一致比较好!
// 使用数据流建模,实现一位的四选一数据选择器
module choose_4to1(input i0,i1,i2,i3, // 数据输入端input s1,s0, // 地址输入端input e, // 使能端output out // 输出);assign out = ((~s1)&(~s0)&i0 | (~s1)&s0&i1 | s1&(~s0)&i2 | s1&s0&i3) & e;endmodule
1.2.2 激励块
调用设计块实例,施加激励信号,利用系统函数显示结果,验证设计块的正确性。
module test();reg I0 = 1,I1 = 0,I2 = 1,I3 = 0;reg S1,S0;reg E = 1;wire OUT;choose_4to1 CT0 (I0,I1,I2,I3,S1,S0,E,OUT);initial#1 $monitor("S1 = %b, S2 = %b, OUT = %b\n",S1,S0,OUT);initialbegin$display("I0 = %b, I1 = %b, I2 = %b, I3 = %b\n\n",I0,I1,I2,I3);#1 S1 = 0; S0 = 0;#1 S1 = 0; S0 = 1;#1 S1 = 1; S0 = 0;#1 S1 = 1; S0 = 1;endendmodule
1.2.3 验证
根据输出结果,验证设计块是否正确,若不正确,则改正后再次验证,直到正确为止。
经过验证,设计块是正确的!
1.3 科技黑箱1:1位四选一数据选择器
目前,一位四选一数据选择器,已经能够直接使用,我们根据起端口和功能,设计出更复杂的功能模块。
2 向量型扩展:多位四选一数据选择器
我们知道,数据在计算机中采用二进制信息进行表示,对于第1节中的数据选择器,输入数据只能是1个二进制位,这显然满足不了现实需求,因此我们对其进行扩展,例如,能够实现100,266等类似数据的选择。
这里为了简单器件,说明设计思想,我们选择将1位扩展为4位
2.1 设计部分:4位四选一数据选择器
2.1.1 层次建模
2.1.2 设计模型
2.1.3 设计思想:分治思想
举例,假设选择的结果的5
合(十进制) | 分(二进制) | |||
---|---|---|---|---|
9 | 1 | 0 | 0 | 1 |
5 | 0 | 1 | 0 | 1 |
7 | 0 | 1 | 1 | 1 |
8 | 1 | 0 | 0 | 0 |
处理(选) | 0 | 1 | 0 | 1 |
合(十进制) | 5 |
简单解释一下:
- 合: 将十进制的数字拆成4位二进制数
- 分: 4个数的第一位进入第一个数据选择器,第二位进入第二个数据选择器……
- 选: 每个数据选择器分别选出一个二进制数
- 合: 将得到的4位二进制数转换为十进制数输出
2.1.4 模型类比
从一位全加器到四位全加器,也是同样的过程。
2.2 实现部分:设计的实现
2.2.1 设计块
`timescale 1ns / 1ps// 使用数据流建模,实现一位的四选一数据选择器
module choose_4to1(input i0,i1,i2,i3, // 数据输入端input s1,s0, // 地址输入端input e, // 使能端output out // 输出);assign out = ((~s1)&(~s0)&i0 | (~s1)&s0&i1 | s1&(~s0)&i2 | s1&s0&i3) & e;endmodule// 向量型扩展,实现4位四选一数据选择器
module choose_4to1_4sizes(input [3:0] d0,d1,d2,d3,input s1,s0,input e,output [3:0] out);// 4个1位四选一数据选择器实例choose_4to1 DT0 (d0[0],d1[0],d2[0],d3[0],s1,s0,e,out[0]);choose_4to1 DT1 (d0[1],d1[1],d2[1],d3[1],s1,s0,e,out[1]);choose_4to1 DT2 (d0[2],d1[2],d2[2],d3[2],s1,s0,e,out[2]);choose_4to1 DT3 (d0[3],d1[3],d2[3],d3[3],s1,s0,e,out[3]);endmodule
2.2.2 激励块
`timescale 1ns / 1psmodule test();reg [3:0] I0 = 9,I1 = 5,I2 = 7,I3 = 8;reg S1,S0;reg E = 1;wire [3:0] OUT;choose_4to1_4sizes CT0 (I0,I1,I2,I3,S1,S0,E,OUT);initial#1 $monitor("S1 = %b, S2 = %b, OUT = %d\n",S1,S0,OUT);initialbegin$display("I0 = %d, I1 = %d, I2 = %d, I3 = %d\n\n",I0,I1,I2,I3);#1 S1 = 0; S0 = 0;#1 S1 = 0; S0 = 1;#1 S1 = 1; S0 = 0;#1 S1 = 1; S0 = 1;endendmodule
2.2.3 结果验证
2.3 科技黑箱2:4位四选一数据选择器
依然还是这个模型,区别是:这里的数据输入和输出端口,支持4个二进制位
3 数组型扩展:1位八选一数据选择器
利用使能端,通过屏蔽理论,将一位四选一数据选择器,扩展为一位八选一数据选择器。
3.0 数组型扩展的核心原则
考虑所有的端口
- 该分开的分开,注意连接顺序
- 该连接的连接,注意连接方法
3.1 设计部分:1位八选一数据选择器
3.1.1 层次建模
3.1.2 设计模型
产生的变化:
- 地址输入端:增加一个
- 利用使能端
- E为1时,高4位有效
- E为0时,低4位有效
- 两个四选一数据选择器:屏蔽理论——利用非门
- 高4位
- 低4位
- 输出端口要合并,使用逻辑或
- 共同的部分连一起
注意对应关系:例如,高4位的0,1,2,3要分别对应4,5,6,7,顺序不能颠倒!
对于RTL级建模来说,这个逻辑图,不画也罢,直接写出其逻辑关系即可!
另外,这里也要为以后考虑,也就是,需要再这个模型上,再加上一个使能端E,以便于后续的扩展。
3.2 设计块
// 一位八选一数据选择器
module choose_8to1 (input i0,i1,i2,i3,i4,i5,i6,i7, // 数据输入端input s2,s1,s0, // 地址输入端input e, // 新的使能端output out // 数据输出端);wire out1,out2; // 两个四选一的输出choose_4to1 DT4 (i4,i5,i6,i7,s1,s0,s2,out1); // 高4位choose_4to1 DT5 (i0,i1,i2,i3,s1,s0,~s2,out2); // 低4位assign out = (out1 | out2) & e;endmodule
3.3 激励块
module test();reg I0 = 1,I1 = 0,I2 = 1,I3 = 0,I4 = 1,I5 = 0,I6 = 1,I7 = 0;reg S2,S1,S0;reg E = 1;wire OUT;choose_8to1 CT0 (I0,I1,I2,I3,I4,I5,I6,I7,S2,S1,S0,E,OUT);initial#1 $monitor("S2 = %b, S1 = %b, S0 = %b, OUT = %b\n",S2,S1,S0,OUT);initialbegin$display("I0 = %b, I1 = %b, I2 = %b, I3 = %b\nI4 = %b, I5 = %b, I6 = %b, I7 = %b\n\n",I0,I1,I2,I3,I4,I5,I6,I7);#1 S2 = 0; S1 = 0; S0 = 0;#1 S2 = 0; S1 = 0; S0 = 1;#1 S2 = 0; S1 = 1; S0 = 0;#1 S2 = 0; S1 = 1; S0 = 1;#1 S2 = 1; S1 = 0; S0 = 0;#1 S2 = 1; S1 = 0; S0 = 1;#1 S2 = 1; S1 = 1; S0 = 0;#1 S2 = 1; S1 = 1; S0 = 1;endendmodule
4 混合型扩展:多位八选一数据选择器
- 先扩展为4位四选一数据选择器
- 再扩展为4位八选一数据选择器
4.1 层次建模
4.2 模块实现
4.2.1 设计块
// 4位八选一数据选择器
module choose_8to1_4sizes (input [3:0] i0,i1,i2,i3,i4,i5,i6,i7, // 数据输入端input s2,s1,s0, // 地址输入端input [3:0] e, // 新的使能端output [3:0] out // 数据输出端);wire [3:0] out1,out2; // 两个四选一的输出choose_4to1_4sizes DT4_0 (i4,i5,i6,i7,s1,s0,s2,out1); // 高4位choose_4to1_4sizes DT4_1 (i0,i1,i2,i3,s1,s0,~s2,out2); // 低4位assign out = (out1 | out2) & e;endmodule
4.2.2 激励块
module test();reg [3:0] I0 = 1,I1 = 2,I2 = 3,I3 = 4,I4 = 5,I5 = 6,I6 = 7,I7 = 8;reg S2,S1,S0;reg [3:0] E = 4'b1111;wire [3:0] OUT;choose_8to1_4sizes CT0 (I0,I1,I2,I3,I4,I5,I6,I7,S2,S1,S0,E,OUT);initial#1 $monitor("S2 = %b, S1 = %b, S0 = %b, OUT = %d\n",S2,S1,S0,OUT);initialbegin$display("I0 = %b, I1 = %b, I2 = %b, I3 = %b\nI4 = %b, I5 = %b, I6 = %b, I7 = %b\n\n",I0,I1,I2,I3,I4,I5,I6,I7);#1 S2 = 0; S1 = 0; S0 = 0;#1 S2 = 0; S1 = 0; S0 = 1;#1 S2 = 0; S1 = 1; S0 = 0;#1 S2 = 0; S1 = 1; S0 = 1;#1 S2 = 1; S1 = 0; S0 = 0;#1 S2 = 1; S1 = 0; S0 = 1;#1 S2 = 1; S1 = 1; S0 = 0;#1 S2 = 1; S1 = 1; S0 = 1;endendmodule
4.2.3 结果验证
4.3 重要知识点与思维方法
4.3.1 解决错误的方法论
- 分析程序的行为是什么,而不是为什么程序不按照你想的来
- 注意使用单元测试
- 保证低层模块没有问题,再实现高层模块
- 如果高层模块出了问题,先从高层找问题
- 再逐一找到之前没有问题的低层模块,进行修正
4.3.2 按位运算与逻辑运算
- 按位运算是二进制位的每一位进行运算,其结果位数为:两个操作数的最小位数
- 逻辑运算把操作数都当成0,1,x,z,其结果也是0,1,x,z(这里是粗略说明,详细内容可以百度一下对应真值表)
4.3.2.1 按位运算:位宽不匹配时的注意事项
当按位运算的两操作数位宽不相等时,短位宽数会在左侧补0,使两操作数位宽一致。
在上述4位八选一数据选择器的设计块中,模块结尾处有这样一条语句:assign out = (out1 | out2) & e;
并且,我在前面使用的是位宽为4的使能端,这里,我想有必要详细解释一下为什么这样做,并且我会告诉你更好的解决方案。
我们知道以下的事实:
- 使能端的作用: 它就相当于选择器的开关,打开它选择器才能正常工作,关闭它选择器就不能工作。
- 开关的两种形式: 低电平有效和高电平有效,这里我采用的是高电平有效,也就是
e = 1
的时候才有效(但是事实上,大多数情况我们都采用低电平有效的使能端,可是,你想过为什么吗?我在后面将解释为什么要这样做)
接下来,我将会讲解为什么使用4位的使能端:
4位的使能端,只有当e = 4b'_1111
的时候才能启动选择器,如果它是1位的,我们则只能为他赋值e = 1'b_1
,此后它将与4位的数据进行逻辑与运算,我们知道,位宽不匹配的时候,短位宽将会自动补0再运算,此时的e将会被虚拟地增加3个0e = 4'b_0001
,显然这样运算的结果是错误的。
我想你能够明白为什么高电平有效的使能端,在这里要使用4位位宽了。
不过,这样会对未来造成麻烦,我非常不建议你这样做,试想一下,如果用这样的八选一数据选择器,去扩展为十六选一的,将会出现一些问题。
那么,如何保证使能端依然是1位呢?
请注意一个事实,那就是,位宽不匹配的时候,系统会自动补0,因为补的是0,所以,我们应该采用低电平有效的使能端,将前面的逻辑表达式改一下就可以assign out = (out1 | out2) & (~e);
5 组合型扩展:一位/多位双四选一数据选择器
这个就是简单的组合而已,没什么好说的,除了使能端共用,输入输出端口依然还是各自独立的。
6 深入浅出:将数据选择器讲给孩子听
尝试将专业的知识将给孩子听,并让他听懂,就说明你真的掌握了,我给你一些提示:
- 输入:香蕉,苹果,橘子,大鸭梨
- 控制:开关 X 1,控制端 X 2
- 输出:某一个水果
7 一句话总结
数据选择器就是,将多组数据输入,通过地址控制和开关控制,输出其中一组指定的数据。
8 科技黑箱:直接应用“数据选择器”解决现实问题
——现实生活中有项目需求,但是没有问题,也没有答案
8.0 对数据选择器新的理解
使用数据选择器,实现:数据输入端的并联,不同的地址,输出同一个结果
8.1 需求:红绿灯故障检测系统
- 正常情况:三盏灯只有一个是亮的
- 故障情况:正常情况均为故障
现要求,能够实现故障的检测,出现故障的时候系统会自动报警,请使用数据选择器设计逻辑电路
8.2 设计部分:组合逻辑电路
8.2.1 信息符号化
两个核心要点:
- 信息的符号表示
- 逻辑值的含义
- 三盏灯 1代表亮,0代表不亮
- 红灯 R
- 黄灯 Y
- 绿灯 G
- 故障检测 F
- F = 1,代表故障
- F = 0,代表没有故障
- 检测开关:使能端E,低电平有效
8.2.2 求逻辑表达式
真值表如下:
Ebar | R | Y | G | 输出:Fbar |
---|---|---|---|---|
1 | x | x | x | x |
0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 1 | 1 |
0 | 0 | 1 | 0 | 1 |
0 | 0 | 1 | 1 | 0 |
0 | 1 | 0 | 0 | 1 |
0 | 1 | 0 | 1 | 0 |
0 | 1 | 1 | 0 | 0 |
0 | 1 | 1 | 1 | 0 |
0 | x | x | x | x |
逻辑表达式如下:
Fbar = Rbar·Ybar·G + Rbar·Y·Gbar + R·Ybar·Gbar
= m0·G + m1·Gbar + m2·Gbar + m3·0
8.2.3 器件选择
使用四选一数据选择器
- 地址输入端:R,Y
- 数据输入端:G,Gbar
- 使能端:E
- 数据输出端:F
8.3 实现部分:Verilog实现逻辑电路
8.3.0 构建模型
8.3.1 设计块
// 红绿灯故障检测
module RYG_Flault_test (input G, // 数据输入input R,Y, // 地址输入input E, // 使能端output F); wire F1;choose_4to1 RYG_test (G,~G,~G,0,R,Y,E,F1); // 为什么不能输出端口不能连接“~F”??assign F = ~F1;
endmodule
特别注意:输出端口不能连接~F,只能多加一步导一下
8.3.2 激励块
// 红绿灯故障检测系统测试
module test;reg R,Y;reg G;reg E = 1;wire F;RYG_Flault_test RYG_Test(G,R,Y,E,F); // 红绿灯检测模块实例initial$monitor($time," R = %b, Y = %b, G = %b, F = %b\n",R,Y,G,F); // 显示红绿灯的状况initialbegin#1 R = 0; Y = 0; G = 0;#1 R = 0; Y = 0; G = 1;#1 R = 0; Y = 1; G = 0;#1 R = 0; Y = 1; G = 1;#1 R = 1; Y = 0; G = 0;#1 R = 1; Y = 0; G = 1;#1 R = 1; Y = 1; G = 0;#1 R = 1; Y = 1; G = 1;end endmodule
8.3.3 结果验证
9 深入剖析数据选择器的本质
数据选择器的本质,是多路选择器!,它可以应用于任何需要做出功能选择的电路中去,它是非常常见并且实用的,数据选择器的设计思想可以推广到任意功能的选择上去,而不仅仅是数据的选择。
10 组合逻辑电路的设计
各种器件的本质,不过是逻辑表达式,对于期间的使用,需要进行实际需求表达式与器件逻辑表达式的对应关系即可!
这个部分我将会在其他文章阐述,敬请期待。
11 Verilog之行为级建模的魅力
事实上,对于以上的Verilog代码,并不需要那样繁琐,也不需要那么多行,为什么我依然坚持使用呢?
- 充分深入底层逻辑,并通过实战帮助你理解数据选择器,为后面使用多路选择器打基础
- 充分使用层次建模思想解决问题,锻炼核心思维
请记住,即便有了更为简单的方法,我们依然不应该完全放弃底层知识的学习,没有底层积累,是不能设计出优秀的顶层设计的。
行为级仿真代码留给读者自行完成。