CRZ's Blog

BUAA-COT 使用手册

声明

本文所介绍的工具 COT 来自开源项目,其源代码与详细信息可见: COT

该项目版权归原作者所有,并遵循其仓库中所标注的开源许可证。本文仅对其使用方法进行学习性说明,所有原始代码与设计均由原作者贡献。若本文中引用了项目中的代码或截图,均仅用于技术交流目的。

配置问题

问题1

我按说明执行start.exe之后出现如下报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Mars: Start dumping datas...
arth_test.asm: Mars is dumping data...
ERROR: UnicodeDecodeError"'utf-8' codec can't decode byte 0xd4 in position 4: invalid continuation byte"
Detailed Stacktree:
Traceback (most recent call last):
File "start.py", line 187, in test
self.machine.start_test()
File "Machine.py", line 38, in start_test
self.__get_std_mars()
File "Machine.py", line 206, in __get_std_mars
self.__runner.run_mars(src, hex_dst, out_log)
File "Runner.py", line 142, in run_mars
self.dump_hex(src, hex_dst)
File "Runner.py", line 63, in dump_hex
contents = safe_read(os.path.join(self._dir, "info.txt"))
File "Runner.py", line 640, in safe_read
contents = file.readlines()
File "codecs.py", line 322, in decode
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd4 in position 4: invalid continuation byte

解决方法

把 Windows 的“语言用于非 Unicode 程序”改成 English 控制面板 → 区域 → 管理 → 非 Unicode 程序语言 → 改为 English(United States)

原理

MARS/Java 输出用的是系统默认编码(ACP),中文是 GBK,会产生 UTF-8 不能解码的字节;切成英文后输出主要是 ASCII,不再产生非法字节,UTF-8 读取自然不报错。

问题2

运行cot发现输出永远为空

解决方法

修改系统环境变量

  1. 添加系统变量 变量名:XILINX 变量值:D:\Xilinx\14.7\ISE_DS\ISE(ISE位置)
  2. 新增PATH 系统变量中选中PATH - 编辑 - 新建
    • D:\Xilinx\14.7\ISE_DS\ISE\lib\nt64
    • D:\Xilinx\14.7\ISE_DS\ISE\bin\nt64

选择评测cpu

如下图,直接链接到cpu所在文件夹即可,评测机不会修改cpu内容。 cpu地址设置

BUAA-CO-P5 基于verilog的流水线MIPS架构CPU(1)

要求

  • 全力转发!
  • 支持指令集:{add,sub,ori,lui,lw,sw,beq,jr,jal,nop}

需要重构的地方

译码方式

选择集中式译码 & 控制信号驱动型译码

e.g.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  //宏定义
`define ADDU 6'b100001
`define RTYPE 6'b000000;
//定义信号
wire Rtype,addu;
wire [1:0]NPCOp;
wire RFWr;
//decode
assign RType = (opcode == RTYPE);
assign addu = RType&(funct == ADDU);
//生成控制信号
assign NPCOp[0] = beq | jr | jalr;
assign NPCOp[1] = jal | jr | jalr | j;

assign RFWr = addu | subu | ori | lw | jal | lui | lb | lh | addi | jalr ;

Splitter

要取代的:

  1. ControlSplitter
  2. InstrSplitter
  • 核心思想:用宏定义代替splitter
    • 各种控制信号在D级生成后打包成controlbus向后传
    • instruction和controlbus都直接完整向后传,如果要用再利用宏定义在对应阶段取出即可

专门建立一个define.v,在里面进行宏定义

1
2
3
4
5
// constants.v
`ifndef DEFINES_V
`define DEFINES_V
//宏定义
`endif

这样可以保证不重复宏定义

命名方式

E_xx:来自E Register的 xx 值/在E stage产生的 xx 值

添加BranchControl模块

原alu输出equal等跳转判断信号原npc使用equal等信号判断是否跳转的功能集成到BranchControl模块中。

  • 实现分支判断提前到ID

拆分P4中的grfSel模块

grfSel = WASel + WDSel

  • WASel:放在ID中实现,然后一路传递
  • WDSel:WB中实现

GRF内部转发

  • 当GRF同时在WB被写入,在ID被读出,且存在读写寄存器冲突,读出的寄存器值应为新写入的值

    1
    2
    assign RD1 = (RA1 != 0 && RA1 == WA && RegWrite) ? WD : register[RA1];
    assign RD2 = (RA2 != 0 && RA2 == WA && RegWrite) ? WD : register[RA2];

前置知识

延迟槽

跳转指令的下一条指令必执行一次(下一条指令已经在IF被取出)

时序相关

时序

  • 规定CCn的上升沿 指的是 CCn开始的上升沿
  • 初始状态:第0个周期,实际上已经预加载了第一条指令的IF阶段。也就是说,CC1上升沿第一条指令实际上是进入ID阶段。

冒险

数据冒险

  • 转发/旁路:

    • 概念:将尚未写入RF已经暂存在流水线寄存器中的计算结果传递给相关功能部件(或流水线寄存器)的技术
    • 基本要点:
      1. 前递:将M(后级)保存的ALUresult向EX stage(前级)的ALU传递
      2. 选择:
        1. 有依赖才转发
        2. 不依赖不转发,就直接用grf读出的数据参与计算
      3. 转发的数据一定是已经锁存过的数据,而不能从正在计算的组合逻辑里拉线!!!
  • 完备性

    转发到/数据来源 M锁存的ALUresult W_WD M_PC8 E_PC8
    ID stage RtData/RsData RtData/RsData RtData/RsData RtData/RsData
    EX stage RtData/RsData RtData/RsData RtData/RsData 不存在
    MEM stage 不存在 RtData 不存在 不存在

    例如:

    • lw - R:
      • lw处于ME时插入空拍,R指令仍处于ID stage,PC冻结;
      • lw处于WB时,R型指令进入EX stage,将W_reg暂存的计算结果转发到ALU输入端(代替operand1/operand2
    • lw - xxx - R:
      • R型指令处于ID stage时,将W_reg暂存的计算结果转发进E_reg(代替RsData/RtData
  • 阻塞的实现

    • 冻结PC
    • 冻结D_reg
    • 清零E_reg (本质上是向EX stage插入nop指令)

控制冒险

  • 提前分支判断:在D级就完成分支判断(已经用D级 BranchControl模块实现)
  • 延迟槽:即使判断跳转,也至少让紧接着的那条指令执行(不用舍弃IF已经取到的指令)

加指令思路

分析指令RTL

严格按照指令说明来!!!

  1. branch类
  • 修改BranchControlifLinkD_flushBranchEn的输出逻辑
  • 清空延迟槽(NullifyCurrentInstruction()) = D_flush(清空延迟槽)
  1. 是否为条件写寄存器? 改造类似条件linkControlBus添加isConditionalWrite,在能确定是否写入的阶段设置ifConditionalWrite并随流水线向后传递,参与TrueRegWrite的选择实现。
    1. 涉及访存的条件写:在MEM阶段才能确定是否要写入(M_TrueRegWrite\W_TrueRegWrite)
    2. 不涉及访存的条件写:在ID阶段就可以确定是否要写入
  2. 是否为条件选择写入寄存器数据?
    1. 不涉及到访存:增加ALU逻辑即可,在MEM阶段确定写入的数据
    2. 涉及到访存:增加ALU逻辑 & 增加WDsel逻辑,在WB阶段确定写入的数据(本质:lw)

易错点

  1. STALL时不能清空延迟槽(因为此时D级还没有取到正确的data,分支判断无法进行,输出的D_flush不对)
    • STALL > D_flush

计算用组合逻辑积累

  1. rtData是否为rsData相反数? wire isNegPair = (rsData + rtData == 0) && (rsData != 32'h8000_0000); 相反数

  2. 取部分信号 data[i*4 +:4]等价于data[i*4+3:i*4]

  3. 对比有无符号

    • 默认无符号
    • $signed()
  4. 判断奇偶 wire isOdd = rsData[0]; wire isEven = ~rsData[0];

  5. 判断是否是2^n 只有一个 bit 是 1

  6. 是否为 rs 的绝对值比 rt 大

1
2
3
wire absgt =
($signed(rsData[31] ? -rsData : rsData)) >
($signed(rtData[31] ? -rtData : rtData));

模块设计

顶层设计

mips.v

数据通路

  • 五个转发去处(D_NewRsData/D_NewRtData/E_NewRsData/E_NewRtData/M_NewRtData):

  • 本质上是两种转发来源

    • 来自M级:ALUresult、PC+4
    • 来自W级:WD
  • 以D_NewRsData为例: D_RsData数据来源

IF

IFU

  • 模块规格
    信号名 方向 位宽 描述
    reset I 1 同步复位信号
    clk I 1 时钟信号
    NPC I 32 pc更新值
    stall I 32 冻住
    instruction O 32 指令机器码
    PC O 32 当前PC

D

Reg_D

  • 模块规格
    信号名 方向 位宽 描述
    clk I 1 clock
    reset I 1 同步复位信号
    F_instruction I 32 F_instruction
    F_PC I 32 F_PC
    stall I 1 冻住
    D_flush I 1 是否清空延迟槽
    E_instruction O 32 E_instruction
    E_PC O 32 E_PC

ID

Control

  • 控制信号表;

    控制信号\指令 add sub ori lw sw beq lui jal jr
    RegWrite 1 1 1 1 0 0 1 1 0
    aluControlSel 000 000 001 001 001 000 001 000 000
    ALUop 0000 0001 0011 0000 0000 0000 0100 0000 0000
    MemWrite 0 0 0 0 1 0 0 0 0
    MemRead 0 0 0 1 0 0 0 0 0
    EXTop 0 0 0 1 1 1 0 0 0
    RegDst 000 000 001 001 000 000 001 011 000
    WDsel 00 00 00 01 00 00 00 10 00
    NPCop 000 000 000 000 000 001 000 010 011
    MemWriteDataOp 00 00 00 00 00 00 00 00 00
    ifB 0 0 0 0 0 1 0 0 0
    ifJ 0 0 0 0 0 0 0 1 1
    BranchOp 000 000 000 000 000 000 000 000 000
    useRt 1 1 0 0 1 1 0 0 0
    islink 0 0 0 0 0 0 0 1 0
    isConditionalLink 0 0 0 0 0 0 0 0 0
    isLikely 0 0 0 0 0 0 0 0 0
  • T模型时间表

    • Tuse :这条指令位于 D 级的时候,再经过多少个时钟周期就必须要使用相应的数据

    • Tnew :位于某个流水级的某个指令,它经过 Tnew 个时钟周期,该指令的结果(即最终要写回 GRF 的值)才会被计算出来,并存入对应的流水级寄存器中,从而可以被后续指令安全使用或转发。

      • Tnew=0GRF
      指令\T RsTuse RtTuse D_Tnew 说明
      R_cal 1 1 2 Rs、Rt在E级送入ALU,结果在E级产生
      I_cal 1 \ 2 只用Rs,结果在E级产生
      lw(及所有涉及到读内存的指令) 1 \ 3 Rs(base)在E级用于算地址,结果在M级(访存后)产生
      sw 1 2 0 Rs(地址)在E级,Rt(要写入内存的数据)在M级,无需写回GRF
      j \ \ 0 不读寄存器、不写寄存器
      jal \ \ 1 不读寄存器,要写入寄存器的值在D级生成(PC + 4)
      jr 0 \ 0 Rs在D级作为跳转目标被使用,不写寄存器
      beq 0 0 0 Rs\Rt在D级参与跳转判断;不写入寄存器
  • 模块规格

    信号名 方向 位宽 描述
    opcode I 6 指令[31:26]位
    funct I 6 指令[5:0]位
    RegWrite O 1 GRF写使能信号
    aluControlSel O 3 alu操作数选择信号
    000:rs/rt
    001:rs/imm
    ALUop O 4 ALU运算模式选择信号
    MemWrite O 1 DM写使能信号
    MemRead O 1 DM读使能信号
    EXTop O 1 EXT扩展模式选择信号
    RegDst O 3 GRF写回地址选择信号:
    001:rt
    000:rd
    010:rs
    011:GPR[31]
    WDsel O 2 GRF写入数据选择信号:
    00:ALU运算结果
    01:DM读出的数据(lw)
    10:PC + 8(延迟槽)
    NPCop O 3 PC更新模式选择信号:
    000:顺序执行
    001:beq分支跳转
    010:j/jal跳转
    011:jr跳转
    MemWriteDataOp O 2 写入DM数据选择信号
    00:RtData
    ifB O 1 是否为B类指令
    ifJ O 1 是否为J类指令
    BranchOp O 3 判断是否跳转的规则的选择信号
    3’b000:if(rsData == rtData) 跳转
    useRt O 1 判断该指令是否涉及到读Rt
    islink O 1 判断该指令是否要link(将PC + 8写入$31
    isConditionalLink O 1 判断该指令是否是条件link
    isLikely O 1 判断该指令是否涉及分支清空延迟槽

HCU(冒险控制单元)

  • 核心任务:(按优先级实现)

    1. 顶层是否空拍一周期?
    2. 是否需要转发?转发路径?
    3. 维持原数据
  • 模块规格

    信号名 方向 位宽 描述
    D_Rs I 5 ID中指令对应的Rs地址
    D_Rt I 5 ID中指令对应的Rt地址
    E_Rs I 5 EX中指令对应的Rs地址
    E_Rt I 5 EX中指令对应的Rt地址
    M_Rt I 5 MEM中指令对应的Rt地址
    E_WA I 5 “上一条”指令(EX中指令)最终要写入的寄存器地址
    M_WA I 5 “上上条”指令(MEM中指令)最终要写入的寄存器地址
    W_WA I 5 (WB中指令)最终要写入的寄存器地址
    E_RegWrite I 1 EX中指令是否涉及写寄存器
    M_RegWrite I 1 MEM中指令是否涉及写寄存器
    W_RegWrite I 1 WB中指令是否涉及写寄存器
    D_useRt I 1 ID中指令是否涉及到读Rt
    M_ifLink I 1 M中指令是否要link
    stall O 1 是否空拍一次
    D_RsData_Sel O 3 实际写入E_reg\NPC的数据选择信号
    000:原RsData
    001:M_ALUresult
    010:W_WD
    011:M_PC8
    100:EPC8
    tip:必须先判断是否涉及写入$0,再判断001&011,最后判断011(即是否涉及从最近的一条MEM的指令跳转)
    D_RtData_Sel O 3 实际写入E_reg的数据选择信号
    000:原RtData
    001:M_ALUresult
    010:W_WD
    011:M_PC8
    100:EPC8
    E_RsData_Sel O 3 实际写入ALUControl的数据选择信号
    000:原RsData
    001:M_ALUresult
    010: W_WD
    011: M_PC8
    100:EPC8
    E_RtData_Sel O 3 实际写入ALUControl的数据选择信号
    000:原RtData
    001:M_ALUresult
    010: W_WD
    011: M_PC8
    100:EPC8
    M_RtData_Sel O 3 实际写入MemWriteDataSel的数据选择信号
    000: 原RtData
    001: W_WD

BranchControl

  • 核心任务: 判断是否需要跳转
  • 整体设计:
  • 模块规格
    信号名 方向 位宽 描述
    rsData I 32 rs data
    rtData I 32 rt data
    ifB I 1 是否是B类指令
    ifJ I 1 是否为J类指令
    islink I 1 判断该指令是否要link(将PC + 8写入$31
    isConditionalLink I 1 判断该指令是否是条件link
    isLikely I 1 判断该指令是否涉及分支清空延迟槽
    BranchOp I 3 跳转规则的选择信号
    3’b000:beq
    BranchEn O 1 是否跳转
    ifLink O 1 是否真正要link
    D_flush O 1 是否清空延迟槽

NPC

  • 核心任务 计算下一周期PC应更新成的值

  • 整体设计 所有分支/跳转目标地址必须基于 D_PC

    • 顺序执行: NPC = PC + 4
    • 分支跳转:NPC = (PC + 4) + (SignExtend(offset))
    • j跳转:NPC = (PC + 4)31...28||instr_index||00
    • jr跳转:NPC = GPR[rs]
  • 模块规格

    信号名 方向 位宽 描述
    immEXT I 32 拓展后的立即数(offset)
    instr_index I 26 instr_index
    NPCop I 3 000:NPC = PC + 4
    001:分支跳转
    010:j
    011:jr
    rd1OrRs I 32 rs储存的值
    BranchEn I 1 B型指令是否跳转
    F_PC I 32 刚进入流水线(IF阶段取出)的指令地址
    D_PC I 32 D_PC
    NPC O 32 IFU中PC更新值

GRF

  • 整体设计
    • 读数据:指令最多需要同时读取两个不同地址的数据 - 两个读端口
    • 写数据
    • 0 号寄存器的值始终保持为 0。其他寄存器初始值(复位后)均为 0,无需专门设置

  • 模块规格:
    信号名 方向 位宽 描述
    reset I 1 异步复位信号
    clk I 1 时钟信号
    RA1 I 5 读地址1
    RA2 I 5 读地址2
    WA I 5 写地址
    WD I 32 写数据
    RegWrite I 1 写使能信号
    RD1 O 32 读数据1
    RD2 O 32 读数据2

WASel

  • 整体设计 选择存入GRF的地址
  • 模块规格:
    信号名 方向 位宽 描述
    RegDst I 3 GRF写回地址选择信号:
    001:rt
    000:rd
    010:rs
    011:GPR[31]
    rs I 5 rs
    rt I 5 rt
    rd I 5 rd

EXT

  • 核心任务 把16位立即数扩展到32位
  • 整体设计 能够对立即数进行零扩展/符号扩展
  • 模块规格
    信号名 方向 位宽 描述
    immOrOffset I 16 待扩展的立即数
    EXTop I 1 扩展方式选择信号:
    1 - 符号扩展
    0 - 零扩展
    immEXT O 32 扩展后的立即数

E

Reg_E

  • 模块规格
    信号名 方向 位宽 描述
    clk I 1 clock
    reset I 1 同步复位信号
    stall I 1 清零信号
    D_ControlBus I CTL_BUS_WIDTH D_ControlBus
    D_instruction I 32 D_instruction
    D_RD1 I 32 D_RD1
    D_RD2 I 32 D_RD2
    D_immEXT I 32 D_immEXT
    D_PC I 32 D_PC
    D_WA I 5 D_WA
    D_ifLink I 1 D_ifLink
    E_ControlBus O CTL_BUS_WIDTH E_ControlBus
    E_instruction O 32 E_instruction
    E_RD1 O 32 E_RD1
    E_RD2 O 32 E_RD2
    E_immEXT O 32 E_immEXT
    E_PC O 32 E_PC
    E_WA O 5 E_WA
    E_ifLink O 1 E_ifLInk

EX

ALUControl

  • 整体设计 为alu选择operand1,operand2
  • 模块规格
    信号名 方向 位宽 描述
    RsData I 32 RsData
    RtData I 32 RtData
    immEXT I 32 扩展后的立即数
    shamt I 5 shamt
    aluControlSel I 3 选择信号
    000:rs/rt
    001:rs/imm
    operand1 O 32 operand1
    operand2 O 32 operand2

ALU

  • 整体设计
    • 能够实现多种运算功能,并根据ALUop选择对应功能
      ALUop 运算
      0000 无符号op1 + op2
      0001 无符号op1 - op2
      0010 op1 & op2
      0011 op1 | op2
      0100 op2低16位加载至高位,低位补0
      0101 $signed(operand1)<$signed(operand2) ? 1:0
  • 模块规格
    信号名 方向 位宽 描述
    ALUop I 4 选择运算功能
    operand1 I 32 operand1
    operand2 I 32 operand2
    ALUresult O 32 运算结果

M

Reg_M

  • 模块规格
    信号名 方向 位宽 描述
    clk I 1 clock
    reset I 1 同步复位信号
    E_ControlBus I CTL_BUS_WIDTH+1 来自 E 级的控制信号
    E_instruction I 32 E_instruction
    E_ALUresult I 32 E_ALUresult
    E_RD1 I 32 E_RD1
    E_RD2 I 32 E_RD2
    E_immEXT I 32 E_immEXT
    E_MemWriteData I 32 E_MemWriteData
    E_PC I 32 E_PC
    E_WA I 5 E_WA
    E_ifLink I 1 E_ifLInk
    M_ControlBus O CTL_BUS_WIDTH+1 M_ControlBus
    M_instruction O 32 M_instruction
    M_ALUresult O 32 M_ALUresult
    M_RD1 O 32 M_RD1
    M_RD2 O 32 M_RD2
    M_immEXT O 32 M_immEXT
    M_MemWriteData O 32 M_MemWriteData
    M_PC O 32 M_PC
    M_WA O 5 M_WA
    M_ifLink O 1 M_ifLInk

MEM

MemWriteDataSel

  • 整体设计 选择写入内存的数据
  • 模块规格
    信号名 方向 位宽 描述
    MemWriteDataOp I 2 写入DM数据选择信号
    00:RtData
    RtData I 32 rt data
    MemWriteData O 32 写入DM的数据

DM

  • 模块规格
    信号名 方向 位宽 描述
    Address I 32 读/写地址,默认是ALUresult
    WriteData I 32 要写入的数据
    MemWrite I 1 写使能信号
    MemRead I 1 读使能信号
    clk I 1 时钟信号
    reset I 1 异步复位信号
    PC I 32 当前指令的地址
    LoadType I 2 load模式选择信号
    2’b00:lw
    2’b01:lh
    SaveType I 2 Save模式选择信号
    2’b00:sw
    2’b01:sh
    MemReadData O 32 读出的数据

W

Reg_W

  • 模块规格
    信号名 方向 位宽 描述
    clk I 1 clock
    reset I 1 同步复位信号
    M_ControlBus I CTL_BUS_WIDTH+1 来自 M 级的控制信号
    M_instruction I 32 M_instruction
    M_ALUresult I 32 M_ALUresult
    M_RD1 I 32 M_RD1
    M_RD2 I 32 M_RD2
    M_immEXT I 32 M_immEXT
    M_MemReadData I 32 M_MemReadData
    M_PC I 32 M_PC
    M_WA I 5 M_WA
    M_ifLink I 1 M_ifLInk
    W_ControlBus O CTL_BUS_WIDTH+1 W_ControlBus
    W_instruction O 32 W_instruction
    W_ALUresult O 32 W_ALUresult
    W_RD1 O 32 W_RD1
    W_RD2 O 32 W_RD2
    W_immEXT O 32 W_immEXT
    W_MemReadData O 32 W_MemReadData
    W_PC O 32 W_PC
    W_WA O 5 W_WA
    W_ifLink O 1 W_ifLInk

WB

WDSel

  • 整体思路 选择写回GRF的数据
  • 模块规格
    信号名 方向 位宽 描述
    WDsel I 2 GRF写入数据选择信号:
    00:ALU运算结果
    01:DM读出的数据(lw)
    10:PC + 8(延迟槽)
    ALUresult I 32 ALU运算结果
    MemData I 32 DM读出的数据
    PC I 32 PC
    WD O 32 WD

测试

测试覆盖

  1. 基本指令功能
  2. 转发测试
  3. 暂停测试

基本指令功能测试

转发测试

按照完备性表格分类测试

转发到/数据来源 M锁存的ALUresult W_WD M_PC8
ID stage RtData/RsData RtData/RsData RtData/RsData
EX stage RtData/RsData RtData/RsData RtData/RsData
MEM stage 不存在 RtData 不存在
  • (1,1):ID ← M_ALUresult

    1
    2
    3
    4
    5
    ori $3, $0, 1
    ori $4, $0, 2
    beq $5, $3, label
    label:
    nop
  • (1,2):ID ← W_WD

    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
      ori $3, $0, 1
    ori $4, $0, 2
    ori $6, $0, 2
    beq $5, $3, label
    label:
    nop
    ```
    - (13):ID ← M_PC + 8

    ```mips
    jal return
    ori $4, $0, 2
    beq $5, $31, label
    return:
    jr $31
    label:
    nop
    ```
    - (21):EX ← M_ALUresult

    ```mips
    ori $3, $0, 1
    ori $4, $3, 2
    ```
    - (22):EX ← W_WD

    ```mips
    ori $3, $0, 1
    add $5, $3, $3
    ori $4, $3, 2
    ```
    - (23):EX ← M_PC + 8

    ```mips
    jal return
    ori $4, $31, 2
    return:
    jr $31
    label:
    nop
    ```


    - (32):MEM ← W_WD

    ```mips
    ori $3, $0, 5
    sw $3, 0($0)
    ```

    ## 暂停测试
    - E_Conflict:
    ```mips
    ori $1,$0,3
    beq $1,$0,label
    label:
    nop
  • M_Conflict:

    1
    2
    3
    4
    5
    lw $1, 0($0)      
    nop
    beq $1, $0, label
    label:
    nop

测试出来的问题

link类

  • 问题
    • 我默认传递的PC值不会被覆盖,所以在WB阶段直接写入W_PC + 4。但实际上由于stall、D_flush的存在,PC值在中间是可能被覆盖的。
      • 所以为了保证link的正确性,我们应该额外设置数据通路,将实际要写回的D_PC + 4往后传。

What is FLUSH ?

  • D_flush(likely导致的):在D级注入一条nop(本质:REPLACEMENT
    • 清空instruction
    • PC保持原值
  • E_flush(stall导致的):在E级注入一条nop(本质:INSERTION
    • 清空instruction
    • PC保持原值(该nop是凭空加在两条指令之间的,没有对应的PC)

思考题

  1. 我们使用提前分支判断的方法尽早产生结果来减少因不确定而带来的开销,但实际上这种方法并非总能提高效率,请从流水线冒险的角度思考其原因并给出一个指令序列的例子。

    • 原因:提前判断需要RsData和RtData,而这两个数据可能需要转发。如果HCU判断需要stall,那么提前判断节省的一个T和stall延迟的一个T就抵消了,并没有提高效率。
    • 例如:
      1
      2
      lw   $t0, 0($t1)    
      beq $t0, $zero, L
  2. 因为延迟槽的存在,对于 jal 等需要将指令地址写入寄存器的指令,要写回 PC + 8,请思考为什么这样设计?

    • jal把下一条“应该执行”的指令地址写入$ra$31),但由于延迟槽的存在,下一条要执行的指令实际上是PC + 8处的(下下条)指令。
  3. 我们要求大家所有转发数据都来源于流水寄存器而不能是功能部件(如 DM 、 ALU ),请思考为什么?

    • 功能部件在同一周期内输出还不稳定,但来自流水寄存器的数据已经进行过一次锁存,是稳定的数据
    • 运用其它阶段正在计算的中间值本质上破坏了流水线的时序结构,相当于又类似单周期CPU一样形成了横跨各阶段的巨大组合逻辑
  4. 我们为什么要使用 GPR 内部转发?该如何实现?

    • 内部转发本质:当GRF同时在WB被写入,在ID被读出,且存在读写寄存器冲突,读出的寄存器值应为新写入的值
    • 如果不实现内部转发,就会引发同周期的WD-ID数据冒险
    • 实现:
      1
      2
      assign RD1 = (RA1 != 0 && RA1 == WA && RegWrite) ? WD : register[RA1];
      assign RD2 = (RA2 != 0 && RA2 == WA && RegWrite) ? WD : register[RA2];
  5. 我们转发时数据的需求者和供给者可能来源于哪些位置?共有哪些转发数据通路? 需求者\供给者|M锁存的ALUresult|W_WD|M_PC8|E_PC8 —|—|—|— ID stage:|RtData/RsData|RtData/RsData|RtData/RsData|RtData/RsData EX stage:|RtData/RsData|RtData/RsData|RtData/RsData|不存在 MEM stage:|不存在|RtData|不存在|不存在

  6. 在课上测试时,我们需要你现场实现新的指令,对于这些新的指令,你可能需要在原有的数据通路上做哪些扩展或修改?提示:你可以对指令进行分类,思考每一类指令可能修改或扩展哪些位置。

    • 对指令进行分类
      • R_cal
      • I_cal
      • B
      • J
        • B&J可能有以下属性
          • Link
          • Likely
          • ConditionalLink
      • Load:所有涉及到读dm的指令
      • Save
    • 具体见上“加指令思路”
  7. 确定你的译码方式,简要描述你的译码器架构,并思考该架构的优势以及不足。

    • 选择集中式译码 & 控制信号驱动型译码
    • 优势:
      • 集中式译码:只用设置一个Control单元,译码之后把ControlBus一直往后传即可
      • 控制信号驱动型译码:代码简洁,拓展性好
    • 不足:
      • 集中式译码:ControlBus位宽大(即数据通路过宽)

BUAA-CO-P4 基于verilog的单周期MIPS架构CPU

改动

相比P3,需要增加的:

  1. 自行实现ROM、RAM
  2. 实例化&端口映射
  3. 改为同步复位
  4. 用initial对reg赋初值
  5. 对P3中集成alu进行拆分:增加aluControl来进行operand1、operand2的选择
  6. 对grf的修改
    • 现在默认RA1接rs,RA2接rt
    • 若需读取二者以外的寄存器,可临时为grf添加RA3RD3,为aluControl添加rd3
  7. DM添加LoadType\SaveType,为lh、sh、lb、sb等指令的添加预留可扩展性

注意项

  1. case语句要写default
  2. 用综合的check syntax检查
  3. verilog默认数字为十进制32位数:除了明确十进制数,一定带上格式
  4. 避免任何可能产生锁存器的地方
  5. 缩减异或: 对于一个 n 位的向量 A[n-1:0],表达式 ^A 在逻辑上等价于:A[n-1] ^ A[n-2] ^ ... ^ A[1] ^ A[0]
  6. 到底是要用rs还是RsData
    • 需要在命名和顶层连线时注意区分。

实例化

1
2
3
4
5
6
7
// 格式: module_name instance_name ( .port_name(wire_name), ... );
control ctrl_unit (
.opcode(opcode),
.reg_write(reg_write),
.mem_to_reg(mem_to_reg)
// ... connect other ports
);

可拓展

  1. dm相关
    • 可增加dmSel模块来模拟MUX对写入dm的地址和数据进行选择
    • lb/sb的实现

层次设计

EXT(扩展单元)

  • 核心任务 把16位立即数扩展到32位
  • 整体设计 能够对立即数进行零扩展/符号扩展
  • 模块规格
    信号名 方向 位宽 描述
    immOrOffset I 16 待扩展的立即数
    EXTop I 1 扩展方式选择信号:
    1 - 符号扩展
    0 - 零扩展
    immEXT O 32 扩展后的立即数

aluControl

  • 整体设计 为alu选择operand1,operand2
  • 模块规格
    信号名 方向 位宽 描述
    rs I 32 rs value
    rt I 32 rt value
    immEXT I 32 扩展后的立即数
    shamt I 5 shamt
    aluControlSel I 3 选择信号
    000:rs/rt
    001:rs/imm
    operand1 O 32 operand1
    operand2 O 32 operand2

ALU

  • 整体设计
    • 能够实现多种运算功能,并根据op选择对应功能
      op 运算
      0000 无符号op1 + op2
      0001 无符号op1 - op2
      0010 op1 & op2
      0011 op1 | op2
      0100 op2低16位加载至高位,低位补0
  • 模块规格
    信号名 方向 位宽 描述
    op I 4 选择运算功能
    operand1 I 32 operand1
    operand2 I 32 operand2
    result O 32 运算结果
    equal O 1 判断是否相等

NPC

  • 核心任务 计算下一周期PC应更新成的值

  • 整体设计 - 顺序执行: NPC = PC + 4 - 分支跳转:NPC = (PC + 4) + (SignExtend(offset)) - j跳转:NPC = PC31…28||instr_index||00 - jr跳转:NPC = GPR[rs]

  • 模块规格

    信号名 方向 位宽 描述
    equal I 1 判断reg rs、rt所存值是否相同
    immEXT I 32 拓展后的立即数(offset)
    instr_index I 26 instr_index
    NPCop I 3 000:NPC = PC + 4
    001:分支跳转
    010:j
    011:jr
    rd1OrRs I 32 rs储存的值
    pc I 32 当前指令地址
    npc O 32 pc更新值

IFU

  • 核心任务:根据PC中的地址,从ROM中读出指令输出,同时更新PC
  • 整体设计
    • 地址转换逻辑

      • 地址偏移:pc - 0x00003000
      • 将PC的字节地址转换为ROM的字地址
    • ROM:用1024个[31:0]reg来构建ROM

    • 载入数据?

      1
      2
      3
      initial begin
      $readmemh("code.txt", imem);
      end
  • 模块规格
    信号名 方向 位宽 描述
    reset I 1 同步复位信号
    clk I 1 时钟信号
    npc I 32 pc更新值
    Instruction O 32 指令机器码
    pc O 32 当前pc

GRF(通用寄存器组)

  • 核心任务:为ALU提供快速的数据源,并接收计算结果

  • 整体设计

    • 读数据:指令最多需要同时读取两个不同地址的数据 - 两个读端口
    • 写数据
    • 0 号寄存器的值始终保持为 0。其他寄存器初始值(复位后)均为 0,无需专门设置

  • 模块规格:

    信号名 方向 位宽 描述
    reset I 1 异步复位信号
    clk I 1 时钟信号
    RA1 I 5 读地址1
    RA2 I 5 读地址2
    WA I 5 写地址
    WD I 32 写数据
    RegWrite I 1 写使能信号
    RD1 O 32 读数据1
    RD2 O 32 读数据2

    注:这里的“地址”指的是GRF中的第n个reg

DM(数据存储器)

  • 核心任务 根据 CPU 提供的地址,进行读或写操作。它连接了 CPU 的核心与外部数据存储。
  • 整体设计
    • 使用 RAM 实现,容量为 3072 × 32bit,应具有异步复位功能,复位值0x00000000

    • 起始地址0x00000000地址范围0x00000000 ~ 0x00002FFF

    • RAM 应使用双端口模式,即设置 RAM 的 Data Interface 属性为 Separate load and store ports

  • 模块规格
    信号名 方向 位宽 描述
    Address I 32 读/写地址
    WriteData I 32 要写入的数据
    MemWrite I 1 写使能信号
    MemRead I 1 读使能信号
    clk I 1 时钟信号
    reset I 1 异步复位信号
    PC I 32 当前指令的地址
    LoadType I 2 load模式选择信号
    2’b00:lw
    2’b01:lh
    SaveType I 2 Save模式选择信号
    2’b00:sw
    2’b01:sh
    MemData O 32 读出的数据

grfSel

  • 核心任务 决定写入grf的地址和数据
  • 模块设计
    信号名 方向 位宽 描述
    RegDst I 3 GRF写回地址选择信号:
    001:rt
    000:rd
    010:rs
    011:GPR[31]
    WDsel I 2 GRF写入数据选择信号:
    00:ALU运算结果
    01:DM读出的数据(lw)
    10:PC + 4
    rs I 5 rs
    rt I 5 rt
    rd I 5 rd
    ALUresult I 32 ALU运算结果
    MemData I 32 DM读出的数据
    PC I 32 PC
    WA O 5 WA
    WD O 32 WD

instrSplitter

  • 核心任务 对指令机器码进行拆分
  • 模块设计
    信号名 方向 位宽 描述
    Instruction I 32 指令
    opcode O 6 instr[31:26]
    rs O 5 instr[25:21]
    rt O 5 instr[20:16]
    rd O 5 instr[15:11]
    shamt O 5 instr[10:6]
    funct O 6 instr[5:0]
    instr_index I 26 instr[25:0]
    immOrOffset I 16 instr[15:0]

Controller

  • 核心任务:根据当前正在执行的指令输出对应控制信号

  • 整体设计 解码逻辑 = 和逻辑 + 或逻辑

    • 和逻辑:识别

      • 将输入的机器码识别为相应指令
    • 或逻辑:生成

      • 根据输入指令生成对应的控制信号
      • 控制信号表;
        控制信号\指令 add sub ori lw sw beq lui jal jr
        RegWrite 1 1 1 1 0 0 1 1 0
        aluControlSel 000 000 001 001 001 000 001 000 000
        ALUop 0000 0001 0011 0000 0000 0000 0100 0000 0000
        MemWrite 0 0 0 0 1 0 0 0 0
        MemRead 0 0 0 1 0 0 0 0 0
        EXTop 0 0 0 1 1 1 0 0 0
        RegDst 000 000 001 001 000 000 001 011 000
        WDsel 00 00 00 01 00 00 00 10 00
        NPCop 000 000 000 000 000 001 000 010 011
  • 模块规格

    信号名 方向 位宽 描述
    opcode I 6 指令[31:26]位
    funct I 6 指令[5:0]位
    RegWrite O 1 GRF写使能信号
    aluControlSel O 3 alu操作数选择信号
    000:rs/rt
    001:rs/imm
    ALUop O 4 ALU运算模式选择信号
    MemWrite O 1 DM写使能信号
    MemRead O 1 DM读使能信号
    EXTop O 1 EXT扩展模式选择信号
    RegDst O 3 GRF写回地址选择信号:
    001:rt
    000:rd
    010:rs
    011:GPR[31]
    WDsel O 2 GRF写入数据选择信号:
    00:ALU运算结果
    01:DM读出的数据(lw)
    10:PC + 4
    NPCop O 3 PC更新模式选择信号:
    000:顺序执行
    001:beq分支跳转
    010:j/jal跳转
    011:jr跳转

测试方案

工具

借用Mars_CO.jar进行检查。

使用

  1. 命令行进入Mars_CO.jar所在目录
  2. 测试文件也放在这个目录下
  3. 在命令行运行
    1
    java -jar Mars_CO.jar test.asm mc CompactLargeText coL1

思考题

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

    DM示意图

    • 这个addr由alu给出(alu对gpr(base)(sign_ext)offset做加运算)
    • addr以字节为单位,对应到以字为单位的DM,故应除以4,即取[11:2]
  2. 思考上述两种控制器设计的译码方式,给出代码示例,并尝试对比各方式的优劣。

    • 记录指令对应的控制信号如何取值:
      • 优点:思考流畅,一次模拟一整条指令的运行过程即可;便于调试,找到出错指令对应分支进行检查即可
      • 缺点:controll模块繁杂
    • 记录控制信号每种取值所对应的指令:
      • 优点:每个信号利用一个assign表达式描述即可,代码冗余少;
      • 缺点:可读性差,较难找出错误在哪儿
  3. 在相应的部件中,复位信号的设计都是同步复位,这与 P3 中的设计要求不同。请对比同步复位与异步复位这两种方式的 reset 信号与 clk 信号优先级的关系。

    • 同步复位:clk优先
    • 异步复位:reset优先
  4. C 语言是一种弱类型程序设计语言。C 语言中不对计算结果溢出进行处理,这意味着 C 语言要求程序员必须很清楚计算结果是否会导致溢出。因此,如果仅仅支持 C 语言,MIPS 指令的所有计算指令均可以忽略溢出。 请说明为什么在忽略溢出的前提下,addi 与 addiu 是等价的,add 与 addu 是等价的。

    • addaddu 执行的加法计算是完全一样的,唯一的区别在于add指令多了一个溢出检测异常处理的步骤:

         if temp32 ≠ temp31 then
         SignalException(IntegerOverflow)
         else
         GPR[rd] ← temp31..0
         endif
      

      忽略溢出时add与addu等价。

    • addiaddiu同理

BUAA-CO-P3 基于logisim的单周期MIPS架构CPU

声明

P3对alu、grf的设计非常不合理,直接将各个可能的操作数接入alu,而且默认grf要读的寄存器是rs、rt。但因为足够应付需求(懒得重构)就保留了这种错误方法,但在后面的设计中都注意规避了。

前置理解

内存

1. 哈佛结构(本CPU应用的) 内存 = 数据内存(DM) + 指令内存(IM)

  • IM: CPU只会从IM读取指令,永远不会写入指令 使用ROM
  • DM: 可读(lw)可写(sw) 使用RAM

2. 冯·诺伊曼结构 指令和数据存放在同一个物理内存RAM中

指令处理

  1. 用RTL描述每条指令
    • :数据传送方向;
    • R[a] : 寄存器 a;
    • M[a] : 主存中地址为a的单元;
    • PC :程序计数器
    • f(data) : 表示对数据data进行f操作
  2. 加指令:
    • R/I/J
    • 看清指令的RTL描述,把数据流搞清楚
  3. 测试指令直接使用$3这样的寄存器,更易读

Debug

Wrong instruction behavior: in MIPS code 0x3c09cba1, we got ‘@00003030: $9 <= 0xcba10000’ when we expected ‘@0000303c: $11 <= 0x00030000’. The wrong behavior was next to the correct execution of MIPS code 0x3c083bc2.

部分 含义
MIPS code 0x3c09cba1 实际出错的机器码(十六进制)
‘@00003030: $9 <= 0xcba10000’ PC = 00003030时,0xcba10000被写入$9
next to … 出错前的最后一条正确执行的指令(帮助你定位上下文)
  1. 机器码 指令:转化为32位机器码,翻译为指令,例如:

    opcode rs rt immediate
    001101 00110 00110 0110011001101100
     ori $6, $6, 0x666c
    
  2. PC:起始地址为00003000

简化操作的logisim部件

  • odd/even partry:odd/even输出1
  • Bit Finder:找到最高位/最低位1/0的位置
  • negator:取负
  • Bit Adder:不要傻傻地自己去写 (TT…

易错

  1. 输出的regWrite等信号是否为终值?
    • 即:到底有没有写入?

模块设计

IM (指令存储器)

  • 核心任务:根据PC中的地址,从ROM中读出指令输出
  • 整体设计
    • 地址转换逻辑
      • 地址偏移:pc - 0x00003000
      • 将PC的字节地址转换为ROM的字地址
  • 模块规格
    信号名 方向 位宽 描述
    PC I 32 指令的地址
    Instruction O 32 指令机器码

GRF(通用寄存器组)

  • 核心任务:为ALU提供快速的数据源,并接收计算结果

  • 整体设计

    • 读数据:指令最多需要同时读取两个不同地址的数据 - 两个读端口
    • 写数据
    • 0 号寄存器的值始终保持为 0。其他寄存器初始值(复位后)均为 0,无需专门设置

  • 模块规格:

    信号名 方向 位宽 描述
    reset I 1 异步复位信号
    clk I 1 时钟信号
    RA1 I 5 读地址1
    RA2 I 5 读地址2
    WA I 5 写地址
    WD I 32 写数据
    RegWrite I 1 写使能信号
    RD1 O 32 读数据1
    RD2 O 32 读数据2

    注:这里的“地址”指的是GRF中的第n个reg

ALU

  • 整体设计
    • 能够实现多种运算功能,并根据op选择对应功能
      op 运算
      0000 无符号rs + rt
      0001 无符号rs - rt
      0010 rs & imm
      0011 rs | imm
      0100 16位立即数加载至高位
      0101 rs + imm
  • 模块规格
    信号名 方向 位宽 描述
    op I 4 选择运算功能
    rs I 32 rs value
    rt I 32 rt value
    immEXT I 32 扩展后的立即数
    result O 32 运算结果
    equal O 1 判断是否相等

DM(数据存储器)

  • 核心任务 根据 CPU 提供的地址,进行读或写操作。它连接了 CPU 的核心与外部数据存储。
  • 整体设计
    • 使用 RAM 实现,容量为 3072 × 32bit,应具有异步复位功能,复位值0x00000000

    • 起始地址0x00000000地址范围0x00000000 ~ 0x00002FFF

    • RAM 应使用双端口模式,即设置 RAM 的 Data Interface 属性为 Separate load and store ports

  • 模块规格
    信号名 方向 位宽 描述
    Address I 32 读/写地址
    WriteData I 32 要写入的数据
    MemWrite I 1 写使能信号
    MemRead I 1 读使能信号
    clk I 1 时钟信号
    reset I 1 异步复位信号
    MemData O 32 读出的数据

EXT(扩展单元)

  • 核心任务 把16位立即数扩展到32位
  • 整体设计 能够对立即数进行零扩展/符号扩展
  • 模块规格
    信号名 方向 位宽 描述
    immIn I 16 待扩展的立即数
    op I 1 扩展方式选择信号:
    1 - 符号扩展
    0 - 零扩展
    immOut O 32 扩展后的立即数

Controller

  • 核心任务:根据当前正在执行的指令输出对应控制信号

  • 整体设计 解码逻辑 = 和逻辑 + 或逻辑

    • 和逻辑:识别

      • 将输入的机器码识别为相应指令
    • 或逻辑:生成

      • 根据输入指令生成对应的控制信号
      • 控制信号表;
        控制信号\指令 add sub ori lw sw beq lui addi j
        RegWrite 1 1 1 1 0 0 1 1 0
        ALUop 0000 0001 0011 0101 0101 0000 0100 0101 0000
        MemWrite 0 0 0 0 1 0 0 0 0
        MemRead 0 0 0 1 0 0 0 0 0
        EXTop 0 0 0 1 1 1 0 1 0
        RegDst 00 00 01 01 00 00 01 01 00
        WDsel 0 0 0 1 0 0 0 0 0
        NPCop 00 00 00 00 00 01 00 00 10
  • 模块规格

    信号名 方向 位宽 描述
    opcode I 6 指令[31:26]位
    funct I 6 指令[5:0]位
    RegWrite O 1 GRF写使能信号
    ALUop O 4 ALU运算模式选择信号
    MemWrite O 1 DM写使能信号
    MemRead O 1 DM读使能信号
    EXTop O 1 EXT扩展模式选择信号
    RegDst O 2 GRF写回地址选择信号:
    01:rt
    00:rd
    10:rs
    WDsel O 2 GRF写入数据选择信号:
    0:ALU运算结果
    1:DM读出的数据(lw)
    NPCop O 2 PC更新模式选择信号:
    00:顺序执行
    01:beq分支跳转
    10:j跳转

NPC

  • 核心任务 计算下一周期PC应更新成的值

  • 整体设计

    • 顺序执行: NPC = PC + 4
      • 分支跳转:NPC = (PC + 4) + (SignExtend(offset))
      • j跳转:NPC = PC31…28||instr_index||00
  • 模块规格

    信号名 方向 位宽 描述
    immExt I 32 拓展后的立即数(offset)
    instr_index I 26 instr_index
    equal I 1 判断reg rs、rt所存值是否相同
    NPCop I 2 00:NPC = PC + 4
    01:分支跳转
    10:j
    PC I 32 当前指令地址
    NPC O 32 下一条指令地址

测试

核心思路:让 CPU 在执行指令后,将关键的结果存储到DM的特定位置。测试结束后,比较 Logisim 和 Mars 的 DM 内容是否完全一致

测试方法

环境设置

  • 选择Compact,Data at Address 0

编写MIPS汇编测试程序

  1. 初始化
    • 初始化寄存器:初始化为不同的非零值
    • 初始化指针:设置$t0作为指向DM的指针,初始值为0x0000
  2. 书写程序
    • 执行一条指令,保存一个结果,移动一次指针
      • 对于直接产生结果的指令(如add、ori)
        • 直接将结果保存到内存
      • 对于不直接产生结果的指令(如beq、j)
        • 构造场景,使得跳转成功与否导致存入内存的值不同
    • 例如:
      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
      .text
      # .globl main 告诉汇编器 main 是一个全局标签,程序的入口
      .globl main

      main:
      # --- 1. 初始化阶段 ---
      # 初始化寄存器为非零值
      addi $s0, $zero, 10
      addi $s1, $zero, 20
      addi $s2, $zero, 10
      # ... 初始化更多寄存器 ...

      # 初始化内存指针 $t0
      addiu $t0, $zero, 0x0000 # $t0 = 0x0000, 指向 DM 的开始

      # --- 2. 测试指令并保存结果 ---

      # --- 测试 add 指令 ---
      add $t1, $s0, $s1 # $t1 = 10 + 20 = 30
      sw $t1, 0($t0) # 将结果 30 保存到内存地址 0x0000
      addi $t0, $t0, 4 # 指针移动到 0x0004

      # --- 测试 beq (分支不发生) ---
      beq $s0, $s1, BRANCH_TAKEN # 10 != 20, 不会跳转
      # 如果没跳转,存入 0x0
      addiu $t2, $zero, 0x0
      sw $t2, 0($t0)
      addi $t0, $t0, 4
      j END_BRANCH_1

      BRANCH_TAKEN:
      # 如果跳转了,存入 0x1
      addiu $t2, $zero, 0x1
      sw $t2, 0($t0)
      addi $t0, $t0, 4

      END_BRANCH_1:
      # --- 测试 beq (分支发生) ---
      beq $s0, $s2, BRANCH_TAKEN_2 # 10 == 10, 应该跳转
      # 如果没跳转,存入 0x0
      addiu $t2, $zero, 0x0
      sw $t2, 0($t0)
      addi $t0, $t0, 4
      j END_BRANCH_2

      BRANCH_TAKEN_2:
      # 如果跳转了,存入 0x1
      addiu $t2, $zero, 0x1
      sw $t2, 0($t0)
      addi $t0, $t0, 4

      END_BRANCH_2:
      # --- 3. 结束程序 ---
      # 可以用一个无限循环来停住 CPU
      END_LOOP:
      j END_LOOP
  3. 在Mars中生成标答
    1. 执行程序
    2. 手动点击Pause暂停死循环
    3. 导出DM File - Dump Memory
      • 选择Data Segment
      • 选择Hexadecimal Text格式
      • Dump to file得到txt文件
      • 在txt文件开头加上v2.0 raw

在logisim中运行CPU

  1. 找到IM中的ROM,右键选择Load Image
  2. reset
    1. 看DM
      1. 运行时钟直到进入死循环
      2. 找到DM中的RAM,右键选择Edit Content
    2. 实例化grf后直接观察grf
      1. 右键main中grf,选择view grf
      2. ctrl + T开始观察即可

测试数据

计算类指令功能测试

  1. 寄存器数据:
    1. 0及附近的数:−2,−1,0,1,2
    2. 32 位数边界附近的数:−2147483648,−2147483647,2147483646,2147483647
    3. 32 位数范围内的一些随机数:−1000786109,1919156834,…
  2. 无符号立即数:
    1. 0 及附近的数:0,1,2,3
    2. 16 位无符号数边界附近的数:65533,65534,65535
    3. 16 位无符号数范围内的一些随机数:25779,42528,…
  3. 符号立即数:
    1. 0 及附近的数:−2,−1,0,1,2
    2. 16 位符号数边界附近的数:−32768,−32767,32766,32767
    3. 16 位符号数范围内的一些随机数:−5329,25299,…

存取类指令功能测试

  1. offset :+/-/0
  2. $base:+/-/0

跳转类指令功能测试

  • 跳转,且目标在此跳转指令之前
  • 跳转,且目标是此跳转指令
  • 跳转,且目标在此跳转指令之后
  • 不跳转,且目标在此跳转指令之前
  • 不跳转,且目标是此跳转指令
  • 不跳转,且目标在此跳转指令之后

可能添加的

lb/sb

dm本身不变

  • lb:dm取字后再经MUX,将Addr[1:0]作为选择端,选出真正要读出的字节
  • sb:先利用MUX选出要读出的字节
    • 太麻烦暂时不考虑
    • 多周期CPU中实现……

思考题

  1. 上面我们介绍了通过 FSM 理解单周期 CPU 的基本方法。请大家指出单周期 CPU 所用到的模块中,哪些发挥状态存储功能,哪些发挥状态转移功能。

    状态存储:GRF、PC、DM; 状态转移:NPC、Controller、EXT、ALU

  2. 现在我们的模块中 IM 使用 ROM, DM 使用 RAM, GRF 使用 Register,这种做法合理吗? 请给出分析,若有改进意见也请一并给出。

    合理。

    1. IM没有增加、修改或删除指令的需求,所以用ROM(Read-Only Memory)可以覆盖需求,而且可以避免指令被意外修改导致程序故障;
    2. DM需要实现数据的写入和读出,所以使用RAM即可;
    3. GRF使用register也可以完状态储存的需求。

    改进:用同一个RAM储存Instructions和Data,可以支持自修改代码,内存使用也更灵活

  3. 在上述提示的模块之外,你是否在实际实现时设计了其他的模块?如果是的话,请给出介绍和设计的思路。

  4. 事实上,实现 nop 空指令,我们并不需要将它加入控制信号真值表,为什么?

    因为nop指令本质上就是sll $0, $0, 0的R型指令,机器码为0x00000000。把它当作R型指令处理,执行时任何存储单元的值都不会发生变化,所以不用把它再加入控制信号真值表。

  5. 阅读 Pre 的 “MIPS 指令集及汇编语言” 一节中给出的测试样例,评价其强度(可从各个指令的覆盖情况,单一指令各种行为的覆盖情况等方面分析),并指出具体的不足之处。

    1. 寄存器数据、立即数方面:没有测试边界附近的数;没有测试目标寄存器为0的情况
    2. 存取类指令:没有测试offset为负的情况
    3. 跳转类指令:没有测试跳转到前面和当前指令的情况

2025-BUAA-OOPre学习心得

整体架构

UML类图

alt text

Main类

接收控制台读入,并对读入的字符串进行解析,获取对应的指令并执行指令。

1
2
3
CommandFactory factory = new CommandFactory();
Command cmd = factory.parse(strings);
cmd.execute(strings);

迭代调整

分离思想

每个class功能应遵守单一职责原则,故当迭代中发现某个类功能过于繁杂、修改一动而牵全身时,应进行分离处理。

  1. 在发现Adventurer类变得过于繁杂之后,分离了其中处理Portable(可携带品)的功能:
    1
    2
    //Adventurer class
    private final Inventory inventory;
    在Inventory内部实现涉及到Portable的操作。 alt text

命令模式

迭代过程中不断需要添加指令。初始设计为在Main类中用case结构执行对应指令,但很快发现:

  1. Main类中方法过长
  2. 添加指令不方便

因此引入Command Pattern设计模式,优化指令可拓展性并简化Main类

单例模式

对Adventurer实例的管理:采取单例模式,创建World类,保证所有操作都是在同一个World实例里执行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    public class World {

private static Map<String, Adventurer> adventurerMap = new HashMap<>();
private static final World world = new World(adventurerMap);

private World(Map<String, Adventurer> adventurerMap) {
World.adventurerMap = adventurerMap;
}

public static World getInstance() {
return world;
}

public Map<String, Adventurer> getAdventurerMap() {
return adventurerMap;
}
}

工厂模式

将创建各种Usable的方法集成于CreateFactory类中。alt text

容器选择

几乎所有操作的都涉及到idobject的对应关系(key-value),故基本都采用HashMap来管理对象。 但,一旦涉及到顺序性,就必须更换容器,例如:

  1. fight指令涉及到战斗对象的先后顺序,故使用ArrayList来储存
  2. 携带物品的操作涉及FIFO,故在Inventory中采用:
    1
    private final Map<String, LinkedList<Portable>> bag;
    来记录携带信息

使用JUnit的心得体会

  1. 如果一个类很难被测试,通常意味着它的设计存在问题(例如职责不清、耦合度过高),进而推动我们修改代码。

学习OOPre的心得体会

  1. 什么才是一份好的代码?
    • 易于维护、扩展和复用
      • 尝试使用设计模式
      • 遵守设计原则:单一职责、开闭……
  2. 面向过程到面向对象的思维转变
    • 思考应设计什么对象、对象应具备什么属性和行为、对象之间如何协作

对OOPre课程的简单建议

  1. 迭代作业每次要切换仓库,导致版本管理流程(如commit、push等)较为繁琐,建议统一使用同一个仓库来进行迭代开发。

To Begin

Operation


本地打开

1
2
$ cd C:\Users\69877\blog
$ hexo server

清除缓存

ctrl + c 停止服务

1
$ hexo clean && hexo server

添加图片

blog/source/images中添加即可,后续所有路径写成/images/的形式

更改设置

路径:

BLOG/node_modules/hexo-theme-candelas/_config.yml

在里面更改。

更新blog

更新需要一段时间请不要着急()


路径

终端进入blog文件夹后,输入:

1
hexo clean && hexo g -d

markdown格式相关

开头

1
2
3
4
5
6
7
---
title: Hello World
date: 2025/10/11
categories:
- tech
- new
---

常用效果

  1. 代码 (Code)

    • 行内代码:用一对反引号``包裹。
    • 代码块:用三对反引号```包裹,并在第一组反引号后声明语言类型,可以实现语法高亮 e.g. ``` bash xxx ```
  2. 超链接 [title](link)

  3. 文本样式 斜体 * * 删除线~~ ~~

  4. 引用

    >放在文段前来引用

  5. 图片 ![title](link)

  6. 换行 段落间建议使用一个空行

  7. 插入Latex公式

    • 行内插入:$E = \frac{1}{2}mv^2$
    • 块级公式:$$ $$
    • 常用Latex语法
      • 上标:x^{20}

      • 下标:x_{10}

      • 分数:\frac{1}{2}

      • 希腊字母

        命令 显示 命令 显示
        \alpha α \beta β
        \gamma γ \delta δ
        \Delta Δ \epsilon ϵ
        \zeta ζ \eta η
        \theta θ \lambda λ
        \mu μ \nu ν
        \xi ξ \pi π
        \rho ρ \sigma σ
        \Sigma Σ \phi ϕ
        \Phi Φ \psi ψ
        \omega ω \Omega Ω
      • 常见符号

        • \sin
        • \sqrt[n]{expression} expressionn
        • \times × ,\div ÷ ,\cdot
        • \sum ,\int ,\lim lim
        • \infty
        • \rightarrow
      • 矩阵

        1
        2
        3
        4
        A = \begin{pmatrix}
        a & b \\\\
        c & d
        \end{pmatrix}

        效果:

        A=(abcd)

Copyright © 2026 CRZ's Blog

Powered by Hexo • Theme Candelas