/supervisor-mips32

计算机组成原理课程32位监控程序

Primary LanguageC++Apache License 2.0Apache-2.0

supervisor-32: 32位监控程序

Build Status

介绍

Thinpad 教学计算机搭配了监控程序,能够接受用户命令,支持输入汇编指令并运行,查看寄存器及内存状态等功能。监控程序可在学生实现的 32 位 MIPS CPU 上运行,一方面可以帮助学生理解、掌握 MIPS 指令系统及其软件开发,另一方面可以作为验证学生 CPU 功能正确性的标准。

监控程序分为两个部分,Kernel 和 Term。其中 Kernel 使用 MIPS32 汇编语言编写,运行在 Thinpad 上学生实现的 CPU 中,用于管理硬件资源;Term 是上位机程序,使用 Python 语言编写,有基于命令行的用户界面,达到与用户交互的目的。Kernel 和 Term 直接通过串口通信,即用户在 Term 界面中输入的命令、代码经过 Term 处理后,通过串口传输给 Kernel 程序;反过来,Kernel 输出的信息也会通过串口传输到 Term,并展示给用户。

Kernel

Kernel 使用汇编语言编写,使用到的指令有20余条,均符合 MIPS32 Release2 规范。Kernel 提供了三种不同的版本,以适应不同的档次的 CPU 实现。它们分别是:第一档为基础版本,直接基本的I/O和命令执行功能,不依赖异常、中断、CP0等处理器特征,适合于最简单的 CPU 实现;第二档支持中断,使用中断方式完成串口的I/O功能,需要处理器实现中断处理机制,及相关的CP0处理器;第三档在第二档基础上进一步增加了TLB的应用,要求处理器支持基于TLB的内存映射,更加接近于操作系统对处理器的需求。

为了在硬件上运行 Kernel 程序,我们首先要对 Kernel 的汇编代码进行编译。编译时必须使用MTI Bare Metal工具链:Linux版下载 。将下载的压缩包解压到任意目录后,设置环境变量 GCCPREFIX 以便 make 工具找到编译器,例如:

export GCCPREFIX=/usr/local/mipsel-linux-musl-cross/bin/mipsel-linux-musl-

下面是编译监控程序的过程。在kernel文件夹下面,有汇编代码和 Makefile 文件,我们可以使用 make 工具编译 Kernel 程序。假设当前目录为 kernel ,目标版本为基础版本,我们在终端中运行命令

make ON_FPGA=n

即可开始编译流程。如果顺利结束,将生成 kernel.elfkernel.bin 文件,即可执行文件。要在模拟器中运行它,可以使用命令

make sim

它会在 QEMU 中启动监控程序,并等待 Term 程序连接。本文后续章节介绍了如何使用 Term 连接模拟器。

若要在编译硬件上运行的 kernel(与 QEMU 版本的区别是串口外设不同),首先用 make clean 清除之前编译的结果,最后用命令

make ON_FPGA=y

编译用于硬件的 kernel.bin。使用开发板提供的工具,将 kernel.bin 写入内存 0 地址(物理地址)位置,并让处理器复位从 0x8000000 地址(MIPS32中对应物理地址为0的虚地址)处开始执行,Kernel 就运行起来了。

Kernel 运行后会先通过串口输出版本号,该功能可作为检验其正常运行的标志。之后 Kernel 将等待 Term 从串口发来的命令,关于 Term 的使用将在后续章节描述。

接下来我们分别说明三个档次的监控程序对于硬件的要求,及简要的设计**。

基础版本

基础版本的 Kernel 共使用了21条不同的指令,它们是:

  1. ADDIU 001001ssssstttttiiiiiiiiiiiiiiii
  2. ADDU 000000ssssstttttddddd00000100001
  3. AND 000000ssssstttttddddd00000100100
  4. ANDI 001100ssssstttttiiiiiiiiiiiiiiii
  5. BEQ 000100ssssstttttoooooooooooooooo
  6. BGTZ 000111sssss00000oooooooooooooooo
  7. BNE 000101ssssstttttoooooooooooooooo
  8. J 000010iiiiiiiiiiiiiiiiiiiiiiiiii
  9. JAL 000011iiiiiiiiiiiiiiiiiiiiiiiiii
  10. JR 000000sssss0000000000hhhhh001000
  11. LB 100000bbbbbtttttoooooooooooooooo
  12. LUI 00111100000tttttiiiiiiiiiiiiiiii
  13. LW 100011bbbbbtttttoooooooooooooooo
  14. OR 000000ssssstttttddddd00000100101
  15. ORI 001101ssssstttttiiiiiiiiiiiiiiii
  16. SB 101000bbbbbtttttoooooooooooooooo
  17. SLL 00000000000tttttdddddaaaaa000000
  18. SRL 00000000000tttttdddddaaaaa000010
  19. SW 101011bbbbbtttttoooooooooooooooo
  20. XOR 000000ssssstttttddddd00000100110
  21. XORI 001110ssssstttttiiiiiiiiiiiiiiii

根据 MIPS32 规范(在参考文献中)正确实现这些指令后,程序才能正常工作。

监控程序使用了 8 MB 的内存空间,其中约 1 MB 由 Kernel 使用,剩下的空间留给用户程序。此外,为了支持串口通信,还设置了一个内存以外的地址区域,用于串口收发。具体内存地址的分配方法如下表所示:

虚地址区间 说明
0x80000000-0x800FFFFF 监控程序代码
0x80100000-0x803FFFFF 用户代码空间
0x80400000-0x807EFFFF 用户数据空间
0x807F0000-0x807FFFFF 监控程序数据
0xBFD003F8-0xBFD003FD 串口数据及状态

串口控制器访问的代码位于kern/utils.S,其数据格式为:

地址 说明
0xBFD003F8 [7:0] 串口数据,读、写地址分别表示串口接收、发送一个字节
0xBFD003FC [0] 只读,为1时表示串口空闲,可发送数据
0xBFD003FC [1] 只读,为1时表示串口收到数据

Kernel 的入口地址为 0x80000000,对应汇编代码kern/init.S中的 START:标签。在完成必要的初始化流程后,Kernel 输出版本信息,随后进入 shell 线程,与用户交互。shell 线程会等待串口输入,执行输入的命令,并通过串口返回结果,如此往复运行。

当收到启动用户程序的命令后,用户线程代替 shell 线程的活动。用户程序的寄存器,保存在从 0x807F0000 到 0x807F0077 的连续120字节中,依次对应 $1 到 $30 用户寄存器,每次启动用户程序时从上述地址装载寄存器值,用户程序运行结束后保存到上述地址。

进阶一:中断和异常支持

作为扩展功能之一,Kernel 支持中断方式的I/O,和 Syscall 功能。要启用这一功能,编译时的命令变为:

make ON_FPGA=y EN_INT=y

这一编译选项,会使得代码编译时增加宏定义ENABLE_INT,从而使能中断相关的代码。

为支持中断,CPU 要额外实现以下指令

  1. ERET 01000010000000000000000000011000
  2. MFC0 01000000000tttttddddd00000000lll
  3. MTC0 01000000100tttttddddd00000000lll
  4. SYSCALL 000000cccccccccccccccccccc001100

此外还需要实现 CP0 寄存器的这些字段:

  1. Status: IM4, EXL, IE
  2. Ebase: ExceptionBase
  3. Cause: BD, IP4, ExcCode
  4. EPC

CP0 寄存器字段功能定义参见 MIPS32 特权态规范(在参考文献中)。

监控程序对于异常、中断的使用方式如下:

  • 入口地址 0x80001180,根据异常号跳转至相应的异常处理程序。
    • 串口硬件中断:中断号为 IP4,作用是唤醒shell线程。为此,shell和用户线程运行时屏蔽串口硬件中断,idle线程中打开。
    • 系统调用:shell线程调用SYS_wait,CPU控制权转交idle线程。
  • 异常帧保存29个通用寄存器(k0,k1不保存)及STATUS,CAUSE,EPC三个相关寄存器。32个字,128字节,0x80字节。
  • 禁止发生嵌套异常。
  • 支持SYS_wait和SYS_putc两个系统调用。写串口忙等待,与禁止嵌套异常不冲突。
  • 当发生不能处理的中断时,表示出现严重错误,终止当前任务,自行重启。并且发送错误信号 0x80 提醒TERM。
  • 初始化时设置CP0_STATUS(BEV)=0,CP0_CAUSE(IV)=0,EBase=0x80001000,使用正常中断模式。
  • 初始化时设置CP0_STATUS(ERL)=0,使eret指令以EPC寄存器值为地址跳转。

进阶二:TLB支持

在支持异常处理的基础上,可以进一步使能TLB支持,从而实现用户态地址映射。要启用这一功能,编译时的命令变为:

make ON_FPGA=y EN_INT=y EN_TLB=y

CPU 要额外实现以下指令

  1. TLBP 01000010000000000000000000001000
  2. TLBR 01000010000000000000000000000001
  3. TLBWI 01000010000000000000000000000010
  4. TLBWR 01000010000000000000000000000110

此外还需要实现 CP0 寄存器:

  1. Context
  2. Config1: MMUSize
  3. Index
  4. Entryhi: VPN2
  5. Entrylo0/1: PFN, D, V
  6. Wired
  7. Random

以及TLB相关的几个异常,其中 Refill 异常入口地址为 0x80001000,与其它异常的入口地址不同。

为了简化,TLB实际的映射是线性映射。将0x80100000-0x803FFFFF放在kuseg地址最低端,将0x80400000-0x807EFFFF放在kuseg的地址最高端。4MB的地址映射在kseg2的页表里只需8KB的页表。因此设CP0的WIRED=2,TLB最低两项存kseg2地址翻译。

在一般中断处理中,需要处理TLB不合法异常。修改异常通过统一置D位为一避免。当访问无法映射的地址时,向串口发送地址访问违法信号,并重启。因为正常访问kseg2不会引发TLB异常,所以异常类型TLBL,TLBS,Mod(修改TLB只读页)都是严重错误,需要发送错误信号 0x80 并重启。

kuseg的映射:

  • va[0x00000000, 0x002FFFFF] = pa[0x00100000, 0x003FFFFF]
  • va[0x7FC10000, 0x7FFFFFFF] = pa[0x00400000, 0x007EFFFF]

页表:

  • PTECODE: va(i*page_size)->[i]->RAM0UBASE[i]
  • PTESTACK: va(KSEG0BASE+i*page_size-RAM1USIZE)->[i]->RAM1[i]

初始化过程:

  1. 从Config1获得TLB大小,初始化TLB
  2. 设Context的PTEBase并填写页表
  3. PageMask设零(固定为4K页大小)
  4. 将用户栈指针设为 0x80000000
  5. Wired设为2,设置对kseg2的映射。

Term

Term 程序运行在实验者的电脑上,提供监控程序和人交互的界面。Term 支持7种命令,它们分别是

  • R:按照$1至$30的顺序返回用户程序寄存器值。
  • D:显示从指定地址开始的一段内存区域中的数据。
  • A:用户输入汇编指令或者数据,并放置到指定地址上。输入行只有数值时视为数据,否则为指令。
  • F:从文件读入汇编指令或者数据,并放置到指定地址上,格式与 A 命令相同。
  • U:从指定地址读取一定长度的数据,并显示反汇编结果。
  • G:执行指定地址的用户程序。
  • T:查看指定的TLB条目。本功能仅在Kernel支持TLB时有效。
  • Q:退出 Term

利用这些命令,实验者可以输入一段汇编程序,检查数据是否正确写入,并让程序在处理器上运行验证。

Term 程序位于term文件夹中,可执行文件为term.py。对于本地的 Thinpad,运行程序时用 -s 选项指定串口。例如:

python term.py -s COM3 或者 python term.py -s /dev/ttyACM0(串口名称根据实际情况修改)

连接远程实验平台的 Thinpad,或者 QEMU 模拟器时,使用 -t 选项指定 IP 和端口。例如:

python term.py -t 127.0.0.1:6666

测试程序

监控程序附带了几个测试程序,代码见kern/test.S。我们可以通过命令

make show-utest

来查看测试程序入口地址。记下这些地址,并在 Term 中使用G命令运行它们。

用户程序编写

根据监控程序设计,用户程序的代码区为0x80100000-0x803FFFFF,实验时需要把用户程序写入这一区域。用户程序的最后需要以jr $31结束,从而保证正确返回监控程序。

在输入用户程序的过程中,既可以用汇编指令,也可以直接写16进制的数据(机器码)。空行表示输入结束。

以下是一次输入用户程序并运行的过程演示:

MONITOR for MIPS32 - initialized.
>> a
>>addr: 0x80100000
one instruction per line, empty line to end.
[0x80100000] ori $v0,$0,5
[0x80100004] xor $t0,$t0,$t0
[0x80100008] xor $t1,$t1,$t1
[0x8010000c] loop:
[0x8010000c] addu $t1,$t1,$t0
[0x80100010] addiu $t0,$t0,1
[0x80100014] bne $v0,$t0,loop
[0x80100018] nop
[0x8010001c] jr $ra
[0x80100020] nop
[0x80100024] 
>> u
>>addr: 0x80100000
>>num: 64
0x80100000: li	v0,0x5
0x80100004: xor	t0,t0,t0
0x80100008: xor	t1,t1,t1
0x8010000c: addu	t1,t1,t0
0x80100010: addiu	t0,t0,1
0x80100014: bne	v0,t0,0x8010000c
0x80100018: nop
0x8010001c: jr	ra
0x80100020: nop
0x80100024: nop
0x80100028: nop
0x8010002c: nop
0x80100030: nop
0x80100034: nop
0x80100038: nop
0x8010003c: nop
>> g
>>addr: 0x80100000

elapsed time: 0.000s
>> r
R1 (AT)    = 0x00000000
R2 (v0)    = 0x00000005
R3 (v1)    = 0x00000000
R4 (a0)    = 0x00000000
R5 (a1)    = 0x00000000
R6 (a2)    = 0x00000000
R7 (a3)    = 0x00000000
R8 (t0)    = 0x00000005
R9 (t1)    = 0x0000000a
R10(t2)    = 0x00000000
R11(t3)    = 0x00000000
R12(t4)    = 0x00000000
R13(t5)    = 0x00000000
R14(t6)    = 0x00000000
R15(t7)    = 0x00000000
R16(s0)    = 0x00000000
R17(s1)    = 0x00000000
R18(s2)    = 0x00000000
R19(s3)    = 0x00000000
R20(s4)    = 0x00000000
R21(s5)    = 0x00000000
R22(s6)    = 0x00000000
R23(s7)    = 0x00000000
R24(t8)    = 0x00000000
R25(t9/jp) = 0x00000000
R26(k0)    = 0x00000000
R27(k1)    = 0x00000000
R28(gp)    = 0x00000000
R29(sp)    = 0x807f0000
R30(fp/s8) = 0x807f0000
>> q

当处理器和 Kernel 支持异常功能时(即上文所述 EN_INT=y ),用户还可以用 Syscall 的方式打印字符。打印字符的系统调用号为 30。使用时,用户把调用号保存在v0寄存器,打印字符参数保存在a0寄存器,并执行 syscall 指令,a0寄存器的低八位将作为字符打印。例如:

ori $v0, $0, 30          # 系统调用号
ori $a0, $0, 0x4F        # 'O'
syscall 0x80
nop
ori $a0, $0, 0x4B        # 'K'
syscall 0x80
nop
jr $ra
nop

参考文献

  • CPU采用的MIPS32指令集标准:MIPS32® Architecture For Programmers Volume II: The MIPS32® Instruction Set
  • MIPS32中断及TLB等特权态资源:MIPS32® Architecture For Programmers Volume III: The MIPS32® Privileged Resource Architecture

项目作者

  • 初始版本:韦毅龙,李成杰,孟子焯
  • 后续维护:张宇翔,董豪宇