admin管理员组

文章数量:1794759

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

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

  • 实验目的
  • 实现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