在上次的文章 - 浅谈“数字电路”的学习(8)- 编码器、译码器、多路复用器、解复用器的关系和应用 - 中,我梳理了一下数字电路教程中组合逻辑部分的一些典型逻辑应用和他们之间的关系,并通过一个简单的2:4译码器、3:8译码器来学习了一下用Verilog代码如何实现这些逻辑。
作为组合逻辑部分的总结篇,我们用小脚丫FPGA核心板来实现一个4位加法器的功能,正巧小脚丫板上有:
4个开关 - 可以做加法器的4位二进制的加数,可以实现十进制0、1、2...15的输入;
4个按键 - 可以做加法器的4位二进制的加数,在组合逻辑部分还没有涉及到按键的消抖功能,且同时按多个键相对于开关困难一些,因此我们至少可以用这4个按键的任意一个实现十进制的0、1、2、4、8的输入;
2个数码管 - 即便4个按键同时按下,最大输入的值也就15,因此此加法器能够产生的最大输出值为30,用一个数码管显示个位数,会显示0-9;另一个数码管显示十位数,会显示0-3;
8个分立的LED - 可以选择其中的4个作为4位二进制加法器的进位显示,发生进位的时候相应的LED就会亮。
用小脚丫FPGA做4位加法器实验的硬件配置
板上的硬件条件齐全,就要用Verilog代码来实现逻辑了。
相对于前期我们讲述的例子,这个项目复杂的地方在于它包含了以下几个部分的知识点:
加法器 - 如何使用1位的全加器、1位的半加器构成一个4位的二进制加法器;
二进制转BCD码的解码 - 我们用4位二进制加法器实现的结果,在我们人类的认知里其实是十进制的,要用数码管以十进制的方式在两个数码管上将结果显示出来,这就需要我们先将二进制的加法结果(加上进位共5位二进制数据)转换为两个十进制的数值来表示;
7段(加上小数点8段)数码管的显示驱动 - 点亮数码管上的7根LED,通过这7根LED的组合生成我们认知的数字0、1、2、3、4、5、6、7、8、9,将要显示的数字映射到这7段LED上就需要一个编码的过程,有的文章也说是解码(decoder),总之是码制的转换过程
所以,这算是一个综合性的项目,用FPGA通过Verilog编程来实现也能体会到由一个项目由多个Module构成的方式,并能够理解我们常说的“并行”处理的含义
代码的架构如下面的框图所示:
顶层功能模块add_4bits调用其它的几个并行执行的module,并定义了与外界打交道的输入输出管脚。代码如下:
module adder_4bits(sw,key,seg_led_1,seg_led_2,led_carry);
input [3:0] sw;
input [3:0] key;
output [8:0] seg_led_1;
output [8:0] seg_led_2;
output [3:0] led_carry;
wire [3:0] input1;
wire [3:0] input2;
wire [3:0] answer;
wire carry_out;
wire [3:0] carry;
assign input1 = sw;
assign input2 = ~key;
genvar i;
generate
0;i<4;i=i+1) =
begin: generate_N_bit_Adder
=0) =
half_adder f(input1[0],input2[0],answer[0],carry[0]);
else
full_adder f(input1[i],input2[i],carry[i-1],answer[i],carry[i]);
end
assign carry_out = carry[3];
endgenerate
assign led_carry = ~carry;
wire [4:0] sum;
assign sum = {carry_out,answer};
wire [3:0] seg1_input;
wire [3:0] seg2_input;
binary2bcd b2b(sum,seg1_input,seg2_input);
LED display_answer(seg1_input,seg2_input,seg_led_1,seg_led_2);
endmodule
module half_adder(x,y,s,c);
input x,y;
output s,c;
assign s=x^y;
assign c=x&y;
endmodule // half adder
module full_adder(x,y,c_in,s,c_out);
input x,y,c_in;
output s,c_out;
(x^y) ^ c_in; =
(y&c_in)| (x&y) | (x&c_in); =
endmodule // full_adder
要注意的是,在小脚丫FPGA板上,出于让同学们遇到问题才能学习的考虑,我们特别将开关和按键的缺省状态设置成了两种不同的方式,4个开关缺省状态为低电平,开关闭合时该管脚电平拉高;4个按键缺省状态为高电平,按下时该管脚电平为低;故在代码中做了取反~的处理(体会开关的状态以及Verilog对反相信号的处理方式,在使用中体会开关和按键的不同)。
二进制转BCD码在网上有很多文章介绍,常用的方法为移位➕3的方式,只需要短短几行代码就可以实现,关于其原理大家可以自行搜索一下,我也在下面的代码的注释部分附上了这部分代码供同学们参考。考虑到两个4位加法输出的结果只有5位二进制、对应的最多31种结果,我们要做的就是将这5位二进制映射到2个10进制的数字上,有点类似5-32的译码操作,在这里我们使用查找表的方式来构建,主要也是让同学们体会查找表的灵活性、构建方法和Verilog的语法。要知道的是FPGA内部的逻辑都是基于查找表的方式来实现的。
module binary2bcd(binary_data,tens,ones);
input [4:0] binary_data;
output reg [3:0] tens;
output reg [3:0] ones;
always @*
case(binary_data)
5'd0:
begin
tens = 4'd0;
ones = 4'd0;
end
5'd1:
begin
tens = 4'd0;
ones = 4'd1;
end
5'd2:
begin
tens = 4'd0;
ones = 4'd2;
end
5'd3:
begin
tens = 4'd0;
ones = 4'd3;
end
5'd4:
begin
tens = 4'd0;
ones = 4'd4;
end
5'd5:
begin
tens = 4'd0;
ones = 4'd5;
end
5'd6:
begin
tens = 4'd0;
ones = 4'd6;
end
5'd7:
begin
tens = 4'd0;
ones = 4'd7;
end
5'd8:
begin
tens = 4'd0;
ones = 4'd8;
end
5'd9:
begin
tens = 4'd0;
ones = 4'd9;
end
5'd10:
begin
tens = 4'd1;
ones = 4'd0;
end
5'd11:
begin
tens = 4'd1;
ones = 4'd1;
end
5'd12:
begin
tens = 4'd1;
ones = 4'd2;
end
5'd13:
begin
tens = 4'd1;
ones = 4'd3;
end
5'd14:
begin
tens = 4'd1;
ones = 4'd4;
end
5'd15:
begin
tens = 4'd1;
ones = 4'd5;
end
5'd16:
begin
tens = 4'd1;
ones = 4'd6;
end
5'd17:
begin
tens = 4'd1;
ones = 4'd7;
end
5'd18:
begin
tens = 4'd1;
ones = 4'd8;
end
5'd19:
begin
tens = 4'd1;
ones = 4'd9;
end
5'd20:
begin
tens = 4'd2;
ones = 4'd0;
end
5'd21:
begin
tens = 4'd2;
ones = 4'd1;
end
5'd22:
begin
tens = 4'd2;
ones = 4'd2;
end
5'd23:
begin
tens = 4'd2;
ones = 4'd3;
end
5'd24:
begin
tens = 4'd2;
ones = 4'd4;
end
5'd25:
begin
tens = 4'd2;
ones = 4'd5;
end
5'd26:
begin
tens = 4'd2;
ones = 4'd6;
end
5'd27:
begin
tens = 4'd2;
ones = 4'd7;
end
5'd28:
begin
tens = 4'd2;
ones = 4'd8;
end
5'd29:
begin
tens = 4'd2;
ones = 4'd9;
end
5'd30:
begin
tens = 4'd3;
ones = 4'd0;
end
5'd31:
begin
tens = 4'd3;
ones = 4'd1;
end
endcase
/*
integer i;
always @(binary_data)
begin
tens = 4'd0;
ones = 4'd0;
for (i=7; i >= 0; i=i-1)
begin
if (tens>=5)
tens = tens +3;
if (ones >= 5)
ones = ones +3;
tens = tens << 1;
tens[0] = ones[3];
ones = ones << 1;
ones[0] = binary_data[i];
end
end
*/
endmodule
至于2个7段数码管的显示驱动,由于小脚丫FPGA核心板上管脚比较富裕,采用了直接映射的方式,在很多管脚受限的应用场景需要用到动态扫描的方式或通过专用的芯片来扩展,在后面时序逻辑部分的示例中会再讲。
2个7段数码管的显示驱动代码如下:
// ********************************************************************
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// ********************************************************************
// File name : segment.v
// Module name : segment
// Author : STEP
// Description : segment initial
// Web : www.stepfpga.com
//
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date: |Changes Made:
// V1.0 |2021/10/08 |Initial ver
// --------------------------------------------------------------------
// Module Function:数码管的译码模块初始化
module LED (seg_data_1,seg_data_2,seg_led_1,seg_led_2);
input [3:0] seg_data_1; //数码管需要显示0~9十个数字,所以最少需要4位输入做译码
input [3:0] seg_data_2; //小脚丫上第二个数码管
output [8:0] seg_led_1; //在小脚丫上控制一个数码管需要9个信号 MSB~LSB=DIG、DP、G、F、E、D、C、B、A
output [8:0] seg_led_2; //在小脚丫上第二个数码管的控制信号 MSB~LSB=DIG、DP、G、F、E、D、C、B、A
reg [8:0] seg [9:0]; //定义了一个reg型的数组变量,相当于一个10*9的存储器,存储器一共有10个数,每个数有9位宽
initial //在过程块中只能给reg型变量赋值,Verilog中有两种过程块always和initial
//initial和always不同,其中语句只执行一次
begin
seg[0] = 9'h3f; //对存储器中第一个数赋值9'b00_0011_1111,相当于共阴极接地,DP点变低不亮,7段显示数字 0
seg[1] = 9'h06; //7段显示数字 1
seg[2] = 9'h5b; //7段显示数字 2
seg[3] = 9'h4f; //7段显示数字 3
seg[4] = 9'h66; //7段显示数字 4
seg[5] = 9'h6d; //7段显示数字 5
seg[6] = 9'h7d; //7段显示数字 6
seg[7] = 9'h07; //7段显示数字 7
seg[8] = 9'h7f; //7段显示数字 8
seg[9] = 9'h6f; //7段显示数字 9
end
assign seg_led_1 = seg[seg_data_1]; //连续赋值,这样输入不同四位数,就能输出对于译码的9位输出
assign seg_led_2 = seg[seg_data_2];
endmodule
大家看到了这段代码有详细的注释,是不是感觉可读性增加了很多?这是我们Web IDE中的案例代码,可以直接复制到你自己的项目中来使用,作为一种示例,也是希望大家能够体会到注释的重要性,以后在自己的项目中加强这方面的规范性。
** 由于微信的编辑器对Verilog代码的格式识别和支持不够,显示的效果与我们Web IDE上不同。
代码编写完毕,在Web IDE中点击“逻辑综合”,系统会根据你的顶层文件自动提取端口的管脚,让你非常便捷给相应的信号绑定器件的管脚,如下图示。
绑定完管脚,点击保存 - -》 FPGA映射,生成可以下载的jed文件,鼠标指向下载JED文件的图标,右键保存到StepFPGA16的盘里,就完成了对FPGA的编程,你手里的小脚丫FPGA就成了一个4位的加法器。
来看看最终的结果:
搞定!
如果你也能够独立完成这个项目,那你:
对数字电路部分的组合逻辑有了非常深刻的认识;
对FPGA的使用,尤其是管脚的配置、外设的状态、显示器件的使用有了深刻的认识;
对Verilog的语法使用有了更多、更深入的体会,包括多module的设计思路、module之间的接口、并行执行等。
来试一试吧。
周末愉快!
浅谈“数字电路”的学习(1)- 我们身处的“数字逻辑”世界
浅谈“数字电路”的学习(2)- 在兴趣和体验中高效学习
浅谈“数字电路”的学习(3)- 不需要安装软件、人人一学就会的FPGA学习板
浅谈"数字电路"的学习 (4) - 学用FPGA从点灯开始
浅谈“数字电路”的学习(5) - “组合逻辑”的学习逻辑。
浅谈“数字电路”的学习(6) - 看视频了解什么是FPGA?
浅谈“数字电路”的学习(7)- 一切数字计算的核心功能、被教程忽略的加法器
浅谈“数字电路”的学习(8)- 编码器、译码器、多路复用器、解复用器的关系和应用
硬禾小帮手 - 学习硬件设计的随身助手
硬禾学堂 - 电子技术在线学习平台