从门到CPU

参考资料

Crash Course 计算机视频

计算机的早期历史

美索不达米亚 1000 BC 算盘

time area Instance 进制
1000BC 美索不达米亚 算盘 10
1694 德国 步进计算器 10
before 20 C world 计算表
1822 英国(Charles Babbage) 差分机(论文)
1822-1842 英国(Charles Babbage) 分析机(general computer)
18X(8|9)? 美国(Hollerith IBM 创始人) 打孔卡片制表机

Ada Lovelace 给分析机写了假象程序(第一程序员)

Charles Babbage 设计了分析机,即通用计算机 (计算机之父)

电子计算机

机械继电器(开关),利用电磁线圈的磁场把开关吸下来,联通电路

二极管 (首先出现为热电子管)

三级真空管(二代继电器),每秒客开闭千次

1947 贝尔实验室 晶体管(半导体材料继电器),每秒万次开闭,加州硅谷,出现仙童半导体公司,仙童来变成了英特尔,目前的晶体管体积 50 纳米,每秒上百万次开闭,工作几十年,

time area instance person remarks
1944 IBM Mark II IBM 1947 pulled a bug
1943.12 英国 Bletchley Mark I Tommy Flowers 大规模使用真空管(1600),可编程,解密 Nazi
1941 英国 Bletchley Bombe Alan Turing designd to break Enigma code
1946 ENIAC Pennsylvania 积分,真正意义电子计算机
1958 IBM608 IBM 使用晶体管

布尔逻辑与逻辑门

布尔代数

关于真值的逻辑运算称为布尔代数(Boolean Algebra),以它的创始人布尔命名。

在编程语言中表示 T 值和 F 值的数据类型叫做布尔类型,在 C 语言中通常用int类型来表示,非 0 表示 T,0 表示 F。布尔逻辑是写程序的基本功之一,程序中的很多错误都可以归因于逻辑错误。

1
以下是一些布尔代数的基本定理,为了简洁易读,T和F用1和0表示,AND用*号表示,OR用+号表示(从真值表可以看出AND和OR运算确实有些类似*和+),NOT用¬表示,x、y、z的值可能是0也可能是1。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
¬¬x=x

x*0=0
x+1=1

x*1=x
x+0=x

x*x=x
x+x=x

x*¬x=0
x+¬x=1

x*y=y*x
x+y=y+x

x*(y*z)=(x*y)*z
x+(y+z)=(x+y)+z

x*(y+z)=x*y+x*z
x+y*z=(x+y)*(x+z)

x+x*y=x
x*(x+y)=x

x*y+x*¬y=x
(x+y)*(x+¬y)=x

¬(x*y)=¬x+¬y
¬(x+y)=¬x*¬y

x+¬x*y=x+y
x*(¬x+y)=x*y

x*y+¬x*z+y*z=x*y+¬x*z
(x+y)*(¬x+z)*(y+z)=(x+y)*(¬x+z)

除了第1行之外,这些公式都是每两行一组的,每组的两个公式就像对联一样:把其中一个公式中的*换成+、+换成*、0换成1、1换成0,就变成了与它对称的另一个公式。这些定理都可以通过真值表证明

逻辑门 and or not xor

逻辑门的晶体管实现

VDD 就是电源的意思

and 与门

true true true
true false false
false true false
false false false

or 或门

true true true
true false true
false true true
false false false

not 非门

true false
false true

xor 异或门

true true false
true false true
false true true
false false false

二进制

二进制,逢二进一

27 26 25 24 23 22 21 20
1 0 1 1 0 1 0 0

二进制中,

一个 0 或 1 表示一位(1bit)

8bit = 1byte(字节)

字符的表示

字符表示最简单的方法就是给字母一个编号,就有了 ASCII

为了表示 the world 的所有字符,后来就出现了 Unicode 和 ISO,之后他们俩兼容了,现在常用的编码实现位 utf-8,它是一种可变长的编码,utf-8 编码规则

unicode
1
2
3
4
5
正如上一节所说,世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。为什么电子邮件常常出现乱码?就是因为发信人和收信人使用的编码方式不一样。

可以想象,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是 Unicode,就像它的名字都表示的,这是一种所有符号的编码。

Unicode 当然是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,U+4E25表示汉字严。具体的符号对应表,可以查询unicode.org,或者专门的汉字对应表。
unicode 的问题
1
2
3
4
5
6
7
需要注意的是,Unicode 只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。

比如,汉字严的 Unicode 是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说,这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。

这里就有两个严重的问题,第一个问题是,如何才能区别 Unicode 和 ASCII ?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果 Unicode 统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。

它们造成的结果是:1)出现了 Unicode 的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示 Unicode。2)Unicode 在很长一段时间内无法推广,直到互联网的出现。
utf-8
1
2
3
4
5
6
7
8
9
10
11
12
互联网的普及,强烈要求出现一种统一的编码方式。UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式。其他实现方式还包括 UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个字节表示),不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8 是 Unicode 的实现方式之一。

UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。

UTF-8 的编码规则很简单,只有二条:

1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。

2)对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。

下表总结了编码规则,字母x表示可用编码的位。

Unicode 符号范围(十六进制) UTF-8 编码方式(二进制)
0000 0000-0000 007F 0xxxxxxx
0000 0080-0000 07FF 110xxxxx 10xxxxxx
0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
1
2
3
4
5
跟据上表,解读 UTF-8 编码非常简单。如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。

下面,还是以汉字严为例,演示如何实现 UTF-8 编码。

严的 Unicode 是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800 - 0000 FFFF),因此严的 UTF-8 编码需要三个字节,即格式是1110xxxx 10xxxxxx 10xxxxxx。然后,从严的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,严的 UTF-8 编码是11100100 10111000 10100101,转换成十六进制就是E4B8A5。

引用

字符编码笔记:ASCII,Unicode 和 UTF-8

算数逻辑单元 ALU (Arithmetic & Logic Unit)

计算单元

上面我们介绍了二进制和布尔代数,现在通过硬件保存和布尔运算二进制数,来实现计算单元

加法实现

a half adder 半加器

进行 1bit 运算, 没有进位(十进制满10进1,二进制满2进1),在使用半加器做加法时,两个输入,

040
全加器

full adder 使用两个 half adder 实现

041
8bit adder 8bit位加法
042
043

逻辑单元

进行布尔运算 and or not xor

对输入的信号,进行逻辑运算,结果符合真值表

ALU 抽象

044

寄存器和内存(Registers & RAM)

RAM (内存): Random Access Memory (随机访问存储,意思是可以直接访问到任意内存位置,而不是访问的时候是随机的) Registers (寄存器): 存储一个数字,如果计算机设计时时8位,那么一个寄存器就时一个8位数字存储器

sava 1

把或门输出与输入链接,可以形成一个 1 的永久存储,因为输入始终有一个1

045

sava 0

把与门输出与输入连接,可以形成一个 0 的永久存储,因为输入始终有一个0

046

锁存(AND-OR LATCH)

将上述两个电路结合起来就做成了一个锁存器

047

set 设为 1 后,无论 set 如何改变,输出一直为 1,即 set 将状态置为 1。

reset 设为 1 后,无论 reset 如何改变,输出一直为 0,即 reset 将状态置为 0。

set 和 reset 都为 0 的话,锁存器将会保存最后设置的状态,1bit 的记忆功能就实现了,记忆的结果取决于最后关闭的是谁。

门锁(GATED LATCH)

1bit 的可控制写入存储

048

允许写入线为 0 时,set,reset 恒为 0,不可写入,允许写入线为 1 时,输入 0 将 reset 锁存,存储0,输入 1 将 set 锁存,存储1

抽象出门锁组件

049

寄存器 register

寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。其实寄存器就是一种常用的时序逻辑电路,但这种时序逻辑电路只包含存储电路。寄存器的存储电路是由锁存器或触发器构成的,因为一个锁存器或触发器能存储 1 位二进制数,所以由 N 个锁存器或触发器可以构成 N 位寄存器。寄存器是中央处理器内的组成部分。寄存器是有限存储容量的高速存储部件,它们可用来暂存指令

050

16x16 门锁矩阵

更大位的寄存器

051

启用某个锁存的方式位激活相应的排线和列线,注意,排线和列线并无连接,可以想象他们交界处,排线像拱桥一样越过了列线,另外,排线会连接他对应的那一排的锁存组件,列线会连接他对应的那一列的锁存组件。

052

在允许写入线前加一个与门,与行线和列线相连,就可以具体指向某个锁存,比如第一排,第一列,那么第一排第一列所连接的锁存,它的读写控制线才能真正被启动

054

启用行列线,再启用写入操作线,就可以操作这个锁存,再因为没有行列线的锁存是关闭启用写入线,所以写入操作线就算公用的(1 条线连所有),也可以做到针对某个锁存操作,

055

启用行列线,再启用读取操作线,就可以打开门锁输出线的开关组件,而此时的写入操作线是关闭状态,所以可以放心启用数据输入输出线,来读取到这个锁存的输出

057

启用行列线,再启用写入操作线,就可以开启此所存的写入操作,此时操作数据输入输出线,可以设置锁存的值

056

地址

如何表示操作的是哪个锁存,(我们是使用 16x16 门锁矩阵)

4bit 可以表示 16 个数字(0-15)正好对应16根线,比如0100,意思就是4,就是第5根线(起始值是0000代表第一根线),1111是15代表第16根线。两个16线用8个bit就能表示,前4代表行,后4代表列;8 个 bit 就可以表示一个地址(行,列),例如 3 行 10 列(0011,1010)

多路复用器(multiplexer)

为了将行和列转换为地址,我们需要多路复用器(multiplexer) 多路复用器就是执行将 8bit的一个数字拆成两个4bit,在把4bit的数字,对应到那一根线,让那根线的开关闭合,连通电路

058

内存(memory)

现在这个内存是通过8根线,代表地址,1根线代表数据读写,1根控制可读,1根控制可写。

059

静态随机地址存储(SRAM)

我们将8个1bit的内存并联,操作同一个地址,8根数据线分别写入8个小内存里,另外两根是可读可写线。如此一来,一个可存8位数据的,地址范围是0到255的存储装置就做好了,这也就是RAM(内存)

存储 8bit 数据的方式,8 个比特位用相同的地址分别存储

060

这个是一个 SRAM (static random access memory)

把这个内存单元,看成拥有 256 个地址,每个地址能读写一个 8bit 值,这就是 RAM

061

The Center Processing Unit (CPU)

基础组件

将上面所封装的寄存器和内存拿下来使用,记住我们始终操作的是8位的数据,鉴于我们的设计,我们肯定是需要一个8位的寄存器来存储地址,鉴于我们的读写功能,可能需要多于1个的8位寄存器来存储读取的数据,在一下的内容中,我们希望实现一些计算功能,我们其实此时完成了图灵机的一部分,即,一个存储设备,读写存储设备,访问存储设备的任意位置,目前我们缺少的是程序,而接下来我们定义指令,并在计算时,使用到上面封装的计算单元(ALU)来完成我们的图灵计算机。

063

指令表

我们定义指令,指令由8bit组成,前4位代表操作,后四位代表操作对象(地址,或寄存器),在这个例子中,我们的地址只有4位,(这是为了简化举例,我们上面所组装的8位地址是有用的,现代计算机早已经是64位了,而64位就代表其能操作的数值是64位,能使用的地址远超我们现在的例子,所以当前使用4位的地址仅仅是方便举例)如图所示,LOAD_A的意思是把后四位代表内存位置的值,读取到A寄存器里。

062

cpu 基本架构

我们还需要两个寄存器来完成 CPU,一个用来存储指令地址(本例是 RAM),一个用来存储指令

064

cpu 执行流程

启动计算机,所有寄存器从零开始

065

为了举例我们在 RAM 放了一个程序

取指令

cpu 的第一个阶段叫做取指令阶段FETCH PHASE ,负责拿到指令

  • 指令地址寄存器连到 RAM,此时寄存器地址为0,因此 RAM 返回地址 0 的值,值会复制到指令寄存器里
067

解码指令

cpu 的第二个阶段叫做解码阶段DECODE FHASE,负责解析出要执行什么指令

068

在我们定义的指令中,一个 8 位值,前 4 位代表指令,后 4 位代表参数;对于指令寄存器的值我们需要使用一个控制单元来进行解码

  • 检查指令是不是 LOAD_A,我们可以用很少的逻辑门实现
069

执行指令

知道了是什么指令,我们就可以进入执行阶段

070
  • 电路的连接保证 LOAD_A 正确执行
071
  • 关闭电路,指令地址寄存器+1
072

控制单元

分析完一个指令后,其他的指令也是类似的情况,所以将所有指令封装成一个控制单元

073
  • 对于 ADD 指令我们需要使用 ALU
074
  • 始终以精确的间隔触发电信号,控制单元会利用这个信号,推进 cpu 的内部操作,确保一切按步骤进行,cpu"取指令-->解码-->执行"的速度叫"时钟速度(CLOCK SPEED)" 通过时钟,计算机一步一步得执行,取指令,解码指令,执行指令,读指令... 计算机中运行的是电信号,电的传播速度在我们的认知上是光速,所以cpu的极限频率大概和光速有关吧。
075

CPU

我们把寄存器,ALU ,指令控制封装到一起,到中国人很喜欢提的芯片上,这个芯片就叫CPU

076

指令和程序(Instruction&Programs)

cpu 之所以强大,是因为它是可编程的,写入不同的指令就会执行不同的任务,CPU 是一个可以被软件控制的硬件

这是上一节的例子,四个指令形成了一个加法的操作

RA(register A)

  • RAM 地址 14 的值 加载到 RA(寄存器A)
  • RAM 地址 15 的值加载到 RB(寄存器B)
  • ADD RB & RA ,将结果写入 RA(寄存器A)
  • 将 RA 的值存到 RAM 的 13 地址
077

增加一些指令

078

负数跳JUMP是通过 ALU 的标志位实现的

079

没有 halt,cpu 就会继续执行下去,由于 0 不是指令所以电脑会崩,因为指令和数据都放在内存里,他们在根本上没有区别,都是二进制数,所以 halt 很重要,能区分指令和数据

循环的指令

INST(instruction)

  • RAM14 LOAD RA
  • RAM15 LOAD RB
  • ADD RB & RA into RA
  • JUMP 2 =>INST.ADDR.REGISTER(指令寄存器) change into 2=> EXECUTE RAM 2 INST(指令)
  • ADD RB & RA into RA
  • JUMP 2 =>INST.ADDR.REGISTER(指令寄存器) change into 2=> EXECUTE RAM 2 INST(指令
  • ...

(INFINITE LOOP)

080

有条件的循环

081

SUB(subtract) ALU(Arithmetic & Logic Unit)

  • RAM14 LOAD RA
  • RAM15 LOAD RB
  • SUB RA - RB into RA 11-5=6>0
  • JUMP_NEG 5 => ALU RESULT >0 =>don't EXECUTE
  • JUMP 2=>INST.ADDR.REGISTER change into 2=> EXECUTE RAM2 INST
  • SUB RB RA into RA into RA 6-5=1>0
  • JUMP_NEG 5 => ALU RESULT >0 =>don't EXECUTE
  • JUMP 2=>INST.ADDR.REGISTER change into 2=> EXECUTE RAM2 INST
  • SUB RA - RB into RA 1-5=-4>0
  • JUMP_NEG 5 => ALU RESULT <0 =>INST.ADDR.REGISTER change into 5=> EXECUTE RAM5 INST
  • ADD RB & RA into RA
  • STORE RA into RAM 13
  • HALT (结束)

ALU 没有除法,但是我们可以通过程序来实现,

扩展资料