这一章将详细介绍硬件与软件是如何连结在一起的,以及软件在物理层面是如何表达出来的。
所有的电子设备,从简单的到复杂的,在现实世界里都通过操控电流来实现期望的效果。计算机也不例外。当我们编写软件的时候,我们间接操控了物理层的电流,从而使底下的计算机产生了期望的效果。为了理解这个过程,让我们来考量一下很简单的灯泡。灯泡可以通过开关在开与关两个状态之间周期性地变换:关代表0,开代表1.
然而这样的开关需要人的手动干预,这是个问题。按照之前的描述,我们需要一个基于电压的自动开关。要使电信号可以自动开关,就需要用到由威廉·肖克利、约翰·巴丁和沃尔特·布拉顿发明的晶体管。这项发明开启了整个计算机产业。
本质上,晶体管只是一个阻值可以随输入电压变化的电阻。
有了这样的性质,晶体管就可以用作电流放大器(高电压低阻值)或者根据电压打开关闭电信号(导通或是阻断电流)。输入为0伏时,没有电流可以通过晶体管,因而表现得像是开关处于断开状态的电路(灯泡于是熄灭),因为阻值已经足够阻断电流。类似的当输入为3.5伏时,因为阻值减小电流可以通过晶体管,有效形成了电流,因而表现得像是开关处于闭合状态的电路。(如果您想了解更多关于晶体管的解释,比如电子是如何移动的,您可以访问YouTube,观看Ben Eater的视频“How semiconductors work”)
位(bit)有两个状态:0和1,这是所有数字系统和软件的基石。就好像灯泡可以打开和关闭,位也可以用来自电源的电流来表示:用0伏(没有电流)来表示位0,用正3.5伏到5伏(有电流)来表示位1。晶体管之所以可以准确地表示位,是因为它可以根据输入电压调节电流。
经典晶体管的发明打开了微电子原件的全新世界。在这之前,人们用真空管——一种更花里胡哨的灯泡——表示0和1,而且需要人工打开关闭。MOSFET,全名金属氧化物半导体场效晶体管,1959年由Dawon Kahng与Martin M. (John) Atalla在贝尔实验室发明,是经典晶体管的改良版本,因为在0和1两种状态之间切换更快,更稳定,能耗更小且容易生产,因而更适用于电子设备。
与晶体管类似,MOSFET也有两种类型:n-MOSFET与p-MOSFET。简称NMOS与PMOS。
所有数字元器件都是用逻辑门设计的。逻辑门是一种实现了布尔函数的电路。每个逻辑门包含了数个输入与单个输出。所有计算机操作都是由逻辑门的组合构成的,也就是布尔函数的组合。
逻辑门接受数个二进制输入输入是0或1并产生二进制输出。换句话说,逻辑门是转化二进制值的函数。幸运的是,恰好存在一个数学分支专门解决与二进制值相关的问题,叫做逻辑代数,由乔治·布尔于19世纪引入。有了可靠的数学理论作为基础,逻辑门应运而生。由于逻辑门实现了布尔函数,如果有一组函数可以构成其他所有的布尔函数,我们称这一组布尔函数是函数自足的。后来,查尔斯·桑德斯·皮尔士(在1880到1881年间)证明了可以用或非/与非布尔函数来构建其他所有的布尔逻辑函数。因而或非门和与非门就是函数自足的(Peirce,1933)。门是布尔逻辑函数的实现,因而与非门或者或非门就足够用来实现所有其他逻辑门。CMOS电路可以实现的最简单的门是逆变器(非门),而有了逆变器就有了与非门。有了与非门,我们就有信心实现其余的那些。这就是为什么晶体管,以及之后CMOS电路的发明革新了计算机产业。(如果想要理解与非门为什么以及是如何构建所有布尔函数和计算机的,我推荐Coursera上的Build a Modern Computer from First Principles: From Nand to Tetris课程:https://www.coursera.org/learn/build-a-computer。想要了解更多,学完这个课程可以继续学习Edx上的Computational Structures系列。)
我们应该认识到并感激所有编程语言都提供的布尔函数是多么强大。
每个逻辑门底下都是叫做CMOS——互补式金属氧化物半导体——的电路。CMOS由两个互补的晶体管组成:NMOS与PMOS。最简单的CMOS电路是逆变器或者非门:
图2.2.2 逆变器的电流。输入在左输出在右。靠上的组件是PMOS,靠下的组件时NMOS,二者都连接到输入输出。(来源:https://www.falstad.com/circuit/)
(a) 输入为低电平
(b) 输入为高电平
用非门可以构建与非门:
图2.2.3 与非门的电流。
(a) 输入00,输出1
(b) 输入01,输出1
(b) 输入10,输出1
(b) 输入11,输出0
有了与非门,我们就有了其他所有门。如图所示,这样简单的电路执行了日常编程语言里的逻辑运算符操作,比如说非操作符(~)直接由逆变器电路执行,与操作符(&)由与门电路执行等等。代码并不是运行在魔法黑盒子里边。相反地,代码的执行既准确又透明,常常就像是运行某些接好的电路一样简单。当我们在写软件的时候,我们只是操控了物理层面的电流去连通恰当的电路来产生期望的输出。然而,这整个过程某种程度上与电流本身无关。这才是真正的魔术,稍后我们会解释它。
CMOS的一个有趣性质是有k个输入的门电路会使用k个PMOS和k个NMOS晶体管(Wakerly,1999)。所有的逻辑门都是由成对的NMOS和PMOS晶体管组成,而门是所有无论简单还是复杂的电子元件的基石,包括任意的计算机。多亏了这种模式,我们才有可能把实际的物理电路实现与逻辑实现区分开。数字电路先设计逻辑门,而后“编译”成物理电路。实际上,稍后我们会看到逻辑门成为了描述电路如何工作的语言。理解CMOS是如何工作的对理解计算机是如何设计,进而计算机是如何工作的都非常重要。(再一次,如果您想了解逻辑门是如何构成计算机的,考虑一下先前推荐的Coursera和Edx课程。)
最后,用导线与晶体管实现的电路封装之后就叫做芯片。芯片通过在基板上刻蚀集成电路得到的。然而,芯片也指代消费市场上完全封装好的集成电路。根据不同的上下文,对它的理解也不一样。
图2.2.4 74HC00芯片外观
示例2.2.1 74HC00是一个有着四个双输入与非门的芯片。芯片有八个输入针脚和四个输出针脚,一个针脚用于接电源,一个针脚用于接地。这个元器件是可以实际使用的与非门具体实现。然而它不只是单独一个门,而是可以组合使用的四个门。每种组合都可以构成不同的逻辑函数,组成其他的逻辑门。这个功能让这颗芯片非常受欢迎。
图2.2.5 74HC00逻辑电路图(来源:74HC00规格表https://neurophysics.ucsd.edu/courses/physics_120/74HC00_QUAD_NAND.pdf)
如早前展示了的,以上每一个门电路都是一个简单的与非门,可以有电流通过。然而,许多这样的与非门芯片组合起来就可以组成一个简单的计算机。在物理层面,软件就只是电流。
图2.2.6 用与非门构建的门,每一个门都接受两个输入信号并产生一个输出信号。
如何用74HC00构建上边的门电路?很简单:因为每个门都有两个输入针脚和一个输出针脚,我们可以把一个与非门的输入作为另一个与非门的输入,从而把与非门串起来得到上边的图。
硬件设备由于是基于门构建,而门只接受一系列的0和1,所以也只能理解0和1。然而,设备只以系统化的方式接受0和1。机器语言是一组独特的位模式,设备可以识别并执行对应的操作。而机器指令是设备可以识别的唯一的位模式。在计算机系统内,有着自己机器语言的元器件叫做CPU——中央处理器,它控制着计算机内所有的活动。举个例子,在x86架构内,模式10100000
代表让CPU对两个数相加,而00000101
则命令计算机停机。在计算机时代的早期,人们完全用二进制码写程序。
为什么这样的位模式可以让设备做事情呢?原因是每一个指令下面都是实现了这个指令的一个小型电路。就好像计算机程序中函数/子任务是通过它们的名称调用的,位模式就是CPU可以执行的函数的名称,一旦匹配就立即执行。
要注意,不只是CPU有着自己的机器语言。CPU这个名字只是表示这个硬件设备控制着计算机系统。一个硬件设备可以不是CPU却仍然有自己的机器语言。有着自己机器语言的设备被叫做可编程设备,而用户可以用这种语言来命令设备执行各种操作。举个例子,打印机有自己一套命令来指示自己如何打印页面。
示例2.3.1 用户可以在不了解其内部工作原理,只知道接口的情况下就可以使用74HC00芯片。首先,我们需要知道它的布局:
图2.3.1 74HC00的针脚布局(来源:74HC00规格表https://datasheetspdf.com/pdf-file/948168/NXP/74HC00/1)
然后是每个针脚的功能:
表2.3.1 针脚描述(来源:74HC00规格表https://datasheetspdf.com/pdf-file/948168/NXP/74HC00/1)
标记 | 针脚 | 描述 |
---|---|---|
1A 至 4A | 1, 4, 9, 12 | 数据输入 |
1B 至 4B | 2, 5, 10, 13 | 数据输入 |
1Y 至 4Y | 3, 6, 8, 11 | 数据输出 |
GND | 7 | 接地(0伏) |
Vcc | 14 | 电源 |
最后是如何使用这些针脚:
表2.3.2 功能描述
输入 | 输出 | |
---|---|---|
nA | nB | nY |
L | L | H |
L | X | H |
X | L | H |
H | H | L |
n是数字1,2,3或是4。H = 高电压;L = 低电压;X = 任意
功能描述表是一个真值表,其中包含了所有可能的针脚输入输出值,同时也描述了这个元器件所有针脚的用法。用户并不需要知道元器件的实现,而只要有这张表就可以使用它了。我们可以说,上边的真值表就是这个元器件的机器语言。因为元器件是数字化的,那么它的机器语言是二进制串的集合:
- 元器件有8个输入针脚,这意味着它接受8位的二进制串
- 元器件有4个输出针脚,这意味着它接受8位的二进制串输入、产生4位的二进制串输出
输入二进制串的多少代表元器件可以理解多少,而输出二进制串的多少代表元器件可以表达多少。放在一起,就构成了这个元器件的语言。虽然这个元器件很简单,然而这种语言包含了相当数量的二进制串:28 + 24 = 272。然而,相比于CPU这种有着数百个针脚的复杂元器件,这个数字就小巫见大巫了。
不做任何修改,74HC00就是个有两个4位输入的与非门元器件(或者简称4位与非门,因为它最多只能接受4位的输入。)
输入 | 输出 | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
针脚 | 1A | 1B | 2A | 2B | 3A | 3B | 4A | 4B | 1Y | 2Y | 3Y | 4Y |
值 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 1 |
可视化的输入输出如下:
图2.3.2 接收二进制串对应数字信号的针脚图。绿色表示输入;蓝色表示输出。
另一方面,如果要实现或门,我们只能用74HC00构建双输入的或门,因为它需要三个与非门:两个输入与非门和一个输出与非门。每个输入与非门只能表示或门的1位输入。在下面的表格里,每个输入与非门的针脚永远被设定为同样的值(两个输入要么都是A要么都是B)来表示最终或门的单个位输入:
图2.3.3 二位或门的实现
(a)二位或门逻辑图,用3个4针脚与非门来得到二位的输入。
(b)针脚3A与3B接受1Y和2Y的输出值。
表2.3.3 或门逻辑图的真值表
A | B | C | D | Y |
---|---|---|---|---|
0 | 0 | 1 | 1 | 0 |
0 | 1 | 1 | 0 | 1 |
1 | 0 | 0 | 1 | 1 |
1 | 1 | 0 | 0 | 1 |
要实现四位的或门,我们总共需要4个配置为或门的74HC00芯片,封装成图2.3.4里的单个芯片。
图2.3.4 用四个74HC00元器件组成的4位或芯片
汇编语言是二进制机器码符号化的表现形式,给了所有的位模式一个好记的名字。相比于程序员需要直接写0和1的时代,这是一种极大的进步。举个例子,程序员只需要写hlt
就可以停下计算机,而不用再写00000101
了。这种抽象让CPU指令更好记,进而可以记住更多的指令,减少了查阅CPU手册寻找二进制形式指令的时间,并因此代码的书写速度更快了。
即使是到了今天,理解汇编语言对于底层编程也是必要的。程序员想要理解的指令越多,就需要对机器架构有更深的理解。
示例2.3.2 我们可以用两个汇编指令来构建设备:
or <op1>, <op2>
nand <op1>, <op2>
or
接受两个四位操作数。与之对应的是由四个74HC00芯片构建的四输入或门。nand
接受两个四位操作数。与之对应的是单个74HC00芯片,无需任何改动。
本质上,示例2.3.1中的门实现了这些指令。到了这里,我们只是指定了输入输出,然后手动把它们接入到元器件。也就是说,要执行某个操作:
- 手动选择元器件
- 手动把电信号接入到针脚上
首先,我们要把元器件选择的过程自动化。也就是说,我们只想简单地编写汇编指令,接着实现了这个指令的元器件就被正确选中了。解决这个问题很简单:
-
用二进制机器码给每个指令一个索引值,称为操作码,简称opcode,并且把它作为输入的一部分。表2.3.4指明了每个指令的值。
表2.3.4 指令-opcode对应关系
指令 二进制码 nand 00 or 01 现在,每个输入在起始部分包含了额外的数据:一个opcode。举个例子,指令:
nand 1100, 1100
对应了二进制串:
0011001100
。如前表所示,开头的两位00
是与非指令。 -
引入另一个元器件,根据指令的二进制码来选择设备。
这样的元器件叫做解码器,它是CPU内决定使用哪个电路的重要组件。在上边的例子中,当0011001100
被发送到解码器时,因为opcode是00
,数据被送到与非门去做计算。
最后,编写汇编代码只是一种编写元器件可以理解的二进制串的相对容易的方式。当我们把写好的汇编代码保存到文本文件之后,汇编器程序把这个文本文件翻译成元器件可以理解的二进制串。那么,汇编器又是怎么来的?设想,如果这是世界上第一个汇编器,那么它肯定是用二进制码写的。下一版本,任务就容易多了:程序员用汇编代码写好汇编器,然后用第一个版本编译它自己。这些二进制串可以存储在另一个元器件中,稍后可以取回并发送到解码器去。存储设备就是可以存储机器指令的设备,它是一组用来存储0/1状态的电路。
与其他元器件类似,解码器也是用逻辑门构建的。然而,存储设备可以是任何可以存储0和1、可检索的设备。存储设备可以是利用磁性存储信息的磁化设备,也可以是电路,施加电压就可以记忆和改变状态。无论使用哪种技术,只要设备可以存储、读取数据,这就够了。确实,如今的设备非常复杂,几乎不可能也没有必要去理解每一个实现细节。我们只需要学习设备暴露的接口,比如针脚。
图2.3.5 解码器获取了箭头指向的当前指令,然后选择与非门元器件来执行nand
指令
计算机本质上实现了这样的过程:
- 从存储设备获取指令
- 解码指令
- 执行指令
简单来说,就是一个获取-解码-执行循环。上边的设备非常初级,然而它已经可以代表一台有着获取-解码-执行循环的计算机了。添加更多的元器件,分配更多的opcode,然后相应地更新解码器就可以实现更多的指令。为1961年到1972年的阿波罗宇航计划生产的阿波罗制导计算机,全部由或非门——构建其他逻辑门电路、与非门之外的另一个选择——组成。类似的,如果继续增强我们这台虚拟设备,最终它会成为一台完整功能的计算机。
相比于直接写0和1,汇编语言前进了一步。随着时间推移,人们意识到许多汇编代码的使用模式是重复的。如果可以不用在各个地方反复地写这些重复的代码,而只是用简单的文字形式来指代这些代码就太好了。举个例子,一段汇编代码检查某个变量是不是大于另外一个变量,如果结果为真执行某一段代码,否则执行令一段代码;在C语言里,这样的汇编代码可以用if
表达式代替,它与人类自然语言已经非常接近了。
图2.3.6 重复性的汇编代码模式抽象成了新的语言
人们创建了各种文本形式来代表常见的汇编代码块,例如上边的if
语法。把这些文本翻译成机器代码的程序叫做编译器:
图2.3.7 从上一层语言到下一层语言
编程语言可以实现的任何软件逻辑,硬件都可以实现。反过来也成立:任何用电路实现的硬件逻辑可以用编程语言重新实现。原因很简单:编程语言、汇编语言、机器语言或者逻辑门都是用来描述计算的语言。软件无法实现硬件也实现不了的东西,因为编程语言只是使用底下硬件的一种相对容易的方式。最终,编程语言都被翻译成了合法的CPU机器指令。否则,代码无法运行,这个软件也就毫无用处。反过来,软件可以做任何(运行这个软件的)硬件可以做到的事,因为编程语言只是种相对简单的使用硬件的方式。
现实中,虽然所有的编程语言在能力上都是等价的,然而不是每个编程语言都有能力表达用其他编程语言编写的程序。高阶与低阶编程语言之间有很大的差别。
编程语言越高阶,它与硬件的距离就越远。在某些高阶编程语言里,比如Python,程序员没有办法直接操作底层硬件,哪怕它可以如低阶编程语言一样完成同样的运算。原因在于高阶语言想要隐藏硬件的具体信息,从而解放程序员,不用再去处理与当前问题域无关的一些细节。然而这种便利不是没有代价的:它要求软件包含处理硬件细节的额外代码,因而运行更缓慢,也使得硬件编程变得更困难甚至不可能。编程语言越抽象,编写底层代码就越困难,比如硬件驱动,或是操作系统。这也就是为什么C语言常常用来编写操作系统的原因,因为C语言只是底层硬件的简单封装,使得理解硬件设备是如何运行一段C语言代码的变得很简单。
每一种编程语言都代表了一种思考程序的方式。高阶编程语言关注的是与硬件完全无关的问题域,在这里程序员的效率比计算机的效率更重要。低阶编程语言关注的是计算机的内部工作,因而最适合控制硬件相关的问题域。这就是为什么有那么多编程语言存在的原因。使用正确的工具完成正确的任务才能获得最好的结果。
抽象是一种把与问题无关的复杂度隐藏起来的技术。举个例子,假如写程序的时候只有底层的电路而没有其他任何层次。程序员就不仅需要深入了解电路是如何工作的,也使得设计电路的过程更加模糊,因为设计师面对的是原始电路而脑袋里却想的是诸如逻辑门这样的高阶内容。这个过程非常分散注意力,因为设计师必须不断地把概念转化为各种电路。对于设计师来说首先只把高阶的概念想明白,然后再把这些概念变换成电路是可行的。这样做不只是更有效率,也同时更加准确,因为设计师可以把全部的精力聚焦在用高阶的想法验证设计。如果有新的设计师加入,他可以轻而易举的理解这些高阶设计,于是可以很容易的开发或是维护现有的系统。
在计算机的每一层,抽象都有所体现:
- 逻辑门电路把CMOS的细节抽象掉了
- 机器语言把逻辑门的细节抽象掉了
- 汇编语言把机器语言的细节抽象掉了
- 编程语言把汇编语言的细节抽象掉了
我们反复看到了下一层是如何构建上一层的模式:
- 在下一层有一种反复出现的模式。于是,这种反复出现的模式被抽取出来,并在此之上构建了一种语言。
- 上一层剥离了与下一层相关的(非重复性的)细节,从而关注于重复性的细节。
- 相比于下一层的语言,重复性的细节有了一种新的、更简单的语言。
要认识到,每一层都只是一种描述下一层更方便的语言。只有对事物的描述用上一层语言完全建立的情况下,它才可以用下一层语言实现。
- CMOS层有一种反复出现的模式,可以保证逻辑门电路可以准确地翻译成CMOS电路:有k个输入的门电路需要k个PMOS和k个NMOS晶体管(Wakerly,1999)。由于数字设备只使用CMOS,一种语言在既描述了上一层概念又隐藏了CMOS电路之后应运而生:逻辑门。
- 逻辑门隐藏了电路语言,关注如何实现原始布尔函数以及组合它们以构建新函数。所有的逻辑门以二进制数字格式接受输入、产生输出。多亏了这种反复出现的模式,逻辑门电路被新语言隐藏起来了:汇编语言,它是一组预定义的二进制模式,可以让底下的门执行一个具体动作。
- 很快,人们发现汇编语言里也出现了许多反复出现的模式。汇编源文件里出现了重复的汇编代码块,它们表达对了完全相同或是相似的概念。有许多这样的概念是可以准确地翻译成汇编代码的。因此,这些概念被抽取出来,构建到了今天每个程序员都有学习的高级编程语言里去了。
反复出现的模式是抽象的关键。反复出现的模式是抽象为什么有效的原因。没有它们,就无法构建编程语言,因而就不会有抽象。幸运的是,人们已经发展出一套学习模式的系统性规则:数学。英国数学家戈弗雷·哈罗德·哈代(2005)说:
数学家如画家或诗人一样,是模式的制造者。如果说数学家的模式比画家或是诗人的更经得起考验,那是因为这些模式是由思想创造的。
难道,数学公式不是模式的表现形式么?变量不是在给定条件下有着同样属性的值么?数学为识别和描述自然界中存在的模式提供了一套形式化的系统。由于这个原因,这个系统当然可以被应用到数字化世界,这只是真实世界的一部分。数学作为一种公共语言,可以使层级之间的转换更加容易,也对层级之间的理解有帮助。
图2.4.1 数学是所有层次的通用语言。因为所有层次都可以用数学表达它们的技术,于是每一层都可以翻译到另一层去。
构建新语言进行抽象当然可以提升生产效率,因为这样做剥离了与问题无关的各种细节。设想没有任何其他层次,而只用最底层的电路写程序。就像上边例子展示的一样,当高阶的概念用低阶语言表达的时候,复杂度就这样出现了。不幸的是,这是软件的问题,因为现在的编程语言更着重于软件而不是问题域。也就是说,如果没有先验知识,用某种编程语言写出来的代码没有办法表达出问题域的相关知识。换句话说,如果编程语言的语法是专门为表达它尝试解决的问题域设计的,我们说这种语言是富有表达性的。考虑这样的例子:即,它能做什么而不是它会如何做。
示例2.4.1 Graphviz(https://www.graphviz.org)是一个款可视化软件,它提供了一种叫做dot
的语言,用来描述图:
图2.4.2 从图的描述到图
可以看到,代码完美地表达了图是如何构建的。哪怕不是程序员也可以很容易地理解、运用这样地语言。基于C语言的实现就会更复杂,这还是以假定绘制图的函数已经可用为前提的。画一条线,用C语言写的话大概是这样的:
draw_line(a, b);
然而,与下边这种形式相比还是啰嗦:
a -> b;
并且与dot语言的隐式节点相比,a
和b
还必须用C语言定义。然而,即使我们不考虑啰嗦程度,C语言仍然有局限性:它没有办法改变它的语法来适应问题域。领域特定语言或许更啰嗦,但是它使得领域更好理解。如果问题域必须用C语言来表达,那它会受限于C语言的语法。既然C语言不是某个问题域的特定语言而是一种通用编程语言,相关的领域知识被隐含在了实现细节里。结果就是C语言程序员需要像解谜一样解读、获取领域知识。如果无法获取领域知识,那么这个软件也就没有办法再开发了。
示例2.4.2 Linux有许多被领域特定语言控制的应用程序,它们被放在/etc
目录里,比如Web服务器。相比于重新编写这些软件,它们使用了领域无关的编程语言。
总的来说,可以表达问题域的代码必须可以被领域专家理解。即使在软件领域内部,为重复性的编程模式构建一种语言也是很有用的。它帮助人们了解代码里存在这种模式,并且因此使得软件维护起来更加方便。也只有当编程语言具备调整自己来适应问题域的能力才能达成这个目标。我们把这种语言叫做可编程的编程语言。然而,这种把软件结构暴露出来的方式在程序员中间并不受欢迎,因为因为一种新语言的出现必须要有新的工具链的支持。因而,软件结构以及领域知识被隐藏在了用通用语言的语法编写的代码里,如果程序员不熟悉,甚至不知道这种代码模式的存在,要了解代码是几乎不可能的。一个典型的例子是阅读控制硬件的C语言代码,比如操作系统:如果程序员对硬件一无所知,那么用C语言阅读和编写操作系统代码是根本不可能的,哪怕他可能用C语言写了20年的应用程序代码。
有了抽象,软件工程师无需具备物理电路设计的专业知识也可以理解设备的内部工作机制,就可以写出控制设备的代码。逻辑与物理实现的隔离也意味着门电路设计即使在底层技术变更的情况下可以继续复用。比如说,在遥远的未来生物计算机可能成为现实,门电路可能不再是CMOS实现的,而是某种活的生物细胞;而无论是那种技术:电子的或是生物的,只要逻辑门可以物理实现,就可以实现一模一样的计算机设计。