verilog语言实现简易二进制计算器

  • 一、实验目的
  1. 实现74LS283全加器IP核的编写。
  2. 实现四位二进制加法器的功能。
  3. 实现四位二进制减法器的功能。
  4. 实现四位二进制乘法器的功能。
  5. 将二进制数转换为BCD码。
  6. 将BCD码通过7段数码管进行显示。
  • 二、各模块原理及功能
  • 1. Adder模块

原理:采用矢量拼接并赋值的算法,获得两个二进制数之和。

功能:实现两个四位二进制数输入[3:0]A, [3:0]B,一个8位二进制输出两者之和[7:0]sum。

  • 2. Multiplier 模块

原理:将[3:0]A作为被乘数,[3:0]B作为乘数,采用4次循环的方式,每次循环对A进行向左的移位,若B的右边第i位为1则将其加到中间结果中去,循环结束,中间结果即为最终结果。

功能:实现两个四位二进制数输入[3:0]A, [3:0]B,一个8位二进制输出两者之积[7:0]product。

  • 3. Subtraction模块

原理:采用书本P105利用全加器实现全减器的原理。对减数取反再加1作为补码,将被减数和减数的补码相加,得到5位二进制数,当第一位为1表示结果为正,后四位为运算的结果,当第一位为0表示结果为负,若为负数则需要变换为补码才是结果。

功能:实现两个四位二进制数输入[3:0]A, [3:0]B,一个8位二进制输出两者之差[7:0]sub,输出一位二进制数代表结果正负p_or_n(1代表正数0代表负数)。

  • 4. 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输出

  • 5. hex7seg模块

原理:对于每一片数码管都是共阴极的,因此是低电平有效,需要对其进行动态扫描。由于公用一个段码,因此一个时刻只能显示一片。

功能:输入选择显示的结果(加减乘中间的一位),输入100M时钟信号,以及加减乘的运算结果(都已经在主函数中调用完成产生了结果),输出七段数码管的片选[3:0]an,以及段选[6:0]a_to_g。

  • 6. calculate主模块

原理:调用前面的函数完成计算器的功能。

功能:输入自带的时钟信号,输入数据选择的向量[2:0]choice,输入两个四位二进制数[3:0]A、[3:0]B,输出四片七段数码管的片选以及段选,还有减法时的正负符号p_or_n。

  • 三、实现过程以及代码
  • 1. 创建全加器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核所示。

  • 2. 创建加法模块

欲通过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位二进制数。

  • 3. 创建减法模块

采用补码的运算实现减法,具体实现代码如下。

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

  • 4. 创建乘法模块

将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码使用。

  • 5. 建立二进制数转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位。

  • 6. 创建七段数码管显示模块

代码如下所示。

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)的循环控制四段数码管的显示。

  • 7. 建立顶层模块

顶层模块通过调用加减乘法子模块,并采用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

采用了分别调用计算模块,并将计算结果都作为输入,并通过按键进行选择输出的结果。

  • 8. 编写管教约束文件

整体代码如下图所示。

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]的形式进行取反。

  • 五、程序的不足
  1. 没有想到除法如何显示如何计算,因此计算器的最简单的功能加减乘除只实现了前三者。
  2. 减法的符号显示较为笨拙,如果需要在数码管中显示符号需要对另一半的数码管进行扫描,不太好显示以及程序编写。
  3. 计算器的出发方式较为机械,需要一直按在按键上才会显示,因为在输出时如果需要用上升沿和下降沿触发的话,数码管显示模块的代码将会存在更多的重复,因此在实现中采用折衷的方式,降低代码复杂度。

  • 7
    点赞
  • 81
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值