自己动手写CPU_step6_算术运算指令

2024-09-02 07:04

本文主要是介绍自己动手写CPU_step6_算术运算指令,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

接上篇,本篇开始实现算数运算指令,包括加减乘除,加减比较好实现,乘除则需要考虑指令周期与其他指令的周期长度不一致问题,可能会导致流水线效率下降,本篇先实现简单的算术运算。


指令定义

`define EXE_ADD         6'b100000   //  rs + rt -> rd(检查溢出)
`define EXE_ADDU        6'b100001   //  rs + rt -> rd(不检查溢出)
`define EXE_SUB         6'b100010   //  rs - rt -> rd
`define EXE_SUBU        6'b100011   //  rs - rt -> rd
`define EXE_SLT         6'b101010   //  (rs < rt) -> rd(比较结果为1/0)
`define EXE_SLTU        6'b101011   //  (rs < rt) -> rd(1/0)
`define EXE_CLZ         6'b100000   //  clz(rs) -> rd(从最高位开始直到遇到1,之前所有0的个数保存至rd)
`define EXE_CLO         6'b100001   //  clo(rs) -> rd(从最高位开始直到遇到0,之前所有1的个数保存至rd)
`define EXE_MUL         6'b000010   //  rs × rt -> rd(保存结果低32位)
`define EXE_MULT        6'b011000   //  rs × rt -> {HI,LO}
`define EXE_MULTU       6'b011001   //  rs × rt -> {HI,LO} 无符号`define EXE_ADDI        6'b001000   //立即数加法             rs add imm -> rt
`define EXE_ADDIU       6'b001001   //无符号立即数加法       rs add imm -> rt
`define EXE_SLTI        6'b001010   //立即数比较指令         (rs < imm) -> rt(1/0)
`define EXE_SLTIU       6'b001011   //无符号立即数比较       (rs < imm) -> rt(1/0)

上面的是R型指令,下面的是I型指令

简单的算术运算指令和之前篇的额添加指令其实是一样的,只需要修改EX模块和ID模块即可。


ID模块

`include "defines.v"
//译码阶段  对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 32boutput reg [`RegDataBus]        reg2,       //源操作数2 32boutput 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);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 @ (*) beginif (rst) beginreg1_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 beginreg1_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           <= 7'd0;                //操作类型if (op == `EXE_SPECIAL) beginreg1_rden   <= 1'd1;reg2_rden   <= 1'd1;reg_wb      <= 1'd1;case (func) `EXE_AND:   beginaluop   <=  `EXE_AND_FUNC;end            `EXE_OR:    beginaluop   <=  `EXE_OR_FUNC;end`EXE_XOR:   beginaluop   <=  `EXE_XOR_FUNC;end`EXE_NOR:   beginaluop   <=  `EXE_NOR_FUNC;end`EXE_SLLV:  beginaluop   <=  `EXE_SLL_FUNC;end`EXE_SRLV:  beginaluop   <=  `EXE_SRLV_FUNC;end`EXE_SRAV:  beginaluop   <=  `EXE_SRAV_FUNC;end`EXE_SLL:   beginreg1_rden   <=  1'd0;imm[4:0]    <=  shmat;aluop       <=  `EXE_SLL_FUNC;end`EXE_SRL:   beginreg1_rden   <=  1'd0;imm[4:0]    <=  shmat;aluop       <=  `EXE_SRL_FUNC;end`EXE_SRA:   beginreg1_rden   <=  1'd0;imm[4:0]    <=  shmat;aluop       <=  `EXE_SRA_FUNC;end`EXE_MOVN:  beginif (reg2 == 32'd0)  beginreg_wb  <=  1'b0;end else beginreg_wb  <=  1'b1;aluop   <=  `EXE_MOVN_FUNC;endend`EXE_MOVZ:  beginif (reg2 == 32'd0)  beginreg_wb  <=  1'b1;aluop   <=  `EXE_MOVZ_FUNC;end else beginreg_wb  <=  1'b0;endend`EXE_MFHI:  beginreg1_rden   <=  1'b0;reg2_rden   <=  1'b0;aluop       <=  `EXE_MFHI_FUNC;end`EXE_MFLO:  beginreg1_rden   <=  1'b0;reg2_rden   <=  1'b0;aluop       <=  `EXE_MFLO_FUNC; end`EXE_MTHI:  beginreg2_rden   <=  1'b0;reg_wb      <=  1'b0;aluop       <=  `EXE_MTHI_FUNC;end`EXE_MTLO:  beginreg2_rden   <=  1'b0;reg_wb      <=  1'b0;aluop       <=  `EXE_MTLO_FUNC;end`EXE_ADD:   beginaluop   <=  `EXE_ADD_FUNC;end`EXE_ADDU:  beginaluop   <=  `EXE_ADDU_FUNC;end`EXE_SUB:   beginaluop   <=  `EXE_SUB_FUNC;end`EXE_SUBU:  beginaluop   <=  `EXE_SUBU_FUNC;enddefault:    aluop       <=  7'd0;endcaseend else if (op == `EXE_SPECIAL2) begin     //由FUNC字段决定,操作码为011100的指令reg1_rden   <=  1'd1;reg2_rden   <=  1'd1;reg_wb      <=  1'd1;case (op)default:    beginreg1_rden   <=  1'd0;reg2_rden   <=  1'd0;reg_wb      <=  1'd0;endendcaseend else beginreg1_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:  beginaluop   <=  `EXE_ANDI_OP;end `EXE_XORI:  beginaluop   <=  `EXE_XORI_OP;end`EXE_LUI:   beginreg1_rden   <= 1'b0;aluop   <=  `EXE_LUI_OP;end`EXE_ADDI:  beginaluop   <=  `EXE_ADDI_OP;end`EXE_ADDIU: beginaluop   <=  `EXE_ADDIU_OP;enddefault:aluop   <=  7'd0;    endcaseendend
endalways @ (*) beginif (rst) beginreg1 <= 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 beginreg1 <= 32'd0;end
endalways @ (*) beginif (rst) beginreg2 <= 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 beginreg2 <= 32'd0;end
endendmodule

本篇,我们只添加实现6条指令代码,分别是有符号加减、无符号加减、立即数有符号加和立即数无符号加法运算。鉴于之前给的主要是代码和结果,不太好理解,本篇开始一步一步的进行仿真,尽量把步骤详细写出来。


EX模块

`include "defines.v"
//执行阶段,根据译码阶段得到的操作码和操作数进行运算,得到结果
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,    //写回数据到目的寄存器//HILO寄存器input [`RegDataBus]         hi_reg_i,       //读取HI寄存器数据input [`RegDataBus]         lo_reg_i,       //读取LO寄存器数据output reg [`RegDataBus]    hi_reg_o,       //写入HI寄存器数据output reg [`RegDataBus]    lo_reg_o,       //写入LO寄存器数据output reg                  hi_wren,        //HI寄存器写使能        output reg                  lo_wren,        //LO寄存器写使能        //  HILO寄存器旁路                          input [`RegDataBus]         wb_hi_i,input [`RegDataBus]         wb_lo_i,input                       wb_hi_wren_i,  //有指令写HI,从写回阶段给出旁路(隔一条指令)input                       wb_lo_wren_i,  //有指令写LO,从写回阶段给出旁路(隔一条指令)input [`RegDataBus]         mem_hi_i,input [`RegDataBus]         mem_lo_i,input                       mem_hi_wren_i,  //有指令写HI,从访存阶段给出旁路(上一条指令)input                       mem_lo_wren_i  //有指令写LO,从访存阶段给出旁路(上一条指令));//需要转换成补码的指令
wire        reg2_mux   =   ((aluop==`EXE_SUB_FUNC)||(aluop==`EXE_SUBU_FUNC)||(aluop==`EXE_SLT_FUNC)) ? (~reg2+1) : reg2;
wire [31:0] res        =   reg1 + reg2_mux;    //判断加减的结果是否溢出,减法转换成加法
//overflow flag 两个正数相加得负或两个负数相加得正则溢出
wire        of         =   ((!reg1[31]&&!reg2[31]&&res[31])||(reg1[31]&&reg2[31]&&!res[31])); always @ (*) beginif (rst) beginreg_wb_o        <= 1'd0;reg_wb_addr_o   <= 5'd0;reg_wb_data     <= 32'd0;hi_reg_o        <= 32'd0;lo_reg_o        <= 32'd0;hi_wren         <= 1'b0;lo_wren         <= 1'b0;end else beginreg_wb_o        <= reg_wb_i;reg_wb_addr_o   <= reg_wb_addr_i;reg_wb_data     <= 32'd0;hi_wren         <= 1'b0;lo_wren         <= 1'b0;hi_reg_o        <= 32'd0;lo_reg_o        <= 32'd0;case (aluop) `EXE_ORI_OP,`EXE_OR_FUNC:       beginreg_wb_data      <=  reg1 | reg2;end`EXE_ANDI_OP,`EXE_AND_FUNC:     beginreg_wb_data     <=  reg1 & reg2;end`EXE_XORI_OP,`EXE_XOR_FUNC:     beginreg_wb_data     <=  reg1 ^ reg2;end`EXE_LUI_OP:                    beginreg_wb_data     <=  {reg2[15:0],reg2[31:16]};end`EXE_NOR_FUNC:                  beginreg_wb_data     <=  ~(reg1 | reg2);end`EXE_SLL_FUNC,`EXE_SLLV_FUNC:   beginreg_wb_data     <=  reg2 << reg1[4:0];end`EXE_SRL_FUNC,`EXE_SRLV_FUNC:   beginreg_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`EXE_MOVN_FUNC,`EXE_MOVZ_FUNC:  beginreg_wb_data     <=  reg1;end`EXE_MFHI_FUNC:                 beginif (mem_hi_wren_i) begin            //访存阶段数据旁路reg_wb_data     <=  mem_hi_i;end else if (wb_hi_wren_i) begin    //写回阶段数据旁路reg_wb_data     <=  wb_hi_i;end else beginreg_wb_data     <=  hi_reg_i;   //正常读取HI寄存器endend`EXE_MFLO_FUNC:                 beginif (mem_lo_wren_i) begin            //旁路reg_wb_data     <=  mem_lo_i;end else if (wb_lo_wren_i) begin    //旁路reg_wb_data     <=  wb_lo_i;end else begin                      //正常读取LO寄存器reg_wb_data     <=  lo_reg_i;endend`EXE_MTHI_FUNC:                 beginhi_wren         <=  1'b1;hi_reg_o        <=  reg1;lo_reg_o        <=  lo_reg_i;end`EXE_MTLO_FUNC:                 beginlo_wren         <=  1'b1;lo_reg_o        <=  reg1;hi_reg_o        <=  hi_reg_i;end`EXE_ADD_FUNC,`EXE_SUB_FUNC,`EXE_ADDI_OP:                   begin       //加法减法都是加法实现reg_wb_data     <=  res;if (of) beginreg_wb_o    <=  1'd0;end else begin  reg_wb_o    <=  1'd1;endend`EXE_ADDU_FUNC,`EXE_SUBU_FUNC,`EXE_ADDIU_OP:                  begin       //无符号数无需判断溢出,直接截断保存reg_wb_data     <=  res;enddefault:                        beginreg_wb_o        <= 1'd0;reg_wb_addr_o   <= 5'd0;reg_wb_data     <= 32'd0;hi_reg_o        <= 32'd0;lo_reg_o        <= 32'd0;hi_wren         <= 1'b0;lo_wren         <= 1'b0;endendcaseendendendmodule

有符号加减时需要判断溢出,如果溢出的话,就不讲结果写回目的寄存器;无符号加减则不需要考虑溢出问题,直接高位截断即可。


仿真测试1

3c018000:        LUI        reg1=>8000,0000
3c028000:        LUI        reg2=>8000,0000
34210001:        ORI       reg1=>8000,0001
34420002:        ORI       reg2=>8000,0002
00221820:        ADD      reg3=>溢出,无结果
00222021:        ADDU    reg4=>0000,0003 截断

有测试结果可知,ADD指令的有符号和无符号指令正确执行,在第五个时钟周期得到结果。


仿真测试2

00242822:        SUB        reg1 - reg3 => reg4 溢出无结果
00243023:        SUBU      reg1 - reg3 => reg5 无符号截断 8000,0001+(3)补码        

有测试结果可知,这两条减法指令也是正确无误。


加减指令都测试无误,下一篇将介绍其他指令的设计与测试!

这篇关于自己动手写CPU_step6_算术运算指令的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1129323

相关文章

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

uva 575 Skew Binary(位运算)

求第一个以(2^(k+1)-1)为进制的数。 数据不大,可以直接搞。 代码: #include <stdio.h>#include <string.h>const int maxn = 100 + 5;int main(){char num[maxn];while (scanf("%s", num) == 1){if (num[0] == '0')break;int len =

工作常用指令与快捷键

Git提交代码 git fetch  git add .  git commit -m “desc”  git pull  git push Git查看当前分支 git symbolic-ref --short -q HEAD Git创建新的分支并切换 git checkout -b XXXXXXXXXXXXXX git push origin XXXXXXXXXXXXXX

动手学深度学习【数据操作+数据预处理】

import osos.makedirs(os.path.join('.', 'data'), exist_ok=True)data_file = os.path.join('.', 'data', 'house_tiny.csv')with open(data_file, 'w') as f:f.write('NumRooms,Alley,Price\n') # 列名f.write('NA

Android中如何实现adb向应用发送特定指令并接收返回

1 ADB发送命令给应用 1.1 发送自定义广播给系统或应用 adb shell am broadcast 是 Android Debug Bridge (ADB) 中用于向 Android 系统发送广播的命令。通过这个命令,开发者可以发送自定义广播给系统或应用,触发应用中的广播接收器(BroadcastReceiver)。广播机制是 Android 的一种组件通信方式,应用可以监听广播来执行

【Java中的位运算和逻辑运算详解及其区别】

Java中的位运算和逻辑运算详解及其区别 在 Java 编程中,位运算和逻辑运算是常见的两种操作类型。位运算用于操作整数的二进制位,而逻辑运算则是处理布尔值 (boolean) 的运算。本文将详细讲解这两种运算及其主要区别,并给出相应示例。 应用场景了解 位运算和逻辑运算的设计初衷源自计算机底层硬件和逻辑运算的需求,它们分别针对不同的处理对象和场景。以下是它们设计的初始目的简介:

Java程序到CPU上执行 的步骤

相信很多的小伙伴在最初学习编程的时候会容易产生一个疑惑❓,那就是编写的Java代码究竟是怎么一步一步到CPU上去执行的呢?CPU又是如何执行的呢?今天跟随小编的脚步去化解开这个疑惑❓。 在学习这个过程之前,我们需要先讲解一些与本内容相关的知识点 指令 指令是指导CPU运行的命令,主要由操作码+被操作数组成。 其中操作码用来表示要做什么动作,被操作数是本条指令要操作的数据,可能是内存地址,也

位运算:带带孩子吧,孩子很强的!

快速进制 在聊到位运算之前,不妨先简单过一遍二进制的东西。熟悉二进制和十进制的快速转换确实是掌握位运算的基础,因为位运算直接在二进制位上进行操作。如果不熟悉二进制表示,很难直观理解位运算的效果。 这里主要涉及二进制和十进制之间的互相转换。 十进制转二进制 十进制转二进制可以使用常见的 除2取余法 进行。每次将十进制除以2并记录所得余数,直到商为0,然后再将记录的余数 从下往上排列即

mysql中导入txt文件数据的操作指令

1 表tt的格式:    CREATE TABLE `tt` (   `ind` int NOT NULL auto_increment,   `name` char(100) default NULL,   PRIMARY KEY  (`ind`)  )   2 文件d.txt的内容示例:  1,a  2,b  3,c