确定完两个计数器后来思考输出信号 seg_sel 的变化。根据信号波形图得知,第 1 秒时 FPGA输出值为 8’hfe;第 2 秒时输出值为 8’hfd;以此类推,在第 8 秒时输出值为 8’h7f。设计中使用 cnt1来表示第几个 1 秒,即 cnt1==0 的时候,输出值为 8’hfe;在 cnt1==1 的时候输出值为 8’hfd;以此类推,在 cnt1==7 时输出值为 8’h7f。将其翻译成代码,调用至简设计法模板,在编辑模式下输入“Shixu2”,然后补充完整,得到信号 seg_sel 代码如下:
观察补充完整的代码可以发现:代码表示与文字描述基本上是一模一样的,这其实是体现了 verilog“硬件描述语言”的特点。上文所列代码可以正确实现 seg_sel 功能,且从实现角度和资源角度来说都足以完成设计,但依然可以将其进行进一步的概括从而得到简化版的代码。根据设计目标可知,第 1 个数码管显示“0”时,seg_sel 输出为 b8’b1111_1110;第 2 个数码管显示“1”时,seg_sel 输出为 b8’b1111_1101;第 3 个数码管显示“2”时,seg_sel 输出为 b8’b1111_1011;第 4 个数码显示“3”时,seg_sel 输出为 b8’b1111_0111;第 5 个数码管显示“4”时,seg_sel 输出为 b8’b1110_1111;第 6 个数码管显示“5”时,seg_sel 输出为 b8’b1101_1111;第 7个数码管显示“6”时,seg_sel 输出为 b8’b1011_1111;第 8 个数码管显示“7”时,seg_sel 输出为 b8’b0111_1111;根据规律可以发现每个控制数码管的闪亮信号,其对应数字“0”都依次向左移动 1 位。在设计中可以统一设为将 8’b1 向左移位,将其取反后的值再赋给 seg_sel,可以观察出每次左移的位数和 cnt1 的值相等。因此可得以下代码,其中的第 6 行即表示先将 8’b1 向左移位,再取反,将该值赋给 seg_sel。
也可以带入数值计算一下,假如此刻 cnt1 等于 0,则 8’b1<<0 的结果是 8’b0000_0001,取反的值为 8’hfe 即b8’b1111_1110;假如此刻 cnt1 等于 3,则 8’b1<<3 的结果为 8’b000_1000,取反后的结果为 8’hf7 即 8’b1111_0111。两段代码的对比结果相同,但是这种方法下代码经过简化,更加清晰。
这里同样可以在编辑模式下输入“Shixu2”,调出至简设计法模板,补充完整后得到代码如下:
最后来思考输出信号 seg_ment 的变化。分析波形图可知,在第 1 次显示时,其输出值为 7’h01;在第 2 次显示时,其输出值为 7’h4f;以此类推,在第 8 次显示时,其输出值为 7’h0f。用计数器 cnt1 表示第几次显示,也就是说:当 cnt1==0 时 seg_ment 输出值为 7’h01;在 cnt1==1 时输出值为 7’ h4f;以此类推,在 cnt1==7 时输出值为 7’h0f。将其翻译成代码,在编辑模式下“Shixu2”,调出至简设计法模板,补充完整后的代码如下:
上面的代码可以正确实现 seg_ment 的功能,对于本设计来说已经足够。但进一步思考可以发现:该代码实现了类似译码的功能,将数字设成数码管显示的值,代码中只对 0~7 进行译码。为了方便日后设计,在这里设计一个通用的译码模块,将 0~9 都进行译码。这样的话今后遇到类似的工程,就可以随时调用这一代码,译码模块代码如下所示:
在本设计中只要控制 data 信号,即可实现在数码管显示相应数字。前文可知:当 cnt1=0,则数码管会显示 0。当 cnt1=1,则数码管会显示 1。因此 data 信号代码如下:
在代码的最后一行写下 endmodule
至此,主体程序已经完成。
3.3 信号定义
接下来要将 module 补充完整,首先来定义信号类型。再次强调,在进行 reg 和 wire 的判断时,总会有多余的联想,比如认为 reg 是寄存器,wire 是线;或者认为 reg 的会综合成寄存器,wire 不会综合成寄存器。但是这些其实和 reg 型还是 wire 型都并无关系,在信号类型的判断时不需要做任何的联想,只要记住一个规则“用 always 实现的是 reg 型,其他都是 wire 型”就可以了。进行类型定义时,需要设定信号的位宽。至简设计法在这里们分享一个非常实用的位宽获取技巧:打开计算器,点击“查看”,选择“程序员”模式,在“十进制”下将信号值输入进去,就会获得对应的信号位宽,如下图所示。
cnt0 是用 always 产生的信号,因此类型为 reg。其计数的最大值为 50_000_000,通过计算器可以得知需要用 26 根线表示,即位宽是 26 位。add_cnt0 和 end_cnt0 都是用 assign 方式设计的,因此类型为 wire。其值是 0 或 1,用 1 个线表示即可。打开 GVIM,在编辑模式下输入“Wire1”调用至简设计法模板,补充完整后得到代码表示如下:
同理,cnt1 是用 always 产生的信号,因此类型为 reg。其计数的最大值为 7,需要用 3 根线表示,即位宽是 3 位。编辑模式下输入“Reg3”调用至简设计法模板并补充完整;add_cnt1 和 end_cnt1 都是用 assign 方式设计的,因此类型为 wire。其值是 0 或者 1,用 1 根线表示即可。编辑模式下输入“Wire1”调用至简设计法模板,补充完整后得到代码表示如下:
seg_sel 是用 always 方式设计的,因此类型为 reg,其需要用 8 根线表示,即位宽为 8。编辑模式下输入“Reg8”调用至简设计法模板,补充完整后得到代码表示如下:
seg_ ment 是用 always 方式设计的,因此类型为 reg,其需要用 7 根线表示,即位宽为 7,代
码表示如下:
如果使用译码电路则会用到 data 信号。该信号是用 assign 设计的,所以类型为 wire,其最大值为 9,位宽为 4。编辑模式下输入“Wire4”调用至简设计法模板,补充完整后得到代码表示如下:
至此,整个代码的设计工作已经完成。完整版的工程代码如下: