用yacc编写的算术运算计算器_10天学会四则运算小计算器设计之第5天

用yacc编写的算术运算计算器_10天学会四则运算小计算器设计之第5天_第1张图片

4517ebef3dfdb2bc7c956a66c168cf09.png

写在前面的话

    我大学本科学的是测控专业,2012年考取首都师范大学物理系研究生。我从未学习过数字电路设计,对FPGA和Verilog语言没有任何概念,更没有设计数字电路系统的基础和经验,也从未自己动手装配和完成过一台能实际运行的电子系统。但我从小就对电子设计有浓厚的兴趣。为什么小小的计算器按几下就能完成非常复杂的数学计算,一直困惑着我,激起我年轻的好奇心。大学四年里,虽然学习过“数字电路”和“模拟电路”课程,考试成绩也很不错,但对我而言,计算器是如何设计的,仍旧是一头雾水。

    听同学们说,如果掌握了FPGA设计,这个谜就能找到答案。我用关键字“FPGA培训”在百度搜索,发现一个公司正在开设FPGA就业培训(100天)班,也知道这个班由北京航空航天大学的夏宇闻教授亲自讲授和管理。于是下定决心抽出3个月时间,认真学习一下FPGA。经过100天的学习和练习,我初步掌握了如何用FPGA芯片设计和搭建复杂数字系统。现在我有充分的信心,只要设计需求明确,我完全有能力独立设计并完成一个较复杂的数字系统,并能可靠地完成预先设定的数据 处理任务。这个阶段的学习给了我很多启发,也增强了我的信心,很想把自己的感受和学习心得编写成小册子与大家分享。我的想法得到夏宇闻教授的支持。于是我把学习期间的心路历程和学到的知识、经验略加整理,以日记的形式写出来,与大家分享,希望能给打算学习Verilog和FPGA设计的初学者一些帮助和启发,起到抛砖引玉的作用。

    本书使用的硬件为至芯科技的四代开发板、Altera CycloneⅣ的芯片,软件为QuartusⅡ13.0 sp1。 

——作者

1

设计需求讲解

今天的设计任务有两项:1)完成计算器中负责二进制四则运算的算术逻辑模块(即alu0)。2)改写计算控制状态机模块(即key2bcd1)。算术逻辑模块是计算机设计中最后一个小模块。它运行所需的准备工作几乎都是由以前调试通过的模块完成的。操作数a和b,以及操作符opcode都可以从键盘输入,并转换为二进制数存入寄存器A、B。算术逻辑模块 alu0只需做相应的算术运算即可。

用yacc编写的算术运算计算器_10天学会四则运算小计算器设计之第5天_第2张图片

2

设计工具使用讲解

我们需要新建一个文件来作为计算模块。

选择File–New–Verilog HDL File,如图5-1所示,然后点击下面的OK按钮。

用yacc编写的算术运算计算器_10天学会四则运算小计算器设计之第5天_第3张图片

图5-1 新建文件

1、计算模块的可综合代码

计算模块的可综合代码如下:

module alu0(a, b, clk, rst_n, opcode, bin_data);

    input [23:0] a,b;
    input clk;
    input rst_n;
    input [3:0] opcode;

    output reg [24:0] bin_data;

    always @ (posedge clk or negedge rst_n)
    begin
        if(!rst_n)
        begin
            bin_data <= 0;
        end
        else
        begin
            case(opcode)
                10: begin bin_data <= a + b; end  
                11: begin bin_data <= a - b; end  
                12: begin bin_data <= a * b; end  
                13: begin bin_data <= a / b; end 
            endcase
        end
    end

endmodule

经过这些天的练习,写起这段代码来一定很轻松了。我们只需要判断opcode的值来运行不同的运算即可。

2、计算模块的测试

计算模块的测试代码如下:

`timescale 1ns/1ns
module alu_tb0;

    reg [23:0] a,b;
    reg clk;
    reg rst_n;
    reg [3:0] opcode;

    wire [23:0] bin_data;

    alu0 u1(.a(a),.opcode(opcode),.b(b),.clk(clk),
.rst_n(rst_n),.bin_data(bin_data));

    initial 
    begin
        a = 0;b = 0;rst_n = 0; clk = 1; opcode = 0; //复位,初始化

        #101 rst_n = 1; //复位结束

        #1000 a = 11;       //11 + 2
        opcode = 10; 
        b = 2;

        #1000 a = 13;       //13 - 3
        opcode = 11; 
        b = 3;

        #1000 a = 4;              //4 × 5
        opcode = 12; 
        b = 5;

        #1000 a = 10;       //10 ÷ 2
        opcode = 13; 
        b = 2;

        #1000
        $stop;

    end

    always #10 clk = ~clk;

endmodule

因为模块比较简单,逻辑不易出错,所以简单地做一遍测试即可。写好之后将alu.v和alu_tb.v加入到仿真文件中,如图5-2所示。

设置完毕后启动RTL仿真。

用yacc编写的算术运算计算器_10天学会四则运算小计算器设计之第5天_第4张图片

图5-2 新建仿真设置

3、转到ModelSim仿真工具进行仿真

将计算模块的测试代码转到ModelSim仿真工具进行仿真,得到的仿真波形如图5-3所示。观察时可以将所有的变量格式设置为unsigned,方便观察。

用yacc编写的算术运算计算器_10天学会四则运算小计算器设计之第5天_第5张图片

图5-3 仿真结果

复位后的第一组运算,a为11,b为2,opcode为10(加法),结果bin_data输出13,没有问题。同样,13-3=10,4×5=20,10÷2=5均运算正确。

4、模块连接关系

目前我们的顶层连接还是以前做的如图5-4所示这种形式,后来编写的BCD码与二进制数之间的转换模块以及计算模块还都没有添加进去,所以还需要修改顶层代码。

用yacc编写的算术运算计算器_10天学会四则运算小计算器设计之第5天_第6张图片

图5-4 模块连接图

前面曾经讨论过,由于计算模块需要二进制数,而其它模块均以BCD码传输,所以计算模块alu前后必须紧跟BCD转二进制,以及二进制转BCD。所以把这3个模块接在按键转BCD码key2bcd后面最合适不过了。而值得考虑的是,计算完的结果输出到哪里。这里有两种选择,一种是输出到显示模块display,另一种是输出到按键转BCD的模块key2bcd。一般情况下我们会先想到要直接输出到显示模块,这样计算的结果就直接显示出来了。但是这样就又牵扯到一个问题,那就是显示模块只能输出一个数据,也就是说,只能输出A,B,或者结果,到底要显示哪一个数,显示模块需要做出判断。而我们之前已经在显示模块里加了判断A或者B的代码,不如就在这个基础上进行改造,将输出结果赋值给A或者B,这样显示模块就可以不用改动,继续判断A,B进行输出即可。而这一做法还有另外一个好处,就是为了改进计算器实现连续运算提供了方便。当我们想实现连续运算A+B+C+D……的时候,我们不可能会设置N多个变量来保存这些操作数,可以将每一步的运算结果赋值回A,这样就可以简化为用A一直在与另外一个数B进行运算,只需要两个操作数。关于连续运算以后再进行讨论。下面先修改key2bcd中的代码。

module key2bcd1 (clk, 
                 rst_n, 
                 real_number, 
                 opcode, 
                 BCDa, 
                 BCDb, 
                 result ); //端口加上计算结果result

    input [4:0] real_number;
    input rst_n,clk;
    input [23:0] result;    //result定义为输入

    output reg [23:0] BCDa,BCDb;
    output reg [3:0] opcode;

    reg [3:0] opcode_reg;
    reg [3:0] state;
    reg datacoming_state,datacoming_flag;


    always @(posedge clk)
       if (!rst_n)
         begin
        datacoming_state <=0;
        datacoming_flag <=0;
         endelseif (real_number!=17)    case(datacoming_state)
            0: begin                
            datacoming_flag <=1;
            datacoming_state <=1;
             end
            1: begin
            datacoming_flag  <=0;
            datacoming_state <=1;
              end
        endcase
         else
        begin
           datacoming_state <= 0;
           datacoming_flag <= 0;
        end

    always @ (posedge clk or negedge rst_n)
    beginif(!rst_n)
        begin
            BCDa <= 0;
            BCDb <= 0;
            state <= 0;
            opcode <= 0;
        end elseif(datacoming_flag)
        begincase(state)
           0:   case(real_number)
             0,1,2,3,4,5,6,7,8,9:
            begin
             BCDa[23:0] <= {BCDa[19:0],real_number[3:0]};
                 state <= 0;
             end
             10,11,12,13:   
              begin
                 opcode_reg <= real_number[3:0];    
                 state <= 1;
              end
                default:state <= 0;
             endcase

         1: case(real_number)
             0,1,2,3,4,5,6,7,8,9:
             begin
               opcode <= opcode_reg;    
             BCDb[23:0] <= {BCDb[19:0],real_number[3:0]};
                 state <= 1;
             end
             14: begin
                  BCDa <= result;   //在按完等号之后将结果赋值给A
                  BCDb <= 0;     //同时清空B的值,数码管就可以显示A
                  opcode <= 0;
                  state <= 2;
                 end
             default:state <= 1;
           endcase

//加上一个状态,表示上一次运算结束后,重新输入数值A时,不受到运算结果的影响
          2: case(real_number)  
          0,1,2,3,4,5,6,7,8,9:
              begin
             BCDa <= real_number;
             state <= 0;  //输入完一位数后,状态跳回0,便又可以移位显示
              end
           10,11,12,13: 
             begin
             opcode_reg <= real_number[3:0];    
                  state <= 1;
             end
             default:state <= 2;
          endcase
            default : state <= 0;
          endcase
        end
    end
endmodule

同时,顶层模块的连接关系也就产生了,如图5-5所示。

用yacc编写的算术运算计算器_10天学会四则运算小计算器设计之第5天_第7张图片

图5-5 模块连接图

按照这个连接关系把可综合顶层修改如下:

module calc2(clk,rst_n,seg,sel,keyin,keyscan);

    input clk, rst_n;
    input [3:0] keyin;

    output [3:0] keyscan;
    output [2:0] sel;
    output [7:0] seg;

    wire clk_slow;
    wire [4:0] real_number;
    wire [23:0] BCDa,BCDb,a,b,bin_data,result;
    wire [3:0] opcode;


    display3 u1( .clk(clk),
                 .adata(BCDa),
                 .bdata(BCDb),
                 .rst_n(rst_n),
                 .sel(sel),
                 .seg(seg),
                 .clk_out(clk_slow) 
);


    keyscan0 u2(
                  .clk(clk_slow),
                .rst_n(rst_n),
                .keyscan(keyscan),
                .keyin(keyin),
                .real_number(real_number)
              );

    key2bcd1 u3(
                  .clk(clk_slow),
                  .real_number(real_number),
                  .opcode(opcode),
                  .rst_n(rst_n),
                  .BCDa(BCDa),
                  .BCDb(BCDb),
                  .result(result)
                 );

    bcd2bin0 u4(
                 .BCDa(BCDa),
                 .BCDb(BCDb),
                 .a(a),
                 .b(b)
                    );

    bin2bcd0 u5(
                .bin(bin_data),
                .bcd(result)
                );

    alu0 u6(
                .a(a),
                .b(b),
                .clk(clk_slow),
                .rst_n(rst_n),
                .opcode(opcode),
                .bin_data(bin_data)
              );

endmodule
写好之后将calc2模块设置为顶层,然后进行分析综合。通过之后可以单击RTL Viewer看一下综合后各个模块的连接关系,是否和之前设想的一样,如图5-6所示,在Analysis & Synthesis下找到RTL Viewer,双击即可。

用yacc编写的算术运算计算器_10天学会四则运算小计算器设计之第5天_第8张图片

图5-6 打开RTL视图

打开之后会弹出一个窗口如图5-7所示窗口,里面绘制了各个模块之间的连线和输入输出端口。

用yacc编写的算术运算计算器_10天学会四则运算小计算器设计之第5天_第9张图片

图5-7 RTL模型图

可以看出,程序综合出了我们想要的模块连接,接下来可以进行全编译,然后将代码下载到开发板里了。

5、下载程序到开发板进行调试

由于之前已经将管脚分配好了,所以等待编译成功之后,打开Programmer直接下载程序即可。下载成功后,初始状态数码管显示6个0,顺序输入数字、操作符、数字、等号,即可得到正确的结果,继续按下数字或者操作符,可以进行下一次运算。

3

今天工作总结

经过5天的努力,终于完成了可以实现基本功能的6位计算器了,赶快享受一下劳动成果吧!

今天通过上机仿真,不断地代码修改,逐步掌握了电路构造的思想,理解了模块划分,逐块调试和整合调试的概念。回顾一下今天所做的工作和学到的内容总结如下:

1)计算模块及其测试代码的编写。

2)多个模块之间数据和控制连接的思路。

3)通过观察RTL Viewer学会如何查看综合生成的逻辑电路。

4)   修改了key2bcd模块,增加了一个运算结果输入接口。

5)通过result接口,可将运算结果result从binary2bcd模块输出至key2bcd模块中的BCDa寄存器,直接输出display模块显示。

到此为止,我已达到了夏老师提出的最基本要求。从明天开始,要对这个计算器进行改进,包括占用逻辑资源的优化、运算功能的添加、显示效果的美化(消去有效数字前面的0)、连续运算等。

4

夏老师评述

赵然同学今天完成的工作非常出色,计算器四则运算的功能基本实现了。当然在资源消耗方面尚未做深入思考。他之所以能顺利地完成今天的工作,跟他前四天的工作基础是不能分割的。今天通过综合和布局布线后下载的模块calc2中,大部分是前几天经过测试成熟的模块。键盘扫描分析模块keyscan0不用做任何修改,显示模块display3、数制转换模块bcd2bin0和bin2bcd0也不用做任何修改,计算模块alu0与教材上的例子相似;计算控制状态机key2bcd1是在第三天编写的key2bcd0基础上修改的。该模块是赵然同学在彻底理解了老师课堂上介绍的gewshiwbaiw模块原理的基础上,自己动手编写的。key2bcd1模块思路巧妙,可以实现连续计算操作。该计算控制状态机的成功是赵然同学今天工作顺利的主要原因。首先他把数字0~9的的输入和四则运算操作符号的输入分别用不同的状态记录,他还把等号的输入与四则运算操作符、0~9数字的输入区分开,用不同状态记录,以此来管理参与运算数据的位数确定、中间结果的寄存和最后运算结果的确认。最后他还把计算结果输入到该状态机中的BCDa寄存器,使得显示模块不必做任何修改,而且还能实现连续计算,一箭双雕。他之所以能写出这个状态机,与他勤于思考,较深刻地理解状态的含义和状态在控制中的作用有关,也跟他敢于实践,勇于尝试新的途径 、不怕失败、相信自己有关。

60796c18ea1ec56d44030731420af27c.gif

  下期预告:

第6天——可进行连续运算的状态机改进

往期回顾

10天学会四则运算小计算器设计之第4天

姿势已摆好

就等你点啦

你们的“在看”是小编连载的动力喔 ?

你可能感兴趣的:(用yacc编写的算术运算计算器)