实验3 静态数码管显示一
在设计复杂数字系统时,根据整个系统也就是顶层的功能需求进行分析,将复杂的系统功能分解为多个必要的子功能,依据这些子功能分别对各个功能模块进行设计,这些功能模块同样可以再继续分解为多个更底层的模块,这就是自顶向下的设计思想,也是目前主流的数字系统设计思想,而模块化设计就是遵循这一设计思想的重要设计方法。事实上,无论多么复杂的系统总能够逐步分解为多个小的功能模块,模块化设计可以令整个设计的思路更清晰,便于大型设计的分工合作和仿真测试,而且有助于设计文件的维护和复用。在这篇文章中,将会分享两个组合逻辑的模块化设计实例以及仿真和上板演示过程。
自顶向下的设计思想
数码管显示原理
由于本次介绍的两个实例都涉及数码管的显示问题,因此在进行实例分析之前,我们先了解一下数码管的显示原理。开发板上八段数码管的实物图如下所示,所谓八段数码管是指通过八个发光二极管按指定图形排列并封装在一起的显示器件,单个八段数码管可以显示数字0至9、字母A至F以及一个小数点。
数码管
下图是开发板上的数码管原理图,数码管内八个发光二极管的阴极连接在一起故被称为共阴数码管,六个数码管的显示由8bit的段码以及4bit的位码控制,8bit的段码由八个段选信号从低位到高位以LED A、LEDB、LED C、LED D、LED E、LED F、LEDG、 LED DP的顺序组成,4bit的位码由4个位选信号从低位到高位以DIG1(或COM1,下同)、DIG2、DIG3、DIG 4的顺序组成。段码决定数码管显示的数字或字母,而位码决定点亮哪几位数码管。对于共阴极数码管而言,段选信号是高电平有效而位选信号是低电平有效的。一般来说,多位数码管的段选信号是连接在共享复用的,这可以节省数码管占用的I/O,本文演示所使用的FPGA开发板上的数码管也是如此,因此同一时刻选中的位码会根据当前段码在被选中的位上显示相同的数字或字母。
开发板上数码管的原理图
下面举例说明一下怎么让数码管显示指定的数字或字母。例如,对于开发板上的共阴极数码管,要使从左往右第一个数码管显示数字7,就需要通过位选信号选中DIG1,使第一个数码管被选中,而为了显示数字7,根据上方的原理图,需要点亮LED A、LED B以及LED C,所以数码管的位码(低电平有效)应为4’b1110,段码(高电平有效)为8’b00000111,若用十六进制表示,段码为8’h07。类似地,如果需要使左边三个数码管同时显示字母F(十六进制的15),那么就需要通过位选信号选中DIG1、DIG2和DIG3,而为了显示字母F,就需要点亮LED A、LED E、LED F以及LED G,所以数码管的位码应为4’b1000,段码为8’01110001即十六进制的8’h71。
本文的实例如下:在FPGA开发板上实现一个组合逻辑电路,拨码开关SW0至SW3作为4bit的数据输入并通过数码管以十六进制显示,拨码开关SW4至SW5作为控制输入,控制输入00至11分别对应点亮数码管的DIG1至 DIG4。
实例1框图
分析如上的电路功能需求,不难发现,该电路的功能可以很清晰地分为两个部分,一是根据数据输入决定数码管显示什么样的数字/字母,即对数据输入进行数码管的段码译码;二是根据控制输入决定点亮哪一位的数码管,即对控制输入进行数码管的位选译码。那么依据模块化设计的思路和上文所介绍的数码管显示原理,我们可以针对分解得到的两个功能分别设计对应的功能模块。对于底层功能模块的设计,就和上一篇文章介绍的组合逻辑基础设计一样,首先弄清楚该模块的输入输出关系,再通过硬件描述语言对电路进行描述。本例中,段码译码模块的输入为4bit的数据输入,输出为8bit的数码管段码输出,位选译码模块的输入为2bit的控制输入,输出为4bit的数码管位选输出,两者都可采用译码器进行设计。
代码分析
如下是数码管段码译码模块的RTL代码,根据上文介绍的数码管显示原理,通过case语句分别给出各输入情况下对应的数码管段码输出即可。
module seg_decoder(data_disp,seg);
input [3:0] data_disp;
output reg [7:0] seg;
always@(data_disp)
case(data_disp)
4'h0 : seg = 8'h3f;
4'h1 : seg = 8'h06;
4'h2 : seg = 8'h5b;
4'h3 : seg = 8'h4f;
4'h4 : seg = 8'h66;
4'h5 : seg = 8'h6d;
4'h6 : seg = 8'h7d;
4'h7 : seg = 8'h07;
4'h8 : seg = 8'h7f;
4'h9 : seg = 8'h6f;
4'ha : seg = 8'h77;
4'hb : seg = 8'h7c;
4'hc : seg = 8'h39;
4'hd : seg = 8'h5e;
4'he : seg = 8'h79;
4'hf : seg = 8'h71;
endcase
endmodule
接下来是数码管位选译码模块的参考代码,要注意的是由于开发板上的数码管为共阴极连接,因此位选是低电平有效,需要点亮的数码管对应的位选信号应该给低电平。
module DIG_decoder(ctrl,sel);
input [1:0] ctrl;
output reg [3:0] sel;
always @ (ctrl)
case (ctrl)
2'b00 : sel = 4'b1110;
2'b01 : sel = 4'b1101;
2'b10 : sel = 4'b1011;
2'b11 : sel = 4'b0111;
endcase
endmodule
子功能模块设计好后,便可以对它们进行连接以构建功能完整的系统。下面将进行顶层文件的编写,通常只需要将各个功能模块例化并进行正确的连接即可。各个模块的例化其实就和画电路图一样,要分清楚各个模块的输入输出以及它们之间的连接关系。
module TOP(data_disp,ctrl,seg,sel);
input [3:0] data_disp;
input [1:0] ctrl;
output [7:0] seg;
output [3:0] sel;
DIG_decoder DIG_decoder(
.ctrl(ctrl),
.sel(sel)
);
seg_decoder seg_decoder(
.data_disp(data_disp),
.seg(seg)
);
endmodule
RTL代码文件都编写好后,不妨将这些文件都加入工程中,如果代码编写正确,在默认设置下就可以在TD的Project窗口下已经自动地进行了分层显示,如下图所示,可以清楚地看到设计文件的层次。
TD中Project的分层显示
添加好设计文件后还可以在TD的Tools -> SchematicViewer -> Detailed -> Read Design Schematic得到电路原理图。
可以看到生成的原理图这与我们进行功能分解时画的框图很相似,在该图中可以清晰地看到各个模块是如何进行连接的,双击这些功能模块还可以看到每个模块内的电路细节。
实例1的RTL分析电路原理图
功能仿真
在进行复杂数字系统设计时,应该对各个子功能模块分别进行独立的仿真测试,最后再对由这些模块构建的系统进行整体的仿真,本文的实例由于相对简单,将略去对子功能模块仿真的步骤。如下是本例的testbench参考代码:
timescale 1ns / 1ps
module digitron_tb;
reg [3:0] data_disp;
reg [1:0] ctrl;
wire [7:0] seg;
wire [3:0] sel;
TOP uut(
.data_disp(data_disp),
.ctrl(ctrl),
.seg(seg),
.sel(sel)
);
initial begin
data_disp = 4'b0000;
ctrl = 2'b00;
end
always #20 begin
data_disp = data_disp + 1;
ctrl = ctrl + 1;
end
endmodule
下图是本例在Modelsim中的部分仿真波形,testbench中已测试了所有的输入组合,比对输入和输出结果,可见该电路实现了正确的功能。
实例1的仿真波形
上板验证
完成仿真后,就可进行后续的综合、管脚约束、布局布线以及生成比特流文件,根据实例1的功能需求,本例具体的管脚约束如下表所示。
set_pin_assignment { ctrl[0] } { LOCATION = A12; }
set_pin_assignment { ctrl[1] } { LOCATION = B12; }
set_pin_assignment { data_disp[0] } { LOCATION = A9; }
set_pin_assignment { data_disp[1] } { LOCATION = A10; }
set_pin_assignment { data_disp[2] } { LOCATION = B10; }
set_pin_assignment { data_disp[3] } { LOCATION = A11; }
set_pin_assignment { sel[0] } { LOCATION = C9; IOSTANDARD = LVCMOS33; }
set_pin_assignment { sel[1] } { LOCATION = B6; IOSTANDARD = LVCMOS33; }
set_pin_assignment { sel[2] } { LOCATION = A5; IOSTANDARD = LVCMOS33; }
set_pin_assignment { sel[3] } { LOCATION = A3; IOSTANDARD = LVCMOS33; }
set_pin_assignment { seg[0] } { LOCATION = A4; IOSTANDARD = LVCMOS33; }
set_pin_assignment { seg[1] } { LOCATION = A6; IOSTANDARD = LVCMOS33; }
set_pin_assignment { seg[2] } { LOCATION = B8; IOSTANDARD = LVCMOS33; }
set_pin_assignment { seg[3] } { LOCATION = E8; IOSTANDARD = LVCMOS33; }
set_pin_assignment { seg[4] } { LOCATION = A7; IOSTANDARD = LVCMOS33; }
set_pin_assignment { seg[5] } { LOCATION = B5; IOSTANDARD = LVCMOS33; }
set_pin_assignment { seg[6] } { LOCATION = A8; IOSTANDARD = LVCMOS33; }
set_pin_assignment { seg[7] } { LOCATION = C8; IOSTANDARD = LVCMOS33; }
管脚约束
实验4 静态数码管显示二
实验原理
本文的第二个实例如下:在FPGA开发板上实现一个组合逻辑电路,拨码开关SW0至SW3为第一个数据输入ina,拨码开关SW4至SW7为第二个数据输入inb,由数码管以十六进制形式显示ina与inb中较大的那个数,且ina较大时由右边两位数码管一起显示,inb较大时由左边两位数码管一起显示,两个输入相等时由四位数码管共同显示该数据。
实例2框图
这里同样采用模块化设计的思路,首先对电路进行功能分解,可以看到整个电路的输入是两个数据输入ina和inb,输出仍然是数码管的段码和位选信号。不难分析出,该例从输入到输出有一个比较、选择和显示译码的过程。有了实例一的经验,既然涉及多位数码管的显示,那么必然需要由数码管段码译码模块和位选译码模块来负责显示译码的功能,两者都可以通过设计译码器来解决。选择模块也很简单,其功能是从两个数据输入中选择其一进行后续的显示译码,通过上一篇文章也介绍过的多路复用器即可实现。最后再分析比较模块,它的功能应该是比较两个数据输入的大小,并输出比较的结果,这很容易联想到数字电路中学习过的比较器。
代码解析
下面是比较器的参考代码,实现对两个4bit的输入数据进行比较,并通过高电平有效的LT(表示ina<inb)、GT(表示ina>inb)、EQ(ina = inb)三个信号输出比较的结果。
module comparator(ina,inb,LT,GT,EQ);
input [3:0] ina;
input [3:0] inb;
output LT;
output GT;
output EQ;
assign LT = (ina < inb) ? 1'b1 : 1'b0;
assign GT = (ina > inb) ? 1'b1 : 1'b0;
assign EQ = (ina == inb) ? 1'b1 : 1'b0;
endmodule
如下是选择模块的参考代码,通过多路复用器从两路4bit的输入数据中选择一路进行输出。根据这种写法,选择信号为0时输出ina,选择信号为1时输出inb,因此根据本例的功能需求,在连接时需要将比较器的小于输出信号LT连到该模块的选择信号输入端口。
module Mux(ina,inb,sel,data_out);
input [3:0] ina;
input [3:0] inb;
input sel;
output [3:0] data_out;
assign data_out = sel ? inb : ina;
endmodule
数码管的段码译码模块与实例1完全一致,这里仅给出数码管位选译码的参考代码。根据本例的功能需求,数码管的位选根据ina与inb的三种大小关系(ina>inb, ina<inb,ina=inb)有三种不同的数码管位选结果(右边两位点亮,左边两位点亮,四位全部点亮),那么该译码模块至少需要两位的输入编码,下面的代码编写时已提前设想好通过将比较器的等于输出信号EQ和大于输出信号GT作为译码器的2bit编码输入。
module DIG_decoder(ctrl,sel);
input [1:0] ctrl;
output reg [3:0] sel;
always@(ctrl)
case(ctrl)
2'b00 : sel = 4'b1100;
2'b01 : sel = 4'b0011;
2'b10 : sel = 4'b0000;
default : sel = 4'b1111;
endcase
endmodule
最后是顶层文件的编写,需要注意各模块间的连接,尤其是比较器输出的连接,在上文已进行过详细的说明。
module TOP(ina,inb,seg,sel);
input [3:0] ina;
input [3:0] inb;
output [7:0] seg;
output [3:0] sel;
wire LT;
wire GT;
wire EQ;
wire [3:0] data_disp;
comparator comparator(
.ina(ina),
.inb(inb),
.LT(LT),
.GT(GT),
.EQ(EQ)
);
Mux Mux(
.ina(ina),
.inb(inb),
.sel(LT),
.data_out(data_disp)
);
DIG_decoder DIG_decoder(
.ctrl({EQ,GT}),
.sel(sel)
);
seg_decoder seg_decoder(
.data_disp(data_disp),
.seg(seg)
);
Endmodule
这里我们同样可以借助TD的RTL分析功能得到对应RTL代码的电路原理图,如下图所示,能直观地看到四个子模块及其连接方式。在仿真之前,可以使用这个功能检查一下子模块的连接。
实例2的RTL分析电路原理图
功能仿真
接下来是对设计进行功能仿真,下面给出了实例2的testbench参考文件,设置了几组不同大小关系的输入组合。
timescale 1ns / 1ps
module digitron_tb;
reg [3:0] ina;
reg [3:0] inb;
wire [7:0] seg;
wire [5:0] sel;
TOP uut(
.ina(ina),
.inb(inb),
.seg(seg),
.sel(sel)
);
initial begin
ina = 4'h0; inb = 4'h0; //ina = inb
#20
ina = 4'h5; inb = 4'h2; //ina > inb
#20
ina = 4'h7; inb = 4'h9; //ina < inb
#20
ina = 4'h6; inb = 4'h6; //ina = inb
#20
ina = 4'he; inb = 4'ha; //ina > inb
#20
ina = 4'hb; inb = 4'hf; //ina < inb
#20
$stop;
end
endmodule
使用上述testbench得到的部分仿真波形如下,这里除了顶层的输入输出信号外还添加了中间信号比较器的输出波形进行观察,可以看到比较器输出正常,数码管的段码与位选也译码正确。
实例2的仿真波形
上板验证
之后进行综合、管脚约束、布局布线以及生成比特流文件。这里同样给出了本例具体的管脚约束,根据要求,将两个4bit的数据输入分别约束至了拨码开关SW0至SW3以及SW4至SW7,输出约束至数码管的段选和位选即可。
set_pin_assignment { ina[0] } { LOCATION = A9; }
set_pin_assignment { ina[1] } { LOCATION = A10; }
set_pin_assignment { ina[2] } { LOCATION = B10; }
set_pin_assignment { ina[3] } { LOCATION = A11; }
set_pin_assignment { inb[0] } { LOCATION = A12; }
set_pin_assignment { inb[1] } { LOCATION = B12; }
set_pin_assignment { inb[2] } { LOCATION = A13; }
set_pin_assignment { inb[3] } { LOCATION = A14; }
set_pin_assignment { sel[0] } { LOCATION = C9; IOSTANDARD = LVCMOS33; }
set_pin_assignment { sel[1] } { LOCATION = B6; IOSTANDARD = LVCMOS33; }
set_pin_assignment { sel[2] } { LOCATION = A5; IOSTANDARD = LVCMOS33; }
set_pin_assignment { sel[3] } { LOCATION = A3; IOSTANDARD = LVCMOS33; }
set_pin_assignment { seg[0] } { LOCATION = A4; IOSTANDARD = LVCMOS33; }
set_pin_assignment { seg[1] } { LOCATION = A6; IOSTANDARD = LVCMOS33; }
set_pin_assignment { seg[2] } { LOCATION = B8; IOSTANDARD = LVCMOS33; }
set_pin_assignment { seg[3] } { LOCATION = E8; IOSTANDARD = LVCMOS33; }
set_pin_assignment { seg[4] } { LOCATION = A7; IOSTANDARD = LVCMOS33; }
set_pin_assignment { seg[5] } { LOCATION = B5; IOSTANDARD = LVCMOS33; }
set_pin_assignment { seg[6] } { LOCATION = A8; IOSTANDARD = LVCMOS33; }
set_pin_assignment { seg[7] } { LOCATION = C8; IOSTANDARD = LVCMOS33; }
实例2管脚约束
至此,两个组合逻辑的模块化设计实例就分享完毕了,这里简单总结一下,模块化设计是设计复杂数字系统的重要方法,对复杂设计的分工、仿真测试以及代码的维护和复用都有好处,如何将复杂的功能划分成若干小的功能模块和子系统是一种能力,是FPGA/逻辑设计工程师真正的核心竞争力。进行模块化设计时,首先要明确电路的功能需求,将复杂的整体功能逐步分解为相对简单的子功能,然后根据分解得到的子功能来设计各个功能模块,最后将各个功能模块进行例化连接,构建功能完整的系统。
END
文章来源:
https://www.yuque.com/yingmuketang/01/dh38vu
https://www.yuque.com/yingmuketang/01/zd64dt
推荐内容
- 【安路 EG4S20 版本】基础板卡信息及使用教程:芯片和板卡简介
- 【安路 EG4S20 版本】基础板卡信息及使用教程:第一工程
- 【安路 EG4S20 版本】基础板卡信息及使用教程:使用ChipWatcher
- 【安路 EG4S20 版本】基础板卡信息及使用教程:使用Modelsim
- 【安路 EG4S20 版本】基础实验设计与实现:实验1 流水灯
- 【安路 EG4S20 版本】基础实验设计与实现:实验2 集成逻辑门及其基本应用
- 【安路 EG4S20 版本】基础实验设计与实现:实验3 译码器 编码器
- 【安路 EG4S20 版本】基础实验设计与实现:实验4 数据选择器
- 【安路 EG4S20 版本】基础实验设计与实现:实验5 触发器
- 【安路 EG4S20 版本】基础实验设计与实现:实验6 加法计数器
- 【安路 EG4S20 版本】基础实验设计与实现:实验7 抢答器
- 【安路 EG4S20 版本】基础实验设计与实现:实验8 功能数字钟
- 【安路 EG4S20 版本】基础实验设计与实现:实验9 矩阵键盘
- 【安路 EG4S20 版本】基础实验设计与实现:实验10 二进制转BCD码
- 【安路 EG4S20 版本】综合性实验设计与实现:实验11 PWM
- 【安路 EG4S20 版本】综合性实验设计与实现:实验12 DA及DDS
- 【安路 EG4S20 版本】综合性实验设计与实现:实验13 FPGA内部AD多通道采样实验
- 【安路 EG4S20 版本】综合性实验设计与实现:实验14 UART串行通信
- 【安路 EG4S20 版本】综合性实验设计与实现:实验15 高速ADC和DAC实验
- 【安路 EG4S20 版本】HDMI_Ethernet_DAP_SDRAM_Camera使用指南
- 【安路 EG4S20 版本】组合逻辑基础实验
更多内容请关注走进FPGA专栏