一开始其实我是一头雾水的,完全不知道要干什么,不过看了几个Example之后,也自己能猜的大概,然后试试,果真如此,非常开心。
下面就来一句句地实现。
首先指令是这样的格式:

现在是我来设计指令,那一切都是听我的,OP-CODE就完全是我自己定义的了,我想让他是多少就是多少。
第一条指令是INL,就是INPUT的低 $8$ 位送到寄存器DR,我想让这句话的OP-CODE是 $001$。
而RS在这里是不适用的,因为没有源寄存器,就是从I/O获得的数据,我用 $XX$ 表示。
RD是目标寄存器,是编码的时候确定的,我用 $YY$ 表示。
$0010XXYY$ 就是这条指令。
由于 $XXYY$ 是不确定的,所以显然不能用来散转,那么散转的指令就应该是 $0010$,是 $4$ 位操作码,因此,运用散转规则,得到的散转地址是 $11001000000$。

散转地址用 $16$ 进制表示,就是 $640$。
这意味着,我第一句指令,应该写在散转地址为 $640$ 的地方。
这之前的都不需要动,程序复位以后这两句话是没错的。

找到微地址为 $640$ 的地方。
这里要执行一句话,是从I/O到寄存器,取低 $8$ 位,也就是可以认为是偶字节传递。
目的编码应该是通用寄存器。

原编码应该是I/O。

因为是取第 $8$ 位,所以可以认为是偶字节传递。

这条指令到这里就执行完了,是一条长度为 $1$ 的指令。
因此,执行完以后,$\mu PC$ 需要做结尾变址,且 $PC = PC+1$。


单击“修改”,这条指令就被录入了,是在 $640$ 的地方。

接下来去指令系统定义这条指令。
这条指令的助记码是INL,需要的操作数只有一个寄存器R0,长度为 $1$。
指令码的话,如上所言,我定义的是 $0010XXYY$,用 $16$ 进制表示就是 $2X$,不妨就设置为 $20$。
那么,这条指令就应该被定义为:
INL R0 20 1
事实上,正如上面说的,指令码是 $2X$,所以其实定义为 $22$ 或者 $29$ 或者其他的形式都是可以的,最后程序装载、运行得到的散转地址都是 $640$,都能够正常运行。
为此,我还特意尝试过,确实是可以的。


但是定义操作码为 $30$ 显然不行,那就散转到 $660$ 去了。
类似地,定义第二条指令。
OUTL SR,是将源寄存器的低 $8$ 位取出,输出到I/O区域。
上面我用掉了 $001$,现在我想用 $010$。
那么这条指令就是 $0100XXYY$,取 $0100$ 进行散转,得到的散转地址是 $11010000000$,用 $16$ 进制表示就是 $680$,所以这一条指令应该写在散转地址为 $680$ 的地方。
是将寄存器的值输出到I/O,所以源地址是寄存器,目的地址是准双向输入输出,取的是低 $8$ 位,所以总线规则是偶字节传递,这是单条指令,执行完就好了,所以 $PC++$ 且 $\mu PC$ 是结尾变址回到 $0001$。


类似地,定义指令系统。
指令码应该是 $0100XXYY$,所以就是 $4X$,我定义的是 $40$。
OUTL R0 40 1
第三条指令,JMP。
下面轮到 $011$ 了,那就是 $11011000000$,换成 $16$ 进制是 $6C0$。
因为地址是 $16$ 位的,所以势必需要分批次将数据读入。
且,这条指令的长度为 $3$,其中第 $1$ 个字节是指令,后面 $2$ 个字节都是数据,因此,应该要做 $PC=PC+3$。
第 $1$ 步,由于要读内存,所以要先取出 $PC$ 的值放到 $AR$,这时候 $PC$ 做第 $1$ 次递增,微地址递增。
因此,指令是 $4307C0$。
第 $2$ 步,将内存里面的值取出一个字节来,放到低位,那就相当于是奇字节到偶字节,我放到了寄存器BX里面。
这时候,源地址就是内存,注意是RAM而不是ROM,所以选择了源是MRD以后,需要选上E/M,目的地址是我选择了BX, 都无所谓,总线规则是奇字节到偶字节。
因此,指令是 $850400$。
第 $3$ 步,和第 $1$ 步是一样的,指令是 $4307C0$。
第 $4$ 步,要读内存里面的下一个字节了,放到高位,那就相当于是奇字节到奇字节,与第 $2$ 步是类似的,只要稍微改动一点,指令是 $840400$。
第 $5$ 步,数据准备完毕就可以跳了,跳的本质上就是用跳转到的地址覆盖掉 $PC$ 里面的值,这样的话下一次 $PC$ 取指取出来的就是跳过去那个地方的下一条指令了。
所以,执行的是,$PC$ 装载。
我是把数据放在BX的,所以应该是源地址选择ALU。

而ALU是算数逻辑单元,现在我不需要计算 $A+B$ 或者 $A \& B$之类的东西,我只想要BX的值,所以选择的是

目的地址应该是选择 $PC$ 装载。

微变址是结尾变址,因为已经执行完了。
$PC$ 装载了,自然也不存在保持还是增量这一说。

因此,指令是 $C039F2$。

回去定义指令系统。
操作数是内存,所以是*,指令的长度是 $3$,操作码我刚才定义的是 $0110$,也就是$6X$,所以我写的是 $60$。
JMP * 60 3
指令全部设计好了,回过头看一眼,所有的微地址都是唯一的,不冲突,所以满足要求。
下面就可以编写汇编代码了。
这个就非常简单了。
data segment ;将程序装载到数据存储器
assume ds:data
org 0
start: INL r0
OUTL r0
JMP start
data ends
end start
装载,看一眼,好像还挺正常。


微单步执行,正常取出第 $1$ 条指令 $20$,散转,得到 $640$,跳过去了。

然后执行INL,我输入的是 $5936$,取低 $8$ 位,放到了寄存器。

然后正常回去,取下一条指令,并产生下址。


我重置I/O为 $0000$,观察得到低 $8$ 位输出成功。

然后是JMP。


这时候BX的值已经是要回去的地址了,$0000$,然后ALU将BX的值存放到PC。

然后就跳回去了,回到起点了,$0000$。实现了循环往复。

程序到此就结束了。
最后完整的代码附于本文文末。
下面就是画指令流程图。

机器指令格式在上面已经解释过了。
INL是 $XXX0YYZZ$,OUTL是 $XXX0YYZZ$,JMP是 $XXX0YYYY[ADDR][ADDR]$。
至此,作业就写完了。
下面玩一些好玩的。
例如,我想从输入获得一个数,放到AX,从输入获得一个数,放到BX,然后执行 $AX+BX$,结果存到SP。




也是完全可以的。
最后附上代码:
data segment ;将程序装载到数据存储器
assume ds:data
org 0
start: INL r0
OUTL r0
JMP start
data ends
end start
INL R0 20 1 OUTL R0 40 1 JMP * 60 3
| 微地址 | 微指令 | 表达式 | μPC |
|---|---|---|---|
| 0000 | 000000 | +1 | |
| 0001 | 0307C0 | AR=PC | +1 |
| 0002 | 800405 | IR=RAM | 0600 |
| 0640 | 4F0392 | REG=IO, PC++ | 0001 |
| 0680 | 4B0692 | IO=REG, PC++ | 0001 |
| 06C0 | 4307C0 | AR=PC, PC++ | +1 |
| 06C1 | 850400 | BX=RAM | +1 |
| 06C2 | 4307C0 | AR=PC, PC++ | +1 |
| 06C3 | 840400 | BX=RAM | +1 |
| 06C4 | C039F2 | PC=B | 0001 |

ROM和RAM的区别是什么啊 是指令放在ROM中 数据放在RAM中吗
不太理解为什么 JMP addr是从内存中读地址 为什么不是直接把addr赋值给PC
@hhh跳转,本质上是用一个地址覆盖掉 PC 的值,这样下一次执行的语句就是目标地址。
但是你没有办法把内存的值直接装载到 PC,你只能把寄存器或者 ALU 出来的东西装载到 PC,所以我们先把内存读到 BX,然后把 BX 装载到 PC。同时,这样还可以处理大小端,或者其他的内存计算。