监控程序阅读

监控程序是跑在教学计算机上的一段程序,其基本功能有限,只能用来辅助执行一段用户程序,主要功能都是通过串口传输数据配合Term实现的,重点在于验证教学计算机能不能跑对。虽然程序很长,但里面大部分都是注的水,全是读写用的代码(因为是汇编语言,所以写起来总有些复杂),基本功能其实很清晰。

下面是我绘制的程序框图,其中矩形的部分是汇编语言的标签,圆角矩形是程序具体功能。(点击图片看大图)

%E7%9B%91%E6%8E%A7%E7%A8%8B%E5%BA%8F%E6%A1%86%E5%9B%BE.png

监控程序主要分为以下几个部分:

  • 数据传输:通过串口读写数据
  • 中断处理:处理用户程序的中断
  • 初始化
  • 主程序循环
  • R(SHOWREGS)模块
  • D(SHOWMEM)模块
  • A(ASM)模块
  • U(UASM)模块
  • G(GO)模块

下面我把每部分的代码和注释贴出来(虽然其中还是有一些我不那么懂的部分)。

数据传输

教学计算机把串口在内存中映射到BF01,这样比较方便。

; 测试8521是否能写
TESTW:
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    ADDIU R6 0x0001
    LW R6 R0 0x0000    ; 读入BF01处的数据
    LI R6 0x0001
    AND R0 R6
    BEQZ R0 TESTW    ; BF01 & 1 = 0则等待
    NOP
    JR R7    ; 返回
    NOP

; 测试8521是否能读
TESTR:
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    ADDIU R6 0x0001
    LW R6 R0 0x0000    ; 读入BF01处的数据
    LI R6 0x0002
    AND R0 R6
    BEQZ R0 TESTR    ; BF01 & 12= 0则等待
    NOP
    JR R7    ; 返回
    NOP

中断处理

;保存用户程序寄存器的地址
;0xBF10 0xBF11 0xBF12 0xBF13 0xBF14 0xBF15
;R0 R1 R2 R3 R4 R5

NOP
B INIT    ; 跳到INIT函数
NOP

DELINT:    ; 中断处理程序
    NOP    ; 为什么会有那么多NOP?
    NOP
    NOP
    NOP
    NOP
    ; 保存用户程序现场
    LI R6 0xBF
    SLL R6 R6 0x0000    ; R6 = 0xBF10,保存用户程序的起始地址
    ADDIU R6 0x10
    SW R6 R0 0x0000
    SW R6 R1 0x0001
    SW R6 R2 0x0002

    ; R1 = 中断号(也就是从SP对应的内存地址开始的一部分存储着中断相关的信息)
    LW_SP R1 0x00
    ADDSP 0x0001
    LI R0 0x00FF
    AND R1 R0

    ; R2 = 应用程序的pc
    LW_SP R2 0x0000
    ADDSP 0x0001

    ; 保存R3
    ADDSP 0xFFFF    ; SP--
    SW_SP R3 0x0000    ; 把R3保存在相对SP的内存地址中

    ; 保存用户程序返回地址
    ADDSP 0xFFFF
    SW_SP R7 0x0000    ; 把返回地址保存在相对SP的内存地址中

    ; 提示终端,进入中断处理
    LI R3 0x000F
    MFPC R7
    ADDIU R7 0x0003    ; 在R7中保存返回地址
    NOP
    B TESTW    ; 跳转到TESTW,测试8521是否能写
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    SW R6 R3 0x0000    ; 向串口写入SI(启用中断)
    NOP
    ; 输出中断号
    MFPC R7
    ADDIU R7 0x0003    ; ADDIU后面也需要加NOP?
    NOP
    B TESTW
    LI R6 0x00BF
    SLL R6 R6 0x0000
    SW R6 R1 0x0000    ; 把中断号写入串口

    ; 提示终端,中断处理结束(这处理真是简单)
    LI R3 0x000F
    MFPC R7
    ADDIU R7 0x0003
    NOP
    B TESTW
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    SW R6 R3 0x0000    ; 向串口写入SI(结束中断)
    NOP

    ; R6保存返回地址
    ADDIU3 R2 R6 0x0000    ; 话说,为什么用的是ADDIU3?

    ; r3=IH(高位变成1)
    MFIH R3
    LI R0 0x0080
    SLL R0 R0 0x0000    ; R0=10000000…
    OR R3 R0

    ; 恢复现场
    LI R7 0xBF
    SLL R7 R7 0x0000
    ADDIU R7 0x10    ; R7 = 0xBF10
    LW R7 R0 0x0000
    LW R7 R1 0x0001
    LW R7 R2 0x0002

    ; r7=用户返回地址
    LW_SP R7 0x0000    ; 载入保存在相对SP的内存地址中的返回地址

    ADDSP 0x0001
    ADDSP 0x0001
    NOP
    MTIIH R3    ; 把IH的高位变成1再存回去的意义?
    JR R6    ; 跳转到返回地址(R6和R7的区别?)
    NOP
LW_SP R3 0x00FF    ; 都跳转了还要恢复R3的意义?

初始化

INIT:
    ; 初始化IH寄存器,最高位为1时,允许中断,为0时不允许
    ; IH最高位初始化为0,kernel不允许中断
    LI R0 0x07
    MTIH R0
    ; 初始化栈地址
    LI R0 0x00BF
    SLL R0 R0 0x0000
    ADDIU R0 0x10    ; R0=0xBF10(0xBF10到0xBFFF是系统堆栈区)
    MTSP R0
    NOP

    ; 初始化用户寄存器值(所以用户寄存器是堆栈区模拟出来的?)
    LI R6 0x0
    SW R0 R6 0x0000    ; MEM[0xBF10 + 0] = 0
    SW R0 R6 0x0001
    SW R0 R6 0x0002
    SW R0 R6 0x0003
    SW R0 R6 0x0004
    SW R0 R6 0x0005

OK:
    ; 初始化完成,输出"OK\n"
    MFPC R7
    ADDIU R7 0x0003    ; 在R7中记录返回地址(B TESTW后一条)
    NOP
    B TESTW
    LI R6 0x00BF
    SLL R6 R6 0x0000
    LI R0 0x004F    ; 输出字符"O"
    SW R6 R0 0x0000
    NOP

    MFPC R7
    ADDIU R7 0x0003    ; 在R7中记录返回地址(B TESTW后一条)
    NOP
    B TESTW
    LI R6 0x00BF
    SLL R6 R6 0x0000
    LI R0 0x004B    ; 输出字符"K"(汇编输出两个字符都要写这么长……)
    SW R6 R0 0x0000
    NOP

    MFPC R7
    ADDIU R7 0x0003
    NOP
    B TESTW
    LI R6 0x00BF
    SLL R6 R6 0x0000
    LI R0 0x000A    ; 输出LF
    SW R6 R0 0x0000
    NOP

    MFPC R7
    ADDIU R7 0x0003
    NOP
    B TESTW
    LI R6 0x00BF
    SLL R6 R6 0x0000
    LI R0 0x000D    ; 输出CR
    SW R6 R0 0x0000
    NOP

主程序循环

BEGIN:
    ; 检测命令
    MFPC R7
    ADDIU R7 0x0003
    NOP
    B TESTR
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    LW R6 R1 0x0000    ; 从串口读入
    LI R6 0x00ff
    AND R1 R6    ; 保留从串口读入的后两个字节
    NOP

    ; 检测是否为R命令
    LI R0 0x0052    ; R的ASCII码
    CMP R0 R1
    BTEQZ SHOWREGS    ; 跳到处理R命令的代码部分
    NOP

    ; 检测是否为D命令
    LI R0 0x0044
    CMP R0 R1
    BTEQZ SHOWMEM    ; 跳到查看内存值的模块
    NOP

    ; 检测是否为A命令
    LI R0 0x0041
    CMP R0 R1
    BTEQZ GOTOASM    ; 跳到修改内存值的模块
    NOP

    ; 检测是否为U命令
    LI R0 0x0055
    CMP R0 R1
    BTEQZ GOTOUASM    ; 跳到反汇编的模块
    NOP

    ; 检测是否为G命令
    LI R0 0x0047
    CMP R0 R1
    BTEQZ GOTO_EXECUTE    ; 跳到运行程序的模块
    NOP

    B BEGIN    ; 不断循环主程序
    NOP

; 各处理块的入口(为什么不直接跳转各处理块?)
GOTOUASM:
    B UASM
    NOP
GOTOASM:
    B ASM
    NOP
GOTO_EXECUTE:
    B GO
    NOP

R(SHOWREGS)模块

SHOWREGS:    
    ; R命令,打印R0~R5:打印用户寄存器的值
    LI R1 0x0006    ; R1递减
    LI R2 0x0006    ; R2不变

LOOP:
    LI R0 0x00BF
    SLL R0 R0 0x0000
    ADDIU R0 0x0010    ; R0=0xBF10
    SUBU R2 R1 R3    ; R3=0, 1, 2, 3, 4, 5
    ADDU R0 R3 R0
    LW R0 R3 0x0000    ; 读取第R3个寄存器的值
    ; 发送低八位
    MFPC R7
    ADDIU R7 0x0002
    B TESTW
    NOP    ; 为什么这里的格式和之前不一样……
    LI R6 0x00BF
    SLL R6 R6 0x0000
    SW R6 R3 0x0000    ; 所以每次写串口保存的只有低八位
    ; 发送高八位
    SRA R3 R3 0x0000
    MFPC R7
    ADDIU R7 0x0002
    B TESTW
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000    ; 保存右移之后的低八位

    ADDIU R1 0xFFFF    ; R1--
    BNEZ R1 LOOP
    NOP
    B BEGIN    ; 直接回到BEGIN模块
    NOP

D(SHOWMEM)模块

; 查看内存:从终端接收起始地址和个数,将内存数据传回终端
SHOWMEM:
    ; D 读取起始地址低位到R5
    MFPC R7
    ADDIU R7 0x0002
    B TESTR
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    LW R6 R5 0x0000
    LI R6 0x00FF
    AND R5 R6    ; 保留串口读入的低八位(作为低位)
    NOP

    ; 读取起始地址高位到R1
    MFPC R7
    ADDIU R7 0x0002
    B TESTR
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    LW R6 R1 0x0000
    LI R6 0x00FF
    AND R1 R6    ; 保留串口读入的低八位(作为高位)
    NOP

    ; R1中保存要查看的内存的起始地址
    SLL R1 R1 0x0000
    OR R1 R5

    ; 读取内存单元个数低位到R5
    MFPC R7
    ADDIU R7 0x0002
    B TESTR
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    LW R6 R5 0x0000
    LI R6 0x00FF
    AND R5 R6    ; 保留串口读入的低八位(作为低位)
    NOP

    ; 读取内存单元高位到R2
    MFPC R7
    ADDIU R7 0x0002
    B TESTR
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    LW R6 R2 0x0000
    LI R6 0x00FF
    AND R2 R6    ; 保留串口读入的低八位(作为高位)
    NOP

    ; R2中保存要查看的内存单元个数
    SLL R2 R2 0x0000
    OR R2 R5

READMEM:
    ; 发送内存数据给终端
    LW R1 R3 0x0000    ; R1中保存当前内存地址;R3中保存读出来的16位数据
    ; 发送低八位
    MFPC R7
    ADDIU R7 0x0002
    B TESTW
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    SW R6 R3 0x0000
    ; 发送高八位
    SRA R3 R3 0x0000
    MFPC R7
    ADDIU R7 0x0003    ; 仍然不是很懂加数的个数
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    SW R6 R3 0x0000

    ; 读取下一个内存单元
    ADDIU R1 0x0001
    ADDIU R2 0xFFFF
    BNEZ R2 READMEM
    NOP

    ; R命令执行结束
    B BEGIN
NOP

A(ASM)模块

ASM:
    ; 写入指令:直接读取终端翻译成二进制代码的汇编代码,写入内存中;实际上每次都先读一个内存地址再读一个指令
    ; 读取起始地址低位到R5
    MFPC R7
    ADDIU R7 0x0002
    B TESTR
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    LW R6 R5 0x0000
    LI R6 0x00FF    
    AND R5 R6    ; 保留从串口读入的低八位(起始地址低位)
    NOP
    ; 读取起始地址高位到R1
    MFPC R7
    ADDIU R7 0x0002
    B TESTR
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    LW R6 R1 0x0000
    LI R1 0x00FF    
    AND R1 R6    ; 保留从串口读入的低八位(起始地址高位)
    NOP

    ; R1为写入指令的起始地址
    SLL R1 R1 0x0000
    OR R1 R5

    ; 检测地址是否为0
    LI R0 0x0000
    CMP R0 R1
    BTEQZ GOTOBEGIN    ; 在地址为0(传输结束时)才跳出循环。
    NOP

    ; 读取指令低位到R5
    MFPC R7
    ADDIU R7 0x0002
    B TESTR
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    LW R6 R5 0x0000
    LI R6 0x00FF    
    AND R5 R6    ; 保留从串口读入的低八位(起始地址低位)
    NOP
    ; 读取指令高位到R2
    MFPC R7
    ADDIU R7 0x0002
    B TESTR
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    LW R6 R2 0x0000
    LI R1 0x00FF    
    AND R2 R6    ; 保留从串口读入的低八位(起始地址高位)
    NOP

    ; R2为将要写入的指令
    SLL R2 R2 0x0000
    OR R2 R5

    ; 写入指令
    SW R1 R2 0x0000
    NOP

    ; A命令执行结束
    B ASM    ; 回到开头再读一遍
    NOP

GOTOBEGIN:
    B BEGIN
NOP

U(UASM)模块

UASM:
    ; 反汇编:将需要汇编的地址处的值发给终端处理
    ; 读取反汇编起始地址低位到R5
    MFPC R7
    ADDIU R7 0x0002
    B TESTR
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    LW R6 R5 0x0000
    LI R6 0x00FF    ; 留下从串口读取的低位(起始地址低位)
    AND R5 R6
    NOP
    ; 读取反汇编起始地址高位到R1
    MFPC R7
    ADDIU R7 0x0002
    B TESTR
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    LW R6 R1 0x0000
    LI R6 0x00FF    ; 留下从串口读取的低位(起始地址高位)
    AND R1 R6
    NOP
    ; R1中保存反汇编起始地址
    SLL R1 R1 0x0000
    OR R1 R5

    ; 读取反汇编指令条数低位到R5
    MFPC R7
    ADDIU R7 0x0002
    B TESTR
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    LW R6 R5 0x0000
    LI R6 0x00FF    ; 留下从串口读取的低位(指令条数低位)
    AND R5 R6
    NOP
    ; 读取反汇编起始地址高位到R2
    MFPC R7
    ADDIU R7 0x0002
    B TESTR
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    LW R6 R2 0x0000
    LI R6 0x00FF    ; 留下从串口读取的低位(指令条数高位)
    AND R2 R6
    NOP
    ; R2中保存反汇编指令的条数
    SLL R2 R2 0x0000
    OR R2 R5

READINSTRUCTION:
    ; 读取指令(把指令发送给终端,让它去反汇编)
    LW R1 R3 0x0000
    ; 发送指令低八位
    MFPC R7
    ADDIU R7 0x0002
    B TESTW
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    SW R6 R3 0x0000
    ; 发送指令高八位
    SRA R3 R3 0x0000
    MFPC R7
    ADDIU R7 0x0002
    B TESTW
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    SW R6 R3 0x0000

    ; 读取下一条指令
    ADDIU R1 0x0001
    ADDIU R2 0xFFFF
    BNEZ R2 READINSTRUCTION
    NOP

    ; U命令执行结束
    B BEGIN
NOP

G(GO)模块

GO:
    ; G命令
    ; 读取起始地址低位到R5
    MFPC R7
    ADDIU R7 0x0002
    B TESTR
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    LW R6 R5 0x0000
    LI R6 0x00FF
    AND R5 R6
    NOP
    ; 读取起始地址高位到R2
    MFPC R7
    ADDIU R7 0x0002
    B TESTR
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    LW R6 R2 0x0000
    LI R6 0x00FF
    AND R2 R6
    NOP
    ; R2中保存内存地址,并传给R6
    SLL R2 R2 0x0000
    OR R2 R5
    ADDIU3 R2 R6 0x0000

    ; 关中断(为什么运行用户程序的时候要关中断?)
    MFIH R5
    LI R1 0x0080
    SLL R1 R1 0x0000
    OR R5 R1
    MTIH R5

    ; 保存现场(为什么要保存现场?)
    LI R7 0x00BF
    SLL R7 R7 0x0000
    ADDIU R7 0x0010    ; R7=0xBF10

    SW R7 R0 0x0000
    SW R7 R1 0x0001
    SW R7 R2 0x0002
    SW R7 R3 0x0003
    SW R7 R4 0x0004
    SW R7 R5 0x0005

    ; 执行用户程序
    MFPC R7
    ADDIU R7 0x0003
    JR R6
    NOP

    ; 恢复现场
    LI R7 0x00BF
    SLL R7 R7 0x0000
    ADDIU R7 0x0010    ; R7=0xBF10

    LW R7 R0 0x0000
    LW R7 R1 0x0001
    LW R7 R2 0x0002
    LW R7 R3 0x0003
    LW R7 R4 0x0004
    LW R7 R5 0x0005

    ; 开中断
    MFIH R0
    LI R1 0x007F
    SLL R1 R1 0x0000
    LI R2 0x00FF
    OR R1 R2    ; R1=7FFF
    AND R0 R1
    MTIH R0

    ; 给终端发送结束用户程序提示
    LI R1 0x0007
    MFPC R7
    ADDIU R7 0x0002
    B TESTW
    NOP
    LI R6 0x00BF
    SLL R6 R6 0x0000
    SW R6 R7 0x0000    ; 向终端发送7(响铃)

    ; G命令执行结束
    B BEGIN
NOP


新增一则回应

除非特别注明,本页内容采用以下授权方式: Creative Commons Attribution-ShareAlike 3.0 License