自己动手写CPU_step4_逻辑运算|移位指令

news2025/1/10 18:24:20

上一篇中我们解决了流水线的数据相关问题,本篇将添加多条逻辑运算和移位运算指令,这些指令的格式严格按照MIPS的指令格式设计。


MIPS指令格式

由于本人也是处于学习的一个过程,如有不对之处,还请大牛指正。

就逻辑运算和移位运算两中类型的指令而言,我将他们分为两类,一类是由OP操作码(高6位)决定的指令;另一类是由FUNCTION功能字段(低6位)决定的;如果是由FUNCTION决定的指令,其OP字段必为全0;按照这个规律设计将它们定义出来。


指令定义如下

//R型指令通过OP=6'd0标识
`define EXE_SPECIAL     6'b000000    //R型指令

//I型指令通过OP字段区分
`define EXE_ANDI        6'b001100    //与指令(立即数参与)    rs and imm -> rt
`define EXE_ORI         6'b001101    //或指令(立即数参与)    rs or imm -> rd
`define EXE_XORI        6'b001110    //异或指令(立即数参与)  rs xor imm -> rt
`define EXE_LUI         6'b001111    //立即数扩展指令        {imm,16'h0000} -> rt
//R型(三寄存器)指令的OP字段(高6位)为全0,用func字段(低6位)区分不同指令
`define EXE_AND         6'b100100    //与指令                rs and rt -> rd
`define EXE_OR          6'b100101    //或指令                rs or rt -> rd
`define EXE_XOR         6'b100110    //异或指令              rs xor rt -> rd
`define EXE_NOR         6'b100111    //或非指令              rs nor rt -> rd
`define EXE_SLL         6'b000000   //逻辑左移      rt << shmat -> rd
`define EXE_SLLV        6'b000100   //逻辑左移      rt << rs -> rd
`define EXE_SRL         6'b000010   //逻辑右移      rt >> shmat ->rd
`define EXE_SRLV        6'b000110   //逻辑右移      rt >> rs -> rd
`define EXE_SRA         6'b000011   //算数右移      rt >> shmat -> rd
`define EXE_SRAV        6'b000111   //算术右移      rt >> rs -> rd

//aluop     EX阶段输入的操作码通过增加一位来判断是通过OP还是FUNC来决定指令类型
`define EXE_ORI_OP          7'b1_001101     //首位为1标识使用OP决定指令类型
`define EXE_ANDI_OP         7'b1_001100     
`define EXE_XORI_OP         7'b1_001110    
`define EXE_LUI_OP          7'b1_001111
`define EXE_AND_FUNC        7'b0_100100     //首位为0标识OP段全0使用FUNC字段决定指令类型
`define EXE_OR_FUNC         7'b0_100101
`define EXE_XOR_FUNC        7'b0_100110
`define EXE_NOR_FUNC        7'b0_100111
`define EXE_SLL_FUNC        7'b0_000000
`define EXE_SLLV_FUNC       7'b0_000100
`define EXE_SRL_FUNC        7'b0_000010
`define EXE_SRLV_FUNC       7'b0_000110
`define EXE_SRA_FUNC        7'b0_000011
`define EXE_SRAV_FUNC       7'b0_000111

本次需要设计的指令如上所示。由图可知,我们使用增加一位的方式将这两种类型的指令统一起来传送到执行阶段,这样使得执行阶段能够通过一个统一的接口来实现不同的指令内容。

接下来只需要修改译码部分和执行部分。

译码阶段

//译码阶段  对if_id传入的指令进行译码,分离出操作数和操作码
module id(
    input                           rst,
    input [`InstAddrBus]            pc,
    input [`InstDataBus]            inst,
    //读通用寄存器        读取        
    input [`RegDataBus]             reg1_data,
    input [`RegDataBus]             reg2_data,
    output reg                      reg1_rden,
    output reg                      reg2_rden,
    output reg [`RegAddrBus]        reg1_addr,  //源操作数1的地址
    output reg [`RegAddrBus]        reg2_addr,  //源操作数2的地址
    //送到ex阶段的值
    output reg [`RegDataBus]        reg1,       //源操作数1 32b
    output reg [`RegDataBus]        reg2,       //源操作数2 32b
    output reg                      reg_wb,     //写回目的寄存器标志    
    output reg [`RegAddrBus]        reg_wb_addr,//写回目的寄存器地址
    output reg [`AluOpBus]          aluop,      //操作码    
    //相邻指令的冲突,由EX阶段给出数据旁路
    input                           ex_wr_en,   //处于执行阶段的指令是否要写目的寄存器 
    input [`RegDataBus]             ex_wr_data, 
    input [`RegAddrBus]             ex_wr_addr,  
    //相隔一条指令的冲突,由MEM阶段给出数据旁路
    input                           mem_wr_en,  //处于访存阶段指令是否要写目的寄存器
    input [`RegDataBus]             mem_wr_data,
    input [`RegAddrBus]             mem_wr_addr
    );

/*      ORI指令                               
    31:26   25:21   20:16   15:0
     op      rs      rt      imm
*/

wire [5:0] op = inst[31:26];         //从指令中获取操作码   高6位
wire [5:0] func = inst[5:0];        //从指令中获取功能号确定指令类型   低6位
wire [4:0] shmat = inst[10:6];      //部分移位位数不从寄存器取值,直接由shmat给出
reg [`RegDataBus] imm;              //立即数

always @ (*) begin
    if (rst) begin
        reg1_rden       <= 1'd0;
        reg2_rden       <= 1'd0;
        reg1_addr       <= 5'd0;
        reg2_addr       <= 5'd0;
        imm             <= 32'd0;
        reg_wb          <= 1'd0;
        reg_wb_addr     <= 5'd0;
        aluop           <= 7'd0;
    end else begin
        reg1_rden       <= 1'd0;
        reg2_rden       <= 1'd0;
        reg1_addr       <= inst[25:21];         //默认从指令中读取操作数1地址
        reg2_addr       <= inst[20:16];         //默认从指令中读取操作数2地址
        imm             <= 32'd0; 
        reg_wb          <= 1'd0;
        reg_wb_addr     <= inst[15:11];         //默认结果地址寄存器rd        
        aluop           <= {1'b1,inst[31:26]};  //高6位为操作码
        //操作类型
        if (op == `EXE_SPECIAL) begin
            reg1_rden   <= 1'd1;
            reg2_rden   <= 1'd1;
            reg_wb      <= 1'd1;
            case (func) 
                `EXE_AND:   begin
                        aluop   <=  `EXE_AND_FUNC;
                end            
                `EXE_OR:    begin
                        aluop   <=  `EXE_OR_FUNC;
                end
                `EXE_XOR:   begin
                        aluop   <=  `EXE_XOR_FUNC;
                end
                `EXE_NOR:   begin
                        aluop   <=  `EXE_NOR_FUNC;
                end
                `EXE_SLLV:  begin
                        aluop   <=  `EXE_SLL_FUNC;
                end
                `EXE_SRLV:  begin
                        aluop   <=  `EXE_SRLV_FUNC;
                end
                `EXE_SRAV:  begin
                        aluop   <=  `EXE_SRAV_FUNC;
                end
                `EXE_SLL:   begin
                        reg1_rden   <=  1'd0;
                        imm[4:0]    <=  shmat;
                        aluop       <=  `EXE_SLL_FUNC;
                end
                `EXE_SRL:   begin
                        reg1_rden   <=  1'd0;
                        imm[4:0]    <=  shmat;
                        aluop       <=  `EXE_SRL_FUNC;
                end
                `EXE_SRA:   begin
                        reg1_rden   <=  1'd0;
                        imm[4:0]    <=  shmat;
                        aluop       <=  `EXE_SRA_FUNC;
                end
                default:    
                        aluop       <=  7'd0;
            endcase
        end else begin
            reg1_rden   <= 1'd1;  //需要读取操作数1 rs寄存器的值
            reg2_rden   <= 1'd0;  //不需要读取操作数2 rt寄存器值,
            imm         <= {16'h0, inst[15:0]};   
            reg_wb      <= 1'd1;
            reg_wb_addr <= inst[20:16];
            case (op)
                `EXE_ORI:   begin           //或指令  rs寄存器值是操作数1,imm是操作数2,结果放到rt寄存器
                        aluop   <=  `EXE_ORI_OP;           
                end
                `EXE_ANDI:  begin
                        aluop   <=  `EXE_ANDI_OP;
                end 
                `EXE_XORI:  begin
                        aluop   <=  `EXE_XORI_OP;
                end
                `EXE_LUI:   begin
                        aluop   <=  `EXE_LUI_OP;
                end
                default:
                        aluop   <=  7'd0;    
            endcase
        end
    end
end

always @ (*) begin
    if (rst) begin
        reg1 <= 32'd0;
    end else if (reg1_rden == 1'd1 && ex_wr_en == 1'd1 && ex_wr_addr == reg1_addr) begin    //执行阶段旁路
        reg1 <= ex_wr_data;
    end else if (reg1_rden == 1'd1 && mem_wr_en == 1'd1 && mem_wr_addr == reg1_addr) begin   //访存阶段旁路
        reg1 <= mem_wr_data;
    end else if (reg1_rden == 1'd1) begin   //从通用寄存器获取操作数
        reg1 <= reg1_data;
    end else if (reg1_rden == 1'd0) begin   //从指令中获取操作数
        reg1 <= imm;
    end else begin
        reg1 <= 32'd0;
    end
end
    
always @ (*) begin
    if (rst) begin
        reg2 <= 32'd0;
    end else if (reg2_rden == 1'd1 && ex_wr_en == 1'd1 && ex_wr_addr == reg2_addr) begin    //执行阶段旁路
        reg2 <= ex_wr_data;
    end else if (reg2_rden == 1'd1 && mem_wr_en == 1'd1 && mem_wr_addr == reg2_addr) begin   //访存阶段旁路
        reg2 <= mem_wr_data;
    end else if (reg2_rden == 1'd1) begin   //从通用寄存器获取操作数
        reg2 <= reg2_data;
    end else if (reg2_rden == 1'd0) begin   //从指令中获取操作数
        reg2 <= imm;
    end else begin
        reg2 <= 32'd0;
    end
end
    
endmodule

上图代码主要修改了指令解析部分,若是OP字段全0的指令,说明需要使用三个寄存器,reg1和reg2寄存器的值都需要读取,并且默认目的寄存器就是都是统一的,唯一不同的就是部分移位指令由指令shmat字段给出移位位数而不读取寄存器获取,此时将shmat字段赋给imm,不都寄存器时默认读imm。由OP字段决定的指令和之前篇中的ORI指令实现类似,不再赘述。


执行阶段

//执行阶段,根据译码阶段得到的操作码和操作数进行运算,得到结果
module ex(
    input                       rst,
    input [`AluOpBus]           aluop,
    input [`RegDataBus]         reg1,
    input [`RegDataBus]         reg2,
    input                       reg_wb_i,
    input [`RegAddrBus]         reg_wb_addr_i,
    output reg                  reg_wb_o,
    output reg [`RegAddrBus]    reg_wb_addr_o,
    output reg [`RegDataBus]    reg_wb_data     //写回数据到目的寄存器
    );
    
    always @ (*) begin
        if (rst) begin
            reg_wb_o        <= 1'd0;
            reg_wb_addr_o   <= 5'd0;
            reg_wb_data     <= 32'd0;
        end else begin
            reg_wb_o        <= reg_wb_i;
            reg_wb_addr_o   <= reg_wb_addr_i;
            case (aluop) 
                `EXE_ORI_OP,`EXE_OR_FUNC:       begin
                       reg_wb_data      <=  reg1 | reg2;
                end
                `EXE_ANDI_OP,`EXE_AND_FUNC:     begin
                        reg_wb_data     <=  reg1 & reg2;
                end
                `EXE_XORI_OP,`EXE_XOR_FUNC:     begin
                        reg_wb_data     <=  reg1 ^ reg2;
                end
                `EXE_LUI_OP:                    begin
                        reg_wb_data     <=  {reg2[15:0],reg2[31:16]};
                end
                `EXE_NOR_FUNC:                  begin
                        reg_wb_data     <=  ~(reg1 | reg2);
                end
                `EXE_SLL_FUNC,`EXE_SLLV_FUNC:   begin
                        reg_wb_data     <=  reg2 << reg1[4:0];
                end
                `EXE_SRL_FUNC,`EXE_SRLV_FUNC:   begin
                        reg_wb_data     <=  reg2 >> reg1[4:0];
                end
                `EXE_SRA_FUNC,`EXE_SRAV_FUNC:   begin       //算术移位也可以直接使用>>>
                        reg_wb_data     <=  ({32{reg2[31]}} << (6'd32 - {1'b0,reg1[4:0]})) | reg2 >> reg1[4:0];
                end
                default:    
                        reg_wb_data     <=  32'd0;
            endcase
        end
    end
    
endmodule

仿真测试

inst1:   3401ffff        (001101)ORI       imm | reg0         => reg1;      res:0000_ffff

inst2:   3c02ffff        (001101)LUI        op(imm)             => reg2;     res:ffff_0000

inst3:   3443ffff        (001100)ANDI    imm & reg2        => reg3;      res:ffff_ffff

inst4:   00222024    (100100)AND     reg1 & reg2       => reg4;      res:0000_0000

inst5:   00222825    (100101)OR       reg1 | reg2         => reg5;      res:ffff_ffff

inst6:   00853026    (100110)XOR     reg4 & reg5       => reg6;      res:ffff_ffff

inst7:   00853827    (100111)NOR     ~(reg4 | reg5)    => reg7;      res:0000_0000

inst8:   00054200    (000000)SLL      reg5 << 8           => reg8;      res:ffff_ff00

inst9:   00054e02    (000010)SRL      reg5 >> 24        => reg9;      res:0000_00ff

inst10: 00085103    (000011)SRA      reg8 >> 4          => reg10;     res:ffff_fff0

inst11: 00095903    (000011)SRA      reg9 >> 4          => reg11;     res:0000_000f

inst12: 01656004    (000100)SLLV    reg5 << reg11   => reg12;     res:ffff_8000

inst13: 01656806    (000110)SRLV    reg5 >> reg11   => reg13;     res:0001_ffff

inst14: 01617007    (000111)SRAV    reg1 >>> reg11 => reg14;    res:0000_0001

inst15: 01627807    (000111)SRAV    reg2 >>> reg11 => reg15;    res:ffff_fffe

inst16: 01cf8024     (100100)AND      reg14 & reg15  => reg16;     res:0000_0000

 
仿真结果


由仿真结果可知,写回寄存器的值都是符合我们实现计算结果预期的。

验证了这些指令的正确性。

下一篇进行移动指令的设计!

欢迎对次有兴趣的友友积极交流!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2065253.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【软件逆向】第11课,软件逆向安全工程师之windows API函数,每天5分钟学习逆向吧!

资料获取 关注作者&#xff0c;备注课程编号&#xff0c;获取本课配套课件和工具程序。 干货开始-windows API函数。 微软官方提供的应用程序接口&#xff0c;是一些预先定义的函数&#xff0c;目的是提供应用程序与开发人员基于某软件或硬件提供的能力。 地址&#xff1a;h…

java基础 之 了解final

文章目录 定义使用及规则修饰类修饰方法修饰变量修饰成员变量修饰局部变量final与static共同修饰变量final修饰的变量和普通变量的区别 本篇文章代码就不附上了&#xff0c;建议大家实际敲一敲&#xff0c;更能加快理解 定义 final表示”最后的&#xff0c;最终的“含义&#…

精益思维赋能机器人行业的三大维度

在日新月异的科技浪潮中&#xff0c;机器人行业正以前所未有的速度蓬勃发展&#xff0c;成为推动产业升级与转型的关键力量。然而&#xff0c;如何在激烈的市场竞争中脱颖而出&#xff0c;实现高效、灵活与可持续的发展&#xff1f;精益思维&#xff0c;这一源自制造业的管理哲…

【el-switch更改高、宽、颜色样式】深入浅出element ui的switch同页面存在多个更改样式互不影响

1.技术&#xff1a; “vue”: “^2.6.14”, “element-ui”: “^2.15.6”, 2.需求&#xff1a; 同一个页面存在多个switch组件时&#xff0c; 需要更改各自的高度、宽度、选择颜色、非选中颜色等样式&#xff0c; 并且样式隔离互不影响&#xff01; 3.效果图&#xff1a; 4.重要…

C++动态规划(背包问题)

目录 一&#xff1a;动态规划是什么 二.动态规划的运用 &#xff08;1&#xff09;.用动态规划解决重复子问题 &#xff08;2&#xff09;.动态规划使用的条件与流程 Ⅰ.动态规划的使用条件&#xff1a; Ⅱ.动态规划的使用流程 &#xff08;3&#xff09;.背包问题 三.…

IO进程(学习)2024.8.22

信号 信号函数 信号处理函数 #include <signal.h> sighandler_t signal(int signum, sighandler_t handler); 功能&#xff1a;信号处理函数 参数&#xff1a;signum&#xff1a;要处理的信号 handler&#xff1a;信号处理方式 SIG…

基于矢量控制器的PMSM永磁同步电机速度控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于矢量控制器的PMSM永磁同步电机速度控制系统simulink建模与仿真&#xff0c;仿真输出电机转速跟踪曲线&#xff0c;PID控制器输出曲线以及Te输出曲线。 2.系统仿真结果 &…

kafka的一个有趣问题(BUG)

这是我的第104篇原创文章 问题由来 在使用kafka时&#xff0c;创建topic&#xff0c;对某个topic进行扩分区的操作&#xff0c;想必大家肯定都使用过。尤其是集群进行扩容时&#xff0c;对流量较大的topic进行扩分区操作。一般而言&#xff0c;期望的效果是&#xff1a;新扩的分…

AI在医学领域:HYDEN一种针对医学图像和报告的跨模态表示学习方法

近年来&#xff0c;跨模态文本-图像表示学习在诸多领域取得了显著的突破&#xff0c;尤其是在零样本学习和图像-文本检索等任务上。这一成果的取得很大程度上归功于大量弱监督的图像-文本配对数据的利用&#xff0c;这些数据有效地增强了视觉-语言表示学习的能力。在医学成像领…

如何保证每次生成的都同一张人脸?AI绘画Stable Diffusion的Reference only插件人物一致性教程

Ai绘画有一个很现实的问题&#xff0c;要保证每次画出的都是同一个人物的话&#xff0c;很费劲。 Midjourney就不必说了&#xff0c;人物的高度一致性一直得不到很好的解决。而在Stable Diffusion&#xff08;SD&#xff09;中&#xff0c;常用办法是通过同一个Seed值&#xf…

Linux宝塔面板使用教程 - Centos/Alibaba Cloud Linux,解放命令实现可视化

使用前注意事项&#xff1a;为了您的正常使用&#xff0c;请确保使用全新或纯净的系统安装宝塔面板&#xff0c;不支持已部署项目/环境的系统安装 1.安装命令 yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh &&…

Godot《躲避小兵》实战之创建游戏主场景

游戏主场景 现在是时候将我们所做的一切整合到一个可玩的游戏场景中了。 创建新场景并添加一个 Node节点&#xff0c;命名为 Main。&#xff08;我们之所以使用 Node 而不是 Node2D&#xff0c;是因为这个节点会作为处理游戏逻辑的容器使用。本身是不需要 2D 功能的。&#x…

ZooKeeper 的3种部署模式

ZooKeeper 的3种部署模式 1. 单机模式&#xff08;Standalone Mode&#xff09;2. 伪集群模式&#xff08;Pseudo-Cluster Mode&#xff09;3. 集群模式&#xff08;Cluster Mode&#xff09; &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496;…

[000-01-011].第2节:持久层方案的对比

我的后端学习大纲 MyBatis学习大纲 1.持久层解决方案&#xff1a; 1.1.面试1&#xff1a;请说一说持久层解决方案有哪些&#xff1f;&#xff1f;&#xff1f; 1.jdbc JDBC为访问不同的数据库提供了一种统一的途径&#xff0c;为开发者屏蔽了一些细节问题。Java程序员使用JDB…

Vodafone 推出了与 Wi-Fi 竞争的基于树莓派私人5G技术

随着全球5G网络的逐步推出&#xff0c;在其过程中遇到了可预见的起起伏伏&#xff0c;并且蜂窝技术也开始进入另一个无线技术 Wi-Fi &#xff0c;并且已经占据的市场。私有5G网络&#xff08;即个人或公司建立自己的全设施蜂窝网络&#xff09;如今正在寻找曾经属于Wi-Fi的唯一…

Unity低延迟播放RTSP视频流

Unity播放RTSP视频流这个功能在好几个项目中用到&#xff0c;虽然有一些现成的插件&#xff08;VLC for unity、UMP&#xff09;可以使用&#xff0c;但是延迟高&#xff08;300毫秒以上&#xff09;的问题一直没法解决。 最近终于下定决心来解决这个问题&#xff0c;经过几天…

基于 Jenkins、Gitlab、Harbor、Helm 和 Kubernetes 的 CI/CD

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

【微服务部署】Linux部署微服务启动报ORA-01005

问题背景&#xff1a; Linux机器部署springboot微服务&#xff0c;部署完成后发现无法启动&#xff0c;后台报ORA-01005错误。 解决方案&#xff1a; 1.检查当前服务器是否已安装oracle客户端 命令行执行sqlplus username/passwd实例名&#xff0c;如果执行成功&#xff0c;说…

【Canvas与艺术】十边曲线形光阑

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>十边曲线型光阑</title><style type"text/css"&g…

机器学习-识别手写数字

机器学习可以首先构建一个神经网络&#xff0c;用于识别手写数字。通过训练数据&#xff0c;优化神经网络的参数。再利用测试数据来测试训练完成后的神经网络的准确度。本次需要下载的库有tensorflow和matplotlib&#xff0c;keras和mnist数据集一般都被集成在tensorflow中了。…