Featured image of post 北航CO P4 单周期CPU的verilog搭建

北航CO P4 单周期CPU的verilog搭建

P4课下搭建任务


设计文档

前情提要

本次搭建需要实现的指令有

  • add,sum,ori,lui
  • sw,lw
  • beq
  • jal,jr
  • nop

在此基础之上,本人额外添加了lh,lb,sh,sb,j指令。

verilog的基本构建思路源始于logisim,Tunnel化的logisim电路为verilog代码的编写提供了很大的便利和可扩展性。

verilog的编写仍然从数据通路基础部件和控制器两方面来书写,不过额外的是,在mips.v中应当实现所有的基础接线操作。

或者说,mips.v是最独特的一环。

一切源代码仅供参考,千万不要抄袭!!

数据通路的构建

PC

PC作为CPU中的时序电路核心器件,其功能实现也较为简单,只有同步复位操作和npc赋值操作。

直接由logisim文件分析PC模块的接口

名称 位宽 方向
npc 32 I
clk 1 I
reset 1 I
pc 32 O
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
`default_nettype none
module PC(
    input wire [31:0] npc,
    input wire clk,
    input wire reset,
    output wire [31:0] pc
    );

reg [31:0] PCreg;

always @(posedge clk) begin
    if (reset) begin
        PCreg <= 32'h00003000;
    end
    else begin
        PCreg <= npc;
    end
end

assign pc = PCreg;
endmodule

NPC

NPC与P3不同,加入了jal,jr指令,使得其增加了两种全新跳转方式:

  • 26位立即数经位拼接得到直接地址
  • 32为GRF数据得到直接地址

再加上原来的pc+4与pc+4+sign_ext(imm)

共有四种跳转逻辑,需要较多的控制信号

br 和 zero 控制 相对寻址跳转
j 控制imm_26绝对寻址跳转
jr 控制$ra绝对寻址跳转

接口信息如下:

名称 位宽 方向
pc 32 I
imm_16 16 I
imm_26 26 I
reg_ra 32 I
br 1 I
zero 1 I
j 1 I
jr 1 I
pc_4 32 O
npc 32 O
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
`default_nettype none
module NPC(
    input wire br,
    input wire zero,
    input wire [15:0] imm_16,
    input wire [25:0] imm_26,
    input wire [31:0] pc,
    input wire [31:0] reg_ra,
    input wire j,
    input wire jr,
    output wire [31:0] pc_4,
    output wire [31:0] npc
    );

wire [31:0] j_pc;
wire [31:0] br_pc;

assign pc_4 = pc + 32'd4;
assign j_pc = {pc[31:28],imm_26,{2{1'b0}}};
assign br_pc = pc_4 + {{15{imm_16[15]}},imm_16[14:0],{2{1'b0}}};

assign npc = (jr) ? reg_ra :
             (j)  ? j_pc :
             (br && zero) ? br_pc : pc_4;

endmodule

IM

IM作为需要读取指令的单位,需要调用readmemh
为了保证读取的正确顺序,定义寄存器堆时使用[0:4095]

也可以添加参数控制readmemh读取到的起始位置与终止位置

由于该指令寄存器堆应当存储4096*32bit
然而起始位置却是0x00003000 因此可以先对pc减去0x00003000
再取[13:2]作为指令寄存器堆的读取地址

名称 位宽 方向
pc 32 I
reset 1 I
clk 1 I
Instruction 32 O
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
`default_nettype none
module IM(
    input wire [31:0] pc,
    input wire reset,
    input wire clk,
    output wire [31:0] Instruction
    );
wire [31:0] address;
reg [31:0] InstrMemory [0:4095];
always @(posedge clk) begin
    if(reset) begin
        $readmemh("code.txt", InstrMemory);
    end
end

assign address = pc - 32'h00003000;
assign Instruction = InstrMemory[address[13:2]];
endmodule

EXT

注意位拼接操作的写法!!不能少打大括号

位拼接“0”时,一定要控制有多少个0

例如16个“0”,应使用{16{1’b0}}或{16’b0}

名称 位宽 方向
EXTOp 1 I
imm_16 16 I
IMM_32 32 O
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
`default_nettype none
module EXT(
    input wire EXTOp,
    input wire [15:0] imm_16,
    output wire [31:0] IMM_32
    );
wire [31:0] signed_IMM;
wire [31:0] unsigned_IMM;

assign signed_IMM = {{16{imm_16[15]}},imm_16};
assign unsigned_IMM = {{16{1'b0}},imm_16};

assign IMM_32 = EXTOp ? signed_IMM : unsigned_IMM;
endmodule

GRF

GRF的最特殊性在于其0号寄存器

  • 值不能被改变
  • 始终值为0

在此有两种处理方案

  1. 初始时为所有寄存器赋0
    之后不对0号寄存器进行写入
  2. 0号寄存器时始终输出0

由于个人偏好,希望各个寄存器中的值保持正确,本人采用了第一种写法

根据题目要求,需要输出寄存器被写时的各项参数,需要将pc传入其中

接口如下所示:

名称 位宽 方向
pc 32 I
reset 1 I
clk 1 I
WE 1 I
A1 5 I
A2 5 I
A3 5 I
WD 32 I
RD1 32 O
RD2 32 O
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
`default_nettype none
module GRF(
    input wire [31:0] pc,
    input wire [4:0] A1,
    input wire [4:0] A2,
    input wire [4:0] A3,
    input wire [31:0] WD,
    input wire reset,
    input wire clk,
    input wire WE,
    output wire [31:0] RD1,
    output wire [31:0] RD2
    );

reg [31:0] grf [31:0];
integer i;

always @(posedge clk) begin
    if (reset) begin
        for (i = 0; i < 32;i = i + 1) begin
            grf[i] <= 32'b0;
        end
    end
    else begin
        if(WE && A3) begin
            grf[A3] <= WD;
            $display("@%h: $%d <= %h", pc, A3, WD);
        end
    end
end

assign RD1 = grf[A1];
assign RD2 = grf[A2];

endmodule

ALU

实现基本与logisim同理,但是操作更为简单
注意使用`define以增强代码可读性

名称 位宽 方向
ALUOp 2 I
num_1 32 I
num_2 32 I
zero 1 O
out 32 O
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
`default_nettype none
`define ADD 2'b00
`define SUB 2'b01
`define ORI 2'b10
`define LUI 2'b11
module ALU(
    input wire [1:0] ALUOp,
    input wire [31:0] num_1,
    input wire [31:0] num_2,
    output wire zero,
    output wire [31:0] out
    );

wire [31:0] add;
wire [31:0] sub;
wire [31:0] ori;
wire [31:0] lui;

assign add = num_1 + num_2;
assign sub = num_1 - num_2;
assign ori = num_1 | num_2;
assign lui = {num_2[15:0],{16{1'b0}}};

assign zero = (num_1 == num_2) ? 1'b1 : 1'b0;

assign out = (ALUOp == `ADD) ? add :
             (ALUOp == `SUB) ? sub :
             (ALUOp == `ORI) ? ori : lui;
endmodule

DM

仅对于lw和sw而言,DM本应是极为简单的。
但是正因为sh,sb的特殊性,我尝试了对其功能的描述

sh,sb指令的核心问题,在于我们不能直接做到针对一个字部分内容的修改
因为我们的寄存器都是以32bit为单位的
因此我们要先把它所在寄存器的值取出来,进行位拼接操作得到该寄存器应存入的新数,再存入DM之中

由于添加了指令,我的WE与RE信号都进行了调整

控制信号取值 含义
00 以w为单位
01 以h为单位
10 以b为单位
11 不予使能

同样地,为了输出中间参数,pc需要被引入。

名称 位宽 方向
pc 32 I
data 32 I
addr 32 I
WE 2 I
RE 2 I
clk 1 I
reset 1 I
out 32 O

我还添加了word,half,by_te等中间变量,便于我后续的描述

名称 位宽 方向
pc 32 I
data 32 I
addr 32 I
WE 2 I
RE 2 I
clk 1 I
reset 1 I
out 32 O
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
`default_nettype none
`define SW 2'b00
`define SH 2'b01
`define SB 2'b10

`define LW 2'b00
`define LH 2'b01
`define LB 2'b10

module DM(
    input wire [31:0] pc,
    input wire [31:0] data,
    input wire [31:0] addr,
    input wire [1:0] WE,
    input wire [1:0] RE,
    input wire clk,
    input wire reset,
    output wire [31:0] out
    );

reg [31:0] DataMemory [0:3071];
integer i;
wire [11:0] word;
wire half;
wire [1:0] by_te;

assign word = addr[13:2];
assign by_te = addr[1:0];
assign half = addr[1];

always @(posedge clk) begin
    if (reset) begin
        for (i = 0; i < 3072; i = i + 1) begin
            DataMemory[i] <= 32'b0;
        end
    end
    else begin
        case (WE)
            `SW: begin
                DataMemory[word] <= data;
                $display("@%h: *%h <= %h", pc, addr, data);
            end
            `SH: begin
                case (half)
                    1'b1: begin
                        DataMemory[word] <= {data[15:0], DataMemory[word][15:0]};
                    end
                    1'b0: begin
                        DataMemory[word] <= {DataMemory[word][31:16], data[15:0]};
                    end
                    default: begin
                        
                    end
                endcase
            end
            `SB: begin
                case (by_te)
                    2'b11: begin
                        DataMemory[word] <= {data[7:0], DataMemory[word][23:0]};
                    end
                    2'b10: begin
                        DataMemory[word] <= {DataMemory[word][31:24], data[7:0], DataMemory[word][15:0]};
                    end
                    2'b01: begin
                        DataMemory[word] <= {DataMemory[word][31:16], data[7:0], DataMemory[word][7:0]};
                    end
                    2'b00: begin
                        DataMemory[word] <= {DataMemory[word][31:8], data[7:0]};
                    end
                    default: begin
                        
                    end
                endcase
            end
            default: begin
                
            end
        endcase
    end
end

assign out = (RE == `LW) ? DataMemory[word] :
             (RE == `LH && half == 1'b1) ? {{16{DataMemory[word][31]}},DataMemory[word][31:16]} :
             (RE == `LH && half == 1'b0) ? {{16{DataMemory[word][15]}}, DataMemory[word][15:0]} :
             (RE == `LB && by_te == 2'b11) ? {{24{DataMemory[word][31]}}, DataMemory[word][31:24]} :
             (RE == `LB && by_te == 2'b10) ? {{24{DataMemory[word][23]}}, DataMemory[word][23:16]} :
             (RE == `LB && by_te == 2'b01) ? {{24{DataMemory[word][15]}}, DataMemory[word][15:8]} :
             (RE == `LB && by_te == 2'b00) ? {{24{DataMemory[word][7]}}, DataMemory[word][7:0]} : 32'b0;
endmodule

控制器的编写

在搭建数据通路的过程中,控制信号的要求也已经浮出水面。
关键还是由IM到指令,由指令到控制信号的过程

不过加入了其他操作,使得DMWr,DMRd发生了一定的改变 以及额外添加的Link,J,Jr等控制信号

相较于P3的BSel,此处本人将其更名为ImmSel,强调其解决的是立即数与寄存器谁参与ALU运算的问题

名称 位宽 方向
opcode 6 I
funct 6 I
Br 1 O
EXTOp 1 O
RFWr 1 O
DMWr 2 O
DMRd 2 O
ALUOp 2 O
WrSel 1 O
WdSel 1 O
ImmSel 1 O
Link 1 O
J 1 O
Jr 1 O
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
`default_nettype none
module CTRL(
    input wire [5:0] opcode,
    input wire [5:0] funct,
    output wire Br,
    output wire EXTOp,
    output wire RFWr,
    output wire [1:0] DMWr,
    output wire [1:0] DMRd,
    output wire [1:0] ALUOp,
    output wire WrSel,
    output wire WdSel,
    output wire ImmSel,
    output wire Link,
    output wire J,
    output wire Jr
    );

wire add;
wire sub;
wire ori;
wire lui;
wire sw;
wire sh;
wire sb;
wire lw;
wire lh;
wire lb;
wire beq;
wire j;
wire jal;
wire jr;
wire R;

assign R = (opcode == 6'b000000);
assign add = R && (funct == 6'b100000);
assign sub = R && (funct == 6'b100010);
assign jr = R && (funct == 6'b001000);
assign j = (opcode == 6'b000010);
assign jal = (opcode == 6'b000011);
assign ori = (opcode == 6'b001101);
assign lui = (opcode == 6'b001111);
assign sw = (opcode == 6'b101011);
assign sh = (opcode == 6'b101001);
assign sb = (opcode == 6'b101000);
assign lw = (opcode == 6'b100011);
assign lh = (opcode == 6'b100001);
assign lb = (opcode == 6'b100000);
assign beq = (opcode == 6'b000100);

assign Br = beq;
assign Jr = jr;
assign Link = jal;
assign J = jal || j;
assign WdSel = lw || lh || lb;
assign WrSel = add || sub;
assign ImmSel = ori || lui || sw || sh || sb || lw || lh || lb;
assign EXTOp = sw || sh || sb || lw || lh || lb;
assign ALUOp = sub ? 2'b01 :
               ori ? 2'b10 :
               lui ? 2'b11 : 2'b00;

assign RFWr = add || sub || ori || lui || lw || lb || lh || jal;

assign DMRd = lw ? 2'b00 :
              lh ? 2'b01 :
              lb ? 2'b10 : 2'b11;

assign DMWr = sw ? 2'b00 :
              sh ? 2'b01 :
              sb ? 2'b10 : 2'b11;

endmodule

mips.v

前面说了那么多,其实都是相对独立的结构
那么接下来,才是verilog描述的独特之处
才能更深刻地展示verilog的抽象功能

接口十分简单,毕竟真正来自外部的信号只有clk和reset

名称 位宽 方向
clk 1 I
reset 1 I
1
2
3
4
5
`default_nettype none
module mips(
    input wire clk,
    input wire reset
    );

接线逻辑

在我书写这一板块的过程中,我曾选用了将所有的wire先定义好,再全部统一赋值,再逐个接接口的方法

结果这样导致的问题(或者说我曾经出现的bug)
就是你不能确定你需要的信号都得到了定义与赋值

因此,我秉承了P3的基本思路,对各个部件的外部接线进行分析。

分析方法:

  1. 先定义所有需要的输出信号
  2. 输入信号分为
    • 直接与其他某部件的输出信号直接相连
    • 由其他部件的输出信号相互作用(计算or选择)得到
  3. 为这些相互作用进行assign处理

基础信号

名义上如此,其实这是在模拟我没有模块化的Splitter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 基础信号定义
wire [5:0] opcode;
wire [4:0] rs;
wire [4:0] rt;
wire [4:0] rd;
wire [4:0] shamt;
wire [5:0] funct;
assign opcode = Instr[31:26];
assign rs = Instr[25:21];
assign rt = Instr[20:16];
assign rd = Instr[15:11];
assign shamt = Instr[10:6];
assign funct = Instr[5:0];

wire [15:0] imm_16;
wire [25:0] imm_26;

assign imm_16 = Instr[15:0];
assign imm_26 = Instr[25:0];

CTRL

定义大量控制信号

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//控制信号定义
wire Br;
wire EXTOp;
wire RegWrite;
wire [1:0] DMWr;
wire [1:0] DMRd;
wire [1:0] ALUOp;
wire WrSel;
wire WdSel;
wire ImmSel;
wire Link;
wire J;
wire Jr;

CTRL Ctrl(
    .opcode(opcode),
    .funct(funct),
    .Br(Br),
    .EXTOp(EXTOp),
    .RFWr(RegWrite),
    .DMWr(DMWr),
    .DMRd(DMRd),
    .ALUOp(ALUOp),
    .WrSel(WrSel),
    .WdSel(WdSel),
    .ImmSel(ImmSel),
    .Link(Link),
    .J(J),
    .Jr(Jr)
);

PC

定义输出信号pc

1
2
3
4
5
6
7
8
// PC 输出信号
wire [31:0] pc;
PC Pc(
    .npc(npc),
    .clk(clk),
    .reset(reset),
    .pc(pc)
);

IM

定义输出信号Instr

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// IM输出信号

wire [31:0] Instr;

IM Im(
    .pc(pc),
    .Instruction(Instr),
    .clk(clk),
    .reset(reset)
);

EXT

定义输出信号IMM_32

1
2
3
4
5
6
7
8
// EXT输出信号
wire [31:0] IMM_32;

EXT Ext(
    .EXTOp(EXTOp),
    .imm_16(imm_16),
    .IMM_32(IMM_32)
);

NPC

定义输出信号npc和pc_4

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// NPC输出信号
wire [31:0] npc;
wire [31:0] pc_4;

NPC Npc(
    .br(Br),
    .zero(zero),
    .imm_16(imm_16),
    .imm_26(imm_26),
    .pc(pc),
    .reg_ra(RD1),
    .j(J),
    .jr(Jr),
    .pc_4(pc_4),
    .npc(npc)
);

GRF

定义输出信号RD1,RD2
定义输入信号,并给出其assign语句实现赋值
要知道,输入信号一定要从其他部件的输出信号或者外部的clk 与reset中得到
如果盲目定义输入信号,却不予有意义的赋值,就会产生xxxzzz等问题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// GRF 输入信号·中间变量
wire [31:0] LinkPC;
wire [4:0] A1;
wire [4:0] A2;
wire [4:0] RegAddr;
wire [31:0] RegData;

// GRF 中间变量的赋值
assign LinkPC = pc_4;
assign A1 = rs;
assign A2 = rt;
assign RegAddr = Link ? 5'b11111 :
                 WrSel ? rd : rt;    
assign RegData = Link ? LinkPC :
                 WdSel ? MemReadData : ALUData;


// GRF 输出信号
wire [31:0] RD1;
wire [31:0] RD2;

GRF Grf(
    .clk(clk),
    .reset(reset),
    .WE(RegWrite),
    .pc(pc),
    .A1(A1),
    .A2(A2),
    .A3(RegAddr),
    .WD(RegData),
    .RD1(RD1),
    .RD2(RD2)
);

ALU

输出信号zero和ALUData
定义输入信号num_1,num_2并给出assign

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// ALU 输入信号·中间变量
wire [31:0] num_1;
wire [31:0] num_2;
// ALU 中间变量的赋值
assign num_1 = RD1;
assign num_2 = ImmSel ? IMM_32 : RD2;
// ALU 输出信号
wire zero;
wire [31:0] ALUData;

ALU Alu(
    .ALUOp(ALUOp),
    .num_1(num_1),
    .num_2(num_2),
    .zero(zero),
    .out(ALUData)
);

DM

这两个输入变量的赋值是否没有意义?
MemAddr一定是ALUData?
MemData一定是RD2?
可能确实如此。
但是这样的命名使得整体逻辑更为清晰,也增强了可扩展性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// DM 输入信号·中间变量
wire [31:0] MemAddr;
wire [31:0] MemData;
// DM 中间变量的赋值
assign MemAddr = ALUData;
assign MemData = RD2;
// DM 输出信号
wire [31:0] MemReadData;

DM Dm(
    .pc(pc),
    .data(MemData),
    .addr(MemAddr),
    .WE(DMWr),
    .RE(DMRd),
    .clk(clk),
    .reset(reset),
    .out(MemReadData)
);
endmodule

测试文档

我仍然采用了对拍的方法,并自行构造了一定数据。

对于新增指令,我着重加强了对jr,jal的测试

以下是一组测试数据,重点观察$ra取值的改变是否正常

1
2
3
4
5
6
7
8
9
0c000c06 // jal label
341f0000 //ori $ra, $0, 0
0c000c06 //jal label
341f0000 //ori $ra, $0, 0
0c000c06 //jal label
341f301c //ori $ra, $0, 0x0000301c
//label:
03e00008 //jr $ra
03fff820 //add $ra, $ra, $ra

可知$ra会在pc+4和0不断跳转
直到变为0x0000301c后跳转到add语句变为0x00006038

下面是我的输出结果,与MIPS结果一致

1
2
3
4
5
6
7
@00003000: $31 <= 00003004
@00003004: $31 <= 00000000
@00003008: $31 <= 0000300c
@0000300c: $31 <= 00000000
@00003010: $31 <= 00003014
@00003014: $31 <= 0000301c
@0000301c: $31 <= 00006038

思考题

阅读下面给出的 DM 的输入示例中(示例 DM 容量为 4KB,即 32bit × 1024字),根据你的理解回答,这个 addr 信号又是从哪里来的?地址信号 addr 位数为什么是 [11:2] 而不是 [9:0] ?

addr信号来自于ALU的计算,即GRF[base]+sign_ext(offset)
其中addr[11:2]意为取ALU计算结果从第2位开始的12位数字作为address。
其原因在于对于lw指令,第0位和第1位均为0,自第2位起开始计数才是以32bit为单位,才能与DM寄存器堆的寄存器编号相对应。

思考两种控制器设计的译码方式,给出代码示例,并尝试对比各方式的优劣。

控制信号每种取值所对应的指令: 举例:

1
2
3
4
5
6
7
assign Br = beq;
assign Jr = jr;
assign Link = jal;
assign J = jal || j;
assign WdSel = lw || lh || lb;
assign WrSel = add || sub;
assign EXTOp = sw || sh || sb || lw || lh || lb;

每条指令对应的控制信号取值:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
always@(*) begin
    case (Operation)
        `ADD : begin
            Br = 1'b0;
            Jr = 1'b0;
            Link = 1'b0;
            J = 1'b0;
            WrSel = 1'b1;
            ...
         end
        ...
        default : begin
        end
end

分析优劣: 由指令确定其控制信号: 优点:考虑全面,不会有漏掉的指令,对每个指令也不会有漏掉的控制信号
上机新增指令时直接对其分析,一个也不会漏 缺点:代码重复冗长
产生新的控制信号时还需要对每个指令都加句话

对控制信号归纳其生效指令: 优点:本质上是上一种方法的总结归纳,代码量少
缺点:自行归纳时某些控制信号可能会忘记考虑一些指令

PS:解决方法:对新指令在演草纸上进行所有控制信号枚举,之后再进行归纳,用于代码书写

在相应的部件中,复位信号的设计都是同步复位,这与 P3 中的设计要求不同。请对比同步复位与异步复位这两种方式的 reset 信号与 clk 信号优先级的关系。

同步复位:clk优先(还必须得是上升沿) 异步复位:reset优先

C 语言是一种弱类型程序设计语言。C 语言中不对计算结果溢出进行处理,这意味着 C 语言要求程序员必须很清楚计算结果是否会导致溢出。因此,如果仅仅支持 C 语言,MIPS 指令的所有计算指令均可以忽略溢出。 请说明为什么在忽略溢出的前提下,addi 与 addiu 是等价的,add 与 addu 是等价的。提示:阅读《MIPS32® Architecture For Programmers Volume II: The MIPS32® Instruction Set》中相关指令的 Operation 部分。

1
2
if temp32  temp31 then
SignalException(IntegerOverflow)

这是add和addu、addi和addiu的唯一区别
u的本意即为不考虑输出 那在忽略输出的前提下自然就等价了。

comments powered by Disqus
Easy Life and Easy Learning
使用 Hugo 构建
主题 StackJimmy 设计