{"id":13645759,"url":"https://github.com/z4yx/supervisor-mips32","last_synced_at":"2025-10-30T03:14:03.372Z","repository":{"id":149772384,"uuid":"125690909","full_name":"z4yx/supervisor-mips32","owner":"z4yx","description":"计算机组成原理课程32位监控程序","archived":false,"fork":false,"pushed_at":"2020-06-02T03:30:58.000Z","size":418,"stargazers_count":48,"open_issues_count":0,"forks_count":16,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-26T20:51:34.549Z","etag":null,"topics":["assembly","mips"],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/z4yx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2018-03-18T03:57:48.000Z","updated_at":"2024-07-23T16:50:11.000Z","dependencies_parsed_at":null,"dependency_job_id":"9969afca-c6b9-4002-bfc2-183e1f604692","html_url":"https://github.com/z4yx/supervisor-mips32","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/z4yx%2Fsupervisor-mips32","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/z4yx%2Fsupervisor-mips32/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/z4yx%2Fsupervisor-mips32/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/z4yx%2Fsupervisor-mips32/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/z4yx","download_url":"https://codeload.github.com/z4yx/supervisor-mips32/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248657825,"owners_count":21140842,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["assembly","mips"],"created_at":"2024-08-02T01:02:41.232Z","updated_at":"2025-10-30T03:13:58.335Z","avatar_url":"https://github.com/z4yx.png","language":"C++","funding_links":[],"categories":["C++"],"sub_categories":[],"readme":"# supervisor-32： 32位监控程序\n\n[![Build Status](https://travis-ci.org/z4yx/supervisor-mips32.svg?branch=master)](https://travis-ci.org/z4yx/supervisor-mips32)\n\n## 介绍\n\nThinpad 教学计算机搭配了监控程序，能够接受用户命令，支持输入汇编指令并运行，查看寄存器及内存状态等功能。监控程序可在学生实现的 32 位 MIPS CPU 上运行，一方面可以帮助学生理解、掌握 MIPS 指令系统及其软件开发，另一方面可以作为验证学生 CPU 功能正确性的标准。\n\n监控程序分为两个部分，Kernel 和 Term。其中 Kernel 使用 MIPS32 汇编语言编写，运行在 Thinpad 上学生实现的 CPU 中，用于管理硬件资源；Term 是上位机程序，使用 Python 语言编写，有基于命令行的用户界面，达到与用户交互的目的。Kernel 和 Term 直接通过串口通信，即用户在 Term 界面中输入的命令、代码经过 Term 处理后，通过串口传输给 Kernel 程序；反过来，Kernel 输出的信息也会通过串口传输到 Term，并展示给用户。\n\n## Kernel\n\nKernel 使用汇编语言编写，使用到的指令有20余条，均符合 MIPS32 Release2 规范。Kernel 提供了三种不同的版本，以适应不同的档次的 CPU 实现。它们分别是：第一档为基础版本，直接基本的I/O和命令执行功能，不依赖异常、中断、CP0等处理器特征，适合于最简单的 CPU 实现；第二档支持中断，使用中断方式完成串口的I/O功能，需要处理器实现中断处理机制，及相关的CP0处理器；第三档在第二档基础上进一步增加了TLB的应用，要求处理器支持基于TLB的内存映射，更加接近于操作系统对处理器的需求。\n\n为了在硬件上运行 Kernel 程序，我们首先要对 Kernel 的汇编代码进行编译。编译时必须使用MTI Bare Metal工具链：[Linux版下载](https://cloud.tsinghua.edu.cn/f/16dde018b00749a4a4de/) 。将下载的压缩包解压到任意目录后，设置环境变量 `GCCPREFIX` 以便 make 工具找到编译器，例如：\n\n`export GCCPREFIX=/usr/local/mipsel-linux-musl-cross/bin/mipsel-linux-musl-`\n\n下面是编译监控程序的过程。在`kernel`文件夹下面，有汇编代码和 Makefile 文件，我们可以使用 make 工具编译 Kernel 程序。假设当前目录为 `kernel` ，目标版本为基础版本，我们在终端中运行命令\n\n`make ON_FPGA=n`\n\n即可开始编译流程。如果顺利结束，将生成 `kernel.elf` 和 `kernel.bin` 文件，即可执行文件。要在模拟器中运行它，可以使用命令\n\n`make sim`\n\n它会在 QEMU 中启动监控程序，并等待 Term 程序连接。本文后续章节介绍了如何使用 Term 连接模拟器。\n\n若要在编译硬件上运行的 kernel（与 QEMU 版本的区别是串口外设不同），首先用 `make clean` 清除之前编译的结果，最后用命令\n\n`make ON_FPGA=y`\n\n编译用于硬件的 `kernel.bin`。使用开发板提供的工具，将 `kernel.bin` 写入内存 0 地址（物理地址）位置，并让处理器复位从 0x8000000 地址（MIPS32中对应物理地址为0的虚地址）处开始执行，Kernel 就运行起来了。\n\nKernel 运行后会先通过串口输出版本号，该功能可作为检验其正常运行的标志。之后 Kernel 将等待 Term 从串口发来的命令，关于 Term 的使用将在后续章节描述。\n\n接下来我们分别说明三个档次的监控程序对于硬件的要求，及简要的设计思想。\n\n### 基础版本\n\n基础版本的 Kernel 共使用了21条不同的指令，它们是：\n\n1. `ADDIU` 001001ssssstttttiiiiiiiiiiiiiiii\n1. `ADDU` 000000ssssstttttddddd00000100001\n1. `AND` 000000ssssstttttddddd00000100100\n1. `ANDI` 001100ssssstttttiiiiiiiiiiiiiiii\n1. `BEQ` 000100ssssstttttoooooooooooooooo\n1. `BGTZ` 000111sssss00000oooooooooooooooo\n1. `BNE` 000101ssssstttttoooooooooooooooo\n1. `J` 000010iiiiiiiiiiiiiiiiiiiiiiiiii\n1. `JAL` 000011iiiiiiiiiiiiiiiiiiiiiiiiii\n1. `JR` 000000sssss0000000000hhhhh001000\n1. `LB` 100000bbbbbtttttoooooooooooooooo\n1. `LUI` 00111100000tttttiiiiiiiiiiiiiiii\n1. `LW` 100011bbbbbtttttoooooooooooooooo\n1. `OR` 000000ssssstttttddddd00000100101\n1. `ORI` 001101ssssstttttiiiiiiiiiiiiiiii\n1. `SB` 101000bbbbbtttttoooooooooooooooo\n1. `SLL` 00000000000tttttdddddaaaaa000000\n1. `SRL` 00000000000tttttdddddaaaaa000010\n1. `SW` 101011bbbbbtttttoooooooooooooooo\n1. `XOR` 000000ssssstttttddddd00000100110\n1. `XORI` 001110ssssstttttiiiiiiiiiiiiiiii\n\n根据 MIPS32 规范（在参考文献中）正确实现这些指令后，程序才能正常工作。\n\n监控程序使用了 8 MB 的内存空间，其中约 1 MB 由 Kernel 使用，剩下的空间留给用户程序。此外，为了支持串口通信，还设置了一个内存以外的地址区域，用于串口收发。具体内存地址的分配方法如下表所示：\n\n\n| 虚地址区间 | 说明 |\n| --- | --- |\n| 0x80000000-0x800FFFFF | 监控程序代码 |\n| 0x80100000-0x803FFFFF | 用户代码空间 |\n| 0x80400000-0x807EFFFF | 用户数据空间 |\n| 0x807F0000-0x807FFFFF | 监控程序数据 |\n| 0xBFD003F8-0xBFD003FD | 串口数据及状态|\n\n串口控制器访问的代码位于`kern/utils.S`，其数据格式为：\n\n| 地址 | 位 | 说明 |\n| --- | --- |--- |\n| 0xBFD003F8| [7:0] | 串口数据，读、写地址分别表示串口接收、发送一个字节|\n| 0xBFD003FC| [0] | 只读，为1时表示串口空闲，可发送数据|\n| 0xBFD003FC| [1] | 只读，为1时表示串口收到数据|\n\nKernel 的入口地址为 0x80000000，对应汇编代码`kern/init.S`中的 `START:`标签。在完成必要的初始化流程后，Kernel 输出版本信息，随后进入 shell 线程，与用户交互。shell 线程会等待串口输入，执行输入的命令，并通过串口返回结果，如此往复运行。\n\n当收到启动用户程序的命令后，用户线程代替 shell 线程的活动。用户程序的寄存器，保存在从 0x807F0000 到 0x807F0077 的连续120字节中，依次对应 \\$1 到 \\$30 用户寄存器，每次启动用户程序时从上述地址装载寄存器值，用户程序运行结束后保存到上述地址。\n\n### 进阶一：中断和异常支持\n\n作为扩展功能之一，Kernel 支持中断方式的I/O，和 Syscall 功能。要启用这一功能，编译时的命令变为：\n\n`make ON_FPGA=y EN_INT=y`\n\n这一编译选项，会使得代码编译时增加宏定义`ENABLE_INT`，从而使能中断相关的代码。\n\n为支持中断，CPU 要额外实现以下指令\n\n1. `ERET` 01000010000000000000000000011000\n1. `MFC0` 01000000000tttttddddd00000000lll\n1. `MTC0` 01000000100tttttddddd00000000lll\n1. `SYSCALL` 000000cccccccccccccccccccc001100\n\n此外还需要实现 CP0 寄存器的这些字段：\n\n1. Status: IM4, EXL, IE\n2. Ebase: ExceptionBase\n3. Cause: BD, IP4, ExcCode\n4. EPC\n\nCP0 寄存器字段功能定义参见 MIPS32 特权态规范（在参考文献中）。\n\n监控程序对于异常、中断的使用方式如下：\n\n- 入口地址 0x80001180，根据异常号跳转至相应的异常处理程序。\n\t- 串口硬件中断：中断号为 IP4，作用是唤醒shell线程。为此，shell和用户线程运行时屏蔽串口硬件中断，idle线程中打开。\n\t- 系统调用：shell线程调用SYS\\_wait，CPU控制权转交idle线程。\n- 异常帧保存29个通用寄存器（k0,k1不保存）及STATUS,CAUSE,EPC三个相关寄存器。32个字，128字节，0x80字节。\n- 禁止发生嵌套异常。\n- 支持SYS\\_wait和SYS\\_putc两个系统调用。写串口忙等待，与禁止嵌套异常不冲突。\n- 当发生不能处理的中断时，表示出现严重错误，终止当前任务，自行重启。并且发送错误信号 0x80 提醒TERM。\n- 初始化时设置CP0_STATUS(BEV)=0,CP0_CAUSE(IV)=0,EBase=0x80001000，使用正常中断模式。\n- 初始化时设置CP0_STATUS(ERL)=0，使eret指令以EPC寄存器值为地址跳转。\n\n### 进阶二：TLB支持\n\n在支持异常处理的基础上，可以进一步使能TLB支持，从而实现用户态地址映射。要启用这一功能，编译时的命令变为：\n\n`make ON_FPGA=y EN_INT=y EN_TLB=y`\n\nCPU 要额外实现以下指令\n\n1. `TLBP` 01000010000000000000000000001000\n1. `TLBR` 01000010000000000000000000000001\n1. `TLBWI` 01000010000000000000000000000010\n1. `TLBWR` 01000010000000000000000000000110\n\n\n此外还需要实现 CP0 寄存器：\n\n1. Context\n2. Config1: MMUSize\n3. Index\n4. Entryhi: VPN2\n5. Entrylo0/1: PFN, D, V\n6. Wired\n7. Random\n\n以及TLB相关的几个异常，其中 Refill 异常入口地址为 0x80001000，与其它异常的入口地址不同。\n\n为了简化，TLB实际的映射是线性映射。将0x80100000-0x803FFFFF放在kuseg地址最低端，将0x80400000-0x807EFFFF放在kuseg的地址最高端。4MB的地址映射在kseg2的页表里只需8KB的页表。因此设CP0的WIRED=2，TLB最低两项存kseg2地址翻译。\n\n在一般中断处理中，需要处理TLB不合法异常。修改异常通过统一置D位为一避免。当访问无法映射的地址时，向串口发送地址访问违法信号，并重启。因为正常访问kseg2不会引发TLB异常，所以异常类型TLBL,TLBS,Mod(修改TLB只读页)都是严重错误，需要发送错误信号 0x80 并重启。\n\nkuseg的映射：\n\n- va[0x00000000, 0x002FFFFF] = pa[0x00100000, 0x003FFFFF]\n- va[0x7FC10000, 0x7FFFFFFF] = pa[0x00400000, 0x007EFFFF]\n \n页表：\n \n- PTECODE: va(i*page_size)-\u003e[i]-\u003eRAM0UBASE[i]\n- PTESTACK: va(KSEG0BASE+i*page_size-RAM1USIZE)-\u003e[i]-\u003eRAM1[i]\n\n初始化过程：\n\n1. 从Config1获得TLB大小，初始化TLB\n1. 设Context的PTEBase并填写页表\n1. PageMask设零（固定为4K页大小）\n1. 将用户栈指针设为 0x80000000\n1. Wired设为2，设置对kseg2的映射。\n\n## Term\n\nTerm 程序运行在实验者的电脑上，提供监控程序和人交互的界面。Term 支持7种命令，它们分别是\n\n- R：按照\\$1至\\$30的顺序返回用户程序寄存器值。\n- D：显示从指定地址开始的一段内存区域中的数据。\n- A：用户输入汇编指令或者数据，并放置到指定地址上。输入行只有数值时视为数据，否则为指令。\n- F：从文件读入汇编指令或者数据，并放置到指定地址上，格式与 A 命令相同。\n- U：从指定地址读取一定长度的数据，并显示反汇编结果。\n- G：执行指定地址的用户程序。\n- T：查看指定的TLB条目。本功能仅在Kernel支持TLB时有效。\n- Q：退出 Term\n\n利用这些命令，实验者可以输入一段汇编程序，检查数据是否正确写入，并让程序在处理器上运行验证。\n\nTerm 程序位于`term`文件夹中，可执行文件为`term.py`。对于本地的 Thinpad，运行程序时用 -s 选项指定串口。例如：\n\n`python term.py -s COM3` 或者 `python term.py -s /dev/ttyACM0`（串口名称根据实际情况修改）\n\n连接远程实验平台的 Thinpad，或者 QEMU 模拟器时，使用 -t 选项指定 IP 和端口。例如：\n\n`python term.py -t 127.0.0.1:6666`\n\n### 测试程序\n\n监控程序附带了几个测试程序，代码见`kern/test.S`。我们可以通过命令\n\n`make show-utest`\n\n来查看测试程序入口地址。记下这些地址，并在 Term 中使用G命令运行它们。\n\n### 用户程序编写\n\n根据监控程序设计，用户程序的代码区为0x80100000-0x803FFFFF，实验时需要把用户程序写入这一区域。用户程序的最后需要以`jr $31`结束，从而保证正确返回监控程序。\n\n在输入用户程序的过程中，既可以用汇编指令，也可以直接写16进制的数据（机器码）。空行表示输入结束。\n\n以下是一次输入用户程序并运行的过程演示：\n\n\tMONITOR for MIPS32 - initialized.\n\t\u003e\u003e a\n\t\u003e\u003eaddr: 0x80100000\n\tone instruction per line, empty line to end.\n\t[0x80100000] ori $v0,$0,5\n\t[0x80100004] xor $t0,$t0,$t0\n\t[0x80100008] xor $t1,$t1,$t1\n\t[0x8010000c] loop:\n\t[0x8010000c] addu $t1,$t1,$t0\n\t[0x80100010] addiu $t0,$t0,1\n\t[0x80100014] bne $v0,$t0,loop\n\t[0x80100018] nop\n\t[0x8010001c] jr $ra\n\t[0x80100020] nop\n\t[0x80100024] \n\t\u003e\u003e u\n\t\u003e\u003eaddr: 0x80100000\n\t\u003e\u003enum: 64\n\t0x80100000: li\tv0,0x5\n\t0x80100004: xor\tt0,t0,t0\n\t0x80100008: xor\tt1,t1,t1\n\t0x8010000c: addu\tt1,t1,t0\n\t0x80100010: addiu\tt0,t0,1\n\t0x80100014: bne\tv0,t0,0x8010000c\n\t0x80100018: nop\n\t0x8010001c: jr\tra\n\t0x80100020: nop\n\t0x80100024: nop\n\t0x80100028: nop\n\t0x8010002c: nop\n\t0x80100030: nop\n\t0x80100034: nop\n\t0x80100038: nop\n\t0x8010003c: nop\n\t\u003e\u003e g\n\t\u003e\u003eaddr: 0x80100000\n\n\telapsed time: 0.000s\n\t\u003e\u003e r\n\tR1 (AT)    = 0x00000000\n\tR2 (v0)    = 0x00000005\n\tR3 (v1)    = 0x00000000\n\tR4 (a0)    = 0x00000000\n\tR5 (a1)    = 0x00000000\n\tR6 (a2)    = 0x00000000\n\tR7 (a3)    = 0x00000000\n\tR8 (t0)    = 0x00000005\n\tR9 (t1)    = 0x0000000a\n\tR10(t2)    = 0x00000000\n\tR11(t3)    = 0x00000000\n\tR12(t4)    = 0x00000000\n\tR13(t5)    = 0x00000000\n\tR14(t6)    = 0x00000000\n\tR15(t7)    = 0x00000000\n\tR16(s0)    = 0x00000000\n\tR17(s1)    = 0x00000000\n\tR18(s2)    = 0x00000000\n\tR19(s3)    = 0x00000000\n\tR20(s4)    = 0x00000000\n\tR21(s5)    = 0x00000000\n\tR22(s6)    = 0x00000000\n\tR23(s7)    = 0x00000000\n\tR24(t8)    = 0x00000000\n\tR25(t9/jp) = 0x00000000\n\tR26(k0)    = 0x00000000\n\tR27(k1)    = 0x00000000\n\tR28(gp)    = 0x00000000\n\tR29(sp)    = 0x807f0000\n\tR30(fp/s8) = 0x807f0000\n\t\u003e\u003e q\n\n\n当处理器和 Kernel 支持异常功能时（即上文所述 EN_INT=y ），用户还可以用 Syscall 的方式打印字符。打印字符的系统调用号为 30。使用时，用户把调用号保存在v0寄存器，打印字符参数保存在a0寄存器，并执行 syscall 指令，a0寄存器的低八位将作为字符打印。例如：\n\t\n\tori $v0, $0, 30          # 系统调用号\n\tori $a0, $0, 0x4F        # 'O'\n\tsyscall 0x80\n\tnop\n\tori $a0, $0, 0x4B        # 'K'\n\tsyscall 0x80\n\tnop\n\tjr $ra\n\tnop\n\n## 参考文献\n\n- CPU采用的MIPS32指令集标准：MIPS32® Architecture For Programmers Volume II: The MIPS32® Instruction Set\n- MIPS32中断及TLB等特权态资源：MIPS32® Architecture For Programmers Volume III: The MIPS32® Privileged Resource Architecture\n\n## 项目作者\n\n- 初始版本：韦毅龙，李成杰，孟子焯\n- 后续维护：张宇翔，董豪宇\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fz4yx%2Fsupervisor-mips32","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fz4yx%2Fsupervisor-mips32","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fz4yx%2Fsupervisor-mips32/lists"}