北航计组P3设计文档

2024-10-25

Logisim实现单周期CPU

需求分析

指令一:add

1.从GRF中读取rs,rt

  • 操作:(rs)->RegRead1, (rt)->RegRead2

    2.rs与rt通过ALU相加

  • 控制:ALUSrc=0, ALUOp=000
  • 操作:RegRead1->ALUIn1, RegRead2->ALUIn2, ALUIn1+ALUIn2->ALURes

    3. 将结果写入rd

  • 控制:RegDst=1, RegWrite=1
  • 操作:rd->RegAddr, ALURes->RegData

指令二:sub

1.从GRF中读取rs,rt

  • 操作:(rs)->RegRead1, (rt)->RegRead2

    2.rs与rt通过ALU相减

  • 控制:ALUSrc=0, ALUOp=001
  • 操作:RegRead1->ALUIn1, RegRead2->ALUIn2, ALUIn1-ALUIn2->ALURes

    3. 将结果写入rd

  • 控制:RegDst=1, RegWrite=1
  • 操作:rd->RegAddr, ALURes->RegData

指令三:ori

1.从GRF中读取rs,rt

  • 操作:(rs)->RegRead1,(rt)->RegRead2

    2.imm进行无符号拓展

  • 控制:ExtControl=0
  • 操作:{16{0},imm}->extend

    3.rs和imm通过ALU取或

  • 控制:ALUSrc=1, ALUOp=010
  • 操作:RegRead1->ALUIn1, extend->ALUIn2, ALUIn1|ALUIn2->ALURes

    4.将结果写入rt

  • 控制:RegDst=0, RegWrite=1
  • 操作:rd->RegAddr, ALURes->RegData

指令四:lw

1.从GRF中读取base,rt

  • 操作:(base)->RegRead1, (rt)->RegRead2

    2.offest进行有符号拓展

  • 控制:ExtControl=1
  • 操作:{16{offset[15]},offset}->extend

    3.base和offest通过ALU相加

  • 控制:ALUSrc=1, ALUOp=000
  • 操作:RegRead1->ALUIn1, extend->ALUIn2, ALUIn1+ALUIn2->ALURes

    4.将主存结果写入rt

  • 控制:MemToReg=1, MemWrite=0, RegDst=0, RegWrite=1
  • 操作:ALURes->MemAddr, rt->RegAddr, MemRead->RegData

指令五:sw

1.从GRF中读取base,rt

  • 操作:(base)->RegRead1, (rt)->RegRead2

    2.offest进行有符号拓展

  • 控制:ExtControl=1
  • 操作:{16{offset[15]},offset}->extend

    3.base和offest通过ALU相加

  • 控制:ALUSrc=1, ALUOp=000
  • 操作:RegRead1->ALUIn1, extend->ALUIn2, ALUIn1+ALUIn2->ALURes

    4.将rt写入主存

  • 控制:MemWrite=0, MemWrite=1
  • 操作:ALURes->MemAddr, RegRead2->MemData

指令六:beq

1.从GRF中读取rs,rt

  • 操作:(rs)->RegRead1,(rt)->RegRead2

    2.offest进行有符号拓展

  • 控制:ExtControl=1
  • 操作:{16{offset[15]},offset}->extend

    3.rs和rt通过ALU比较

  • 控制:ALUSrc=0, ALUOp=100
  • 操作:RegRead1->ALUIn1, RegRead2->ALUIn2, ALUIn1==ALUIn2->ALURes

    4.修改pc值跳转

  • 操作:pc+4+extend«2->pc

指令七:lui

1.imm进行有符号拓展

  • 控制:ExtControl=1
  • 操作:{16{imm[15]},imm}->extend

    2.imm通过ALU加载到高位

  • 控制:ALUSrc=1, ALUOp=011
  • 操作:extend->ALUIn2, ALUIn2«16->ALURes

    3.将结果写入rt

  • 控制:RegDst=0, RegWrite=1
  • 操作:rt->RegAddr, ALURes->RegData

控制设计

控制信号含义:

参数 含义
RegWrite 决定是否要写入寄存器文件,0 为否,1 为是
RegDst 决定存放被写入寄存器的地址为哪几位,0 为 I 型指令,1 为 R 型
ALUSrc 决定 ALU 的第二个数据是寄存器还是立即数的值,0 为寄存器,1 为立即数
Branch 决定当前是否为分支指令,0 为否,1 为是
MemWrite 决定是否要写入数据存储器,0 为否,1 为是
MemtoReg 决定写入寄存器的是 ALU 计算结果还是主存读取结果,0 为计算结果,1 为读取结果
ALUOp 决定 ALU 进行的运算,0 为加,1 为减,2 为或,3 为加载到高位,4 为判断是否相等
ExtControl 决定扩展是符号扩展还是 0 扩展,1 为符号拓展,0 为 0 拓展

指令对应的控制信号:

指令 RegWrite RegDst ALUSrc Branch MemWrite MemtoReg ALUOp ExtControl
add 1 1 0 0 0 0 000 1
sub 1 1 0 0 0 0 001 1
ori 1 0 1 0 0 0 010 0
lw 1 0 1 0 0 1 000 1
sw 0 0 1 0 1 0 000 1
beq 0 0 0 1 0 0 100 1
lui 1 0 1 0 0 0 011 1

模块设计

模块一:GRF

  • 寄存器堆

  • 寄存器选择

模块二:ALU

  • ALUOp 决定 ALU 进行的运算,0 为加,1 为减,2 为或,3 为加载到高位,4 为判断是否相等

模块三:Splitter

  • 从高到低按照op,rs,rt,rd,ra,rb分线,各个部分宽度分别为6,5,5,5,5,6bit
  • 将rd,ra,rb拼接起来作为immediate/offset

模块四:IFU

  • 每个时钟周期上升沿将pcNext赋值给pc
  • 将pc的值减去初值0x00003000,作为ROM读取的地址addr
  • 每个时钟周期上升沿从ROM读取指令instr
  • pc若为0则将pc置为初始值0x00003000,地址置为0

模块五:与逻辑

  • 结合MIPS指令集,对op和rb进行判断,确定指令类型

模块六:或逻辑

  • 结合指令对应的控制信号,输入为指令类型,输出为对应的控制信号

模块七:DM

  • 所需要的存储空间为3072*32位,考虑采用三片1024*32位的RAM进行存储
  • 32bit=4byte,所以最低两位是字内的字节地址,无意义
  • 每片RAM的片内地址需要10位,因此2-11位作为片内地址
  • 共有三片RAM,需要两位片选地址,12-13位作为片选地址

模块八:Control

  • 综合模块五与模块六的设计,输入为指令的op和rb,输出为对应的控制信号

模块九:main

(一)直接应用前八个模块

  • 结合前几个模块的功能,对每一个模块的输入输出端口添加对应含义的tunnel

(二)直接翻译八个控制信号含义

  • 按照控制信号含义,使用MUX对各自的功能进行直接翻译

(三)模块与模块建立联系

  • 1.只有lw和sw指令能够对主存进行操作,它们访问的主存地址来自ALU计算结果,因此将ALURes与MemAddr直接相连

  • 2.只有sw指令能够进行写主存操作,它写入的内容是(rt),也就是RegRead2,因此将RegRead2与MemData直接相连

  • 3.结合需求分析,ALU的第一个输入一定来自(rs/base),也就是RegRead1 , 因此将ALUIn1与RegRead2直接相连

思考题

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

    状态存储 :GRF、DM 状态转移 :IFU、ALU、Controller

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

    合理 (1)IM 只需被读取,ROM 只有读取功能,所以采用 ROM;如果采用寄存器或 RAM,它们的造价高于 ROM,而 IM 又不需要被写入,过于浪费 (2)DM 既要进行读取,又要进行写入,RAM具有读写功能,所以采用RAM;如果采用寄存器,DM 需要较大空间,寄存器造价高,空间需求大,过于浪费;如果采用ROM,不能进行写入 (3)GRF 既要进行读取,又要进行写入,寄存器具有读写功能,所以采用寄存器;如果采用 ROM,不能进行写入;如果采用 RAM,GRF 与 ALU 直接连接,需要高速地读写,RAM 的存取速度慢于寄存器

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

没有

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

该指令不需要输出任何控制信号 ,将所有控制信号置低电平 ,即可确保CPU的各个部件不受任何影响 ,所以并不需要将它加入控制信号真值表

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

该测试样例的强度较高。对于各个需要实现的指令,都进行了测试,先从每一条基本指令进行验证,之后再在其基础上对指令进行进一步更复杂的验证,对程序的验证有一定的准确性

  • 测试样例所对应的汇编代码:
    ori $28, $0, 0
    ori $29, $0, 0
    ori $1, $0, 13398
    add $1, $1, $1
    lw $1, 4($0)
    sw $1, 4($0)
    lui $2, 30840
    sub $3, $2, $1
    lui $5, 4660
    ori $4, $0, 5
    nop
    sw $5, -1($4)
    lw $3, -1($4)
    beq $3, $5, 1
    beq $0, $0, 13
    ori $7, $3, 1028
    beq $7, $3, 11
    nop
    lui $8, 30583
    ori $8, $8, -1
    sub $0, $0, $8
    ori $0, $0, 4352
    add $10, $7, $6
    ori $8, $0, 0
    ori $9, $0, 1
    ori $10, $0, 1
    add $8, $8, $10
    beq $8, $9, -2
    beq $0, $0, -1
    
  • 附:自动将七条指令的机器码转化成MIPS汇编语言的python代码 ```python def hex_to_binary(hex_string): return bin(int(hex_string, 16))[2:].zfill(32) def binary_to_decimal_complement(binary_string): return int(binary_string, 2) - (1 « len(binary_string)) if binary_string[0] == ‘1’ else int(binary_string, 2) def binary_to_decimal(binary_string): return int(binary_string, 2) instructions = { “000000”: { “100000”: “add ${rd}, ${rs}, ${rt}\n”, “100010”: “sub ${rd}, ${rs}, ${rt}\n”, “000000”: “nop\n” }, “001101”: “ori ${rt}, ${rs}, {imm}\n”, “100011”: “lw ${rt}, {imm}(${rs})\n”, “101011”: “sw ${rt}, {imm}(${rs})\n”, “000100”: “beq ${rs}, ${rt}, {imm}\n”, “001111”: “lui ${rt}, {imm}\n” } with open(‘in.txt’, ‘r’) as infile, open(‘out.txt’, ‘w’) as outfile: for line in infile: hex_input = line.strip() binary_output = hex_to_binary(hex_input) op = binary_output[:6] rs = binary_output[6:11] rt = binary_output[11:16] rd = binary_output[16:21] imm = binary_output[16:32] if op in instructions: if op == “000000”: rb = binary_output[26:32] outfile.write( instructions[op].get(rb, “error”).format(rd=binary_to_decimal(rd), rs=binary_to_decimal(rs), rt=binary_to_decimal(rt))) else: outfile.write(instructions[op].format(rt=binary_to_decimal(rt), rs=binary_to_decimal(rs), imm=binary_to_decimal_complement(imm))) else: outfile.write(“error\n”)

```