PDF 物理页索引 260
文件中的第 261 页 · 印刷页码:244
第一次循环之后,位于地址0004h和0005h处的16位数等于A7h与1的乘积,这和我们的设计相符。在上图中,地址1003h处的字节通过Load指令载入到累加器,该字节是1Ch。把这个数和001Eh地址的字节相加后,恰好遇到的是Halt指令,当然这是一个合法的数。FFh与1Ch相加的结果与从1Ch中减去1的结果相同,都是1Bh,因为这个数不等于0,所以零标志位是0,1Bh这个结果会存回到1003h地址。下一条要执行的指令是JumpIf Not Zero,零标志位没有置为1,因此发生跳转。接下来要执行的一条指令位于0000h地址。
需要记住的是,Store指令不会影响零标志位的值。只有 Add、Subtract、Add with Carry、Subtract with Borrow 这些指令才能影响零标志位的值,因此它的值与最近执行上述某个指令时所设置的值相同。
经过两次循环后,1004h和1005h地址所保存的16位数等于A7h与2的乘积。1Bh与FFh的和等于1Ah,不为0,因此仍然返回到顶部执行。
当执行到第28次循环时,1004h和1005h地址保存的16 位数等于A7h和1Ch的乘积。1003h地址保存的值是1,它和FFh相加的结果是0,因此零标志位被置位!JumpIfNot Zero 指令不会再跳转到 0000h 地址,相反,下一条要执行的指令即 Halt 指令。这样,我们就完成了全部的工作。
现在可以断言,我们一直不断完善的这组硬件构成的机器确实可以被称为计算机(computer)。当然,它还很原始,但毕竟是一台真正的计算机。条件跳转指令将它与我们以往设计的加法器区别开来,能否控制重复操作或者循环(looping)是计算机(computer)和计算器(calculator)的区别。这里演示了该机器如何用条件跳转实现两个数的乘法运算,用类似的方法还可以进行两个数的除法运算。而且,这不仅仅局限于8位数,它可以对16位、24位、32位,甚至更高位的数进行加、减、乘、除运算。而且,既然它能完成这些运算,那么对于开平方根、取对数、三角函数等运算也完全可以胜任。
既然我们已经装配了一台计算机,因此可以使用计算机相关词汇了。
我们装配的计算机属于数字计算机(digital computer),因为它只处理离散数据。曾经还有一种模拟信号计算机(analog computer),但现在已经非常少见了。(数字数据就是离散数据,即这些数据是一些确定的离散值;模拟数据是连续的,并且在整个取值区间
编码隐匿在计算机软硬件背后的语言
244
PDF 物理页索引 261
文件中的第 262 页 · 印刷页码:245
变化。)
一台数字计算机主要由4部分构成:处理器(processor)、存储器(memory),至少一个输入(input)设备和一个输出(output)设备。我们装配的计算机中,存储器是64KB的RAM阵列,输入和输出设备分别是RAM阵列控制面板上的开关和灯泡。这些开关和灯泡可以让我们向存储器中输入数据,并可以检查运算结果。
除了上述3种设备之外的其他设备都归类于处理器。处理器也被称作中央处理单元(central processing unit)或者CPU。更通俗的说法是将其称作计算机的大脑,但本文将避免使用这样的词,因为我们所设计的处理器称不上是大脑(今天,微处理器这个词使用得非常普遍,它是一种非常小的处理器,可以通过本书第18章讲到的技术来构造它。但本章中通过继电器构造的机器无论如何也称不上“微小”的)。
我们设计的处理器为8位处理器。累加器的宽度为8位,而且大部分数据通路都是8位的宽度。唯一的16位数据通路是RAM阵列的地址通路。如果该通路也采用8位宽度的话,存储器容量最多就只有256字节,而不再是65536字节,这样处理器的功能会受到很大的限制。
处理器包括若干组件。毫无疑问累加器就是其中一个,它只是一个简单锁存器,用来保存处理器内部的部分数据。在我们所设计的计算机中,8位反相器和8位加法器一起构成了算术逻辑单元(Arithmetic Logic Unit),即 ALU。该 ALU只能进行算术运算,最主要的是加法和减法运算。在更加复杂的计算机中(我们会在后面的章节看到),ALU还可以进行逻辑运算,比如“与”(AND),“或”(OR),“异或”(XOR)等。16位的计数器被称做程序计数器(PC,Program Counter)。
我们的计算机是由继电器、电线、开关,以及灯泡构造而成的,这些东西都叫做硬件(hardware)。与之对应,输入到存储器中的指令和数值被称做软件(software)。之所以把“硬”改成了“软”,是因为相对于硬件而言,指令和数据更容易修改。
当我们在计算机领域进行讨论时,“软件”这个词几乎与“计算机程序”(computerprogram),或“程序”(program)等术语是同义的,编写软件也称为计算机程序设计(computer programming)。我们确定用一些指令让计算机实现两个数相乘的过程就是在进行计算机程序设计。
17 自动操作
245
PDF 物理页索引 262
文件中的第 263 页 · 印刷页码:246
通常,在计算机程序中,我们能够把代码(即指令本身)和数据(即代码要处理的数)区别开。但有时它们之间的界限也不是很明显,比如Halt指令(FFh)就可以有两种功能,除了作为代码时表示停止执行外还能代表数值-1。
计算机程序设计有时也被称做编写代码(writing code),或编码(coding),也许你经常会听到:“我整个假期都在编码”,“我一直干到今天早上,敲出了很多行代码”。计算机程序设计人员有时也被称做编码员(coders),尽管有些人可能认为这是一个贬义词。程序员更喜欢被别人称做“软件工程师”(software engineers)。
能够被处理器响应的操作码(比如 Load 指令和 Store指令的代码10h 和11h),称做机器码(machine codes),或机器语言(machine language)。计算机能够理解和响应机器码,其原理和人类能够读写语言是类似的,因此这里使用了“语言”来描述它。
一直以来,我们都在使用很长的短语来引用机器所执行的指令,比如 Add with Carry指令。通常而言,机器码都分配了对应的简短助记符,这些助记符都用大写字母表示,包括2个或3个字符。下面是一系列上述计算机大致能够识别的机器码的助记符。
当这些助记符与另外一对短语结合使用时,其作用更加突出。例如,对于这样一条长语句“把1003h地址处的字节加载到累加器”,我们可以用如下简洁的句子替代:
LOD A, [1003h]
编码隐匿在计算机软硬件背后的语言
246
PDF 物理页索引 263
文件中的第 264 页 · 印刷页码:247
位于助记符右侧的 A和[1003h]称为参数(argument),它们是这个Load 指令的操作对象。参数由两部分组成,左边的操作数称为目标(destination)操作数(A代表累加器),右边的操作数称为源(source)操作数。方括号“[]”表明要加载到累加器的不是1003h这个数值,而是位于存储器地址1003h的数值。
类似的,指令“把001Eh地址的字节加到累加器”,可以简写为:
ADD A, [0010Eh]
指令“把累加器中的内容保存到1003h地址”,可简写为:
STO [1003h], A
注意,在上面的语句中,目标操作数(Store指令在存储器中的位置)仍然在左边,源操作数在右边。这就决定了累加器中的内容必须要保存到存储器的1003h地址。“如果零标志位不是1则跳转到0000h地址处”这个冗长的语句可以简明地表示为:
JNZ 0000h
注意,这里没有使用方括号,这是因为跳转指令要转移到的地址是0000h,而不是保存于0000h地址的值,即0000h地址就是跳转指令的操作数。
用缩写的形式表示指令是很方便的,因为在这种形式下指令以可读的方式顺序列出而不必画出存储器的空间分配情况。通过在一个十六进制地址后面加一个冒号,可以表示某个指令保存在某个特定地址空间,例如:
0000h: LOD A, [1005h]
下面的语句表示了数据在特定地址空间的存储情况。
1000h: 00h, A7h
1002h: 00h, 1Ch
1004h: 00h, 00h
你可能已经注意到了,上面的两个字节都是以逗号分开的,它表示第一个字节保存在左侧的地址空间中,第二个字节保存在该地址后的下一个地址空间中。上面的三条语句等价于下面的这条语句:
1000h: 00h, A7h, 00h, 1Ch, 00h, 00h
17 自动操作
247
PDF 物理页索引 264
文件中的第 265 页 · 印刷页码:248
因此上面讨论的乘法程序可以用如下一系列语句来表示:
使用空格和空行的目的仅仅是为了人们更方便地阅读程序。
在编码时最好不要使用实际的数字地址,因为它们是可变的。例如,如果要把数值保存在存储器的2000h\~2005h地址空间中,你需要在程序中重复多次写这些语句。用标号(label)来指代存储器中的地址空间是个较好的办法。这些标号是一些简单的单词,或是类似单词的字符串。上面的代码可以改写为:
BEGIN:
JNZ BEGIN
编码—隐匿在计算机软硬件背后的语言
248
PDF 物理页索引 265
文件中的第 266 页 · 印刷页码:249
注意,NUM1,NUM2,RESULT这些标号都是指存储器中保存两个字节的地址单元。在这些语句中,NUM1+1,NUM2+1 和 RESULT+1 分别指标号 NUM1,NUM2,RESULT后的第二个字节。注意,NEG1(negative one)用来标记 HLT 指令。
最后,如果你可能忘记这些语句所表示的意思,那么可以在该语句后面加注释(comment),这些注释可以用我们人类的自然语言表述,然后通过分号与程序语句分隔开。
这里给出的是一种计算机程序设计语言,称为汇编语言(assemblylanguage)。它是全数字的机器语言和指令的文字描述的一种结合体。同时它用标号表示存储器地址。人们有时候会混淆机器语言和汇编语言,这是因为它们是对同一种事物的不同描述方式。每一条汇编语句都对应着机器语言中的某些特定字节。
如果你想为本章所设计的计算机编写程序,那么可能首先想到的是用汇编语言来编写(在纸上)。在你确定程序无误并准备验证其运行结果的时候,你需要手工对其汇编:这就意味着要把每一条汇编语句转换成与之对应的机器语言,这仍然要在纸上操作。完成之后,你需要通过开关把这些机器码输入到RAM阵列中并运行该程序,也就是让计算机执行这些指令。
17 自动操作
249
PDF 物理页索引 266
文件中的第 267 页 · 印刷页码:250
对于学习计算机程序设计的人来说,应该尽早了解“错误”(bug)这个术语。当你编写代码时—特别是采用机器语言—是非常容易出错的。输入一个错误的操作数已经很不妙了,如果输错的是一个指令代码的话,情况会怎样呢?当你准备输入10h(Load指令)的时候,却输入了11h(Store指令),造成的后果是:期望的数据不会被机器加载,而该地址的数据还会被累加器中的内容替换掉。
一些错误可能导致意想不到的结果。假设你使用Jump指令跳转到一个地址,而该地址没有存放任何合法的指令,或者你偶然误用Store指令覆盖了其他指令,类似的情况都有可能发生(而且会经常发生)。
甚至上述乘法程序中就存在着一个错误。如果你把程序执行两次,第二次得到的将会是A7h与256相乘的结果,并且程序会把这个结果与第一次运算的结果相加。这是因为程序执行一次之后,1003h地址保存的数值是0。当第二次执行时,FFh与这个0相加的结果不是0,因此程序会继续执行直到它变为0。
我们已经利用该机器完成了乘法运算,用类似的方法也可以进行除法运算。同时,前面也讲过,利用这些基本功能还可以进行平方根、对数、三角函数等运算。机器所需要的仅仅是能够做加、减法的硬件以及利用条件跳转指令执行代码的方法。正如程序员经常挂在嘴边的一句话:“我可以用软件完成其他工作”,这些工作我们都可以编程实现。
当然,软件可能是很复杂的。有很多专门讲授程序员如何用算法(algorithm)解决特殊问题的书,本书不打算讲这些内容。目前我们一直讨论的都是自然数,并没有考虑如何在计算机中表示十进制小数,本书将在第23章讨论这个问题。
前面不止一次强调过,这些硬件部件早在100年前就发明出来了。但是,本章所设计的计算机在当时却并没有被创造出来。当继电器计算机在20世纪30年代中期被设计出来的时候,很多包含在其中的概念还并不为人所知,直到1945年左右世人才开始慢慢了解它们。例如,在当时,人们仍然尝试在计算机中使用十进制数而不是二进制数;而且计算机程序也不是完全存储在存储器中的,有时候它们会被保存在纸带上。特别是在早期的计算机中,存储器非常昂贵并且体积庞大,不论在100年前还是在今天,用500万个电报继电器构建一个64KB的RAM阵列都是令人感到荒唐的事。
编码——隐匿在计算机软硬件背后的语言
250
PDF 物理页索引 267
文件中的第 268 页 · 印刷页码:251
当回顾完计算器和计算机这一段历史,让我们展望一下未来。或许有一天我们会发现:没有必要建造一个如此复杂的继电器计算机。正像在第12章所讨论过的那样,继电器最终会被真空管和晶体管这类电子器件所替代。或许我们还会发现,已经有人创造出一种全新的设备,它的处理器及存储器的能力与我们所设计出的不相上下,但是,这种机器精致小巧,甚至可以放在我们的掌心。
17 自动操作
251
PDF 物理页索引 268
文件中的第 269 页 · 印刷页码:未识别
从算盘到芯片
自古以来,人们为了尽量简化数学计算,绞尽脑汁发明了很多精巧的工具和机器。虽然人类的计数能力与生俱来,但需要帮助是在所难免的。每个人都是各有所长、各有所短,所以经常会遇到一些自身无法解决的问题。
在人类社会早期,人们借助数字这种工具来帮助自己记录物品和财产。包括古希腊以及美洲土著在内的很多文化中,人们借助小卵石或者谷粒进行计数。在欧洲,这些古老的计数方式演变成了计数板,而在中东,则演变成为我们较为熟悉的由骨架和算珠组成的算盘(abacus),算盘的样子就像下面这幅图所示。
尽管人们通常将算盘与亚洲文化,尤其是中国文化联系到一起,但它似乎是在公元1200年左右由商人带到中国的。
很多人不喜欢做乘法和除法,但仍然有一小部分人对此进行了一些研究。苏格兰数学家约翰·纳皮尔(JohnNapier,1550-1617)就是这小部分人中的一员,为了简化某些操作,他发明了对数。例如两个数乘积就可以简单地表示为其对数之和。因此,如果要得到两个数的乘积,可以采取以下步骤:首先在对数表中查出这两个数的值,然后将其值相加,最后在对数表中逆向搜寻出其乘积。
PDF 物理页索引 269
文件中的第 270 页 · 印刷页码:253
在随后的400年时间里,一些最伟大的思想家一直致力于建立对数表这项工作,与此同时,另外一些人则设计出一些小装置期望代替对数表。其中,一种带对数刻度的滑尺久负盛名,它由埃德蒙·甘特(Edmund Gunter,1581-1626)发明,而威廉·奥特雷德(William Oughtred,1574-1660)对其进行了改进。1976年当克鲁夫&艾萨(Keuffel&Esser)公司将其制造的最后一个滑尺赠送给华盛顿史密斯索尼亚(Smithsonian)学院时,滑尺也就宣布退出了历史舞台,导致其退出的原因是手持计算器的出现。
纳皮尔发明了另外一种由刻在骨头、牛角、象牙上的数字条组成的乘法辅助器,称为纳皮尔骨架(Napier's Bones)。在1620年左右,威廉·斯奇卡(Wilhelm Schickard,1592-1635)制造出了最早的机械计算器,它类似于已经初步具备了自动功能的纳皮尔骨架。由相互连接的车轮、齿轮以及水平仪组成的一种计算器几乎在同一时期出现,身为数学家以及哲学家的布莱兹·帕斯卡(BlaisePascal,1623-1662)和哥特福瑞德·武赫勒姆·范·莱布尼兹(Gottfried Wilhelm vonLeibniz,1646-1716)是这种机械计算器的两个最主要的发明者。
你一定对8位加法器以及能对多于8位的数进行运算的加法器中的进位过程记忆犹新,因为这实在是太烦琐了。起初,进位仅被看做是加法运算中的“一碟小菜”,但它却成了加法器的核心问题。换句话讲,尽管我们设计了一个除了进位功能以外其他功能都俱全的加法器,这样的器件离“大功告成”还是很远!
评价老式计算器的一个关键是其进位处理能否成功,例如帕斯卡的设计,他的进位机制禁止进行减法运算,在进行减法运算时,采用的是加上9的补数的方式(在第13章中介绍过)。直到19世纪后半叶,真正意义上的机械计算器才得以出现并为人们所使用。
约瑟夫·玛丽·杰奎德(Joseph Marie Jacquard,1752-1834)发明的一种奇妙的自动织布机对计算的历史产生了深远的影响,其程度甚至不亚于其在纺织行业产生的影响。杰奎德织布机(大约于1801年出现)使用打孔的金属卡片(很像钢琴上面的金属卡片)控制织物上的图案。杰奎德使用了大约1万张卡片完成了一幅杰作,那就是用黑白线织
18 从算盘到芯片
253