《编码:隐匿在计算机软硬件背后的语言》TXT


《编码:隐匿在计算机软硬件背后的语言》 TXT

 

第一章  电筒密谈

假若你才10岁,你的好朋友与你临街而住,而且你们卧室的窗户面对着面。每天晚上,当父母像平常一样很早催你上床睡觉

时,你可能还想与好朋友交流思想、发现、小秘密、传闻、笑话和梦想,没有人可以责备你,毕竟,渴望交流是大多数人的天性。

当你们卧室还亮着灯时,你和你的好朋友可以临窗舞动手臂、打手势或以身体语言来交流思想,但复杂一些的交流就有些困难了。而且一旦父母宣布“熄灯”,交流也就无法继续进行了。

如何联系呢?用电话吗?10岁的小孩子屋里有电话吗?即使有,你们的谈话可能被偷听。如果家里的电脑通过电话线联了网,它可能会提供无声的帮助,不过很不幸,它也不会在你的房间里。

你和朋友采用的方法是用手电筒。所有的人都知道手电筒是为孩子们藏在被窝里看书而发明的,它也适合在黑暗中用来交流。它无声无息,且光的方向性很好,不会从卧室的门缝中泄露而使家人起疑。

用手电筒的光可以交谈吗?这值得一试。一年级你就学过在纸上写字母和单词,把这种方法运用到手电筒上看起来也合情合理。你所需做的就是临窗而站,用光画出字母。画字母‘O’,就打开电筒,在空中画个圈,然后关上开关;字母‘I’则是画竖直的一笔。但是你很快发现这种方法行不通,当你注视来去飞舞的光柱时,会发现在脑海中将它们组合起来不是件容易的事,这些光划成的圈圈杠杠太不准确了。

也许你曾经看过一部电影,影片中两个水手隔海用闪烁的光传递消息。在另一部电影中,一个间谍用镜子反射阳光向一间屋子中被俘获的同伙发送讯息。这就给了你启发,你起先设计一种简单的交流方法,使字母表中的每个字母与一定数目的闪烁相对应。A闪一下,B闪两下,C闪三下,如此递推,Z就闪烁26下。BAD这个词由字母间有间隔的两闪、一闪、四闪组成,这样你不会误以为它是闪七下的字母G了。词间的停顿则比字母间的停顿时间稍长一些。

这看起来很有希望,采用这种方法的优点是你不需要在空中挥舞手电简,只需对准方向按开关就行了;缺点是你试图发送的第一个消息(“Howareyou?”)就需要131次闪烁,更糟的是,你忘了定义标点符号,所以无法表示句尾的问号了。

这离问题的解决已经很近了,你想别人以前肯定也遇到过类似的问题,你解决它的思想一定是正确的。为了解决问题,白天的图书馆之行使你发现了神奇的摩尔斯电码(morsecode),这正是你想要的,即使你不得不重新学习如何“写”字母表中的字母。

以下就是区别:在你发明的体系中,每个字母是一定数目的闪烁,从闪烁一下的A到闪烁26的Z;而在摩尔斯电码中,有长短两种闪烁,当然,这会使摩尔斯电码更为复杂,但它在实际应用中却被证实是更有效的。那句“Howareyou?”现在仅需32次而不是131次闪烁,而且这还包含了问号。

在讨论摩尔斯电码的工作原理时,人们并不说“长闪烁”、“短闪烁”,他们使用“点(dot)”和“划(dash)”,因为这样易于在印刷品上表示。在摩尔斯电码中,字母表中的每一个字母与一个点划序列相对应,正如在下表中你所看到的:

尽管摩尔斯电码与计算机毫不相关,但熟悉它的本质却对深入了解计算机内部语言和软硬件的内部结构有很大的帮助。

在本书中,编码或代码(code)通常指一种在人和机器之间进行信息转换的系统(体系)。换句话说,编码便是交流。有时我们将编码看成是密码(机密),其实大多数编码并不是的。大多数的编码都需要被很好地理解,因为它们是人类交流的基础。

在《百年孤独》的一书的开篇,马尔克斯回忆了一个时代,那时“世界一片混沌,许多事物没有名字。为了加以区别才给事物各个命名。”这些名字都是随意的,没有什么原因说明为什么不把猫称为狗或不把狗称为猫。可以说英语词汇就是一种编码。

我们用嘴发出声音组成单词,这些词可以为那些听得到我们声音,理解我们所用语言的人所听懂,我们称这种编码为“口头语言”或“语音”。对写在纸上(或凿在石头上、刻在木头上或通过比划写在空气中)的词,还有一种编码方式,那就是我们在印刷的报刊,杂志和书籍上看到的字符,称之为“书面语言”或“文本”。在许多语言中,语音和文本间有很强的联系。例如在英语中,字母或一组字母与一定的读音相对应。

手势语言的发明帮助了聋哑人进行面对面的交流。这是一种用手和胳膊的动作组合来表达词语中的单个字母、整个词及其基本概念的语言。对盲人来说,他们可以使用布莱叶盲文(Braille)。这种文字使用凸起的点代表字母,字母串和单词。当谈话内容要被迅速地记录下来时,缩写和速记是很有用的。

人们在相互沟通时使用了各种不同的编码,因为在不同的应用场合,其中的一些较其他的更为简便。例如,语言不能在纸上存储,所以使用了文字;语言、文字不适合用来在黑夜中安静地传递消息,故摩尔斯电码是一个方便的替代品。只要一种编码可以适用于其他编码所不能适用的场合,它就是一种有用的编码。

以后将看到,计算机中使用了不同的编码来传递和存储数字、声音、音乐、图像和视频(电影)。计算机不能直接处理人类世界的编码,因为它不能模拟人类的眼睛、鼻子、嘴和手指来接收信息。尽管这些年来计算机的发展趋势使我们的桌上电脑具有捕获、存储、处理和提供人类交流中所使用的各种信息的能,而且不论这些信息是视觉的(文字和图片)、听觉的(语言、声音及音乐)还是两者的混合(动画和电影)。所有这些信息都要求使用它们自己的编码方式,正如交谈需要使用人的某些器官(嘴和耳朵),而书写和阅读则需要使用另外一些器官(手和眼睛)一样。

用手电筒发送摩尔斯电码时,电筒的开关快速地合开代表一个点,让电筒照亮稍长的时间则代表一个划。举例来说,发送字母A,要先快速地合开开关,然后再稍慢些合开。在发送下一个字母前要有短暂的停顿。约定划的时间大约是点的3倍。例如,如果点的照亮时间为1秒,那么划就是3秒。(实际上,摩尔斯电码的传递速度要快得多。)接收者看到了短闪和长闪就知道是A。

摩尔斯电码中点划之间的间隔是极为关键的。例如,发送字母A时,点划之间的间隔应与一个点的时间大致相同(如果点的时间是1秒,那么间隔的时间也是1秒)。同一个词中字母间间隔稍长,约为划的持续时间(或者3秒,如果那是划的持续时间的话)。下面是单词“hello”对应的摩尔斯电码,图中示意了字母间的间隔(隙):

单词之间相隔大约2倍于划的时间(如果划是3秒,那么间隔即为6秒)。下面是“hithere”对应的编码(码字):

手电筒开和关的时间长度并没有限定,这取决于点的时间长度,点长又由手电筒开关触发的速度和摩尔斯电码发送者记忆电码的熟练程度来决定,熟练发送者的划也许与生手的点等长。这个小问题会使接收电码有些困难,但在一两个字母之后,接收者通常就可以辨认出哪个是点,哪个是划了。

粗看起来,摩尔斯电码的定义—这里所谓的定义是指与字母表中的字母相对应的各种点划序列—与打字机字母的排列一样是随意的。但仔细观察后你会发现不完全如此,简短的码字分配给了使用频率较高的字母,例如E和T,爱赌博的人和“财富之轮”爱好者可能一下就注意到了这个问题;不常用的字母如Q和Z(它们在赌局中是10点)则分配以较长的码字。

几乎所有人都知道一点儿摩尔斯电码,国际遇险信号SOS的摩尔斯电码为“三点三划三点”。SOS并非缩写,选择它仅仅因为它有一个易记的摩尔斯电码序列。第二次世界大战中,英国广播公司选用贝多芬第五交响曲中的片段作为节目前奏—BAH、BAH、BAH、BAHMMMMM,听起来颇像摩尔斯电码中V(代表Victory)的码字。

摩尔斯电码的一个缺点是它没有对大小写字母进行区分。除表示字母外,摩尔斯电码还用5位长的码字来表示数字:

这些数字的码字看起来还有些规律(相对于字母对应的码字而言)。大多数标点符号的码字采用5位、6位或7位的码长:

对欧洲一些语言中的重音字母以及一些有特殊用途的缩写定义了特别的码字,SOS就是这样一个缩写:发送时每个字母的码字之间仅有一点的时间间隔。如果有特制的用于发送摩尔斯电码的手电筒,你和朋友之间的交流就方便多了。这种手电筒除了常有的开关,还有一个按钮,按压按钮就可以控制电筒的亮灭。经过练习后,你们每分钟可以发送和接收5~10个单词。虽然仍比交谈慢(大概每分钟100个词左右)但已足够用了。

当你和朋友最终熟记了摩尔斯电码时(这是唯一精通发送接收的方法),你也可以用它代替日常用的语言。为了达到最高的速度,可以发“滴(dih)”音代表点、“嗒(dah)”音代表划。摩尔斯电码同样也可将文字简化为用点和划两个符号表示。

以上的关键在于“两”这个词—“滴、嗒”两个声音,“点、划”两种方式。实际上任何两种不同的东西经过一定的组合都可以代表任何种类的信息。

 

第二章  编码与组合

      摩尔斯电码由萨缪尔·摩尔斯(1 7 9 1 —1 8 7 2)发明,本书后面会在多处提到他。摩尔斯电码是随着电报机的发明而产生的,电报机我们以后也还要做详尽的说明。正如摩尔斯电码很好地说明了编码的本质一样,电报机也提供了理解计算机硬件的良好途径。

    大多数人认为摩尔斯电码的发送易于接收,即使你没有记住摩尔斯电码,也可以方便地借助下面这张按字母顺序排列的表发送:

       接收摩尔斯电码并将其翻译回单词比发送费时费力多了,因为译码者必须反向地将已编码的“滴-嗒”序列与字母对应。例如,在确定接收到的字母是“Y ”之前,必须按字母逐个地对照编码表。

      问题是我们仅有一张提供“字母→摩尔斯电码”的编码表,而没有一张可供逆向查找的“摩尔斯电码→字母”译码表。在学习摩尔斯电码的初级阶段,这张译码表肯定会提供很大的便利。然而,如何构造译码表却毫无头绪,因为我们似乎无法找出这些按字母顺序排列的“滴-嗒”序列的规律。

      那么忘记那些字母序列吧,也许按照码字中“滴”“嗒”的个数来排列会是个更好的尝试。例如,仅含一个“滴”或“嗒”的摩尔斯电码序列只可能代表E 或T 这两个字母之一:

 

     两个“滴”或“嗒”的组合则代表了4 个字母I 、A 、N 、M

     三个“滴”或“嗒”的序列代表了8 个字母:

     最后(如果不考虑数字和标点符号的摩尔斯电码),四个“滴”或“嗒”的序列则共代表了1 6 个字母:

      四张表共包括2 + 4 + 8 + 16 =3 0 个编码,可与3 0 个字母相对应,比拉丁字母所需的2 6 个字母还多了4 个。出于这个原因,在最后一张表中,你可能注意到有4 个编码与重音字母相对应。

      在翻译别人发送的摩尔斯电码时,上面4 张表提供了极大的便利。当你接收到一个代表特定字母的码字时,按其中含有的“滴”“嗒”个数,至少可以跳到其对应的那张表中去查找。每张表中,全“滴”的字母排在左上角,全“嗒”的字母排在右下角。你注意到4 张表大小的规律了吗?每张表都恰好是其前一张表的两倍大小。这其中包含的意义是:前一张表的码字后加一个“滴”或加一个“嗒“,即构成了后一张表。    可以按下面的方式总结这个有趣的规律:

      四张表中每张码字数都是前一张的两倍,那么如果第一张表含2 个码字,第二张表则含2 ×2个码字,第三张表2 ×2 ×2个码字。以下是另一种表达方式:

     当然,如果遇到数的自乘,可以用幂表示,例如2 ×2 ×2 ×2 可以写成24。数字2 、4 、8 、1 6 分别是2 的1 、2 、3 、4 次幂,因为可以用依次乘2 的方法将它们计算出来。由此我们的总结还可以写成下面的方式:

    这张表简单明了,码字数是2 的次方,次方数目与码字中含有的“滴”“嗒”数目相同。我们可以把表总结为一个简单的公式:

   很多编码中都用到2 的幂,在下一章中我们会看到另一个例子。为了使译码的过程更为简便,可以画出如下一张树形图:

    这张表表示出了由“滴”与“嗒”的连续序列得出的字母。译码时,按箭头所指从左到右进行。例如,你想知道电码“滴-嗒-滴”代表的字母,那么从最左边开始选择点,沿箭头向右选择划,接着又是点,得出对应的字母是R,它写在最后一个点的旁边。

      如果认真考虑,会发现事先建立这样一张表是定义摩尔斯电码所必需的。首先,它保证了你不会犯给不同的字母相同码字的错误!其次,它保证你使用了全部的可用码字,而没有使“滴”与“嗒”的序列毫无必要的冗长。

      我们可以加长码字至5 位或更长,5 位长的码字又提供了额外的3 2 (2 ×2 ×2 ×2 ×2 或25)个码字。一般而言,这就足够1 0 个数字和1 6 个标点符号使用。实际上,摩尔斯电码中的数字确实是5 位的,但在许多其他编码方式中,5 位码字常用于重音字母而不是标点符号。

      为了包含所有的标点符号,系统必须扩充至6 位表示,提供6 4 个附加编码,此时系统可表示2 + 4 + 8 + 1 6 + 3 2 + 6 4 共1 2 6 个字符。这对摩尔斯电码而言太多了,以至于留下许多“未定义”的码字。此处“未定义”指不代表任何意义的码字,如果在你接收的摩尔斯电码中有未定义的码字,就可以肯定发送方出了差错。

由于推出了下面这条公式:

  我们就可以继续导出更长的码字位数所代表的码字数目。很幸运,我们不必为确定码字数目

而写出所有可能的码字,我们所要做的不过是不断地乘2 而已:

 

     摩尔斯电码被称为二元码(binary code ),因为编码中仅含“滴”和“嗒”。这与一个硬币很相似,硬币着地时只可能是正面或反面。二元事物(例如硬币)、二元编码(例如摩尔斯电码)常常用2的乘方来描述。

      上面所做的对二元编码的分析在数学上的一个分支—组合学或组合分析里只能算是一个简单的练习。传统上,由于组合分析能够用来确定事件出现的几率,例如硬币或骰子组合的数目,所以它常用于概率统计,但它也同样有助于我们理解编码的合成与分解。

 

第三章  布莱叶盲文与二元编码

      摩尔斯不是第一个成功地将书写语言中的字母翻译成可解释代码的人,他也不是第一个因为其编码而受到人们纪念的人,享有这个荣誉的是一个晚摩尔斯1 8 年出生的早慧的法国失明少年。虽然人们对他的生平所知甚少,但就是所知的这一些却足以给后人留下深刻印象。   

    路易斯·布莱叶1 8 0 9 年出生于法国的C o u p v r a y ,他的家乡在巴黎以东2 5 英里,父亲以打造马具为生。3 岁时,在这个本不该在父亲作坊里玩耍的年龄,小布莱叶意外地被尖头的工具戳中了眼睛。由于伤口发炎,感染了另一只眼,他从此双目失明。布莱叶原本注定在贫困潦倒中度过一生(正如那时大多数盲人一样),但他的聪明才智和求知欲不久即显露了出来。在本地牧师和一位学校老师的帮助下,布莱叶和其他孩子一道上了学,1 0 岁那年又前往巴黎的皇家盲人青年学院学习。

盲人教育的一大障碍就是他们无法阅读印刷书籍。Valentin Haüy(1745 —1 8 2 2 ),巴黎学校的创始人,发明了一种将字母凸印以供触摸阅读的方法。但这种方法使用起来较为困难,并且只有很少的书籍用这种方法“制造”。

      视力正常的H a ü y 陷入了一种误区。对他而言,字母A 就是A ,它看起来(或感觉起来)也必须像是个A 。(如果给他手电筒作为交流工具,他也会试图在空气中画出字母的形状,而我们已经知道这种方法并不有效。)H a ü y也许没有意识到一种与印刷字母完全不同的编码会更适于盲人使用。

      另一种可选的编码有一个出人意料的起源。法国陆军上尉Charles Barbier 在1819 年发明了一种他自称为écriture nocturne 的书写体系,这种体系也被称为“夜间文字。他使用厚纸板上有规律凸起的点划来供士兵们在夜间无声地传递口信(便条),士兵们使用尖锥状的铁笔在纸的背面刺点和划,凸起的点可以用手指感觉阅读。

        Barbier 体系的问题是其过于复杂。Barbier没有用凸起的点来代表字母表中的字母,而是用其代表声音。这样的系统中一个单词通常需要许多码字表达。这种方法在野外传递短小消息还算有效,但对长一些的文章而言则有明显不足,更不要说是整本的书籍了。

        布莱叶在12 岁时就熟悉Barbier 方法了,他喜欢使用这些凸点,不仅因为它们易于用手指阅读,更因为它们易于书写。教室里拿着铁笔和纸板的学生可以记笔记供课后阅读。布莱叶勤奋地工作试图改进这种编码系统。不出3年(在他15 岁时),他创建了自己的系统,其原理直到今天还在使用。布莱叶系统有很长时间仅局限在他所在的学校使用,后来它逐渐扩散到世界各地。1835年,布莱叶染上了结核病。1852 年,在他43 岁生日过后不久,他便去世了。

      时至今日,布莱叶系统的改进版本甚至可以与有声录音带竞争,它为盲人提供了与书写世界联系的途径。布莱叶方法仍是适于既聋又盲的人阅读的唯一方法。近来年,随着电梯和自动语言机的普及,布莱叶系统更加广为人知。

      本章将剖析布莱叶编码的编码方法及其工作原理,不过不必真正学习布莱叶编码或记住任何东西,我们只要大概了解一下编码的本质就行了。

        布莱叶编码中,普通书写语言的每个字符—具体而言如数字、字母和标点符号—都被编码成局限在2 ×3 小格中一个或多个凸起的点。这些小格一般被标记为1 ~6

   在当今实际使用中,特殊的打字机或刻印机可以在纸上打出布莱叶编码中的小点。

      由于在书中夹印几页布莱叶编码极其昂贵,我们使用了在通常印刷品中常用的布莱叶码的表示方法。在这种表示方法中,小格中的6 个点全部印刷出来,大点代表小格中的凸起点,小点则代表平滑的点。例如下图中的布莱叶字母中,点1 、3 、5 是凸起的,点2 、4 、6 则没有:

      在这里吸引我们的问题是:点是二元的。一个特定的点不是凸起的就是平滑的,那么6 个点的组合数目就是2 ×2 ×2 ×2 ×2 ×2 ,或6 4 ( 26)

         因此,布莱叶编码系统可以代表6 4 个不同的码字。以下就是所有的6 4个码字:

      如果我们发现布莱叶编码只用了6 4 个码字中的一部分,我们会疑问为什么6 4 个码字中有一些不被使用;如果发现布莱叶编码使用了多于6 4 个的码字,则又会让人怀疑我们是否神志清醒或数字计算的真实性,2 乘2 是等于4吗?

      分析布莱叶编码,还是从基本的小写字母开始:

      举例来说,短语“you and me ”在布莱叶编码中看起来是这样的:

      注意,代表同一个单词中的字母的小格用一个小距离分隔,大一些的距离(一般是没有凸点的小格)用来分隔不同的单词。

      这就是布莱叶发明的布莱叶编码的基础,布莱叶还为法文中出现的重音字母设计了码字。注意,W 没有对应的码字,这时由于在古法语中没有W (不必担心,这个字母最终还是会露面的)。这样算来,我们仅使用了6 4 个码字中的2 5个。

       通过仔细的检查,会发现上面的布莱叶编码存在特定的规律。第1 行(从字母a ~j )只用了小格的上面4 个点—点1 、2 、4 、5 ;第2 行除了点3 凸起外其余都与第1 行相同,第3 行则除了点3 、6 凸起外其余都与第1行相同。

          在布莱叶之后,布莱叶编码在许多方面有了扩展,现在大多数英语出版物所使用的系统是二级布莱叶码。二级布莱叶码采用了许多缩写来简化编码树以提高阅读速度。以下的三行(包括“完整的”第3 行)显示了下面这些词的码字:

      因此,在二级布莱叶码中,短语“you and me ”被写成如下形式:

         到现在为止,已描述了3 1 个码字—词间没有凸起点的空格和三行每行1 0 个用于字母和单词的码字。这离理论上可用的6 4个码字还相距甚远。不过我们将要看到,在二级布莱叶码中,没有任何浪费的码字。

      首先,我们使用a ~j 的编码加上凸起的6 号点。它们代表词中的缩写,这其中包括W 和另一个词的缩写:

      举例来说,“about ”可以用二级布莱叶码写成如下形式:

        其次,可以把代表字母a ~j 的码字中的点下移一行,即仅使用点2 、3 、5 和6 。这些码字根据上下文代表标点符号或缩写:

         头4 个码字代表逗号、分号、冒号和句号。注意左括号和右括号用同一个码字代表,但左引号和右引号则使用了不同的码字。

      已经有5 1 个码字了。接下来的6 个码字使用点3 、4 、5 、6 尚未使用的组合来表示缩写和几个额外的标点符号:

  “ble ”的码字非常重要,因为当它不是单词的一部分时,它表明其后跟随的码字要被翻译成数字,这些数字的编码与a ~j 的编码相同:

      由此,如下码字的序列代表数字2 5 6 :

      如果你一直在计数的话,我们还需要7 个码字才能达到总计的6 4 个码字。下面就是剩余的7 个码字:

      第一个(点4 凸起)是重音字母标识符,其余的作为一些缩写的前缀,也用于其他用途:点4 、6 凸起时(本行的第5 个码字),该码字代表数字中的小数点或强调标识符,这由上下文决定。点5 、6凸起时,码字则是与数字标识对应的字母标识。

      最后(也许你正在疑惑布莱叶编码如何表示大写字母),我们用6 号点来作为大写标识,它表明其后跟随的字母是大写的。例如,可用如下的码字写出该编码创始人的名字:

 

      这包含大写字母标识、字母l 、缩写o u 、字母i 和s ,空格,另一个大写字母标识,字母b 、r 、a 、i 、l 、l 和e(在实际应用中,该名字还可以再删掉最后两个不发音的字母)。

      总结一下,我们已经看到了6 个元素(凸点)如何恰 好形成6 4 个码字。这6 4 个码字根据上下文大多有双重含义,其中有数字标识以及取消数字标识作用的字母标识。这些标识改变了跟随其后的码字的含义—从字母变数字或从数字变字母。起这种作用的码字常被称为“先行码/前置码”或“转义码”,它们更改其后字符的含义直至更改作用被取消。

      大写标识表示其后的字母(也仅有字母)应写成大写,这种码字被称为“换码代码”。“换码代码”使你“避免”那种单调的、常规的码字解释,而转入一种新的解释方法。在以后几章中可以看到,当把书面语言转换为二元码字时,“换码代码”和“转义码”的使用是很普遍的。

 

第四章  手电筒剖析

    手电筒的用途极为广泛,用于在黑暗的遮盖物里阅读和用于发送编码消息只是两个用途最明显的方面。最普通的家用手电筒也能在教学演示中说明神秘物质电(e l e c t r i c i t y)时扮演中心角色。

    电是一种令人称奇的现象,尽管它已得到普遍应用,但依然还保持着很大的神秘性,即使对那些自称已经弄清楚它的工作原理的人而言也是这样。但恐怕不管怎么样,我们都必须好好努力钻研一下电学。幸运的是,我们只需要明白一小部分基本概念就可以理解它在计算机中是怎样应用的。

    手电筒当然是一种大多数家庭都拥有的较简单的电器。拆开一支有代表性的手电筒,你会发现它包括一对电池,一个灯泡,一个开关,一些金属片和一个把所有零件装在一起的塑料筒。只用电池和灯泡,就可以自己做一个简单的手电筒。当然,还需要一些短的绝缘线(末端的绝缘皮除掉)和足够多的连接物:

   

    注意上图右边两个松开的线端(头),那就是开关。如果电池有电并且灯泡也没有烧坏的话,接触两个线端,灯就亮了。

    这是我们要分析的头一个简单电路,首先要注意的是电路是一个回路。只有从电池到电线、到灯泡、到开关、再回到电池的路径是连续畅通的,灯泡才会亮。电路中任何一点断开都会引起灯泡的熄灭。开关的目的就是控制电路开闭这个过程。

    电路环接的特性提示我们有某种物质在电路中循环移动,可能与水在水管里流动有某些相似。“水与水管”的类比常用来解释电的工作机理,但最终它也像其他类比一样不可避免地解释不下去了。电在宇宙中是独一无二的,必须用它的术语来解释它。

    在对电的工作的理解中,最流行的科学理论是电子理论(electron theory),该理论认为电起源于电子的运动。

    众所周知,一切物质—我们能看到、感觉到的东西—(通常)是由极其微小的被称为原子的东西构成。每一个原子是由三种微粒构成的,即中子、质子和电子。你可以把原子画成一个小的太阳系,中子和质子固定在原子核内而电子像行星环绕太阳一样围绕原子核运动:

需要解释一下的是该模型与你在一个放大倍数足够大的显微镜下看到的真正原子不是一模一样的,它只是一个示例模型。

    图中原子包含3 个电子、3 个质子和4 个中子,说明这是一个锂原子。锂是已知的11 2种元素之一,它们的原子序数由1 ~11 2 。一种元素的原子序数是指元素的原子核中质子的个数,通常也是其电子数。锂的原子序数为3

    原子能够通过化学合成形成分子,分子与组成它的原子的性质通常是不同的。比如水分子包含两个氢原子和一个氧原子(即H 2 O)。显然水既不同于氢气,也不同于氧气。同样,食盐分子由一个钠原子和一个氯原子构成,而钠和氯都不可能成为法国馅饼的调味品。

    氢、氧、钠、氯都属于元素,水和食盐都属于化合物。但是盐水是一种混合物,而不是化合物,因为其中水和食盐都保持它们各自的性质不变。一个原子的电子数通常等于其质子数。但在某种特定环境下,电子能从原子中电离出来,这样电就产生了。

   单词electron和electricity都源于古希腊词hlektron(elektron),你可能猜它的意思就是“极其微小而不可见的东西”。但事实并非如此—h l e k t r o n 的真正意思是“琥珀”,一种玻璃状的硬质树液。这个看似不相关的词源来自于古希腊人所做的实验,他们用琥珀与木头相摩擦而产生我们今天所说的静电。在琥珀上摩擦木头使木头从琥珀获得电子,结果木头所含的电子数多于质子数而琥珀所含的电子数小于质子数。在更多的现代实验中,地毯能从鞋底获得电子。

    质子和电子具有带电荷的特性,质子带正电荷(+)、电子带负电荷(-)。中子是中性的,不带电。即便我们用加减号来标明质子和电子,但符号并不表示算术运算中的加号和减号的意思,也不表示质子拥有某些电子所不具备的东西。使用这些符号仅仅表示质子和电子在某个方面性质相反。这个相反的特性也正表明了质子和电子是如何相互关联的。

    当质子数与电子数相等时,它们是最适合和最稳定的。质子数与电子数的不平衡会导致它们趋于平衡。静电火花就是电子运动的结果,是电子从地毯通过你的身体再流回到鞋子的过程引起的。

    描述质子和电子关系的另一条途径是注意观察异电性相吸同电性相斥的现象,但光凭看原子结构图我们是不能猜想到的。表面上看原子核中挤在一起的质子是互相吸引的。质子是通过比同性斥力大的某种力聚合在一起的,这种力叫强内力。释放核能的原子核裂变就是由于强内力导致的。本章只讨论通过得失电子获得电(电能)的问题。

    静电不只存在于手指触摸门把手时闪出的火花之中。暴风雨时,云层的下层积累电子而云层的顶层失去电子,闪电的瞬间,电子的不平衡马上消失。闪电正是大量的电子迅速从一端转移到另一端的结果。

    手电筒电路中的电能显然比电火花或闪电之中的电能要好利用得多。灯泡能稳定持续地亮是因为电子并不是从一点跳到另一点。当电路中的一个原子把一个电子传给邻接的另一个原子时,它又从另一个邻接的原子获得电子,而这个原子又从它的一个邻接原子获得电子,如此依次循环。可见电路中的电就是从原子到原子的电子通路。这不可能自发形成。仅仅只把一些破旧的电路材料连接在一起是不可能有电能产生的,需要某种可以激发电子环绕电路移动的物质。再分析一下前面所画的简单手电筒电路图,可以肯定激发电子运动的既不是电线,也不是灯泡,那么最有可能的就是电池了。

   几乎每一个人都多少了解手电筒里所用电池的类型方面的一些知识:

1、它们都呈管状,且大小不同。比如有D 、C 、A 、A A 和A A A等型号。

2、无论电池大小怎样,它们都被标有“1 . 5伏”。

3、电池的一端是平的,标有一个负号(-);另一端中间有一个小突起,标有一个正号(+)。

4、要想设备正常工作,就要正确安装电池(注意电池极性)。

5、电池的电能最终将用尽。有的电池可以充电,有的不行。

6、由此可以猜测,电池是用某种奇特的方式产生电能。

    所有的电池中都发生着化学反应,一些分子裂变成其他分子或者结合形成新的分子。电池中有化学物质,这些化学物质就是用来起反应,从而在标有(-)的电池的一端(称为负极或阴极)产生多余的电子而在电池的另一端(称为正极或阳极)需要得到电子。这样,化学能转化为电能。

    只有当某种特别的电子通过某条途径从电池负极出发,然后再传送到正极时,化学反应才能发生。因此假如一节空电池放在那里,那么什么事也不会发生(事实上,化学反应还是在进行的,只是速度极慢)。只有一条电路能将电子运离负极又为正极提供电子时,反应才会发生。电子在下图电路中是沿逆时针方向运动的:

    如果不是基于这个简单的事实:所有的电子,不管来自什么地方,都是一模一样的,否则,来自电池的化学物质里的电子就不可能如此随意地与铜导线的电子混合在一起的。铜导线的电子与任何其他电子是没有区别的。

    注意,两个电池都是向着同一个方向。放在下面的电池的正极从上面电池的负极获得电子,这样两个电池就好像结合形成了一个更大的电池,这个大电池一端为正极,另一端为负极,其电压是3 伏而不是1 . 5伏了。

    如果把电池中的一个倒置,电路就会连不通,如下图所示:

在化学反应中,两个电池的正极都需要获得电子,但由于它们相互接触,电子无法通过某种途径到达它们。如果两个电池的正极连上了,那么它们的负极也应该连上,如下图所示:

   这样的电路还是能连通。电池的这种连接方法称为并联,前一种连接方法称为串联。并联后的电压与单个电池电压同样都是1 . 5伏。并联后的灯仍然可能亮,但不如串联时亮度大,不过电池的寿命将会是串联时的两倍。

   通常认为电池为电路提供电能,但同样也可以认为电路为电池化学反应的发生创造了条件。电路将电子从负极传送到正极。电路中的化学反应将一直进行到所有的化学物质耗尽,这时你就需要换电池或是给电池充电了。

   电子从电池的负极到正极流经了导线和灯泡。为什么需要导线?电不能通过空气传导吗?噢,可以说能,也可以说不能。电能够通过空气导通(尤其是潮湿的空气),否则也观察不到闪电。但电不能很轻易地流经空气。

    一些物质的导电能力比其他物质的导电能力明显要好。元素的导电能力取决于它内部的原子结构。电子绕核旋转是在不同的轨道上的,这些轨道称为层。最外层只有一个电子的原子最容易失去那个电子,这正是导电需要具备的性质。这些物质易导电因而被称为导体。铜、银和金都是良好导体,这三种元素位于元素周期表的同一列不是巧合。铜是最常用的导线材料。

   导电物质的对立物质称为绝缘物质。一些物质阻碍电的能力比其他物质阻碍电的能力强,这种阻碍电的能力称为电阻。如果一个物质有很大的电阻—说明它根本不能导电—它就被称为绝缘体。橡胶和塑料都是很好的绝缘体,因而它们常用来做电线的绝缘皮。在干燥空气的情况下,布料和木材也是很好的绝缘体。其实只要电压足够高,任何物质都能导电。

    铜的电阻很小,但它仍有电阻。导线越长,电阻越大。如果你用数里长的导线连接手电筒,导线的电阻将会大得令手电筒不亮。导线越粗,电阻越小,这可能有点违反直觉。你也许认为粗的导线需要更多的电来“充满它”。而事实上,导线越粗,电子越容易通过它。我已经提到过电压,只是还没有给出它的定义。一节电池为1 . 5 伏特意味着什么呢?实际上,电压—得名于Count Alessandro Volto(1745—1827),他于1800年发明了第一节电池—是初等电学中较难理解的概念之一。电压表征电势能的大小,无论一节电池是否被连通,电压总是存在的。

   假设有一块砖头。如果把它放在地上,它的势能很小。当你把它举起至离地面4英尺高时,它的势能就增加了。你只要把砖块扔下,就能感觉到势能的存在。当你在一座高楼的顶层举着砖块时,它的势能更大。上面三个例子里,你只是拿着砖块而什么也没做,但砖块的势能却不同。

    电学里更早的一个概念是电流。电流取决于电路中飞速流动的电子的数量。电流用安培来度量,它得名于André Marie Ampére(1775 —1836),一般简称安,比如“10安的保险丝”。当6240000000000000000 个电子在1秒内流过一个特定的点时,就是1安培电流。

    用水和水管作个类比。电流与流经水管的水量很相似,而电压类似于水压,电阻类似于水管的粗细程度—水管越小、阻力越大。因此水压越高,流过水管的水量越大;水管越小,流过它的水量就越少。流过水管的水量(电流)与水压(电压)成正比而与水管的阻力(电阻)成反比。

    在电学中,如果知道电压和电阻的大小,就可计算出电流的大小。电阻—物质阻碍电流通过的能力—用欧姆度量,得名于Georg Simon Ohm(1789—1854),他提出了著名的欧姆定律,定律中表述

 I = E / R

这里I 表示电流,E 表示电压,R表示电阻。

   举个例子,让我们看一节空置的电池:

它的电压E 为1.5 伏,这是电势能。因为电的正负两极只被空气导接,因而电阻(用R表示)非常、非常大,这就意味着电流I等于1.5 除以一个巨大的数,电流几乎为0

   现在用一根短铜导线连接电池的正负两极(从现在开始,本书中导线外的绝缘皮不再表示出来):

    我们已经知道这是短路。电压仍是1.5伏,但电阻很小,这时电流等于1.5除以一个很小的数,也即意味着电流很大。很多很多的电子将流过导线。实际上,电流将受到电池物理大小的限制。电池不可能导通如此大的电流,且实际电压也将低于1.5伏。如果电池足够大,导线将会发热,因为电能转化为了热能。如果导线变得很热,它将会发光(辉光放电)甚至可能熔化。

    绝大部分电路都介于这两个极端之间。可以把它们统一表述为如下图:

电气(子)工程师用折线来表征电阻。这里它表示电阻不是特别大,也不是特别小。

    如果导线的电阻很小,导线将发热发光,这就是白炽灯的工作原理。白炽灯泡是由美国最著名的发明家托马斯·爱迪生(1847—1931)发明的。在他致力于发明灯泡的时候(1879年),这个思想已被普遍接受并且同时还有不少其他发明家在研究这个问题。

    灯泡里的细线叫灯丝,通常用金属钨做成。灯丝的一端连在基座底部的尖端,另一端连在金属基底的一个侧面,用一个绝缘体将它与尖端分开。细线的电阻使它发热。如果暴露在空气中,钨就会由于达到燃烧温度而烧起来。但在灯泡的真空里,钨丝就发亮了。

   大多数普通手电筒用两节电池组成一组,总电压是3.0伏。且选用电阻大约为4欧姆的灯泡。这样,电流等于3除以4即0.75安培,也就是750毫安。这意味着每秒钟有4680000000000000000个电子通过灯泡。(注意,如果你用欧姆表直接测量手电筒灯泡的电阻,你只会得到一个比4欧姆小得多的结果。这是因为钨的电阻还与它的温度有关系,温度越高,电阻越大。)

    你可能已经发现,你买回家的灯泡上标记了特定的瓦特数。瓦特这个名词取自于著名的蒸气机发明家詹姆斯·瓦特(1736—1819)。瓦特是功率P的单位,它用下式计算P = E ×I  手电筒是3伏,0.75 安培,那么灯泡的功率就要求2.25 瓦特。

    家用照明灯大约为100瓦特,这是为家用电压120 伏设计的。在这种情况下,电流为100瓦除以120 伏即大约0.83安培。因此,100瓦特灯泡的电阻为120 伏除以0.83安培即144欧姆。

    到此,我们大致分析了手电筒的每一个组成部分—电池、导线和灯泡。但是我们遗漏了一个最重要的部分、对,是它的开关。开关控制电路的开闭。当开关允许电流动时,我们说它是开的或合上的,而关的或断开的开关是不允许电流动的。(这里所表示的开、关的状态正好与门相反,合上的门不允许事物通过的,而合上的开关允许电通过。)开关或开或关,电流或有或无,灯泡或亮或不亮,就像摩尔斯和布莱叶发明的二元码一样,简单的手电筒或亮或不亮,它没有中间状态。二元码与电气电路之间的相似性将在后面的章节中起很大作用。

 

第五章  绕过拐弯的通信

    你1 2岁了。一天,你最要好的朋友一家要搬到另一个镇上去了。此后,你经常和他在电话里聊天,但电话交谈与那些后半夜的手电筒摩尔斯电码会话完全不一回事。住在你隔壁的另一个好朋友最终成为你新的最要好的朋友。现在到了该教你的新朋友一些摩尔斯电码,让后半夜的手电筒重新亮起来的时候了。

    问题是你的新朋友的卧室窗户与你的不是面对面的。房子是挨着的,卧室的窗户都朝着同一个方向。除非你想办法在室外支起一些镜子,否则手电筒现在是不能适用来在黑夜中通信的。怎么办呢?

    现在,你可能已经知道有关电的一些知识了,因此你决定用电池、灯泡、开关和导线来做自己的手电筒。最初的实验中,你在你的卧室里接好电池和开关。两条导线接出你的窗子,跨过篱笆,再接进你朋友的卧室,并在那里再连好灯泡:

    尽管图中只示意了一节电池,但实际上你可能得用两个。在下面和以后的图中,用下图表示断开的开关:

 

 

    本章的手电筒与上一章中手电筒的工作原理是相同的,尽管本章的手电筒中连接组件的

导线要长得多。当你闭合开关时,你朋友那边的灯泡就亮了:

 

    现在你可以用摩尔斯电码来发送消息了。

    一旦有一个手电筒起作用,你可以做另一个远距离手电筒,好让你的朋友可以发送消息给你

:

    祝贺你!你已经装上了一个双向电报系统。你可能注意到这两个相似的电路彼此完全独立而没有联系。理论上,你可以给你的朋友发送消息而同时你的朋友也可以给你发送消息(尽管对于你的大脑而言,同时阅读和发送消息可能比较困难)。

    聪明的你发现如下改装电路能让你节省2 5 %的导线:

    注意,现在两个电池的负极接在一起了。两个回路(电池到开关到灯泡再到电池)仍是独立工作,尽管它们连在一起像连体双胞胎。

    这种连接叫公用连接。在这个电路中,公用部分从左端灯泡和电池的接合点直到右端灯泡和电池的接合点。图中接合点用黑点标记出来了。

    进一步分析一下。首先当你按下开关,你朋友那边的灯就亮了。图中浅色回路中有电流流过:

在电路的其余部分里没有电流流过,因为没有了可让电子通过的回路。

    当你不发消息而你的朋友发消息时,你朋友房间里的开关控制你房间里灯泡的亮灭。在下图浅色回路中有电流流过:

    当你和你的朋友想要同时发消息时,有时两个开关同时断开,有时一个断开一个闭合,有时两个同时闭合。在最后一种情况下,电路中电的流动如下图所示:

公用部分(两个接合点之间)没有电流流过。

    通过公用部分把两个独立电路连接成一个电路,已经把两栋房子之间的四条导线减少到了三条,也即减少了2 5 %的导线开支。

    如果不得不接很长距离的线路,我们可能会想到再减少一根导线。但不幸的是对于1 . 5伏的D号电池和小灯泡,这是不合适的。如果用的是1 0 0伏的电池和大得多的灯泡时,那就有办法了。

    这是个窍门:如果你要搭建电路的公用部分,你不需要任何导线。你可以用另外某种东西取代它。你所用的取代物是一个直径大约为7 9 0 0英里,由金属、岩石、有机物等多为无生命的物质组成的巨大球体。它就是地球。

   上一章描述的良导体中有银、铜和金。事实上,地球不是一个很好的导体,尽管某些部分(如沼泽)的导电性能比其他部分(如干沙漠)要好得多。但我们知道导体越大越好,一根很粗的导线比一根很细的导线要强得多。这是地球的优势,它的确非常非常大。

    要用地球做导体,并不是把一根小细线插到马铃薯旁边的地里就可以了。你还必须使用某种东西以维持和地球的真正接触,这也就是需要一个大面积的导体。一个很好的解决办法是用一根至少8英尺长,1 / 2英寸粗的粗铜柱,它能提供与地面1 5 0平方英寸的接触。你可以用一个大锤子把它砸进地下,然后再接一根导线。如果你家的水管是铜质的,且从房子外的地下接进来的话,那么你只要把一根导线与水管相连就可以了。

    与地球的电性连接(也就是我们常说的接地)在英国叫earth ,在美国叫ground。用ground可能会引起一点点儿误会,因为它也经常用来指电路的公用部分。本章除非特别声明,否则ground都指与地球的物理连接。

    画电路图时常用下面这个符号表示接地:

电气工程师们使用这个符号是由于他们不喜欢费时间画一个埋在地下的8英尺长的铜柱。让我们来看看它是怎么工作的。从分析单回路开始:

    如果你使用的是高压电池和大灯泡,你只需要在你和你朋友的房子之间接一根导线,因为你可以用大地来做导体:

    当你断开开关,电子的流动如下图所示:

    电子从你朋友房子的地下出发,通过灯泡、导线和你房间里的开关,然后进入电池的正极。电子由电池的负极进入地下的。

    也许你还真的很想看到电子从埋在你家后院的8英尺长的铜柱进入地下,飞速地通过大地到达埋在你朋友家后院的铜柱。

    但是当你考虑到地球在为世界上数以千计的电路完成此功能时,你也许会问:这些电子怎么知道该到哪儿去呢?显然它们不知道。这里要用地球的一个特殊性质来解释。

    是的,地球是一个巨大的导体,但它同时也是电子的来源和仓库。地球对于电子而言就好像大海对于水滴而言。地球的确是电子无尽的源头,也是电子巨大的存储池。

    但是地球也有电阻,这就是为什么如果用1 . 5伏的D号电池和手电筒灯泡就不能用接地来减少电路开支的原因。地球对于低电压电池而言电阻实在太大了。

    你可能注意到上面两张画了电池的图中,电池的负极接地了:

    以后将不再画接地的电池,而用代表电压的字母V来代替它。单回路灯泡电报机现在如下图所示:

         V代表电压,但它也可以表示吸取器。把V看成电子吸取器,把大地看成电子的海洋,电子吸取器从地下吸取电子,放入回路,使之工作(比如点亮灯泡)。接地有时也被看成零电势,意味着没有电压存在。电压—像早先解释的—是一种电势能,就像悬浮的砖块具有势能一样。零电势就好像摆在地上的砖块—它不能再往什么地方掉下去了。

    在第4章中,我们注意到的一件首要的事情是电路是一个回路。新电路看起来一点儿都不像回路,但它仍然是回路。你可以用负极接地的电池代替V,然后用一根线把所有有接地符号的地方连起来,你将得到与本章开始时一样的电路图。

    因此,通过一对铜柱(或是自来水管)的帮助,可以只用两根跨越你和你朋友房子之间篱笆的导线就建立起了双向摩尔斯电码系统:

    这个电路与先前的三线配置电路功能相同。

本章已经迈出了通信改革中的关键性一步。最初,我们只能通过直线视觉和在手电筒的可见范围内进行摩尔斯电码通信。使用电线,不仅突破了直线视觉的限制,而且通过建立系统来绕过拐弯进行通信,我们还摆脱了距离的限制。只要搭造更长更长的线路,就可以越过成百上千英里进行通信。

    对了,这还不太准确。尽管铜是电学上很好的导体,但它不是最完美的。导线越长,电阻越大;电阻越大,电流越小;电流越小,灯泡越暗。

    那么导线可以造多长呢?因情况而定。假设你正在使用原来四根线的双向电路,无接地和公用,并且还用手电筒和灯泡。为了节省开支,你先从电器行买了一些20号规格的电话线,每100英尺$9.99。电话线是用来连接你的扩音器和立体声系统的。它有两根导线,因此它是电报系统的上佳选择。如果你的卧室与你朋友的卧室不到50英尺远,只用一捆电话线就够了。

    美国的导线粗细规格为AWGAWG数越小,导线越粗,电阻越小。你所买的20号规格电话线直径大约0.032英寸,每1000英尺大约10欧姆电阻,这样对于卧室之间100英尺长的回路电阻为1欧姆。

    这并不坏,但如果要连上英里的线呢?线的总电阻将达到100欧姆以上。回想一下上一章中,灯泡电阻仅为4欧姆。利用欧姆定律,可以很容易地计算出电路中的电流不再是以前的0.75安(3伏除以4欧),而是比0.03安还小(3伏除以100欧以上)。几乎可以肯定,电流的大小不够点亮灯泡。

    使用粗线是一个很好的解决方法,但价格太昂贵。10号规格线(电器行的汽车电路耦合线价格为每35英尺$ 11.99,而且你需要双倍长度因为它只有单线)大约0.1英寸粗,1000英尺为1欧姆,即1英里5欧姆。

    另一个解决办法是增加电压,使用大电阻灯泡。比如使用120伏电压的100瓦家用照明灯泡的电阻为144欧姆。电线的电阻对于整个电路电流的影响将大大减小。

    接下来的是150年前,人们在美洲和欧洲之间搭建第一个电报系统时所面临的问题。不管电线多粗,电压多高,电报线还是不能无限延长。根据计划,工作系统的极限为200英里。这与纽约和加利福尼亚间的上千英里距离相差太多。

    这个问题的答案—不是为手电筒,而是为过去的嘀嗒电报—虽说是一个简单易行的设备,但是通过它,整个计算机得以构造。

 

第六章  发报机与断电器

         1791年,萨缪尔·摩尔斯生于马萨诸塞州的查尔斯顿镇,该镇是邦克山之战的地点,也是波士顿东北重镇。摩尔斯出生那年,美国宪法刚实施两年,乔治·华盛顿出任美国第一个任期的总统职务。Catherine大帝统治俄国。路易十六世和Marie Antoinette在两年后的法国大革命中被送上断头台。1791年,莫扎特完成了《魔笛》,他的最后一部作曲,次年于35岁时去世。

摩尔斯在耶鲁受过教育,又在伦敦学过艺术,他是位著名的肖像画家。他的作品《General Lafayette( 1825 )珍藏于纽约市政大厅。1836年,他曾参与过竞选纽约市市长且获得了5.7 %的选票。他也是早先的摄影术狂热爱好者。他从Louis Daguerre本人那儿学习了银版相片的制作,制造出了美国第一批用银版照相术制成的相片,1840年,他把这个手艺传授给了17岁的Mathew Brady。此人以及他的同事后来为美国内战、亚伯拉罕·林肯和摩尔斯本人留下了一些很有纪念价值的照片。

这些只是一个多职业生涯者的足迹。摩尔斯最著名的贡献在于他发明了电报和以他名字命名的编码。

    世界范围内的即时通信我们已经很熟悉,但它是当今新技术发展的结果。19世纪早期,你可以即时通信和远距离通信,但不能同时达到两个要求。即时通信只能限制在你的声音能达到(没有扩音器可用)或是你的眼睛能看到(也许得用望远镜)的范围;远距离通信则要花时间用信件通过马车、火车或者轮船的方式来实现。

    在早于摩尔斯发明的年代里,人们曾做过许多加速远距离通信的尝试。一种技术上简单的方法是雇佣一批人接力,站在山顶上用旗语信号通信。技术上稍微复杂一点儿的方法是使用巨大的带有可动手臂的装备,原理与旗语相同。

    电报思想的正式成形是在1 9世纪早期。1 8 3 2年在摩尔斯开始试验之前,已经有其他科学家在做一些试探。原理上讲,电报思想很简单:你在线的一端做某些事引起线的另一端发生了某些事。这正是上一章用远距离手电筒所做的事情。但摩尔斯不可能使用灯泡作为他的信号设备,因为实用性灯泡直到1 8 7 9 年才发明出来。摩尔斯使用的是电磁现象。

    如果你取一只铁棒,用细导线将它绕几百圈,然后让电流通过导线,铁棒变成了磁铁,这时它就能吸引其他的铁和钢。(电磁铁上细线的电阻足够大以防止电磁铁形成短路。)移开电流,铁棒的磁性消失:

    电磁铁是电报的基础。一端上开关的闭合引起另一端上的电磁铁产生一些动作。

    摩尔斯最早的电报机比后来改进的要复杂得多。摩尔斯认为电报系统应该在纸上实际写点儿什么(这就像后来的电脑使用者描述的“生成一个硬拷贝”)。这当然不必是文字,因为文字太复杂,但某些字符应该记录下来,或曲线或点或划。注意,摩尔斯坚持要用纸记录下发报内容的这种想法,与Valentin Haüy 要求盲人书籍应该使用突起的字母文字一样。

    尽管摩尔斯早在1836年就告知专利局他已经成功地发明了电报,但直到1843年,他才说服议会为此设备的示范表演出资赞助。1844524日是有历史意义的一天,Washington和马里兰州巴尔的摩之间的电报线成功地传送了圣经上的一句话“What hath God wrought !”。

传统电报机发送消息的核心部分如下图所示:

    尽管外观比较怪,但它只是一个为高速开合(闭)设计的开关,称为“按键/按钮”。长时间按键最舒适的方式是在手掌的拇指、食指和中指之间握住把手,然后敲击。短时间敲击形成摩尔斯电码的点,长时间敲击形成摩尔斯电码的划。

    线的另一端是一个接收机,其基本结构是一个电磁铁吸拉一根金属拉杆。起初电磁铁控制的是一支笔,当由小装置控制的机械通过弯曲的弹簧缓慢地拖拉一卷纸时,相连的笔上下蹦弹将点划记录在纸上,懂得摩尔斯电码的人再将点划翻译成字母和文字。

    当然,人是会偷懒的。电报机使用者很快发现只要简单地利用笔跳上跳下的声音他们就能翻译编码。笔的装置最终被撤消,代替的是传统电报机的发声装置,称为“发声器/音响器”,结构如下:

    当电报机的键按下时,发生器的电磁铁将可动棒拖下发出“滴”的声音;当键放开时,棒弹回初始位置,发出“嗒”的声音。快速的“嘀嗒”为点,慢速的则为划。

    按键、发声装置,电池和一些导线可像上一章所述手电筒电报一样连接起来:

   我们已经知道,两个电报站之间不需要两根线。如果大地作为另一半回路的话,一根线就足够了。

    如上一章所做,我们用字母V代替接地的电池,因此最终的单向设置如下图所示:

    双向通信只不过再需要一个按键和发生器。与上章所做相似。

    电报的发明真正标志着现代通信的开始。人类首次能够在眼、耳的范围之外以快于马奔跑的速度通信。发明中使用的二元码是其精华所在,但在后来的电子和无线电通信中,包括电话、收音机和电视,二元码都没有用到,只到最近二元码才出现在计算机、CD盘、DVD盘、数字卫星电视广播和高清晰电视中。

    摩尔斯的电报机战胜了其他设计,部分原因是它对不好的电线状态的容忍度比较大。假如你在按键和发声装置之间接一根线,该电报机通常可以工作,但其他电报系统却不具备这样的容忍性。但正如上章所谈及的,最大的问题在于长距离导线的电阻。尽管一些电报线使用高达300伏的电压能在300英里的范围内工作,导线还是不能无限延伸。

    一个明显的解决办法是使用转发(中继)系统,也称继电器系统。大约每200英里就让某位发报者通过发声装置接收消息再用按键发送出去。

    现在想像一下你已被某电报公司雇佣为转发系统的工作人员。他们把你放在纽约和加利福尼亚之间某个地方的一间简陋得只有一张桌子和一把椅子的小屋里。一根导线从东边的窗户进来连到发声装置上。你的按键连在电池和从西边窗子出去的导线上。你的工作是接收来自于纽约的消息然后把它们发送到加利福尼亚。

    起初,你是接收了整条消息后再转发它。你记录下发声器的嘀嗒,到消息接收结束,你再用你的按键将它们发送出去。最终你掌握了边听边发的技巧而不用把整条信息记录下来,这节约了转发时间。

    某天你在转发消息时,你注意到铁棒上下跳动又注意到了手指按动键的上下跳动。你看了看发声器又看了看键,然后你意识到棒的上下跳动与按键的上下跳动是一致的,于是你出去取回一根小木条,用这根木条和一些线把发声器和按键连接了起来:

    现在它可以自动工作了,你可以去喝下午茶也可以去钓鱼了。

     这只是一个趣味情景的想像。但实际上,摩尔斯很早就理解这个装置的思想。我们已经发明的这个装置叫重发器或继电器。一个继电器就像一个发声装置,输入的电流形成电磁用以拖动金属杆,金属杆作为开关的一个部分连接到外接的导线上。这样,微弱的输入电流被扩大形成比较强的输出电流。

    继电器的概要描述如下图所示:

   

 

因此电报按键、继电器和发声器大致连接如下:

    继电器是一种卓越的设备。它是一个开关,但并不是由人工而是借助于电流进行开关操作的。利用这种设备可以做出令人惊奇的事情。事实上,你可以用继电器装配出一台计算机中的大部分部件。

    是的,继电器这种设备是一种很好的发明,足以与电报相提并论。后面还将会用到,且它会变得非常小巧、方便。但是,在能够使用它之前,得先学会数数。

 

第七章  十进制记数法

    语言仅仅是一种编码的想法似乎很容易被人们接受,很多人在学生时代至少学过一种外语,因此,我们知道在英语中“cat”(猫)也可以被叫作gatochatKatzeKOIIIKkapa

    然而,数字不那么容易随文化的不同而改变。不论那种语言,也不管怎样读那些数字,地球上我们能够遇到的几乎所有的人都用同样的方式来写数字:

    数学,从某种意义上来说是不是可以称得上是一种世界语言呢?

    毫无疑问,数字是我们平时能够接触到的最抽象的代码。当你看到数字“3”时并不需要立即将它和任何事情相联系。你可能将它设想为3个苹果或者3个其他什么东西,但是当你从上下文中得知这个数字是指某个小孩的生日、电视频道、曲棍球比赛的得分或者是制作蛋糕的食谱中提供的需要面粉的杯数时,也能够像认为它代表3个苹果时一样自然。因为数字一开始产生时就很抽象,所以让我们理解这些苹果:

并不一定要用符号“3”来表示就更困难了。本章的很大一部分以及下一章将来讲解这些苹果:

也可以用“11”的形式来表示。

    先不讨论数字10与生俱来的特殊性。大多数人使用的数字系统是基于10(有时候是5)的,这种情况并不奇怪。最初人们是用手指来数数的。要是人类进化成有8个或12个手指,人类计数的方式就会有所不同。英语Digit(数字)这个单词也可以指手指或脚趾,单词five(五)和单词fist(拳头)有相同的词根,这种情况并不是巧合。

    这样看来,人类选择使用以10为基础的记数方法(或称为十进制记数法)完全是任意的,但我们赋予1 0的整数次幂重大的意义,并给它们命名:十个一年是一个十年;十个十年是一个世纪;十个世纪是一个千年;千个一千是百万;千个百万是十亿。下面是1 0的各次幂:

    多数历史学家认为数字最初创造出来是用来数东西的,比如:人数、财产数、商品交易量等。举个例子来说,假定某个人有4只鸭子,他可能画4只鸭子作为记录:

    后来,专门负责画鸭子这项工作的人想:“我为什么一定要画4只鸭子呢?为什么不能只画1只鸭子,然后用其他方法(管它用什么方法,哪怕用一条竖线来代表一只鸭子)来表示有4只呢?”

但若某人有2 7只鸭子,用画竖线来表示鸭子只数的方法就显得很荒谬了:

    于是,有人想到得有一种好的办法才行,数字系统就这样诞生了。

    在早期的数字系统中,只有罗马数字系统沿用至今。钟表的表盘上常常使用罗马数字,此外它还用来在纪念碑或雕像上标注日期、标注书的页码,或作为提纲条目的标记。最令人惊奇的是罗马数字常用在电影中做版本说明。(只要你有足够快的速度将字幕结尾处出现的M CMLIII译码,通常情况下就可以回答“这部影片是什么时候拍的”这个问题。)

    27只鸭子可以用罗马数字这样表示:

这里用到的概念非常简单:X代表1 0条竖线,V代表5条竖线。

现在仍在使用的罗马数字有:

    字母I代表一个一,这可能来自于一条竖线或者伸出的一个手指。字母V很可能是一只手的符号,代表五;两个字母V组成字母X,代表十;字母L代表五十;字母C来自于拉丁文中表示一百的单词—centum;字母D代表五百;最后,字母M来自拉丁文中的单词—mille,代表一千。

    也许你不一定同意,很长一段时间以来,罗马数字被认为用来做加减运算非常容易,这也是罗马数字能够在欧洲被长期用于记帐的原因。事实上,当对两个罗马数字进行相加运算时,只需将这两个罗马数字的所有符号合并然后用下面的方法将其简化:五个I是一个V,两个V是一个X,五个X是一个L,等等。

    但使用罗马数字做乘除法是很难的。很多其他早期的数字系统(比如古希腊数字系统)和罗马数字系统相似,它们在做复杂运算时存在一定的不足。尽管如此,古希腊人所发明的非凡的几何学至今仍是中学的一门课程,古希腊人不是以代数享誉世界的。

   我们现在使用的数字系统通常称为阿拉伯数字系统,或称为印度—阿拉伯数字系统。它起源于印度,但由阿拉伯数学家传入欧洲。一位著名的波斯数学家—Muhammed ibn-Musaa l - Khwarizmi(由它的名字得到单词algorithm(算法))在大约公元825年写了一本代数书,书中用的就是印度的数字系统(阿拉伯数字)来计数。产生于公元1120年的拉丁文译本对整个欧洲用现在的阿拉伯数字代替当时使用的罗马数字的过渡过程产生了很大的影响。

    印度—阿拉伯数字系统与先前的数字系统相比在以下三个方面不同:

1印度-阿拉伯数字系统是和位置相关的,也就是说,一个数字依据位置的不同代表不同的数量。数字的位置和数字的大小一样,都是很重要的。(但实际上,数字的位置更重要。)1001000000中都只有一个1,但我们知道一百万比一百要大得多。

2几乎所有早期的数字系统都有一个阿拉伯数字所没有的东西,那就是用来表示数字10的一个专门的符号。现在使用的数字系统中是没有代表10的专门符号的。

3另一方面,几乎所有早期的数字系统都缺少一个阿拉伯数字中有的,而且事实证明是比代表数字10的符号重要得多的符号,那就是零。

    是的,就是零。这个小小的零毫无疑问是数字和数学历史上 最重要的发明之一。它支持位置表示法,因为它可以将205250区别开来。数字零也使得与位置无关的数字系统中非常复杂的运算变得简单,尤其是乘除法。

    印度—阿拉伯数字的整体结构是以读它们的方式展现的。拿4825作为例子,我们把它读作“四千八百二拾五”,意思是:

  四个一千,八个一百,两个十,一个五

    或者,可以将它的组成写成这样:4825 = 4000 + 800 + 20 + 5

    或者,可以将它进一步分解,写成这样:4825 = 4×1000 +8×100 +2×10 +5×1

    另外,也可以使用10的整数次幂的形式,重新写成:4825 = 4×103+8×102+2×101+5×100

    记住,任何数的0次幂都等于1

    多位数中的每位都有特定的意义,如下图所示。这7个方格可以表示从09 999 9999的任何一个数字:

    每一个位置(位)与10的一个整数次幂相对应。不需要一个专门的符号来表示数字10,因为可以将1放在不同的位置,用0作为占位符。

    分()数可以同样的形式作为数字放在十进制数的小数点的右边,这一点非常好。数字42705.684是:

该数也可以写成不带除法的形式,如下:

或写成1 0的整数次幂的形式:

    注意10的指数是怎样变到零再变成负数的。

    我们知道,3加上4等于7。同样,30加上40等于70300加上400等于7003000加上4000等于7000。这正是阿拉伯数字系统的“魅力”所在,无论你进行多长的十进制的加法,只要根据一种方法将问题分成几步即可。每一步最多只是将两个一位数字相加,这也是很久以前有人强迫你记加法表的原因:

    从最上边的一行和最左边的一列找到要相加的两个数字,在行与列的交叉点上找到它们相加的结果。例如,4加上6等于10

    同样,做两个十进制数相乘的运算时,方法可能稍稍复杂一点儿,但仍然只需将问题分成几步,这样就不会比做加法和一位数的乘法更复杂了。你在小学时可能也必须记住下面的乘法表:

    与位置相关的记数系统的优点不在于它多么好用,而在于当它用在不是十进制的系统中时,也一样的好用。我们现在用的数字系统不一定适合所有的人。十进制数字系统的一个很大问题就在于它和卡通人物没有任何关系。大多数的卡通人物每只手上只有4个手指,因此它们喜欢基于8的数字系统(八进制)。有趣的是,我们所知的大部分关于十进制数的知识同样可以用于卡通朋友所喜爱的八进制数字系统中。

 

第八章  其他进位制记数法

      10对我们来说是一个非常重要的数字。1 0是我们大多数人拥有的手指或脚趾的数目,我们当然希望所有人的手指脚趾都是1 0个。因为我们的手非常适合数数,因而我们人类已经适应了以1 0为基础的数字系统:

    前面数章已经提到过,通常使用的数字系统称为以1 0为基础的数字系统或十进制。这个数字系统对我们来说非常自然,因而我们很难想像出还有其他的数字系统。事实上,当我们看到数字1 0的时候,不由自主地就会认为这个数是指下面这么多只鸭子:

    但是,数字1 0是指这么多只鸭子的唯一理由是因为这么多只鸭子与我们的手指数目相同。如果人类不是有那么多只手指,我们数数的方式就会有所不同,数字1 0就可能代表别的东西了。同样是数字1 0,可以指这么多只鸭子:

或这么多只鸭子:

甚至可以是这么多只鸭子:

    当我们明白了1 0可以指只有两只鸭子的时候,也就可以解释开关、电线、灯泡、继电器(或干脆就叫计算机)是怎样表示数字的了。

    如果人类像卡通人物那样,每只手上只有4个手指会怎样呢?我们可能永远都不会想到要发明一种以1 0为基础的数字系统的问题,取而代之的是我们可能会认为数字系统基于8是正常、自然、合理、必然的,是毫无疑问的,是非常合适的。这时,就不能称之为十进制了,得将它称作为以8为基础的数字系统或八进制。

    如果数字系统是以8为基础组织起来的,就不需要这样的一个符号:9

    把这个符号拿给任何一个卡通人物看,都会有同样的反应:“那是什么?它是干什么用的?”如果再仔细想一会儿的话,你会发现连这样的一个字符也不需要:8

    在十进制数字系统中,没有专门用来表示0的符号,所在在八进制数字系统中,也没有专门用来表示10的符号。

    在十进制数字系统中数数的方式是0123456789,然后是1 0。在八进制数字系统中数数的方式是01234567,然后是什么呢?我们已经没有符号可用了,唯一的一个有意义的可用符号是1 0,的确是那样。在八进制数中,7之后紧接着的数字是1 0,但是1 0并不是指人类的手指那么多的数目。在八进制数中,1 0指的是卡通人物手指的数目:

    使用非十进制的数字系统时,将数字“1 0”读作“么零”可以避免一些混淆。同样,“1 3”可以读作“么三”,“2 0”可以读作“二零”。要想真正避免混淆,可以将“2 0”读作“八进制二零”或“基于8的数二零”。

    即使没有手指和脚趾帮忙,我们仍能够将八进制数继续数下去。除了要跳过那些含有89的数字以外,它基本上和数十进制的数是一样的。当然,相同的数字代表的数量是不同的:

0 12345671 0111 21 31 41 51 61 7

2 0 2 12 22 32 42 52 62 73 03 13 23 33 43 53 63 74 0

4 1 4 24 34 44 54 64 75 05 15 25 35 45 55 65 76 06 16 2

6 3 6 46 56 66 77 07 17 27 37 47 57 67 71 0 0 . . .

    最后一个数字读作“么零零”,是卡通人物拥有的手指数自乘的结果(即平方)。

    在写十进制或八进制数时,为避免混淆,可以借助使用特定的标记以区别表示数字系统。下面用标记“TEN”表示十进制数,标记“EIGHT”表示八进制数。

这样,白雪公主遇到的小矮人的数目是7TEN7EIGHT

卡通人手的手指数是8TEN10EIGHT

贝多芬写的交响乐的首数是9TEN11EIGHT

人的手指的数目是10TEN12EIGHT

一年中的月份数是12TEN14EIGHT

两个星期所包含的天数是14TEN16EIGHT

“情人”的生日庆祝会是16TEN20EIGHT

一天中所包含的小时数是24TEN30EIGHT

拉丁字母表中的字符数是26TEN32EIGHT

与一夸脱液体相当的盎司数为32TEN40EIGHT

一副牌中含有的牌数是52TEN64EIGHT

国际象棋棋盘的方格数是64TEN100EIGHT

Sunset Strip最著名的17牌号是77TENor 115EIGHT

美式足球场的面积是100TEN144EIGHT

参加温布尔登网球公开赛女单初赛的人数是128TEN200EIGHT

古埃及孟斐斯城市面积的平方英里数是256TENor 400EIGHT

   注意,在上面一系列的八进制数中,有一些好整数,像100EIGHT200EIGHT400EIGHT。好整数通常是指结尾有一些零的数。在结尾处有两个零的十进制数意味着它是100TEN10TEN乘以10TEN;在八进制数中,结尾处有两个零表示它是100EIGHT10EIGHT乘以10EIGHT(或8TEN乘以8TEN,等于64TEN)。

     你可能已经注意到了,好的八进制整数100EIGHT200EIGHT400EIGHT与十进制数64TEN128TEN256TEN相等,它们都是2的整数次幂。例如,400EIGHT等于4EIGHT乘以10EIGHT乘以10EIGHT,所有这些数都是2的整数次幂。任何时候,将2的整数次幂和另一个2的整数次幂相乘,得到的仍是2的整数次幂。

    下表给出了一些2的整数次幂的十进制及其对应的八进制的表示形式:

    最右边一列的好整数给我们一个暗示:十进制以外的数字系统可能对使用二元码有所帮助。

    八进制数字系统和十进制数字系统在结构上没有什么差别,只是在细节上有一些差异。例如,八进制数的每一个位置代表的值是该位数字乘以8的整数次幂的结果:

这样,八进制数3 7 2 5EIGHT可以拆分成这样:

    还可以写成另外几种不同的形式。下面就是其中的一种,采用十进制形式的8的整数次幂:

采用八进制形式的8的整数次幂的情况:

还有另外的一种拆分形式:

    如果算出其十进制的结果,会得到2 0 0 5TEN。这就是将八进制数转换成十进制数的方法。

    可以采用与做十进制加法和乘法相同的办法来做八进制数的加法和乘法。唯一真正的区别在于要采用不同的表格来对各个数字进行乘法或加法运算。下面是八进制数的加法表:

   如,5EIGHT+ 7EIGHT= 14EIGHT。可以采用与做十进制加法相同的方法将两个稍长一点儿的八进制数相加:

        

       先从最右边的一列做起,5加上3等于1 0,该位写下0,向前进1134等于1 0 ,该位写下0,向前进1116等于1 0

   同样,在八进制中,2乘以2仍然等于4。但是3乘以3却不等于9,那是多少呢?3乘以3等于11EIGHT,此数与9TEN所代表的数量相等。下图是完整的八进制数的乘法表:

    这里,4×6等于30EIGHT,也即表明30EIGHT4×6的十进制结果24TEN是等值的。

    八进制数字系统与十进制数字系统一样,都是有效的,但八进制数字系统在理解上更深了一层。既然我们已为卡通人物开发出了一套数字系统,就再给龙虾开发一套适合它们用的数字系统吧。龙虾根本没有手指,但它两只前爪的末端都有螯。适合于龙虾的数字系统是四进制数字系统或称为基于4的数字系统:

    四进制数可以这样来数:

          01231 0111 21 32 02 12 22 33 03 13 23 31 0 01 0 11 0 2

1 0 311 0,等等。

    这里不打算在四进制数上花太多的时间,因为还有更重要的事情要做。但我们还是要看一下四进制中的每一位是怎样和4的某个整数次幂相对应的:

   四进制数3 1 2 3 2可以写成:

   也可以写成:

   还可以写成:

    如果以十进制数的形式计算其结果,就会发现31 232FOUR等于878TEN

    现在,我们要做一个跳跃并且是最远的一跳。假定我们是海豚,并且必须用两鳍来数数。则这个数字系统就是基于2的数字系统或二进制的。这样似乎只需要两个数字,即01

    现在,01已是你要处理的全部问题,需要练习一下才能习惯使用二进制数。二进制数最大的问题是数字用完得很快。例如,下图是海豚怎样用它的鳍数数的例子:

    是的,在二进制中,1后面的数字是1 0。这是令人惊讶的,但也并不奇怪。无论使用哪种数字系统,当单个位的数字用完时,第一个两位数字都是1 0。在二进制系统中,可以这样来数数:

0 11 0111 0 01 0 111 01111 0 0 01 0 0 11 0 1 0

1 0 1111 0 011 0 1111 011111 0 0 0 01 0 0 0 1、……

    这些数看起来好像很大,实际上并不是这样。更准确地说二进制数长度增长的速度要快过二进制数增大的速度:

每个人的头的个数为1TEN1TW O

海豚身上的鳍的个数为2TEN10TW O

一个大汤匙中包括的小茶匙的数目为3TEN11TW O

正方形的边数为4TEN100TW O

每个人一只手的手指数为5TEN101TW O

一种昆虫的腿数为6TEN11 0TW O

一星期的天数为7TEN111TW O

八重奏中音乐家的个数为8TEN1000TW O

太阳系中的行星(包括冥王星在内)总数为9TEN1001TW O

牛仔帽重量以加仑计算为10TEN1010TW O等等。

   在多位二进制数中,数字的位置和2的整数次幂的对应关系为:

因此,任何时候由一个1后跟几个零构成的二进制数一定是2的整数次幂。2的幂与二进制数中零的个数相等。下面是扩充的2的各次幂的表,可用来说明这条规则:

    假定有一个二进制数1 0 11 0 1 0 11 0 1 0,它可以写成:

也可以这样写:

    如果将各个部分以十进制数的形式相加,得到2 0 48+512+256+64+16+8+22906TEN。将二进制数转换成十进制数非常简单,你可能更喜欢借助已准备好的模板进行转换:

    这个模板允许你转换最大长度为8的二进制数,但它扩充起来非常容易。使用时,将8个二进制数字放到上部的8个小盒子中,一个盒子放一个数字。做8个乘法运算,将结果分别放到底部的8个小盒子中。将8个盒子中的数字相加就得到最终结果。下面是将10010110转化成十进制数的例子:

    将十进制数转换成二进制数就没那么直接了。但这里也有一个帮助你将0225范围内的十进制数转换成二进制数的模板:

    实际转化过程要表面上看的麻烦得多,所以一定要仔细按照下面的指导来做。将整个十进制数(应小于等于225)放在左上角的方格中。用除数(128)去除那个数(被除数),如下图所示。将商写在正下方的盒子中(即左下角的盒子中),余数写在右边的盒子中(即上面一行左数第二个盒子中)。用第一个余数再除以下一个算子64。依照模板的顺序用同样的方法继续做下去。

    记住,每次求得的商只能是0或者1。如果被除数小于除数,商为0,余数和被除数相等;如果被除数大于除数,商为1,余数为被除数与除数之差。下面是将1 5 0转换成二进制数的过程:

    如果要做两个二进制数的加法或乘法,也许直接采用二进制来做比转化成十进制再做还要简单。这将是你真正喜欢二进制数的地方。如果只需记住下面的二进制加法表就可以做加法运算,也就不难想象掌握加法运算该有多快:

   用二进制加法表将两个二进制数相加:

    从最右边的一列开始做起:1加上0等于1;右数第2列:0加上1等于1;第3列:1加上1等于0,进位为1;第4列:1(进位值)加上0再加上0等于1;第5列:0加上1等于1;第6列:11等于0,进位为1;第7列:1(进位值)加上1再加上0等于1 0

    乘法表比加法表更简单,因为该表可以由两个基本的乘法规则推导出来:零乘以任何数都等于01与任何数相乘仍是那个数本身:

    下面是1 3TEN11TEN以二进制数的形式做乘法的过程:

   最后结果是143TEN

    人们在使用二进制数的时候通常将它们写成带有前导零的形式(即第一个1的左边有零)例如,0 0 11而不写成11。这些零不会改变数字的值,只是起到一些装饰作用。例如,下面是二进制的前1 6个数以及和它们等值的十进制数:

    让我们再仔细看看这些二进制数字。考虑一下这4个垂直列中每一列的01,注意它们在一列中自上而下是以怎样的规律变化的:

    下面是看待这些数字的另一种方式:在数二进制数的时候,最右边的数字(也称最低位数字)是在01之间变化的。当它每次从1变到0时,右数第二位数字(也称次低位数字)也要发生变化,或者从0变到1,或者从1变到0。每次只要有一个二进制数位的值由1变到0,紧挨着的高位数字也会发生变化,要么从0变到1,要么从1变到0

    我们在写十进制中比较大的数字时,通常每三个数字之间留一点儿空隙,这样,我们一看就知道这个数的大概数值。例如,当你看到数字12000000时,你可能不得不去数其中0的个数,但如果看到的是12 000 000,则马上就能知道是一亿两千万二进制数的位长度增加得特别快。例如,一亿两千万的二进制表示为:1 0 11 0 111 0 0 0 11 0 11 0 0 0 0 0 0 0 0。为了让它更易读,通常是每四个数字之间用连字符或空格来分开。例如;1 0 11 - 0 111 - 0 0 0 1 - 1 0 11 - 0 0 0 0 - 0 0 0 01 0 11 0 111 0 0 0 11 0 11 0 0 0 0 0 0 0 0。本书的后面会讲到更简单的二进制数的表示方法。

    通过将数字系统减少至只有01两个数字的二进制数字系统,我们已经在能够接受的范围内做了深入的讨论。不可能找到比二进制数字系统更简单的数字系统了。二进制数字系统架起了算术与电之间的桥梁。前面各章中,我们所看到的开关、电线、灯泡、继电器等物体都可以表示二进制数01

    电线可以表示二进制数字。有电流流过电线代表二进制数字1;如果没有,则代表二进制数字0

    开关可以表示二进制数字。如果开关闭合,代表二进制数字1;如果开关断开,代表二进制数字0

    灯泡可以表示二进制数字。如果灯泡亮着,代表二进制数字1;如果没亮,代表二进制数字0

    电报继电器可以表示二进制数字。继电器闭合,代表二进制数字1;继电器断开,代表二进制数字0

二进制数与计算机密切相关!

    大约在1948年,美国数学家John Wilder Tukey(生于1915年)提前认识到二进制数将在未来几年中随着计算机的流行而发挥更大的作用。他决定创造一个新的、更短的词来代替使用起来很不灵活的五音节词—binary digit。他曾经考虑用bigitbinit,但最后还是选用了短小、简单、精巧且非常可爱的单词b i t (比特)来代替binary digit 这个词。

 

第九章  二进制数

        1973年,当安东尼·奥兰多在他写的一首歌中要求他挚爱的人“系一条黄色的绸带在橡树上”时,他并没有要求他的爱人进行繁琐的解释或冗长的讨论,只要求她给他一个简单的结果。他不去关心其中的因果,即使歌中复杂的感情和动情的历史在现实生活中重演,所有的人真正想知道的仅仅是一个简单的是或不是。他希望在树上系一条黄色的绸带来表示:“是的,即使你犯了很大的错,并且被判了入狱三年,我仍希望你回来和我一起共渡时光。”他希望用树上没有黄色的绸带来表示:“你连停在这里都别想。”

    这是两个界线分明、相互排斥的答案。奥兰多没有这样唱:“如果你想再考虑一下的话,就系半条黄色的绸带”或者“如果你不爱我但仍希望我们是朋友,就系一条蓝色的绸带吧”。相反,他让答案非常的简单。

    和黄色绸带的有无具有同样效果的另外几个例子(但可能无法用在诗里)是可以选择一种交通标记放在院外,可能是“请进”或“此路不通”。

    或者在门上挂一个牌子,上写“关”或“开”。

    或者用从窗口能够看到的一盏灯的亮灭来表示。

    如果你只需说“是”或“不是”的话,可以有很多种方式来表达。你不必用一个句子来表达是或不是,也不需要一个单词,甚至连一个字母都不要。你只要用一个比特,即只要一个01即可。

    正如我们在前面的章节中所了解到的,通常用来计数的十进制数事实上并没有什么与众不同的地方。非常清楚,我们的数字系统之所以是基于1 0的(十进制数)是因为我们有1 0个手指头。我们同样有理由使用八进制数字系统(如果我们是卡通人物)或四进制数字系统(如果我们是龙虾),甚至是二进制数字系统(如果我们是海豚)。

    但是,二进制数字系统有一点儿特别:它可能是最简单的数字系统。二进制数字系统中只有两种二进制数字—01。要是我们想寻求更简单的数字系统,只好把数字1去掉,这样,就只剩下0一个数字了。只有一个数字0的数字系统是什么都做不成的。

    “b i t (比特)”这个词被创造出来代表“binary digit ”,它的确是新造的和计算机相关的最可爱的词之一。当然,“b i t”有其通常的意义:“一小部分,程度很低或数量很少”。这个意义用来表示比特是非常精确的,因为1比特—一个二进制数字位—确实是一个非常小的量。

    有时候当一个新词诞生时,它还包含了一种新的意思。b i t这个词也是这样。1比特的意思超过了被海豚用来数数的二进制数字位所包含的意义。在计算机时代,比特已经被看作是组成信息块的基本单位。

    当然,上述说法不一定完全正确,比特并不是传送信息的唯一的方式。字母、单词、摩尔斯码、布莱叶盲文,十进制数字都可以用来传递信息。比特传递的信息量很小。1比特只具备最少的信息量,更复杂的信息需要多位比特来传递。(我们说比特传递的信息量小,并不是说它传送的信息不重要。事实上黄绸带对于与它相关的两个人来说是一个非常重要的信息。)

    “听,孩子们,你们很快就能听到Paul Revere午夜的马蹄声。”享利·朗费罗写道。尽管他在描述Paul Revere是怎样通知美国人英国殖民者入侵的消息时不一定与史实完全一致,但他的确提供了一个利用比特传递信息的令人茅塞顿开的例子:

 (他告诉他的朋友:“如果英军今晚入侵,

你就在北教堂的钟楼拱门上悬挂点亮的提灯

作为信号。一盏提灯代表英军由陆路入侵,

两盏提灯代表英军由海路入侵。……)

    也就是说,Paul Revere的朋友有两盏灯。如果英军由陆路入侵,他就挂一盏灯在教堂的钟楼上;如果英军由海路入侵,他就挂两盏灯在教堂的钟楼上。

    然而,朗费罗并没有将所有的可能都涉及到。他留下第三种情况没有说,那就是英军根本就没有入侵的情况。朗费罗已经暗示第三种可能的信息可以由不挂提灯的方式来传递。

    让我们假设那两盏灯是永久固定在教堂钟楼上的。在正常情况下,它们都不亮:

    这就是指英军还没有入侵。如果一盏提灯亮:

    表示英军正由陆路入侵。如果两盏提灯都亮:

表示英军正由海路入侵。

    每一盏提灯都代表一个比特。亮着的灯表示比持值为1,未亮的灯表示比特值为0。前面奥兰多已经说明了传送只有两种可能性的信息只需要一个比特。如果Paul Revere只需被告知英军正在入侵(不管是从何处入侵)的消息,一盏提灯就足够了。点亮提灯代表英军入侵,未点亮提灯代表又是一个和平之夜。

    传递三种可能性的消息还需要再有一盏提灯。一旦再有一盏提灯,两个比特就可以通知有四种可能的信息:

0 0 =英军今晚不会入侵

0 1 =英军正由陆路入侵

1 0 =英军正由陆路入侵

11 =英军正由海路入侵

         Paul Revere将三种可能性用两盏提灯来传送的做法事实上是相当富有经验的。用通信理论的术语说,他采用了冗余的办法来降低噪声的影响。通信理论中的噪声是指影响通信效果的任何事物。电话线路中的静电流显然是影响电话通信的一种噪声。然而,即使是在有噪声的情况下,电话通信仍能够成功,因为口语中存在大量的冗余。你同样可以听懂对方的话而无需将每个音节、每个字都听得很清楚。

    在上述例子中,噪声是指晚上光线黯淡以及Paul Revere距钟楼有一定的距离,它们都阻碍了Paul Revere声将钟楼上的两盏灯区分清楚。下面是朗费罗的诗中很重要的一段:

(哦!他站在与钟楼等高的位置观察,

一丝微光,然后,有一盏灯亮了!

他跳上马鞍,调转马头,

徘徊,凝视,直到看清所有的灯

另一盏灯也亮了!)

    那当然不是说Paul Revere正在辨清到底是哪盏灯先亮的问题。

    这里最本质的概念是信息可能代表两种或多种可能性的一种。例如,当你和别人谈话时,说的每个字都是字典中所有字中的一个。如果给字典中所有的字从1开始编号,我们就可能精确地使用数字进行交谈,而不使用单词。(当然,对话的两个人都需要一本已经给每个字编过号的字典以及足够的耐心。)

    换句话说,任何可以转换成两种或多种可能的信息都可以用比特来表示。不用说,人类使用的很多信息都无法用离散的可能性来表示,但这些信息对我们人类的生存又是至关重要的。这就是人类无法和计算机建立起浪漫关系的原因所在(无论怎样,都希望这种情况不会发生)。如果无法将某些信息以语言、图片或声音的形式表达,那也不可能将这些信息以比特的形式编码。当然,你也不会想将它们编码。

    举手或不举手是一个比特的信息。两个人是否举手—就像电影评论家Roger Ebert和刚去世不久的Gene Siskel对新影片提供他们最终的评价结果那样—传递两个比特的信息。(我们将忽略掉他们实际上对影片做的评语,而只关心他们有没有举手的问题。)这样,我们用两个比特代表四种可能:

   00 =他们都不喜欢这部影片

           01 = Siskel讨厌它,Ebert喜欢它

           10 = Siskel喜欢它,Ebert讨厌它

    11 = SiskelE bert都喜欢它

    第一个比特值代表Siskel的意见,0表示Siskel讨厌这部影片,1表示Siskel喜欢这部影片。同样,第二个比特值代表Ebert的意见。

    因此,如果你的朋友问你SiskelEbert是怎么评价《Impolite Encounter》这部电影的,你不用回答“Siskel举手了,Ebert没有举手”或者“Siskel喜欢这部电影,Ebert不喜欢这部电影”,你可以简单地回答“么零”。你的朋友只要知道哪一位代表的是Siskel的意见,哪一位代表的是Ebert的意见,并且知道值为1代表举手,值为0代表没有举手,你的回答就是可以被人理解的。当然,你和你的朋友都要知道这种代码的含义。

    我们也可以一开始就声明值为1的比特位表示没有举手,值为0的比特位表示举手了,这可能有点违反常规。通常我们会认为值为1的比特位代表正面的事情,而值为0的比特位代表相反的一方面,这的确只是一种很随意的指派。无论怎样,用此种代码的人只要明白01分别代表什么就可以了。

    某一位或几位比特位的集合所代表的意义通常是和上下文相关的。橡树上的黄绸带可能只有系绸带的人和期望看到绸带的人知道其中的意思,改变绸带的颜色、系绸带的树或系绸带的日期,绸带可能会被认为只是一块毫无意义的破布。同样,要从SiskelEbert的手势中得到有用的信息,我们至少要知道正在讨论的是哪部影片。

    如果你保存了SiskelEbert对一系列影片的评价和投票结果,你就有可能在表示SiskelEbert意见的比特信息中再增加一位代表你自己的观点的比特位。增加的第三位使得其代表的信息可能性增加到8种:

000 = Siskel讨厌它,Ebert讨厌它,我讨厌它

001 = Siskel讨厌它,Ebert讨厌它,我喜欢它

010 = Siskel讨厌它,Ebert喜欢它,我讨厌它

011 = Siskel讨厌它,Ebert喜欢它,我喜欢它

100 = Siskel喜欢它,Ebert讨厌它,我讨厌它

101 = Siskel喜欢它,Ebert讨厌它,我喜欢它

110 = Siskel喜欢它,Ebert喜欢它,我讨厌它

111 = Siskel喜欢它,Ebert喜欢它,我喜欢它

    使用比特来表示信息的一个额外好处是我们清楚地知道我们解释了所有的可能性。我们知道有且仅有8种可能性,不多也不少。用3个比特,我们只能从0数到7,后面再没有3位二进制数了。

    在描述SiskelEbert的比特时,你可能一直在考虑一个严重的,并且是令人烦恼的问题—对于Leonard MaltinMovie &Video Guide怎么办呢?别忘了,Leonard Maltin是不采用举手表决这种形式的,他对电影的评价用的是更传统的星级系统。

    要想知道需多少个Maltin比特,首先要了解一些关于Maltin评分系统的知识。Maltin给电影的评价是14颗星,并且中间可以有半颗星。(仅仅是为了好玩,他实际上不会给电影只评一颗星,取而代之的是给一个BOMB[炸弹]。)这里总共有七种可能性,也就是说只需要3个比特位就可以表示一个特定的评价等级了:

    你可能会问111怎么办呢,111这个代码什么意义都没有,它没有定义。如果二进制代码111被用来表示Maltin等级,那一定是出现错误了。(这可能是计算机出的错误,因为人不会给出这样的评分。)

    前面我们曾用两个比特来代表SiskelEbert的评价结果,左边的一位代表Siskel的评价意见,右边的一位代表Ebert的评价意见。在上述Maltin评分系统中,各个比特位都有确定的意义吗?是的,当然有。将比特编码的数值加2再除以2,就得到了Maltin评分中对应的星的颗数。这样编码是由于我们在定义代码时遵循了合理性和连贯性,我们也可以下面的这种方式编码:

    只要大家都了解代码的含义,这种表示就和前述代码一样,都是合理的。

    如果Maltin遇到了一部连一颗星都不值得给的电影,他就会给它半颗星。他当然有足够的代码来表示半颗星的情况,代码会像下面这样定义:

    但是,如果他再遇到连半颗星的级别都不够的影片并且决定给它没有星的级别(AOMICBOMB),他就得再需要一个比特位了,已经没有3个比特的代码空闲了。

   《Entertainment Weekly》杂志常常给事物定级,除了电影之外还有电视节目、CD、书籍、CD - ROM、网络站点等等。等级的范围从A +F,如果你数一下的话,发现共有1 3个等级。这样,需要四个比特来代表这些等级:

    有3个代码没有用到,它们是:11 0 1111 01111,加上后总共是1 6个代码。

    只要谈到比特,通常是指特定数目的比特位。拥有的比特位数越多,可以传递的不同可能性就越多。

    对十进制数当然也是同样的道理。例如,电话号码的区号有几位呢?区号共有3位数字。如果所有的区号都使用的话(实际上有一部分区号并没有使用,将它们忽略),一共有1031000个代码,从000999。区号为2127位数的电话号码有多少种可能呢?10710000000个;区号为212并且以260开头的电话号码有多少个呢?10410000个。

    同样,在二进制数中,可能的代码数等于2的比特位数次幂:

    每增加一个比特位,二进制代码数翻一番。

    如果知道需要多少个代码,那么怎样才能知道需要多少个比特位呢?换句话说,在上述表中,如何才能由代码数反推出比特位数呢?

    用到的方法叫作取以2为底的对数,对数运算是幂运算的逆运算。我们知道27次幂等于128,以2为底的128的对数就等于7。用数学记号来表示第一个句子为:27= 128,它与下述句子等价:log2128 = 7

    因此,如果以2为底的128的对数等于7,以2为底的256的对数等于8,那么,以2为底的200的对数等于多少呢?大约是7 . 6 4,但实际上并不需要知道它。如果要表示200种不同的事物,我们共需要8个比特。

    比特通常无法从日常观察中找到,它深藏于电子设备中。我们看不到压缩磁盘( CD )、数字手表或计算机中编过码的比特,但有时候比特也可以清晰地看到。

    下面就是一个例子。如果你手头有一个使用3 5毫米胶片的相机,观察一下它的卷轴。这样拿住胶卷:

    胶卷上有像国际跳棋棋盘一样的银色和黑色方格,方格已用数字11 2标识。这叫作D X编码,这1 2 个方格实际上是1 2个比特。一个银色的方格代表值为1的比特,一个黑色的方格代表值为0的比特。方格17通常是银色的(代表1)。

    这些比特是什么意思呢?你可能知道有些胶片对光的敏感程度要比其他胶片强,这种对光的敏感程度称作胶片速度。说对光非常敏感的胶片很快是因为这种胶片的曝光速度快。曝光速度是由ASAAmerican standards association,美国标准协会)来制定等级的,最常用的等级有100200400ASA等级不只是以十进制数字的形式印在胶卷的外包装和暗盒上,而且还以比特的形式进行了编码。

    胶卷总共有2 4ASA等级,它们是:

    为ASA等级编码需要多少个比特呢?答案是5个比特。我们知道,24= 1 6 ,与2 4比太小了;25= 32 ,又超过了所需的编码数。

    比特值与胶片速度的对应关系如下所示:

    多数现代的35毫米照相机胶片用的都是这些代码(除了那些要手工进行曝光的相机和具有内置式测光表但需要手工设置曝光速度的相机以外)。如果你看过照相机的内部放置胶卷的地方,你应该能够看到和胶片的金属方格(16号)相对应的6个金属可接触点。银色方格实际上是胶卷暗盒中的金属,是导体;油漆了的黑色方格,是绝缘体。

    照相机的电子线路中有一支流向方格1的电流,方格1通常是银色的。这支电流有可能流到方格26,这要依方格中是纯银还是涂了油漆而定。这样,如果照相机在接触点45检测到了电流而在接触点236没有检测到,胶片的速度就是4 0 0 ASA。照相机可以据此调节曝光时间。

    廉价的照相机只要读方格2和方格3,并且假定胶片速度是50100200400 ASA四种可能速度之一。

    多数相机不读方格81 2。方格891 0用来对这卷胶卷进行编码;方格111 2指出曝光范围,依胶片用于黑白照片、彩色照片还是幻灯片而定。

    也许最常见的二进制数的表现形式是无处不在的UPCuniversal product code,通用产品代码),即日常所购买的几乎所有商品包装上的条形码。条形码已经成为计算机在日常生活中应用的一种标志。

    尽管UPC常常使人多疑,但它确实是一个无辜的小东西,发明出来仅仅是为了实现零售业的结算和存货管理的自动化,且其应用是相当成功的。当它和一个设计精良的结算系统共同使用时,顾客可以拿到列出细目的售货凭条,这一点是传统现金出纳员所无法做到的。

    有趣的是,UPC也是二进制代码,尽管它初看起来并不像。将UPC解码并看看UPC码具体是怎样工作的是很有益的。

    通常情况下,UPC30条不同宽度的垂直黑色条纹的集合,由不同宽度的间隙分割开,其下标有一些数字。例如,以下是Campbell公司10.75盎司的罐装鸡汁面包装上的UPC

    可将条形码形象地看成是细条和黑条,窄间隙和宽间隙的排列形式,事实上,这是观察条形码的一种方式。黑色条有四种不同的宽度,较宽的条的宽度是最细条的宽度的两倍、三倍或者四倍。同样,各条之间的间隙中较宽的间隙是最窄间隙的两倍、三倍或者四倍。

    但是,看待UPC的另一种方式是将它看作是一系列的比特。记住,整个条形码与条形码扫描仪在结算台“看”到的并不完全一样。扫描仪不会识别条形码底部的数字,因为识别数字需要一种更复杂的技术—光学字符识别技术,又称作OCRoptical character recognition)。

    实际上,扫描仪只识别整个条形码的一条窄带,条形码做得很大是为了便于结算台的操作人员用扫描仪对准顾客选购的物品。扫描仪所看到的那一条窄带可以这样表示:

    它看上去是不是很像摩尔斯编码?

    当计算机自左向右进行扫描时,它给自己遇到的第一个条分配一个值为1的比特值,给与条相邻的间隙分配一个值为0的比特值。后续的间隙和条被当作一行中一系列比特中的1个、2个、3个还是4个比特读进计算机要依据条或间隙的宽度而定。扫描进来的条形码的比特形式很简单:

因此,整个UPC只是简单的由95个比特构成的一串。本例中,这些比特可以像下面这样分组:

    起初的3个比特通常是1 0 1,这就是最左边的护线,它帮助计算机扫描仪定位。从护线中,扫描仪可以知道代表单个比特的条或间隙的宽度,否则,所有包装上的UPC印刷大小都是一样的。

    紧挨着最左边的护线是每组有7个比特位的六组比特串,每一组是数字09的编码之一,我们在后面将证明这一点。接着的是5个比特的中间护线,此固定模式(总是0 1 0 1 0)是一种内置式的检错码。如果扫描仪在应当找到中间护线的地方没有找到它,扫描仪就认为那不是UPC。中间护线是防止条形码被窜改或错印的方法之一。

    中间护线的后面仍是每组7个比特的6组比特串。最后是最右边的护线,也总是1 0 1。最后的最右护线使得UPC反向扫描(也就是自右向左扫描)同正向扫描一样成为可能,这一点我们将在后面解释。

因而整个UPC1 2个数字进行了编码。左边的UPC包含了6个数字的编码,每个数字占有7个比特位。你可以用下表进行解码:

    注意,每个7位代码都是以0开头,以1结尾的。如果扫描仪遇到了第一个比特位值为1或最后一个比特位值为0的情况,它就知道自己没有将UPC正确地读入或者是条形码被窜改了。另外我们还注意到每个代码都仅有两组连续的值为1的比特位,这就意味着每个数字对应着条形码中的两个竖条。

    上表中的每个代码中都包含有奇数个值为1的比特位,这也是用于检测差错和数据一致性的一种机制,称为奇偶校验。如果一组比特位中含有奇数个1,就称之为奇校验;如果含有偶数个1,就称之为偶校验。这样看来,所有这些代码都拥有奇校验。

    为了给UPS右边的7位一组的数字解码,可以采用下面的表格:

    这些代码都是前述代码的补码或补数:凡是1的地方都换成0,凡是0的地方都换成1。这些代码都是以1开始,以零结束,并且每组都有偶数个1,称之为偶校验。

    现在,可以对UCP进行解码了。借助前两个表格,Campbell公司1 0盎司的罐装鸡汁面的包装上用UPC编码的1 2个数字是:0 51000 01251 7

    这个结果是令人失望的,正如你所看到的那样,它们和印在UPC底部的数字完全相同。(这样做是有意义的,因为由于某种原因,扫描仪可能无法识别条形码,收银员就可以手工将这些数字输进去。)我们还没有完成解码的全部任务,而且,我们也无法从中解码任何秘密信息。然而,关于UPC的解码工作已经没有了,那3 0个竖条已经变成了1 2个数字。

   第一个数字(在这里是0)被称为数字系统字符,0的意思是说这是一个规范的UPC编码。如果是具有不同重量的货物的UPC(像肉类或其他商品),这个数字是2;订单、票券的UPC编码的第一个数字通常是5

    紧接着的5个数字是制造商代码。在上例中,5 1 0 0 0Campbell鸡汁面公司的代码。Campbell公司生产的所有产品都使用这个代码。再后面的5个数字(0 1 2 5 1)是该公司的某种产品的编号,上例中是指1 0盎司的罐装鸡汁面。别的公司的鸡汁面可能有不同的编号,且0 1 2 5 1在另外一个公司可能是指一种完全不同的产品。

    和通常的想法相反,UPC中没有包含该种产品的价格。产品的价格信息可以从商店中使用的与该扫描仪相联的计算机中检索互到。

    最后的数字(这里是7)称作模校验字符,这个字符可用来进行另外一种错误检验。为了解释校验字符是怎样工作的,将前11个数字(是0 51000 01251)各用一个字母来代替:A BCDEF GHIJK

    然后,计算下式的值:

         3×(A + C + E + G + I + K+B + D + F + H + J

    从紧挨它并大于等于它的一个1 0的整倍数中减去它,其结果称为模校验字符。在上例中,有:

         3×(0 + 1 + 0 + 0 + 2 + 1+5 + 0 + 0 + 1 + 5 = 3×4 + 11 = 2 3

    紧挨2 3并大于等于2 3的一个1 0的整倍数是3 0 ,故:3 0 2 3 = 7

    这就是印在外包装上并以UPC形式编码的模校验字符,这是一种冗余措施。如果扫描仪计算出来的模校验结果和UPC中编码中的校验字不一致,计算机就不能将这个UPC作为一个有效值接收。

    正常情况下,表示从09的十进制数字只需4个比特就足够了。在UPC中,每个数字用了7个比特,这样总共有9 5个比特来表示11个有用的十进制数字。事实上,UPC中还包括空白位置(相当于90比特),位于左、右护线的两侧。因而,总共有11 3个比特用来编码11个十进制数,平均每个十进制数所用超过了1 0个比特位!

    正像我们所知道的那样,有部分冗余对于检错来讲是必要的。这种商品编码如果能够很容易地被顾客用粗头笔修改的话,这种代码措施也就难以发挥其作用了。

    UPC 编码可以从两个方向读,这一点是非常有益的。如果扫描仪解码的第一个数字是偶校验(即:每7位编码中共有偶数个1),扫描仪就知道它正在从右向左进行解码。计算机系统用下表对右边的数字解码:

   下面是对左边数字的解码表:

    这些7位编码与扫描仪由左向右扫描时所读到的编码完全不同,但不会有模棱两可的现象。

    让我们再看看本书中提到的由点、划组成其间用空格分开的摩尔斯电码。摩尔斯电码看上去不像是由01组成的,但它确实是。

    下面回忆一下摩尔斯电码的编码规则:划的长度等于点长度的三倍;单个的点或划之间用长度与点的长度相等的空格分开;单词内的各个字母之间用长度等于划的长度的空格分隔;各单词之间由长度等于两倍的划长度的空格分开。

    为使分析更加简单,我们假设划的长度是点长度的两倍而不是3倍。也就是说,一个点是一个值为1的比特位,一个划是两个值为1的比特位,空格是值为0的比特位。

   下面是第2章的摩尔斯电码的基本表:

  下面是将它转化为比特形式的结果:

   注意,所有的编码都以1开头,以两个0结束。结尾处的两个零代表单词中各个字母之间的空格,单词之间的空格用另外的一对0来表示。因而,“H i , t h e r e”的摩尔斯电码通常是这样的:

    但是,采用比特形式的摩尔斯电码看起来像UPC编码的横切面:

    用比特的形式表示布莱叶盲文比表示摩尔斯电码容易得多。布莱叶编码是6比特代码。布莱叶盲文中的每一个字母都是由6个点组成的,点可能是凸起的,或没有凸起(平滑)的。如在第3章中讲的那样,这些点通常用数字16编号:

   例如,单词“code”可以用布莱叶盲文这样表示:

    如果突起的点是1,平坦的点是0,则布莱叶盲文中的每一个符号都可以用6个比特的二进制代码表示。单词“c o d e”中的四个布莱叶字母符号就可以简单地写成:100100 101010 100110 100010,最左边的一位对应编号为1的位置,最右边的一位对应编码为6的位置。

    正如前面所讲到的,比特可以代表单词、图片、声音、音乐、电影,也可以代表产品编码、胶片速度、电影的受欢迎程度、英军的入侵以及某人所挚爱的人的意愿。但是,最基本的一点是:比特是数字。当用比特表示信息时只要将可能情况的数目数清楚就可以了,这样就决定了需要多少个比特位,从而使得各种可能的情况都能分配到一个编号。

    比特在哲学和数学的奇怪混合物—逻辑—中发挥作用。逻辑最基本的目标是证明某个语句是否正确,正确与否也可以用10来表示。

第十章  逻辑与开关

真理是什么呢?亚里士多德认为逻辑与它有关。他的讲义合集《工具论》(Organon,可追溯到公元前4世纪)是最早的关于逻辑的详细著作。对于古希腊人而言,逻辑是追寻真理的过程中用于分析语言的一种手段,因此它被认为是一种哲学。亚里士多德的逻辑学的基础是三段论。最有名的三段论(它并非是在亚里士多德的著作中发现的)是:

在三段论中,两个前提被假设是正确的,并由此推出结论。

苏格拉底之死这个例子看上去似乎太直白了,但还有许多其他不同的三段论。例如,考虑下面两个由19世纪数学家CharlesDodgson(也就是LewisCarroll)提出的前提:

它所能推出的结论一点儿也不明显。(事实上,结论是“一些顽固的人不是哲学家(Somedostinatepersonsarenotphilosophers)”)请注意结论中一个出乎意料且令人迷惑的词“一些(some)”。

两千多年来,数学家们对亚里士多德的逻辑理论苦苦思索,试图用数学符号和操作符来表现它。19世纪以前,唯一能接近这个目标的人是莱布尼兹(1648—1716),他早年涉足逻辑学领域,后来转向其他学科(比如说,他几乎和牛顿同时独立地发明了微积分)

接下来有所突破的是乔治·布尔。

乔治·布尔1815年生于英格兰,他周围的环境对他的成长很不利。他父亲是鞋匠,而母亲曾是女仆,英国森严的等级制度使布尔学不到什么有别于父辈的东西。但是,靠着他自身强烈的好奇心及父亲的帮助(其父对科学研究、数学和文学有浓厚的兴趣),年轻的乔治自学了上层阶级男孩才能学到的课程,包括拉丁文、希腊语及数学。由于他早年在数学方面发表的论文,1849年,布尔被任命为爱尔兰Cork市的皇后大学数学系的首席教授。

 

19世纪中期的几位数学家在逻辑理论的数学定义上做了一些工作(最著名的是迪摩根),但只有布尔有真正概念上的突破。他最早的贡献是发表的一本很简短的书《TheMathematicalAnalysisofLogic,BeinganEssayTowardsaCalculusofDeductiveReasoning》(1847),接着又发表了一篇很长且充满抱负的文章:《AnInvestigationoftheLawsofThoughtonWhichAreFoundedtheMathematicalTheoriesofLogicandProbabilities》(1854),简称为《TheLawsofThought》。1864年的一天,布尔在雨中赶去上课时不幸感染上了肺炎,不治身亡,享年49岁。

我们可以从布尔在1854年所著书的题目中看出他富于野心的想法:由于充满理性的人脑用逻辑去思考,那么,如果能用数学来表征逻辑,我们也就可以用数学来描述大脑是如何工作的。当然,现在看来这种想法似乎十分幼稚。(但却超越了他所在的年代。)

布尔发明了一种和传统代数看起来、用起来都十分相似的代数。在传统代数中,操作数(通常是字母)代表数字,而操作符(多是“+”或“×”)指明这些操作数如何结合到一起。一般我们可用传统代数解决类似下面的问题:如果安娜有3磅豆腐,贝蒂的豆腐是安娜的2倍,卡门的豆腐比贝蒂多5磅,迪尔德丽的豆腐是卡门的3倍。那么,迪尔德丽有多少豆腐呢?

为了计算这个问题,我们首先把语句转化为算术式子,用四个字母代表每个人拥有豆腐

的数量,即:

可以通过代入把上述四个表达式合为一个式子,最后执行加法和乘法,即:

当做传统代数题时,要遵循一定的规则。这些规则可能已经和实践融为一体,以至于我们不再认为它们是规则,甚至忘记了它们的名字。但规则确实是任何形式的数学的基础。

第一个规则是加法与乘法的交换律,即我们可以在操作符两边交换操作数的位置:A+B=B+A,A×B=B×A

相反,减法和除法是不满足交换律的。

加法和乘法也满足结合律,即:A+(B+C)=(A+B)+C,A×(B×C)=(A×B)×C

最后,乘法对加法可以进行分配:A×(B+C)=(A×B)+(A×C

传统代数的另外一个特点是它总是处理数字,如豆腐的重量或鸭子的数量,火车行驶的距离或家庭成员的年龄。是布尔超凡的智慧使代数脱离了数字的概念而变得更加抽象。在布尔代数中(布尔的代数最终被这样命名)操作数不是指数字,而是指集(类)。一个类仅仅表示一组事物,也就是后来熟知的集合。

让我们来讨论一下猫。猫或公或母,为方便起见,我们用字母M指代公猫的集合,用F指代母猫的集合。记住,这两个符号并不代表猫的数量,公猫或母猫的数量随着小猫仔的出生和老猫的不幸离去而变化,这两个字母代表的是猫的种类—具有某种特点的猫。因而我们不说公猫,而是用M来代表它们。

我们也可以用其他字母代表猫的颜色。例如,用T代表黄褐色的猫,用B代表黑猫,用W代表白猫,而用O代表所有其他颜色的猫。最后(至少就这个例子而言),猫要么是阉过的要么是有生育能力的。我们用字母N代表阉过的猫,而用U代表有生育能力的猫。

在传统代数中,操作符+和×被用于表示加法和乘法。在布尔代数中,同样用到了+和×。这似乎会引起混淆。人人都知道在传统代数中如何对数字进行加和乘,但是我们如何对“类”进行加和乘呢?

事实上,在布尔代数中我们并不真正地做加或乘,相反,这两个符号有着完全不同的意思。

在布尔代数中,符号+意味着两个集合合并,两个集合的合并就是包含第一个集合的所有成员及第二个集合的所有成员。例如,B+W表示黑猫和白猫的集合。布尔代数中的符号×意味着取两个集合的交集,两个集合的交集包含的元素既在第一个集合中,也在第二个集合中。例如,F×T代表了一种猫的集合,这个集合中的猫既是母猫又是黄褐色的。与传统代数一样,我们可以把F×T写成F·T或简写为FT(这正是布尔代数所期望的)。你可以把这两个字母看成是连在一起的两个形容词:黄褐色的母猫。

为避免传统代数和布尔代数之间的混淆,有时候用符号∪和∩而不用+和×来表示并运算和交运算。但布尔对数学的解放性的部分影响是使熟悉的操作符更加抽象,所以,我们决定坚持他的决定,而不为他的代数引入新的符号。

交换律、结合律和分配律在布尔代数中均适用。而且,在布尔代数中,操作符+可以对×进行分配,这在传统代数中是不成立的,即:W+(B×F)=(W+B)×(W+F

这个式子表示白猫(W)和黑色母猫(B×F)的并集和等式右边两个集合的交集是一样的,这两个集合是白猫和黑猫的并集(W+B)及白猫和母猫的并集(W+F)。要掌握这个规则有些困难,但它的确有用。

为了使布尔代数更加完整,我们还需要两个符号。这两个符号看上去像数字,但它们并不真的是数字,因为有时候它们和数字有些不同。符号“1”在布尔代数中表示“整个宇宙(全集)”,也就是我们所谈论的每件事物。本例中,符号“1”表示“所有的猫”。这样:M+F=1

即母猫和公猫的并集是所有的猫。同样,黄褐色猫、黑猫、白猫及其他颜色的猫的并集也是所有的猫,即:T+B+W+O=1

你也可以这样表示所有的猫:N+U=1

符号1可以用一个减号-来排除一些事物。例如:1-M

表示除了公猫以外的所有猫。排除公猫以后的全集就是母猫的集合:1-M=F

我们所需要的另外一个符号是“0”。在布尔代数中,“0”表示空集,即不含任何事物的集合。当求取两个完全相互排斥的集合的交集时,空集就产生了。例如,既是母的又是公的猫的集合可以表示为:F×M=0

注意,符号1和0有时的用法与传统代数相同。例如,所有的猫和母猫求交集即是母猫这个集合:1×F=F

空集和母猫求交集还是空集:0×F=0

空集和母猫的并是母猫这个集合:0+F=F

但有时与传统代数中得到的结果就不太一样了。例如,所有的猫和母猫的并集是所有的猫:1+F=1

这个表达式在传统代数中是没有意义的。

由于F代表母猫的集合,1-F代表所有其他猫的集合,则这两个集合的并集是1:F+(1-F)=1并且它们的交集是0:F×(1-F)=0

历史上,这个公式代表了逻辑中一个十分重要的概念,即矛盾律。它表明一个事物不能同时是它自己和它自己的反面。

使布尔代数和传统代数看起来完全不同的是下面这个表达式:F×F=F

这个式子在布尔代数中有着完美的意义:母猫的集合和母猫的集合的交集仍旧是母猫的集合。但若F代表一个数字的话,这个公式显然就不对了。布尔认为:X2=X

是使他的代数与传统代数区分开来的唯一表达式。另一个按照传统代数看起来很有趣的布尔表达式是:F+F=F

母猫和母猫的并集仍是母猫这个集合。

布尔代数为解决亚里士多德的三段论提供了一个数学方法。再看看这个著名三段论的两个前提:所有的人都是要死的;苏格拉底是人。

我们用字母P代表所有人的集合,M代表要死的东西的集合,S代表苏格拉底。那么,所谓“所有的人都是要死的”意味着什么呢?它其实表示了所有人的集合和所有要死的东西的集合的交集是所有的人这个集合,即:P×M=P

而P×M=M这个式子是错误的,因为要死的东西还包括猫、狗、榆树等等。

而“苏格拉底是人”意味着苏格拉底这个集合(非常小)和所有人的集合(很大)的交集是苏格拉底这个集合:S×P=S

由于从第一个式子中知道P=P×M,所以可以把它代入第二个式子,即:S×(P×M)=S

根据结合律,上式等同于:(S×P)×M=S

但我们已经知道S×P等于S,所以上式可简化为:S×M=S

现在计算完毕。这个表达式告诉我们,苏格拉底和所有要死东西的集合的交集是苏格拉底,也就是说苏格拉底是要死的。相反,如果认为S×M等于0,那么结论就是苏格拉底不会死。再如果,若S×M等于M,则能推出的结论就是苏格拉底是唯一会死去的东西,而其他任何东西都是不朽的!

用布尔代数来证明显而易见的事实似乎有些小题大做(尤其当考虑到苏格拉底早已在2400年以前就去世了时),不过,布尔代数还可以用来判断一些事物是否满足一定的标准。也许有一天,你走进宠物店对店员说:“我想要一只阄过的公猫,白的或黄褐色的均可;或者要一只没有生殖能力的母猫,除了白色,其他任何颜色均可;或者只要是只黑猫,我也要。”店员对你说:“看来您想要的猫是下面的式子表示的集合中的一只:(M×N×(W+T))+(F×N×(1-W))+B

对吗?”你回答道:“是的,完全正确!”

为了证明店员是正确的,你可能想放弃并和交的概念而转向“OR(或者/或)”和“AND(并且/与)”。大写这两个词是因为虽然在通常情况下它们代表语言中的概念,但它们也代表了布尔代数中的操作。当求两个集合的并集时,你实际上是从第一个集合“或”从第二个集合中取得事物放入结果集合里。当求两个集合的交集时,满足条件的事物必定在第一个集合中“并且”也在第二个集合中。此外,每当你看见后跟减号的1,你可以使用单词“NOT(非)”来表示。小结如下:

1、+(以前表示求并集)现在表示OR。

2、×(以前表示求交集)现在表示AND。

3、1-(以前表示从全集中排除一些事物)现在表示NOT。

这样,刚才的表达式可以写成下面的形式:(MANDNAND(WORT))OR(FANDNAND(NOTW))ORB

这与你的口头描述已经十分接近了。注意圆括号是如何清楚地表达出你的意图的。你想要的猫来自下面三个集合之一:(MANDNANDWORT))或(FANDNANDNOTW))或B

写下这个公式后,店员就可以进行布尔测试的工作了。别这么大惊小怪的,这里已经悄悄转移到另一种不同形式的布尔代数中去了。在这种形式的布尔代数中,字母不再只表示集合,字母还可以被赋予数字,但需要注意的是它们只能被赋予0或者1。数字1表示“是的”、“正确”,本例中的意思是“这只猫符合我的要求”;数字0表示“否定”、“错误”、本例中即“这只猫不符合我的要求”。

首先,店员拿出一只未阄过的黄褐色的公猫。下面是满足条件的猫的集合:(M×N×(W+T))+F×N×(1W))+B当用01代替字母后就变成了下面的样子:(1×0×(0+1))+0×0×(1-0))+0

注意被赋予了1的字母只有MT,因为拿来的这只猫是公的,黄褐色的。

现在必须要做的是简化这个表达式。如果简化后表达式的结果是1,这只猫就满足了你的要求,否则就不是你想要的猫。当简化表达式时,千万记住我们并不是在真正地做加法和乘法。当+表示OR,×表示AND时,大部分规则是相同的。(现代课本中有时用∧和∨分别表示AND和OR,而不用×和+;但这里用+和×这两个符号却是恰到好处的。)

当用×表示AND时,可能的结果是:

换句话说,只有当×的左、右两个操作数均为1时,结果才为1。这个过程和普通乘法一模一样。若用一张小表总结一下,你会发现它们和第8章的加法表和乘法表的形式相似:

当用+表示OR时,可能的结果是:

当+的左、右操作数中有一个为1时,结果就是1。除了1+1=1这种情况,这种计算和普通加法产生的结果是一致的。可用另一张小表来总结:

现在可以用这些表来计算前面那个表达式的结果了:(1×0×1)+(0×0×1)+0=0+0+0=0

结果是0,表示“否定”、“错误”,即这只小猫不满足客户需求。接下来,店员拿来一只无生育能力的白色的小母猫。原始表达式是:(M×N×(W+T))+(F×N×(1-W))+B

把0和1代入上式:(0×1×(1+0))+(1×1×(1-1))+0

并且把它简化一下:(0×1×1)+(1×1×0)+0=0+0+0=0

看来,这只可怜的小猫还是不符合要求。

然后,店员又拿来一只无生育能力的灰色的小母猫。(灰色是非白色、黑色或黄褐色的一种其他颜色。)下面是表达式:(0×1×(0+0))+(1×1×(1-0))+0

现在把它简化为:

(0×1×0)+(1×1×1)+0=0+1+0=1

最后的结果1表示“是的”、“正确”,这只小猫总算找到新家了!在你买到小猫的那天晚上,当小猫蜷身睡在你的腿上时,你开始考虑是否能够通过电线连接一些开关和灯泡来决定哪些小猫满足你的要求。(你真是一个奇怪的家伙。)你丝毫没有意识到你将要实现一个关键概念上的突破。你要做的是一些试验,这些试验把布尔代数和电路结合在一起,从而使使用二进制数字工作的计算机的设计和制造成为可能。(可别让这些话吓着你。)

下面就开始了。你像往常一样把灯泡和电池连接在一起,这一回你用了两个开关:

开关这种方式的连接—一个在另一个的右边—称为串联的。如果你闭合了左边的开关,什么也不会发生:

同样,如果你让左边的开关断开而闭合右边的开关,结果还是一样。只有当左右两个开关都闭合时,灯泡才会发光,如下所示:

这里的关键是“都”。只有左边和右边的开关都闭合时,电流才能流过回路。

这个电路执行了一个逻辑运算。事实上,灯泡回答了这个问题:“两个开关都处于闭合状态吗?”可以把电路的工作总结为下面这张表:

在前一章中,我们已知道二进制数字(或“位”)是如何表示信息的:它可以表示从最普通的数字到RogerEbert的拇指方向等的一切事情。可以说“0”代表“Ebert拇指向下的方向”,而“1”表示“Ebert拇指向上的方向”。一个开关有两个位置,所以它可以代表一个位。“0”表示“开关是断开的”,而“1”表示“开关是闭合的”。一个灯泡有两种状态,所以它也可以表示一个二进制位。“0”表示“灯泡不亮”而“1”表示“灯泡亮”。现在可以把上面的表简化一下:

注意,如果交换左、右开关,结果是一样的,所以没必要指明哪个开关是左开关或右开关。因此这张表可以重画成类似于前面“AND”表和“OR”表的样子:

事实上,这和“AND”表是一样的。让我们检查一下:

这个简单的电路实际上执行了布尔代数的“AND”操作。

现在试着用另一种方式连接电路:

这些开关称为并行连接。它和前一种连接方式的区别是,如果闭合了上面的开关,灯泡会亮:

如果闭合了下面的开关,灯泡会亮:

 

如果同时闭合上、下两个开关,灯泡还是会亮:

可见,当上面或下面的开关有一个闭合时,灯泡就会亮。这里的关键字是“或”。

这个电路也执行了一个逻辑运算,灯泡回答了这样一个问题:“是否有开关闭合?”下面的表总结了这个电路是如何工作的:

仍然用“0”表示开关断开或灯泡不亮,用“1”表示开关闭合或灯泡亮。这张表可以这样:

同样,这两个开关交换位置也没关系,所以这张表可以重写成如下的样子:

你可能已经猜到了这和布尔代数中的“OR”表是一样的:

这意味着两个并联的开关执行的是和布尔一样的操作。

当你再进入宠物店时,你告诉店员:“我想要一只阄过的公猫,白的或黄褐色的均可;或者要一只没生育能力的母猫,除了白色,其他任何颜色均可;或者只要是只黑猫,我也要。”店员便得到了如下的表达式:(M×N×(W+T))+(F×N×(1-W))+B

现在你知道两个串联开关执行的是逻辑与(AND,由符号×来表示),两个并联开关执行的是逻辑或(OR,由符号+来表示),你可以按如下方法连接8个开关:

这个电路中的每一个开关都被标上了一个字母(与布尔表达式中所用字母相同)。表示非W,是1-W的另一种写法。事实上,如果按从左至右,从上至下的顺序来阅读这个电路图,你遇到的字母的顺序和它们在布尔表达式中出现的次序是一样的。表达式中的乘号(×)都对应角是电路图中串联的两个或两组开关的位置;表达式中的加号(+)号对应的是电路图中并联的两个或两组开关的位置。

你应该记得,店员最先挑出的是只未阄过的褐色的公猫。闭合相应的开关:

尽管M、T和非W这三个开关都闭合了,但没有构造出一个完整的电路来点亮灯泡。接着,店员拿出一只无生育能力的白色的母猫:

这次,由于右边开关未闭合也无法构成一个完整的电路。但最后,店员拿出一只无生育W能力的灰色的母猫:

这样就可以构出一个完整的电路,灯泡被点亮并表示小猫符合你的要求。

乔治·布尔从来没有连接过这样一个电路,他也没能看到用开关、电线和灯泡来实现一个布尔表达式。当然,其中的一个原因是直到布尔死后15年,白炽灯泡才被发明。但摩尔斯在1844年展示了他的电报机,比布尔的《TheLawsofThought》的发表早10年,而用一个电报发声器来代替所示电路中的灯泡是十分简单的。

可惜19世纪没有人把布尔代数中的与、或和串联、并联一些简单的开关联系起来。数学家没有、电工没有、电报操作员也没有,没有人想到过这种联系,甚至连计算机革命的创始人查尔斯·巴贝芝(1792—1871)也没有。他曾和布尔联系过,并了解过他的工作,他一生中大部分时间致力于设计第一台差分机及接下来的解析机。一个世纪之后,这些机器被认为是现代计算机的雏型。我们现在知道,帮助巴贝芝的是他认识到计算机应产生于电报继电器中,而非那些齿轮和控制杆。是的,问题的答案正是电报继电器。

第十一章  逻辑门电路

在遥远的将来,当人们回顾20世纪的计算机发展史时,有人可能会以为一种称为“logicgates(逻辑门)”的设备是以著名的微软公司创始人的名字命名的(BillGates中的Gates在英语中有“门”的意思),其实并非如此。我们很快就会明白,逻辑门和通常让水和人通过的门十分相似。逻辑门通过阻挡或允许电流通过在逻辑中执行简单的任务。

回忆一下在上一章中你走进一个宠物店所要的那只猫,这可以由下面的布尔表达式说明:

(M×N×(W+T))+(F×N×(1-W))+B

同时,也可以用下面的电路来选择符合条件的小猫:

这样一个电路有时被称为网络。但在今天,网络这个词更多地被用来指连接起来的计算机,而不仅仅只是开关的集合。

尽管这个电路包含的全是19世纪发明的东西,但那时却没有人意识到布尔代数可以直接由电路实现。这种等同性直到20世纪30年代才被发现,主要贡献人是克劳德·香农(生于1916年)。香农在他著名的、于1938年在麻省理工学院所写的硕士论文《ASymbolicAnalysisofRelayandSwitchingCircuits》中阐述了这个问题。(10年之后,香农的文章TheMathematicalTheoryofCommunication》是使用“位(bit)”这个字来表示二进制数字的第1篇出版物。)

1938年以前,人们已经知道当把两个开关串联起来时,只有两个开关都闭合电流才能流通;而当把两个开关并联起来时,只需闭合其中的一个即可构成回路。但没有人能像香农那样清晰地阐述电子工程师可以使用布尔代数的所有工具来设计带开关的电路。此外,如果你简化了描述网络的布尔表达式,你也可以相应地简化网络。

例如,描述你想要的小猫的表达式是:(M×N×(W+T))+(F×N×(1-W))+B

用结合律把用×结合的变量重新排序并按下面的方式重写表达式:(N×M×(W+T))+(N×F×(1-W))+B

为更清楚地表达意图,可以定义名为X和Y的两个新变量:X=M×(W+T),Y=F×(1-W)

现在,描述你想要的小猫的表达式可以写成下面的样子:(N×X)+(N×Y)+B

完成简化后,我们再把X、Y代回原来的式子。

注意,变量N在表达式中出现了两次。使用分配律,表达式可以按如下方式重写,并只使用一个N:(N×(X+Y))+B

现在把X、Y表达式代入:(N×((M×(W+T))+(F×(1-W))))+B

由于有很多圆括号,该表达式看上去似乎仍很复杂。但表达式中少了一个变量项(减少了一次×运算),也就意味着网络中少了一个开关。这是修改后的电路图:

确实,证明修改前后的两个电路图功能是一样的比去证明两个表达式功能是相同的要简单。

可是,网络中仍然多余了三个开关。理论上讲,你只需要四个开关来定义你心目中的猫咪。为什么是四个呢?因为每个开关都是一个“位”。你需要一个开关来定义性别(断开表示公的,而闭合表示母的);一个开关来定义是否有生育能力(闭合表示阄过的,断开表示未阄过的)还需要两个开关表示颜色。因为只有四种可能的颜色(白、黑、褐和其他所有颜色),而我们知道四种选择可以用两个二进制位来定义,所以只需要两个开关来表示颜色。例如,两个开关都断开表示白色,一个闭合表示黑色,另一个闭合表示褐色,两个开关都闭合就表示其他所有颜色。

现在,让我们做一个控制面板来选择一只猫。控制面板上有四个开关(正如你家里的电灯开关)和一个灯泡:

开关打到上面是指开关闭合,反之是指开关断开。也许表示猫的颜色的两个开关标识得不是很清楚,这是为了把控制面板做得更简练不得已而造成的。在表示颜色的一对开关中,左边的开关标着B,如果只有它往上就表示黑色;右边的开关标着T,如果只有它往上就表示黄褐色;B、T两个开关均往上则表示其他颜色,由O标识;B、T两个开关均往下则表示白色,由W标识。

在计算机专业术语中,开关是一种输入设备,输入是控制电路如何工作的信息。本例中输入开关对应于描述一只猫咪的4位信息,输出设备是灯泡。如果开关描述了一只符合条件的猫,灯泡就会亮。上面介绍的控制面板上的开关被设置成表示一只无生育能力的黑母猫,这是符合你的要求的,所以灯泡亮了。

现在所要做的是设计一个使控制面板工作的电路。

前面提到过香农的论文题目是《ASymbolicAnalysisofRelayandSwitchingCircuits》,他所指的relay和第6章中所讲的电报系统的继电器很类似。在香农的论文发表时,继电器已被用作其他目的,尤其是用于电话系统的大型网络。

像开关一样,继电器也可以串联或并联以执行逻辑中的简单任务。继电器的组合称为逻辑门。这里所说的“逻辑门执行简单逻辑任务”是指逻辑门只完成最基本的功能。继电器比开关好是因为继电器可以被其他继电器控制而不必用手指控制,这意味着逻辑门可以被组合起来以执行更复杂的任务,比如一些简单的算术操作。事实上,下一章就要展示如何用电线连接开关、灯泡、电池和继电器来构造一个加法机(尽管它只能工作于二进制数字状态)。

继电器对电报系统的工作十分重要。连接电报站的电线长距离时电阻很大,需要一种方法来接收微弱的信号并把它增强后发送出去。继电器通过使用电磁铁控制开关可做到这一点。事实上,继电器放大了一个很弱的信号使其成为一个强信号。

就我们的目的而言,我们并不对它的信号放大能力感兴趣,真正使我们着迷的是继电器作为开关可用电来控制而不用手指。可以用电线把继电器、开关、灯泡和一对电池做如下连接:

注意左边的开关是断开的,灯泡不亮。当闭合开关时,电流流过围绕在铁棒上的线圈,于是铁棒具有了磁性,并把上面有弹性的金属簧片拉下来,从而连通了电路,使灯泡发光:

当电磁铁把上面的金属簧片拉下来时,这个继电器被称为“触发了”。当左边的开关断开时,铁棒不再有磁性,继电器中的金属簧片则弹回到原来的位置。

这看上去似乎是用一种很不直接的方式点亮灯泡的,但实际上这种方式是很直接的。如果我们只对点亮灯泡感兴趣,我们完全可以舍弃继电器。但我们的兴趣并非只是点亮灯泡这么简单,我们有更宏伟的目标。

本章要多次用到继电器(当逻辑门建好之后就会很少再用了),所以要把上面那幅图简化一下。可以通过大地省去一些导线。在这种情况下,大地仅代表了一个公共端,并不是指真正的物理接地:

这看上去仍然不够简化,但还不至于那样做。注意两个电池的负极均接地,所以当看到的电池是这样的时:

可用大写字母“V(它代表电压)”代替上图中的电池(如在第5和第6章中所做的)。现在继电器看上去如下图所示:

当右边开关闭合时,电流从V端流出,经过电磁铁芯流到地上。这使得电磁铁把上面有弹性的金属簧片拉下来,从而连通了接有灯泡的电路,灯泡点亮:

上面图显示了两个电源和两个接地,但本章的所有图中,电源,即“V”,可以互连,接地端也可以互连。本章及下一章的所有继电器和逻辑门的网络只要求有一个电池,但可能是一个大容量的电池。例如,上一幅图可只用一个电池,如下所示:

但这幅图并不能清楚地表明要用继电器做什么。先不考虑电路而把注意力放到输入和输出上,就像前面的控制面板一样:

如果电流流经输入(例如,用一个开关把输入连到“V”端),电磁铁就会被触发,输出就有了一个电压。

继电器的输入不一定只能是开关,其输出也未必只限于灯泡。一个继电器的输出可以连到另一个继电器的输入,如下所示:

当闭合开关时,第一个继电器被触发,它为第二个继电器提供了输入电压,于是第二个继电器也被触发,灯泡被点亮了:

把继电器连接起来是构造逻辑门的关键。

事实上,灯泡可以两种方式连到继电器上。注意,具有弹性的金属簧片是被电磁铁拉下来的。平时,金属簧片与上端接触,当电磁铁吸引它的时候,它便和下端接触。我们一直把金属簧片与下端的接触作为继电器的输出,但我们也可以把它与上端的接触作为输出。当使用这种输出时,结果正好相反,输入开关断开时灯泡是亮的:

而当输入开关闭合时,灯泡便灭了:

使用这种开关的继电器称为双掷继电器,它的两个输出在电性上是相反的—当一个有电压时,另一个则没有。

顺便说一下,如果你不知道现在的继电器是什么样子,你可以很方便地从当地的电器行的透明小包里看到一些。有些继电器就像(加入饮料的)方形小冰块一样大小,如元件号为275-206和275-214的继电器就是这种大小的且经久耐用的继电器。它们被封在一个干净的塑料外壳里,所以你可以看到电磁铁和弹性金属簧片。本章和下一章所描述的电路都使用的是元件号为275-240的继电器,它体积小且价格便宜(每个$2.99)。

正如两个开关可被串联一样,两个继电器也可以串联:

上面继电器的输出为下面的继电器提供了输入电压。如上所示,当两个开关均断开时,灯泡不会发光。试着闭合上面的开关:

由于下面的开关仍旧断开,下面的继电器没有触发,所以灯泡仍然不亮。若断开上面的开关而闭合下面的开关:

灯泡仍旧不亮。因为上面的继电器未被触发,电流无法流经灯泡。点亮灯泡的唯一方法是闭合两个开关:

现在,两个继电器都被触发了,电流可以在电源、灯泡和接地点之间流通。

同串联开关一样,这两个继电器也执行了逻辑操作。只有当两个继电器都被触发时,灯泡才会点亮。串联的两个继电器就是一个“ANDgate(与门)”。为避免复杂的图示,电气工程师使用一个特殊的符号表示“与门”,如下图示:

这是四个基本逻辑门中的第一个。与门有两个输入端(在图的左部),一个输出端(在图的右部)。这样表示的与门通常输入在左部,输出在右部。这是因为人们习惯于从左到右读图。但是与门同样可以画成输入在上部、右部或底下。

有两个继电器、两个开关和一个灯泡的原始电路图如下所示:

使用“与门”符号,上图可同样表示成:

注意与门不仅代替了串联的两个继电器,同时也隐含了上面的继电器连着电源,且两个继电器都是接地的。只有当上下两个开关都闭合时,灯泡才会发光,这就是它之所以叫与门的原因。

与门的输入未必一定要和开关连接,且输出也不一定只能是灯泡。我们真正要处理的是输入端的电压和输出端的电压。例如,一个与门的输出可以是另一个与门的输入:

只有当三个开关都闭合时,灯泡才会发光。当上面的两个开关闭合时,第一个与门的输出会触发第二个与门的第一个继电器,而最下面的开关会触发第二个与门的第二个继电器。

如果把不加电压视为0,加上电压视为1,则与门的输出按如下方式由输入来决定:

正如两个串联的开关一样,与门的输入输出关系可作如下描述:

与门也可以有多于两个的输入端。例如,假设串联了三个继电器:

只有当三个开关同时闭合时,灯泡才会发光。这种配置可用如下符号表示:

它被称为三输入端与门。

以下逻辑门可用并联的继电器解释:

注意两个继电器的输出是连接在一起的,这个连接在一起的输出为灯泡提供了电源。任何一个继电器都可以点亮灯泡,例如,如果闭合上面的开关,灯泡会亮。这时,灯泡从左上角的继电器得到了电力供应。

如果让上面的开关断开而闭合下面的开关,灯泡也会亮:

当两个开关都闭合时,灯泡同样会亮:

可见,当上开关或下开关中的任何一个闭合时,灯泡都会亮。这里的关键是“或”,所以这样的门叫“ORgate(或门)”。电气工程师使用如下符号表示或门:

它看上去和与门很相似,只是接输入端的一边是弧形的,很像英语“OR”中的字母“O”。

或门的两个输入中,只要有一个加上电压,输出就是高电位。同样,如果约定不加电压是0,而加电压是1,则或门也有四种可能的组合状态:

可以把或门的输入输出关系小结成如下表格:

或门也可以有两个以上的输入端(当任一输入端为1时,输出端就为1;只有所有输入端均为0时,输出端才为0)。

前面解释过继电器可称为双掷继电器,因为其输出可以两种不同的方式连接。通常情况下,当开关断开时,灯泡不亮:

当开关闭合时,灯泡点亮。

也可以用另外一种连接方式,使开关断开时灯泡点亮:

在这种情况下,只有闭合开关时灯泡才熄灭。以这种方式连接的继电器叫作反向器。反向器不是逻辑门(逻辑门通常有两个以上的输入),但它十分有用。反向器可以用下面的符号表示:

它被称为反向器的原因是当输入为0时输出却为1,反之亦然:

有了反向器、与门和或门,我们就可以制作控制板来自动选择理想的小猫了。让我们从开关开始。第一个开关的闭合表示母猫,断开表示公猫。这样,可以产生称为F和M的两个信号,如下图所示:

当F是1,M就是0,反之亦然。同样,第二个开关的闭合表示阄过的猫,而断开表示有生育能力的猫:

接下来的两个开关更复杂一些,不同的组合要代表四种不同的颜色。这里有两个开关,都与电源相连:

当两个开关都断开时,它们表示白色。我们用两个反向器和一个与门来产生信号W。如果选择了一只白猫,W就为1,否则为0:

当开关断开时,两个反向器的输入是0,这样反向器的输出(也就是与门的输入)为1,这也就意味着与门的输出为1。一旦一个开关闭合,与门输出即为0。

为表示一只黑猫,闭合第一个开关,这可以用一个反向器和一个与门实现:

 

只有当第一个开关闭合而第二个开关断开时,与门的输出才是1。同样,当第二个开关闭合而第一个开关断开时,与门的输出也为1。我们用来表示褐色:

而如果两个开关都闭合时,用如下图示表示其他颜色:

现在把四个小电路集成为一个大电路(通常,黑点表示电线的连接点,没有黑点的交叉线是不连接的):

这个连接图看起来十分复杂。但如果仔细地沿着线路走,看清楚每个与门的输入而不要关心这些输入又连到了别的什么地方,你就会明白电路是如何工作的。如果两个开关都断开,信号W会是1,其余信号都是0。如果第一个开关闭合,则信号B会是1,其余信号都是0。

连接门和反向器时可以遵循一些简单的规则:一个门(或反向器)的输出可以作为其他门(或反向器)的输入,但是两个以上的门(或反向器)的输出永远不能互连在一起。

由4个与门和2个反向器组成的电路叫作“2-4译码器”。输入是两个二进制位的不同组合,共代表了4个不同的值。输出是4个信号,任何时刻只能有一个是1,至于哪一个是1取决于两个输入位。用同样的原理还可以构造“3-8译码器”或“4-16译码器”等等。

选择小猫的表达式的简化表示是:(N×((M×(W+T))+(F×(1-W))))+B

对于表达式中的每一个加号(+),必定对应电路中的一个或门。对于每一个乘号(×),则对应一个与门:

电路图左边的符号和它们在表达式中出现的顺序是一样的。这些信号来自于和反向器连接的开关及2-4译码器的输出。注意,图中用了反向器来表示表达式中的(1-W)。

你可能会说:“这不过是一堆继电器而已。”不错,这正是一堆继电器,每个与门和或门中都有两个继电器,一个反向器中有一个继电器,因而只能说你必须习惯它。以后的各章会用更多的继电器。不过,所幸的是你不用真正地去买一堆回家连起来。

本章再看两个逻辑门。这两个门都会用到这样一个继电器,该继电器在不被触发时,其输出为高电位(这是用在反向器中的输出)。例如,下面配置中,一个继电器的输出为第二个继电器提供了电源。当两个输入都断开时,灯泡是点亮的:

如果上面的开关闭合了,灯泡就会熄灭:

灯泡的熄灭是因为第二个继电器没有电源供应。同样,若下面的开关闭合灯泡也会熄灭:

若两个开关都闭合,灯泡还是不亮:

这种行为和或门的行为正好相反,被称为“NORgate(或非门)”。下面是或非门的符号:

它和或门的符号很相像,只是在输出端有一个空心的小圆圈,这个小圆圈表示反向,故而或非门也可用下面的表示:

或非门的输出如下表所示:

这张表显示的结果和或门相反。在或门中,输入端中只要有一个是1,输出就是1;只有输入端均为0时,输出才为0。

连接两个继电器的另一种方式如下图所示:

在这种情况下,两个输出连在一起。除了连在继电器的另一个触点上之外,这种连接形式与或门类似。当两个开关都断开时灯泡是亮的。

当只有上面的开关闭合时,灯泡也是亮的:

当只有下面的开关闭合时,灯泡也是亮的:

只有当两个开关都闭合时,灯泡才会熄灭:

这种行为和与门的行为正好相反,被称为“NANDgate(与非门)”。与非门的画法和与门的画法很相像,只是在输出端加了一个小圆圈,表示其最后的输出和与门的输出是相反的:

与非门的输出如下表所示:

注意,与非门的输出与与门恰恰相反。对与门而言,当两个输入都为1时,输出才为1;否则输出就是0。

到此为止,我们已经看到可用四种不同的方式来连接有两个输入、一个输出的继电器,每一种方式的行为功能都不一样。为避免画继电器,我们把这些连接称为逻辑门并使用电气工程师们使用的符号来表示它们。特定的逻辑门的输出取决于其输入,总结如下:

现在已有了四个逻辑门和一个反向器,完成这些工具的其实就是原始的继电器:

上图称为缓冲器,用符号表示如下:

它和反向器的符号类似,只是没有小圆圈。缓冲器的特点是“什么都不做”,其输出和输入是相同的:

当输入信号很弱时,可以使用缓冲器,这是因为这也正是多年前继电器被用于电报当中的原因。此外,缓冲器也可用于延迟一个信号,这是因为继电器可能要求多一点儿动作时间,如1秒的几分之一才被触发。

本书从现在开始不再画继电器,取而代之的是电路将由缓冲器、反向器、4个基本逻辑门及更复杂的电路(如2-4译码器)组成。当然,所有这些部件也是由继电器构成的,但我们用不着看到它了。

前面讲过,可用下面的小电路构造一个2-4译码器:

两个输入被反向后成为与门的输入。有时,像这样的配置可以去掉反向器而画成如下的样子:

注意与门输入端的小圆圈,这些小圆圈表示信号在这些点上被反向了,0会变成1,而1变为0。具有反向输入端的与门和或非门的行为是一样的:

只有两个输入端都为0时输出才为1。

同样,具有反向输入端的或门和与非门的行为是一样的:

只有输入端均为1时输出才为0。

这两对等同的电路实际上就是迪摩根定律的内容。迪摩根是维多利亚时代的另一位数学家,他比布尔年长9岁。据说,他的书《Formallogic》发表于1847年,和布尔的《TheMathematicalAnalysisoflogic》恰好是同一天。事实上,布尔正是由于受到发生在迪摩根和另一个英国数学家之间的剽窃事件的触动而研究逻辑的。(迪摩根最后证明是清白的。)很早以前,迪摩根就意识到了布尔思想的重要性。他无私地鼓励和帮助布尔进行研究,但最终除了他的这个著名的定律外,他几乎被人们遗忘了。

迪摩根定律可以简单地表示成:

A和B是两个布尔操作数。在第一个表达式中,它们被取反(即反向)后再相与。这和先把它们相或后再取反(或非门的功能)的结果是一致的。第二个表达式中,两个操作数被取反后再相或,这和先把它们相与后再取反(与非门的功能)的结果是一样的。

迪摩根定律对于简化布尔表达式,进而简化电路是一个很重要的工具。从历史上讲,这正是香农的论文对电气工程师的真正含义。但是,专门简化电路并非本书的焦点,更重要的是让事物工作、起作用。下面我们要运行起来的就是一台简单的加法机。

 

 

第十二章  二进制加法机

加法是最基本的算术运算。所以,如果想要建造一台计算机(这是本书隐含讨论的问题),必须首先知道如何构造一种机器,它可以把两个数加起来。当你解决了这个问题,你会发现加法正是计算机唯一所做的事情,因为通过使用用于加法的机器,我们还可以构造用加法来实现减法、乘法、除法以及计算房产抵押款、引导向火星发射卫星、下棋和电话计费等等功能的机器。

同现代的计算器和计算机比起来,本章构造的加法机庞大、笨重、速度慢且噪声大。但有意思的是构成它的部件完全是前几章学过的电子设备,如开关、灯泡、电线、电池以及可构成几种逻辑门的继电器。这个加法机包含的所有部件都于120年以前就已发明,而且,我们并不用真正地在屋子里建造它,只需在纸上和脑子里构造这台机器就行了。

这个加法机只能工作于二进制数,而且它缺少很多现代计算机(器)的辅助设备。它不能用键盘来敲入你想加的数,代之的你只能用一系列开关表示待加的数。它也不能用显示器显示结果,你所看到的只是一排灯泡。

但这台加法机确实实现了两数相加的功能,而且其工作方式和计算机做加法十分相似。

二进制加法与十进制加法很像。当你相加十进制数如245和673时,你把问题分解成简单的步骤,每一步只对一对十进制数字相加。本例中,第1步是把5和3加起来。生活中,你若能记住加法表,问题的解决就快多了。

十进制加法和二进制加法的一大区别是二进制数字的加法表要比十进制数字的加法表简单得多:

你可能在学校里记过上面这张表,并背诵过如下口诀:

把相加结果的数前加上零,可以把加法表改写成如下形式:

这样一来,二进制数字相加的结果是两位数,分别称为“和”和“进位”(比如“1加1等于0,进位是1”)。现在,可以把这张二进制加法表分成两张表,第1张是表示“和”的表:

第2张是表示“进位”的表:

以这种方式来看待二进制加法就很方便了,因为加法机会分开求和与进位。构造二进制加法机需要设计一个能执行表中所描述操作的电路。因为电路的所有部件,如开关、灯泡、电线都是可以表示成二进制数的,因而该电路由于仅工作于二进制数从而大大降低了电路的复杂性。

与十进制加法一样,二进制加法也从最右边的一列开始,逐列相加两个数:

注意,当从右边加到第3列的时候,产生了一个进位。同样的情况也发生在第6、7、8列。

我们要加多大的数呢?由于这个加法机只是在脑子里构造,因而可以加很长的数字。为更合理一些,选择不超过8位的二进制数。也就是说,操作数的范围是从0000-0000~1111-1111,即十进制的0~255。两个8位二进制数的和最大可以是1-1111-1110,即510

此二进制加法机的控制面板如下图所示:

板上有两行开关,每行8个。这些开关集是输入设备,我们将用它输入两个8位数。开关往下表示0,往上表示1,正如家里墙上的开关。输出设备在板的底部,是一行灯泡,共9个。这些灯泡用来表示加法的结果,不亮的灯泡表示0,亮的表示1。我们用了9个灯泡是因为两个8位数相加的结果可能是9位数。

加法机的余下部分包含了以不同方式连接而成的逻辑门。开关触发逻辑门中的继电器,继电器接着点亮相应的灯泡。例如,如果我们想把0110-0101和1011-0110加起来(即前例中显示的两个数字),需把相应的开关设置成下面的样子:

灯泡的亮暗表明答案是1-0001-1011。(当然,这只是希望的情况。毕竟,我们还没有把这个加法机构造出来!)

上一章提到过本书将会用到很多继电器,本章中的8位加法机就至少需要144个继电器,其中每一对数进行加法操作需要18个继电器(8×18=144)。如果画出完整的电路图,你一定会大惊失色,任何人都无法将连成一堆的144个继电器看得明明白白,所以我们将用逻辑门分步解决这个问题。

当你看到下面两个1位二进制数相加的进位表时,你可能立刻会想到逻辑门和二进制加法之间有某种联系:

你也许已意识到这和上章所述的与门的输出是一样的:

所以,与门可以用来计算两个1位进制数位相加得到的进位。

看来我们已取得一点儿进展了,下一步就要看看有没有继电器能完成下面的工作:

这是二进制加法运算中的另一半问题,虽说表示和的这一位不如进位那么容易实现,但我们会有办法。

首先应意识到或门的输出和我们所期望的很近似,只是右下角的结果不同:

而对于与非门而言,除了左上角的输出不同以外,其他结果也与期望的一样:

所以,使用相同的输入,让我们把与非门和或门连接起来:

下表总结了或门和与非门的输出,并将其结果和加法机所要求的结果进行比较:

注意,当或门和与非门的输出都为1时,就可以得到期望的结果1,这暗示着把两个输出作为与门的输入:

好,这样就能满足要求了。

整个电路仍然只有两个输入,一个输出。两个输入既连到了或门,也连到了与非门。或门和与非门的输出作为与门的输入,从而得到预期的结果:

这个电路有它自己的名字,称为“异或门(ExclusiveORgateXOR)”。异或门输出为1时,A输入为1或B输入为1,但不能同时为1。不用再去画一个或门、一个与非门和一个与门,可以用电气工程师规定的符号来表示它:

它看上去和或门很像,只是在输入端还有一条曲线。异或门的行为表示如下:

异或门是本书需要详细描述的最后一个逻辑门(在电气工程中有时还会遇到第六个门,称为“同或门”,同或门只有两个输入相等时输出才为1。同或门描述的输出情况正好和异或门相反,所以这个门的符号和异或门相同,同时在输出端有一个小圆圈)。

让我们来总结一下。两个二进制数相加产生两个表,一个是表示“和”的表,另一个是表示“进位”的表:

 

用下面两个逻辑门可以得到同样的结果:

二进制数的“和”可以由异或门得到,而“进位”可以由与门得到,所以可以把异或门和与门结合起来来完成两个二进制数A和B的加法:

不用画与门和异或门,可以把上图简单地表示成如下的样子:

其中的方块称为“半加器(HalfAdder)”,它可以把两个二进制位A和B相加,从而得到一个和输出(简称S)和一个进位输出(简称CO)。但大部分二进制数是多于1位的,半加器不能够把前一步的进位加到本次运算中。例如做如下加法:

只能用半加器来计算最右边一列数:即1加1等于0,进位为1。对于右边第2列数,由于进位的存在,需要加3个数。接下来的几列都有这个问题,每一列二进制位的加法都包括了来自前一列的进位。

要把3个二进制数相加,需要按如下方式把两个半加器和一个或门连接起来:

要理解它的工作原理,先从最左边第一个半加器的A输入和B输入开始,其输出是一个和及相应的进位。这个和必须和前一列的进位输入(简称CI)加起来,然后把它们输入到第二个半加器。第二个半加器的和输出是最后的和。两个半加器的进位输出又输入到一个或门,或门产生了本次加法的进位输出。你可能会想这里还需要一个半加器,这当然是可行的。但当你把所有的可能情况考虑完,你会发现两个进位不可能同时为1。当两个输入不能同时为1时,或门已足够用于表示两个进位的加法,此时或门和异或门的功能是相同的。

上图可简化表示为下面的方块图,称其为“全加器(FullAdder)”:

下面的表是对全加器所有可能的输入及其相应输出的小结:

前面说过加法机需要144个继电器,这个数目是如何得到的呢?每个与门、或门、与非门都需要2个继电器,所以,一个异或门需6个继电器。一个半加器由一个异或门和一个与门构成,所以它要8个继电器。1个全加器需要两个半加器和一个或门,所以它要18个继电器。对于8位二进制加法机而言,共需8个全加器,因而总共是144个继电器。

回想一下本章最开始那个带开关和灯泡的控制面板:

现在可以把这些开关和灯泡连接成全加器了。

首先把最右边的两个开关和一个灯泡连到一个全加器上,如下图所示:

当把两个二进制数相加时,第1列的处理有所不同。因为接下去的几列可能包括来自前面加法的进位,而第1列不会有进位,所以全加器的进位输入端是接地的,这表示输入为“0”。第1列相加后很可能会产生一个进位输出,这个进位输出是下一列加法的输入。

对于接下去的两个二进制位和灯泡,可以按如下办法连接全加器:

第一个全加器的进位输出是第二个全加器的进位输入。接下去的每一列数都以这种方式连接,每一列的进位输出都是下一列的进位输入。

第八个灯泡和最后一对开关连到最后一个全加器上,连接方式如下图所示:

这里最后的进位输出连到第九个灯泡上。

这样,8个全加器就构造成功了。

还可以用另一种方式来看8个全加器的集成,每个全加器的进位输出都是下一个全加器的进位输入:

下面是一个完整的屏蔽在一个盒子里的8位加法器。输入是A和B标识为从A0~A7及B0~B7。输出为和输出,标识为从S0~S7:

这是标识多位数字的常用方法。下标为0的位A0、B0和S0表示最右边的、最不起眼的位。而位A7、B7和S7是最左边的、最引人注目的位。例如,下面展示的是这些字母是如何用来表示二进制数0110-1001的:

下标始于0,且向高位递增的原因是它们和2的乘方数(幂)是对应的:

如果把每个二进制位和对应的2的幂次方相乘再依次相加,你就会得到0110-1001的十进制数表示,即64+32+8+1=105。8位加法器的另一种画法是:

双线箭头包含了8个输入端,代表一组8个分开的信号。它们标识为A7…A0、B7…B0、S7…S0也用来表示一个8位二进制数。

一旦构造了一个8位加法器,就可以构造另一个加法器。把它们级联起来可以很容易地构成16位加法器:

右边加法器的进位输出连到左边加法器的进位输入端。左边加法器的输入包含了两个加数的高8位,同时产生了结果的高8位。

现在,你可能会问:“计算机真的是以这种方式把数字加起来的吗?”

基本上是这样的,但不完全是。

首先,加法器应该做得更快。如果你明白这个电路是如何工作的,你会看到最低位相加产生的进位作为下一列数相加的一个输入,而第3列的加法又等着第2列加法的进位,依此类推。加法器总体的速度等于加数的位数乘以单个全加器的速度。这种进位方式称为行波进位。更快的加法器使用称为先行进位的加法电路,从而加快了加法进程。

第二(但是十分重要),计算机再也不用继电器了!尽管它们曾经用过。建于20世纪30年代初的第一批数字计算机使用继电器,后来又用了真空管。现代计算机用晶体管。当用在计算机中时,晶体管和继电器的功能差不多,但是晶体管速度更快,体积更小,更安静,更省电,而且还便宜不少。构造一个8位加法器仍然需要144个晶体管(如果采用先行进位,则需要更多),但整体电路的体积却小多了。

第十三章  如何实现减法

在你确信继电器可以连接起来以构成二进制加法器后,你可能会问:“减法器如何实现呢?”本章将会为你解答这个问题,且提出这个问题也表明你有了一定的理解力。减法和加法在某些方面是互为补充的,但两种计算的机制不同。加法从最右边一列向最左边一列计算,每一列的进位都加到下一列中去。减法不用进位,相反,要用到借位—一种本质上与加法不同的机制。

例如,让我们看一道典型的不断借位的减法题目:

要做这道题,从最右边一列开始。首先,6比3大,所以需要从5借1,这样就变成了13减6,结果是7。由于从5借了1,5就变成了4,4比7小,所以继续从2借1,14减7等于7。2被借1后成为1,1减1为0,所以最后结果是77

如何用逻辑门来实现这看似不合常理的逻辑呢?

我们不会直接用这种方法,代替的是用一个小技巧,使不通过借位来实现减法。这会是一个使大家都满意的好办法。详细地了解减法的完成是很有用的,因为它和用二进制编码在计算机中存储负数的机制有很大联系。

为解释这样的工作,需要清楚地指明两个操作数,即减数和被减数。减数从被减数中去掉后,结果是二者之差:

要想不借位,首先将减数从999中减去:

这里用999是因为操作数是3位,如果是4位数,就用9999。把一个数从一串9中减去得到的结果称为9的补数或补码。176的9的补数是823,反之,823的9的补数是176。这样做的好处在于,无论减数是什么,计算9的补数永远不需要借位。

在计算出减数的9的补数之后,把它加到原来的被减数上:

最后,你再加1并且减去1000

这样就得到结果了。答案和以前一样,且你根本不用借位。

这是什么原理呢?原来的减法题目是:253-176

表达式加一个数再减同一个数得到的结果是一样的。所以先加上1000,再减去1000:253-176+1000-1000

这个式子等同于下面的式子:253-176+999+1-1000

再按如下方式重新组合:253+(999-176)+1-1000

这与前面描述过的用9的补数进行的计算是一致的。虽然用了两个减法和两个加法来代替一个减法,但是也因此省去了讨厌的借位。

但是,如果减数比被减数大怎么办呢?例如如下计算:

通常情况下,你看到这个式子后可能会说:“减数比被减数大只需交换两数位置,再做减法,然后给结果取个相反数。”于是你在脑子里交换了它们的位置,并求出了答案:

要省去借位来做这道题和前面的例子有所不同。首先你要求出253的9的补数,即

再把该补数和原来的被减数相加:

这时候,按照上一道题的步骤,你应该对其加1再减去1000,但在本题中,这种方法不会生效。如果你还按这种步骤做,就需要从923中减去1000,这又导致了借位。

既然实际上前面已经加了999,这里再减去999

当做到这一步时,可看出结果是个负数,故需要交换两数位置,不过这样再做减法时已不需要借位,答案如预期所料:

同样的方法可用于二进制数减法,而且会比十进制数减法来得简单。让我们看看该如何做。

原来的减法题目是:

当把这些数转化为二进制数时,问题变成:

步骤1用11111111减去减数:

当计算十进制数减法时,减数是从一串9中减去,得到称为9的补数的结果。对于二进制数减法,减数从一串1中减去,差称为1的补数。但请注意,求1的补数实际上并不需要做减法,因为1的补数中,原来的0变成1,原来的1变成0,所以,1的补数有时也称为相反数或反码。(你是否还记得第11章中反向器的作用是把0变成1,把1变成0。)

步骤2把步骤1中求得的补数和被减数相加:

步骤3对结果加1

步骤4减去100000000(256):

该结果就是十进制数77

现在把两数颠倒位置后再做一遍。在十进制中,减法题目对应于:

而在二进制中,即是:

步骤1从11111111中减去减数。得到补数:

步骤2把步骤1中的补数和被减数相加:

现在,11111111必须再从结果中减掉。当减数比被减数小时,可以通过先加1再减去100000000来达到此目的。但现在这样做却会用到借位。所以,我们先用11111111减去步骤2中的结果:

这实际上是对步骤2中得到的结果取反。最后的结果是77,而真正的答案应该是-77

现在,已经可以改进加法机使它既能执行加法操作亦能执行减法操作。为使简便起见,这个加/减法机只执行被减数大于减数的减法操作,即差为正数的操作。

该加法机的核心部件是由逻辑门集成的8位全加器:

前面讲过输入A0~A7及B0~B7连接到开关上,用于表示8位操作数。进位输入端接地。S0~S7连接8个灯泡,用于表示加法的和。由于和可能会是9位数,进位输出端也连了一个灯泡。

控制面板如下图所示:

上图中,开关被设为183(或10110111)和22(或00010110),产生的结果是205或11001101)。用于加/减法的新的控制面板有一点儿修改,它包含了一个用于选择做加法还是做减法的额外开关。

如图所示,当这个开关向下时表示选择加法运算,反之是选择减法运算。此外,只有最右边的8个灯泡用于表示结果,第九个灯泡用来标识上溢/下溢,它指明了一个不能用8个灯泡表示的数。当加法操作得到的和大于255(称为上溢)或减法计算中出现一个负数(下溢)时,这个灯泡就会亮。减数比被减数大时,结果就是一个负数。

这个加法机主要增加了一个求8位二进制数的补数的电路。由于一个数的补数就是取其每一位的相反数,所以这个电路看起来很简单,就是8个反向器而已。

该电路存在一个问题,就是它不分情况地对输入求反。我们需要一台既能做加法又能做减法的机器,而此电路只有做减法时才取反。对它进行一下改进,如下图所示:

图中标识为“取反”的信号输入到每一个异或门中。回忆一下异或门的功能:

如果“取反”信号为0,则异或门的8个输出和8个输入是相同的。例如,如果输入是01100001,则输出也是01100001;若“取反”信号为1,则输出取反。例如,当输入是01100001时,输出为10011110。让我们把8个异或门集成到一个盒子里,称为求补器:

求补器、8位加法器及一个异或门可按下图连接:

注意上图中有3个信号都标识为“SUB”,这是加/减法转换开关。当该信号为0时做加法,为1时做减法。做减法时,B输入在送入加法器之前先求补。此外,做减法时,通过设置加法器的进位输入端(CI)为1,使由加法器得到的结果加1。对加法而言,求补电路没有起作用,CI输入也就是0

“SUB”信号及加法器的CO输出作为异或门的输入来控制表示上溢/下溢的小灯泡。如果“SUB”信号为0(表示做加法),则当CO输出为1时灯泡点亮,这表示加法的和大于255

当做减法时,如果被减数大于减数,则加法器的CO端正常输出1,这表示在减法的最后一步中要减去100000000。所以,只有当加法器的CO输出为0时,上溢/下溢灯泡才被点亮。这时减数大于被减数,差是个负数。上面这个加/减法器现在还不能表示负数。

你一定兴致勃勃地想知道该如何实现减法了。本章一直在谈论负数,但没有指出二进制负数的表示方法。你可能会认为它的表示和十进制负数一样,只需在数的前面加个负号。例如,-77在二进制中写成-1001101。你当然可以这么表示,但别忘了用二进制数的目的在于只用0和1表示所有的东西,当然也包括一个小小的负号了。

你可以用某一位代替负号,当该位为1时就表示负数,为0时表示正数,这似乎也是可行的。但还有一种方法,它不仅能表示负数,而且还很适于把正数和负数相加到一起。这种方法的不足之处是你必须提前决定数字需要多少位。

通常用来表示正、负数的方法的好处是这种方法能表示所有的正数、负数。我们把0想象成向一个方向延伸的无穷的正数流和向另一个方向延伸的无穷的负数流的中点:…-1000000-999999…-3-2-10123…9999991000000…,但是,如果并不需要无限大或无限小的数,而是完全可以确定计算中所遇到的数的范围,情况便有所不同了。

下面来看看帐户的例子,人们有时可以在帐户上看到负数。假设帐户上从来没有超过$500的存款,而银行给我们的预支额是$500,这就意味着帐户上的数字在$499~-$500之间。假设我们不会一次取出$500,也不会写一张超过$500的支票,同时我们只处理美元,而不考虑到更小的货币单位—美分。

这些假设表明帐户能处理的数字范围是从-500~499,总共1000个数。这个限制暗示我们只能用3位十进制数,且可不用负号来表示这1000个数。其中的关键在于我们不需要500~999之间的正数,所以它们就可以用来表示负数。下面是其工作原理:

换句话说,以5、6、7、8、9开头的3位数实际上都表

示负数。不用如下的表示法:

-500-499-498…-4-3-2-101234…497498499

而用这样的表示法:

500501502…996997998999000001002003004…497498499

注意这样形成了一个环形排序,最小的负数(500)看上去是最大的正数(499)的延续。数字999是比零小的第一个负数。如果给999加上1,通常得到1000。但由于只处理3位数,所以实际上是000

这种处理称为10的补数。要把3位负数转换成10的补数,需从999中减去它再加1。换句话说,10的补数是9的补数再加1。例如,要把-255写成10的补数,应先从999中减去255得到744,再加上1后得到745

你可能听说过“减法不过是负数的加法”,你也可能回答过“其实还是不得不做减法”。然而,通过使用10的补数,就不用去做减法了,全部都可以用加法来计算。

假设你有余额为$143的帐户,并写了一张$78的支票,这表明你要把-78加到143上。-78的补数是999-78+1,即922。所以新的余额是143+922(忽略上溢),即65。若我们再写一张$150的支票,则必须减去150,用补数表示就是850。先前的余额065加上850等于915,所以,新的余额实际上是-$85

二进制中对应的系统称为2的补数。假设我们用8位二进制数工作,范围从00000000~11111111,对应于十进制的0~255。这时如果你想要表达负数,则以1开头的每个8位数都表示一个负数,如下所示:

你可以表示的数的范围从-128~127。最左边的一位称为符号位,1表示负数,0表示正数。

要计算2的补数得先求出1的补数再加上1,这等同于先求反再加1。例如,十进制数125是01111101,要用2的补数来表示-125,可先取反得10000010,再加1就得到10000011。可用上表来验证这个结果。要回到原来的数只需同样的操作:取反后加1

这个系统使不用负号就能表示正、负数,它也使我们只用加法规则就可以随意进行正、负数运算。例如,计算-127+124,利用上表即得

和是十进制的-3

这里要注意上溢或下溢,即结果大于127或小于-128的情况。例如,125加125

因为最高位是1,结果代表一个负数:-6。再看-125加上它自已:

由于限制了只取8位数,所以最左边的1被扔掉,剩下的8位表示6

一般而言,若两个操作数的符号相同,而结果的符号与操作数的符号不相同时,这样的加法是无效的(即加法运算产生了溢出!)。

现在,二进制数可以有两种不同的使用方法。二进制数可以是无符号的或有符号的,无符号的二进制8位数的表示范围从0~255,有符号的二进制8位数的表示范围从-128~127。这些数本身不会告诉你它们是否带有符号。例如,假设有人问:“10110110对应于十进制数的几?”这时,你必须先问清楚它是无符号数还是有符号数?它可能是182或-74

这就是二进制数的麻烦:它们仅仅是一些0和1而没有告诉它们的任何含义。

第十四章  反馈与触发器

人人都知道电可以使物体运动。随便看一眼就会发现,很多家用电器中都装了电动机,如钟、风扇,食品加工机、CD机等等。电也能使扬声器中的磁芯振动,从而使音响设备、电视机产生了声音、话音和音乐。不过,电使物体运动的一个最简单、最神奇的例子可能是电子蜂鸣器和电铃。

将继电器、电池、开关按如下形式连接:

如果你觉得它看起来很奇怪,则你还没有发挥出你的想像力。我们还从未见过如此连接的继电器。原来的继电器中,输入和输出通常是分开的,这里却构成一个闭环。当闭合开关时,电路连通了:

接通的电路使电磁铁把金属簧片拉下来(电流的作用):

当金属簧片改变位置后,电路不再完整,电磁铁失去了磁性,金属簧片又弹回原来的位置:

这样,电路便又一次接通了。可见,只要开关是闭合的,金属簧片就会上下跳动—使电路闭合或断开—并制造一种声音。如果金属簧片制造了一种刺耳的声音,它就构成了一个蜂鸣器。如果金属簧片附上一把小锤子,再加一个金属锣,它就构成了一个电铃。

有两种方法可用来连接继电器以构造一个蜂鸣器,下面是另一种方法的描述:

你可能从上述图中认出了这是第11章介绍过的反向器,所以电路可以简化为:

对于反向器而言,当输入为0时,输出为1;输入为1时,输出为0。在该电路中闭合开关会使反向器中的继电器间断地闭合和断开。如果去掉开关,可以使反向器连续地工作,如下图示:

这幅图似乎在演示一种逻辑矛盾,反向器的输出是和其输入相反的,但是在这里,其输出同时又是其输入。需要特别指出的是,反向器实际上是一个继电器,而继电器从一个状态转换到另一个状态是需要时间的。所以,即使输入和输出是相等的,输出也会很快地改变,成为输入的倒置(当然,随即输出也就改变了输入,如此反复)。

电路的输出是什么呢?其实就是提供电压和不提供电压之间的变换。或者说输出要么是0,要么是1。

这个电路称为振荡器,它和我们以前见到的每样东西都有本质上的区别。以前,所有的电路都靠手动地断开或闭合开关来改变状态,而振荡器却不需要人的干涉,它可以自主地工作。

当然,单独的一个振荡器不会有什么用,但在本章的后面及接下去的几章里,你会看到这个电路和其他电路连接后构成了自动控制中一个十分关键的部分。所有计算机都靠某种振荡器来使其他部件同步工作。

振荡器的输出是0和1的交替序列,可以用下图形象地来表示它:

图中,水平轴表示时间,垂直轴表示输出是0或1:

此图表示随着时间的变化,振荡器的输出在0和1之间交替变化。基于这个原因,振荡器有时称为时钟(clock),因为通过对振荡次数记数还可确定时间。

那么,振荡器运行的速度有多快呢?也就是说,金属簧片上下跳动的频率是多少?每秒有多少次呢?很明显,这依赖于继电器是如何构造的。容易想到,一个大的、笨重的继电器只能迟钝地上下摆动;而一个小的、轻巧的继电器可以迅速地跳动。

我们把振荡器从某个时间的输出开始,经历一段变化又回到同样输出的这一段间隔称为振荡器的一个循环(cycle):

一个循环所需要的时间称为振荡器的周期。假设一个振荡器的周期是0.05秒,则可以在水平轴上标出时间:

振荡器的频率是周期的倒数。本例中,若振荡器的周期是0.05秒,则其频率是1÷0.05秒,即每秒钟20个循环。这表明振荡器的输出每秒钟改变20次。

每秒循环数与每小时英里数、每平方英寸磅数、每份食物(饮料)的卡路里数等毋需多解释的术语一样是一个很容易理解的概念,但已不常用。为了纪念第一个发送和接收无线电波的人—鲁道夫·赫兹(1857-1894),我们用“赫兹”这个词表示每秒的循环数。这个用法始于20世纪20年代的德国,后来传到其他国家。

于是,我们可以说这个振荡器的频率是20赫兹,或直接简写为20Hz。

到目前为止,我们只是在假设一个振荡器的速度。到本章末尾,我们可以构造一种器件来真正地测量一个振荡器的速度。

为了构造这个器件,先看一个用特殊方式连接的一对或非门。或非门的特点是只有两个输入都为0时,输出才为1:

下图是含有两个或非门、两个开关和一个灯泡的电路:

注意图中奇特的连接方式:左边或非门的输出是右边或非门的输入,右边或非门的输出是左边或非门的输入。这是一种反馈。事实上,这和在振荡器中类似,输出又返回作为一种输入。这是本章中大部分电路的特点。

在上图电路中,一开始,只有左边或非门的输出有电流,因为它的两个输入均为0。现在闭合上面的开关,左边或非门的输出变为0,于是右边或非门的输出变为1,灯泡点亮:

神奇之处在于当你断开上面的开关时,由于或非门的输入中只要有一个为1,其输出就是0,因而左边或非门的输出不变,灯泡仍然亮着:

你不觉得奇怪吗?两个开关都断开着,和第一幅图一样,但灯泡却亮着。这种情形和以前所见到的完全不同。通常,一个电路的输出仅仅依赖于输入,这里的情况却不一样。无论断开或闭合上面的开关,灯泡总是亮着。这里开关对电路没有什么影响,原因是左边或非门的输出一直是0。

现在闭合下面的开关。由于右边或非门的输入中有一个是1,则其输出变为0,灯泡熄灭。左边或非门的输出此刻变为1:

现在,再断开下面的开关,灯泡仍旧不亮:

此电路和初始电路一样。然而这回却是下面开关的状态对灯泡没有什么影响。总结起来就是:

1、闭合上面的开关使灯泡点亮,当再断开时,灯泡仍然亮着。

2、闭合下面的开关使灯泡熄灭,当再断开时,灯泡仍然不亮。

电路的奇特之处是:有时当两个开关都断开时,灯泡亮着;而有时,当两个开关都断开时,灯泡却不亮。当两个开关都断开时,电路有两个稳定状态,这样的一个电路称为触发器。触发器是1918年在英国射电物理学家WilliamHenryEccles(1875-1966)和F.W.Jordan的工作中发明的。

触发器电路可以保持信息,换句话说,它有记忆性。它可以“记住”最近一次是哪个开关先闭合的。如果你遇到这样一个触发器,它的灯泡亮着时,你可以确定最近闭合的是上面的开关;而灯泡灭着时则是下面的开关。触发器和跷跷板很像。跷跷板有两个稳定状态,它不会长期停留在不稳定的中间位置。你只要一看跷跷板就知道哪边是最近被压下来的。

触发器是十分关键的工具,尽管你现在可能还没看出来。它们赋予电路“记忆”,使其知道以前曾有过的状态。想像一下,如果你没有记忆力,你该如何去数数,你记不住你刚数过的数,当然也无法确定下一个数是什么。同样,一个能计数的电路(本章后面要提到)必定需要触发器。

触发器有很多种,刚才所看到的是最简单的一种,称为R-S(或Reset-Set,复位/置位)触发器。下面以对称的方式把它重新绘出来:

用于点亮灯泡的输出称为Q,另一个输出-Q是Q的倒置。如果Q是0,-Q就是1,反之亦然。两个输入端S(Set)和R(Reset)分别表示置位和复位。你可以把“置位”理解为把Q设为1,而“复位”是把Q设为0。当S为1时(对应于前面图中闭合上面开关的情况),Q变为1而-Q变为0;当R为1时(对应于前面图中闭合下面开关的情况),Q变为0而-Q变为1。当S和R都为0时,输出保持Q原来的状态。输入与输出的关系小结于下表中:

这张表称为功能表、逻辑表或真值表。它指明不同的输入组合能产生不同的输出结果。由于R-S触发器有两个输入端,因而不同的输入组合有4种,分别对应于表中的4行。

注意表中倒数第2行中S和R均为零,而输出标识为Q和-Q。这表示当S和R输入均为零时,Q和-Q端的输出保持S、R同时设为0以前的输出值。表中最后一行说明S和R输入都为1是非法的、禁止的。这是因为S、R同时为1时,两个输出Q和-Q均为零,这与Q和-Q互为倒置的关系相矛盾。所以,当你用R-S触发器设计电路时,要避免使R、S输入同时为1的情况。

R-S触发器通常画成有两个输入,两个输出的方块图,如下图所示:

R-S触发器能够记住哪一个输入端最近被输入高电位,这确实很有趣。但更有用的电路应该能记住某个特定时间点上上一个信号是0还是1。在实际构造这种电路之前,先来思考一下它的行为功能。它需要两个输入,其中一个称为数据端(Data)。像所有数字信号一样,数据端输入可以是0或1。另一个输入称为保持位(Holdthatbit)。通常情况下,保持位设为0,这时,数据端对电路没什么影响。当保持位置为1时,电路就反映出数据端的值。接着,保持位又置为0,这时,电路将记住数据端输入的最近一个值。数据端信号的任何改变不会对电路再有影响。

换句话说,它的功能表可以这样写:

在前两种情况下,保持位置为1,Q端输出和数据端输入相同;后两种情况下,当保持位置为0时,Q端输出和它以前的值相同,即保持原状态。注意,后两种情况中当保持位为0时,Q端输出不再受数据端输入的影响,功能表可以简化表示为:

X表示不关心其取值情况,它的值对于电路输出没有影响。

基于R-S触发器来实现保持位的功能要求在输入端增加两个与门,如下图所示:

要使与门输出为1,两个输入端必须同时为1。在上图中,Q输出为0,而-Q输出为1。

只要保持位置为0,置位信号对于输出就没有影响:

同样,复位信号对电路输出也没有影响:

只有当保持位信号是1时,电路的功能才和前述的R-S触发器相同:

这时,由于上面与门的输出和复位端输入相同,而下面与门的输出和置位端输入相同,所以此电路的功能就和普通的R-S触发器是一样的了。

但我们还没有达到目标,我们只想要两个输入,而不是三个,怎么办呢?前面讲过R-S触发器中两个输入同时为1的情况是禁止的;而两个输入同时为零的情况没有什么意义,因为那只是输出保持不变的简单情况。这里,只要将保持位置为0,就可以完成同样的功能。

可见,真正有意义的输入是S为0,R为1或R为0,S为1。把数据端信号当作置位信号,它取反后的值就是复位端信号,如下图示:

在这种情况下,S和R输入以及输出Q均为0,-Q为1。只要保持位为0,数据端输入对于电路输出就没有影响:

当保持位为1时,电路反映出数据端输入的值:

Q端输出现在和数据端输入是一致的,-Q则相反。现在,保持位又回到0:

 

这时,电路会记得当保持位最后一次置为1时数据端输入的值。数据端以后的变化对电路的输出没有影响:

这个电路称为电平触发的D型触发器,D(Data)表示数据端输入。所谓电平触发是指当保持位输入为某一特定电平(本例中为“1”)时,触发器才对数据端的输入值进行保存。(很快,你将会看到另一种形式的触发器。)

通常情况下,当这样一个电路出现在书中时,输入并不被标为保持位,而是标为“时钟”。当然,这个信号并不是一个真的时钟,但它有时却具有类似钟一样的属性,即在0和1之是有规律地来回变化。但是现在时钟只是用来指示什么时候保存数据。

把数据端简写为D,时钟端简写为Clk,其功能表如下所示:

这个电路就是所谓的电平触发的D型锁存器,它表示电路锁存住一位数据并保持至将来使用。它也可以称为1位存储器。本书将在第16章中说明如何将多个1位存储器连起来以构成多位存储器。

在锁存器中保存多位值是很有用的。假如你想用第12章中的加法机把三个8位数加起来,你可以在第1行开关上输入第一个加数,在第2行开关上输入第二个加数,但是你必须把第一次加法运算的结果记录下来,然后以同样方式把记下来的结果和第三个加数再用开关输入。这是十分麻烦的。使用锁存器可以解决这个问题。让我们把8个锁存器集成到一个盒子里,形成一个8位锁存器。每个锁存器用到两个或非门、两个与门和1个反向器。时钟端输入是互相连在一起的。结果如下图所示:

这个锁存器一次可以保存8位数。上面的8个输入标为D0~D7,下面的8个输出标为Q0~Q7。左边的输入是时钟(Clk),时钟信号通常为0。当时钟信号为1时,D端输入被送到Q端输出。当时钟信号变为0时,8位输出值保持不变,直到时钟信号再次被置为1。8位锁存器也可以画成下面的样子:

下面是8位加法器:

通常(先不考虑上一章的减法),8个A输入和8个B输入是连在开关上的,CI(进位输入)端接地,8个S(和输出)和CO(进位输出)端连着灯泡。

经修改,8位加法器的输出既与灯泡相连,也作为8位锁存器的数据端(D)输入。标为“保存”(Save)的开关是锁存器的时钟输入,用于保存加法器的运算结果:

标识为2-1选择器的方块是让你用一个开关来选择加法器的B端输入是取自第2排开关还是取自锁存器的Q端输出。当选择开关闭合时,就选择了用8位锁存器的输出作为B端输入。2-1选择器用了8个如下电路:

如果选择(Select)端输入为1,或门的输出和B端输入是一样的。这是因为上面与门的输出和B端输入是一样的,而下面与门的输出是0。同样,如果选择端输入是0,或门的输出则和A端输入是一样的。总结起来如下表所示:

修改后的加法机中包含了8个这样的1位选择器。所有选择端的信号输入是连在一起的。

改进过的加法机不能很好地处理进位输出(CO)信号。如果两个数的相加使进位输出信号为1,则当下一个数再加进来时,这个信号就被忽略了。一个可能的解决方法是使加法器、锁存器、选择器均为16位宽度,或者至少比你可能遇到的最大的和的位数多一位。这个问题会在第17章中专门讲述。

对加法机一个更好的改进方法是完全去掉一排开关,但是这需要先对D触发器做一点儿小的改进,对它加一个或门和一个称为清零(Clear)的输入信号。清零信号通常为0,但当它为1时,Q输出为0,如下图所示:

无论其他信号是什么,清零信号总迫使Q输出为0,起到了给触发器清零的作用。

你也许还不明白为什么要设置这个信号,为什么不能通过把数据端输入置0和时钟端输入置1来使触发器清零呢?这也许因为我们并不能控制数据端的输入。下图中,8个锁存器连着8位加法器的输出:

注意,标识为“相加”(Add)的开关此刻控制着锁存器的时钟输入。

你可能会发现这个加法器比前面那个好用,尤其是当你需要加上一长串数字时。刚开始时,按下清零开关,这个操作使锁存器输出为0,并熄灭了所有的灯泡,同时使加法器的B端输入全为0。接着,通过开关输入第一个加数,闭合“相加”开关,则此加数反映在灯泡上。再输入第二个数并再次闭合“相加”开关,由开关输入的8位操作数加到前面的结果上,其和输出到灯泡。如此反复,可以连加很多数。

触发器是电平触发式的,意思是说只有在时钟端输入从0变到1后(即高电平时),数据端输入的值才能保存在锁存器中。注意,在时钟端输入为1期间,数据端输入的任何改变都将反应在Q或-Q端的输出值上。

对一些应用而言,电平触发时钟输入已经足够用了;但对另外一些应用来说,边沿触发时钟输入更为适用。对于边沿触发器而言,只有当时钟从0变到1的瞬间,输出才会改变。在电平触发器中,当时钟输入为0时,数据端输入的任何改变都不会影响输出;而在边沿触发器中,当时钟输入为1时,数据端输入的改变也不会影响输出。只有在时钟输入从0变到1的瞬间,数据端的输入才会影响边沿触发器的输出。

边沿触发的D型触发器是由两级R-S触发器按如下方式连接而成的:

这时,时钟输入既控制着第一级,也控制着第二级。但是应该注意到时钟信号在第一级中取了反,这意味着除了当时钟信号为零时保存数据外,第一级工作原理和D型触发器完全相同。第二级的输出是第一级的输入,当时钟信号为1时,它们被保存。总的结论就是只有当时钟信号从0变为1时,数据端输入才会保存下来。

让我们进一步分析。下面是处于非工作状态的触发器,其数据端、时钟输入均为0,Q端输出也是0:

现在,使数据端输入为1:

这改变了第一级触发器状态,因为时钟信号取反后为1。但第二级仍保持不变,因为时钟端输入仍为0。现在把时钟输入变为1:

这就引起第二级触发器改变,使Q端输出变为1。与前面不同的是现在无论数据端输入如何变化(如变为0),它也不会影响Q端的输出值:

Q和-Q端输出只有在时钟输入从0变到1的瞬间才发生改变。

边沿触发的D型触发器的功能表需要一个新符号来表示这种从0到1的瞬时变化,即用一个向上指的箭头(↑)表示:

箭头表示当Clk信号从0变到1时,Q端输出和数据端输入是一样的,这称为Clk信号的“正跳变”(“负跳变”是从1到0的转换)。触发器的符号图如下所示:

图中的小三角符号表示触发器是边沿触发的。

现在向你展示一个使用边沿触发器的电路。先回忆一下本章开始构造的振荡器,振荡器的输出是在0和1之间变化的:

把振荡器的输出连到边沿触发的D型触发器的时钟输入端,并把端输出连到自己的D输入端:

触发器的输出同时又是它自己的输入。(实际上,这种构造可能是有问题的。振荡器是由来回迅速转变状态的继电器构成的。振荡器的输出和构成触发器的继电器相连,而这些继电器不一定能跟上振荡器的速度。为了避免这些问题,这里假设振荡器中继电器的速度比这个电路中其他地方的继电器的速度都慢。)

观察下面的功能表,就可以明白电路中发生的情况了。刚开始时,Clk输入和Q端输出都是0,则端输出为1,而它和D输入是相连的:

当Clk输入从0变到1后,Q端输出就和D输入一样了:

但是因为-Q端输出变为0,因而D输入也变为0。Clk输入现在是1:

当Clk信号变回为0时,不会影响输出:

现在Clk信号再变为1。由于D输入为0,则Q为0且-Q为1:

所以D输入也变为1:

以上发生的情况总结起来就是:每当Clk输入从0变到1时,Q端输出就发生改变,或者从0变到1,或者从1变到0。看看下面的图,问题就更清楚了:

当Clk输入从0变到1时,D的值(与的值是相同的)被输出到Q端。当下一次Clk信号从0变到1时,同样会改变D和的值。

若振荡器的频率是20赫兹(即每秒20次循环),则Q的输出频率是它的一半,即10赫兹。由于这个原因,这种电路(其中输出依循触发器的数据端输入)称为分频器。

当然分频器的输出可以是另一个分频器的Clk输入,并再一次进行分频。下面是三个分频器连在一起的情况:

让我们来看一下上图顶部的4个信号的变化规律:

这里只给出了这幅图的一部分,因为这个电路会周而复始地变化下去。从这个图中,有没有发现使你眼熟的东西?

提示你一下,把这些信号标上0和1:

现在看出来了吗?把这个图顺时针旋转90度,读一读横向的4位数字,每一组输出都对应了十进制中0~15中的一个数:

这个电路只具备了一个计数功能,如果再多加上几个触发器,它就可能计更多的数。第8章曾指出在一个递增的二进制数序列中,每一列数字在0和1之间变化的频率是其右边那一列数字变化频率的一半,这个计数器模仿了这一点。时钟信号每一次正跳变时,计数器的输出就递加了1。

可以把8个触发器集成于一个盒子里,构成一个8位计数器:

这个计数器称为8位行波(异步)计数器,因为每一个触发器的输出都成为下一个触发器的时钟输入。变化是沿着触发器一级一级地传递的,最后一级触发器的变化必定要延迟一些。更复杂的计数器是“并行(同步)计数器”,在这种计数器中,所有输出是同时改变的。

输出端信号已标识为从Q0~Q7,Q0是第一个触发器的输出。如果把灯泡连到这些输出上,就可以把8位结果读出来。

这样一个计数器的时序图可以把8个输出分开来表示,也可以把它们一起表示,如下图所示:

时钟信号的每个正跳变发生时,一些Q输出可能改变,另一些可能不改变,但总体上是使原来的结果递增了1。

本章前面曾提到过可以找到某种方法来确定振荡器的频率,现在这个方法已经找到了。如果把振荡器连到8位计数器的时钟输入上,计数器会显示出振荡器经历了多少次循环。当计数器总和达到11111111时,它又会返回到00000000。使用计数器确定振荡器频率的最简单方法是把计数器的输出连到8个灯泡上。当所有输出为0时(即没有一个灯泡点亮),启动一个秒表;当所有灯泡都点亮时,停住秒表。这就是振荡器循环256次所需要的时间。假设是10秒钟,则振荡器的频率就是256÷10,或者说是25.6赫兹。

当触发器功能增加时,它也变得更复杂。下面这个触发器称为具有预置(Preset)和清零功能的边沿触发的D型触发器。

通常情况下,预置和清零信号输入会忽视时钟和数据端输入,且均为0。当预置信号输入为1时,Q变为1,变为0。当清零信号为1时,Q为0,变为1(同R-S触发器中的S和R输入一样,预置和清零信号不能同时为1)。其他情况下,该触发器的行为和普通边沿触发的D型触发器是一样的。

电路图符号可以简化地用下图代替:

现在,我们已经知道如何用继电器来做加法、减法和计数,是不是很有成就感?因为我们所用的硬件是100多年以前就存在的东西,我们还有更多的空间去探索。但是先暂时休息一下,不用再去构造什么,回过头来再看看关于数字的问题吧。

第十五章  字节与十六进制

上一章中的两个改进的加法机清晰地解释了数据路径的概念。在整个电路中,8位值从一个部件传到另一个部件。它们是加法器、锁存器、选择器的输入,经过运算或操作又从这些部件输出。这些数由开关定义,最后由灯泡来表示结果。可以认为电路中的数据路径的宽度是8位。可是,为什么一定是8位,而不是6位、7位、9位或10位呢?

最简单的回答就是这些加法机是在第12章中最原始的加法机上改进而来的,而最原始的那个加法机就是8位。不过,这个解释似乎很缺乏说服力。实际上,用8位的原因是它表示一个字节。

字节这个词大概是在1956年前后由IBM公司最早提出来的。这个词起源于bite,但用y代替了i,以便不会被人误认为它是bit。曾经有一段时期,字节仅仅简单地表示特定数据路径上数据的位数。但是到了20世纪60年代中期,随着IBM的360系统的发展(一种大型复杂的商用计算机),字节这个词专门用来表示8位二进制数。

作为一个8位数,一个字节可以从00000000取值到11111111。这些数可以代表0~255的正数,也可以表示-128~127范围之内的正、负数。总之,一个特定的字节可以代表28即256种不同事物中的一个。

8位数事实上是很适用的,字节在很多方面都比一位数优越。IBM采用字节的一个原因就是它们易于以BCD(将在第23章中描述)这种格式保存。巧的是,在以后的各章中你会看到字节用于保存文本也是很合适的,因为世界上大部分书面语言都可以用少于256个字符的字符集来表示(除了汉语、日语、韩语等所用的表意文字以外)。用字节表示黑白图像中的灰度也是很合适的,因为人眼大约能区分256种不同程度的灰度。当一个字节不足以表示信息时(如刚才说的表意语言:汉语、日语、韩语等),用两个字节,即216或65536也可以很好地表示。

半个字节,即4位二进制数,有时被称为半位元组,它比起字节而言,用的并不频繁。

由于字节在计算机内部经常出现,所以尽可能简单明了地表示它会带来很大方便。例如,一个8位二进制数10110110的确很直观但是不简明。

当然也可以用十进制数来表示字节,但这要求从二进制换算成十进制,这样做不仅不简单,反而是件很令人讨厌的事。第8章曾描述了一种很直观的方法。每一位二进制数字写到对应的方盒子中,并在其下方标上2的乘方数。把每一列相乘后再相加即可得到对应的十进制数。下面是10110110的转换:

把十进制数转换为二进制数就更麻烦了。你可以用十进制数去除以以递减顺序排列的2的幂,每除一次,商是一个二进制位,而余数则继续去除以下一个最大的2的幂。下面是十进制数182转换成对应二进制数的过程:

第8章有关于这个方法的更多描述。总之,在十进制数和二进制数之间进行转换通常不是件十分简单的事。

从第8章中我们还学习了八进制数字系统,八进制数字系统只使用数字0、1、2、3、4、5、6、7。在八进制数和二进制数之间进行转换却是小菜一碟,你只要记住每个八进制数字对应3位二进制数字即可,如下表所示:

如果已有一个二进制数字(如10110110),则从最右边的数字开始,每3位二进制数字组成一组,即可转换为对应的八进制数:

可见,字节10110110可以表示为八进制的266。这显然已简单了很多,八进制确实是表示字节的一个好方法,但其中仍然有一个问题。

字节的二进制表示范围是从00000000~11111111,对应的八进制表示范围是从000~377。上例清楚地表示出3位二进制数对应于最右边和中间的八进制数,而2位二进制数对应于最左边的八进制数,这就表明一个16位二进制数的八进制表示和把它分成两个字节后的八进制表示有所不同:

为了使多字节值能和单字节值的表示一致,需要的系统应该能使每个字节平分,这意味着应该把每个字节分成4组,每组2位(以4为基数);或2组,每组4位(以16为基数)

让我们看看以16为基数的情况,这是我们还未接触过的新的记数系统。它被称为“十六进制(hexadecimal)”,这个词本身就让人迷惑,因为大部分以hexa-为前缀的词都是指与6有关的事物,而这里hexadecimal却是指16。虽然微软公司在技术出版物的格式方面明确地声明不要将十六进制缩写为hex,但绝大多数人还是使用这种缩写。

在十进制中,我们这样计数:

0123456789101112……

在八进制中,不需要8和9

01234567101112……

同样,以4为基数的系统不需要4、5、6或7

0123101112……

而二进制只需要0和1

011011100……

但是十六进制有些不同,它需要的符号比十进制还要多,如下所示:

0123456789??????101112……

上图中10出现的地方是表示十进制中的数16。此外,还需要6个符号来表示十六进制数,但这些符号是什么呢?它们来自哪里呢?最形象的方法是引入6个新符号,例如:

它们不同于大部分数字用的符号,这些符号的优点是便于记忆,而且从某种意义上代表了它们应该表示的数字。你看,重10加仑的牛仔帽、一个橄榄球(一个橄榄球队有11人)、一打椰子、一只黑猫(和不吉利的13相联系)、一轮满月(一般出现在新月后的第14天晚上)以及让人们联想到JuliusCaesar在三月的第15天被暗杀时所用的匕首。

每个字节都可以用两个十六进制数表示,换句话说,一个十六进制数代表4位二进制数,即半个字节。下表描述了在二进制数、十六进制树和十进制数之间的转换:

下图表示如何用十六进制表示字节10110110:

即使要表示多字节数也很容易:

一个字节总是由一对十六进制数来表示。

不过,我们绝不会真的用橄榄球或匕首来表示十六进制数,事实上,我们用6个拉丁字母来表示那6个十六进制数,如下所示:

0123456789ABCDEF101112……

下表表示出在二进制数、十六进制数和十进制数之间的转换:

字节10110110可以由十六进制数B6来表示,而不用再画上一个橄榄球。同前面的章一样,用下标来表示记数系统的基数,如:10110110TWO表示二进制、2312FOUR表示四进制、266EIGHT表示八进制、182TEN表示十进制、B6SIXTEEN则表示十六进制。不过,这真是挺麻烦的。还好,有更简单、更通用的方法来表示十六进制:B6HEX还可以进一步简化为B6h

在十六进制数中,每一位的位置都对应于16的幂:

十六进制数9A48Ch是:

用16的乘方表示为:

用对应的十进制数代入为:

注意,在写一个数时,不用下标来表示数的基数也不会引起混淆。9就是9,不管它是十进制数还是十六进制数;而A则显然是个十六进制数,相当于十进制中的10

把所有数字转换成十进制数需要下列运算:

答案是631948。以上是一个十六进制数如何转换成十进制数的过程。

下面是把任何一个4位十六进制数转换成十进制数的模板:

把十进制数转换为十六进制数需要做除法。如果数字比255小,则可以用1个字节来表示,对应于两个十六进制数。为了求出这两个数,用原数去除以16得到商和余数。举例说明,182除以16得11,余6,所以十六进制表示为B6h

如果想要转换的十进制数比65536小,则十六进制表示会有4位或更少。下面是把这样一个十进制数转换为十六进制数的一个模板:

先把整个十进制数放到左上角的盒子里,作为第一个被除数,用它除以4096(第一个除数),商放到被除数下面的盒子里,余数放到被除数右边的盒子里。余数成为新的被除数,用它除以256。以下是31148如何转换成十六进制数的过程:

当然,十进制数的10和12用十六进制表示就是A和C,故结果是79ACh.

这个方法的问题是如果你想用一个计算器来做除法运算,它不会显示出余数是多少。如果你用31148除以4096,计算器给出的结果只能是7.6044921875。为了计算余数你得用4096×7(得到28672),再从31148中减去它;或者用4096乘以0.6044921875,即商的小数部分。(不过,有些计算器具有十进制数和十六进制数之间的转换功能。)

把小于65535的十进制数转换为十六进制数的另一个方法是先把原数除以256而分为两个字节,对于每个字节,再除以16。下面是模板:

从最上面开始,每做完一次除法,商就进入除数左下方的盒子里,而余数进入右边的盒里里。例如,51966的转换过程是:

得到的十六进制数字是12、10、15和14,即CAFE,它看起来倒更像一个单词而非一个数字!

下面是和十六进制相关的加法表:

可以用这张表和通常的进位规则来对十六进制数做加法:

曾在第13章中讲过可以用2的补数来表示负数。如果在二进制中处理8位带符号数,则所有的负数都是以1开头的。在十六进制中,两位带符号数若是以8、9、A、B、C、D、E或F开头则是负数。例如,99h可能表示十进制的153(如果处理的是1个字节的无符号数)或十进制的-103(如果处理的是有符号数)。

字节99h也有可能就是十进制的99,关于这一点会在第23章中解释,但是下一步必须先讲讲存储器。

 

第十六章  存储器组织

每天早上,当我们从睡梦中醒来时,记忆会填充大脑的空白。我们会记起我们在哪里,做过什么,计划做什么。我们可能一下子就能想起来,也可能几分钟都想不起来,不过,总的来说,我们通常能够重新组织自己的生活,保持高度的连续性,开始新的一天。

当然,人类的记忆是无序的。当回忆高中的几何课时,你可能会想到是谁坐在你前面;或者那一天当老师要解释什么是QED(quoderatdemonstrandum,证完/证毕)的时候,刚好进行消防演习。

人类的记忆也非安全无比。其实,书写的发明就是为了弥补人类记忆的不足。前一天晚上你可能因为突然冒出的一个关于剧本的好主意而在凌晨三点醒来,抓起床边特地准备的笔和纸记下来以便不会忘掉,第二天早上你就可以看到这个好主意并开始着手写剧本。当然你也可以不用这样。

先写后读,先保存后取回,先存储后访问,存储器的作用就是在这两类事件间保证信息的完好无损。无论什么时候存储信息,都要用到不同类型的存储器。纸是保存文本信息的最佳媒体,磁带则能很好地保存音乐和电影。

电报继电器—当集成为逻辑门然后再集成为触发器—也一样可以保存信息。正如我们所知道的,一个触发器可保存1位信息。保存1位信息当然并不代表保存全部信息,但这是一个开端。一旦我们知道了如何存储1位信息,就可以容易地存储2位、3位或更多位信息。

第14章曾讲过电平触发的D型触发器,它由一个反向器、两个与门和两个或非门构成:

当时钟输入为1时,Q端输出与数据端输入是相同的。但当时钟输入变为0时,Q端输出将维持原来的数据端输入,再改变数据端输入不会影响Q端输出,直到时钟输入再次变为1为止。触发器的逻辑表格如下:

在第14章中,这种触发器的功能体现在两个不同的电路中。而在本章,它仅以一种方式来使用—即用于保存1位信息。正因为如此,给输入端和输出端重新命名,以便与该目的更为一致。

这是同一个触发器,但现在Q输出端命名为数据输出(DataOut),时钟输入端(在第14章是作为保持位)命名为写入(Write)。就像可以在纸上记录信息一样,写入信号使得数据输入(DataIn)信号写入或存储到电路中。通常,若写入信号(W)为0,则数据输入(DI)信号对输出无影响。而当我们想在触发器中存储数据输入信号时,写入信号应先置1后置0。就像在第14章提到的,这种类型的电路也叫锁存器,因为它锁定数据。下面画出了一个1位锁存器,但没有画出其所包含的单个部件:

把多个1位锁存器连成多位锁存器是相当容易的,只需连接好写入信号:

该8位锁存器具有8个输入端和8个输出端。另外,这个锁存器有一个写入输入端,通常为0。要在这个锁存器中存储一个8位二进制数,应将写入信号先置1后置0。也可以把这个锁存器画成一个整体,就像这样:

为了与1位锁存器一致,也可以画成这样:

另外一种集成8个1位锁存器的方法不像上述这么直接。假设只想用一个数据输入信号端和一个数据输出信号端,且又希望它具备在一天或一分钟内存储8次数据输入信号的能力。同时,也希望能通过检测这个数据输出信号端就可以检查这8个数据。

换句话说,我们只想存储8个单独的1位数,而不想在8位锁存器中存储1个8位数。

为什么会有这种想法呢?可能是因为我们仅有一个灯泡的缘故吧!

我们知道这需要8个1位锁存器。先不要考虑这些数据是怎样存储在这些锁存器中的,先让我们把注意力放在如何用一个灯泡来检查8个锁存器的数据输出信号上。当然,我们通常用手工把灯泡从一个锁存器移到另一个锁存器来测试各个锁存器的输出,不过,我们更倾向于用更自动化的方法来实现。实际上,我们打算用开关来选择想要检查的锁存器。

那么,需要多少个开关呢?若是8个锁存器,则需要3个开关。3个开关可表示8个不同的值:000、001、010、011、100、101、110和111

目前已有8个1位锁存器、3个开关、1个灯泡,此外还有“其他东西”用在开关和灯泡之间:

这个“其他东西”就是标识为“这是什么?”的神秘盒子,它上面有8个输入端,左侧有3个输入端。通过闭合和断开这三个开关,就可以从8个输入中选择一个,使其经过底部至输出端输出,该输出使灯泡发光。

“这是什么?”到底是什么呢?我们以前曾见过类似的东西,尽管没有这么多的输入端。它类似于第14章中第一个改进的加法机里用到的电路。那时我们需要某种东西用于是选择一行开关还是选择一个锁存器的输出作为加法器的输入,我们把这种东西叫2-1选择器,这里需要8-1选择器:

8-1选择器具有8个数据输入端(显示在上部)和3个选择输入端(SelectInput)(显示在左侧),选择输入端用于选择哪个输入数据在输出端输出。例如,若选择输入端为000,则输出D0的值;若选择端为111,则输出D7的值;若选择端为101,则输出D5的值。其逻辑表如下:

8-1选择器由三个反向器、八个4输入与门和一个8输入或门构成,如下所示:

这是一个相当复杂的电路,但只需一个例子就可以使你明白它是如何工作的。假设S2=1,S1=0,S0=1,从上面数第六个与门的输入包括S0、-S1、S2,它们全为1。没有其他与门有同样的三个输入信号,因此,其他与门输出全部为0。若D5=0,则第六个与门输出为0;若D5=1,则其输出为1。对最右边的或门来说也是如此。因此,若选择端为101,则输出为D5

概括一下我们想干什么。我们想连接8个1位锁存器,它们能够通过一个数据输入信号端分别写入,也能通过一个数据输出信号端分别检查。已经证明可以用一个8-1选择器从8个锁存器中选择一个数据输出信号,如下图所示:

到现在已完成了任务的一半。我们已经实现了输出端的要求,现在再来看一下输入端。

输入端包括数据输入信号及写入信号。在锁存器的输入端,可以把所有数据输入信号连接在一起。但不能把8个写入信号也都连在一起,因为我们还想分别向每个锁存器中写入数据。此外,还要有一个单独的写入信号,它必须能连到其中任一个(并且只能是一个)锁存器上:

为了完成这项工作,需要另外一个电路,这个电路看起来与8-1选择器有点相似,但实际上却正好相反。这就是3-8译码器。前面我们曾见过简单的数据译码器—第11章曾通过连接开关来选择理想的猫的颜色。

3-8译码器有8个输出端。任何情况下,锁存器除了一个输出端外,其余的均为0。这个例外是由S0、S1、S2输入信号所选择的输出端。该输出端输出的也是数据输入端的输入:

再说一遍,从上面数第六个与门的输入包括S0、-S1、S2,没有另外的与门有同样的三个输入。若选择输入端为101,则其他与门输出都为0。若数据输入为0,则第六个与门输出为0;若数据输入为1,则其输出为1。其逻辑表格如下:

下面是具有8个锁存器的完整电路:

注意,译码器和选择器的三个选择信号相同,现在这三个信号都记作地址(Address)。就像信箱号一样,3位地址决定了选择8个锁存器中的哪一个。在输入端,地址输入决定写入信号触发哪一个锁存器来存储输入的数据。在输出端(图的下部),地址输入控制8-1选择器选择8个锁存器中的一个进行输出。

这种锁存器的配置有时也称为读/写存储器,但通常叫作随机访问存储器或RAM。RAM可存储8个单独的1位数据,如下图所示:

称它为存储器是因为它能保存信息,称为读/写存储器是因为可以在每个锁存器中保存新的数据(也就是写数据),同时还可以查看每个锁存器中所保存的数据(也就是读数据)。称它为随机访问存储器是因为通过简单地改变地址输入就可以从8个锁存器中的任意一个读出或写入数据。相比之下,其他类型的存储器必须顺序读出—也就是,在可以读出存储在地址101的数据之前,必须读出存储在地址100的数据。

RAM配置通常称作RAM阵列,上述这种特定配置的RAM阵列以简写形式8×1的方式组织起来。阵列中可以存放8个数,每个仅占1位,RAM阵列中能存储的位数等于这两个值的乘积。

RAM阵列可通过各种方法来组合。例如,可以把两个8×1RAM阵列连接起来,使它们按照相同的方法来寻址:

这两个8×1RAM阵列的地址和写入输入端连接在一起,所以其结果为一个8×2RAM阵列。

 

这个RAM阵列可存储8个数,但每个数占2位。

两个8×1RAM阵列也可以按照与单个锁存器连接相同的方式—通过一个2-1选择器和一个1-2译码器—来组合,如下图所示:

连接到译码器和选择器的选择(Select)输入实质上选择两个8×1RAM阵列中的一个,在这里它也就是第4根地址线。所以,该图实际上是一个16×1RAM阵列。

此RAM阵列可存储16个数,每个数占1位。

RAM阵列的存储容量与地址输入端数目有直接关系。无地址输入端时(即1位锁存器和8位锁存器这种情况),只能存1个数;有一个地址输入端时,可存2个数;有两个地址输入端时,可存4个数;有三个地址输入端时,可存8个数;有四个地址输入端时,可存16个数。其关系可归纳成如下等式:

RAM阵列的存储容量=2地址输入端数目

上面已讲了如何构造小的RAM阵列,那么再规划大的RAM阵列应该并不困难。例如:

这个RAM阵列可存储8192位信息,按1024个数、每个8位来组织。因为1024=210,所以它有10条地址线。此外,它有8个数据输入端和8个数据输出端。

换句话说,这个RAM阵列可存储1024个字节。就像一个邮局有1024个邮箱,每个信箱中有一个不同的1字节数。

1024个字节即1K字节(kilobyte),1K字节在此会引起许多混淆。公制里前缀k(来自于希腊文khilioi,意思为1千)经常用到,如1kg=1000g,1km=1000m。但这里所说的1K字节=1024字节—而非1000字节。

原因在于公制是基于10的幂,而二进制是基于2的幂,这两种进制永远不会有相同的值。10的整数次幂为10、100、1000、10000、100000等,而2的整数次幂为2、4、8、16、32、64等,没有10的整数次幂与2的整数次幂相等的情况。

但有时它们非常接近。的确,1000非常接近1024,可以用“约等于(≈)”符号进行数学化表示:210≈103

这个关系式并非不可思议,它只不过表明2的某次幂等于10的某次幂而已。这种特例允许人们方便地把1024字节称作1K字节。

K字节简写为K或KB。所以,上面展示的RAM阵列存储1024字节或1K(1KB)

不能把1KB的RAM阵列说成是存储1000字节,它大于1000,即1024,为了让人知道你在说什么,你可以把它说成“1K”或“1K字节”。

1K字节的存储器具有8个数据输入端,8个数据输出端和10个地址输入端。由于是通过10条地址线来访问字节,所以RAM阵列可存储210个字节。无论何时再加上一条地址线,其存储容量将翻倍。下面每一行都都表示存储容量的翻番:

可以看出左侧的数字也是2的整数次幂。

按照同样的逻辑,我们能把1024字节称作1KB,当然也可以把1024KB称作1M字节(megabyte,希腊文megas意思为大),M字节缩写为MB。以下仍是存储容量翻番的式子:

希腊文gigas意思为巨大,所以把1024MB称作1G字节(gigabyte),缩写为GB

同样,1T字节(terabyte,希腊文teras意思为庞然大物)等于240字节(约1012)或1099511627776B,terabyte缩写为TB

1KB约为1000B,1MB约为1000000B,1GB约为1000000000B,1TB约为1000000000000B

再大的数就很少用了,如1PB(petabyte)=250

B或1125899906842624字节,约等于1015。1EB(exabyte)=260B或1152921504606846976字节,约等于1018

下面提供一些基本常识。在此书编写的时候(1999年),家用电脑一般都配有32MB或64MB或128MB的随机访问存储器(为不至于混淆,这里不谈任何关于硬盘驱动器的事情,而只谈论RAM),即33554432B或67108864B或134217728B

当然,人们总拣方便的讲。有65536字节内存的人会说“我有64K”;有33554432字节的人会说“我有32M”。虽说不多,但有1073741824字节的人也会说“我有1G”。

有时人们可能会提到K位或M位(注意是位而不是字节),不过这很少见。人们谈到存储器时,几乎总是指字节数而非位数。(当然,把字节转换成位,乘8即可。)在线路传送数据时,通常会有这样的短语每秒千位(kbps)或每秒兆位(mbps)出现。例如,56K的调制解调器指的是56Kbps,而非每秒千字节。

至此我们已经明白如何构造所需的RAM阵列,但不要离题太远。现在让我们看一下已经集成了65536字节的存储器:

为什么是64KB而非32KB或128KB?因为65536是一个整数,刚好为216B,也即该RAM阵列有16位地址。换句话说,该地址正好是2个字节。用十六进制来表示其地址范围是0000h~FFFFh.

64KB的内存在1980年的PC机上是比较普遍的配置,尽管它不是用电报继电器制成的。但是,你真的能用继电器来实现吗?肯定不能。因为按照我们的设计方案需要为每位存储器提供9个继电器,那么64K×8的RAM阵列需要将近500万个继电器。

利用控制面板来操作所有的存储器—写入数据到存储器或验证写入的数据—将更加先进。这种控制面板用16个开关来表示地址,8个开关来表示需要输入存储器的8位数据,8个灯泡来显示8位数据,再用一个开关来表示写入信号,如下图所示:

所有的开关均显示在0位置。另外,还有一个标识为接管(takeover)的开关,使用这个开关的目的是使其他电路可以使用与控制面板相连的同一个存储器。当接管开关置0时(如图所示),控制面板上的其余开关将不起任何作用;而当此开关置1时,控制面板将对存储器进行专门控制。

这些都可以用若干2-1选择器来实现。实际上,需要25个—16个接地址信号、8个接数据输入开关、另外1个接写入开关。其电路如下:

当接管开关断开时(如上图),64K×8RAM阵列的地址、数据输入和写入信号来自于外部信号,显示在2-1选择器的左上角;当接管开关闭合时,RAM阵列的地址、数据输入和写入信号来自于控制面板开关传来的信号。无论哪种情况,RAM阵列的数据输出信号传到8个灯泡上或其他可能的地方。

下图是带有控制面板的64K×8RAM阵列:

当接管开关闭合时,可用16个地址开关来选择65536个地址中的任何一个,而灯泡将显示当前地址中所存的8位数据。可用8个数据开关来定义一个新数,并通过写入开关把它写入存储器中。

通过64K×8RAM阵列和控制面板可以与需要处理的65536个8位数据中的任何一个保持联系。但我们也留了一些机会让别的东西—也许是其他一些电路—使用存在存储器中的数据,或者把数据写入存储器。

还有一个必须注意的有关存储器的问题,它非常重要。在第11章介绍逻辑门的概念时,并未画出构成逻辑门的单个继电器的构造。特别地,没有标出每个继电器连接的电源。任何时候当继电器触发时,电流流过电磁线圈并在适当的位置吸下金属簧片。

如果一个装满65536字节的64K×8RAM阵列被关掉电源,将会发生什么情况呢?所有的电磁铁将失去磁性,所有继电器的触点将回到未触发状态,RAM中的内容也将永远丢失。

这就是随机访问存储器也称为易失性存储器的原因,它需要恒定的电源来保持其中的内容。

 

第十七章  自动操作

人类是非常富于创造性而且是十分勤勉的,但是,人类在本质上也是十分懒惰的。非常明显,人类并不愿意去工作,这种对工作的反感导致人们用大量的时间来设计和制造可以把工作日缩短到几分钟的设备。幻想使人感到兴奋,甚至远比我们所看到新奇的事物更令人兴奋得多。

当然不会在这里介绍自动割草机的设计。本章将通过设计更精密的机器,使加减法运算更加自动化,这听起来也许有些不可思议。本章最后设计出的机器将具有广泛的用途,它实际上可以解决任何利用加减法的问题,这些问题的范围太大了。

当然,由于精密机器越来越复杂,因此有些部分可能会很粗糙。不过如果你略过了某些困难的细节,没有人会责备你。有时,你可能会不耐烦并且发誓再也不会因为一个数学问题而去寻求电或机械的帮助。不过请耐心坚持到底,因为本章最后将发明一个叫作计算机的机器。

我们曾在第14章见过一个加法器。它有一个8位锁存器,累加由8个开关输入的数的和:

前面曾讲过,8位锁存器用触发器来保存8位数据。使用这个设备时,必须首先按下清零开关使锁存器的存储内容清零,然后用开关来输入第一个数字。加法器简单地把这个数字与锁存器输出的零相加,因此其结果就是你刚输入的数字。按下相加开关可在锁存器中保存该数并且通过灯泡显示出来。现在从开关上输入第二个数,加法器把这个数与存储在锁存器中的数相加,再按下相加开关把总和存储在锁存器中并通过灯泡显示出来。通过这种方法,你可以加上一串数字并显示出运算总和。当然,其中存在的一个局限是8个灯泡不能显示总和超过255的数。

第14章介绍该电路的时候,只讲到一种锁存器,它是电平触发的。在电平触发的锁存器中,时钟输入端必须先置1然后回到0,才能使锁存器保存数据。当时钟信号等于1时,锁存器的数据输入可以改变,这种改变将会影响所保存的数据输出。第14章的后面又介绍了边沿触发的锁存器,这种锁存器在时钟输入从0变化到1的瞬间保存数据。由于边沿触发的锁存器易于使用,所以假定本章用到的锁存器为边沿触发的锁存器。

用于累加数字的锁存器叫作累加器,本章后面将会看到累加器并非仅仅进行简单的累加。累加器通常是一个锁存器,保存第一个数字,然后该数字又加上或减去另一个数字。

上面这个加法机存在的最大问题已经相当明显:如果想把100个二进制数加起来,你就得坐在加法机前耐着性子输入每一个数字并累加起来。当你完成时,却发现有两个数字是错误的,你只好又重复全部的工作。

不过,也可能并非如此。上一章用了差不多500万个继电器来构造一个64KB的RAM阵列。另外,我们还连接了一个控制面板,用来闭合接管开关接通线路,并使用开关进行RAM阵列的写入和读出。

如果你向RAM阵列中输入100个二进制数字,而不是直接输入到加法机中,那么进行数据修改会容易得多。

现在我们面临着一个挑战,即如何将RAM阵列连到累加器上。显而易见,RAM的数据输出信号应该代替累加器的开关组。但是,用一个16位的计数器(正如在第14章构造的)就可以控制RAM阵列的地址信号。在下面这个电路中,连到RAM的数据输入信号和写入信号可以不要:

 

当然这并非已经发明的最容易操作的计算装置。在使用之前,必须先闭合清零开关,以清除锁存器的内容并把16位计数器的输出置为0000h,接着闭合RAM控制面板上的接管开关。你可以从RAM地址的0000h处开始输入一组想要加的8位数,如果有100个数,则它们保存在从0000h~0063h的地址中(也可以把RAM阵列中没有用到的单元都设置为00h)。然后断开RAM控制面板上的接管开关(这样控制面板不会再对RAM阵列起控制作用了),并断开清零开关。这时,你就只需坐着看灯泡的亮灭变化了。

其工作情况为:当清零开关第一次断开时,RAM阵列的地址输入为0000h,保存在RAM阵列当前地址的8位数是加法器的输入。由于锁存器也清零,所以加法器的另8位输入为00h。

振荡器提供时钟信号—一个在0和1之间迅速交替变化的信号。在清零开关断开后,当时钟由0变为1时,将同时发生两个事件:锁存器保存来自加法器的结果;同时,16位计数器加1,指向RAM阵列的下一个地址。在清零开关断开后,当时钟第一次由0变为1时,锁存器保存第一个数,同时,计数器增加到0001h;当时钟第二次由0变为1时,锁存器保存第一个数与第二个数之和,同时计数器增加到0002h;依此类推。

当然,这里先做了一些假设,首要一点,振荡器需慢到允许电路的其余部分可以工作。对于每次时钟振荡,在加法器输出端显示有效和之前,许多继电器必须触发其他继电器。

这种电路有一个问题,即没有办法让它停止。到一定时候,灯泡会停止闪动,因为RAM阵列中的其余数都为00h。这时,你可以看到二进制和。但当计数器最终到达FFFFh时,它又会翻到0000h(就像汽车里程表),这时自动加法器又会开始把这些数加到已经计算过的和中。

这种加法机还有一个问题:它只能用于加法,并且只能加8位数。不仅在RAM阵列中的每个数不能超过255,而且其总和也不能超过255。这种加法器也没有办法进行减法运算。虽然可以用2的补码表示负数,但是在这种情况下,加法器只能处理-128~127之间的数字。让它处理更大数字(例如,16位数)的一种显而易见的方法就是使RAM阵列、加法器和锁存器的宽度加倍,同时再提供8个灯泡。不过你可能不太愿意做这种投资。

当然,要不是我们最终要去解决这些问题,这儿是不会提到这些问题的。不过我们首先想谈的却是另外一个问题。如果不是要把100个数加成一个数,会怎么样?如果只想用自动加法器把50对数字加成50个不同的结果又会怎么样?也许你希望有一个万能的机器来累加多对数字、10个数字或100个数字,并且希望所有的结果都可方便地使用。

前面提到的自动加法器在与锁存器相连接的一组灯泡上显示出其相加结果。对于把50对数字加成50个不同的和来说,这种方法并不好。你可能希望把结果存回RAM阵列中,然后,在方便的时候用RAM控制面板来检查结果。控制面板上有专门为此目的而设计的灯泡。

这意味着连接在锁存器上的灯泡可以去掉。不过,锁存器的输出端必须连接到RAM阵列的数据输入端上,以便于和可以写入到RAM中:

 

上图中省略了自动加法器的其余部分,特别是振荡器和清零开关,因为不再需要显著标出计数器和锁存器的清零和时钟输入来源。此外,既然我们已经充分利用了RAM的数据输入端,就需要有一种方法来控制RAM的写入信号。

我们不去考虑这个电路能否工作,而把重点放在需要解决的问题上。当前需要解决的问题是能配置一个自动加法器,它不会仅用来累加一串数字。我们希望能随心所欲地确定累加多少数字、在RAM中存储多少不同的结果以供日后检查。

例如,假设我们希望先把三个数字加在一起,然后把另两个数字加在一起,最后再把另外三个数加在一起。我们可能会将这些数字存储在从地址0000h开始的RAM阵列中,存储器的内容如下所示:

 

这是本书第16章所说明的内容。方格里是存储单元中的内容,存储器的每一个字节在一个方格中。方格的地址在方格左面,并非每一个地址都要表示出来,存储器的地址是连续的,因而可以算出某个方格的地址。方格的右侧是关于这个存储单元的注释,它们表示出我们希望自动加法器在这些空格中存储三个结果。(虽然这些方格是空的,但存储单元并非空的。存储单元中总有一些东西,即使只是随机数,但此时它不是有用的数。)

现在可以试一下十六进制算术运算并且把结果存到方格中,但这并不是此项试验的要点,我们想让自动加法器来做一些额外的工作。

不是让自动加法器只做一件事情—在最初的加法器中,只是把RAM地址中的内容加到称为累加器的8位锁存器中—实际上是让它做四件不同的事。要做加法,需先从存储器中传送一个字节到累加器中,这个操作叫作Load(装载)。第二项所要执行的操作是把存储器中的一个字节加(Add)到累加器中。第三项是从累加器中取出结果,保存(Store)到存储器中。最后,需要有一些方法使自动加法器停止(Halt)工作。

详细说来,让自动加法器所做的工作如下所示:

  • 把地址0000h中的数装载到累加器中

  • 把地址0001h中的数加到累加器中

  • 把地址0002h中的数加到累加器中

  • 把累加器中的数保存到地址0003h

  • 把地址0004h中的数装载到累加器中

  • 把地址0005h中的数加到累加器中

  • 把累加器中的数保存到地址0006h

  • 把地址0007h中的数装载到累加器中

  • 把地址0008h中的数加到累加器中

  • 把地址0009h中的数加到累加器中

  • 把累加器中的数保存到地址000Ah

  • 停止自动加法器的工作

注意,同最初的自动加法器一样,存储器的每个字节的地址是连续的,开始处为0000h。以前自动加法器只是简单地把存储器中相应地址的数加到累加器中。某些情况下,现在仍然需要这样做,但有时我们也想直接把存储器中的数装载到累加器中或者把累加器中的数保存到存储器中。在所有事情都完成以后,我们还想让自动加法器停下来以便检查RAM阵列中的内容。

怎样完成这些工作呢?只是简单地键入一组数到RAM中并期望自动加法器来正确操作是不可能的。对于RAM中的每个数字,我们还需要一个数字代码来表示自动加法器所要做的工作:装载,加,保存或停止。

也许最容易(但肯定不是最便宜)的方法是把这些代码存储在一个完全独立的RAM阵列中。这第二个RAM阵列与最初的RAM阵列同时被访问,但它存放的不是要加的数,而是用来表明自动加法器将要对最初的RAM阵列的相应地址进行某种操作的代码。这两个RAM阵列可以分别标为数据(最初的RAM阵列)和代码(新的RAM阵列):

已经确认新的自动加法器能够把“和”写入到最初的RAM阵列(标为数据),而要写入新的RAM阵列(标为代码)则只能通过控制面板来进行。

我们用4个代码来表示自动加法器希望能实现的4个操作。4个代码可任意指定,下面为可能的一组代码:

操作码           代码

Load(装载)     10h

Store(保存)      11h

Add(加)          20h

Halt(停止)       FFh

为了执行以上例子中提到的三组加法,需要用控制面板把下面这些数保存到代码RAM阵列中:

 

你可能想把这个RAM阵列中的内容与存放累加数据的RAM阵列的内容作一比较。你会发现代码RAM中的每个代码或者对应于数据RAM中一个要装入或加到累加器的数,或者表示一个要存回到存储器中的数。这样的数字代码通常称作指令码或操作码,它们“指示”电路执行某种“操作”。

前面谈到过,早期自动加法器的8位锁存器的输出需要作为数据RAM阵列的输入,这就是“保存”指令的功能。另外还需要一个改变:以前,8位加法器的输出是作为锁存器的输入,但现在为了实现“装载”指令,数据RAM的输出有时候也要作为8位锁存器的输入,这种改变需要2-1数据选择器。改进的自动加法器如下图:

上图中少了一些东西,但它显示了各种组件间的8位数据通路,一个16位计数器为2个RAM阵列提供地址。通常,数据RAM阵列输出到8位加法器上执行加法指令。8位锁存器的输入可能是数据RAM阵列的输出也可能是加法器的输出,这需要2-1选择器来选择。通常,锁存器的输出又流回到加法器,但对“保存”指令而言,它又作为数据RAM阵列的输入。

上图中缺少的是控制这些组件的信号,统称为控制信号。它们包括16位计数器的时钟(Clk)和清零(Clr)输入,8位锁存器的Clk和Clr输入,数据RAM阵列的写入(W)输入以及2-1选择器的选择(S)输入。其中有一些信号明显基于代码RAM阵列的输出,例如,若代码RAM阵列的输出表示装载指令,则2-1选择器的S输入必须为0(选择数据RAM阵列的输出)。仅当操作码为保存指令时,数据RAM阵列的W输入才为1。这些控制信号可以由逻辑门的各种组合来产生。

利用最小数量的附加硬件和新增的操作码,也能让这个电路从累加器中减去一个数。第1步是扩充操作码表:

操作码       代码

Load         10h

Store        11h

Add          20h

Subtract(减) 21h

Halt        FFh

加法和减法只通过操作码的最低有效位来区分。若操作码为21h,除了在数据RAM阵列的输出数据输入到加法器之前取反并且加法器的进位输入置1外,电路所做的几乎与电路执行加法指令所做的完全相同。在下面这个改进的有一个反相器的自动加法器里,C0信号可以完成这两项任务:

现在假设把56h和2Ah相加再减去38h,可以按下图所显示的存储在两个RAM阵列中的操作码和数据进行计算:

装载操作完成后,累加器中的数为56h。加法操作完成后,累加器中的数为56h加上2Ah的和,即80h。减法操作使数据RAM阵列的下一个数(38h)按位取反,变为C7h。加法器的进位输入置为1时,取反的数C7h与80h相加:

其结果为48h。(按十进制,86加42减56等于72。)

还有一个未找到适当解决方法的问题是加法器及连到其上的所有部件的宽度只有8位。以往唯一的解决方法就是连接两个8位加法器(或其他的两个部件),形成16位的设备。

但也有更便宜的解决方法。假设要加两个16位数,例如:

这种16位加法同单独加最右边的字节(通常称作低字节):

然后再加最左边的字节,即高字节

得到的结果一样,为99D7h。因此,如果像这样把两个16位数保存在存储器中:

结果中的D7h存在地址0002h中,99h存在地址0005h中。

当然,并非所有的数都这样计算,对上例中的数是这样计算。若两个16位数76ABh和236Ch相加会怎么样呢?在这种情况下,2个低字节数相加的结果将产生一个进位:

这个进位必须加到2个高字节数的和中:

最后的结果为9A17h

可以增强自动加法器的电路功能以正确进行16位数的加法吗?当然可以。需要做的就是保存低字节数相加结果的进位,然后把该进位作为高字节数相加的进位输入。如何存储1位呢?当然是用1位锁存器。这时,该锁存器称为进位锁存器。

为了使用进位锁存器,需要有另一个操作码,称作“进位加”(AddwithCarry)。在进行8位数加法运算时,使用的是常规“加法”指令。加法器的进位输入为0,加法器的进位输出锁存在进位锁存器中(尽管根本不必用到)。

在进行16位数加法运算时,仍然使用常规“加法”指令来进行低字节加法运算。加法器的进位输入为0,其进位输出锁存到进位锁存器中。要进行高字节加法运算就要使用新的“进位加”指令。这时,两个数相加使用进位锁存器的输出作为加法器的进位输入。如果低字节加法有进位,则其进位可用在第二次运算中;如果无进位,则进位锁存器的输出为0

如果进行16位数减法运算,还需要一个新指令,称为“借位减”(SubtractwithBorrow)。通常,减法操作需要使减数取反且把加法器的进位输入置为1。因为进位通常不是1,所以往往被忽略。在进行16位数减法运算时,进位输出应保存在进位锁存器中。高字节相减时,进位锁存器的结果应作为加法器的进位输入。

加上新的“进位加”和“借位减”操作,共有7个操作码:

操作码                         代码

Load                          10h

Store                         11h

Add                           20h

Subtract                      21h

AddwithCarry(进位加)          22h

SubtractwithBorrow(借位减)    23h

Halt                          FFh

在减法和借位减法运算中,需要把送往加法器的数取反。加法器的进位输出作为进位锁存器的输入。无论何时执行加法、减法、进位加法和借位减法操作,进位锁存器都被同步。当进行减法操作,或进位锁存器的数据输出为1并且执行进位加法或者借位减法指令时,8位加法器的进位输入被置为1

记住,只有上一次的加法或者进位加法指令产生进位输出时,进位加法操作才会使8位加法器的进位输入为1。任何时候进行多字节数加法运算时,不管是否必要,都应该用进位加法指令计算。为正确编码前面列出的16位加法,可用如下所示方法:

不管是什么样的数,该方法都能正确工作。

有了这两个新的操作码,极大地扩展了机器处理的范围,使其不再只局限于进行8位数加法。重复使用进位加法指令,能进行16位数、24位数、32位数、40位数等更多位数的加法运算。假设要把32位数7A892BCDh与65A872FFh相加,则需要一个加法指令及三个进位加法指令:

当然,把这些数存放到存储器中并非真的很好。这不仅要用开关来表示二进制数,而且数在存储器中的地址也并不连续。例如,7A892BCDh从最低有效字节开始,每个字节分别存入存储器地址0000h、0003h、0006h及0009h中。为了得到最终结果,还必须检查地址0002h、0005h、0008h及000Bh中的数。

此外,当前设计的自动加法器不允许在随后的计算中重复利用计算结果。假设要把3个8位数加起来,然后再在和中减去一个8位数,并且存储结果。这需要一次装载操作、两次加法操作、一次减法和一次保存操作。但如果想从原先的和中减去另外一个数会怎么样呢?那个和是不能访问的,每次用到它时都要重新计算。

原因在于我们已经建造了一个自动加法器,其中的代码RAM和数据RAM阵列同时、顺序地从0000h开始寻址。代码RAM中的每条指令对应于数据RAM中相同地址的存储单元。一旦“保存”指令使某个数据保存在数据RAM中,这个数就不能再被装载到累加器中。

为了解决这个问题,要对自动加法器做一个基本的及大的改变。虽说刚开始看上去会异常复杂,但很快你就会看到一扇通向灵活性的大门打开了。让我们开始吧,目前我们已经有了7个操作码:

操作码                    代码

Load                      10h

Store                     11h

Add                       20h

Subtract                  21h

AddwithCarry              22h

SubtractwithBorrow        23h

Halt                      FFh

每个操作码在存储器中占1个字节。除了“停止”代码外,现在希望每条指令在存储器中占3个字节,其中第一个字节为代码本身,后两个字节存放一个16位的存储器单元地址。对于装载指令来说,其地址指明数据在数据RAM阵列中的存储单元,该存储单元存放要装载到累加器中的字节;对于加法、减法、进位加法和借位减法指令来说,地址指明要从累加器中加上或者减去的字节的存储单元;对于保存指令来说,地址指明累加器中的内容将要保存的存储单元。

例如,当前自动加法器所能做的最简单的工作就是加两个数。要完成这项工作,可以按照下面的方法来设置代码RAM阵列和数据RAM阵列:

在改进的自动加法器中,每条指令(除了“停止”)需要3个字节:

每条指令(除了“停止”)后跟2个字节,用来表示在数据RAM阵列中的16位地址。这三个地址碰巧为0000h、0001h和0002h,它们可以是任何其他地址。

前面说明了如何使用加法和进位加法指令来相加一对16位数—比如76ABh和232Ch。必须把2个数的低字节保存在存储器单元0000h和0001h中,把2个高字节保存在0003h和0004h中,其相加结果保存在0002h和0005h中。

这样,我们可以用更合理的方式来保存两个加数及其结果,这可能会保存在以前从未用过的存储区域:

这6个存储单元不必像图中这样连在一起,它们可分散在整个64KB数据RAM阵列中的任何地方。为了把这些地址中的数相加,必须在代码RAM阵列中按如下所示设置指令:

可以看到保存在地址4001h和4003h中的两个低字节首先相加,并把结果保存在地址4005h中。两个高字节(在地址4000h和4002h中)利用进位加法进行相加,其结果保存在地址4004h中。如果去掉“停止”指令并向代码RAM中加入更多指令,随后的计算就可以简单地通过存储器地址来利用原先的数及它们的和。

实现这种设计的关键就是把代码RAM阵列中的数据输出到3个8位锁存器中,每个锁存器保存3字节指令的一个字节。第一个锁存器保存指令代码,第二个锁存器保存地址的高字节,第三个锁存器保存地址的低字节。第二和第三个锁存器的输出组成了数据RAM阵列的16位地址:

从存储器中取出指令的过程叫作取指令。在上述加法机中,每个指令长3个字节。因每次只能从存储器中取出一个字节,因此每次取指令需要3个时钟周期。此外,一个完整的指令周期需要四个时钟周期。所有这些变化使得控制信号变得更为复杂。

机器响应指令代码执行一系列操作称为执行指令,但这并不是说机器是有生命的东西,它也不是通过分析机器码来决定做什么。每一个机器码用唯一的方式触发各种控制信号,使机器产生各种操作。

注意,为了使上述加法机更为有用,我们已经放慢了它的速度。利用同样的振荡器,它进行数字加法运算的速度只是本章列出的第一个自动加法器的1/4。这符合一个叫作TANSTAAFL的工程原理,TANSTAAFL的意思是“世界上没有免费的午餐”。通常,机器在某一方面好一点儿,在另一些方面必然会差一些。

如果不用继电器来建造这样一个机器,电路的大部分显然只是两个64KBRAM阵列。确实,早就该省去这些组件,并且一开始就决定只用1KB的存储器。如果能保证存储的所有东西都在地址0000h~03FFh之间,那么用少于64kB的存储器也能很好地解决问题。

然而,你可能也不会太在意用到了两个RAM阵列。事实上,也确实不用。前面介绍过的两个RAM阵列—一个存储代码,一个存储数据—使得自动加法器的体系结构变得尽可能清晰、简单。但既然已经决定每条指令占3个字节—用第二和第三个字节来表示数据的存储地址—就不再需要有两个独立的RAM阵列,代码和数据可存储在同一个RAM阵列中。

为了实现这个目标,需要一个2-1选择器来确定如何寻址RAM阵列。通常,像前面一样,其地址来自16位计数器。RAM数据输出仍然连接到用来锁存指令代码及其2字节地址的三个锁存器上,但它们的16位地址是2-1选择器的第二个输入。在地址被锁存后,选择器允许被锁存的地址作为RAM阵列的地址输入:

我们已经取得了很大的进步。现在把指令和数据输入到一个RAM阵列中已成为可能。例如,下图显示出怎样把两个8位数相加再减去第三个数:

通常,指令开始于0000h,这是因为复位后计数器从0000h处开始访问RAM阵列。最后的停止指令存储在地址000Ch处。可以把这3个数及其运算结果保存在RAM阵列中的任何位置(除了开始的13个字节,因为这些存储单元已经被指令占用),因而我们选择从0010h处开始存储数据。

现在假设你需要再加两个数到结果中,你可以输入一些新的指令来替换你刚输入的所有指令,不过可能你并不想这样做。你也许更愿意在那些已有的指令末尾接着新的指令,但首先得用一个新的装载指令来替换地址000Ch中的停止指令。此外,还需要两个新的加法指令、一个保存指令和一个新的停止指令。唯一的问题在于有一些数据保存在地址0010h中,必须把这些数据移到更高的存储地址中,并且修改那些涉及到这些存储器地址的指令。

想一想,把代码和数据混放在一个RAM阵列中也许并不是一个迫切的问题,但可以肯定,这样的问题迟早会到来,因此必须解决它。在这种情况下,可能你更愿意做的就是从地址0020h处开始输入新指令,从地址0030h处开始输入新数据:

注意第一条装载指令指向存储单元0013h,即第一次运算结果存储的位置。

因此现在有开始于0000h的一些指令、开始于0010h的一些数据、开始于0020h的另外一些指令以及开始于0030h的另外一些数据。我们想让自动加法器从0000h处开始并执行所有的指令。

我们必须从000Ch处去掉停止指令,并用其他一些东西来替换它,但这样就足够了吗?问题在于无论用什么来替换停止指令都会被解释为一个指令字节,并且至此后存储器中每隔3个字节—在000Fh、0012h、0015h、0018h、001Bh和001Eh处,字节也会被解释为一个指令字节。如果其中一个正好是11h会怎样呢?这是一个保存指令。如果保存指令后的两个字节刚好指向地址0023h又会怎样呢?机器会把累加器的内容写入该地址中,但是该地址中已经包含有一些重要的东西。即使没有诸如此类的事情发生,加法器从存储器地址001Eh的下一个地址中取得的指令字节将在地址0021h中,而不是0020h中,而0020h却正好是下一个指令的真实所在。

我们是否都同意不把停止指令从000Ch处移走,而期待最佳方案呢?不过,我们可用一个叫作Jump(转移)的新指令替换它。现在把它加入到指令表中。

操作码                         代码

Load                           10h

Store                          11h

Add                            20h

Subtract                       21h

AddwithCarry                   22h

SubtractwithBorrow             23h

Jump(转移)                     30h

Halt                           FFh

通常,自动加法器顺序寻址RAM阵列。转移指令改变其寻址模式,而从RAM阵列的某个特定地址开始寻址。这样的命令有时也叫分支(branch)指令或者goto指令,即“转到另外一个地方”的意思。

在前面的例子中,可用转移指令来替换000Ch中的停止指令:

30h就是转移指令的代码,其下的16位地址表示自动加法器要读的下条指令的地址。因此,在前面的例子中,自动加法器仍从地址0000h处开始,执行一条装载、一条加法、一条减法和一条保存指令,然后执行转移指令,接着继续从0020h处执行一条装载、两条加法和一条保存指令,最后执行停止指令。

转移指令影响16位计数器。当自动加法器遇到转移指令时,计数器被强制输入紧随转移指令代码的新地址,这可以通过组成16位计数器的边沿触发的D型触发器的预置(Pre)和清零(Clr)输入来实现:

前面曾讲过,正常操作下,预置和清零输入都应该为0。但如果Pre=1,则Q=1;如果Clr=1,则Q=0

如果想装载一个新值(称作A,代表地址)到单个触发器中,可这样连线:

通常,置位信号为0。这时,触发器的预置端为0。除非复位信号为1,否则清零端也为0。这样触发器可以不通过置位信号就可以清零。当置位信号为1时,若A=1,则Pre=1且Clr=0;若A=0,则Pre=0且Clr=1。这意味着Q端的值设置为A端的值。

16位计数器的每一位都需要一个这样的触发器。一旦装载一个特定的值,计数器将从那个值开始继续计数。

然而,这些变化并不大。从RAM阵列中锁存的16位地址既可作为2-1选择器(它允许该地址作为RAM阵列的地址输入)的输入也可作为16位计数器的输入并由置位信号设置:

显而易见,只有当指令代码为30h且其后面的地址被锁存,我们才必须保证置位信号为1

转移指令当然很有用,但它并非和一条只有时跳转而并非时刻跳转的指令一样有用,这样的一个指令叫作条件转移。为了显示该命令如何有用,可提出这样一个问题:怎样才能让自动加法器完成两个8位数的相乘?例如,怎样才能得到像A7h乘以1Ch这样简单运算的结果?

很容易,不是吗?两个8位数相乘的结果是一个16位数。为了方便起见,乘法中的3个数都用16位数来表示。首要的工作是决定把乘数和乘积放在何处:

每个人都知道A7h和1Ch(即十进制的28)相乘的结果与A7h相加28次的结果相同。因此,在1004h和1005h处的16位数就是累加结果。下图显示的是把A7h加一次到那个位置的代码:

在这6条指令执行完后,存储单元1004h和1005h处的16位数等于A7h乘以1。因此,为了使这16位数等于A7h乘以1Ch,这6个指令必须重复执行27次。可以通过在地址0012h处接着输入27次这6个指令来实现;也可以在0012h处输入停止指令,然后按28次复位键来得到最终结果。

当然,这两个方案都不理想。它们需要你做某些事情(输入大批指令或者按复位键)的次数和乘数相当。当然你不愿意这样去进行16位数的乘法运算。

但是如果在0012h处输入转移指令会怎么样呢?这个指令使计数器从0000h重新开始计数:

这当然是一个技巧。第一次执行指令后,存储单元1004h和1005h处的16位数等于A7h乘1,然后转移指令使其返回到存储器顶部。第二次执行指令后,此16位数等于A7h乘2。终于,其

结果将等于A7h乘1Ch。不过这样的过程并不会停止,它将不断地运行、运行、运行。

我们想让转移指令做的是使循环过程只重复所需的次数,这就是条件转移,它实施起来并不困难。我们要做的第一件事情就是增加一个与进位锁存器类似的1位锁存器。因为只有8位加法器的输出全为0时它才锁存1,所以叫它零锁存器:

只有当或非门的8个输入全为0时,其输出才为1。同进位锁存器的时钟输入一样,只有当加法、减法、进位加法或借位减法指令运行时,零锁存器的时钟输入才锁存一个数,这个被锁存的数值叫作零标志位。注意它,是因为它似乎行为相反:如果加法器输出全为0,则零标志位为1;若加法器输出不全为0,则零标志位为0

利用进位锁存器和零锁存器,可以在指令表中再添加四条指令:

操作码                           代码

Load                            10h

Store                           11h

Add                             20h

Subtract                        21h

AddwithCarry                    22h

SubtractwithBorrow              23h

Jump                            30h

JumpIfZero(零转移)              31h

JumpIfCarry(进位转移)           32h

JumpIfNotZero(非零转移)         33h

JumpIfNotCarry(无进位转移)      34h

Halt                            FFh

例如,只有当零锁存器输出为0时,非零转移指令才转移到指定地址。换句话说,如果上一次加法、减法、进位加法和进位减法指令计算结果为0,则没有转移发生。实现这个设计只需在实现常规转移命令的控制信号上再加上一个控制信号:如果为非零转移指令,则只有当零标志位为0时,16位计数器的置位信号才被触发。

利用上述代码实现两个数的乘法所需的操作可由如下开始于地址0012h处的指令完成:

正如我们所设计的,循环一次后,位于1004h和1005h处的16位数等于A7h乘以1。上图中的这些指令把字节从1003h处装载到加法器中,此字节为1Ch。再把这个字节与001Eh处的数据相加,此处数据正好是停止指令,但当然也是有效数字。把FFh同1Ch相加与从1Ch减去1的结果相同,都等于1Bh。这个值不为0,所以零标志位为0,字节1Bh存回到地址1003h处。接下来是一条非零转移指令,零标志位没有置为1,所以转移发生。下一条指令位于地址0000h处。

记住,存储指令不会影响零标志位。零标志位只能被加法、减法、进位加法、借位减法指令所影响,因此它同这些指令中最近一个执行时所设置的值相同。

循环两次后,位于1004h和1005h处的16位数将等于A7h乘以2。而1Bh加上FFh等于1Ah,不是0,因此又返回到存储器顶部。

循环到第28次时,位于1004h和1005h处的16位数等于A7h乘以1Ch。位于1003h处的值等于1,它将加上FFh结果等于0,因此零标志位被置位。非零转移指令不再转移到存储器地址0000h处,相反,下一条指令为停止指令。至此,我们完成了全部工作。

现在可以肯定,很长一段时间以来我们已经装配了一组硬件,同时可以把它叫作计算机。当然,它只是一台原始的计算机,但它毕竟是一台计算机。它与我们以前设计的计算器的不同之处在于条件转移指令,控制重复或循环是计算机和计算器的区别。这里已经演示了条件转移指令是如何使得这台机器进行两个数的乘法运算的。用类似的方法,它也能计算两个数的除法。而且,还不局限于8位数。它能加、减、乘、除16位、24位、32位甚至更多位的数,而且如果它能实现这些操作,也就能计算平方根,对数和三角函数。

既然已装配了一台计算机,就可以开始使用一些计算机方面的词汇。我们装配的计算机归类为数字计算机,因为它采用的是离散值。曾经有过模拟计算机,但它们正逐渐消失。(数字数据是离散数据,是具体的确定的值;而模拟信息是连续的、在整个范围内变化的值。)

数字计算机有4个主要部分:处理器、存储器、至少一个输入设备和一个输出设备。上述机器中,存储器是一个64KB的RAM阵列。输入和输出设备分别是RAM阵列控制面板上的几行开关和灯泡。这些开关和灯泡使人们可以输入数据到存储器并检查结果。

处理器是计算机中除存储器、输入/输出设备以外的一切东西。处理器也叫中央处理器单元或CPU。再通俗一点儿,处理器有时也称作计算机的大脑。但尽量避免用这样的术语,这是因为在本章中我们所设计的东西根本不像大脑。(今天,微处理器这个词用得非常普及。微处理器只是一个很小的处理器,通过采用第18章将要讲到的技术而实现。但此刻我们用继电器所建造的东西则很难用“微”来定义。)

我们所建造的处理器是一个8位处理器。累加器宽度为8位,并且许多数据通路的宽度都

是8位,只有RAM阵列的地址的数据通路是16位的。如果用8位的地址通路,则存储器容量只能限于256字节而非65536字节,那样处理器则有太大的局限性。

处理器有一些组件。已经确定的一个是累加器,它是一个简单的锁存器,用来在处理器内部保存数据。我们所设计的计算机中,8位反向器和8位加法器一起称作算术逻辑单元或ALU。ALU只能进行算术运算,主要是加法和减法。在稍微复杂一点儿的计算机中(我们将会看到),ALU也可进行逻辑运算,如“与”、“或”、“异或”。16位计数器叫作程序计数器PC

我们的计算机是用继电器、电线、开关和灯泡建造的,所有这些都是硬件。与之对应,指令和输入存储器中的其他数据叫作软件,之所以叫“软件”是因为它们比硬件更容易改变。

当谈论计算机时,“软件”和“计算机程序”,更简单地讲“程序”是同义的,编写软件也称作计算机程序设计。当采用一系列计算机指令使计算机进行两个数的乘法时,我们所做的工作就是计算机程序设计。

通常,在计算机程序中,可以区分代码(即指令)和供代码使用的数据。有时这种区分并不明显,如停止指令还可作为数-1执行双重功能。

计算机程序设计有时也叫编写代码或编码。有时计算机程序员也叫编码员,尽管一些人可能认为这是一个贬义的名词。程序员更愿意被称作“软件工程师”。

处理器可以响应的操作码(如指装载和存储的10h和11h)叫作机器码,或机器语言。之所以用“语言”这个术语是因为机器码类似于可读/写的人类语言可被机器理解和响应。

我们要用很长的短语表示机器所执行的指令,如:进位加法(AddwithCarry)。通常,机器码都分配指定了用大写字母表示的短的助记符,这些助记符有2或3个字符。下面是一系列可能的上述计算机所能识别的机器码的助记符:

操作码                        代码               助记符

装载(Load)                  10h                LOD

保存(Store)                 11h                STO

加(Add)                     20h                ADD

减(Subtract)                21h                SUB

进位加(AddwithCarry)        22h                ADC

借位减(SubtractwithBorrow)  23h                SBB

转移(Jump)                  30h                JMP

零转移(JumpIfZero)          31h                JZ

进位转移(JumpIfCarry)       32h                JC

非零转移(JumpIfNotZero)     33h                JNZ

无进位转移(JumpIfNotCarry)  34h                JNC

停止(Halt)                  FFh               HLT

这些助记符特别适于和另外一对简洁短语结合使用。例如,不说像“把1003h处的值装载到累加器中”这样罗嗦的话,而是用下面语句来代替:

LODA,[1003h]

位于助记符LOD右边的A和[1003]叫作操作数,它们是特定的装载(Load)指令的操作对象。左边的操作数为目的操作数(A代表累加器),右边的为源操作数,方括号表示要装载到累加器中的值不是1003h,而是存储在存储器地址1003h中的值。

同样,指令“把001Eh处的字节加到累加器中”可简写为:

ADDA,[001Eh]

而“把累加器中的内容保存到地址1003h处”记作:

STO[1003h],A

注意,目的操作数(存储指令的存储单元)仍然在左边,源操作数在右边。累加器的内容存储在地址1003h处。指令“若零标志位不为1则转移到0000h处”可简洁地记作:

JNZ0000h

该指令中没有使用方括号,这是因为该指令是转移到地址0000h处而不是转移到地址0000h中保存的值所表示的位置处。

用缩写指令的形式来表示很方便,因为指令能以可读的方式连续列出来而不需画出存储器的分配图。为了表示某一指令存储在某一地址,可以用一个十六进制地址后加冒号来表示,如下所示:

0000h:LODA,[1005h]

下面表示了一些存储在某一地址的数据:

1000h:00h,A7h

1002h:00h,1Ch

1004h:00h,00h

用逗号隔开的两个字节表示第一个字节保存在左边的地址中,第二个字节保存在紧接着

该地址的下一个地址中。上述三行相当于:

1000h:00h,A7h,00h,1Ch,00h,00h

因此,整个乘法程序可写成如下一系列语句:

0000h:LODA,[1005h]

ADDA,[1001h]

STO[1005h],A

LODA,[1004h]

ADCA,[1000h]

STO[1004h],A

LODA,[1003h]

ADDA,[001Eh]

STO[1003h],A

JNZ0000h

001Eh:HLT

1000h:00h,A7h

1002h:00h,1Ch

1004h:00h,00h

使用空格和空行只是为了使程序具有更好的可读性,以方便人们阅读程序。

写代码时最好不要用真实的数字地址,因为它们是会变的。例如,如果要把数字存储到地址2000h~2005h处,需要重写许多语句。较好的方法是使用标号来指定存储单元,这些标号是简单的单词,或类似于单词的东西,如:

BEGIN:LODA,[RESULT+1]

ADDA,[NUM1+1]

STO[RESULT+1],A

LODA,[RESULT]

ADCA,[NUM1]

STO[RESULT],A

LODA,[NUM2+1]

ADDA,[NEG1]

STO[NUM2+1],A

JNZBEGIN

NEG1:HLT

NUM1:00h,A7h

NUM2:00h,1Ch

RESULT:00h,00h

注意,标号NUM1、NUM2和RESULT都表示保存两个字节的存储单元。在这些语句中,标号NUM1+1、NUM2+1和RESULT+1都指向特定标号后的第二个字节。注意,NEG1

(negativeone)用来标记HLT指令。

此外,为了不忘记这些语句的意思,可以加上一些注释,它们与语句之间用分号隔开:

BEGIN:LODA,[RESULT+1]

ADDA,[NUM1+1];Addlow-orderbyte(加低字节)

STO[RESULT+1],A

LODA,[RESULT]

ADCA,[NUM1];Addhigh-orderbyte(加高字节)

STO[RESULT],A

LODA,[NUM2+1]

ADDA,[NEG1];Decrementsecondnumber(第二个数减1

STO[NUM2+1],A

JNZBEGIN

NEG1:HLT

NUM1:00h,A7h

NUM2:00h,1Ch

RESULT:00h,00h

以上表示的是一种计算机程序设计语言,称作汇编语言。它是全数字的机器代码和指令描述性语言的综合,且存储器地址用符号表示。人们有时会把机器语言和汇编语言弄混淆,因为它们是表示同种事情的两种不同的方法。汇编语言的每条语句都对应于机器代码的特定字节。

如果你想为本章所创建的计算机编写程序,你可能首先想用汇编语言写出来(在纸上)。然后,在认为它正确并准备测试它时,可以对它进行手工汇编:这意味着用手工的方法把每一个汇编语句转换成机器代码,仍然写在纸上。接着,你可以用开关把机器码输入到RAM阵列并运行该程序,即让机器执行指令。

学习计算机程序设计的概念时,不可能很快就能正确知道程序的毛病所在。编写代码时—特别是用机器代码—很容易产生错误。输入一个错误的数字已经很不好了,但如果输入一条错误的指令会怎么样呢?本想输入10h(装载指令),但却输入了11h(保存指令),不但机器不会把期望的数据装载,而且该处的数据还会被累加器中的内容覆盖。

一些错误可以导致难以预料的结果。假设使用无条件转移指令转移到没有有效指令代码的位置,或者偶然使用存储指令覆盖了一些指令,任何事情都可能发生(经常如此)。

上述乘法程序中也有一些毛病。如果你执行它两次,则第二次将会是A7h乘以256,并且结果将加到原来计算的结果中。这是因为程序执行一次后,地址1003h处的值为0。当程序第二次执行时,FFh将加到那个值中,其结果不为0,程序将继续执行直到它为0。

我们已看到上述机器可以进行乘法运算,同样,它也可以进行除法运算。此外,它可利用这些基本功能进行平方根、对数和三角函数的计算。机器所需要的只是用来进行加法、减法的硬件及利用条件转移指令来执行适当代码的一些方法。正如一个程序员所说:“我可以用软件完成其余功能”

当然,软件可能相当复杂。许多书中都描述了一些算法供程序员解决专门的问题,本书还没准备这样做。我们一直在考虑自然数而没有考虑如何在计算机中表示十进制小数,我们将在第23章介绍它。

前面已说过几次,建造这些设备的所有硬件在100多年前就有了。但本章中出现的计算机在那时却没有建造出来。在20世纪30年代中期,最早的继电器计算机制造出来时,包含在设计中的许多概念还未形成,直到1945年左右人们才开始意识到。例如,直到那时候,人们仍然设法在计算机内部使用十进制数而不是二进制数;计算机程序也并非总是存储在存储器中,而是有时把它存在纸带上。特别是早期计算机的存储器非常昂贵且体积庞大。不管是在100年前还是在现在,用500万个电报继电器来建造64KB的RAM阵列都是荒唐的。

当我们展望和回顾计算器和计算装置的历史时,可能会发现根本没必要建造这样精致的继电器计算机。就像在第12章提到的,继电器最终会被真空管和晶体管这样的电子设备所取代。或许我们也会发现他人制造的相当于我们设计的处理器和存储器的东西能小到放在手掌中。

 

第十八章  从算盘到芯片

纵观历史,人类发明了很多灵巧的工具和机器以满足广泛的需求,从而使数学运算变得更容易了些。虽然人类天生就有使用数字的能力,但仍能经常需要帮助。人们常遇到一些自己不能轻易解决的问题。

数字可看成是早期帮助人类记录商品和财富的工具。许多文明,包括古希腊和美洲土著,都借用石子或谷物来计数。在欧洲使用计数板,而在中国则对由框和珠子组成的算盘较为熟悉:

没有人真的喜欢乘法和除法,但却有人为它做过什么,苏格兰数学家JoinNapier(1550-1617)就是这少数人中的一个。他发明了对数来简化这些操作,两数之积简化为它们对数的和。因此,如果你想使两数相乘,先在对数表中分别查出它们的值,然后相加,再用相反的方法查对数表就可得到它们的积。

对数表的建立,使得随后400年里一些最伟大的思想家一直为此忙碌,而另一些人却在设计使用小装置来代替对数表。一种有对数标尺的滑尺已有很长的历史了,它由EdmundGunter(1581-1626)发明并由WilliamOughtred(1574-1660)修正。1976年,当Keuffel&Esser公司将其公司最后制造的滑尺捐赠给华盛顿特区的Smithsonian学院时,滑尺的历史也就宣告结束了,其中的原因是手持计算器的出现。

Napier也发明了一种乘法辅助器,它由刻在骨头、号角、像牙上的数字条组成,因而这样的辅助器称为Napier骨架。1620年左右,WilhelmSchickard(1592-1635)制造出了最早的有点儿自动功能的由Napier骨架组成的机械计算器。几乎在同时出现了由互相连结的轮子、齿轮和水平仪组成的另外一种计算器,这种机械计算器的两个最主要的制造者是数学家和哲学家布莱兹·帕斯卡(1623-1662)和莱布尼兹(1646-1716)

你一定能记得最初的8位加法器和能自动进行多于8位数的加法计算的计算机中的进位是多么令人讨厌。进位原先似乎只是加法运算中的一个小问题,但在加法机中却成了一个中心问题。即使设计一个能进行除进位外的所有工作的加法机,也不能说工作就算完成了。

进位处理是否成功是评估老式计算机的关键。例如,帕斯卡设计的进位机制禁止减法运算。为了进行减法,必须加上9的补码,这在第13章中已经讲到。直到19世纪后期,才出现了真正可以为人们所使用的机械计算器。

一个奇特的发明对计算的历史产生了深远的影响,就像它对纺织所产生的深远影响一样,这就是约瑟夫·玛丽·杰奎德(1752-1834)所发明的自动织布机。杰奎德织布机(大约产生于1801年)使用上面已打孔的金属卡片(就像钢琴上的金属卡片)来控制编织物的图案。杰奎德的一大杰作就是用黑白丝线织成的自画像,为此使用了大约1万张卡片。

18世纪(甚至直到20世纪40年代),计算机就像一个以计算数字谋生的人。使用星星进行航海导航经常需要对数表,并且三角函数表也是必需的。如果需要发布新表,则需要许多计算机来工作,然后把结果汇总起来。当然,在这一过程的任何阶段,即从初始化计算到设置类型来打印最后几页都可能会出现错误。

从数学表中消除错误的愿望激发了查尔斯·巴贝芝(1791—1871)。巴贝芝是一位英国的数学家和经济学家,他和摩尔斯差不多是同一时代的人。

在那时,数学表(以对数表为例)并不是通过计算表中每一项确切的对数值而建立的,因为这得花费很多时间。取而代之的是选择一些数进行对数计算,而介于这些数中间的那些数则采用插补,即称作差分的方法,通过相对简单的计算来得到。

大约在1820年,巴贝芝认为可以设计并制造一台机器来自动建立表,甚至可以到自动设置打印类型这一步,这样可以消除错误。他构想了差分机,这是一个很大的机械加法机。通过切换,可使位于10个不同位置的轮子来表示各位数的十进制数字,负数用10的补码来计算。尽管一些早期的模型可以证明巴贝芝的设计是可行的,并且也从英国政府获得了一些支持,但差分机却从未完成过。巴贝芝于1833年放弃了这一工作。

然而,就在那个时候,巴贝芝又有了一个更好的构想,这就是解析机(重复的设计和再设计不断耗费着巴贝芝的生命,直到他死去),解析机是19世纪最接近计算机的发明。在巴贝芝的设计中,有一个存储系统(类似于今天存储器的概念)和运算器(算术单元)。乘法由重复加法来实现,除法由重复减法来实现。

解析机最精华的部分在于它可以用卡片来编程,这些卡片是由杰奎德的按图案编织的织布机上的卡片经过改造而制成的。正如艾达·奥古斯塔,即拉弗雷斯女伯爵(1815-1852)在她翻译的由一个意大利数学家写的,关于巴贝芝解析机的文章的按语里写的:“我们可以说解析机编织的是代数模型,正如杰奎德织布机编织的是花和叶一样”。

巴贝芝可能是第一个意识到计算机中条件转移的重要性的人。拉弗雷斯女伯爵关于此也曾写道:“操作循环必须理解成一批操作,这些操作可以重复多次。无论是只重复两次还是无穷次,都是一个循环,归根到底重复组成了循环的这些操作。许多情况下,还会出现一个或多个循环的重复,即循环的循环或多个循环的循环”。

尽管差分机最终由GeorgEdvardScheutz父子在1853年制成,但巴贝芝的机器那时已被遗忘了很久,直到20世纪30年代人们开始追寻20世纪计算机的根源时才再次想起。巴贝芝曾经做的东西已经被后来的技术所超越,除了他对自动化的超前认识外,他并没有为20世纪的计算机工程留下什么东西。计算机历史上另一个里程碑来源于美国宪法第二部分的第一篇。这一部分里除其他事情外还要求每10年进行一次人口普查。1880年人口普查的时候,人口信息按年龄、性别及祖籍来收集,数据的收集差不多花了七年的时间来进行。

由于担心1890年的人口普查可能需要超过10年的时间,人口普查局寻求使该系统工作自动化的可能性并选用了赫曼·霍勒瑞斯(1860—1929)开发的机器,此人是1880年人口普查的统计员。

霍勒瑞斯的想法是采用马尼拉穿孔卡片,大小是。(虽然霍勒瑞斯不可能知道巴贝芝如何使用卡片在他的解析机上编程,但他却很熟悉杰奎德织布机上卡片的使用。)卡片上的孔组成24列,每列12个,这样共有288个位置,这些位置表示某个人在人口普查记录中的某些特征。普查员通过在卡片的适当位置上打1/4英寸的方孔来标识这些特征。

本书可能使得人们习惯于用二进制码的概念来思考问题,因此,你可能马上会想到卡片上的288个穿孔点可以存储288位信息。但是,这些卡片并不是这样用的。

例如,在纯二进制系统中,人口普查卡片会有一个位置来表示性别,可以用打孔表示男性,未打孔表示女性(或者相反)。但是,霍勒瑞斯的卡片用两个位置表示性别,一个位置打孔表示男性,另一个位置打孔表示女性。同样,用两个穿孔表示年龄,一个穿孔指明一个5年的年龄范围:04591014等等,另一个孔用5个位置中的一个来表明在该范围的确切年龄。年龄编码需要卡片上总共28个位置。而纯二进制系统只需要7个位置就可编码0127的任何年龄。

我们应该原谅霍勒瑞斯在记录人口普查信息时没有采用二进制系统,对于1890年的人口普查员来说,把年龄转换成二进制数要求太高了一些。还有一个实际原因来解释穿孔卡片系统为什么不能全部是二进制的是因为二进制系统可能出现这样一种情形,即所有的孔都被打孔,使得卡片很脆弱,结构不牢固。

人口普查的数据收集可进行统计或制成表格。你可能想知道每一个区域内有多少人生活,当然,你也可能对人口的年龄分布统计信息感兴趣。正因为如此,霍勒瑞斯制造了一个制表机,它能结合手工操作和自动操作。操作员把一个有288个弹簧针的板子压到每一个卡片上,对应于卡片上每一个穿孔的针接触水银池形成电路,电路触发电磁铁使十进制计数器计数。

霍勒瑞斯在分类卡片的机器上也用了电磁铁。例如,如果需要统计所记录的每一个职业的年龄资料,首先需要按职业对卡片分类,然后对每一个职业统计年龄资料。分类机与制表机一样用手压,但分类机使用电磁铁打开一个开口,对应于26个分隔区域中的一个,操作员把卡片放入分隔区域,然后用手工关上开口。

这项实验在自动进行1890年的人口普查工作中取得了巨大成功,处理了超过6200万张的卡片,包含的数据是1880年人口普查的2倍,而数据处理只花了大约1880年人口普查所花时间的1/3。霍勒瑞斯和他的发明享誉全球。1895年,他甚至到了莫斯科并成功地卖出了他的设备,该设备在1897年第一次用于俄罗斯的人口普查。

霍勒瑞斯开始进行各种活动。1896年,他创立了制表机公司,出租和出售穿孔卡片设备。1911年,经过合并,该公司成为计算-制表-记录(ccomputing-Tabulating-Recording)公司,即C-T-R公司。到1915年,C-T-R的主席是ThomasJ.Watson(1874-1956),他在1924年把公司的名字改为国际商用机器公司,即IBM

1928年,原先的1890年的人口普查卡片已经演化成为著名的“不会卷曲、折叠’、翘页”的IBM卡片,有8012行。它们用了50多年,即使在最后几年也被称作霍勒瑞斯卡片。在第202124章将要讲到这些卡片的影响。

在把目光移到20世纪之前,不要对19世纪那个年代有太多的偏见。显然,本书主要着眼于数字系统的发明,这些发明包括电报、布莱叶盲文、巴贝芝机器和霍勒瑞斯卡片。当与数字概念和数字设备一起工作时,很容易会把整个世界都想像成数字世界。但是,19世纪的特征更多体现在那些不是数字的发明及发现上。的确,我们感受到的自然世界只有很小一部分是数字的,它更接近于是连续的而不那么容易被量化。

尽管霍勒瑞斯在他的卡片制表机和卡片分类机上用了继电器,但是直到20世纪30年代中期,人们才真正开始用继电器来制造计算机(后来叫机电式计算机)。这些机器上用的继电器通常不是电报继电器,而是那些用来在电话系统中控制呼叫路由的继电器。

早期的继电器计算机并不像我们在上一章中制造的继电器计算机(将会看到,后者计算机的设计基础是基于从20世纪70年代开始的微处理器)。特别地,今天对我们来说计算机内采用二进制数是显然的,但那时并不是这样。

我们所设计的继电器计算机与早期的继电器计算机之间的另一个区别是在20世纪30年代,没有人会狂热到用继电器构造524288位的存储器!所需的造价、空间及功耗使得这样大的存储器不可能实现。可用的很小的存储器只是用来存储中间结果,而程序本身存储在像带有穿孔的纸带这样的物理媒体上。的确,把代码和数据放入存储器的处理方式是一个很现代化的概念。

按年代排列,第一个继电器计算机似乎是由ConradZuse1910-1995)建造的。1935年当他还是一个工程系的学生的时候,他就开始在他父母位于柏林的住所里制造计算机。他采用的是二进制数,但却是早期版本,且用的是机械存储器而不是继电器。Zuse在老式的35毫米的电影胶片上穿孔来进行计算机编程。

1937年,贝尔电话实验室的GeorgeStibitz1904-1995)把一对电话继电器安装在家里,并且又连接了一个1位加法器到餐桌上,后来他的夫人称它为“K机器”(K表示kitchen,厨房)。该实验导致在1939年产生了贝尔实验室的复数计算机。

与此同时,哈佛大学的研究生HowardAiken1900-1973)因需要某种方法来做大量重复计算,从而使得哈佛大学和IBM合作,制造出了最终称为HarvardarkI的自动顺序控制计算机(ASCCautomatedsequencecontrolledcalculator),此项工作在1943年完成。这是第一台打制表格的数字计算机,它终于实现了巴贝芝的梦想。MarkII是以巨大的继电器为基础的机器,使用了13000个继电器。由Aiken领导的哈佛计算实验室讲授了计算机科学的第1课。

继电器并不是制造计算机的最好器件,因为它是机械的,工作时需弯曲一个金属簧片,如果超负荷工作,簧片就会折断;如果有一小片污垢或纸片粘在触点之间,继电器就会失效。一个著名的事件发生在1947年,从HarvardMarkII计算机的一个继电器中找到一只蛾子。GraceMurryHopper1906—19921944年加入Aiken的小组,此人后来在计算机程序设计语言领域非常有名。他在计算机日志中记录了这只蛾子,写道“第一次发现了真正的bug”。

继电器的一种可能的替代品是真空管,真空管由JohnAmbroseFleming1849—1945)和LeedeForest1873—1961)发明用来同无线电设备连接。到20世纪40年代,真空管早已用来放大电话信号。事实上,每一家的落地式收音机都装上了用来放大无线电信号的真空管,以便人们能听见。真空管可以连接成与、或、与非和非门,这一点非常像继电器。

逻辑门是由继电器还是由真空管来制造的并不重要。利用逻辑门可集成加法器、选择器、译码器、触发器和计数器。前面几章讲的基于继电器的器件在当继电器被换成真空管时仍然可用。

不过真空管也有问题,它们昂贵、耗电量大、散发的热量多。然而最大的问题在于它们最终会被烧毁,这也就是它们的寿命问题。有真空管收音机的人就习惯于隔一段时间更换这些管子。电话系统设计成有许多多余的管子,因此损失点儿管子也不是大的问题。(没有人能指望电话系统不出一点儿问题。)然而计算机中的一个管子烧毁以后,并不能很快被检测到,而且,计算机中使用了如此多的真空管,可能每几分钟就会烧毁一个。

使用真空管相对于继电器的最大好处在于它每百万分之一秒(即1微秒)就可以跳变一次。真空管改变状态(开关闭合或断开)的速度比继电器快1000倍,在最好的情况下,继电器状态的变化大约需1毫秒,即千分之一秒。有趣的是,在早期计算机的研究中,速度问题并不是最重要的,这是因为早期计算机总的计算速度与机器从纸带或电影胶片中读取程序的速度密切相关。正是因为计算机是基于这种方式制造的,真空管比继电器速度快多少也就无关紧要了。

20世纪40年代初,真空管开始在新的计算机中替换继电器。直到1945年,晶体管制成。正如继电器机器称为机电式计算机,真空管则是第一台电子计算机的基础。

在英国,Colossus计算机(1943年开始使用)用于破译德国的“Enigma”代码生成器生成的密码。为这个项目(和英国以后的一些计算机项目)做出贡献的人是艾伦·M·图灵(1912—1954),他当时由于写了两篇很有影响的论文而闻名于世。第一篇论文发表于1937年,其中首先提出了“计算能力”的概念,用以分析计算机可以做到和不能做到的事。他构思出了现在称为图灵机的计算机抽象模型。图灵写的第二篇著名论文的主题是人工智能,他介绍了一个测试机器智能的方法,现在称作图灵测试法。

在摩尔电气工程学校,J.PresperEckert(1919-1995)JohnMauchly(1907—1980)设计了ENIACelectronicnumericalintegratorandcomputer,电子数字积分器和计算机)。它采用了18000个真空管,于1945年末完成。纯粹按吨位(大约30吨)计算,ENIAC是曾经制造出来的(也许以后也是)最大的计算机。到1977年,你可以在电器行买到更快的计算机。然而,EckertMauchly的专利却被JohnV.Atanasoff(1903—1995)给阻挠了。Atanansoff在早期曾设计了一个电子计算机,但它从未很好地工作过。

ENIAC引起了数学家约翰·冯·诺依曼(1903—1957)的兴趣。从1930年开始,匈牙利出生的冯·诺依曼就一直住在美国。他是一个令人瞩目的人物,因能在脑子里构思复杂的算法而享有很高的声誉,他是普林斯顿高级研究学院的一名数学教授,研究范围很广,从量子理论到游戏理论的应用再到经济学。

冯·诺依曼帮助设计了ENIAC的后继产品EDVACelectronicdiscretevariableautomaticcomputer)。特别是在1946年与ArthurW.BurksHermanH.Goldstine合写的论文《PreliminaryDiscussionofthelogicalDesignofanElectronicComputinginstrumert》中,他描述了有关计算机的几点功能,这些功能使得EDVACENIAC更先进。EDVAC的设计者感觉到在计算机内部应该使用二进制数,而ENIAC用的是十进制数;计算机应该具有尽可能大的存储器,当程序执行时,这个存储器可用来存储程序代码和数据;(ENIAC中的情况不是这样,对ENIAC进行编程是通过断开开关和插上电缆来进行的。)指令应该在存储器中顺序存放并用程序计数器来寻址,但也应该允许条件转移。这种设计思想叫作存储程序概念。

这种设计思想是重要的革命化的一步,今天称为冯·诺依曼体系结构,上一章建造的计算机就是典型的冯·诺依曼机器。但冯·诺依曼体系结构也带来冯·诺依曼瓶颈,冯·诺依曼型机器需要花费大量的时间从存储器中取出指令来准备执行。应该还记得第17章最后设计的计算机取指令的时间占整个指令周期的3/4

EDVAC时代,用真空管构建存储器是不值得的,因而人们使用一些古怪的方法来解决这个问题。一个成功的方法就是水银延迟线存储器,它使用5英尺长的水银管子。在管子的一端,每隔1微秒向水银发一个小脉冲。这些脉冲需要1毫秒的时间到达管子的另一端(此时,它们像声波一样会被检测到,然后送回到开始的地方),因此每个水银管可存储大约1024位信息。

直到20世纪50年代中期,磁芯存储器才开发出来。这种存储器由大量的环绕着电线的电磁金属环组成,每个小环保存1位信息。在磁芯存储器被其他技术取代后的相当一段时期内,还经常听到老程序员把由处理器访问的存储器称作磁芯。

20世纪40年代,冯·诺依曼并不是唯一一个对计算机的本质进行概念上思考的人。

克劳德·香农(1916年出生)是另外一个有着重大影响的思想家。第11章曾经提到他1938年的硕士论文,论文中确立了开关、继电器和布尔代数之间的关系。1948年,当他在贝尔电话实验室工作时,他在《BellSystemTechnicalJournal》上发表了一篇题为《AMathematicalTheoryofCommunication》的论文,其中不仅引入了“位”的概念,而且确立了一个现代称为“信息理论”的研究领域。信息理论涉及在噪声(经常阻碍信息传送)存在的情况下传送数字信息以及如何进行信息补偿等问题。1949年,他写了第1篇关于编写让计算机下棋的程序的文章;1952年他设计了通过继电器控制的机械老鼠,这个老鼠可以在迷宫中记住路径。香农同时也因为他会骑独轮车,玩变戏法而在贝尔实验室很出名。

NorbertWiener(1894-1964)18岁时就在哈佛大学取得了数学博士学位,因《Cybernetics,orControlandCommunicationintheAnimalandMacbine》(1948)一书而闻名于世。他首次使用控制论(Cybernetics)这个词来表示一种把人及动物的生物活动与计算机及机器人的机理联系起来的理论。在现代文化里,广泛使用cyber-前缀表示与计算机相关的东西。更特别的是,成千上万的计算机通过因特网进行的互连称作cyberspace(信息空间),这个词来自科幻小说作家WilliamGibson1984年的小说《Neuromancer》中的词cyberpunk

1948年,Eckert-Mauchly计算机公司(RemingtonRand公司的后继者)开始开发第一台商用计算机UNIVAC(universalautomaticcomputer),并于1951年完成。第一台被送往人口普查局。UNIVAC的首次网络应用是用于CBS,用来预测1952年的总统选举结果。WalterCronkite称它为“电脑”。同样是在1952年,IBM发布了它的第一个商用计算机系统,即701

从此,开始了社团和政府使用计算机的漫长历史。然而,之所以对这段历史感兴趣可能是因为我们要追踪另一段历史轨迹—即降低计算机造价和大小并且使它进入家庭的轨迹,它开始于1947年一场几乎不被人注意的电子技术突破。

贝尔电话实验室许多年里都是这样一个地方:聪明的人可以在此做他感兴趣的任何事。所幸的是,他们之中有人对计算机感兴趣,如已经提到的GeorgeStibitzClaudeShannon,他们在贝尔实验室工作的时候都为早期的计算机作出了突出的贡献。后来,在20世纪70年代,贝尔实验室诞生了很有影响的操作系统UNIX和程序设计语言C语言,这些在随后的几章里将要讲到。

192511日,美国电话电报公司正式把它的科学和技术研究部分与商业部分分离,另外建立附属机构,这样贝尔实验室诞生了。贝尔实验室的主要目的是为了研究能够提高电话系统性能的技术。幸运的是,它的要求非常含糊,可以包含所有的事情。但在电话系统中,一个明确的长期的目标是:在线路上传输的声音信号能不失真地放大。

1912年起,贝尔电话系统就采用了真空管放大器,大量的研究和工程人员着手提高电话系统使用的真空管的性能。尽管这样,真空管仍然有许多问题。管子体积大,功耗大且最终会烧毁。不过,在当时却是唯一的选择。

19471216日,当贝尔实验室的两个物理学家JohnBardeen(1908—1991)WalterBrattain(1902—1987)在装配一个不同类型的放大器时,所有的一切都改变了。这种新型放大器由锗片—一种称作半导体的元素—和一条金箔构成。一个星期后,他们给他们的上司WilliamShockley1910—1989)进行了演示。这就是第一个晶体管,一种被人们称为20世纪最伟大的发明的器件。

晶体管不是凭空产生的。8年前,即19391229日,Shockley在笔记本写下:“今天我想用半导体而不是真空管做放大器在原理上是可能的。”第一个晶体管被发明以后,随后许多年它继续被完善。1956年,ShockleyBardeenBrattain获得诺贝尔物理学奖—“因为他们在半导体上的研究并且发明了晶体管。”

本书的前面谈到了导体和绝缘体。之所以称作导体是因为它们非常容易导电,铜、银和金都是很好的导体。并非巧合,所有这三种元素都在元素周期表的同一列。

前面讲过,原子中的电子分布围绕在原子核的外层。上述三种导体的特征是只有一个单独的电子在最外层。这个电子很容易与原子的其余部分分离并自由移动形成电流。与导体相对的是绝缘体,像橡皮和塑料,它们几乎不能导电。

锗和硅元素(还有一些化合物)称为半导体,并不是因为它们的导电性是导体的一半,而是因为它们的导电性可以用多种方法来控制。半导体最外层有4个电子,是最外层所能容纳电子最大数目的一半。在纯半导体中,原子彼此非常稳固地结合在一起,具有与金刚石相似的晶状结构。这种半导体不是好的导体。

但是半导体可以掺杂,意思是与某种杂质相混合。半导体很容易与其他杂质结合而变得不纯。有一类杂质为原子的结合提供额外的电子,这种半导体叫N型半导体(N表示negative);另一种类型的杂质掺杂生成P型半导体。

两个N型半导体中夹一个P型半导体可制成放大器,称作NPN晶体管,对应的三部分分别是集电极(Collector)、基极(Base)和发射极(Emitter)

下面是一个NPN晶体管的示意图:

基极上的一个小电压能控制一个很大的电压经过集电极到发射极。若基极上没有电压,它会有效截止晶体管。

晶体管通常被封装在一个直径大约为1/4英寸的小金属容器里,有三个引脚伸出:

晶体管首创了固态电子器件,意思是晶体管不需要真空,可由固体做成,特别是指由半导体和当今最普遍的硅制成。除了比真空管体积更小外,晶体管功耗小,发热少,并且更耐用。携带一个电子管收音机很不方便。晶体管收音机靠小电池供电,并且不像电子管,它不会变热。对于1954年圣诞节早晨收到礼物的幸运者来说携带晶体管收音机已成为可能。那些第一批袖珍晶体管收音机是由德克萨斯仪器公司制造的,该公司是半导体革命中的一个重要公司。

然而,晶体管的第一个商业应用是助听器。为了纪念贝尔一生为聋人的贡献,ATT公司允许助听器生产厂家不用付任何专利权税就可使用晶体管技术。第一台晶体管电视机出现于1960年,而今天,电子管器件几乎已消失了。(然而,并未完全消失,一些高保真发烧友及电子吉他手还是喜欢用电子管放大器来放大声音而不是用晶体管。)

1956年,Shockley离开贝尔实验室成立Shockley半导体实验室。他迁到加利福尼亚的PaloAlto—他的成长之地。他的公司是那里成立的第一家从事这种工作的公司。后来,其他半导体及计算机公司也在那儿建立了业务,于是旧金山南部的这个地方也就成为现在非正式地称作硅谷(SiliconValley)的地方。

真空管原是为放大信号而开发的,但它们也用来作为逻辑门的开关。晶体管也是如此。下面你将会看到一个由晶体管组成的与门在构造上很像用继电器组成的与门。只有当A输入为1B输入为1时,两个晶体管才都导通,输出才为1。这里所示的电阻用来防止短路。

正如你所看到的,下图连接了两组晶体管,右边一个是或门,左边一个是与门。在与门电路里,上面管子的发射极连接到下面管子的集电极。在或门电路里,两个晶体管的集电极都连到电源,发射极连到一起:

因此,我们所学的有关由继电器构造逻辑门和其他部件的知识也适用于晶体管。继电器、电子管和晶体管最初主要用来放大信号,但也可用同样的方法连接成逻辑门来建造计算机。第一台晶体管计算机制造于1956年,短短几年内,新计算机的设计中就放弃使用电子管了。

这里有一个问题:晶体管当然使计算机更可靠,更小和更省电,但晶体管能使计算机的制造更简单吗?

并非如此。虽然晶体管使得在一个小空间里能安装更多的逻辑门,但你还得担心这些组件的互连。连接晶体管形成逻辑门像连接继电器和真空电子管一样困难,某种程度上,它甚至更困难,因为晶体管较小,不容易掌握。如果想用晶体管建造第17章所建造的计算机及64KBRAM阵列,设计工作中的一部分将是设法发明某种能容纳所有组件的结构。这些劳动是乏味的,需要在数百万晶体管之间建立起数百万连接。

然而,就像我们所发现的,一些晶体管的连接会重复出现。许多对晶体管几乎都是先连接成门,门再连接成触发器、加法器、选择器或译码器,触发器再组成多位锁存器或RAM阵列。如果晶体管事先能连接成通用的结构,那么组装一台计算机就容易多了。

这种思想应该首先由英国物理学家GeoffreyDummer(1909出生)19525月的一次演讲中提出,他说:

“我想预测一下未来。随着晶体管的出现及半导体的日益广泛应用,现在似乎可以设想电子设备在一个固体块里而无需接线。这个块由隔离、传导、整理、放大四个层组成,电子功能通过各层的隔离区域直接连接起来。”

然而,真正可用的产品还不得不再等几年。

在对Dummer的预言毫不知情的情况下,19587月,德克萨斯仪器公司的JackKilby(生于1923年)想到了在一个硅片上造出许多晶体管、电阻及其他电子组件的方法。6个月以后,即19591月,RobertNoyce1927—1990)也找到了基本上相同的方法。Noyce原先曾为Shockley半导体实验室工作过,但在1957年,他和其他7位科学家离开实验室并成立了Fairchild(仙童)半导体公司。在技术史上,同时产生的发明很普遍,可能超出了你的想像。尽管KilbyNoyce6个月发明了这项技术,并且德克萨斯仪器公司比仙童公司也早申请专利,但Noyce还是首先获得了专利。法律上的争斗随之而来,但仅过了10年,问题就得到了令他们都满意的解决。尽管他们从未在一起工作过,但KildyNoyce被认为是集成电路,或称IC,更普遍的是叫芯片的共同发明者。

集成电路要经过复杂的工序才能生产出来,这些工序包括把很薄的硅晶片分层,精确地上涂料,和在不同的区域蚀刻形成微部件。尽管开发一种新的集成电路很昂贵,但收益来自于它的大量生产—生产得越多,就越便宜。

实际的硅晶片薄且很脆弱,为了保护芯片并提供某种方法使芯片中的部件与别的芯片连接,芯片必须安全地封装。集成电路的封装有几种不同的方式,但通常多采用矩形塑料双排直插式封装(或dualinlinepackageDIP),有141640个管脚从旁边伸出:

这是一个16管脚的芯片。如果你拿着芯片,并使芯片上的凹陷朝左时(如图),从芯片左下角起环绕到右边至末端,管脚依次编号为116,第16管脚在左上角。每一边管脚之间相距刚好为1/10英寸。

20世纪60年代,空间项目和军备竞赛刺激了早期的集成电路市场。在民用方面,第一个包含有集成电路的商业产品是1964年由Zenith出售的助听器。1971年,德克萨斯仪器公司开始出售第一批袖珍计算器,同时Pulsar出售了第一块数字表(显然数字手表中集成电路的封装完全不同于刚才图示的例子)。随后出现了其他很多种包含有集成电路的产品。

1965年,戈登·E·摩尔(当时在仙童公司,后来是Intel公司的合伙创始人)注意到技术以这样一种方式在发展:1959年后,可以集成到一块芯片上的晶体管的数目每年都翻一番。他预测这种趋势将继续下去。事实上后来这种趋势放慢了些,因此摩尔定律(最终这样命名)修改为预计每18个月在一个芯片上集成的晶体管数量将增长一倍。这仍是一个令人惊异的增长速度,这也就解释了为什么家用电脑只用了短短几年好像就已过时了。一些人认为摩尔定律将继续适用到2015年。

早期,人们习惯于说小规模集成电路,即SSIsmall-scaleintegration),指那些少于10个逻辑门的芯片;中规模集成电路,即MSI10100个门);大规模集成电路,即LSI1005000个门)。随后的术语为超大规模集成电路,即VLSI500050000个门);极大规模集成电路,即SLSI50000100000个门);特大规模集成电路,即超过100000个门。

本章的其余部分和下一章将把时钟停留在20世纪70年代中期,即第一部《星球大战》发行前的年代,那时,VLSI刚刚出现,且好几种技术用来制作构成集成电路的组件,每一种技术有时被称为一个IC家族。到70年代中期,两个“家族”流行开来:TTLCMOS

TTL代表晶体管-晶体管逻辑。70年代中期,如果你是一位数字电路设计工程师(即用IC来设计大的电路),一本1.25英寸厚的、德克萨斯仪器公司于1973年出版的名为《TheTTLDataBookforDesignEngineer(TTL工程师设计数据手册)的书(以下简称《TTL数据手册》)就会是你书桌上的常客。这是一本关于德克萨斯仪器公司和其他公司出售的TTL集成电路7400系列的完整的参考书,之所以这样称呼是因为该家族的每个集成电路都以开头是74的数字标识。

7400系列中每一个集成电路由许多预先按特定方式连接的逻辑门组成。一些具备简单的预先连接的逻辑门的芯片可以用来建造更大的组件。还有一些芯片提供通用组件,如:触发器、加法器、选择器和译码器。

7400系列中第一个集成电路是号码7400本身,它在《TTL数据手册》里描述为:“四个双输入正与非门”。这意味着这个特定的集成电路包含四个2输入与非门。它们之所以称为“正”与非门是因为有一个电压值与逻辑1对应,没有电压值与逻辑0对应。这个集成电路是一个14管脚的芯片,数据手册中的一个小图展示了管脚对应的输入和输出:

该图是芯片的俯视图(管脚在下面),小的凹陷在左边。

管脚14标为VCC,等同于V符号,它表明是电压(顺便说一下,大写字母V的双下标表明是一个电源,下标中的C是指晶体管的集电极输入,它在内部连接到电源)。管脚7标为GND,表示接地。在特定电路中所使用的任何集成电路都必须接电源和公共地。

对于TTL7400系列,VCC必须在4.755.25V之间,换句话说,电源电压必须在5V±5%的范围内。若电压低于4.75V,芯片就无法工作;若超过5.25V,芯片则会被烧坏。通常TTL器件不能使用电池供电,即使你刚好有一个5V的电池,电压也不可能刚好适合于芯片。TTL通常需要从墙上接入电源。

7400芯片中每一个与非门有两个输入和一个输出,它们独立工作。前面曾把输入用1(表明有电压)和0(表明无电压)加以区分,实事上与非门的输入电压可在0(地)~5VVCC)之间变化。TTL中,00.8V之间的电压视为逻辑“0”,25V之间的电压视为逻辑“1”,应当避免出现0.82V之间的输入电压。

TTL的典型输出是0.2V代表逻辑“0”,3.4V代表逻辑“1”。由于电压可能会有点儿变化,集成电路的输入/输出有时称作“高”或“低”,而不是“1”或“0”。此外,低电平也可以表示逻辑“1”,高电平也可以表示逻辑“0”,这种说法称为负逻辑。当7400芯片称作“四个双输入正与非门”时,“正”就表示上面讲到的正逻辑。

如果TTL的典型输出是0.2V代表逻辑“0”,3.4V代表逻辑“1”,则输出确实是在输入范围内,即逻辑“0”在00.8V、逻辑“1”在25V之间,这也是TTL能隔离噪声的原因。一个“1”输出即使下降1.4V后,电压仍可以高到作为“1”电平输入;一个“0”输出升高0.6V后,电压仍可以低到作为“0”电平输入。

了解一个集成电路最重要的事实可能是它的延迟时间,这指的是输入变化反应到输出所花费的时间。

芯片的延迟通常以纳秒来计算,纳秒缩写为ns1纳秒非常短暂,千分之一秒为1毫秒,百万分之一秒为1微秒,十亿分之一秒才是1纳秒。7400芯片与非门的延迟时间要保证少于22纳秒,也就是0.000000022秒,或十亿分之22秒。

不能感觉纳秒的长短不仅仅是你一人,地球上任何人除了对纳秒有智力上的理解之外就没有什么了。纳秒比人所经历的任何事物要短暂得多,所以它们永远也不会被理解,任何解释都只会使纳秒变得更难以捉摸。例如,你拿着一本书离你的脸有一英尺远,那么1纳秒就是光从书到你的眼所用的时间。现在,你能说你对纳秒有了更好的认识?

然而,纳秒在计算机中出现是可能的。正如在第17章所见,计算机处理器很笨拙地做着简单的事情—从存储器中取一个字节到寄存器,把一个字节同另一个字节相加,再把结果存回存储器。计算机(不是第17章中的计算机,而是现在使用的计算机)能做大量事情的唯一原因就是那些操作能迅速执行。引用RobertNoyce的话:“当你理解了纳秒之后,在概念上计算机操作就相当简单了。”

我们继续细读《TTL工程师设计数据手册》,就会在书中看到许多熟悉的小条目。7402芯片有四个双输入或非门,7404有六个反相器,7408有四个双输入与门,7432有四个双输入或门,7430有一个8输入与非门:

图中缩写NC表示该引脚没有连接到内部电路。

7474芯片是听起来很熟悉的一个芯片,它是“带预置和清零的双D型正边沿触发器”。如下图示:

TTL数据手册甚至还包含这个芯片的每个触发器的逻辑图:

你会发现这与第14章结尾处的图很相似,只是第14章的图中使用的是或非门。《TTL数据手册》中的逻辑表也有一点点不同:

上表中,“H”代表高电平,“L”代表低电平。你也可以把它们想成是10。在上述触发器里,预置与清零输入通常为“0”,在这里它们为“1”。

继续翻阅《TTL数据手册》,你会发现7483芯片是一个4位二进制全加器,74151是一个8-1数据选择器,741544-16译码器,74161是同步4位二进制计数器,74175是四个带清除功能的D型触发器。你可以选择这些芯片中的两个做一个8位锁存器。

所以现在你该明白从第11章起使用的各种各样的组件是如何来的了,它们都是从《TTL工程师设计数据手册》中得来的。

作为一名数字电路设计工程师,需要花费大量的时间去通读《TTL数据手册》,了解要使用的TTL芯片的类型。一旦掌握了你所需要的工具,你就可以用TTL芯片实际组装第17章所示例的计算机。把芯片连接起来要比连接晶体管容易得多,然而你可能不想用TTL组成64KBRAM阵列。在1973年的《TTL数据手册》中,所列最大容量的RAM芯片才256×1位,需要2048个这种芯片才能组成64KBRAMTTL远不是组织存储器的最好技术,第21章将要更多地谈到关于存储器的事情。

你可能也想用好一些的振荡器。可以将TTL反相器的输出端连到输入端,但使用一个事先可预测频率的振荡器要更好一些。构造一个这样的振荡器很容易,就是使用一个石英晶体,在一块小片上引出两条线。这些晶体在特定的频率下产生振荡,通常每秒至少100万周。每秒一百万周称为兆赫,缩写为MHz。如果第17章所示的计算机是用TTL构造的话,那么它在10MHz的时钟频率下可能会运行地很好。每一条指令需要400纳秒的执行时间。当然,这已经比用继电器工作时所能想像的要快多了。

另一个流行的芯片家族是(现在仍然是)CMOS,它代表由金属氧化物填充的半导体。如果你是70年代中期用CMOS集成电路进行电路设计的业余爱好者,你可能会使用一本由NationalSemicondactor(国家半导体公司)出版的参考书,它在你所在地方的电器行就能见到,书名为《CMOSDatabook》。此书包含了CMOS集成电路4000系列的信息。

TTL的电源要求是在4.755.25伏之间,但CMOS则可以是318伏之间的任何数值,范围多么大呀!此外,CMOSTTL功耗要小,这就可以使用电池来驱动小型CMOS电路。CMOS的缺陷是速度慢。例如,CMOS40084位全加器在5伏电压下只能保证750纳秒的延迟。当电源电压升高时,速度加快—10伏时,延迟为250纳秒;15伏时,延迟为190纳秒。但是CMOS设备不能接近于TTL4位加法器,TTL4位加法器的延迟为24纳秒。(25年前,在TTL的速度和CMOS的低功耗之间的权衡是很清楚的,今天,也有低功耗的TTL和高速率的CMOS。)

实践上,你可以在塑料面包板上连接这些芯片进行实验:

每一个有5个孔的短行在塑料板下是电导通的。你把芯片插在面包板上,并使芯片跨在中间的长槽上,管脚插入槽两边的孔中。集成电路的每一个管脚都与其他4个孔电连接。你可以将线插入其余孔中来连接芯片。

你也可以使用一种称为线缠绕的技术使芯片的连接更加牢固。每一个芯片插入带有长方形柱子的插座上:

每一个柱子对应于芯片的一个管脚,插座本身也插入打孔的板上。在板的另一边,你会看到特殊的用线缠绕的插槽紧紧包裹着围绕柱子的绝缘线。柱子的方形边缘穿破绝缘层并使它和导线电连接。

如果你实际在使用集成电路来构造一个电路,就要使用一块印刷线路板。以前,这是业余爱好者可以做的事情。板子上有孔,并覆盖一层薄的铜箔。首先,你要在需要保护的区域的铜箔上涂上防酸物,并用酸腐蚀其余部分,然后你把集成电路插座(或集成电路本身)直接焊在板的铜上。由于集成电路之间有许多内部连接,一层铜箔通常是不够用的,商业制造的印刷线路板有许多内部互连的层。

70年代早期,已可以在一块电路板上用集成电路构造一个完整的计算机处理器。把整个处理器做在一块芯片上已只是时间问题。尽管德克萨斯仪器公司1971年为单片计算机申请了专利,但实际的制造荣誉却属于Intel—一家于1968年,由前仙童雇员RobertNoyceGordonMoore建立的公司。Intel的第一个主要产品是1970年生产的可存储1024位的存储器芯片,在当时这是可做在一块芯片上的最大存储容量。

Intel在为由日本的Busicom公司生产的可编程计算器设计芯片时,决定采用不同的方法。正如Intel公司的工程师TedHoff写的:“不是想使他们的设备成为一个带有编程能力的计算器,而是想使它作为通常目的计算机可编程为一个计算器”。这就产生了Intel4004,第一个“芯片上的计算机”或微处理器。197111月,4004投入使用,它带有2300个晶体管。(根据摩尔定律,18年后微处理器应该有4000倍数量的晶体管,即大约1000万个,这是相当准确的预计。)

有了晶体管的数量,下面将描述4004的其他三个很重要的特性。自4004诞生以来,这三个指标经常用来作为微处理器相互比较的标准。

第一,40044位的微处理器。这意味着处理器的数据通路宽度只有4位,做加、减法运算时,它一次只处理4位。相比较,第17章中开发的计算机有8位数据通路,所以它是8位处理器。正如我们将看到的4位微处理器很快就被8位微处理器所超越,没有人会停滞不前。70年代后期,16位微处理器出现了。回想一样第17章的内容,以及在8位处理器上进行两个16位数相加所需要的指令代码,你就会欣赏16位处理器带给你的好处。80年代中期,32位微处理器出现了,并从那以后一直作为家用计算机的主流微处理器。

第二,4004有最大每秒108000周的时钟频率,即108KHz。时钟频率是可连接到微处理器并能运行的振荡器的最大频率。再要快的话,微处理器就可能出错。到1999年,家用计算机微处理器的时钟频率已达到500MHz—大约4004运行速率的5000倍。

第三,4004可寻址的存储器是640个字节。这看起来是一个小得可笑的数字,然而这和当时可用的存储芯片的容量是相匹配的。下一章你就会看到,两年内微处理器可达到64KB的寻址空间,这是第17章所提及的机器的容量。到1999年,Intel的微处理器已达64TB的可寻址空间,尽管大多数人家用电脑的RAM容量还低于256MB

这三个数字不会影响一台计算机的能力。例如,一个4位处理器要进行32位数的加法,只要简单地按4位一次进行。某种意义上,所有数字计算机都是相同的。如果一个处理器的硬件能做别的处理器做不了的,那么别的处理器可以用软件实现,最终它们能做同样的事情。这也就是图灵1973年的论文里有关计算能力的含义。

然而,处理器根本的不同是在速度上。同时,速度也是我们为什么使用计算机的一个重要原因。

最大时钟频率是影响处理器总体速度的一个显著因素,时钟频率决定了每一条指令的执行速度。处理器的数据宽度也影响其执行速度。虽然一个4位处理器可进行32位数的加法运算,但它的执行速度不可能与32位处理器一样。然而,令人迷惑的是,处理器可寻址的最大存储器容量也是影响速度的一个因素。最初,寻址空间看起来好像与处理器速度无关,而只反映了处理器在执行某些需要大量存储空间的功能时处理器的能力限度。但处理器通过利用存储器地址来控制用于保存或提取信息的其他媒体,可避开存储容量的限制。(例如,假设写到某个存储地址的每个字节实际上都是在纸带上穿孔,从存储地址读的每个字节都是从纸带上读。)然而这种做法减慢了整个计算机的处理速度—又是速度问题。

当然,这三个数字都只是初略地显示了微处理器的运行速度。这些数字没有告诉任何有关微处理器内部体系结构或机器码指令的效率和能力的问题。处理器越来越复杂,许多以前用软件来实现的普通工作现在可以用微处理器来实现。我们在后面的各章中可看到这种趋势的一些例子。

即使所有的数字计算机都具有同等的能力,即使它们只能做与图灵设计的原始计算机器一样的工作,处理器的执行速度最终也会影响计算机系统的总体用途。例如,比人类大脑的计算速度还慢的计算机是毫无用处的。当我们在现代计算机的屏幕上看电影时,如果处理器需要花费1分钟的时间来处理每一帧,我们也是无法忍受的。

回到20世纪70年代中期,先不说4004的局限性,但毕竟它是一个开始。19724月,Intel发布了8008—一个8位微处理器,时钟频率为200KHz,可寻址16KB的存储空间。(仅用三个数来总结一个处理器是多么容易。)后来,19745月期间,IntelMotorola公司同时发布了对8008进行改进的微处理器,这两种芯片改变了整个世界。

第十九章  两种典型的微处理器

微处理器—集成计算机中央处理器(CPU)的所有组件在一个硅芯片上—诞生于1971年。它的产生有一个很好的开端:第一个微处理器是Intel4004,其中有2300个晶体管。今天,差不多30年过去了,为家用计算机所制造的微处理器中将近有10000000个晶体管。

微处理器实际的作用基本上保持不变。现在的芯片上附加的上百万个晶体管可以做许多有趣的事情,但在微处理器最初的探索过程中,这些事情更多的是分散我们的注意力而不是给我们以启迪。为了对微处理器的工作情况获得更清晰的认识,让我们先看一下最初的微处理器。

这些微处理器出现在1974年。在该年度,Intel公司在4月推出了8080,Motorola(摩托罗拉)—从20世纪50年代开始生产半导体和晶体管产品的公司—在8月份推出了6800。它们并非是那年仅有的微处理器。同样是在1974年,德克萨斯仪器公司推出了4位的TMS1000,用在许多计算器、玩具和设备上;NationalSemiconductor(国家半导体公司)推出了PACE,它是第一个16位的微处理器。然而,回想起来,8080和6800是两个最具有重大历史意义的芯片。

Intel设定8080最初价格为$360,这是对IBMSystem/360的一个讽刺。IBMSystem/360是一个大型机系统,由许多大公司使用,要花费几百万美元。(今天,你只花$1.95就可以买到一个8080芯片。)这并不是说8080可以与System/360相提并论,但不用几年,IBM公司也将会正视这些很小的计算机。

8080是一个8位的微处理器,有6000个晶体管,时钟频率为2MHz,可寻址64KB的存储空间。6800(今天也只卖$1.95)有大约4000个晶体管,也可寻址64KB的存储空间。第1代6800的时钟频率为1MHz,但到1977年Motorola公司发布新款的6800时,其时钟频率已为1.5MHz和2MHz

这些芯片称作“单芯片微处理器”,不太准确的名称为“一个芯片上的计算机”。处理器只是整个计算机的一部分。此外,计算机至少还需要一些随机访问存储器(RAM)、供人们输入信息到计算机的方法(输入设备),供人们从计算机获取信息的方法(输出设备),以及其他可把所有这些东西连接在一起的芯片。这些组件将在第21章详细介绍。

从现在起,我们只考察微处理器。描述微处理器时,通常是用框图来画微处理器的内部组件及它们是如何连接的。但在第17章已有够多的图了,现在,我们将通过观察处理器与外界的相互作用来了解它的内部。换句话说,为了弄清楚它的作用,可以把微处理器看成是一个黑盒子,它的内部操作不需要做详细研究。我们可以通过测试芯片的输入和输出信号,特别是芯片的指令集来掌握微处理器的功能。

8080和6800都是40管脚的集成电路。这些芯片最普通的IC封装大约是2英寸长,半英寸宽,1/8英寸厚:

当然,你看到的只是外包装。位于其内部的硅晶片非常小,就拿早期的8位微处理器来说,其硅晶片小于1/4平方英寸。外包装保护硅晶片并通过管脚提供对芯片的输入和输出点的访问。下图显示了8080的40个管脚的功能:

本书的所有电气或电子设备都需要某种电源供电。8080的一个特别之处在于它需要三种电源电压:管脚20必须连到5伏电源上,管脚11连到-5伏电源上,管脚28连到12伏电源上;管脚2接地(1976年,Intel发布了8085芯片,简化了这些电源需求)。

其余管脚都画有箭头。从芯片中出来的箭头表示输出信号,这是由微处理器控制的信号,计算机中其余芯片对此作出响应。指向芯片的箭头表示输入信号,这是来自于其他芯片的信号,8080对它们做出响应。有些管脚既是输入又是输出。

第17章的处理器需要振荡器使它工作。8080需要两个不同的2MHz同步时钟输入,在22和15管脚上分别标记为?1和?2。这些信号可以很方便地由Intel公司生产的8224时钟信号发生器提供。给这个芯片连上一个18MHz的石英晶体,剩下的工作它基本上可以完成。

一个微处理器通常有多个输出信号来寻址存储空间,这种信号的数目与微处理器可寻址的存储器空间的大小直接相关。8080有16个地址信号,标为A0~A15,具有寻址216即65536字节的存储空间的能力。

8080是一个8位微处理器,一次可从存储器中读出、写入8位数据。它包括8个数据信号D0~D7,这些信号是在此芯片中仅有的几个既作为输入又作为输出的信号。当微处理器从存储器读数据时,这些管脚作为输入;当微处理器向存储器写数据时,这些管脚作为输出。

微处理器的另外10个管脚是控制信号。例如,RESET输入用来复位微处理器。输出信号-WR表示微处理器要向RAM中写数据。(-WR信号对应于RAM阵列的写入输入。)另外,当芯片读取指令时,其他控制信号会在某个时候出现在D0~D7管脚。由8080构成的计算机系统通常使用8228系统控制芯片来锁存这些附加的控制信号。后面将会讲述一些控制信号。由于8080的控制信号非常复杂,因此,除非你想基于8080芯片来实际设计计算机,否则最好不要用这些控制信号来折磨自己。

假定8080微处理器连接了64KB的存储器,这样可以不通过微处理器来读写数据。

8080芯片复位后,它从存储器的地址0000h处读取该字节,送到微处理器中。这可以通过在地址信号端A0A15输出160来实现。它读取的字节必须是8080指令,这种读取字节的过程叫作取指令。

在第17章构造的计算机里,所有指令(除了停止指令HLT)都是3个字节长,包括一个操作码和两个字节的地址。在8080中,指令长度可以是1个字节、2个字节或3个字节。有些指令可使8080从存储器的某一位置处读出一个字节送到微处理器中;有些指令可使8080从微处理器中把数据写入存储器的某一位置处;其他指令可使8080不使用RAM而在内部执行。第一条指令执行完后,8080访问存储器中的第二条指令,依此类推。这些指令组合在一起构成一个计算机程序,用来完成一些自己感兴趣的事情。

8080运行在最高速度即2MHz时,每个时钟周期为500纳秒(1除以2000000周等于0.000000500秒)。第17章中的每条指令都需要4个时钟周期,8080的每条指令则需要418个时钟周期,这意味着每条指令的执行时间为29微秒(即百万分之一秒)。

了解微处理器功能的最好方法可能是在系统方式下测试其完整的指令集。

17章最后出现的计算机仅有12条指令。一个8位微处理器很容易就有256条指令,每个操作码对应于某个8位值。(如果一些指令有2个字节的操作码,则实际会有更多的指令)。8080虽没有那么多,但它也有244条操作码。这看起来似乎很多,但总的来说,却又不比第17章中的计算机功能多多少。例如,如果想用8080做乘法或除法,仍然需要写一段小程序来实现。

17章中讲过,处理器指令集的每个操作码都和某个助记符相联系,有些助记符之后可能还有操作数。但这些助记符仅用来方便地表示操作码。处理器只读取字节,它并不懂组成这些助记符的字符的含义。

17章中的计算机有两条很重要的指令,称作装载(Load)和保存(Store)指令。这些指令都占用三个字节的存储空间。装载指令的第一个字节是操作码,操作码后的两个字节表示16位地址。处理器把在此地址中的字节送到累加器。同样,保存指令把累加器中的内容存储到指令指定的地址处。

下面,我们用助记符来简写这两个操作:

LOD     A,[aaaa]

STO     [aaaa],A

在此,A表示累加器(装载指令的目的操作数,保存指令的源操作数),aaaa表示一个16位的存储器地址,通常用4位十六进制数来表示。

8080中的8位累加器称作A,就像第17章中的累加器。正如第17章中的计算机一样,8080也有两条与装载和保存指令功能一样的指令。8080中这两条指令的操作码为32h3Ah,每个操作码后有一个16位地址。8080的助记符为STA(代表存储累加器的内容)和LDA(代表装载到累加器):

操作码            指令

32            STA[aaaa],A

3A            LDAA,[aaaa]

除了累加器,8080微处理器内部还包括6个寄存器(register),每个寄存器可以保存8位的值。这些寄存器和累加器非常相似,事实上,累加器被看作是一种特殊的寄存器。和累加器一样,这6个寄存器也是锁存器。处理器可以把数据从存储器传送到寄存器,也可以把数据从寄存器送回到存储器。然而,这些寄存器没有累加器的功能强大。例如,当两数相加时,其结果通常送往累加器而非其中一个寄存器。

8080中,除累加器外的6个寄存器的名字分别为BCDEHL。人们通常问的第一个问题是“用FG干什么?”,第二个问题是“用IJK又要做什么?”,答案是使用寄存器HL具有某种特殊的含义。H代表高(High),L代表低(Low)。通常把HL8位合起来记作HL来表示一个16位寄存器对,H作为高位字节,L作为低位字节。这个16位值通常用来寻址存储器。后面我们将看到它的简单用法。

所有这些寄存器都是必需的吗?为什么不在第17章中的计算机中用到它们呢?从理论上说,它们并非必需,但是使用它们会很方便。许多计算机程序在同一时刻要用到几个数据,如果所有这些数据都存储在微处理器的寄存器中而非存储器中,执行程序将会更快,因为程序访问存储器的次数越少,那么它的运行速度也就越快。

8088指令中,有一个至少63个指令码供一条8080指令使用的指令,它就是MOV指令,即Move的简写。该条指令只有一个字节,用于把一个寄存器中的内容传送到另一个寄存器中(或同一个寄存器中)。使用大量MOV指令是设计带有7个寄存器(包括累加器)的微处理器的正常结果。

下面是前32MOV指令。记住目的操作数在左边,源操作数在右边:

操作码           指令           操作码           指令

40            MOVB,B            50            MOVD,B

41            MOVB,C            51            MOVD,C

42            MOVB,D            52            MOVD,D

43            MOVB,E            53            MOVD,E

44            MOVB,H            54            MOVD,H

45            MOVB,L            55            MOVD,L

46            MOVB,[HL]         56            MOVD,[HL]

47            MOVB,A            57            MOVD,A

48            MOVC,B            58            MOVE,B

49            MOVC,C            59            MOVE,C

4A            MOVC,D            5A            MOVE,D

4B            MOVC,E            5B            MOVE,E

4C            MOVC,H            5C            MOVE,H

4D            MOVC,L            5D            MOVE,L

4E            MOVC,[HL]         5E            MOVE,[HL]

4F            MOVC,A            5F            MOVE,A

这些都是很方便的指令。当一个寄存器中有值时,可以把它传送到其他寄存器中。注意,上述指令中有四条指令用到HL寄存器对,如:

MOV    B,[HL]

前面列出的LDA指令把一个字节从存储器中传送到累加器中,这个字节的16位地址直接跟在LDA操作码的后面。这里的MOV指令把一个字节从存储器中传送到寄存器B中,但被装载到寄存器中的字节的地址是保存在寄存器对HL中。HL寄存器是怎样得到16位存储器地址的呢?它可以通过多种方法来实现,或许是通过某种方法计算出来的。

总之,这两条指令

LDA    A,[aaaa]

MOV    B,[HL]

都把一个字节从存储器中装载到微处理器中,但它们用两种不同的方法来寻址存储器地址。第一种方法叫作直接寻址方式,第二种方法叫作间接寻址方式。

第二批32MOV指令表明用HL寻址的存储器地址也可以作为目的操作数:

操作码   指令    操作码      指令

60    MOVH,B    70    MOV[HL],B

61    MOVH,C    71    MOV[HL],C

62    MOVH,D    72    MOV[HL],D

63    MOVH,E    73    MOV[HL],E

64    MOVH,H    74    MOV[HL],H

65    MOVH,L    75    MOV[HL],L

66    MOVH,[HL] 76    HLT

67    MOVH,A    77    MOV[HL],A

68    MOVL,B    78    MOVA,B

69    MOVL,C    79    MOVA,C

6A    MOVL,D    7A    MOVA,D

6B    MOVL,E    7B    MOVA,E

6C    MOVL,H    7C    MOVA,H

6D    MOVL,L    7D    MOVA,L

6E    MOVL,[HL] 7E    MOVA,[HL]

6F    MOVL,A    7F    MOVA,A

其中一些指令如:

MOV    A,A

做的是无用的事,而像:

MOV    [HL],[HL]

这样的指令是不存在的。和这条指令相对应的操作码实际上是停止指令。

观察这些MOV操作码更明显的方法是考察它的位模式,MOV操作码由8位组成:

01dddsss

其中字母ddd代表指代目的操作数的3位代码,sss代表指代源操作数的3位代码。这3位代码是:

000=寄存器B

001=寄存器C

010=寄存器D

011=寄存器E

100=寄存器H

101=寄存器L

110=HL中保存的存储器地址中的内容

111=累加器A

例如,指令:

MOV    L,E

相应的操作码表示为01101011,或6Bh。可以通过检查前面的表来验证。

因此可能在8080内部某个地方,标有sss的3位标识用在8-1数据选择器中,标有ddd的3位标识用于控制3-8译码器,此译码器用来决定哪一个寄存器锁存了一个值。

也可能使用寄存器B和C来构成一个16位寄存器对BC,用寄存器D和E来构成一个16位寄存器对DE。如果每一个寄存器对都包含用于装载或保存一个字节的存储器地址,则可以使用下述指令:

操作码    指令        操作码    指令

02    STAX[BC],A    0A    LDAXA,[BC]

12    STAX[DE],A    1A    LDAXA,[DE]

另一种类型的传送指令叫做传送立即数,指定的助记符为MVI。传送立即数指令占两个字节,第一个是操作码,第二个是1个字节的数据。此字节从存储器中传送到一个寄存器中或由HL寻址的存储单元中。

操作码       指令

06MVI      B,xx

0E         MVIC,xx

16MVI      D,xx

1E         MVIE,xx

26MVI      H,xx

2E         MVIL,xx

36         MVI[HL],xx

3E         MVIA,xx

例如,当指令:

MVI    E,37h

执行后,寄存器E中包含有字节37h。这就是第三种寻址方式,即立即数寻址方式。

32个操作码的集合完成四种基本算术运算,那是在第17章开发处理器时我们就已熟悉的运算,即加法(ADD)、进位加法(ADC)、减法(SUB)和借位减法(SBB)。所有情况中,累加器是两个操作数之一,也是结果的目的地址。

操作码   指令    操作码    指令

80    ADDA,B    90    SUBA,B

81    ADDA,C    91    SUBA,C

82    ADDA,D    92    SUBA,D

83    ADDA,E    93    SUBA,E

84    ADDA,H    94    SUBA,H

85    ADDA,L    95    SUBA,L

86    ADDA,[HL] 96    SUBA,[HL]

87    ADDA,A    97    SUBA,A

88    ADCA,B    98    SBBA,B

89    ADCA,C    99    SBBA,C

8A    ADCA,D    9A    SBBA,D

8B    ADCA,E    9B    SBBA,E

8C    ADCA,H    9C    SBBA,H

8D    ADCA,L    9D    SBBA,L

8E    ADCA,[HL] 9E    SBBA,[HL]

8F    ADCA,A    9F    SBBA,A

假设A中是35h,寄存器B中是22h,当指令:

SUB    A,B

执行后,累加器中的结果为13h

若A中的值为35h,寄存器H中的值为10h,L中的值为7Ch,存储器地址107Ch中的值为4Ah,则指令:

ADD    A,[HL]

把累加器中的内容(35h)和通过寄存器对HL寻址得到的值(4Ah)相加,并把结果(7Fh)保存到累加器中。

ADC和SBB指令允许8080加/减16位、24位、32位和更多位的数。例如,假设寄存器对BC和DE都包含16位数,你想把它们相加,并把结果存到BC中。下面是具体做法:

MOV    A,C;低位字节

ADD    A,E

MOV    C,A

MOV    A,B;高位字节

ADC    A,D

MOV    B,A

其中有两条加法指令,ADD指令用于低位字节相加,ADC指令用于高位字节相加。第一条加法指令的进位位包含在第二条加法指令中。因为只能利用累加器进行加法运算,所以在这么短的代码中也需要至少4条MOV指令。许多MOV指令常常出现在8080代码中。

该是谈论8080标志位的时候了。在第17章的处理器中,已有进位标志位CF和零标志位ZF。8080还有3个标志位,即符号标志位SF、奇偶标志位PF和辅助进位标志位AF。所有标志位都保存在另一个叫作程序状态字(PSW:programstatusword)的8位寄存器中。像LDA、STA和MOV这样的指令不影响标志位,而ADD、SUB、ADC和SBB指令却要影响标志位,影响的方式如下:

  • 当运算结果最高位为1时,符号标志位SF为1,表示结果为负。

  • 当结果为0时,零标志位ZF为1

  • 当运算结果中“1”的个数为偶数时,奇偶标志位PF=1;当运算结果中“1”的个数为奇数时,奇偶标志位PF=0。PF有时用来粗略地检测错误,此标志位在8080程序中不常用。

  • 当ADD或ADC运算产生进位或SUB与SBB运算不发生借位时,进位标志位CF=1。(这点不同于第17章中的计算机进位标志的实现。)

  • 当操作结果的低4位向高4位有进位时,辅助进位标志位AF=1。此标志位只用在DAA(十进制调整累加器)指令中。

有两条指令直接影响进位标志位CF

操作码    指令    含义

37        STC    置CF为1

3F        CMC    CF取反

第17章中的计算机可执行ADD、ADC、SUB和SBB指令(尽管没什么灵活性),但8080还可以进行逻辑运算AND(与)、OR(或)和XOR(异或)。算术运算和逻辑运算都可通过处理器的算术逻辑单元(ALU)来执行:

操作码   指令    操作码    指令

A0    ANDA,B    B0    ORA,B

A1    ANDA,C    B1    ORA,C

A2    ANDA,D    B2    ORA,D

A3    ANDA,E    B3    ORA,E

A4    ANDA,H    B4    ORA,H

A5    ANDA,L    B5    ORA,L

A6    ANDA,[HL] B6    ORA,[HL]

A7    ANDA,A    B7    ORA,A

A8    XORA,B    B8    CMPA,B

A9    XORA,C    B9    CMPA,C

AA    XORA,D    BA    CMPA,D

AB    XORA,E    BB    CMPA,E

AC    XORA,H    BC    CMPA,H

AD    XORA,L    BD    CMPA,L

AE    XORA,[HL] BE    CMPA,[HL]

AF    XORA,A    BF    CMPA,A

AND、XOR和OR指令按位运算,即逻辑操作只是单独地在对应位之间进行。例如:

MVI    A,0Fh

MVI    B,55h

AND    A,Bh

累加器中的结果将为05h。如果第三条指令为OR运算,则结果为5Fh;如果第三条指令为XOR运算,则结果为5Ah

CMP(比较)指令与SUB指令基本上一样,除了结果不保存在累加器中。换句话说,CMP执行减法操作再把结果扔掉。这是为什么?是因为标志位。根据标志位的状态可知道所比较的两数之间的关系。例如,当如下指令:

MVI    B,25h

CMP    A,B

执行完时,A中的内容没有改变。然而,若A中的值为25h,则ZF标志置位;若A中的值小于25h,则CF=1

这8个算术逻辑运算也可以对立即数进行操作:

操作码    指令    操作码    指令

C6    ADIA,xx    E6    ANIA,xx

CE    ACIA,xx    EE    XRIA,xx

D6    SUIA,xx    F6    ORIA,xx

DE    SBIA,xx    FE    CPIA,xx

例如,上面列出的两条指令也可以用下面的指令来替换:

CPI    A,25h

下面是其他两条8080指令:

操作码    指令

27        DAA

2F        CMA

CMA即complementaccumulator,它对累加器中的值进行取反操作。每个0变为1,每个1变为0。如果累加器中的值为01100101,CMA指令使它变为10011010。也可以用下述指令来使累加器按位取反:

XRI    A,FFh

DAA即DecimalAdjustAccumulator,如前所述,它可能是8080中最复杂的一条指令。微处理器中有一个完整的小部件专门用于执行这条指令。

DAA指令帮助程序员用BCD码表示的数来进行十进制算术运算。在BCD码中,每一小块数据的范围在0000~1001之间,对应于十进制的0~9。利用BCD码格式,每8位字节可存储两个十进制数字。

假设累加器中的值为BCD码的27h。由于是BCD码,则它实际上指的是十进制的27。(十六进制的27h等于十进制的39。)再假定寄存器B中的值为BCD码的94h。如果执行指令:

MOV    A27h

MOV    B94h

ADD    AB

累加器中的值将为BBh,当然,它不是BCD码,因为BCD码中的每一块不能超过9。但是,现在执行指令:

DAA

则累加器中的值为21h,且CF=1,这是因为27和94的十进制和为121。如果想进行BCD码的算术运算,这样做是相当方便的。

经常需要对一个数进行加1或减1操作。在第17章的乘法程序中,我们需要对一个数减1,使用的方法是加上FFh,它是-1的2的补码。8080中包含特殊的用于寄存器或存储单元的加1指令(称作增量)和减1指令(称作减量):

操作码   指令  操作码        指令

04    INRB    05    DCRB

0C    INRC    0D    DCRC

14    INRD    15    DCRD

1C    INRE    1D    DCRE

24    INRH    25    DCRH

2C    INRL    2D    DCRL

34    INR[HL] 35    DCR[HL]

3C    INRA    3D    DCRA

单字节指令INR和DCR可影响除CF外的所有标志位。

8080也包含4个循环移位指令,这些指令可使累加器中的内容左移或右移1位:

操作码     指令        含义

07        RLC    累加器循环左移

0F        RRC    累加器循环右移

17        RAL   累加器带进位循环左移

1F        RAR    累加器带进位循环右移

这些指令只影响CF

假定累加器中的值为A7h,即二进制的10100111。RLC指令使A中的内容向左移位,最高位(移出顶端)成为最低位(移进底端),同时决定进位标志位CF的状态。其结果为01001111且CF=1。RRC指令用同样的方法向右移位。开始为10100111,执行RRC指令后,其结果为11010011且CF=1。

RAL和RAR指令有些不同。当向左移位时,RAL指令把CF移入累加器的最低位,而把最高位移入CF中。例如,如果累加器的内容为10100111,CF=0,RAL指令执行的结果是累加器的内容变为01001110,且CF=1。同样,在相同的初始条件下,RAR指令使累加器的内容变为01010011,CF=1。

对于乘2(左移1位)和除2(右移一位)操作,移位指令非常方便。

把微处理器寻址的存储器叫作随机访问存储器(RAM)是有原因的:微处理器可以简单地根据提供的地址访问某一存储位置。RAM就像一本书一样,我们可以打开它的任何一页。它并不像做在微缩胶片上的一个星期的报纸,要找到周六版,需扫过大半周。同样,它也不同于磁带,要播放磁带上的最后一首歌需快进整个一面。微缩胶片和磁带的存储不是随机访问的,而是顺序访问的。

RAM确实效果不错,对于微处理器来说更是如此。但在使用存储器时有所差别是有好处的,下面就是一种既非随机又非顺序访问的存储方式:假定你在一个办公室里,人们到你桌前给你分配工作,每个工作都需要某种文件夹。通常你会发现你在继续某项工作之前,必须使用另外一个文件夹先做一些相关的工作。因此你把第一个文件夹放在桌子上,又拿出第二个文件夹放在它上面进行工作。现在又有一个人来让你做一个优先权高于前面工作的工作,你拿来一个新文件夹放在那两个上面继续工作。而此项工作又需要另外一个文件夹,这样在你的桌子上很快就摆了一堆文件夹了。

注意,这个堆非常明确地、有序地保存了你正在做的工作的轨迹。最上面的文件夹总是最高优先权的工作,去掉这个以后,下一个肯定是你就要做的,如此类推。当你最终去掉了桌子上的最后一个文件夹后(你开始的第1项工作),你就可以回家了。

以这种方式工作的存储器技术叫做作堆栈(stack)。从底向上压入堆栈,从顶向下弹出堆栈,因此这也叫后进先出存储器,或LIFO。最后放入堆栈中的数据最先被取出,最先放入堆栈中的数据最后被取出。

计算机中也可以使用堆栈,不是用来保存工作而是用来存储数据,且已被证明使用起来非常方便。向堆栈中放入数据叫作push(压入),从堆栈中取走数据叫作pop(弹出)。

假定你正在用汇编语言设计程序,程序中使用了寄存器A、B和C。但在编程过程中,你发现此程序需要去做另一件事—一个小的计算,其中也要使用寄存器A,、B、C。而你最终要回到先前的程序,并使用A、B、C中原有的值。

当然,你能做的工作只是简单地把寄存器A、B、C中的值保存到存储器中的不同位置,以后再把这些位置的值装载到寄存器中,但这样做需要保存值被保存的位置。一个显然的方法是把寄存器压入堆栈:

PUSH    A

PUSH    B

PUSH    C

一会儿再解释这些指令的作用。现在,我们只需要知道它们以某种方式把寄存器的内容保存在一个后进先出的存储器中。一旦这些语句执行了,你的程序就可以毫无顾虑地利用这些寄存器来做其他工作。为了得到原来的值,只需简单地按与压入堆栈相反的顺序把它们从堆栈中弹出即可,如下所示:

POP    C

POP    B

POP    A

记住是后进先出。如果用错了POP语句的顺序,就会引起错误。

堆栈机制的一个好处在于一个程序的不同部分都可以使用堆栈而不会出现问题。例如,在把A、B、C压入堆栈中后,程序的其他部分还可能需要把寄存器C、D、E的内容压入堆栈:

PUSH    C

PUSH    D

PUSH    E

接着,这一部分程序所要做的就是在第一部分弹出C、B和A之前,用下述方法恢复寄存

器的值:

POP    E

POP    D

POP    C

堆栈是怎样实现的呢?首先,堆栈只是不被别的东西使用的正常的RAM的一部分。8080微处理器包含一个特殊的16位寄存器来对这一部分存储器进行寻址,这个16位寄存器叫作堆栈指针。

这里举的压入和弹出寄存器的例子对于8080来说不太准确。8080的PUSH指令实际上是存储16位的值到堆栈,POP指令用来恢复它们。因此8080不用像PUSHC和POPC这样的指令,它有下述8条指令:

操作码    指令  操作码    指令

C5    PUSH BC    C1    POP BC

D5    PUSH DE    D1    POP DE

E5    PUSH HL    E1    POP HL

F5    PUSH PSW   F1    POP PSW

PUSHBC指令把寄存器B和C的内容保存到堆栈中,POPBC指令恢复它们。最后一行的缩写PSW指的是程序状态字,前面讲过,它是包含有标志位的8位寄存器。最后一行的两条指令实际上是把累加器和PSW都压入和弹出堆栈。如果你想保存所有寄存器和标志位的内容,

可以使用:

PUSH    PSW

PUSH    BC

PUSH    DE

PUSH    HL

当以后想恢复这些寄存器的内容时,按相反的顺序使用POP指令:

POP    HL

POP    DE

POP    BC

POP    PSW

堆栈是怎样工作的呢?假设堆栈指针为8000h,PUSHBC指令将引起下面这些情况发生:

  • 堆栈指针减1至7FFFH

  • 寄存器B的内容保存在堆栈指针所指的地址处,即7FFFh处

  • 堆栈指针减1至7FFEH

  • 寄存器C的内容保存在堆栈指针所指的地址处,即7FFEh处

  • 当堆栈指针仍然为7FFEh时,POPBC指令执行,用来反向执行每一步:

  • 从堆栈指针所指的地址(即7FFEh)处装载数据到寄存器C中

  • 堆栈指针增1至7FFFh

  • 从堆栈指针所指的地址(即7FFFh)处装载数据到寄存器B中

  • 堆栈指针增1至8000h

对每个PUSH指令,堆栈都会增加2个字节,这可能导致程序出现小毛病—堆栈可能会变得很大以致会覆盖掉程序所需的一些代码和数据。这就是堆栈上溢问题。同样,过多的POP指令会过早用光堆栈内容,这就是堆栈下溢问题。

如果8080同一个64KB的存储器连接,你可能想把初始堆栈指针置为0000h。第一条PUSH指令使地址减1变为FFFFh,这时堆栈占用了存储器的最高地址。如果你的程序放在从0000h处开始的存储器区域,则它和堆栈离的就太远了。

对堆栈寄存器进行赋值的指令是LXI,即loadextendedimmediate(装载扩展的立即数)。下面这些操作码后的指令也是把两个字节装载到16位寄存器:

操作码    指令

01    LXI BC,xxxx

11    LXI DE,xxxx

21    LXI HL,xxxx

31    LXI SP,xxxx

指令:

LXI    BC,527Ah

等价于

MVI    B,52

MVI    C,7Ah

LXI指令保存一个字节。另外,上表中最后一条LXI指令用来对堆栈指针赋值。微处理器复位后,这条指令并不常用来作为首先执行的指令之一:

0000h: LXI    SP,0000h

也可以对寄存器对和堆栈指针执行加1和减1操作,就好像它们是16位寄存器一样:

操作码   指令    操作码    指令

03    INX BC    0B    DCX BC

13    INX DE    1B    DCX DE

23    INX HL    2B    DCX HL

33    INX SP    3B    DCX SP

即然是在讨论16位指令,可以看看更多一些这样的指令。下面的指令是把16位寄存器对的内容加到寄存器对HL中:

操作码    指令

09    DADH L,BC

19    DADH L,DE

29    DADH L,HL

39    DADH L,SP

上面这些指令可节约几个字节。例如,第一条指令正常需要6个字节:

MOV A,L

ADD A,C

MOV L,A

MOV A,H

ADC A,B

MOV H,A

DAD指令通常用于计算存储器地址,这条指令只影响CF。

下一步让我们看以下各种指令。下面的两个操作码后都紧跟着一个2字节地址,分别保存和装载寄存器对HL的内容:

操作码    指令            含义

2h    SHLD[aaaa],HL    直接保存HL

2Ah   LHLDHL,[aaaa]    直接装载HL

寄存器L的内容保存在地址aaaa处,寄存器H的内容保存在地址aaaa+1处。

下面两条指令用来从寄存器对HL中装载程序计数器PC或堆栈指针SP:

操作码        指令                含义

E9h        PCHLPC,HL        把HL中的内容装载到PC

F9h        SPHLSP,HL        把HL中的内容装载到SP

PCHL指令实际上是一种转移指令,8080执行的下一条指令保存在HL寄存器对中的地址所对应的存储单元中。SPHL是另外一个设置SP的方法。

下面两条指令中,第一条指令使HL的内容与堆栈中最上面的两个字节进行交换,第二条指令使HL的内容与寄存器对DE的内容进行交换:

操作码        指令                含义

E3h        XTHLHL,[SP]        HL与堆栈顶端的内容交换

EBh        XCHGHL,DE           DE和HL交换

除了PCHL外,还没有讲过8080的转移指令。前面第17章中讲过,处理器中有一个叫作程序计数器PC的寄存器,PC中包含处理器取回并执行的指令的存储器地址。通常PC使处理器顺序执行存储器中的指令,但有些指令—通常命名为Jump(转移)、Branch(分支)或goto(跳转)—能使处理器偏离这个固定的过程。这些指令使得PC装载另外的值,处理器所取的下一条指令将在存储器的其他位置。

尽管简单、普通的转移指令很有用,但条件转移指令更有用。这些指令可使处理器根据某些标志,如CF或ZF,来转移到另外的地址处。条件转移指令的存在使得第17章中的自动加法机成为一般意义上的数字计算机。

8080有5个标志位,其中4个对条件转移指令有用处。8080支持9种不同的转移指令,包括无条件转移指令和基于ZF、CF、PF、SF是1还是0的条件转移指令。在介绍这些指令之前,先介绍一下与此相关的另外两种指令。第一个是Call(调用)指令。Call指令与Jump指令的不同之处在于:前者把一个新值装入到程序计数器PC中,处理器保存PC中原来的地址,保存在哪里?当然,在堆栈中。

这种策略意味着Call指令可有效地保存“程序从哪里跳转”的标记。处理器最终可利用此地址返回到原来的位置。这个返回指令叫Return。Return指令从堆栈中弹出两个字节,并把该值装载到PC中。

Call和Return指令是任何处理器中都很重要的功能。它们允许程序员编写子程序,子程序是程序中经常用到的代码段。(“经常”一般意味着“不止一次”。)子程序是汇编语言中的基本组成部分。

让我们看一个例子。假设你正在编写一个汇编语言程序,并且需要使两个数相乘,因此你可以写出一段代码来做这项工作,然后继续往下编写程序,现在又需要使两个数相乘。因为你已知道如何进行两数相乘,因此你只需简单地重复使用同样的指令来完成它。但只是简单地两次把这些指令输入到存储器吗?希望不是,这是对时间和存储空间的浪费,更好的方法是转送到原来的代码处。由于无法返回到程序的当前位置,所以一般的Jump指令不能用。但使用Call和Return指令可以让你完成所需的功能。

进行两数相乘的一组指令可以作为一个子程序。下面就是这样的子程序。在第17章中,被乘数(和结果)存放在存储器的某一地址中;而在8080子程序中,寄存器B的值和寄存器C中的值相乘,然后把16位乘积装入寄存器HL中:

Multiply:PUSHPSW;保存要改变的寄存器

PUSH BC

SUB H,H;设置HL(结果)为0000h

SUB L,L

MOV A,B;乘数送到A

CPI A,00h;如果为0,结束

JZA llDone

MVI B,00h;BC的高字节置0

Multloop:DADHL,BC;BC加到HL

DEC A;乘数减1

JNZMultloop;不为0,转移

AllDone:POPBC;恢复保存的寄存器

POP PSW

RET;返回

注意,上述子程序的第1行开始有一个标号Multiply。当然,这个标号对应于子程序所在的存储器地址。子程序开始用了两个PUSH指令,通常在子程序开始处应先保存(以后恢复)它需要使用的寄存器。

然后该子程序把H和L寄存器置为0。虽然可以使用MVI指令而不用SUB指令,但那需要使用4个字节的指令而不是2个字节的指令。子程序执行完后,寄存器对HL中保存有相乘的结果。

下一步该子程序把寄存器B的内容(乘数)移入A中,并且检查它是否为0。如果它为0,乘法子程序到此结束,因为结果为0。由于寄存器H和L已经为0,因而子程序可以只使用JZ指令跳转到末端的两个POP指令处。

否则,子程序把寄存器B置为0。现在,寄存器对BC中包含一个16位的被乘数,A中为乘数。DAD指令把BC(被乘数)加到HL(结果)中。A中的乘数减1,且只要它不为0,JNZ指令就又使BC加到HL中。此小循环继续下去,直到BC加到HL中的次数等于乘数。(可以用8080的移位指令编写一个更有效的乘法子程序。)

利用这个子程序完成25h与12h相乘的程序用下面的代码:

MOV B,25h

MOV C,12h

CALL Multiply

Call指令把PC的值保存在堆栈中,该值是Call指令的下一条指令的地址。然后,Call指令使程序转移到标号Multiply所标识的指令,即子程序的开始。当子程序计算完结果后,执行RET(返回)指令,即从堆栈中弹出程序计数器的值,程序继续执行Call指令后面的语句。

8080指令集中包括条件CALL指令和条件Return指令,但它们远不如条件转移指令用得多。下表中完整地列出了这些指令:

你可能知道,存储器并不是唯一连接在微处理器上的设备。一个计算机系统通常需要输入输出设备以便于实现人机通信。输入输出设备通常包括键盘和显示器。

微处理器是怎样与外围设备(对于连接到微处理器而不是存储器的东西的称呼)进行通信的呢?外围设备具有与存储器相似的接口,微处理器可通过对应于外设的具体地址来对外设进行读写。在有些微处理器中,外围设备实际上占用了通常用来寻址存储器的地址,这种配置叫作内存映像I/O。然而在8080中,在65536个正常地址外还有256个附加地址专门为输入输出设备预留,这些就是I/O端口(I/OPort)。I/O地址信号为A0~A7,但I/O访问与存储器访问不同,由8228系统控制芯片锁存的信号来区分。

OUT指令用于把累加器中的数据写到紧跟该指令的字节所寻址的I/O端口中。IN指令把端口的数据读入到累加器中。

操作码        指令

D3        OUT PP

DB        IN PP

外围设备有时需要引起微处理器的注意。例如,当你在键盘上按键时,如果微处理器能

马上知道这件事通常是有帮助的。这由称作中断(interrupt)的机制来完成,这是连接至8080INT输入端的,由外设产生的信号。

然而,当8080复位时,它不能对中断产生响应。程序必须通过执行EI(Enableinterrupts)指令来允许中断,通过执行DI(DisableInterrupts)指令来禁止中断。

操作码        指令

F3        DI

FB        EI

8080的INTE输出端信号表明允许中断。当外设需要中断微处理器当前工作时,它把8080的INT输入端设置为1。8080通过从存储器中取出指令对它作出响应,但控制信号表明有中断发生。外设通常通过提供下述指令之一来响应8080:

操作码    指令    操作码        指令

C7        RST 0    E7    RST 4

CF        RST 1    EF    RST 5

C7        RST 2    E7    RST 6

DF        RST 3    FF    RST 7

以上称作Restart指令,它们与CALL指令相似,也需要把当前程序计数器的值压入堆栈。但Restart指令随后转移到一个特定的位置:RST0转移到地址0000h处,RST1转移到地址0008h处等等,直到RST7转移到地址0038h处。位于这些地址中的代码段来处理中断。例如,来自键盘的中断引起RST4指令执行,地址0020h处的一些代码从键盘读取数据(这将在第21章做全面介绍)。

到此为止,已讲述了243个操作码。下述12个字节与任何操作码无关:08h、10h、18h、20h、28h、30h、38h、CBh、D9h、DDh、EDh和FDh。这样总共有255个操作码。下面还要提到一个操作码:

操作码   指令

00     NOP

NOP代表noop,即nooperation(无操作)。NOP指令使微处理器什么都不做。这有什么作用吗?用于填空。8080通常可以执行一批NOP指令而不会有任何坏情况发生。以下不打算再详细讨论Motorola6800,因为它的设计与功能与8080非常相似。下面是6800的40个管脚:

VSS代表接地,VCC是5V电源。与8080相似,6800有16个地址输出信号和既可作为输入又可作为输出的8个数据信号。它有RESET信号和R/-W信号。-IRQ信号代表中断请求。6800的时钟信号比8080的更加简单。6800没有I/O端口的概念,所有输入输出设备都必须是6800存储器地址空间的一部分。

6800有一个16位程序计数器PC、一个16位堆栈指针SP、一个8位状态寄存器(作为标志)以及两个8位累加器A和B。它们都被看成是累加器(B不是只作为一个寄存器)是因为没有能用A来做而不能用B来做的事。6800没有附加的8位寄存器。

6800中有一个16位索引寄存器(indexregister),可用来保存一个16位地址,很像8080中的寄存器对HL。对于许多指令来说,它们的地址都可以由索引寄存器和紧跟在操作码后的地址之和得到。

虽然6800和8080所实现的操作相同—装载、保存、加法、减法、移位、转移、调用,但很明显的区别是:它们的操作码和助记符完全不同。例如,下面是6800的分支转移指令:

操作码            指令                含义

20h                BRA                转移

22h                BHI             大于则转移

23h                BLS            小于或相同则转移

24h                BCC            进位为0则转移

25h                BCS            进位置1则转移

26h                BNE            不等则转移

27h                BEQ            相等则转移

28h                BVC            溢出为0则转移

29h                BVS            溢出置1则转移

2Ah                BPL             为正则转移

2Bh                BMI            为负则转移

2Ch                BGE            大于或等于0则转移

2Dh                BLT            小于0则转移

2Eh                BGT            大于0则转移

2Fh                BLE            小于或等于0则转移

6800没有像8080中那样的奇偶标志位PF,但它有一个8080中没有的标志位—溢出标志位(overflowflag)。上述转移指令中有些依赖于标志位的组合。

当然8080和6800指令集是不同的,这两个芯片是同一时间由不同的两个公司的两组不同的工程师设计的。这种不兼容性意味着每一种芯片不能执行对方的机器代码,为一种芯片开发的汇编语言程序也不能翻译成可在另一种芯片上执行的操作码。编写可在多于一种处理器上执行的计算机程序是第24章的主题。

8080和6800还有一个有趣的不同点:在两种微处理器中,LDA指令都是从一个特定的地址处装载到累加器。例如,在8080中,下列字节序列:

将把存储在地址347Bh处的字节装载到累加器。现在把上述指令与6800的LDA指令相比较,后者采用称作6800的扩展地址模式:

该字节序列把存储在地址7B34h处的字节装载到累加器A中。

这种不同点是很微妙的。当然,你也可能认为它们的操作码不同:对8080来说是3Ah,对6800来说是B6h。但主要是这两种微处理器处理紧随操作码后的地址是不同的,8080认为低位在前,高位在后;6800则认为高位在前,低位在后。这种Intel和Motorola微处理器保存多字节数时的根本不同从没有得到解决。直到现在,Intel微处理器在保存多字节数时,仍是最低有效字节在前(即在最低存储地址处);而Motorola微处理器在保存多字节数时,仍是最高有效字节在前。

这两种方法分别叫作little-endian(Intel方式)和big-endian(Motorola方式)。辩论这两种方式的优劣可能是很有趣的,不过在此之前,要知道术语big-endian来自于JonathanSwift的《Gulliver’sTravels》,指的是Lilliput和Blefuscu在每次吃鸡蛋前都要互相碰一下。这种辩论可能是无目的的。先不说哪种方法在本质上说是不是正确的,但这种差别的确当在基于little-endian和big-endian机器的系统之间共享信息时会带来附加的兼容性问题。

这两种微处理器后来怎样了呢?8080用在一些人所谓的第一台个人计算机上,不过可能更准确的说法是第一台家用计算机上。下图是Altair8800,出现在1975年1月份的《PopularElectronics》杂志的封面上。

当你看到Altair8800时,前面面板上的灯泡和开关看起来似乎很熟悉。这和第16章为64KBRAM阵列建议的初始“控制面板”的界面是同一类型的。

8080之后出现了Intel8085,更具意义的是出现了Zilog制造的Z-80芯片。Zilog是Intel公司的竞争对手,是由Intel公司的前雇员,也曾在4004芯片上做出重要贡献的FedericoFaggin建立的。Z-80与8080完全兼容,且增加了许多很有用的指令。1977年,Z-80用于RadioShackTRS-80Model1上。

也是在1977年,由StevenJobs和StephenWozniak建立的苹果计算机公司推出了APPLEIIAPPLEII既不用8080也不用6800,而是使用了采用MOS技术的更便宜的6502芯片,它是对6800的增强。

1978年6月,Intel公司推出了8086,一个16位微处理器,它可访问的存储空间达到1MB8086的操作码与8080不兼容,但它包含乘法和除法指令。一年后,Intel公司又推出了8088,其内部结构与8086相同,但其外部按字节访问存储器,因此该微处理器可使用较流行的为8080设计的8位支持芯片。IBM在其5150个人计算机通常叫作IBMPC—上使用了8088芯片,这种个人计算机在1981年秋季推出。

IBM进军PC市场产生了巨大影响,许多公司都发布了与PC兼容的机器(兼容的含义在随后各章里将要详细讨论)。多年来,“IBMPC兼容机也暗指“Intelinside”,特指所谓x86家族的Intel微处理器。Intelx86家族继续发展,1985年出现了32位的386芯片,1989年出现了486芯片。1993年初,出现了IntelPentium微处理器,普遍地用在PC兼容机上。虽然这些Intel微处理器都不断增加了指令的指令集,但它们仍然支持从8086开始的所有以前处理器的操作码。

苹果公司的Macintosh首次发布于1984年,它使用了Motorola68000—一个16位的微处理器,也即6800的下一代处理器。68000和它的后代(常称为68K系列)是制造出的最受欢迎的一类微处理器。

从1994年开始,Macintosh计算机开始使用PowerPC,一种由Motorola、IBM和Apple公司联合开发的微处理器。PowerPC是由一种称作RISC(精简指令集计算机)的微处理器体系结构来设计的,它试图通过简化某些方面以提高处理器的速度。在RISC计算机中,每条指令通常长度相同,(在PowerPC中为32位),存储器访问只限于装载和保存指令,且指令做简单操作而不是复杂操作。RISC处理器通常有大量的寄存器以避免频繁访问存储器。

因为PowerPC具有完全不同的指令集,所以它不能执行68K的代码。但现在用于APPLEMacintosh的PowerPC微处理器可仿真68K。运行于PowerPC上的仿真程序逐个检验68K程序的每一个操作码,并执行适当的操作。仿真程序不如PowerPC自身代码那样快,但可以工作。

按照摩尔定律,微处理器中的晶体管数量应该每18个月翻一番。这些多增加的晶体管有什么用处呢?

有些晶体管用于增加处理器的数据宽度,从4位到8位到16位再到32位;另外一些增加的晶体管用于处理新的指令。现在大多数微处理器都有用于浮点算术运算的指令(这将在第23章解释);还有一些新增加的指令用来进行一些重复计算,以便在计算机屏幕上显示图片和电影。

现代处理器使用了一些技术用来提高速度,其中之一是流水线技术,处理器在执行一条指令的同时读取下一条指令。由于转移指令会改变执行流程,实际上这样达不到预期效果。现在的处理器还包含一个Cache(高速缓冲存储器),它是做在处理器内部的快速RAM阵列,用于保存最近执行的指令。因为计算机程序经常执行一些小的指令循环,因而Cache可以避免这些指令重复装载。所有这些速度提升措施都需要在处理器中有更多的逻辑器件和晶体管。

如前所述,微处理器只是完整的计算机系统的一部分(尽管是最重要的部分)。我们将在第21章构造这样一个系统,但首先必须学习怎样编码存在存储器中的除了操作码和数字外的其他东西。我们必须返回到小学一年级,再学习一下怎样读写文本。

第二十章  ASCII码和字符映射

数字计算机存储器按位存储,所以,需要在计算机上处理的信息必须按位的形式存储。我们已经知道如何用位来表示数和机器码,下一个问题是如何用它来表示文本。毕境世界上大量堆积的信息是文本形式的,就像装满图书馆的书、杂志和报纸。尽管我们最终要用计算机来存放声音、图像和电影信息,但我们还是以较容易的文本存放开始。

为了以数字形式表示文本,必须开发一些系统使得系统里的每一个字母有唯一的编码。文本中也存在数字和标点符号,所以也必须有它们的编码。简单地说,所有的字母、数字和符号都要编码,这样的系统叫作字符编码集,每一个编码叫作字符编码。

第一个问题是:这些编码需要多少位?这并不是容易回答的问题。

当考虑用位表示文本的时候,需要切合实际。我们习惯于看到书中、报刊、杂志上精美的文本格式,段落按照相同的间隔整齐地分成一行一行,但这些并不是文本的本质。当我们在杂志上看到一个小故事,几年后在一本书中又看到同样故事的时候,我们不会因为书中文本间距的不同而认为是不同的故事。

换句话说,不要以这种印刷成行列的二维格式来看待文本,应该把文本看成是一维的字母、数字和标点符号流,此外,也许还有额外的编码用来表示一段的结束和另一段的开始。

再来看看,如果在杂志上看到一个故事,后来又在书中看到同样的故事但字样有些不同,这是一个大问题吗?如果杂志上的写法为Call me Ishmael而书中的写法为Call me Ishmael这些差别难道是我们真正关心的吗?恐怕不是。印刷样式是微妙地影响了文本的观感,但故事本身并没有因为样式的改变而不同。样式可以经常修改,但不会带来什么影响。

接下来另外一个简化问题的方法是:用平版的文本。没有斜体,没有粗体,没有下划线,没有颜色,没有空心体,没有上下标,没有音调标记,没有等符号,只有99%英语文本里纯粹的拉丁字母。

在对摩尔斯电码和布莱叶盲文的早期研究中,可以看到如何将字母字符表示成二进制的形式。尽管这些系统在特定的场合应用地很好,但用到计算机里都有一些问题。例如:摩尔斯电码是宽度可变的编码:对常用的字符采用短编码,对不常用的字符采用长编码。这样的编码系统适用于电报,但对计算机来说却不合适。另外,摩尔斯电码对字母的大小写没有区分。

布莱叶盲文是宽度固定的编码,很适合计算机。每一个字符由6位表示,也可以区分大小写,尽管它是用特殊的escape码来区分的,该代码表明下一个字符为大写。这也就是说,每个首部字符需要两个代码而不是一个。数字用shift码表示,在这个特定的代码后紧跟的代码被看作表示数字,直到又一个shift码将其转换到字符状态。

我们的目标是开发一个字符编码集,使得像如下的句子I have 27 sisters。可以用一串代码来表示,每一个代码具有一定的位数。一些代码用来表示字母,一些表示标点符号,一些表示数字。甚至有代码来表示字间的空格。上面的句子中有18个字符(包括字间空格),这样一个句子的连续字符代码常称作文本串。

在文本串里,用代码来表示数字(如27)似乎很奇怪,因为前面许多章里已讲过用位来表示数字。我们可能会用简单的二进制数10和111作为该句中2和7的代码,但用在这里是不合适的。该句中,字符2和7可像英文作品中出现的任何一种字符一样来看待,它们可能具有与它们的实际值毫不相干的字符代码。

也许最经济的字符编码是5位编码,它首先用于1874年的电报机,是由法国电报服务公司职员EmileBaudot发明的。他的编码1877年被服务公司采纳,后来由DonaldMurray修改并在1931年被CCITT,即现在的国际电联(ITU)标准化。该编码的正式名称是国际电报字母表NO.2或ITA-2,在美国通常称为Baudot,尽管更科学的叫法为Murray编码。

在20世纪,Baudot经常用于电传打字机。Baudot电传打字机有一个键盘,除了只有30个键和一个间隔棒外,有些像打字机。电传打字机的键实际上是转换器,它产生二进制代码并且通过电传打字机的输出电缆一位紧接一位地传送出去。电传打字机也有打印机制,从电传打字机的输入电缆输入的代码触发电磁铁在纸上打印出字符。

由于Baudot是5位编码,所以总共只有32个代码,这些代码的十六进制值范围从00h~1Fh。下表是32个代码所对应的字母表中的字符:

十六进制码    Baudet字符    十六进制码    Baudet字符

00                               10        E

01             T                 11        Z

02        CarriageReturn(回车)   12        D

03             O                 13        B

04        Space(空格)            14        S

05             H                 15        Y

06             N                 16        F

07             M                 17        X

08        LineFeed(换行)         18        A

09             L                 19        W

0A             R                 1A        J

0B             G                 1B        FigureShift(数字转义)

0C             I                 1C        U

0D             P                 1D        Q

0E             C                 1E        K

0F             V                 1F        LetterShift(字符转义)

代码00h没有指定。其余的31个代码中,26个指定给字母表中的字符,5个用斜体字或短语表示出来了。

代码04h是空格代码,用来分隔不同的字;代码02h和08h表示回车和换行。这些术语来自于电传打字机。当在电传打字机上打字并且到了一行的末尾时,按下一个杠杆或按钮来完成两件事情。第一,使打印头回到开始处,以便从纸的左边开始打印下一行,这是回车。第二,移动打印头紧接至刚完成的那一行的下一行,这是换行。在Baudot中,独立的键产生这两个代码。打印的时候,Baudot电传打字机响应这两个代码,完成相应动作。

在Baudot系统里,如何表示数字和标点符号呢?这就是代码1Bh的作用,在表中标识为数字转义。在数字转义代码之后,所有的代码序列被看作是数字或标点符号,直到遇到字符转义代码(1Fh)再返回到字符状态。下表是数字和标点符号的代码。

十六进制码       Baudot字符      十六进制码     Baudot字符

00                                10                3

01                5               11                +

02        CarriageReturn          12           WhoAreYou?

03                9               13                 ?

04            Space               14                ‘

05                #               15                 6

06                ,             16                 $

07                。              17                 /

08            LineFeed            18                 -

09                )               19                 2

0A                4               1A             Bel(响铃)

0B                &               1B          FigureShift

0C                8               1C                 7

0D                0               1D                 1

0E                :             1E                 (

0F                =               1F          LetterShift

实际上,ITU没有定义代码05h0Bh16h,而是保留为“国家使用”,这个表里列出的是美国的用法。这些代码在某些欧洲国家语言中用作重音符号。响铃代码用来敲响电传打字机上能听见的铃声;“WhoAreYou”代码激活一种机制,用它电传打字机能识别自己。

像摩尔斯电码一样,这5位编码不能区别大、小写。语句

I SPENT  $25  TODAY.

由下面的十六进制数据流来表示:

0C 04 14 0D 10 06 01 04 1B 16 19 01 1F 04 01 03 12 18 15 1B 07 02 08

注意三个转义代码:1Bh在数字的前面,1Fh在数字的后面,最后一部分之前又有1h。该行代码用回车、换行代码来结束。

然而,如果一行两次传送该数据流到电传打印机,将会出现以下情形:

I  SPENT  $25  TODAY.

8 ‘03,5  $25  TODAY.

这是怎么回事?打印机接收到的上一行的最后一个转义代码是数字代码,所以第二行开始的代码被解释成数字。

类似这样的问题是采用转义代码所产生的典型的令人烦恼的结果。尽管Baudot是很经济的编码,但人们可能更想采用能唯一表示字符或标点符号且对大、小写进行区分的代码。

如果想确定比Baudot更好的编码系统需要多少位,只需把各种符号加起来:大小写字母需52个代码,09数字需10个代码,这已经有62个,加上一些标点符号,则超过了64个代码,这意味着需要多于6位的编码。但是距离128个字符数,似乎还有足够的余地。如果超过128个字符,则需要8位编码。

所以答案应该是7。如果不用转换代码来区分大、小写,那么英文里应该用7位来表示字符。

这些字符编码都是什么呢?当然,我们可以随心所欲地编码。如果打算自己制造计算机且计算机的每一个硬件都由自己制造,自己编程且不把自己所造的计算机去与任何其他计算机连接,则可以构造自己的编码,所要做的就是给每一个字符一个唯一的编码。

但是因为很少有独立制造和使用计算机这种情形发生,所以通常是大家遵循并使用同一编码。这样制造出来的计算机就可以与其他计算机兼容,并且可以交换文本信息。

我们可能也不应该随意编码,例如,当在计算机上处理文本时,如果字母表上的字符是按顺序进行编码的,则会带来很多好处,其码这样的顺序使得按字母排序和分类更容易一些。

幸运的是,我们已经有了这样一个标准,即美国信息交换标准代码,简写为ASCII码。它1967年正式公布,此后一直是计算机工业界最为重要的标准。除了一个大的例外(在后面讲到),可以肯定的是,无论什么时候处理文本,总会以某种方式涉及到ASCII码。

ASCII码是7位编码,用二进制代码00000001111111,即十六进制代码00h7Fh来表示。让我们来看ASCII码,但不要从最开始看,因为前32个代码比其余代码理解起来要困难一些。从第二批的32个代码开始讲起,它包括标点符号和10个数字。下表列出了它们的十六进制代码及对应的字符:

十六进制码      ASCII字符       十六进制码       ASCII字符

20                space            30                0

21                !               31                1

22                “               32                2

23                #                33                3

24                $                34                4

25                %                35                5

26                &                36                6

27                ‘                37                7

28                 (                38                8

29                 )                39                9

2A                 *                3A                 :

2B                 +                3B                 ;

2C                  ,               3C                 <

2D                  -                D                 =

2E                  .                3E                >

2F                  /                3F                ?

注意20h是空格符,用来分隔单词和句子。

接下来的32个代码包括大写字母和一些附加的标点符号。除@符号和下划线之外,这些符号在打字机上不经常出现,它们出现在标准的计算机键盘上:

十六进制码      ASCII字符       十六进制码       ASCII字符

40                @                50                P

41                A                51                Q

42                B                52                R

43