多周期CPU设计

本文

主要介绍一个基于MIPS架构的多周期处理器,本项目来自计算机组成原理课程设计。

版本 说明
0.1 初版发布

专业术语与缩略语

缩写 全称 说明
ISA Instruction Set Architecture 指令集架构
IF Instruction Fetch 取指
DE Decode 译码
EXE Execute 执行
LS Load/Store 访存
WB WriteBack 写回

概述

  • MIPS架构是一种采取精简指令集(RISC)的处理器架构,采取32位定长指令字,以及IO与内存统一编址。
  • 本设计基于MIPS架构,实现I型、R型、J型共26条指令,支持IO和中断处理电路。并且编写汇编程序,通过IO接口和中断实现流水灯的Led显示和花型切换。

指令集

从编码格式区分

  • I型指令:
指令 [31:26] [25:21] [20:16] [15:0] 意义
addi 001000 rs rt immediate
andi 001100 rs rt immediate
ori 001101 rs rt immediate
xori 001110 rs rt immediate 异或
lui 001111 00000 rt immediate 将imm设置rt高16bit,低位补零
  • R型指令:
指令 [31:26] [25:21] [20:16] [15:11] [10:6] [5:0] 意义
add 000000 rs rt rd 00000 100000
sub 000000 rs rt rd 00000 100010
and 000000 rs rt rd 00000 100100
or 000000 rs rt rd 00000 100101
xor 000000 rs rt rd 00000 100110 异或
sll 000000 00000 rt rd sa 000000 左移
srl 000000 00000 rt rd sa 000010 逻辑右移
sra 000000 00000 rt rd sa 000011 算术右移
jr 000000 rs 00000 00000 00000 001000 寄存器跳转
beq 000100 rs rt offset[15:11] offset[10:6] offset[5:0] 相等转移
bne 000101 rs rt offset[15:11] offset[10:6] offset[5:0] 不等转移
lw 100011 rs rt offset[15:11] offset[10:6] offset[5:0] 读存储
sw 101011 rs rt offset[15:11] offset[10:6] offset[5:0] 写存储
  • J型指令:
指令 [31:26] [25:0] 意义
j 000010 address 直接跳转
jal 000011 address 调用跳转

从指令功能区分

  • 运算型指令
指令 [31:26] [25:21] [20:16] [15:11] [10:6] [5:0] 意义
addi 001000 rs rt imm[15:11] imm[10:6] imm[5:0]
andi 001100 rs rt imm[15:11] imm[10:6] imm[5:0]
ori 001101 rs rt imm[15:11] imm[10:6] imm[5:0]
xori 001110 rs rt imm[15:11] imm[10:6] imm[5:0] 异或
lui 001111 00000 rt imm[15:11] imm[10:6] imm[5:0] 将imm设置rt高16bit,低位补零
add 000000 rs rt rd 00000 100000
sub 000000 rs rt rd 00000 100010
and 000000 rs rt rd 00000 100100
or 000000 rs rt rd 00000 100101
xor 000000 rs rt rd 00000 100110 异或
sll 000000 00000 rt rd sa 000000 左移
srl 000000 00000 rt rd sa 000010 逻辑右移
sra 000000 00000 rt rd sa 000011 算术右移
  • 访存型指令:
指令 [31:26] [25:21] [20:16] [15:11] [10:6] [5:0] 意义
lw 100011 rs rt offset[15:11] offset[10:6] offset[5:0] 读存储
sw 101011 rs rt offset[15:11] offset[10:6] offset[5:0] 写存储
  • 分支型指令:
指令 [31:26] [25:21] [20:16] [15:11] [10:6] [5:0] 意义
jr 000000 rs 00000 00000 00000 001000 寄存器跳转
beq 000100 rs rt offset[15:11] offset[10:6] offset[5:0] 相等转移
bne 000101 rs rt offset[15:11] offset[10:6] offset[5:0] 不等转移
j 000010 addr[25:21] addr[20:16] addr[15:11] addr[10:6] addr[5:0] 直接跳转
jal 000011 addr[25:21] addr[20:16] addr[15:11] addr[10:6] addr[5:0] 调用跳转

寄存器

32个32位通用寄存器:

标号 表示 意义
0 zero 总返回0
1 at (汇编暂存寄存器)为汇编保留
2-3 v0、v1 存放子函数调用返回结果,还可用于表达式求值
4-7 a0 - a3 存放向子函数传递的参数
8-15 t0- t7 存放临时运算结果,在发生函数调用时不必保存它们的内容
16-23 s0 - s7 存放局部变量,在发生函数调用时一般要保存它们的内容
24,25 t8-t9 存放临时运算结果,在发生函数调用时不必保存它们的内容
26,27 k0, k1 为中断/陷入处理保留,你也可以改变
28 gp 全局指针
29 sp 栈(stack)指针
30 s8/fp 帧(frame)指针
31 ra 返回地址(用于函数调用)

不过此设计比较简单,只需关注zero、ra、k0/k1寄存器,其他寄存器作为普通寄存器即可。

执行阶段

取指

在此阶段 CPU 主要完成两个任务,一是 PC+4,二是根据 PC 值从指令存储器中取出指令。当 IF 阶段运行时,EXE 阶段处于闲置状态,设计 PC+4 在 ALU中完成,不再单独设置加法器,可以降低资源。另外添加取指操作中PC值的选择逻辑,包括跳转/分支、加4、中断响应。

译码

此阶段主要完成的任务是把操作码 op、功能码sa 送入控制器中,得出控制信号,由控制信号来决定 CPU 接下来的任务。主要控制信号有指令操作、存储操作、执行操作、写回操作任务和 PC 值变化形式。另外此阶段会读取寄存器,以及立即数的扩展逻辑,由控制信号 EXTOP 来决定是零扩展还是符号扩展。

执行

跳转指令在译码阶段已经完成了,其他四种类型指令将进入执行阶段,不同类型指令在执行阶段的情况也不同。在执行阶段设置一个新的判决电路模块,把在存储器、寄存器堆中取出的数据和控制器发出的相关控制信号送进去经过判决生成两个信号,作为 ALU 操作数端口前的数据选择器的选择信号。

访存

在此阶段读/写存储器中的数据。在 MIPS 架构中只有两条指令涉及此阶段,lw(读数据)和 sw(写数据),在执行阶段中已计算出存储器的地址,根据地址读取或写入数据。

写回

此阶段就是为了把 ALU 计算的数据或者是在存储器中取出的数据写回到寄存器堆中,其中写回的目的寄存器号有 rt 和 rd。

数据通路

逻辑结构


指令流程


状态机控制


汇编程序

以下汇编程序只是示意。

  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
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
//-------------------------------------------------------------------------
        // exception vector
        j start
        j exception_handler
//-------------------------------------------------------------------------


//-------------------------------------------------------------------------
start:
        //store_led_data:
        addi r1, r0, 0x100  //led type0 base mem_addr
        ori  r2, r0, 0x1    //8'b0000_0001
        sw   r2, 0(r1)      //store to mem[0x100]
        addi r1, r1, 0x4    //mem_addr+4 (4*8=32)
        ori  r2, r0, 0x2    //8'b0000_0010
        sw   r2, 0(r1)      //store to mem[0x104]
        addi r1, r1, 0x4    //mem_addr+4 (4*8=32)
        ori  r2, r0, 0x4    //8'b0000_0100
        sw   r2, 0(r1)      //store to mem[0x108]
        addi r1, r1, 0x4    //mem_addr+4 (4*8=32)
        ori  r2, r0, 0x8    //8'b0000_1000
        sw   r2, 0(r1)      //store to mem[0x10c]
        addi r1, r1, 0x4    //mem_addr+4 (4*8=32)
        ori  r2, r0, 0x10   //8'b0001_0000
        sw   r2, 0(r1)      //store to mem[0x110]
        addi r1, r1, 0x4    //mem_addr+4 (4*8=32)
        ori  r2, r0, 0x20   //8'b0010_0000
        sw   r2, 0(r1)      //store to mem[0x114]
        addi r1, r1, 0x4    //mem_addr+4 (4*8=32)
        ori  r2, r0, 0x40   //8'b0100_0000
        sw   r2, 0(r1)      //store to mem[0x118]
        addi r1, r1, 0x4    //mem_addr+4 (4*8=32)
        ori  r2, r0, 0x80   //8'b1000_0000
        sw   r2, 0(r1)      //store to mem[0x11c]

        addi r1, r0, 0x150  //led type0 base mem_addr
        ori  r2, r0, 0x81   //8'b1000_0001
        sw   r2, 0(r1)      //store to mem[0x150]
        addi r1, r1, 0x4    //mem_addr+4 (4*8=32)
        ori  r2, r0, 0x42   //8'b0100_0010
        sw   r2, 0(r1)      //store to mem[0x154]
        addi r1, r1, 0x4    //mem_addr+4 (4*8=32)
        ori  r2, r0, 0x24   //8'b0010_0100
        sw   r2, 0(r1)      //store to mem[0x158]
        addi r1, r1, 0x4    //mem_addr+4 (4*8=32)
        ori  r2, r0, 0x18   //8'b0001_1000
        sw   r2, 0(r1)      //store to mem[0x15c]
        addi r1, r1, 0x4    //mem_addr+4 (4*8=32)
        ori  r2, r0, 0x3c   //8'b0011_1100
        sw   r2, 0(r1)      //store to mem[0x160]
        addi r1, r1, 0x4    //mem_addr+4 (4*8=32)
        ori  r2, r0, 0x7e   //8'b0111_1110
        sw   r2, 0(r1)      //store to mem[0x164]
        addi r1, r1, 0x4    //mem_addr+4 (4*8=32)
        ori  r2, r0, 0xff   //8'b1111_1111
        sw   r2, 0(r1)      //store to mem[0x168]
        addi r1, r1, 0x4    //mem_addr+4 (4*8=32)
        ori  r2, r0, 0x00   //8'b0000_0000
        sw   r2, 0(r1)      //store to mem[0x16c]

        addi r1, r0, 0x100  //default display led type0
//-------------------------------------------------------------------------


//-------------------------------------------------------------------------
display_led:
        addi r2, r1, 0x0    //r2 is led type
        addi r3, r0, 0x200  //set 0x200 is IO addr to display led
        lw r4, 0(r2) //get one led_data
        sw r4, 0(r3) //display led
        jal delay_500ms
        addi r2, r2, 0x4 //get next led_data
        lw r4, 0(r2)
        sw r4, 0(r3) //display led
        jal delay_500ms
        addi r2, r2, 0x4 //get next led_data
        lw r4, 0(r2)
        sw r4, 0(r3) //display led
        jal delay_500ms
        addi r2, r2, 0x4 //get next led_data
        lw r4, 0(r2)
        sw r4, 0(r3) //display led
        jal delay_500ms
        addi r2, r2, 0x4 //get next led_data
        lw r4, 0(r2)
        sw r4, 0(r3) //display led
        jal delay_500ms
        addi r2, r2, 0x4 //get next led_data
        lw r4, 0(r2)
        sw r4, 0(r3) //display led
        jal delay_500ms
        addi r2, r2, 0x4 //get next led_data
        lw r4, 0(r2)
        sw r4, 0(r3) //display led
        jal delay_500ms
        addi r2, r2, 0x4 //get next led_data
        lw r4, 0(r2)
        sw r4, 0(r3) //display led
        jal delay_500ms
        j display_led
//-------------------------------------------------------------------------


//-------------------------------------------------------------------------
delay_500ms:
        addi r5, r0, 0x1000  //delay counter max value
loop:
        subi r5, r5, 0x1
        bne r5, r0, loop
        jr r31
//-------------------------------------------------------------------------


//-------------------------------------------------------------------------
exception_handler: // 切换花型
        addi r7, r0, 0x100
        beq r1 , r7, type1 //如果当前是type0 则切换到type1,否则切换到type0
type0:
        addi r1, r0, 0x100  //set display led type0
        j display_led
type1:
        addi r1, r0, 0x150  //set display led type1
        j display_led
//-------------------------------------------------------------------------

其他补充

关于访存数据宽度

宽度 字节数 意义
dword 8 “d”代表ld
word 4 “w”代表lw
halfword 2 “h”代表lh
byte 1 “b”代表lb

本设计只支持单字访问,也就是lw和sw,并且只支持单字对齐访问(地址低2位为0)。

关于多周期与单周期处理器

多周期处理器,把一条指令的执行分为几个周期来完成,这样每个周期只做一件事,大大的减轻单拍任务量,使处理器的频率更快,并且不同指令所需周期数不同,整体单条指令的平均执行时间,多周期处理器优于单周期处理器。

关于汇编程序中的函数调用

通过两条指令实现:

1
2
jal lable
jr r31
  • jal指令会跳转至lable代表的地址,同时将当前PC+4写回r31寄存器。
  • jr,将pc赋值为rs寄存器的值,当rs为r31时,就是返回函数调用前的地址。

关于中断和异常

本设计不支持异常处理,支持中断处理,这里的中断处理机制为自定义模式,未严格遵循MIPS架构。

本设计的中断主要为了支持流水灯的花型控制,通过外部开关触发中断。设计中为中断信号添加额外的去抖动和边沿检测电路,最终中断信号送入处理器,待当前指令执行结束后强制跳转至中断处理程序。

汇编程序头部设有两条跳转指令:

  • 第一条为Reset时PC指向地址,跳转至主程序入口
  • 第二条为Interrupt时PC指向地址,跳转至中断处理程序入口

升级为流水线处理器需要做些什么

流水线处理器与多周期处理器相比,会增加数据冒险、控制冒险、结构冒险的处理。


文章原创,可能存在部分错误,欢迎指正,联系邮箱 cao_arvin@163.com。