概述
- 实验目的
- 实现74LS283全加器IP核的编写。
- 实现四位二进制加法器的功能。
- 实现四位二进制减法器的功能。
- 实现四位二进制乘法器的功能。
- 将二进制数转换为BCD码。
- 将BCD码通过7段数码管进行显示。
- 各模块原理及功能
- Adder模块
原理:采用矢量拼接并赋值的算法,获得两个二进制数之和。
功能:实现两个四位二进制数输入[3:0]A, [3:0]B,一个8位二进制输出两者之和[7:0]sum。
- Multiplier 模块
原理:将[3:0]A作为被乘数,[3:0]B作为乘数,采用4次循环的方式,每次循环对A进行向左的移位,若B的右边第i位为1则将其加到中间结果中去,循环结束,中间结果即为最终结果。
功能:实现两个四位二进制数输入[3:0]A, [3:0]B,一个8位二进制输出两者之积[7:0]product。
- Subtraction模块
原理:采用书本P105利用全加器实现全减器的原理。对减数取反再加1作为补码,将被减数和减数的补码相加,得到5位二进制数,当第一位为1表示结果为正,后四位为运算的结果,当第一位为0表示结果为负,若为负数则需要变换为补码才是结果。
功能:实现两个四位二进制数输入[3:0]A, [3:0]B,一个8位二进制输出两者之差[7:0]sub,输出一位二进制数代表结果正负p_or_n(1代表正数0代表负数)。
- Transferbcd模块
原理:采用移位加三的算法,实现的过程:(1)把二进制数左移一位(2)如果共移了8位,那么BCD码就在百位、十位和个位列,转换完成(3)如果再BCD列中,任何一个二进制数是5或比5更大,那么就再BCD列的数值加三(4)返回步骤1。实例如下图所示。
操作 | 百位 | 十位 | 个位 | 二进制数 | |
十六进制数 | F | F | |||
开始 | 1111 | 1111 | |||
左移1 | 1 | 1111 | 111 | ||
左移2 | 11 | 1111 | 11 | ||
左移3 | 111 | 1111 | 1 | ||
加3 | 1010 | 1111 | 1 | ||
左移4 | 1 | 0101 | 1111 | ||
加3 | 1 | 1000 | 1111 | ||
左移5 | 11 | 0001 | 111 | ||
左移6 | 110 | 0011 | 11 | ||
加3 | 1001 | 0011 | 11 | ||
左移7 | 1 | 0010 | 0111 | 1 | |
加3 | 1 | 0010 | 1010 | 1 | |
左移8 | 10 | 0101 | 0101 | ||
BCD码 | 2 | 5 | 5 |
功能:实现8位二进制数输入,10位BCD码[9:0]p输出
- hex7seg模块
原理:对于每一片数码管都是共阴极的,因此是低电平有效,需要对其进行动态扫描。由于公用一个段码,因此一个时刻只能显示一片。
功能:输入选择显示的结果(加减乘中间的一位),输入100M时钟信号,以及加减乘的运算结果(都已经在主函数中调用完成产生了结果),输出七段数码管的片选[3:0]an,以及段选[6:0]a_to_g。
- calculate主模块
原理:调用前面的函数完成计算器的功能。
功能:输入自带的时钟信号,输入数据选择的向量[2:0]choice,输入两个四位二进制数[3:0]A、[3:0]B,输出四片七段数码管的片选以及段选,还有减法时的正负符号p_or_n。
- 实现过程以及代码
- 创建全加器IP核
为更好的实现代码复用,因此建立74LS283的IP核,IP核代码如下:
module adder( input [3:0] A, input [3:0] B, input C0, output [3:0] S, output C4 ); assign {C4, S}={1'b0, A}+{1'b0, B}+C0; endmodule |
其中C0为来自上一位的进位,C4为对下一位的进位,采用了拼接的方式实现S以及C4的赋值。
并通过仿真程序对74LS283进行验证。
module adder_sim; reg [3:0] A; reg [3:0] B; reg C0; wire [3:0] S; wire C4; adder u1(A, B, C0, S, C4); initial //从仿真开始时刻开始执行下面语句 begin A=4'b0; B=4'b0; C0=0; #100; A=4'b0001; B=4'b0000; C0=0; #100; A=4'b0011; B=4'b0011; C0=0; #100; A=4'b1111; B=4'b1111; C0=1; #100; A=4'b1100; B=4'b0011; C0=1; #100; A=4'b0000; B=4'b0000; C0=0; end endmodule |
并创建IP核所示。
- 创建加法模块
欲通过IP核的调用实现全加器,由于单纯实现四位二进制数的加法直接实现的代码长度较于调用74LS283而言更短,因此采用直接实现的方式。代码如下。
module adder( input wire [3:0]A, input wire [3:0]B, output wire [7:0]sum ); wire C4; wire [3:0]S; assign {C4, S}={1'b0, A}+{1'b0, B}; //采用拼接再赋值的方式确定和以及进位 assign sum = {3'b000,C4, S}; //将进位作为第五位二进制数拼接成8位二进制数 endmodule |
由于中间变量不需要通过条件语句更改值,因此只需要作为网表类型即可,最后由于用不到进位,也没有来自上一位的进位,因此不需要加上C0。由于要求输出的位数满足计算器的最多位数的要求,因此输出为8位二进制数。
- 创建减法模块
采用补码的运算实现减法,具体实现代码如下。
module subtraction( input wire [3:0]A, input wire [3:0]B, output reg [7:0]sub, output reg p_or_n //正负 1为正0为负 ); reg C4; //因为要采用if语句对其进行更改因此定义为reg reg C0; reg [3:0]S; reg [4:0]q; always@(*) begin C0=1; q={1'b0, A}+{1'b0, ~B}+1; //被减数与补码的和(补码为反码加一) C4=q[4]; //最高位(代表结果的符号) S=q[3:0]; //后四位 if (C4==1) //最高位为1,表示结果为正,结果为后四位 begin p_or_n = 1; sub = {4'b0000,S}; end if (C4==0) //最高位为0,结果为负,为后四位的补码 begin p_or_n = 0; sub = {4'b0000, !S[3], !S[2], !S[1], !S[0]}+1; end end endmodule |
由于需要在条件语句中修改输出的值,因此将输出和中间变量定义位reg型,能在always中修改,在此时在通过p_or_n输出减法运算结果的正负情况,为正时输出1,为负时输出0。
并对减法模块进行仿真程序的编写,编写的仿真程序如下所示。
module subsim; reg [3:0] A; reg [3:0] B; wire [7:0] sub; wire p_or_n; subtraction u1(.A(A),.B(B),.sub(sub),.p_or_n(p_or_n)); initial //从仿真开始时刻开始执行下面语句 begin A=4'b0; B=4'b0; #100; A=4'b0001; B=4'b0000; #100; A=4'b0011; B=4'b0011; #100; A=4'b1111; B=4'b1111; #100; A=4'b1000; B=4'b1100; #100; A=4'b0000; B=4'b0000; end endmodule |
- 创建乘法模块
将A作为被乘数,每次向左移动一位,与此同时向左遍历B,若此时B为1,则将此时的A加入到中间结果中,遍历一边(即四次)之后则中间变量为最终的输出。其实例如下图所示。
最终实现的代码如下。
module multer( input [3:0] A, input [3:0] B, output reg [7:0] product //乘积 ); reg[7:0]pv; //每次相加产生的中间变量 reg[7:0]ap; //A的每次移位 integer i;
always@(*) begin pv=8'b00000000; ap={4'b0000,A}; //被乘数 for(i=0;i<=3;i=i+1) begin if(B[i]==1) //如果乘数是1则将此时的ap加到中间结果中 pv=pv+ap; ap={ap[6:0],1'b0}; //左移1位 end product=pv; //结果,因为四位二进制数乘法最高为225,只要8位二进制就能完全表示 end endmodule |
由于也需要在always语句块中执行,因此也要定义成寄存器型。由于1111*1111为最大的结果225为8位。因此限定了其他函数运行结果的位数,以便后续转BCD码使用。
- 建立二进制数转BCD码模块
原理为移位加三算法,由于8位二进制数最多位255,因此需要的最多的BCD码位数位10位,因此输出为10为用二进制表示的10进制数。代码如下。
module transferbcd( input wire [7:0] b, output reg [9:0] p //10位二进制数一定够用了 ); reg [17:0] z; // 中间变量 integer i; always @ ( * ) begin for (i = 0; i <=17; i = i + 1) z[i] = 0; z[10:3] = b; // 向左移3位 repeat (5) // 重复5次 begin if (z[11:8] > 4) // 如果个位大于4 z[11:8] = z[11:8] +3; // 加3 if (z[15:12] > 4) // 如果十位大于4 z[15:12] = z[15:12] +3; // 加3 z[17:1] = z[16:0]; // 左移一位 end p = z[17:8]; // BCD(用二进制表示的十进制数前四位表示个位......) end endmodule |
先进行了初始化,令每一位都为0(for语句在这里没有end)。因为刚开始只有左移三位之后才有可能大于4,因此刚开始时都会左移三位。重复5次一共相当于左移了8位,将输入的二进制全部转换为BCD码了。最后输出位中间变量的左边10位。
- 创建七段数码管显示模块
代码如下所示。
module hex7seg( inout wire [2:0]choice, input wire clk_100M, input wire clr, input wire [15:0]x1, //要显示的BCD(减法) input wire [15:0]x2, //(乘法结果) input wire [15:0]x3, //(加法结果) output reg [6:0]a_to_g, //段选 output reg [3:0]an //片选 );
wire [1:0]s; reg [15:0]x; reg [3:0]digit; reg [20:0]clkdiv;
always@(*) begin if (choice == 3'b001) //按动减法按钮 x =x1[15:0]; else if (choice == 3'b010) //按动乘法按钮 x=x2[15:0]; else if (choice == 3'b100) //按动加法按钮 x=x3[15:0]; else x=16'b0; //不按动或者其他情况 end
always @(posedge clk_100M or posedge clr) //相当于一个分频 begin if(clr==1) //清零只是刷新频率为0 clkdiv<=0; else clkdiv<=clkdiv+1; //时钟上升沿加一非阻塞赋值,直接赋值不需要等待) end assign s=clkdiv[20:19]; //只取20 19两位
always@(*) begin an=4'b0000; //位选,显示那个数码管 an[s]=1; //因为clkdiv只有00 01 10 11四位,因此是四个数码管轮流显示 end
always @(*) case(s) 0:digit=x[3:0]; //显示第一个数码管(最右边) 1:digit=x[7:4]; //显示第二个数码管 2:digit=x[11:8]; //显示第三个数码管 3:digit=x[15:12]; //显示第四个数码管 default:; endcase
always@(*) case(digit) 0:a_to_g=7'b1111110; //每个数字的显示 1:a_to_g=7'b0110000; 2:a_to_g=7'b1101101; 3:a_to_g=7'b1111001; 4:a_to_g=7'b0110011; 5:a_to_g=7'b1011011; 6:a_to_g=7'b1011111; 7:a_to_g=7'b1110000; 8:a_to_g=7'b1111111; 9:a_to_g=7'b1111011; 'hA:a_to_g=7'b1110111; 'hB:a_to_g=7'b0011111; 'hC:a_to_g=7'b1001110; 'hD:a_to_g=7'b0111101; 'hE:a_to_g=7'b1001111; 'hF:a_to_g=7'b1000111; default:; endcase endmodule |
将每个模块的输出值作为此模块的输入,最后通过控制信号choice实现对于输出结果的显示。并采用分频电路实现数码管的动态扫描,其中分频电路采用自带时钟信号出发的计数电路,并且分频之后的信号为计数电路的最高两位信号,实现了2位二进制的定周期循环。并用分频之后的2位二进制(00, 01, 10, 11)的循环控制四段数码管的显示。
- 建立顶层模块
顶层模块通过调用加减乘法子模块,并采用BCD转换和数码管显示模块实现。代码如下所示,
module calculate( input wire clk, input wire clr, input wire [2:0]choice, input wire [3:0]A, input wire [3:0]B, output wire [6:0]a_to_g, //段选 output wire [3:0]an, //片选 output wire p_or_n ); wire [15:0]x1; wire [15:0]x2; wire [15:0]x3; wire [9:0]p1; wire [9:0]p2; wire [9:0]p3; wire [7:0] mid1; wire [7:0] mid2; wire [7:0] mid3; assign x3={6'b000000,p3}; assign x2={6'b000000,p2}; assign x1={6'b000000,p1}; //都是并行的除非有begin end subtraction mux_zero(.A(A),.B(B),.sub(mid1),.p_or_n(p_or_n)); //1代表减法 multer mux_first(.A(A),.B(B),.product(mid2)); //2代表乘法 adder mux_two(.A(A),.B(B),.sum(mid3)); //3代表加法 transferbcd mux_three1(.b(mid1),.p(p1)); transferbcd mux_three2(.b(mid2),.p(p2)); transferbcd mux_three3(.b(mid3),.p(p3)); hex7seg mux_four (.choice(choice),.clk_100M(clk),.clr(clr),.x1(x1),.x2(x2),.x3(x3),.a_to_g(a_to_g),.an(an)); endmodule |
采用了分别调用计算模块,并将计算结果都作为输入,并通过按键进行选择输出的结果。
- 编写管教约束文件
整体代码如下图所示。
set_property -dict {PACKAGE_PIN P5 IOSTANDARD LVCMOS33} [get_ports {A[3]}] set_property -dict {PACKAGE_PIN P4 IOSTANDARD LVCMOS33} [get_ports {A[2]}] set_property -dict {PACKAGE_PIN P3 IOSTANDARD LVCMOS33} [get_ports {A[1]}] set_property -dict {PACKAGE_PIN P2 IOSTANDARD LVCMOS33} [get_ports {A[0]}] set_property -dict {PACKAGE_PIN R2 IOSTANDARD LVCMOS33} [get_ports {B[3]}] set_property -dict {PACKAGE_PIN M4 IOSTANDARD LVCMOS33} [get_ports {B[2]}] set_property -dict {PACKAGE_PIN N4 IOSTANDARD LVCMOS33} [get_ports {B[1]}] set_property -dict {PACKAGE_PIN R1 IOSTANDARD LVCMOS33} [get_ports {B[0]}] set_property -dict {PACKAGE_PIN P17 IOSTANDARD LVCMOS33} [get_ports {clk}] set_property -dict {PACKAGE_PIN R15 IOSTANDARD LVCMOS33} [get_ports {clr}] set_property -dict {PACKAGE_PIN U4 IOSTANDARD LVCMOS33} [get_ports {choice[2]}] set_property -dict {PACKAGE_PIN R11 IOSTANDARD LVCMOS33} [get_ports {choice[0]}] set_property -dict {PACKAGE_PIN R17 IOSTANDARD LVCMOS33} [get_ports {choice[1]}] set_property -dict {PACKAGE_PIN F6 IOSTANDARD LVCMOS33} [get_ports {p_or_n}] set_property -dict {PACKAGE_PIN G1 IOSTANDARD LVCMOS33} [get_ports {an[3]}] set_property -dict {PACKAGE_PIN F1 IOSTANDARD LVCMOS33} [get_ports {an[2]}] set_property -dict {PACKAGE_PIN E1 IOSTANDARD LVCMOS33} [get_ports {an[1]}] set_property -dict {PACKAGE_PIN G6 IOSTANDARD LVCMOS33} [get_ports {an[0]}] set_property -dict {PACKAGE_PIN D4 IOSTANDARD LVCMOS33} [get_ports {a_to_g[6]}] set_property -dict {PACKAGE_PIN E3 IOSTANDARD LVCMOS33} [get_ports {a_to_g[5]}] set_property -dict {PACKAGE_PIN D3 IOSTANDARD LVCMOS33} [get_ports {a_to_g[4]}] set_property -dict {PACKAGE_PIN F4 IOSTANDARD LVCMOS33} [get_ports {a_to_g[3]}] set_property -dict {PACKAGE_PIN F3 IOSTANDARD LVCMOS33} [get_ports {a_to_g[2]}] set_property -dict {PACKAGE_PIN E2 IOSTANDARD LVCMOS33} [get_ports {a_to_g[1]}] set_property -dict {PACKAGE_PIN D2 IOSTANDARD LVCMOS33} [get_ports {a_to_g[0]}] |
其中正负号通过F6 LED灯进行显示,量代表减法结果为正,灭代表减法结果为负。输入A、B通过8位拨码开关实现输入。
- 结论与总结
在对不同情况赋不同的值的情况下,需要对除去直接赋值的语句外还需要对情况之外的可能进行赋值,使系统更加稳定。在实验过程中对于不同的choice而言要输出不同的结果,在除去其中有一个为1 的情况下还可能有多个1或者没有1的情况,一次代码要如下所示。
always@(*) begin if (choice == 3'b001) //按动减法按钮 x =x1[15:0]; else if (choice == 3'b010) //按动乘法按钮 x=x2[15:0]; else if (choice == 3'b100) //按动加法按钮 x=x3[15:0]; else x=16'b0; //不按动或者其他情况 end |
要加一个else不然出现不同的结果,因此在同时按动两个以上按钮的时候不会出现错误结果。
对于一个矢量进行取反,既可以通过 ~B直接各位取反,也可以通过!S[3], !S[2], !S[1], !S[0]的形式进行取反。
- 程序的不足
- 没有想到除法如何显示如何计算,因此计算器的最简单的功能加减乘除只实现了前三者。
- 减法的符号显示较为笨拙,如果需要在数码管中显示符号需要对另一半的数码管进行扫描,不太好显示以及程序编写。
- 计算器的出发方式较为机械,需要一直按在按键上才会显示,因为在输出时如果需要用上升沿和下降沿触发的话,数码管显示模块的代码将会存在更多的重复,因此在实现中采用折衷的方式,降低代码复杂度。
最后
以上就是火星上刺猬为你收集整理的verilog语言实现简易二进制计算器的全部内容,希望文章能够帮你解决verilog语言实现简易二进制计算器所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复