第17章:自动操作

简单的自动加减法器

我们希望通过一组指令(下图)来实现对硬件的控制。

操作码

代码

Load (加载)

10h

Store (存储)

11h

Add (加)

20h

Substract (减)

21h

Halt (停止)

FFh

注:h表示16进制。

我们先实现一个最简单版本的自动加减法器,直接上图:

自动加减法器V1

以下是一些说明:

  1. 通过一个与时钟相连的16位计数器,我们能够从头开始逐个访问每个地址中的内容。两个64K×864K \times 8的RAM分别用于储存指令和数据,指令和对应的数据储存在不同RAM的相同位置。

  2. 控制取反器的信号C0C_0用来选择加法或者是减法,2-1选择器选择数据是直接读入到锁存器还是读入到加法器中,通过W写信号将锁存器内容存储回RAM。通过以上的信号和其他一些多选1逻辑门,我们可以将指令RAM中读取到的指令转化为硬件信号。

  3. 如下图的例子所示,如果我们想将56h和2Ah相加,结果再减去38h,我们只需要在对应的地址上输入对应的代码即可。

地址

代码

操作

地址

数据

0000h:

10h

Load

0000h:

56h

0004h:

20h

Add

0004h:

2Ah

0008h:

21h

Substract

0008h:

38h

000Ch:

11h

Store

000Ch:

计算结果

0010h:

FFh

Halt

0010h:

--

进阶的自动加减法器

我们希望能够处理更高位数的加减法,并且不使用指令地址对应数据地址指令这样笨拙的方法。我们将每个指令的大小从1字节扩展到3字节,除了首字节仍旧存放指令外,剩下两个字节用于存放16位的地址。

为了处理更高位的加减法,如16位加减法,我们可以将其拆解为低8位加减法和高8位加减法,注意到高8位加减法还需要处理低8位的进位/借位信息,因此我们将原有的指令集扩展成7个:

操作码

代码

Load (加载)

10h

Store (存储)

11h

Add (加)

20h

Substract (减)

21h

Add with Carry (进位加法)

22h

Substract with Borrow (借位减法)

23h

Halt (停止)

FFh

新的电路示意图如下:

自动加减法器V2
  1. 我们将指令和数据放在同一个RAM中,使用四个锁存器,通过一个选择器来决定每次是将数据加载到指令锁存器、低位地址锁存器、高位地址锁存器还是数据锁存器。

  2. 通过一个2-1选择器来决定寻址是从地址锁存器中获得还是从16位计数器中获得。

  3. 每次我们能够读取一个8位数据,在现有的结构下,执行一个指令需要4次读取数据或是类似的操作,即需要花费4个时钟周期。

  4. ?此处作者并没有说明如何实现在4个锁存器中选择,并且16位计数器的计数会在第四个时钟周期时暂停。

乘法的实现

我们首先考虑通过跳转指令(30h)来实现指令存储地址的不连续性。通过跳转指令和其后的两位地址,我们可以将指令地址强行跳转至指定位置继续执行。也就是说,计数器会被强制输出跳转指令后的16位地址。回忆14章的边沿触发器中的预置功能,我们可以将地址锁存器与16位计数器的预置装置相连接,实现地址的强制跳转。(应该也可以利用类似的设置实现计数器暂停?)

有预置功能的边沿触发器
考虑跳转自动加减法器电路

我们现在考虑A7h(263)和1Ch(28)相乘得到16位数的结果,我们将所有的数字都用16位表示,并存储于此:

地址

数据

1000h

00h

16位乘数保存在此

A7h

1002h

00h

16位被乘数保存在此

1Ch

1004h

00h

16位乘积保存在此

00h

指令则如下:

地址

数据

0000h

10h

将1005h处的数据载入到累加器

10h

05h

0003h

20h

将1001h处的数据加到到累加器

10h

01h

0006h

11h

将累加器的结果保存到1005h处

10h

05h

0009h

10h

将1004h处的数据载入到累加器

10h

04h

000Ch

22h

将1000h处的数据进位加到到累加器

10h

00h

000Fh

11h

将累加器的结果保存到1004h处

10h

04h

0012h

...

...

通过以上的操作,我们将1000h处的数据加载到了1004h处,也即实现了A7h×1\mathrm{A7h} \times 1

我们考虑引入条件跳转。此处我们考虑的是零条件跳转,即累加器的八位结果全部为0时,才进行跳转。我们引入一个1位锁存器,将其称为零锁存器(zero latch),结构如下:

零锁存器结构

通过一个8输入-或非门,只有全部为0时输出才为1,并且与进位锁存器的时钟输入一样,只有当Add、Add with Carry、Substract、Substract with Borrow这几个指令执行的时候,锁存器才锁存一个值(?如何实现),将其称为零标志位(zero flag)。在引入进位锁存器之后,我们可以引入新的几条指令:

操作码

代码

Load (加载)

10h

Store (存储)

11h

Add (加)

20h

Substract (减)

21h

Add with Carry (进位加法)

22h

Substract with Borrow (借位减法)

23h

Jump (跳转)

30h

Jump if Zero (零转移)

31h

Jump if Carry (进位转移)

32h

Jump if not Zero (非零转移)

33h

Jump if not Carry (非进位转移)

34h

Halt (停止)

FFh

并且我们可以利用非零转移实现乘法:

地址

数据

...

...

...

0012h

10h

将1003h处的数据载入到累加器

10h

03h

0015h

20h

将1001h处的数据加到到累加器

00h

1Eh

0018h

11h

将累加器的结果保存到1003h处

10h

03h

001Bh

33h

如果零标志位不为0,则跳转至0000h处

00h

00h

001Eh

FFh

停止

注意到我们是将1Ch与001Eh处的FFh相加,相当于将1Ch减1。这样我们就通过机器码的编程,实现了乘法的操作。

我们可以通过助记符来标记不同的指令:

操作码

代码

助记符

Load (加载)

10h

LOD

Store (存储)

11h

STO

Add (加)

20h

ADD

Substract (减)

21h

SUB

Add with Carry (进位加法)

22h

ADC

Substract with Borrow (借位减法)

23h

SBB

Jump (跳转)

30h

JMP

Jump if Zero (零转移)

31h

JZ

Jump if Carry (进位转移)

32h

JC

Jump if not Zero (非零转移)

33h

JNZ

Jump if not Carry (非进位转移)

34h

JNC

Halt (停止)

FFh

HLT

举个例子:

注意到是否加[]的区别,[地址]表示取地址中的数据,而没有[]的地址表示地址本身。

Last updated