cpu设计实战-verilog实现简单MIPS中CPU的ALU(1)

什么是ALU?

ALU要完成加减运算,比较运算,还要完成移位运算,逻辑运算。那么它的电路应该如何设计呢?分析可知,ALU内部包含做加减操作、比较操作、移位操作和逻辑操作的逻辑,ALU的输人同时传输给这些逻辑,各逻辑同时运算,最后通过一个多路选择电路将所需的结果选择出来作为ALU的输出。整体电路结构图如下所示。

 两个输入信号为两个32位操作码,一个控制信号用于选择进行哪种逻辑运算。

为什么输入时两个信号源?

根据下图可以看到,常用的指令的大部分是对两个寄存器中的数值进行操作,最终输出一个

控制信号alu_control是多少位?

因为cpu设计中对于选择信号一般使用独热码进行编码,alu_control的位数取决于alu内部有多少种运算。在本文中实现的简单MISP CPU的ALU中有基本的加减,比较,逻辑操作和运算共12种,故一共12位。据此可以确定输入输出信号的代码:

module MIPS_ALU (
    input [31:0] alu_src1,
    input [31:0] alu_src2,
    input [11:0] alu_control,
    output [31:0]alu_result
);

接下来对选择信号和输出信号进行细分,因为肯定不可能直接对alu_control和alu_result进行操作

alu_control细分为每个操作的选择:

    wire op_add;    //加
    wire op_sub;    //减
    wire op_slt;    //有符号比较
    wire op_sltu;   //无符号比较
    wire op_and;    //与
    wire op_or;     //或
    wire op_nor;    //或非
    wire op_xor;    //异或
    wire op_sll;    //逻辑左移
    wire op_srl;    //逻辑右移
    wire op_ora;    //算数右移
    wire op_lui;    //高位加载

    assign op_add  = alu_control[0];
    assign op_sub  = alu_control[1];
    assign op_slt  = alu_control[2];
    assign op_slt  = alu_control[3];
    assign op_and  = alu_control[4];
    assign op_or   = alu_control[5];
    assign op_nor  = alu_control[6];
    assign op_xor  = alu_control[7];
    assign op_sll  = alu_control[8];
    assign op_srl  = alu_control[9];
    assign op_ora  = alu_control[10];
    assign op_lui  = alu_control[11];

alu_result细分为每个操作的输出结果(32位):

     //拆分输出信号alu_result  
    wire [31:0] add_result; 
    wire [31:0] sub_result; 
    wire [31:0] slt_result; 
    wire [31:0] sltu_result; 
    wire [31:0] and_result; 
    wire [31:0] or_result; 
    wire [31:0] nor_result; 
    wire [31:0] xor_result; 
    wire [31:0] sll_result; 
    wire [31:0] srl_result; 
    wire [31:0] ora_result; 
    wire [31:0] lui_result; 

加减法可以用一个操作来表示,具体实现和原理后面再说,修改一下:

     //拆分输出信号alu_result  
    wire [31:0] add_sub_result; 
    wire [31:0] slt_result; 
    wire [31:0] sltu_result; 
    wire [31:0] and_result; 
    wire [31:0] or_result; 
    wire [31:0] nor_result; 
    wire [31:0] xor_result; 
    wire [31:0] sll_result; 
    wire [31:0] srl_result; 
    wire [31:0] ora_result; 
    wire [31:0] lui_result; 

alu的输出就是根据选择信号进行对应输出,现在每个选择信号和输出信号都有了,可以先写最终的输出代码了,至于具体每个alu_result后面再写:

    assign alu_result = ({{32{op_add | op_sub}} & add_sub_result}) |
                        ({{32{op_slt         }} & slt_result    }) |
                        ({{32{op_sltu        }} & sltu_result   }) |
                        ({{32{op_and         }} & and_result    }) |
                        ({{32{op_or          }} & or_result     }) |
                        ({{32{op_nor         }} & nor_result    }) |
                        ({{32{op_xor         }} & xor_result    }) |
                        ({{32{op_sll         }} & sll_result    }) |
                        ({{32{op_srl         }} & srl_result    }) |
                        ({{32{op_ora         }} & ora_result    }) |
                        ({{32{op_lui         }} & lui_result    }) ;

这个代码是如何写出来的?

整体来看每个()之间的|可以当作选择器的作用,然后我们拿出其中一个来看,比如

({{32{op_slt         }} & slt_result    })

假如我们现在就是需要执行op_slt这个操作,那么输入选择信号alu_control为001000_000000(之前说了它是独热码,这也是为什么用独热码编码的原因),此时op_slt的值为1,那么此时整个的输出就是slt_result;

假如我们现在就是需要执行不是op_slt,那么根据独热码的原理,是op_slt的值为0,那么此时整个的输出就是0;而又因为每个()之间使用或逻辑,所以对整体的输出没有影响。

这就巧妙的符合我们想要的操作,并且使用assign语句而不是case语句,这将减少消耗和资源并且优化了代码结构,后面的cpu设计中经常会参考此模版,需要好好理解。

那么接下来就是每一个result是怎么实现的?

我们需要了解每种运算的原理。

最简单的逻辑运算与或非等

    assign and_result = alu_src1 & alu_src2;
    assign or_result  = alu_src1 | alu_src2;
    assign nor_result = ~or_result;
    assign xor_result = alu_src1 ^ alu_src2;

然后是LIU指令即Load Upper Immediate(LUI),它所做的操作是把16位的立即数加载到寄存器的高位,寄存器的低位用0补位。因此实现起来也简单:

    assign lui_result = {alu_src2[15:0],16'b0};

然后逻辑左移右移和算数右移也比较好写先写上:

    assign sll_result = alu_src2 << alu_src1[4:0];
    assign srl_result = alu_src2 >> alu_src1[4:0];
    assign sra_result = ($signed(alu_src2)) >>> alu_src1[4:0];

由于>>只能实现逻辑右移所以需要加上内置函数以及>>>才能实现算术右移

接下来实现加法减法器,我们通过一些原理可以同时实现加法减法的运算

补码加法器有如下性质:

A的补码-B的补码=A的补码+B的补码取反+1

据此可用加法实现减法,只需要对源输入进行一定处理。如果进行减法操作就把B输入进行取反并且进位加一。

那么如何判断进行的是加法还是减法操作呢?

可以根据选择信号来判断,如果进行减或者比较操作的话就需要进行减法运算,因为比较操作也可以用加法操作来实现,因此可写代码如下

    wire [31:0] adder_a;
    wire [31:0] adder_b;
    wire        adder_cin;   
    wire [31:0] adder_result;//加减的输出
    wire        adder_cout;//进位输出用于作比较

    assign adder_a = alu_src1;
    assign adder_b = (op_sub | op_slt | op_sltu) ? ~alu_src2 : alu_src2;
    assign adder_cin = (op_sub | op_slt | op_sltu) ? 1 : 0;
    assign {adder_cout , adder_result}= adder_a + adder_b + adder_cin;

    assign add_sub_result = adder_result;

 比较操作可以用减法操作来实现,首先了解下slt指令的含义:

SLT是 MIPS 指令集中的一条指令,用于比较两个寄存器的内容,如果第一个寄存器的值小于第二个寄存器的值,则将目标寄存器的值设置为 1,否则设置为 0。

 可以首先判断两个值的正负(符号位0正1负),如果一正一负那么肯定正大负小,如果都是正载做减法比较,其余位补充0;

    //比较指令
    //slt_result的逻辑表达式求解
    /*
    src1 src2 adder_result  slt_result  表达式
    1    0                  1           src1 & ~src2
    0    1                  0        
    1    1     0            1           ?
    1    1     1            0           ?
    0    0     0            0  
    0    0     1            1          ( ~src1^src2 )&adder_result    
    src1 src2 adder_result  adder_cout        sltu_result  表达式
    0    0    1             0                  1
    0    0    0             1                  0      
    */
    assign slt_result[31:1] = 0;
    assign sltu_result[31:1] = 0;
    assign slt_result[0] = (alu_src1[31] & (~alu_src2[31]))|
                           ((~(alu_src1[31]^alu_src2[31])) & adder_result[31]);
    assign sltu_result[0] = ~adder_cout;

 至于最终的逻辑表达式怎么写,则要利用逻辑代数关系去求解。根据数字电路的知识,只关注另结果等于真的逻辑关系就可以了。

下一节对此alu进行仿真和测试

  • 31
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Verilog单周期MIPS CPU设计是一种基于MIPS指令集架构的CPU设计,采用单周期的执行方式,包括取指、译码、执行、访存和写回等阶段。该设计需要实现MIPS指令集的各种指令,包括算术运算、逻辑运算、分支跳转、访存等操作。同时,还需要考虑CPU的时序控制、数据通路设计、寄存器堆、ALU等模块的实现。该设计需要对Verilog语言有一定的掌握和理解,同时需要对计算机组成原理和数字电路等相关知识有一定的了解。 ### 回答2: Verilog单周期MIPS CPU设计,是一种基于MIPS结构的央处理器的设计,使用Verilog硬件描述语言实现MIPS架构是一种经典的RISC架构,广泛应用于嵌入式系统MIPS CPU设计可以分为指令集架构、流水线结构以及单周期结构三个方面。在单周期结构,每个指令都需要一个时钟周期完成。整个CPU由多个单元组成,包括PC计数器、指令存储器、寄存器组、ALU等。 具体来说,Verilog单周期MIPS CPU设计需要考虑以下几个方面: 1. 指令存储器的设计。指令存储器是存储指令的地方,需要按照MIPS架构格式存储指令,同时需要设计好指令计数器(PC)。 2. 控制器的设计。控制器是CPU的核心部件之一,用于根据指令控制各个单元的操作。在单周期CPU,控制器需要根据指令的类型和操作码生成不同的控制信号。 3. 寄存器组的设计。寄存器组是一个非常重要的部分,需要提供32个通用寄存器,同时需要根据指令设计好数据通路,实现寄存器之间的数据传输。 4. ALU设计ALU是完成算术和逻辑运算的核心部件,需要支持基本的加减乘除、移位、与或非等操作。 5. 数据通路的设计。数据通路将各个单元连接在一起,实现了数据的传输和操作。需要根据指令设计好数据通路,保证指令的正确执行。 在完成以上设计后,需要进行仿真和验证。使用Verilog语言的仿真工具进行验证,确保CPU的性能和正确性。最后,完成物理实现后将MIPS CPU与外设进行连接,实现系统的最终功能。 总之,Verilog单周期MIPS CPU设计是一项复杂而重要的工程,需要深入理解MIPS架构,同时也需要综合运用Verilog的各种知识和技术。它的设计实现对于嵌入式系统的发展有着非常重要的意义。 ### 回答3: Verilog单周期MIPS CPU设计是基于MIPS架构的单周期CPU设计,这种CPU可以实现多种寄存器、指令和内存等常用的微处理器功能。MIPS CPU在计算机硬件领域应用十分广泛。 Verilog单周期MIPS CPU设计可以分为数据通路和控制器两个部分。数据通路包括ALU、存储器、寄存器和地址传输等,控制器则负责产生各种信号来控制CPU的各种行为。 MIPS CPU采用大量寄存器,通过控制器模块控制多路选择器的不同输入,来实现数据传输和指令执行等操作。寄存器制作时需要注意,尽量将读操作和写操作明确分开,避免两者发生竞争引起的问题。 指令的执行则需要根据不同指令的性质进行设置。CPU设计了多路选择器,用于选择正确的指令操作数和操作。再通过ALU进行指令计算,最后将执行结果写回寄存器或者内存。 总体来说,Verilog单周期MIPS CPU设计需要对MIPS指令集进行深入了解,清楚每条指令的功能和使用方法。然后根据逻辑设计知识,使用Verilog语言编写代码实现具体功能。在设计过程,要注意各个模块之间的衔接,确保数据和控制信号的流畅处理。 值得注意的是,完成Verilog单周期MIPS CPU设计只是整个设计的开始。接下来需要进行仿真,调试,并将设计映射到硅片上,进行硬件验证和测试。只有这样才能让设计变成可用的CPU,为人们带来实际应用带来便利。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值