计算机系统要素 C4

存储

D、A、M

Hack 机器包含两个16位寄存器——D 和 A,分别是 Data 和 Address 的缩写。他们本质上是一样的,用途的不同是分配出来的。

如当访问 M 这个符号的时候,总是以 A 寄存器的值作为地址去操作内存。即 M 解释为 Memory[A]

又如 goto 指令,跳转到的位置也是指令存储器的 A 地址,即 goto 跳到 InstructionMemory[A]

最后,A 寄存器也可以像 D 寄存器一样用于存储数据值,而到底它存的是什么含义,其实取决于上下文的解释。

指令

从寄存器 A 的用法上可以看出,Hack 程序的很大一部分指令应该都是在操作地址。写一个地址,操作一下,写一个地址,操作一下这样。因此他的指令也分为两类:

A 指令

A 指令的第一位是 0,后面跟 15 位值。表示将 A 寄存器设为这个值。

它的意义首先是允许了输入常数(15位),这是在将 A 寄存器的值做数字处理的时候;其次为操作内存、跳转指令 提供地址。

因此也可以推导 Hack 机器的内存地址空间是 15 位的。

C 指令

C 指令第一位是 1,之后两位没有使用。后面的 13 位被分为三个域:7 位的计算域(Comp)、3 位的目标域(Dest)、3 位的跳转域(Jump)。

为什么空出两位没有使用,而目标域和跳转域都是 3 位呢?

目标域是因为 C 指令操作的存储对象只有三个:A 、D 、M。每个对象都需要表达 “存入” 或 “不存入” 两种选择,因此需要三个位的域宽。

因为计数器的存在,程序的默认执行流程是顺序执行的,即执行完 ROM[N] 后执行 ROM[N+1]。跳转域需要表达下一步指令是否需要跳转的问题,且跳转的本质是实现流程控制,故该域还需要能够对计算域输出的值做出反应。计算域的输出值范围有 16 位宽,判断条件则是一个确定的数,比如 10086。那么输出值和判断条件之间的关系就应该定义三种:小于、等于和大于。这三种关系的输出值之间互相独立,故每种都需要表达 “跳” 或 “不跳” 两种选择,因此需要三个位的域宽。

另外两点:1. 至于说输出值和判断条件之间的关系为什么定义三种,是出于效率考虑的,两个数字之间的关系也可以表达为 两种:等于或不等于。但这样在做大于或小于判断的时候就会非常麻烦。 2. 因为指令已经没有空间存放判断条件的比较数,因此跳转域实际使用的条件值是 0,即你需要先减一次比较数。

计算域有 7 位,用于控制 ALU。记得 A 寄存器有两种意义吗,它既可以解释为值,也可以解释为地址。这个解释操作其实就是用计算域的第一位控制的,通常管他叫 a-bits,为 0 时表示输入值,为 1 时表示输入 M[A] 的值。剩余 6 为称为 c-bits ,用于控制 ALU 函数的选择。具体对应需要参考表 4-3。但如果你看的是中文版,注意该表右半部助记符有个错误,应该是 (当a=1)。

说到勘误,4.1.1 的寄存器段还有一个错误,寄存器的宽度与内存相等,都是 16 位。因此那里应该是 “只存储 1 个值”,而不是书中的 “只存储 1 位”.
以及本章错误真鸡儿多,感觉跟前三章不是同一个人翻的。

汇编

本章和附录都没有一个很完善的汇编参考或教程,因此在做题的时候虽然思路是有的但不知道应该怎么写,或者不应该怎么写。于是把章内的 sum(1..100) 程序抄了一遍,并且用编译器和CPU模拟器尝试跑了一下。

原始的 asm 是这样的:(OSC 的 markdown 渲染有点傻逼)

    [@i](https://my.oschina.net/izhuchao)		// i refers to some mem. location
    M=1
    [@sum](https://my.oschina.net/Asum)
    M=0
(LOOP)
    [@i](https://my.oschina.net/izhuchao)
    D=M
    [@100](https://my.oschina.net/laoka)
    D=D-A
    [@END](https://my.oschina.net/u/567204)
    D;JGT		// if (i-100)>0 goto END
    @i
    D=M
    @sum
    M=D+M
    @i
    M=M+1
    @LOOP
    0;JMP		// goto LOOP
(END)
    @END
    0;JMP		// infinit loop

我疑惑的点在于,这些 isum 啊的变量到底是怎么分配内存的?还有 (LOOP) 这种标记算什么?为什么有缩进,他们有具体的含义吗?当把它编译之后,得到的文件是这样的:

0   @16
1   M=1
2   @17
3   M=0
4   @16
5   D=M
6   @100
7   D=D-A
8   @18
9   D;JGT
10  @16
11  D=M
12  @17
13  M=D+M
14  @16
15  M=M+1
16  @4
17  0;JMP
18  @18
19  0;JMP

程序自动为 i 分配了 16(10000),而 sum 分配了 17(10001)。之后不论我怎么修改程序,发现总是按顺序从 16 开始赋值,即使你的程序写成有冲突的样子,比如:

@a
M=1
@16
M=2

它依然会编译成:

0   @16
1   M=0
2   @16
3   M=1

这个故事告诉我们,不要随便硬编码内存地址,尽量都通过符号来分配。

后记:眼瞎如我,其实有一段(4.2.4)讲解符号的。

套路

把题做完后总结了一些套路:

  1. 所有变量存在内存里,使用符号指定
  2. 只有一个A寄存器,它不能既用来存地址,又用来存值。这意味着:当使用跳转命令时,A 的值只能是跳转的目标地址,因此表达式只能是 D 或常数。即当使用 C 指令时,总是要先把值存入 D 或使用常数。
  3. 流程控制全靠跳转,跳转全靠比较。看到有流控的需求的时候,第一反应把程序分几段。
  4. 因为 hack 机器只有一个 D 和 一个 A 寄存器,写汇编就像玩汉诺塔,总想着怎么倒数据
  5. 将 M 的值赋给 A 要使用 C 指令而不是 A 指令,即 A=M, 而不是 @A

转载于:https://my.oschina.net/lionets/blog/2906856

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值