
一开始其实我是一头雾水的,完全不知道要干什么,不过看了几个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。同时,这样还可以处理大小端,或者其他的内存计算。