无锡市梁溪区建设局网站,网站表格怎么做,怎么看一级还是二级域名,网站改版影响数码管动态显示 文章目录 1 理论学习1.1 数码管动态扫描显示原理 2 实战演练2.1 实验目标2.2 程序设计2.2.1 框图绘制2.2.2 数据生成模块 data_gen2.2.2.1 波形绘制2.2.2.2 代码编写2.2.2.3 代码编译2.2.2.4 逻辑仿真2.2.2.4.1 仿真代码编写2.2.2.4.2 仿真代码编译2.2.2.4.3 波…数码管动态显示 文章目录 1 理论学习1.1 数码管动态扫描显示原理 2 实战演练2.1 实验目标2.2 程序设计2.2.1 框图绘制2.2.2 数据生成模块 data_gen2.2.2.1 波形绘制2.2.2.2 代码编写2.2.2.3 代码编译2.2.2.4 逻辑仿真2.2.2.4.1 仿真代码编写2.2.2.4.2 仿真代码编译2.2.2.4.3 波形仿真 2.2.3 BCD 转码模块 bcd_84212.2.3.1 BCD 码简介2.2.3.2 框图绘制2.2.3.3 波形绘制2.2.3.4 代码编写2.2.3.5 代码编译2.2.3.6 逻辑仿真2.2.3.6.1 仿真代码编写2.2.3.6.2 仿真代码编译2.2.3.6.3 波形仿真 2.2.4 动态显示驱动模块 segment_dynamic2.2.4.1 波形绘制2.2.4.2 代码编写2.2.4.3 代码编译2.2.4.4 逻辑仿真2.2.4.4.1 仿真代码编写2.2.4.4.2 仿真代码编译2.2.4.4.3 波形仿真 2.2.5 动态显示模块 segment_595_dynamic2.2.5.1 代码编写2.2.5.2 代码编译 2.2.6 顶层模块 top_segment_5952.2.6.1 代码编写2.2.6.2 代码编译2.2.6.3 逻辑仿真2.2.6.3.1 仿真代码编写2.2.6.3.2 仿真代码编译2.2.6.3.3 波形仿真 2.2.7 上板验证2.2.7.1 引脚绑定2.2.7.2 结果验证 Quartus II 报错信息汇总 Error (12007): Top-level design entity “top_segment_595” is undefined Error (10170): Verilog HDL syntax error at segment_dynamic.v(129) near text “1”; expecting “;” Error (10228): Verilog HDL error at bcd_8421.v(2): module “bcd_8421” cannot be declared more than once Error (12006): Node instance “hc595_ctrl_inst” instantiates undefined entity “hc595_ctrl” Error (10112): Ignored design unit “tb_top_segment_595” at tb_top_segment_595.v(5) due to previous errors
在上一小节当中我们对数码管的静态显示做了一个详细的讲解但是如果单单只掌握数码管的静态显示这种显示方式是远远不够的因为数码管的静态显示当中被选中的数码位它们显示的内容都是相同的这种显示方式在实际应用当中显然是不合适的我们希望控制每个数码位能够独立的显示我们想要显示的内容如何实现这一操作呢就是本小节所要讲解的内容数码管的动态显示。
本小节的主要内容分为两个部分
第一部分是理论学习在这一部分我们会对数码管的动态显示的工作原理做一个详细的讲解第二部分是实战演练在这一部分会通过实验工程设计并实现数码管的动态显示。
首先是理论学习
1 理论学习
我们征途系列开发板使用的是六位八段数码管实物图1如下 焊接在开发板的右上角位置 1.1 数码管动态扫描显示原理
六位八段数码管的内部结构图如下 由图可知六位八段数码管当中每个数码位的段选信号全部连接到了一起然后进行输出每个数码位单独引出一个位选信号用来控制数码位的选择这种连接方式会使得被选中的数码位显示的内容都是相同的因为这些被选中的数码位的段选信号已经全部连接到了一起。如何使用这个六位八段数码管来实现数码管的动态显示呢我们需要使用一种方式动态扫描
如何使用动态扫描的方式来实现数码管的动态显示呢这里给大家举一个例子比如说我们想要使用六位八段数码管显示数字 123456 如何使用六位八段数码管来显示数字 123456 呢
首先我们选中第一个数码位让这个数码位显示数字 1然后它显示的时间设为 T \text{T} T这个 T \text{T} T 可以看作一个周期 当第一个数码位完成一个 T \text{T} T 周期数字 1 的显示之后立刻选中第二个数码位注意此时只选中了第二个数码位让它显示数字 2显示的时间同样是一个周期 T \text{T} T 当第二个数码位完成了一个周期 T \text{T} T 数字 2 的显示之后立刻选中第三个数码位这儿注意也是只选中了第三个数码位让它显示数字 3同样显示时间为 T \text{T} T 依次往下类推那么此时就显示 4 然后是 5 然后是 6 当第六个数码位完成了一个周期 T \text{T} T 数字 6 的显示之后再重新选中第一个数码位这儿也是只选中了第一个数码位让它继续显示数字 1然后显示的时间仍然是 T \text{T} T 周期这样依次往下循环。
通过上述动态显示过程的描述我们知道这样一个循环是六个周期就是 6 T 6\text{T} 6T如果说给这个 T \text{T} T 规定一个确切的时间会怎样呢首先给 T \text{T} T 规定一个确切的时间 1s如果说 T \text{T} T 等于 1s六位八段数码管的六个数码位会依次显示 1、2、3、4、5、6 每个数字显示的时间为 1s 如果进一步把这个 T \text{T} T 进行缩短比如说缩短到 0.2s 这时候六位八段数码管的六个数码位会进行闪烁显示显示的内容依次是 1、2、3、4、5、6 如果进一步缩短时间 T \text{T} T 为 1ms这时候六位八段数码管的六个数码位实际上也是依次进行闪烁的显示显示的内容依次是 1、2、3、4、5、6 每个数字显示时间是 1ms 但是它们切换的频率太快了我们的肉眼不能分辨这种闪烁就误以为六位八段数码管的六个数码位在同时进行显示而且显示的内容是 123456。
这样就使用动态扫描的方式实现了数码管的动态显示。
使用动态扫描的方式实现数码管的动态显示实际上是利用了两个现象人眼的视觉暂留特性和数码管的余晖效应。人眼在观察景物时光信号传入到大脑神经需要经过一段时间光的作用结束之后我们的视觉影像并不会立刻的消失这种残留的视觉被称为后像这种现象就被称为视觉暂留数码管的余晖效应是什么意思呢当我们停止向发光二极管供电时发光二极管的亮度仍能够维持一段时间。我们的动态扫描利用这两个特性就实现了数码管的动态显示。
以上内容就是数码管动态显示的工作原理接下来就开始进行实战演练
2 实战演练
在实战演练部分我们会通过实验工程设计并实现数码管的动态显示。
2.1 实验目标
首先先来说一下我们的实验目标。我们的实验目标是使用六位八段数码管实现数码管的动态显示显示的内容是十进制的 ( 0 ) 10 (0)_{10} (0)10 到最大值 ( 999999 ) 10 (999999)_{10} (999999)10当计数到最大值让它归零循环显示每 0.1s 加 1也就是说第一个 0.1s 显示的是 0第二个 0.1s 显示的是 1第三个 0.1s 显示的是 2然后依次往后排。实验目标的效果如下
六位数码管显示0~359999
六位数码管显示360000~719999
六位数码管显示720000~999999
2.2 程序设计
了解了实验目标之后下面开始程序的设计。
首先建立一个文件体系
segment_595_dynamic
├─doc
├─quartus_prj
├─rtl
└─sim2.2.1 框图绘制
然后打开 doc 文件夹建立一个 Visio 文件用来绘制框图和波形图
segment_595_dynamic
├─doc
│ segment_595_dynamic.vsdx
│
├─quartus_prj
├─rtl
└─sim首先先来绘制模块框图顶层模块 top_segment_595 的框图如下 输入信号
sys_clk系统时钟信号sys_rst_n系统复位信号
输出信号(传入到 74HC595)
ds串行数据shcp移位寄存器时钟stcp存储寄存器时钟oe_n输出使能
下面结合我们的实验目标和层次化的设计思想将整个系统工程进行子功能的划分。我们的实验目标是使用六位八段数码管实现数码管的动态显示显示的内容是 ( 0 ) 10 (0)_{10} (0)10 到 ( 999999 ) 10 (999999)_{10} (999999)10显示的这个数据肯定需要一个模块来产生我们就先定义一个新的子功能模块
数据生成模块 data_gen
让该模块产生我们需要显示的数据 输入信号
sys_clk系统时钟信号sys_rst_n系统复位信号
输出信号
data[19:0]待显示的数据。它的最大值是 ( 999999 ) 10 (999999)_{10} (999999)10换算成二进制就是 ( 1111 _ 0100 _ 0010 _ 0011 _ 1111 ) 2 (1111\_0100\_0010\_0011\_1111)_{2} (1111_0100_0010_0011_1111)2所以它的位宽是 20就是 [19:0]point[5:0](数码管动态显示模块 segment_595_dynamic 可能会用于温湿度的显示所以说要增加小数点的生成)小数点信号。六位八段数码管有 6 个小数点段所以该信号的位宽是 6 位宽sign(数码管动态显示模块 segment_595_dynamic 可能会用于电压测量的显示所以说要生成符号位)符号位。数值的负号(高电平进行负号的显示)seg_en(为了能够更好的控制数码管动态显示模块 segment_595_dynamic生成一个使能信号)当使能信号为有效的高电平时数码管可以正常的显示当使能信号为低电平时数码管就不工作
有了数据产生模块 data_gen 之后接下来就是数码管动态显示模块 segment_595_dynamic 输入信号
sys_clk系统时钟信号sys_rst_n系统复位信号data[19:0]待显示数据point[5:0]待显示数据的小数点sign待显示数据的负号seg_en控制数码管动态显示模块工作与否的使能信号
数码管动态显示模块 segment_595_dynamic 在后面的实验工程中会经常用到这里为了提高它的复用性所以增加小数点 point[5:0]、符号 sign、使能 seg_en 等信号端口。
输出信号(就是传入到 74HC595 芯片的四路信号)
ds串行数据shcp移位寄存器时钟stcp存储寄存器时钟oe_n输出使能
下面对数码管动态显示模块 segment_595_dynamic 继续进行功能的划分划分的方式参照数码管的静态显示将它划分为两个功能模块第一个模块是动态显示驱动模块 segment_dynamic、第二个模块是 74HC595 控制模块 hc595_ctrl。
为什么要进行模块的进一步划分呢因为 74HC595 控制模块 hc595_ctrl 在数码管的静态显示当中已经完全实现了我们可以直接调用。我们使用动态显示驱动模块 segment_dynamic 生成位选信号 sel[5:0] 和段选信号 seg[7:0]然后 74HC595 控制模块 hc595_ctrl 将位选信号 sel[5:0] 和段选信号 seg[7:0] 转换成 ds、shcp、stcp、oe_n 这四路信号传入到 74HC595 控制芯片。
动态显示驱动模块 segment_dynamic 输入信号
sys_clk系统时钟信号sys_rst_n系统复位信号data[19:0]待显示数据point[5:0]待显示数据小数点位sign待显示数据的符号位seg_en数码管动态显示模块使能信号
输出信号
sel[5:0]位选信号seg[7:0]段选信号
74HC595 控制模块 hc595_ctrl 输入信号
sys_clk系统时钟信号sys_rst_n系统复位信号sel[5:0]位选信号seg[7:0]段选信号
输出信号(就是传入到 74HC595 芯片的四路信号)
ds串行数据shcp移位寄存器时钟stcp存储寄存器时钟oe_n输出使能
各子功能模块的模块框图绘制完成下面开始系统框图的绘制。
首先是数码管动态显示模块 segment_595_dynamic 它的模块功能由两个子功能模块实现输入到数码管动态显示模块 segment_595_dynamic 当中的时钟信号 sys_clk 和复位信号 sys_rst_n 分别传给两个子功能模块数码管动态显示驱动模块 segment_dynamic 产生的位选信号 sel[5:0] 和段选信号 seg[7:0] 传给 74HC595 控制模块 hc595_ctrl。
下面开始系统框图的绘制。顶层模块 top_segment_595 包含两个子功能模块一个是数据产生模块 data_gen另一个是数码管动态显示模块 segment_595_dynamic 传入到顶层模块 top_segment_595 的时钟信号 sys_clk 和复位信号 sys_rst_n 要传给两个子功能模块然后数据生成模块 data_gen 产生的待显示数据 data[19:0]、小数点位 point[5:0]、符号位 sign 和使能信号 seg_en 要传给数码管动态显示模块 segment_595_dynamic。通过这个系统框图我们可以了解各个模块之间的层次关系以及信号的走向。
系统框图绘制完成之后接下来就开始实现各个子功能模块的功能。
2.2.2 数据生成模块 data_gen
2.2.2.1 波形绘制
首先是数据生成模块 data_gen我们先来绘制一下数据生成模块 data_gen 的波形图 由模块框图可知输入信号有两路时钟信号 sys_clk 和复位信号 sys_rst_n。 在实验目标当中我们提到我们想要使用六位八段数码管实现数码管的动态显示显示的内容是十进制的数字 ( 0 ) 10 (0)_{10} (0)10 到最大值 ( 999999 ) 10 (999999)_{10} (999999)10 的循环计数计数的间隔是每 0.1 s 0.1\text{s} 0.1s 加 1 1 1这个 0.1 s 0.1\text{s} 0.1s 的计数就需要一个计数器来实现所以我们需要声明一个计数器变量 cnt_100ms 对 0.1 s 0.1\text{s} 0.1s 进行计数。首先当复位信号 sys_rst_n 有效时给计数器变量 cnt_100ms 赋一个初值 0 0 0复位信号 sys_rst_n 无效时 cnt_100ms 每个系统时钟周期自加 1 1 1cnt_100ms 的计数周期是 0.1 s 0.1\text{s} 0.1s 就是 100 ms 100\text{ms} 100mscnt_100ms 计数器完成 0.1 s 0.1\text{s} 0.1s 的计数要计数多少个系统时钟周期呢我们知道系统时钟的频率是 50 MHz 50\text{MHz} 50MHz 换算为周期就是 T s y s _ c l k 20 ns \text{T}_{sys\_clk}20\text{ns} Tsys_clk20ns计数器要完成 0.1 s 0.1\text{s} 0.1s 即 100 ms 100\text{ms} 100ms 的计数 100 ms 100\text{ms} 100ms 换算成 ns \text{ns} ns 就是 T c n t _ 100 m s 1 × 1 0 8 ns \text{T}_{cnt\_100ms}1\times10^8\text{ns} Tcnt_100ms1×108ns计数器若想要完成 0.1 s 0.1\text{s} 0.1s 的计数需要在频率为 50 MHz 50\text{MHz} 50MHz 的系统时钟下完成 T c n t _ 100 m s / T s y s _ c l k ( 20 ns ) / ( 1 × 1 0 8 ns ) 5 × 1 0 6 \text{T}_{cnt\_100ms}/\text{T}_{sys\_clk}(20\text{ns})/(1\times10^8\text{ns})5\times10^6 Tcnt_100ms/Tsys_clk(20ns)/(1×108ns)5×106 个系统时钟周期的计数因为计数器是从 0 0 0 开始计数所以说计数的最大值应该是 5 × 1 0 6 − 1 4999999 5\times10^6-14999999 5×106−14999999cnt_100ms 计数的最大值就是 4 _ 999 _ 999 4\_999\_999 4_999_999。当 cnt_100ms 计数到最大值就让它归零开始下一个周期的计数。
然后还需要声明一个变量就是 cnt_flag 信号那么为什么要声明这个 cnt_flag 信号呢我们需要使用这个 cnt_flag 信号作为条件控制输出的待显示数据 data[19:0] 让它进行自加。那么有的朋友可能想到我们也可以使用计数器当 cnt_100ms 计数到最大值作为一个条件控制输出的待显示数据 data[19:0] 让它进行自加那么这样也是可以的但是我们声明这个 cnt_flag 信号是为了让约束条件更加的简洁清晰。首先当复位信号有效时给 cnt_flag 赋一个初值 0 0 0当 cnt_100ms 计数到最大值减一( 4 _ 999 _ 998 4\_999\_998 4_999_998)的时候将 cnt_flag 拉高一个时钟周期其他时刻 cnt_flag 保持低电平。
输出信号有四路第一路是待显示的数据 data[19:0]、第二路是小数点位 point[5:0]、第三路是符号位 sign、第四路是使能信号 seg_en。
首先看一下待显示数据 data[19:0] 的波形。复位信号有效时给 data[19:0] 赋一个初值 20d0在第一个 0.1 s 0.1\text{s} 0.1s 显示时间内数码管显示数字 0 0 0所以 data[19:0] 在 cnt_flag 信号出现有效的高脉冲前一直保持初值当 cnt_flag 信号出现有效的高脉冲时就表示 0.1 s 0.1\text{s} 0.1s 计数完成然后输出的待显示数据 data[19:0] 要自加一因为是时序逻辑延迟了一个时钟周期是没有问题的当 cnt_flag 信号为有效的高脉冲 data[19:0] 自加一自加到最大值 20d999_999 归零开始下一个周期的计数。
因为我们征途系列开发板使用的是六位八段数码管每个数码位都有一个小数点位所以说 point 它的位宽为六位宽 point[5:0]每一位表示每个数码位的小数点位。在这里我们定义为高电平使小数点位有效通过实验目标可知我们的显示过程中并没有使用到小数点位所以说让 point[5:0] 一直保持为无效的低电平。
符号位 sign 同样是高电平有效当数码管进行负数显示的时候在显示的数据之前会加一个负号用来区分正负数 在本次的显示当中是显示数字 ( 0 ) 10 (0)_{10} (0)10 到最大值 ( 999999 ) 10 (999999)_{10} (999999)10 没有负号显示所以说让 sign 一直保持为无效的低电平。
最后一路输出信号使能信号 seg_en。使能信号 seg_en 控制数码管的显示与否当它为有效的高电平时数码管可以进行正常的显示当它为无效的低电平时数码管就不能进行显示。在这个实验当中我们实现的是十进制的数字 ( 0 ) 10 (0)_{10} (0)10 到最大值 ( 999999 ) 10 (999999)_{10} (999999)10 的循环计数所以使能信号 seg_en 就要一直拉高这样数码管才能够正常的显示。首先复位期间先给赋一个初值低电平然后当复位信号无效时让它一直保持高电平。
数据生成模块 data_gen 的整体波形图绘制完成后接下来就参照这个波形图进行代码的编写
cnt_100ms 计数的最大值是 5 × 1 0 6 − 1 ( 4 _ 999 _ 999 ) 10 5\times10^6-1(4\_999\_999)_{10} 5×106−1(4_999_999)10换算成二进制就是 ( 4 _ 999 _ 999 ) 10 ( 100 _ 1100 _ 0100 _ 1011 _ 0011 _ 1111 ) 2 (4\_999\_999)_{10}(100\_1100\_0100\_1011\_0011\_1111)_{2} (4_999_999)10(100_1100_0100_1011_0011_1111)2所以 cnt_100ms 的位宽是 23 位宽 cnt_100ms[22:0] 2.2.2.2 代码编写
数据生成模块的代码编写完成后我们保存为 data_gen.v
//模块开始 模块名称 端口列表
module data_gen
#(parameter CNT_MAX 23d4_999_999,//计数 0.1s 计数最大值parameter DATA_MAX 20d999_999 //待显示数据最大值
)
(input wire sys_clk , //系统时钟50MHzinput wire sys_rst_n , //系统复位低电平有效output reg [19:0] data , //待显示数据output wire [5:0] point , //小数点output wire sign , //负号output reg seg_en //数码管动态显示模块工作使能
);// 中间变量
reg [22:0] cnt_100ms; //100ms 计数器
reg cnt_flag; //100ms 计时时间到达标志//变量赋值
always(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n 1b0)cnt_100ms 23d0;else if (cnt_100ms CNT_MAX)cnt_100ms 23d0;elsecnt_100ms cnt_100ms 23d1;always(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n 1b0)cnt_flag 1b0;else if (cnt_flag (CNT_MAX-1))cnt_flag 1b1;elsecnt_flag 1b0;always(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n 1b0)data 20d0;else if ((cnt_flag1b1) (dataDATA_MAX))data 20d0;else if (cnt_flag1b1)data data 20d1;elsedata data;assign point 6b000_000;assign sign 1b0;always(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n 1b0)seg_en 1b0;elseseg_en 1b1;//模块结束
endmodule
文件体系
segment_595_dynamic
├─doc
│ segment_595_dynamic.vsdx
├─quartus_prj
├─rtl
│ data_gen.v
└─sim2.2.2.3 代码编译
然后建立一个新的实验工程添加刚刚编写的代码文件进行全编译出现了报错信息我们点击 OK 看一下报错信息 Error (12007): Top-level design entity top_segment_595 is undefined 提示我们不存在 top_segment_595 模块这个问题在数码管的静态显示工程中我们也遇到过在那个时候我们提到了这个问题有两种解决方式第一种方式是重新编写一个顶层文件将子功能模块例化到顶层文件然后进行编译第二种方式是将子功能模块强制置为顶层。之前我们使用的是第一种方式解决这个问题在这儿我们使用第二种方式解决这个问题选中 data_gen 子功能模块点击鼠标右键选择第三项
Set as Top-Level Entity 将它置为顶层 然后再次进行编译编译通过点击 OK 2.2.2.4 逻辑仿真
2.2.2.4.1 仿真代码编写
接下来就需要编写仿真文件
//时间参数
timescale 1ns/1ns//模块开始 模块名称 端口列表(空)
module tb_data_gen();//变量声明
reg sys_clk;
reg sys_rst_n;//声明变量将输出信号引出
wire [19:0] data ;
wire [5:0] point ;
wire sign ;
wire seg_en;//变量初始化
initialbeginsys_clk 1b1;sys_rst_n 1b0;#35sys_rst_n 1b1;end//产生频率为 50MHz 的系统时钟
always #10 sys_clk ~sys_clk;//模块的实例化
data_gen
#(.CNT_MAX (23d49),//计数 0.1s 计数最大值.DATA_MAX(20d9 ) //待显示数据最大值
)
data_gen_inst
(.sys_clk (sys_clk ), //系统时钟50MHz.sys_rst_n(sys_rst_n), //系统复位低电平有效.data (data ), //待显示数据.point (point ), //小数点.sign (sign ), //负号.seg_en (seg_en ) //数码管动态显示模块工作使能
);//模块结束
endmodule
仿真模块编写完成后保存为 tb_data_gen.v
文件体系
segment_595_dynamic
├─doc
│ segment_595_dynamic.vsdx
│
├─quartus_prj
│ │ top_segment_595.qpf
│ │ top_segment_595.qsf
│ │
│ ├─db
│ │ logic_util_heursitic.dat
│ │ ......
│ │ top_segment_595.vpr.ammdb
│ │
│ ├─incremental_db
│ │ │ README
│ │ │
│ │ └─compiled_partitions
│ │ top_segment_595.db_info
│ │ ......
│ │ top_segment_595.root_partition.map.kpt
│ │
│ ├─output_files
│ │ top_segment_595.asm.rpt
│ │ ......
│ │ top_segment_595.sta.summary
│ │
│ └─simulation
│ └─modelsim
│ top_segment_595.sft
│ ......
│ top_segment_595_v.sdo
│
├─rtl
│ data_gen.v
│
└─simtb_data_gen.v2.2.2.4.2 仿真代码编译
然后回到实验工程添加仿真模块进行全编译编译完成点击 OK 2.2.2.4.3 波形仿真
然后进行仿真设置开始仿真仿真完成之后打开 sim 窗口添加模块波形波形界面全选、分组、消除前缀然后点击 Restart将时间参数先设置为 10us运行一次。
发现 cnt_flag 波形有问题 查看 data_gen.v 中关于 cnt_flag 的赋值部分发现第 33 行给 cnt_100ms 赋值高电平的条件编写错误将 (cnt_flag (CNT_MAX-1)) 修改成 (cnt_100ms (CNT_MAX-1)) 并且保存
data_gen.v
//模块开始 模块名称 端口列表
module data_gen
#(parameter CNT_MAX 23d4_999_999,//计数 0.1s 计数最大值parameter DATA_MAX 20d999_999 //待显示数据最大值
)
(input wire sys_clk , //系统时钟50MHzinput wire sys_rst_n , //系统复位低电平有效output reg [19:0] data , //待显示数据output wire [5:0] point , //小数点output wire sign , //负号output reg seg_en //数码管动态显示模块工作使能
);// 中间变量
reg [22:0] cnt_100ms; //100ms 计数器
reg cnt_flag; //100ms 计时时间到达标志//变量赋值
always(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n 1b0)cnt_100ms 23d0;else if (cnt_100ms CNT_MAX)cnt_100ms 23d0;elsecnt_100ms cnt_100ms 23d1;always(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n 1b0)cnt_flag 1b0;else if (cnt_100ms (CNT_MAX-1))cnt_flag 1b1;elsecnt_flag 1b0;always(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n 1b0)data 20d0;else if ((cnt_flag1b1) (dataDATA_MAX))data 20d0;else if (cnt_flag1b1)data data 20d1;elsedata data;assign point 6b000_000;assign sign 1b0;always(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n 1b0)seg_en 1b0;elseseg_en 1b1;//模块结束
endmodule
重新编译 data_gen.v 模块将时间参数设置为 12us 再次仿真参照我们绘制的波形图来看一下仿真波形 首先是计数器 cnt_100ms
下面看一下 cnt_flag 信号 那么下面就看一下待显示数据 data 下面是小数点信号 point始终为 0 没有问题 符号位 sign 始终为低电平也没有问题 然后是使能信号 seg_en初值为低电平复位信号无效时一直保持高电平 这三路信号与我们绘制的波形图是完全一致的我们的数据生成模块 data_gen 通过了仿真验证。
我们已经实现了系统框图的绘制完成了数据生成模块 data_gen 的功能实现接下来我们将继续生成子功能模块。
首先先来看一下系统框图 由系统框图我们可以知道顶层模块 top_segment_595 包含两个子功能模块一个是已经实现了的数据生成模块 data_gen另一个是动态显示模块 segment_595_dynamic动态显示模块 segment_595_dynamic 又包含两个子功能模块一个是动态显示驱动模块 segment_dynamic另一个是 74HC595 控制模块 hc595_ctrl。74HC595 控制模块 hc595_ctrl 在上一小节数码管的静态显示当中已经完全实现了这儿就可以直接调用。
2.2.3 BCD 转码模块 bcd_8421
2.2.3.1 BCD 码简介
接下来就需要实现动态显示驱动模块 segment_dynamic。在实现动态显示驱动模块 segment_dynamic 之前我们这儿要补充一个知识点BCD 码。在这里为什么要补充 BCD 码的相关知识呢
动态显示驱动模块 segment_dynamic 的作用是将传入的待显示的十进制数据 data[19:0] 转化为可以输出的位选信号 sel[5:0] 和段选信号 seg[7:0]传入的数据 data[19:0] 是由数据生成模块 data_gen 产生并传入的data[19:0] 是使用二进制表示的多位十进制数这种编码方式并不能够直接用于产生位选信号 sel[5:0] 和段选信号 seg[7:0]我们需要将 data[19:0] 转换为以 BCD 码表示的十进制数然后通过得到的 BCD 码表示的十进制数来产生位选信号 sel[5:0] 和段选信号 seg[7:0]这是为什么呢在解答这个问题之前我们先来学习一下什么是 BCD 码
BCD 码的英文全称是 Binary-Coded Decimal翻译为二进制编码的十进制又称为二—十进制码它使用 4 位二进制数来表示一位十进制数中的 0~9 这十个数码是一种二进制的数字编码形式是用二进制编码的十进制代码。简要概括一下就是BCD 码它是使用 4 位二进制数来表示一位十进制数 0~9一种编码形式。
BCD 码根据权值的有无可以分为有权码和无权码“权”表示权值有权码的 4 位二进制数的每一位都有一个固定的权值而无权码没有权值常见的有权码有 8421 码、5421 码和 2421 码8421 码它的权值从左到右是 8、4、2、1 8421 码 bit3 ‾ bit2 ‾ bit1 ‾ bit0 ‾ 权值 8 4 2 1 \begin{align} \text{8421 码}\underline{\text{bit3}}\quad\underline{\text{bit2}}\quad\underline{\text{bit1}}\quad\underline{\text{bit0}} \\ \text{权值}\,\,\,\,8\,\qquad4\,\qquad2\,\qquad1 \end{align} 8421 码权值bit3bit2bit1bit08421 5421 码它的权值从左到右就是 5、4、2、1 5421 码 bit3 ‾ bit2 ‾ bit1 ‾ bit0 ‾ 权值 5 4 2 1 \begin{align} \text{5421 码}\underline{\text{bit3}}\quad\underline{\text{bit2}}\quad\underline{\text{bit1}}\quad\underline{\text{bit0}} \\ \text{权值}\,\,\,\,5\,\qquad4\,\qquad2\,\qquad1 \end{align} 5421 码权值bit3bit2bit1bit05421 2421 码它的权值从左到右就是 2、4、2、1 2421 码 bit3 ‾ bit2 ‾ bit1 ‾ bit0 ‾ 权值 2 4 2 1 \begin{align} \text{2421 码}\underline{\text{bit3}}\quad\underline{\text{bit2}}\quad\underline{\text{bit1}}\quad\underline{\text{bit0}} \\ \text{权值}\,\,\,\,2\,\qquad4\,\qquad2\,\qquad1 \end{align} 2421 码权值bit3bit2bit1bit02421 其中 8421 码是最为常用的 BCD 编码也是本实验当中使用的编码形式常用的无权码有余 3 码、余 3 循环码。
下面这张表格表示的就是几种常见的 BCD 编码方式所对应的十进制数 0~9 的编码格式
十进制数01234567898421 码00000001001000110100010101100111100010015421 码00000001001000110100100010011010101111002421 码0000000100100011010010111100110111101111余 3 码0011010001010110011110001001101010111100余 3 循环码0010011001110101010011001101111111101010
接下来我们讲解一下如何使用 BCD 码来表示十进制数我们以 8421 码为例比如说十进制数数字 7它的 8421 码编码格式是 0111那么我们怎么通过 0111 这个编码格式得到十进制数数字 7 呢将 BCD 码的每位二进制数与其对应的权值相乘之后将所有的乘积相加相加和就是 BCD 码所表示的十进制数我们来计算一下 8421 码 0 ‾ 1 ‾ 1 ‾ 1 ‾ 权值 8 4 2 1 相乘 ⇓ ⇓ ⇓ ⇓ 乘积 0 4 2 1 相加和 ⇔ 十进制数 0 4 2 1 7 \begin{align} \text{8421 码}\underline{\text{0}}\quad\underline{\text{1}}\quad\underline{\text{1}}\quad\underline{\text{1}} \\ \text{权值}8\quad4\quad2\quad1 \\ \text{相乘}\!\Downarrow\quad\Downarrow\quad\Downarrow\quad\Downarrow \\ \text{乘积}0\quad4\quad2\quad1 \\ \text{相加和}\Leftrightarrow\text{十进制数}0\!\!4\!\!2\!\!17 \end{align} 8421 码权值相乘乘积相加和⇔十进制数01118421⇓⇓⇓⇓042104217 其他的有权码也是使用这种方式将各位二进制数与对应位的权值相乘乘积相加就可以得到有权码所表示的十进制数。
无权码的计算方式我们这儿就不再进行讲解了感兴趣的朋友可以自行查阅相关资料。
了解了有权码的编码方式之后我们来看下前面提出的问题为什么数据生成模块 data_gen 生成的二进制编码的十进制数并不能够直接用于产生位选信号 sel[5:0] 和段选信号 seg[7:0]而 BCD 码表示的十进制数可以直接用于产生位选信号 sel[5:0] 和段选信号 seg[7:0] 呢
我们这儿举一个例子比如说使用多位数码管来表示一个十进制数 ( 233 ) 10 (233)_{10} (233)10十进制数 ( 233 ) 10 (233)_{10} (233)10 使用二进制表示应该是 ( 1110 _ 1001 ) 2 (1110\_1001)_{2} (1110_1001)2 十进制数 ( 233 ) 10 (233)_{10} (233)10 使用 8421 码表示应该是 ( 0010 _ 0011 _ 0011 ) 8421 (0010\_0011\_0011)_{8421} (0010_0011_0011)8421
知道了十进制数 ( 233 ) 10 (233)_{10} (233)10 的两种表示方式之后接下来继续进行分析如果使用动态扫描的方式在多位数码管上显示 ( 233 ) 10 (233)_{10} (233)10就需要在第一个显示周期内点亮数码管的 DIG6 数码位(从左到右依次为DIG1,DIG2,DIG3,DIG4,DIG5,DIG6)让它显示个位 ( 3 ) 10 (3)_{10} (3)10 然后第二个显示周期内点亮 DIG5 数码位让它显示十位 ( 3 ) 10 (3)_{10} (3)10 在第三个显示周期内点亮 DIG4 数码位让它显示百位 ( 2 ) 10 (2)_{10} (2)10 如果说扫描的频率足够快加上人眼的视觉暂留特性以及数码管的余晖效应我们的肉眼就以为数码管在同时显示 ( 233 ) 10 (233)_{10} (233)10 这样就实现了 ( 233 ) 10 (233)_{10} (233)10 的显示。这种显示方式就要求我们能够从传入的数据当中直接提取出待显示数据 data[19:0] 的个位、十位和百位而传入的使用二进制表示的十进制数从其中并不能直接提取出个位、十位和百位而 BCD 码它所表示的十进制数是可以直接进行个位、十位、百位信息的提取最低 4 位 ( 0011 ) 2 (0011)_{2} (0011)2 表示个位 ( 3 ) 10 (3)_{10} (3)10中间 4 位 ( 0011 ) 2 (0011)_{2} (0011)2 表示十位 ( 3 ) 10 (3)_{10} (3)10最高 4 位 ( 0010 ) 2 (0010)_{2} (0010)2 表示百位 ( 2 ) 10 (2)_{10} (2)10
这就是 BCD 码所表示的十进制数据能够直接用于产生位选和段选信号而二进制表示的十进制数据并不能直接用于产生位选和段选信号的原因。
了解了这些之后我们引入了一个新的问题如何将二进制码转化为 BCD 码接下来就通过一个例子来讲解一下转换的方法在这里我们以三位十进制数 ( 233 ) 10 (233)_{10} (233)10 为例。
十进制数 ( 233 ) 10 (233)_{10} (233)10 的二进制编码形式是 ( 1110 _ 1001 ) 2 (1110\_1001)_{2} (1110_1001)2。实现转换的第一步在输入的二进制码之前补上若干个 00 的个数规定为 十进制数的十进制位个数 × 4 \text{十进制数的十进制位个数}\times4 十进制数的十进制位个数×4。
参与转换的十进制数有 n n n 个十进制位那么就需要 n n n 个 BCD 码 ( 233 ) 10 (233)_{10} (233)10 的十进制位分别是个位 ( 3 ) 10 (3)_{10} (3)10、十位 ( 3 ) 10 (3)_{10} (3)10、百位 ( 2 ) 10 (2)_{10} (2)10 总共三个位所以 n 3 n3 n3 需要 3 3 3 个 BCD 码每个 BCD 码是 4 个位宽 n × 4 3 × 4 12 n\times43\times412 n×43×412 所以 ( 1110 _ 1001 ) 2 (1110\_1001)_{2} (1110_1001)2 前面就需要补 12 12 12 个 0 得到 ( 0000 _ 0000 _ 0000 _ 1110 _ 1001 ) 2 (0000\_0000\_0000\_1110\_1001)_{2} (0000_0000_0000_1110_1001)2
如果是其他数值的多位十进制数比如说 ( 114514 ) 10 ( 1 _ 1011 _ 1111 _ 0101 _ 0010 ) 2 (114514)_{10}(1\_1011\_1111\_0101\_0010)_{2} (114514)10(1_1011_1111_0101_0010)2 它的十进制位个数是 6 6 6每个 BCD 码需要 4 4 4 位二进制数所以 ( 1 _ 1011 _ 1111 _ 0101 _ 0010 ) 2 (1\_1011\_1111\_0101\_0010)_{2} (1_1011_1111_0101_0010)2 前面就要补 24 24 24 个 0 得到 ( 0 _ 0000 _ 0000 _ 0000 _ 0000 _ 0000 _ 0001 _ 1011 _ 1111 _ 0101 _ 0010 ) 2 (0\_0000\_0000\_0000\_0000\_0000\_0001\_1011\_1111\_0101\_0010)_{2} (0_0000_0000_0000_0000_0000_0001_1011_1111_0101_0010)2 在这里我们就完成了补 0 的操作得到了一组新的数据 ( 0000 _ 0000 _ 0000 _ 1110 _ 1001 ) 2 (0000\_0000\_0000\_1110\_1001)_{2} (0000_0000_0000_1110_1001)2接下来就要对这组数据进行判断运算和移位操作。
首先判断 BCD 码部分判断每一个 BCD 码所表示的十进制数是否大于等于 5如果说每一个 BCD 码所表示的十进制数大于等于 5 就将它与 3 相加如果说每一个 BCD 码所表示的十进制数小于 5 就让它保持原值不变。不论每一个 BCD 码所表示的十进制数大于等于 5 或者小于 5完成判断运算之后都要向左移一个二进制位。
按照上述两条规则得到下面的表格(上例十进制数 ( 233 ) 10 (233)_{10} (233)10 的二进制编码形式 ( 1110 _ 1001 ) 2 (1110\_1001)_{2} (1110_1001)2 转换成 8421BCD 码过程)
执行的操作 1 0 2 10^{2} 102 1 0 1 10^{1} 101 1 0 0 10^{0} 100待转换数据(二进制数)补 12 12 12 个 00000000000001110_1001各个 BCD 码分别和 5 比较保持原值不变0000000000001110_1001整体向左移一个二进制位第 1 次按位左移000000000001110_1001各个 BCD 码分别和 5 比较保持原值不变000000000001110_1001整体向左移一个二进制位第 2 次按位左移00000000001110_1001各个 BCD 码分别和 5 比较保持原值不变00000000001110_1001整体向左移一个二进制位第 3 次按位左移0000000001110_1001各个 BCD 码分别和 5 比较 1 0 0 10^{0} 100 位加 30000000010100_1001整体向左移一个二进制位第 4 次按位左移0000000101001001各个 BCD 码分别和 5 比较保持原值不变0000000101001001整体向左移一个二进制位第 5 次按位左移000000101001001各个 BCD 码分别和 5 比较 1 0 0 10^{0} 100 位加 3000000101100001整体向左移一个二进制位第 6 次按位左移00000101100001各个 BCD 码分别和 5 比较 1 0 1 、 1 0 0 10^{1}\text{、}10^{0} 101、100 位加 300001000101101整体向左移一个二进制位第 7 次按位左移0001000101101各个 BCD 码分别和 5 比较 1 0 0 10^{0} 100 位加 30001000110011整体向左移一个二进制位第 8 次按位左移001000110011输出结果 ( 2 ) 10 (2)_{10} (2)10 ( 3 ) 10 (3)_{10} (3)10 ( 3 ) 10 (3)_{10} (3)10
首先是完成补 0 操作得到的第一组数据它的 BCD 码最高位表示的是 0、次高位也是 0、最低位也是 0判断后它们都小于 5保持原值不变
经过判断运算之后整体向左移一位得到了第二组数据
完成移位操作之后继续进行判断运算最高位同样是 0、次高位也是 0、最低位是 1都小于 5继续保持原值不变
然后完成判断运算第 2 次进行按位左移操作就得到了第四组数据
继续判断运算最高位同样是 0、次高位也是 0、最低位是 33 个 8421 码都小于 5保持原值不变
然后第 3 次按位左移就得到第六组数据
判断运算最高位同样是 0、次高位也是 0、最低位是 7最低位的 7 大于 5最低位加上一个 3 等于 1010最高、次高位保持原值不变得到一组新的数据
然后第 4 次进行按位左移就得到了第八组数据最高位是 0、次高位是 1、最低位是 4都小于 5继续保持原值不变
第 5 次进行移位得到第十组数据最高位是 0、次高位是 2、最低位是 9最低位的 9 大于 5最低位加上一个 3 等于 1100最高、次高位保持原值不变得到一组新的数据
……
……
最后完成了 8 次的判断运算和移位操作得到十进制数 ( 233 ) 10 (233)_{10} (233)10 它的 BCD 码编码形式将每个 BCD 码换算成十进制数就是 ( 233 ) 10 (233)_{10} (233)10。这里需要注意移位次数 8 等于待转换数据的二进制位数即位宽 ( 1110 _ 1001 ) 2 (1110\_1001)_{2} (1110_1001)2 的位宽是 8上例就移位了 8 次如果说输入的是 ( 999 _ 999 ) 10 (999\_999)_{10} (999_999)10 对应的二进制码 ( 1111 _ 0100 _ 0010 _ 0011 _ 1111 ) 2 (1111\_0100\_0010\_0011\_1111)_{2} (1111_0100_0010_0011_1111)2它的位宽是 20 就需要移位 20 次。
通过上述的方式我们就可以将十进制数的二进制码转换为十进制数的 BCD 码。
以上部分就是本小节将会涉及到的 BCD 码的相关知识的一个讲解。
2.2.3.2 框图绘制
了解了 BCD 码的相关知识之后我们需要建立一个新的子功能模块 bcd_8421 用来实现 BCD 码的转码 输入信号
sys_clk系统时钟信号sys_rst_n系统复位信号data[19:0]十进制数的二进制编码
实验目标当中六位八段数码管显示的最大值是 ( 999 _ 999 ) 10 (999\_999)_{10} (999_999)10六位十进制数就需要使用 6 个 BCD 码表示在这里我们将每个 BCD 码都单独进行输出输出信号就应该是六路 BCD 码
输出信号
unit[3:0]个位 BCD 码ten[3:0]十位 BCD 码hun[3:0]百位 BCD 码。hun 是 hundred 的简写tho[3:0]千位 BCD 码。tho 是 thousand 的简写t_tho[3:0]万位 BCD 码。t_tho 是 ten thousand 的简写h_tho[3:0]十万位 BCD 码。h_tho 是 one hundred thousand 的简写
由于加入了新的子功能模块 bcd_8421包含 bcd_8421 的模块也要做一下修改
修改后的 segment_dynamic 模块框图 修改后的 segment_595_dynamic 模块框图 修改后的 top_segment_595 模块框图 2.2.3.3 波形绘制
接下来就开始 BCD 转码模块 bcd_8421 的波形图的绘制 首先是输入信号时钟信号 sys_clk、复位信号 sys_rst_n 以及二进制编码形式的十进制数 data[19:0]输入的十进制数据我们定义成一个固定值 20d114514方便后面波形的绘制。输入信号的波形绘制完成之后我们需要分析一下接下来该如何绘制。我们回看转码过程表格
执行的操作 1 0 2 10^{2} 102 1 0 1 10^{1} 101 1 0 0 10^{0} 100待转换数据(二进制数)补 12 12 12 个 00000000000001110_1001各个 BCD 码分别和 5 比较保持原值不变0000000000001110_1001整体向左移一个二进制位第 1 次按位左移000000000001110_1001各个 BCD 码分别和 5 比较保持原值不变000000000001110_1001整体向左移一个二进制位第 2 次按位左移00000000001110_1001各个 BCD 码分别和 5 比较保持原值不变00000000001110_1001整体向左移一个二进制位第 3 次按位左移0000000001110_1001各个 BCD 码分别和 5 比较 1 0 0 10^{0} 100 位加 30000000010100_1001整体向左移一个二进制位第 4 次按位左移0000000101001001各个 BCD 码分别和 5 比较保持原值不变0000000101001001整体向左移一个二进制位第 5 次按位左移000000101001001各个 BCD 码分别和 5 比较 1 0 0 10^{0} 100 位加 3000000101100001整体向左移一个二进制位第 6 次按位左移00000101100001各个 BCD 码分别和 5 比较 1 0 1 、 1 0 0 10^{1}\text{、}10^{0} 101、100 位加 300001000101101整体向左移一个二进制位第 7 次按位左移0001000101101各个 BCD 码分别和 5 比较 1 0 0 10^{0} 100 位加 30001000110011整体向左移一个二进制位第 8 次按位左移001000110011输出结果 ( 2 ) 10 (2)_{10} (2)10 ( 3 ) 10 (3)_{10} (3)10 ( 3 ) 10 (3)_{10} (3)10
这张表格展示了二进制向 BCD 编码转换的一个完整过程可以帮助我们进行波形图的绘制。首先我们知道二进制向 BCD 码转码需要通过判断运算加移位运算实现并且运算次数规定为和输入的二进制码的位宽相同比如说我们输入的十进制数是 ( 114514 ) 10 (114514)_{10} (114514)10二进制编码形式是 20d114514 20 位宽就需要进行 20 次的判断运算加移位操作才能够实现二进制向 BCD 码的一个转换所以说我们需要一个移位计数器 cnt_shift[4:0] 对判断运算和移位操作的次数进行计数其次在进行判断计算和移位操作的过程中产生的中间数据(24bitBCD码加上20bit二进制码)需要使用一个变量 data_shift[43:0] 储存。需要注意的是二进制到 BCD 码转换的过程中判断运算与移位操作各自在一个时钟周期内完成而且这两个过程有先后顺序判断运算在前移位操作在后所以声明移位标志信号 shift_flag 区分这两个操作步骤并且移位次数的更新都是在判断计算和移位操作之后完成的所以移位标志信号 shift_flag 也可以作为移位次数计数器 cnt_shift[4:0] 计数的一个条件。
完成了变量的声明之后接下来开始变量信号波形的绘制。首先是移位计数器 cnt_shift[4:0]当复位信号 sys_rst_n 有效时给它赋一个初值 0移位计数器从 0 开始计数因为移位次数是 20 次cnt_shift[4:0] 计数 20 次时最大值应该是 19但是我们这里还需要增加两个计数状态cnt_shift[4:0] 一共计数 22 次为什么呢根据上面的表格可知移位计数器 cnt_shift[4:0] 为 0 时实际上是对应二进制的补零操作当移位计数器 cnt_shift[4:0] 计数范围为 1~20 时才是进行判断运算和移位操作的时候当移位计数器 cnt_shift[4:0] 计数到最大值 21 时对应的操作是输出结果提取转换完成的 BCD 码。移位计数器 cnt_shift[4:0] 计数范围确定之后我们要考虑一下移位计数器 cnt_shift[4:0] 的计数条件是什么我们前面已经提到可以使用移位标志信号 shift_flag 作为移位计数器 cnt_shift[4:0] 的计数条件所以说我们首先绘制移位标志信号 shift_flag 的波形。
当复位信号有效时给移位标志信号 shift_flag 赋一个初值 0移位标志信号 shift_flag 的作用是区分判断运算与移位操作这两个阶段各自在一个时钟周期内完成那么当复位信号无效时在每个系统时钟周期下对移位标志信号 shift_flag 进行不断的取反。当移位标志信号 shift_flag 为低电平时进行判断运算当移位标志信号 shift_flag 为高电平时进行移位操作移位标志信号 shift_flag 为高电平也作为条件控制移位计数器 cnt_shift[4:0] 进行计数移位计数器 cnt_shift[4:0] 计数到最大值 21 并且移位标志信号 shift_flag 为高电平移位计数器 cnt_shift[4:0] 归零开始下一个周期的计数。当移位标志信号 shift_flag 为高电平时移位计数器 cnt_shift[4:0] 进行加一计数当移位标志信号 shift_flag 为低电平时移位计数器 cnt_shift[4:0] 保持原来的值不变因为是时序逻辑所以说延迟一个时钟周期当移位计数器 cnt_shift[4:0] 计数到最大值 21 而且移位标志信号 shift_flag 为高电平移位计数器 cnt_shift[4:0] 归零开始下一个周期的计数。
下面开始中间变量移位数据 data_shift[43:0] 的波形绘制。当复位信号有效时给它赋一个初值 0当复位信号无效时并且移位计数器 cnt_shift[4:0] 为 0 时将输入的二进制数 20d114514 更新到移位数据的 [19:0] 位就是给 data[19:0] 前面补 0{24b0,data[19:0]}当移位计数器 cnt_shift[4:0] 的计数范围为 1~20 时如果移位标志信号 shift_flag 为低电平就进行判断运算的操作如果说移位标志信号 shift_flag 为高电平就进行移位的操作当移位计数器 cnt_shift[4:0] 的计数值为 21 时移位数据 data_shift[43:0] 保持原来的值不变移位标志信号 shift_flag 为高电平移位计数器 cnt_shift[4:0] 归零移位数据 data_shift[43:0] 也要进行重新的更新开始下一次的转码。
接下来开始输出信号波形的绘制。当复位信号有效时给输出信号赋一个初值 0当移位计数器 cnt_shift[4:0] 的计数值为 21 时就提取输出信号对应的 BCD 码输出信号个位 unit[3:0] 所对应的 BCD 码就应该是 data_shift[23:20]输出信号十位 ten[3:0] 对应的 BCD 码就应该是 data_shift[27:24]百位 hun[3:0] 对应的是 data_shift[31:28]千位 tho[3:0] 对应的是 data_shift[35:32]万位 t_tho[3:0] 对应的应该是 data_shift[39:36]十万位 h_tho[3:0] 对应的就应该是 data_shift[43:40]。
2.2.3.4 代码编写
完成了 BCD 编码模块 的波形图绘制后接下来可以根据这个波形图进行代码的编写保存为 bcd_8421.v
//模块开始 模块名称 端口列表
module bcd_8421
(input wire sys_clk , //系统时钟50MHzinput wire sys_rst_n , //系统复位低电平有效input wire [19:0] data , //待转码十进制数的二进制编码output reg [3:0] unit , //转码结果的个位 BCD 码output reg [3:0] ten , //转码结果的十位 BCD 码output reg [3:0] hun , //转码结果的百位 BCD 码output reg [3:0] tho , //转码结果的千位 BCD 码output reg [3:0] t_tho , //转码结果的万位 BCD 码output reg [3:0] h_tho //转码结果的十万位 BCD 码
);//变量声明
reg shift_flag ;
reg [4:0] cnt_shift ;
reg [43:0] data_shift ;//变量赋值
always(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n 1b0)shift_flag 1b0;elseshift_flag ~shift_flag;always(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n 1b0)cnt_shift 5d0;else if ((cnt_shift5d21) (shift_flag1b1))cnt_shift 5d0;else if (shift_flag 1b1)cnt_shift cnt_shift 5d1;elsecnt_shift cnt_shift;always(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n 1b0)data_shift 44d0;else if (cnt_shift5d0)data_shift {24b0,data};else if ((shift_flag1b0) ((cnt_shift5d1)(cnt_shift5d20)))begindata_shift[23:20] (data_shift[23:20]4d5)? (data_shift[23:20]4d3): (data_shift[23:20]);data_shift[27:24] (data_shift[27:24]4d5)? (data_shift[27:24]4d3): (data_shift[27:24]);data_shift[31:28] (data_shift[31:28]4d5)? (data_shift[31:28]4d3): (data_shift[31:28]);data_shift[35:32] (data_shift[35:32]4d5)? (data_shift[35:32]4d3): (data_shift[35:32]);data_shift[39:36] (data_shift[39:36]4d5)? (data_shift[39:36]4d3): (data_shift[39:36]);data_shift[43:40] (data_shift[43:40]4d5)? (data_shift[43:40]4d3): (data_shift[43:40]);endelse if ((shift_flag1b1) ((cnt_shift5d1)(cnt_shift5d20)))data_shift data_shift1;elsedata_shift data_shift;//输出信号的赋值
always(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n 1b0)beginunit 4b0;ten 4b0;hun 4b0;tho 4b0;t_tho 4b0;h_tho 4b0;endelse if (cnt_shift5d21)beginunit data_shift[23:20];ten data_shift[27:24];hun data_shift[31:28];tho data_shift[35:32];t_tho data_shift[39:36];h_tho data_shift[43:40];end//模块结束
endmodule
2.2.3.5 代码编译
BCD 编码模块的代码编写完成后回到实验工程添加 bcd_8421.v 模块然后进行全编译查找语法错误编译完成点击 OK 2.2.3.6 逻辑仿真
2.2.3.6.1 仿真代码编写
下面开始编写我们的仿真代码保存为 tb_bcd_8421.v
// 时间参数
timescale 1ns/1ns
include ../rtl/bcd_8421.vmodule tb_bcd_8421();reg sys_clk ;
reg sys_rst_n ;
reg [19:0] data ;wire [3:0] unit ;
wire [3:0] ten ;
wire [3:0] hun ;
wire [3:0] tho ;
wire [3:0] t_tho;
wire [3:0] h_tho;initialbeginsys_clk 1b1;sys_rst_n 1b0;#35sys_rst_n 1b1;data 20d114_514;#1000data 20d358_946;#2000data 20d716_230;#3000data 20d999_999;endalways #10 sys_clk ~sys_clk;bcd_8421 bcd_8421_inst
(.sys_clk (sys_clk ), //系统时钟50MHz.sys_rst_n(sys_rst_n), //系统复位低电平有效.data (data ), //待转码十进制数的二进制编码.unit (unit ), //转码结果的个位 BCD 码.ten (ten ), //转码结果的十位 BCD 码.hun (hun ), //转码结果的百位 BCD 码.tho (tho ), //转码结果的千位 BCD 码.t_tho (t_tho), //转码结果的万位 BCD 码.h_tho (h_tho) //转码结果的十万位 BCD 码
);endmodule
2.2.3.6.2 仿真代码编译
仿真模块编写完成后回到实验工程然后添加 tb_bcd_8421.v 模块仿真模块添加完成之后进行全编译编译通过点击 OK 然后进行仿真设置 2.2.3.6.3 波形仿真
下面开始仿真结合我们绘制的波形图查看一下仿真波形图。
首先是移位标志信号 shift_flag 移位标志信号 shift_flag 的初值是低电平每个系统时钟周期进行取反。
接下来看一下移位计数器 cnt_shift 移位数据 data_shift 的波形就不再进行查看了因为数据量太大了。
接下来看一下输出信号 那么这样我们就实现了 BCD 编码模块的功能下面就可以实现动态显示驱动模块 segment_dynamic
2.2.4 动态显示驱动模块 segment_dynamic
动态显示驱动模块的模块框图之前已经绘制完成了 2.2.4.1 波形绘制
接下来就绘制 segment_dynamic 模块的波形图 输入信号有六路时钟信号 sys_clk、复位信号 sys_rst_n、待显示数据 data[19:0]、小数点位 point[5:0]、符号位 sign 和使能信号 seg_en。待显示数据 data[19:0] 我们赋给它一个固定的值 20d9876这个数据是随机定义的大家可以自己设置。小数点位 point[5:0] 给它赋一个固定值6b000_010表示我们选中倒数第二个小数点位通过 data[19:0]、point[5:0] 这两路信号可以得到我们想要显示的数据应该是 987.6 987.6 987.6。 符号位 sign 首先给它一个初值 0然后当复位信号无效时将它拉高并且让它一直保持高电平这就表示我们想要显示的是负数通过 data[19:0]、point[5:0]、sign 这三路信号可以知道我们想要显示的数据应该是 − 987.6 -987.6 −987.6。 使能信号 seg_en 在复位信号有效期间赋给它一个初值 0当复位信号无效时让它一直保持高电平这就表示数码管一直处于显示状态。
六路输入信号的波形绘制完成接下来应该怎么绘制呢 首先我们要声明六路信号 unit[3:0]、ten[3:0]、hun[3:0]、tho[3:0]、t_tho[3:0]、h_tho[3:0] 将 BCD 编码模块 bcd_8421 输出的 BCD 编码引出来。
首先赋给它们一个初值 0输入的十进制数据 data[19:0] 等于 20d9876(最低位)个位 unit[3:0] 应该是数字 6然后十位 ten[3:0] 应该是 7然后百位 hun[3:0] 应该是 8千位 tho[3:0] 就应该是 9万位 t_tho[3:0] 和十万位 h_tho[3:0] 就是 0。这儿有一点要注意BCD 编码模块完成二进制到 BCD 的转码需要一段时间(22 个系统时钟周期)在波形图上使用省略号形状表示一段时间的延时。
六路 BCD 编码信号已经全部引出来后我们再声明一个变量 data_reg[23:0] 对待显示数据 data[19:0] 进行寄存(只是对符号位和数据位进行寄存,不包含小数点位)。首先赋给它一个初值 0然后使用数据位和符号位给它赋值数据位就是 unit[3:0]、ten[3:0]、hun[3:0]、tho[3:0]、t_tho[3:0]、h_tho[3:0] 这六路 BCD 编码信号首先 data_reg[23:0] 最低 4 位是个位 unit[3:0] 就是 6、然后是十位 7、然后是百位 8、然后是千位 9因为符号位 sign 为高电平需要显示负号data_reg[23:0] 最高 4 位不显示用 X 来表示。
接下来还要声明一个计数器变量 cnt_1ms[15:0]为什么要声明这个计数器变量呢因为之前已经约定好了在数码管的动态扫描显示过程中每个数码位只显示 1ms 的时长对 1ms 进行计数就需要计数器所以说我们要声明这一个计数器变量 cnt_1ms[15:0]。
首先赋给计数器 cnt_1ms[15:0] 一个初值 0 T c n t _ 1 m s 1 ms 1 × 1 0 6 ns T s y s _ c l k 20 ns \text{T}_{cnt\_1ms}1\text{ms}1\times10^6\text{ns}\text{}\text{T}_{sys\_clk}20\text{ns} Tcnt_1ms1ms1×106nsTsys_clk20ns 这就表示要完成 1 ms 1\text{ms} 1ms 的计数要计数 T c n t _ 1 m s / T s y s _ c l k ( 1 × 1 0 6 ) ns / 20 ns 5 × 1 0 4 \text{T}_{cnt\_1ms}/\text{T}_{sys\_clk}(1\times10^6)\text{ns}/20\text{ns}5\times10^4 Tcnt_1ms/Tsys_clk(1×106)ns/20ns5×104 个系统时钟周期因为计数器从 0 开始计数这就表示计数的最大值应该是 5 × 1 0 4 − 1 49999 5\times10^4-149999 5×104−149999声明计数器变量时为什么它的位宽是 16 呢我们来算一下 可以看到 ( 49999 ) 10 (49999)_{10} (49999)10 的二进制形式为 1100 _ 0011 _ 0100 _ 1111 1100\_0011\_0100\_1111 1100_0011_0100_1111 总共 16 位所以它的位宽应该是 16 位宽 [15:0]。当 1ms 计数器计数到最大值归零开始下一个周期的计数。
1ms 计数器的波形绘制完成之后我们还要声明一个变量 flag_1ms 就是 1ms 标志信号为什么要声明一个标志信号呢这个标志信号可以作为一个条件控制数码位的选择 首先赋给 flag_1ms 一个初值 0当计数器 cnt_1ms[15:0] 计数到最大值减一的时候让它保持一个系统时钟周期的高脉冲其他时刻让它保持低电平。
1ms 标志信号的波形绘制完成后我们还要声明一个计数器变量 cnt_sel[2:0]为什么还要声明一个计数器变量呢数码管的动态显示使用的是动态扫描的方式只有每个数码位都完成 1ms 的显示才是一个完整的扫描周期为了对这个扫描周期进行计数所以说我们要声明一个计数器变量我们的开发板使用的是六位八段数码管就表示一个扫描周期就是六个数码位分别进行显示扫描计数器变量它的计数最大值就应该是 6因为是从 0 开始计数 0~6 计数了七次(加消隐)。
首先赋给 cnt_sel[2:0] 一个初值 0当 flag_1ms 信号为高电平时就表示有一个数码位完成了 1ms 的显示扫描计数器就加一当扫描计数器计数到最大值 6 而且 flag_1ms 信号为高电平时将 cnt_sel[2:0] 归零开始下一个周期的计数。
接下来绘制输出位选信号 sel_reg[5:0] 的波形。首先赋给它一个初值 6b111_111这表示选中所有数码位进行消隐当扫描计数器 cnt_sel[2:0] 为 0、标志信号 flag_1ms 为高电平时给它赋值 6b000_001 表示选中第一个数码位就是个位当扫描计数器 cnt_sel[2:0] 为 1、标志信号 flag_1ms 为高电平时将位选信号 sel_reg[5:0] 向左移一位就得到了 6b000_010这表示选中了第二个数码位当扫描计数器 cnt_sel[2:0] 为 2、标志信号 flag_1ms 为高电平时继续左移位选信号 sel_reg[5:0] 的值就应该是 6b000_100表示选中了第三个数码位……当扫描计数器 cnt_sel[2:0] 的计数值再次为 1、标志信号 flag_1ms 为高电平时就对位选信号 sel_reg[5:0] 重新赋值为什么要重新赋值呢因为此时再进行移位的话 sel_reg[5:0] 是消隐时的 6b111_111 表示所有的数码位都选中所以说在这儿要给它赋一个新的值 6b000_001 这就表示选中第一个数码位然后继续进行移位操作。
在开始给段选信号 seg[7:0] 赋值之前我们还需要声明两个变量 data_disp[3:0]、dot_disp
data_disp[3:0] 这个变量表示即将要显示的数据。首先给它赋一个初值 0当选中第一个数码位的时候让它进行显示显示的内容应该是 unit[3:0] 对应的数字 6当选中第二个数码位让它显示 7当选中第三个数码位让它显示的应该是 8然后是第四个数码位就是 9当显示第五个数码位要显示负号负号在这儿用 10 表示你也可以选择除了 0~9 之外的其他数值来表示最高位是不显示的用 11 表示。然后完成了一个周期的扫描消隐后又回到了最低位让它显示数字 6第二位让它显示 7。
dot_disp 表示即将显示的小数点位它的初值应该是高电平(共阳极数码管)表示小数点位不显示什么时候显示小数点位呢由 point[5:0] 的值 6b000_010 可知在第二位的时候显示小数点位所以说应该是在 cnt_sel[2:0] 等于 2 期间让它保持低电平表示在第二个数码位显示小数点然后其他时刻保持高电平表示不显示小数点位。
输出的段选信号 seg[7:0] 因为我们使用的是共阳数码管所以说初值赋给它 8hFF 就表示不点亮进行消隐其他时刻段选信号 seg[7:0] 要根据待显示数据 data[19:0] 进行一个编码因为这儿直接输出的数据是不能够用于显示的必须进行编码因为是使用的时序逻辑所以说应该延迟一个时钟周期第一个数码位显示的是数字 6我们来看一下数字 6 的编码
待显示内容段码(二进制格式)段码(十六进制格式)dpgfedcba0110000008’hC01111110018’hF92101001008’hA43101100008’hB04100110018’h995100100108’h926100000108’h827111110008’hF88100000008’h809100100008’h90A100010008’h88b100000118’h83C110001108’hC6d101000018’hA1E100001108’h86F100011108’h8E
数字 6 所对应的十六进制格式是 8h82然后是第二个数码位第二个数码位对应的数字是 7我们看一下 7字符 7 对应的应该是 8hF8但是有一点要注意小数点位要点亮所以说 dp 应该是 0这样看就应该是 8h78 而不是 8hF8然后是数字 8数字 8 它的段码是 8h80然后是数字 9数字 9 的段码是 8h90然后是用 10 表示的负号负号应该怎么显示呢负号只需要点亮段选信号 g 就可以了也就是说其他段都为高电平 g 段为低电平这样就应该是 8hBF到了 1111 就表示这个数码位不进行显示也就是说不点亮就是 8hFF。经过一个周期的扫描完成了数据的显示接着又进行消隐然后回到了第一个数码位就是 8h82。
段选信号的波形绘制完成。这样我们就完成了动态显示驱动模块 segment_dynamic 整个模块的波形图的绘制。
2.2.4.2 代码编写
接下来就开始代码的编写
//模块开始 模块名称 端口列表
module segment_dynamic
#(parameter CNT_MAX 16d49_999//1ms 计数器计数最大值
)
(input wire sys_clk , //系统时钟50MHzinput wire sys_rst_n , //系统复位低电平有效input wire [19:0] data , //待转码十进制数的二进制编码input wire [5:0] point , //小数点input wire sign , //符号(负号是否显示)input wire seg_en , //数码管显示使能output reg [7:0] seg , //段选output reg [5:0] sel //位选
);wire [3:0] unit ; //转码结果的个位 BCD 码
wire [3:0] ten ; //转码结果的十位 BCD 码
wire [3:0] hun ; //转码结果的百位 BCD 码
wire [3:0] tho ; //转码结果的千位 BCD 码
wire [3:0] t_tho; //转码结果的万位 BCD 码
wire [3:0] h_tho; //转码结果的十万位 BCD 码reg [23:0] data_reg; //寄存待显示数据的 BCD 码
reg [15:0] cnt_1ms; //1ms 计数器
reg flag_1ms; //1ms 计数时间到达
reg [2:0] cnt_sel; //位选计数
reg [5:0] sel_reg; //寄存位选值
reg [3:0] data_disp; //将要显示的 BCD 码
reg dot_disp; //将要显示的小数点always(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n 1b0)data_reg 24b0;//如果待显示十进制数的十万位不等于 0 或者需要显示小数点十进制数则显示在 6 个数码位上else if ((h_tho!4b0000) || (point[5]1b1))data_reg {h_tho, t_tho, tho, hun, ten, unit};//如果待显示十进制数是负数且它的万位不等于 0 或者需要显示小数点则十进制数的绝对值显示在 5 个数码位上//例如待显示的十进制数为 -11451数码管应该显示 -11451else if ((sign1b1) ((t_tho!4b0000)||(point[4]1b1)))data_reg {4d10, t_tho, tho, hun, ten, unit};//4d10我们定义为显示负号//如果待显示十进制数是正数且它的万位不等于 0 或者需要显示小数点则十进制数显示在 5 个数码位上else if ((sign1b0) ((t_tho!4b0000)||(point[4]1b1)))data_reg {4d11, t_tho, tho, hun, ten, unit};//4d11我们定义为不显示//如果待显示十进制数是负数且它的千位不等于 0 或者需要显示小数点则十进制数的绝对值显示在 4 个数码位上else if ((sign1b1) ((tho!4b0000)||(point[3]1b1)))data_reg {4d11, 4d10, tho, hun, ten, unit};//如果待显示十进制数是正数且它的千位不等于 0 或者需要显示小数点则十进制数显示在 4 个数码位上else if ((sign1b0) ((tho!4b0000)||(point[3]1b1)))data_reg {4d11, 4d11, tho, hun, ten, unit};//如果待显示十进制数是负数且它的百位不等于 0 或者需要显示小数点则十进制数的绝对值显示在 3 个数码位上else if ((sign1b1) ((hun!4b0000)||(point[2]1b1)))data_reg {4d11, 4d11, 4d10, hun, ten, unit};//如果待显示十进制数是正数且它的百位不等于 0 或者需要显示小数点则十进制数显示在 3 个数码位上else if ((sign1b0) ((hun!4b0000)||(point[2]1b1)))data_reg {4d11, 4d11, 4d11, hun, ten, unit};//如果待显示十进制数是负数且它的十位不等于 0 或者需要显示小数点则十进制数的绝对值显示在 2 个数码位上else if ((sign1b1) ((ten!4b0000)||(point[1]1b1)))data_reg {4d11, 4d11, 4d11, 4d10, ten, unit};//如果待显示十进制数是正数且它的十位不等于 0 或者需要显示小数点则十进制数显示在 2 个数码位上else if ((sign1b0) ((ten!4b0000)||(point[1]1b1)))data_reg {4d11, 4d11, 4d11, 4d11, ten, unit};//如果待显示十进制数是负数且它的个位不等于 0 或者需要显示小数点则十进制数的绝对值显示在 1 个数码位上else if ((sign1b1) ((unit!4b0000)||(point[0]1b1)))data_reg {4d11, 4d11, 4d11, 4d11, 4d10, unit};//其它情况就只显示在 1 个数码位上elsedata_reg {4d11, 4d11, 4d11, 4d11, 4d11, unit};always(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n 1b0)cnt_1ms 16d0;else if (cnt_1ms CNT_MAX)cnt_1ms 16d0;elsecnt_1ms cnt_1ms 16d1;always(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n 1b0)flag_1ms 1b0;else if (flag_1ms (CNT_MAX-1))flag_1ms 1b1;elseflag_1ms 1b0;always(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n 1b0)cnt_sel 3d0;else if ((flag_1ms1b1) (cnt_sel3d6))cnt_sel 3d0;else if (flag_1ms 1b1)cnt_sel cnt_sel 3d1;elsecnt_sel cnt_sel;always(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n 1b0)sel_reg 6b111_111;//消隐else if ((flag_1ms1b1) (cnt_sel3d6))sel_reg 6b111_111;//消隐else if ((flag_1ms1b1) (cnt_sel3d0))sel_reg 6b000_001;else if (flag_1ms1b1)sel_reg sel_reg 1b1;elsesel_reg sel_reg;always(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n 1b0)data_disp 4b0000;else if ((seg_en1b1) (flag_1ms1b1)) case (cnt_sel)3d0: data_disp 4b1111;//消隐不显示(共阳数码管,高电平熄灭)3d1: data_disp data_reg[3:0]; //个3d2: data_disp data_reg[7:4]; //十3d3: data_disp data_reg[11:8]; //百3d4: data_disp data_reg[15:12]; //千3d5: data_disp data_reg[19:16]; //万3d6: data_disp data_reg[23:20]; //十万default: data_disp 4b0000;//显示数字0endcaseelsedata_disp data_disp;always(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n 1b0)dot_disp 1b1;//共阳数码管,高电平小数点段dp熄灭else if (flag_1ms1b1)dot_disp point[cnt_sel-1]1b1? 1b0: 1b1;elsedot_disp dot_disp;always(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n 1b0)seg 8hFF;else case (data_disp)4d0 : seg {dot_disp,7b100_0000}; //数字04d1 : seg {dot_disp,7b111_1001}; //数字14d2 : seg {dot_disp,7b010_0100}; //数字24d3 : seg {dot_disp,7b011_0000}; //数字34d4 : seg {dot_disp,7b001_1001}; //数字44d5 : seg {dot_disp,7b001_0010}; //数字54d6 : seg {dot_disp,7b000_0010}; //数字64d7 : seg {dot_disp,7b111_1000}; //数字74d8 : seg {dot_disp,7b000_0000}; //数字84d9 : seg {dot_disp,7b001_0000}; //数字94d10 : seg 8b1011_1111 ; //负号-4d11 : seg 8b1111_1111 ; //不显示default:seg 8b1100_0000; //数字0,不带小数点endcase//同步 sel_reg 和 seg 的时钟
always(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n 1b0)sel 6b111_111;elsesel sel_reg;bcd_8421 bcd_8421_inst
(.sys_clk (sys_clk ), //系统时钟50MHz.sys_rst_n(sys_rst_n), //系统复位低电平有效.data (data ), //待转码十进制数的二进制编码.unit (unit ), //转码结果的个位 BCD 码.ten (ten ), //转码结果的十位 BCD 码.hun (hun ), //转码结果的百位 BCD 码.tho (tho ), //转码结果的千位 BCD 码.t_tho (t_tho), //转码结果的万位 BCD 码.h_tho (h_tho) //转码结果的十万位 BCD 码
);//模块结束
endmodule
那么这样我们就完成了动态显示驱动模块 segment_dynamic 的代码编写保存为 segment_dynamic.v
2.2.4.3 代码编译
回到实验工程添加 segment_dynamic.v 模块然后进行全编译编译出错点击 OK 查看一下报错信息 Error (10170): Verilog HDL syntax error at segment_dynamic.v(129) near text 1; expecting ; 发现问题代码在 129 行附近查看修改后保存 回到 Quartus II 再次编译依然报错 Error (10228): Verilog HDL error at bcd_8421.v(2): module bcd_8421 cannot be declared more than once 将 tb_bcd_8421.v 文件的第 3 行tb_bcd_8421.v 删除再次编译工程编译通过点击 OK 2.2.4.4 逻辑仿真
2.2.4.4.1 仿真代码编写
下面我们开始仿真代码的编写
timescale 1ns/1ns //时间参数//模块开始 模块名称 端口列表
module tb_segment_dynamic();reg sys_clk ;
reg sys_rst_n;
reg [19:0] data ;
reg [5:0] point ;
reg sign ;
reg seg_en ;//声明变量将两路输出信号引出来
wire [7:0] seg;
wire [5:0] sel;//对声明的变量进行初始化
initialbeginsys_clk 1b1;sys_rst_n 1b0;data 20d0;point 6b000_000;sign 1b0;seg_en 1b0;#35sys_rst_n 1b1;data 20d9876;point 6b000_010;//选中第二个数码位让它显示小数点sign 1b1;//负号seg_en 1b1;//数码管进行显示endalways #10 sys_clk ~sys_clk; //时钟信号segment_dynamic segment_dynamic_inst
#(.CNT_MAX (16d9)//1ms 计数器计数最大值
)
(.sys_clk (sys_clk ), //系统时钟50MHz.sys_rst_n(sys_rst_n), //系统复位低电平有效.data (data ), //待转码十进制数的二进制编码.point (point ), //小数点.sign (sign ), //符号(负号是否显示).seg_en (seg_en ), //数码管显示使能.seg (seg ), //段选.sel (sel ) //位选
);//模块结束
endmodule
仿真代码编写完成后保存为 tb_segment_dynamic.v
2.2.4.4.2 仿真代码编译
回到实验工程添加 tb_segment_dynamic.v 文件然后进行全编译编译报错点击 OK 查看错误信息
Error (10170): Verilog HDL syntax error at tb_segment_dynamic.v(37) near text “#”; expecting “;”
提示仿真代码第 37 行附近缺少分号查看代码发现实例化模块的名称位置放错了修改保存 再次进行全编译编译通过点击 OK 下面进行仿真设置 2.2.4.4.3 波形仿真
接下来进行仿真时间参数设置为 10us运行一次全局视图 我们发现 flag_1ms 的波形没有变化回到 segment_dynamic.v 文件中查看发现给 flag_1ms 赋值高电平时的条件编写错误修改完保存 回到我们的仿真界面然后全选、删除所有波形回到 Library 窗口找到修改的代码对它进行重编译 然后回到 sim 窗口重新添加波形然后回到波形界面全选、分组然后点击 Restart重新运行 10us 运行一次 再次回到 segment_dynamic.v 文件进行排错发现当 cnt_sel 为 0 时cnt_sel-3d1 作为 point 的索引值出现了负数修改并保存 再次仿真 接下来参照绘制的波形图查看一下仿真波形 首先是从 bcd_8421 模块引出的 BCD 码 他们的初值是 0没有问题然后个位是 6 没有问题十位是 7然后是 8、9最高的两位始终保持 0这儿没有问题。
然后看一下数据寄存
我们继续往下看
下面看一下 1ms 计数器 那么下面是标志信号 然后是扫描计数器 下面是位选信号的寄存 下面就是显示数据波形 可以看到 data_disp 和 sel_reg 错开了一位回到代码查看 data_disp 的赋值语句块修改(时序逻辑节拍延迟cnt_sel 自加一的条件中有 flag_1ms1b1 部分然后在这里的条件又有 flag_1ms1b1 导致延迟两个 flag_1ms 的节拍)并保存 再次仿真查看波形 下面是小数点信号 下面就是位选信号和段选信号先来看一下位选信号 位选信号是在位选寄存信号打一拍得到的在图中红框这个位置确实延迟了一个时钟周期然后他们俩的数据也是对应的。
下面看一下段选信号 可以看到段选信号是有问题的。回到 segment_dynamic.v 查看修改给 dot_disp 赋值的部分因为给 seg 赋值时需要使用 dot_disp 替换最高有效位 还有需要修改 segment_dynamic.v 的 119 行这里我们错误的把段码赋值给 data_disp 了 为了和绘制的波形图保持一致还需要修改代码第 149 行 这时再仿真查看波形观察段选信号 seg 当选中第一个数码位段码 82 与绘制波形是对应的没有问题当选中第二个数码位的时候是 78 没有问题第三个 80第四个 90第五个 bf 都没有问题当选中第六个数码位段码是 ff 与绘制波形图也是一致的 那么这样仿真验证就通过了。 到了这里我们已经实现了动态显示驱动模块它的模块功能接下来就可以实现其他模块的模块功能。
2.2.5 动态显示模块 segment_595_dynamic
动态显示驱动模块 segment_dynamic 的功能实现之后我们就可以通过动态显示驱动模块 segment_dynamic 和 HC595 控制模块 hc595_ctrl 生成动态显示模块 segment_595_dynamic。
2.2.5.1 代码编写
首先复用一下 HC595 控制模块将 segment_595_static/rtl/hc595_ctrl.v 复制粘贴到 segment_595_dynamic/rtl/hc595_ctrl.vHC595 控制模块复用完成后接下来就编写动态显示模块
//模块开始 模块名称 端口列表
module segment_595_dynamic
(input wire sys_clk , //系统时钟50MHzinput wire sys_rst_n , //系统复位低电平有效input wire [19:0] data , //待转码十进制数的二进制编码input wire [5:0] point , //小数点input wire sign , //符号(负号是否显示)input wire seg_en , //数码管显示使能output wire ds , //输出给74HC595的串行数据output wire shcp , //移位寄存器时钟output wire stcp , //存储寄存器时钟output wire oe_n //74HC595的输出使能低电平有效
);//声明两个变量将 segment_dynamic 模块输出的位选、段选输入给 hc595_ctrl
wire [5:0] sel;
wire [7:0] seg;segment_dynamic segment_dynamic_inst
#(.CNT_MAX (16d49_999)//1ms 计数器计数最大值
)
(.sys_clk (sys_clk ), //系统时钟50MHz.sys_rst_n(sys_rst_n), //系统复位低电平有效.data (data ), //待转码十进制数的二进制编码.point (point ), //小数点.sign (sign ), //符号(负号是否显示).seg_en (seg_en ), //数码管显示使能.seg (seg ), //段选.sel (sel ) //位选
);hc595_ctrl hc595_ctrl_inst
(.sys_clk (sys_clk ), //系统时钟50MHz.sys_rst_n(sys_rst_n), //系统复位低电平有效.sel (sel ), //六位数码管位选.seg (seg ), //六位数码管段选.ds (ds ), //输出给74HC595的串行数据.shcp (shcp ), //移位寄存器时钟.stcp (stcp ), //存储寄存器时钟.oe_n (oe_n ) //74HC595的输出使能低电平有效
);//模块结束
endmodule
2.2.5.2 代码编译
参照着模块框图完成动态显示模块的代码编写后将它保存为 segment_595_dynamic.v然后回到实验工程添加刚刚编写的模块然后进行编译编译报错点击 OK 查看错误信息
Error (10170): Verilog HDL syntax error at segment_595_dynamic.v(22) near text “#”; expecting “;”
鼠标左键双击该条 error跳转到代码文件中检查 发现又犯了同样的错误对带参数的模块进行实例化时实例化名称的位置应当在参数后面、对带参数的模块进行实例化时实例化名称的位置应当在参数后面、对带参数的模块进行实例化时实例化名称的位置应当在参数后面但愿之后我能记住吧…………
修改完成保存回到 Quartus II 进行重新编译编译通过点击 OK 对于动态显示模块不再进行单独的仿真等到顶层模块的代码编写完成了再进行整体的仿真。
2.2.6 顶层模块 top_segment_595
2.2.6.1 代码编写
下面就参照顶层模块的框图编写最终的顶层模块 top_segment_595
//模块开始 模块名称 端口列表
module top_segment_595
(input wire sys_clk , //系统时钟50MHzinput wire sys_rst_n , //系统复位低电平有效output wire ds , //输出给74HC595的串行数据output wire shcp , //移位寄存器时钟output wire stcp , //存储寄存器时钟output wire oe_n //74HC595的输出使能低电平有效
);wire [19:0] data ;
wire [5:0] point ;
wire sign ;
wire seg_en;data_gen
#(.CNT_MAX (23d4_999_999),//计数 0.1s 计数最大值.DATA_MAX (20d999_999 ) //待显示数据最大值
)
data_gen_inst
(.sys_clk (sys_clk ), //系统时钟50MHz.sys_rst_n(sys_rst_n), //系统复位低电平有效.data (data ), //待显示数据.point (point ), //小数点.sign (sign ), //负号.seg_en (seg_en) //数码管动态显示模块工作使能
);segment_595_dynamic segment_595_dynamic
(.sys_clk (sys_clk ), //系统时钟50MHz.sys_rst_n(sys_rst_n), //系统复位低电平有效.data (data ), //待转码十进制数的二进制编码.point (point ), //小数点.sign (sign ), //符号(负号是否显示).seg_en (seg_en ), //数码管显示使能.ds (ds ), //输出给74HC595的串行数据.shcp (shcp ), //移位寄存器时钟.stcp (stcp ), //存储寄存器时钟.oe_n (oe_n ) //74HC595的输出使能低电平有效
);//模块结束
endmodule
2.2.6.2 代码编译
那么这样顶层模块的代码编写完成保存为 top_segment_595.v回到实验工程添加顶层模块(这儿有一点要注意要把它置为顶层)然后进行全编译报错点击 OK 我们来看一下报错信息 Error (12006): Node instance hc595_ctrl_inst instantiates undefined entity hc595_ctrl 在顶层模块 top_segment_595.v 中对 hc595_ctrl 模块进行了实例化但是我们并没有将 hc595_ctrl.v 文件添加到 Quartus II 的工程中所以提示我们实体没有定义。回到实验工程添加 hc595_ctrl.v 文件然后重新编译编译通过点击 OK 2.2.6.3 逻辑仿真
2.2.6.3.1 仿真代码编写
接下来编写仿真文件对顶层文件进行一个整体的仿真
//时间参数
timescale 1ns/1ns//模块开始 模块名称 端口列表
module tb_top_segment_595();//声明变量 时钟信号和复位信号
reg sys_clk;
reg sys_rst_n;wire ds ;
wire shcp;
wire stcp;
wire oe_n;//对时钟信号和复位信号进行初始化
initialbeginsys_clk 1b1;sys_rst_n 1b0;#35sys_rst_n 1b1;end//生成时钟信号
always #10 sys_clk ~sys_clk;//为了缩短仿真时间对子功能模块当中的两个计时参数进行重定义
defparam top_segment_595_inst.data_gen_inst.CNT_MAX(23d49);
defparam top_segment_595_inst.segment_595_dynamic.segment_dynamic_inst.CNT_MAX(16d9);//实例化顶层模块
top_segment_595 top_segment_595_inst
(.sys_clk (sys_clk ), //系统时钟50MHz.sys_rst_n(sys_rst_n), //系统复位低电平有效.ds (ds ), //输出给74HC595的串行数据.shcp (shcp ), //移位寄存器时钟.stcp (stcp ), //存储寄存器时钟.oe_n (oe_n ) //74HC595的输出使能低电平有效
);//模块结束
endmodule
2.2.6.3.2 仿真代码编译
顶层模块的仿真代码编写完成后保存为 tb_top_segment_595.v回到实验工程添加仿真模块然后进行全编译编译失败点击 OK 查看错误信息
Error (10170): Verilog HDL syntax error at tb_top_segment_595.v(29) near text “(”; expecting “.”, or “[”, or “” Error (10170): Verilog HDL syntax error at tb_top_segment_595.v(30) near text “(”; expecting “.”, or “[”, or “” Error (10112): Ignored design unit tb_top_segment_595 at tb_top_segment_595.v(5) due to previous errors 双击错误信息跳转到 tb_top_segment_595.v 文件的第 29 行观察发现参数重定义时本应该使用赋值运算符“”而不是括号我们这里使用错误进行修改并保存 回到实验工程再次进行编译编译通过点击 OK 下面我们看一下 RTL 视图。首先看顶层 顶层内部包含两个子功能模块数据生成模块和动态显示模块与我们绘制的框图是一致的 动态显示模块内部又包含动态显示驱动模块和 HC595 控制模块 和 segment_595_dynamic 模块的框图是对应的 然后动态显示驱动模块内部又包含了一个 BCD 编码模块 和 segment_dynamic 模块框图也是对应的 接下来进行仿真设置这里一定记得选择顶层仿真模块因为是对顶层模块进行逻辑仿真 2.2.6.3.3 波形仿真
设置完成开始仿真。仿真编译完成点击 sim 窗口添加模块波形回到 wave 窗口全选、分组、消除前缀然后点击 Restart时间参数先设置为 10us 运行一次、全局视图 在实验工程当中一些子功能模块在编写的时候已经进行了仿真验证这儿就不再查看它们的波形只看一下我们刚刚编写的顶层模块和前面没有仿真的动态显示模块的波形。
首先先来看一下动态显示模块主要是看两个位置 首先找到动态显示模块然后找到动态显示驱动模块看一下 sel、seg 这两路信号。首先是位选 两个 sel 信号它们的数据是相同的没有问题。然后是段选信号 段选信号也是没有问题的。下面就是 ds、shcp、stcp、oe_n 这四路信号HC595 控制模块输出的四路信号我们来看一下。首先是 ds 信号 这儿的 ds 信号波形是一致的没有问题。然后是 oe_n 信号 这儿这两路 oe_n 信号是相同的没有问题。然后是移位寄存器时钟我们看一下 那么这两路信号也是没有问题的。然后是存储寄存器时钟 这两个信号也是没有问题的。这样就表明动态显示模块它是没有问题的 下面我们看一下顶层模块顶层模块主要看一下这俩部分 首先是待显示数据 data 我们来看一下 这两路信号波形是一致的没有问题。然后是小数点位和符号位 它们都始终保持低电平没有问题。然后是使能信号 使能信号它的初值为低电平后面一直保持高电平没有问题两个信号是一致的 下面看一下 ds、shcp、stcp、oe_n 这四路信号首先是 ds 这三路 ds 信号波形是一致的没有问题。然后是 oe_n 始终为低电平没有问题。
然后是移位寄存器时钟 波形一致没有问题。然后是存储寄存器时钟 波形一致没有问题 这样就表示顶层模块没有问题仿真验证通过
2.2.7 上板验证
2.2.7.1 引脚绑定
接下来回到实验工程准备上板验证上板验证开始之前绑定管脚ds–R1、oe_n–L11、shcp–B1、stcp–K9、sys_clk–E1、sys_rst_n–M15 引脚绑定完成重新进行编译编译完成点击 OK 接下来参照下图所示连接板卡、电源、下载器下载器的另一端连接到电脑为开发板进行上电 2.2.7.2 结果验证
然后回到实验工程打开下载界面然后下载程序 上板验证视频请点击跳转到B站观看显示0到352626、显示630984到990981
可以发现数码管在进行一个循环的计数计数的初值是 0最大值是 999999计数到最大值会归零开始下一个循环的计数 这里计数的时间应该是挺长的我们就不再进行等待了。上板验证通过 参考资料
34-第二十三讲-数码管动态显示一
35-第二十三讲-数码管动态显示二
36-第二十三讲-数码管动态显示三
37-第二十三讲-数码管动态显示四
38-第二十三讲-数码管动态显示五
39-第二十三讲-数码管动态显示六
40-第二十三讲-数码管动态显示七
20. 数码管的动态显示 图片来源立创商城 ↩︎