5.18 特殊小节:建立自己的计算机
下面几个问题要暂时离开高级语言编程,打开计算机,看看其内部结构。我们介绍机器语言编程和编写几个机器语言程序。要让这些知识更有价值,我们建立一个计算机(通过软件模拟技术),在其中执行我们的机器语言程序。
5.18(机器语言程序)下面要建立一个 Simpletron
计算机。顾名思义,这是个简单机器,但以后会发现它也是个强大的机器。Simpletron只能运行用Simpletron Machine Language(SML 机器语言)写成的程序。
Simpletron 包含一个累加器(特殊寄存器),存放 Simpletron 用于计算和各种处理的信息。Simpletron处理的所有信息都用“字”处理。字是个带符号的四位十进制数,如 +3364,-1293,+0007,-0001 等等。Simpletron 带有100个字的内存.这些字用其内存单元号00、01…99引用。
运行SML程序之前,要先把程序装入内存。每个SML程序中的第一条指令(或语句)一般放在内存单元00处,模拟器会从该地址开始执行。
用SML编写的每条指令都占用Simpletron内存中的一个字。(因此,指令是带符号的四位十进制数)。我们应假设SML指令的符号总是正号,但数据字的符号可正可负。
Simpletron 内存中的每个内存单元可以包含一条指令、程序使用的数据值或未用(未定义)内存区。每个SML指令的前两位是操作码,指定要进行的操作。图5.37显示了SML操作码。
SML 指令的最后两位是操作数.是要操作的字的特定内存单元。
下面考虑几个简单 SML 程序。第一个 SML 程序(例1)从键盘读取两个数.并计算和打印这两个数的和。指令+1007从键盘读取第一个数并将其放在内存单元07(初始化为0),然后指令 +1008
读取下一个数并将其放在内存单元08。
装人命令 +2007
将第一个数放(复制)到累加器中,加法指令+3008将第二个数与累加器中的数相加。所有SML算术运算指令都把结果留在累加器中。保存指令+2109将结果复制回内存单元 09
,然后写指令+1109取得并打印这个结果(带符号的四位十进制数),停止指令+4300终止程序执行。
输入/输出操作:
const int READ = 10 从键盘读一个字到特定内存单元
const int WRITE = 11; 从特定内存单元将字装入累加器
装入/保存操作:
const int LOAD = 20; 从特定内存单元将字装入累加器
const int STORE = 21; 将累加器中的字存放到特定内存单元
算术运算:
const int ADD = 30; 将特定内存单元中的字加上累加器中的字(结果保留在累加器中)
const int SUBTRACT = 31; 将累加器中的字减去特定内存单元中的字(结果保留在累加器中)
const int DIVIDE = 32 将累加器中的字除以特定内存单元中的字(结果保留在累加器中)
const int MULTIPLY = 33; 将特定内存单元中的字乘以累加器中的字(结果保留在累加器中)
控制转移操作:
const int BARANCH = 40; 转移到特定内存单元
const int BRANCHNEG = 41; 在累加器为负值时转移到特定内存单元
const int BRANCHZERO = 42; 在累加器为0时转移到特定内存单元
const int HOLT = 43; 停止,程序已完成任务
图 5.37 SML 机器语言操作码
例1 地址 | 数值 | 指令 |
---|---|---|
00 | +1007 | (Read A) |
01 | +1008 | (Read B) |
02 | +2007 | (Load A) |
03 | +3008 | (Add B) |
04 | +2109 | (Store C) |
05 | +1109 | (Write C) |
06 | +4300 | (Halt) |
07 | +0000 | (VAriable A) |
08 | +0000 | (VAriable B) |
09 | +0000 | (Result C) |
例2 中的 SML 程序从键盘读取两个数,并确定和打印其中较大的数。注意这里用指令 +4107 作为打件控制转移,与 C++ 中的 if 语句相似。
例2 地址 | 数值 | 指令 |
---|---|---|
00 | +1009 | (Read A) |
01 | +1010 | (Read B) |
02 | +2009 | (Load A) |
03 | +3110 | (Subtract B) |
04 | +4107 | (Branch negative to 07) |
05 | +1109 | (Write A) |
06 | +4300 | (Halt) |
07 | +1110 | (Write B) |
08 | +4300 | (Halt) |
09 | +0000 | (VAriable A) |
10 | +0000 | (Variable B) |
a)用标记控制循环读取10个正数值,计算和打印它们的和。
b)用计数器控制循环读取7个数,有正有负,计算和打印它们平均值。
c)读取一系列数,并确定和打印其最大数。第一个读取的数表示要处理多少个数。
5.19 (计算机模拟程序)这里要建立一台计算机,当然不是硬件连接,而是用软件模拟,建立Simpletron的软件模模型。这个Simpletron模拟程序可以将读者的计算机变成Simpletron并可以实际运行。测试和调试练习5.18所编写的SML程序。
这个 simpletron 模拟程序运行时,开始打印:
*** Welcome to Simpletron! ***
*** Please enter your program one instruction ***
*** (or data word) at a time. I will type the
*** location number and a question mark (?). ***
*** You then type the word for that location. ***
'** Type the sentinel -99999 to stop entering
*** your program. ***
用100个元素的单下标数组 memory 模拟 Simpletron 内存。然后假设运行这个 simpletron 模拟程序,检查一下练习 5.18 例2的程序:
O0 ? +1009
O1 ? +1010
02 ? +2009
03 ? +3110
o4 ? +4107
05 ? +1109
06 ? +4300
07 ? +1110
08 ? +4300
09 ? +0000
10 ? +0000
11 ? -99999
*** Program loading completed ***
*** Program execution begins ***
这时 SML 程序已放进数组 memory 中,Simpletron 开始执行编写的SML程序。程序从内存单元00的指令开始执行,和C++中一样,按顺序继续执行,但可以通过控制转移转入程序的其他部分。
利用变量 accumulator 表示累加寄存器。用变量 counter 跟踪内存中包含所执行指令的内存单元。用变量operationCode表示当前正在进行的操作,即指令宇的左边两位。用变量 operand 表示操作当前指令的内存单元即指令字的右边两位。不要直接从内存中直接执行指令,而是将下一个要执行的指令从内存中转到变量instructionRegister中。然后选取左边两位,放进 operationCode 中,选取右边两位,放进operand中。Simpletron开始执行时,所有特殊寄存器都初始化为0。
下面看看第一个SML指令(内存地址00的指令 +1009)的执行过程,这是条指令执行循环(instruction execution cycle)。
counter 指示下一个要执行指令的内存单元。我们用下列 C++ 语句从 memory 中取得该地址的内容:
instructionRegister = memory[counter];
下列语句从指令寄存器读取操作码和操作数:
operationCode = instructionRegister / 1OO;
operand‘= instructionRegister % 100;
现在 Simpletron 必须确定操作码是读(而不是写、装入等等)。switch 结构区分 SML 的十二种操作。
在 switch 结构中,各种 SML 操作指令的模拟如下(其他留给读者练习):
读(read): cin>>memory [ operand ];
装入(load): accumulator= memory[operand];
加(add): accumulator += memory[operand];
转移(branch): 稍后介绍转移
停止(halt): 这条指令打印下列消息
*** Simpletron excution terminated ***
然后打印每个寄存器的名称与内容即内存的完整内容。这种打印输出称为计算机转储(computer dump)。为了帮助编制转储功能,图5.38显示了一个示例转储格式。注意执行Simpletron程序之后,计算机转储显示指令实际值和终止执行时的数据值。
下面继续执行我们程序的第一条指令,内存单元00中的+1009。前面曾介绍过,switch语句用下列C++语句模拟这个过程:
cin >> memory[ operand ];
执行cin之前,屏幕上显示一个问号(?),提示用户输入。Simpletron等待用户输入一个值并按Return键。然后将这个值读取到内存单元09。
这时,第一条指令模拟已经完成。余下的就是准备让Simpletron执行下一条指令。由于刚刚完成的指令不是控制转移,因此只要增加指令计数器寄存器,如下所示:
++counter;
这就完成了第一条指令的模拟执行。整个过程(即指令执行循环)重新开始,读取下一个要执行的指令。
现在考虑如何模拟分支结构(控制转移),只要调整指令计数器的值即可。因此,无条件转移指令(40)可以用switch模拟如下:
counter = operand;
条件(累加器为0则转移)指令模拟如下:
if( accumulator == O )
counter = operand;
这时就可以实现 Simpletron 模拟程序和运行练习5.18中编写的每个程序了。可以在SML中增加其他特性,并在 Simpletron 模拟程序中提供这些特性。
Simpletron 模拟程序应检查各种错误。例如,程序装入期间,用户输入 Simpletron 程序的 memory 中的每个数应在-9999到+9999之间。Simpletron模拟程序应当用while循环测试输入的每个数值是否在这个范围中,如果不是,则提示用户重新输入,直到输入正确的值。
REGISTERS:
accumulator +0000
counter 00
instructionRegister +0000
operationCode 00
operand 00
MEMORy:
0 1 2 3 4 5 6 7 8 9
0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
10 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
20 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
30 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
40 +0000 +0000 *0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
50 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
6O +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
70 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
80 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
90 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
图 5.38 示例转储
在执行过程中,Simpletron 模拟程序应检查各种严重错误,如除数为0、执行无效操作码、累加器溢出(即算术运算的结果不在-9999到+9999之间)等等。这种严重错误称为致命错误。发现致命错误时,Simpletron 模拟程序应打印下列错误消息:
*** Attempt to divide by zero ***
Simpletron execution abnormally terminated ***
并按前面介绍的格式打印完整的计算机转储,这样可以帮助用户找到程序中的错误。