5.1

image-20250414103254600

image-20250414103339640

Elements

image-20250414103410299

Infomation Flows

  • Element + 三条总线

  • (大概看看,具体的看后面)

image-20250414103512260

The Arithmetic Logic Unit

  • data接入ALU,得到输出
  • 输出值返回数据总线
  • 我觉得是输出out或者回去内存?

image-20250414103725776

  • control总线 告诉ALU进行何种操作
  • ALU 返回告诉 Control 系统其他部分进行什么操作
  • 当 CPU 从程序内存中获取到指令后,会对指令进行解析。对于指令中的控制信息,通过控制总线传出去(让算术逻辑单元、寄存器、内存等部件按要求操作,比如让算术逻辑单元进行加法运算等)
    • 例如如果ALU发现某个数字大于零,他将告诉Control下一个指令的跳转以及下一个指令是什么

image-20250414103745983

Address Register

  • 寄存器储存中间结果。所以数据总线接到Register里
  • 有的寄存器还用作地址储存器,所以register要接入地址总线
    • (将数字,也就是地址放入寄存器中,然后它就指定了我们想要访问的位置。)

image-20250414104302989

Memory

image-20250414104324563

Data Memory

  • Data Memory需要连接数据总线:因为输入和输出(这没问题)

image-20250414104458851

Program Memory

  • 程序内存
  • 需要从PM取出CPU所需要的程序指令,所以连接地址总线
    • 例如,(程序指令是 CPU 找内存要的),CPU 要从程序内存里拿指令时,得先把下一条指令的地址发出去(通过地址总线),程序内存收到地址后,按地址找到指令再传给 CPU(通过数据总线)。

image-20250414104556795

Next

我们在下一单元要做的,是更仔细地研究最内层的循环,也就是我们的硬件应该执行的基本操作:即从内存,程序内存中取出一条指令,并恰当地使用系统的其他部分来执行它。这被称为取指 - 执行周期,这将是下一单元的内容。(一条一条执行程序指令)


5.2 The Fetch-Execute Cycle

The basic CPU loop

一条条 获取-执行 程序指令

image-20250414105855817

获取指令

image-20250414121331533

but下一条指令在哪里找呢?

  • 在内存里

  • 在程序内存里

  • 在程序计数器(program counter)里面(某个寄存器)

    • 它会根据指令的执行情况自动递增或根据跳转指令等进行修改,以指向下一条要取的指令地址

image-20250414121446414

执行指令

  • 有了Instruction,我们需要执行它。

  • 指令代码包含了很多信息(不同位有不同信息)

  • 把取出的指令通过 控制总线 输出到CPU

    • 控制总线 告诉ALU计算什么指令,告诉数据来自哪个寄存器还是哪个数据存储器。

image-20250414121636482

image-20250414121725006

获取-执行 冲突Clash

  • 简单来说,就是因为内存地址输入在同一时刻的唯一性,而取指令和执行指令时都需要使用内存地址来获取不同的内容(指令和数据),从而导致了冲突。

image-20250414121913298

  • 解决方法:一个一个做。通过一个Mux解决(sel:a Fetch/Execute Bit)

image-20250414122048850

image-20250414122147788

  • 更简单的解决方法:把内存分为两个部分,一个部分作为数据存储器,另一个办法作为程序存储器。

image-20250414122213402

Next

  • 这一节,我们讨论了计算机的一般架构

image-20250414122314197

  • 下一节,我们讨论我们的HACK计算机

image-20250414122324359


5.3 Central Processing Unit

CPU: interface and implementation

image-20250414122436406

image-20250414122636301

image-20250414122807022

image-20250414122921132

image-20250414123504372

CPU: Instruction handling

handle A-instruction

  • 地址寄存器

  • 解码:分离操作码(最前面一位,就是instruction[15] )和其他字段(15位的地址或是值)

  • 确定是A指令后,取出后15位值,把它放入A寄存器中。

  • CPU 同时做的另一件事:取出A寄存器的输出(后15位,ins[0..14] ),并通过我们称之为“M地址”的输出将其输出到CPU外部。

image-20250414124010589

handle C-instruction

  • 解码:分离操作码和其他三个字段

image-20250414124307951

A regester – again

  • 我们发现:A寄存器也可以由 ALU的输出 提供数据,而不一定是来自指令输入。(是不是有时候ALU发现该跳转了,所以输出指令给A regester?)
  • 在某些情况下是操作码为0的A指令,在这种情况下,我们希望输入来自指令。
  • 在其他情况下是操作码为1的C指令,在这种情况下,我们希望以某种方式路由A寄存器的输入,使得输入来自ALU。
    • 例如,如果 C 指令是要计算某个数值与 A 寄存器当前值的和,那么 ALU 会从 D 寄存器(或其他数据源)和 A 寄存器获取操作数进行加法运算,运算结果可能需要再存回 A 寄存器,所以就需要将 ALU 的输出路由到 A 寄存器作为其输入。(就不用inM了)

image-20250414124347934

CPU: ALU operation

image-20250414124511512

ALU operation: inputs

  • remember:C指令由不同的位字段组成,每个字段都有不同的含义。

image-20250414124645511

  • ALU 有两个输入源 , 最终输入depend on 控制位c和011111(now)
  • Control bit from the instruction

image-20250414124841120

ALU operation: outputs

  • ALU 的 输出被送到三个地方。

image-20250414125122759

image-20250414125129007image-20250414125141052

  • 我们有三个目的地的位bit,这些bit决定是否打开D寄存器、A寄存器和数据存储器来接受ALU的输出。(即有选择性地接收)

image-20250414125230737

Control bit c’s?

  • 这两个控制位用于记录ALU的输出是负还是零。
  • important for what follows.

image-20250414125405175

CPU:Control (logic

image-20250414125438604

image-20250414130006833

image-20250414130112941

image-20250414130130355

image-20250414130157260

image-20250414130224262

image-20250414130255057

  • if 程序员已经事先将想要跳转到的地址存入了A寄存器。如果我们执行PC等于A,程序计数器将输出接下来必须执行的下一条指令的地址。

image-20250414130323024

  • 如果我们有一个条件跳转,我们必须查看ALU的输出,并决定这个跳转是否应该实际发生。
    • 例如,假设我们有一个条件跳转指令是 “如果寄存器 A 中的值大于寄存器 B 中的值,则跳转到地址 X”。在执行这条指令之前,会先让 ALU 进行 A - B 的运算,然后根据 ALU 的输出结果(如果结果大于 0,表示 A 大于 B)来决定是否跳转到地址 X。

image-20250414130403294

image-20250414193356928

Last :we finish CPU

image-20250414193427312

image-20250414130853170

next

image-20250414130905093


5.4 The Hack Computer

image-20250414131123663

Hack CPU Operation

image-20250414131334263

  • 如果指令中提到了助记符“D”和“A”,CPU就会操作位于CPU内部的相应的D寄存器A寄存器
  • 如果指令是一条A指令,在这种情况下,CPU会取出数据值,即这条指令中所谓的15位的“x”,然后将其放入A寄存器中。
  • 如果指令的右侧(xxx=M)包含 “M”,这个值将从 “inM” 读取。
  • 如果指令的左侧(M=xxx)包含 “M”,那么 ALU 的输出将通过 “outM” 输出,并且 “writeM” 位会→1。

image-20250414131522436

  • 看看CPU如何处理跳转指令

image-20250414131732348

Memory

image-20250414131810280

image-20250414131908596

image-20250414131927630

image-20250414132122759

image-20250414132409978

image-20250414132553181

  • 指令存储器 Instruction Memory (ROM:Read - Only Memory)

  • 在计算机运行之前,需要将编写好的 Hack 程序数据固化到 ROM32K 芯片中

  • 程序中的每一条指令都按照一定的顺序和格式存储在 ROM 的不同存储单元中,每个存储单元都有唯一对应的地址。

  • 在 Hack 计算机运行时,程序计数器(PC)会不断地向 ROM 的地址端口提供地址,ROM 根据这些地址输出相应的指令,然后 CPU 从 ROM 的输出中获取指令并执行,从而实现整个程序的运行。

    • 例如,当 PC 输出地址为 0 时,ROM 会从地址为 0 的存储单元中取出对应的 Hack 指令,传递给 CPU 去执行,然后 PC 再递增,指向下一个地址,ROM 又输出下一条指令,如此循环,使得程序能够按顺序执行。

image-20250414132654354

image-20250414132729661

image-20250414132829344

image-20250414132836410

image-20250414133500747

image-20250414133505468

image-20250414133540257

image-20250414133545883

image-20250414133551712

image-20250414133828858


5.5 code

RAM16K,Screen,Keyboard

background

  • 8K = 2的13次方。16K = 2的14次方。

  • address[15] 表示 address 是一个 16 位的信号。address[15] 是第 1 位,address[0] 就是第 16 位。

  • RAM16K 的地址范围:**(16K)**(0到16383)需要 14 位地址来唯一标识每个存储单元,即 address[0..13] 这 14 位地址。

  • Screen 的地址范围:**(8K)**(16384-24575)需要使用 13 位地址(address[0..12])就可以满足其寻址需求。

  • 可以明显看出,一个地址如果在0-16383内,它就属于 RAM16K 的地址空间;如果在16384-24575内,它就属于 Screen 的地址空间。所以它们的地址是分开的,没有重合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CHIP RAM16K {
IN in[16], load, address[14];
OUT out[16];
// RAM16K是一个存储器,由16千个16-bit寄存器组成。
// 每次输入一个16位的数,RAM16K会根据其address决定将其存储到哪个位置。
// 具体的地址决定方法是:根据address的高2位(address[12..13])选择一个4K的存储块,
// 然后根据低12位(address[0..11])在所选的4K块内确定具体的存储位置。(以此类推,找到the only one)

PARTS:
DMux4Way(in = load,sel = address[12..13],a = a,b = b,c = c,d = d);

RAM4K(in=in , load=a , address=address[0..11] , out=outa );
RAM4K(in=in , load=b , address=address[0..11] , out=outb );
RAM4K(in=in , load=c , address=address[0..11] , out=outc );
RAM4K(in=in , load=d , address=address[0..11] , out=outd );

Mux4Way16(a=outa , b=outb , c=outc , d=outd , sel=address[12..13] , out=out);
}
1
2
3
4
5
6
7
8
9
10
CHIP Screen {

IN in[16], // what to write
load, // write-enable bit
address[13]; // where to read/write
OUT out[16]; // Screen value at the given address

BUILTIN Screen;
CLOCKED in, load;
}
1
2
3
4
CHIP Keyboard{
OUT out[16];
BUILTIN keyboard;
}

Memory

1
2
3
4
5
6
7
8
9
10
11
12
13
CHIP Memory {
IN in[16], load, address[15];
OUT out[16];

PARTS:
DMux4Way(in=load, sel=address[13..14], a=ram1, b=ram2, c=scrn, d=keybd); // 1314??
Or(a=ram1 , b=ram2, out=ram3);
RAM16K(in=in, load=ram3, address=address[0..13], out=ram );
Screen(in=in, load=scrn, address=address[0..12], out=sc);
Keyboard(out=kbd);
Mux4Way16(a=ram, b=ram, c=sc, d=kbd, sel=address[13..14], out=out);

}

CPU

image-20250414122921132 image-20250414123504372

image-20250408152923618image-20250406111659071

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
40
CHIP CPU {

IN inM[16], // M value input (M = contents of RAM[A])
instruction[16], // Instruction for execution
reset; // Signals whether to re-start the current
// program (reset==1) or continue executing
// the current program (reset==0).

OUT outM[16], // M value output
writeM, // Write to M?
addressM[15], // Address in data memory (of M)
pc[15]; // address of next instruction

PARTS:
// A-register 可能执行 A/C 指令(但是c指令只能从alu_out处而来,其实alu本身就能接收指令的,所以第一关下部不给c进只能a进是有原因的)
// A instruction
// @3001
// [0]000101110111001

// 用途一:将常数输入计算机
// 用途二:为 C - 指令提供目标数据内存单元的地址(放入 A 寄存器)
// 用途三:为跳转指令提供目的地址

Not(in=instruction[15] , out=notop );
Mux16(a=aluOut, b=instruction , sel=notop , out=q1 );
Or(a=instruction[5] , b=notop , out=intoA );
ARegister(in=q1, load=intoA, out=A,out[0..14]=addressM); // 同时储存并输出addressM

And(a=instruction[15] , b=instruction[12] , out=AMSwitch );
Mux16(a=A , b=inM , sel=AMSwitch , out=AM );


// C instruction
// i _ _ a c1 c2 c3 c4 c5 c6 d1 d2 d3 j1 j2 j3
// 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
// ins[5]是C指令中的dest指令(=1表示去A寄存器)
// ins[12]是a=0/1,用于在 C指令中 决定是使用 A寄存器的值(A)还是内存的值(inM)(A指令时直接用inM了,有sel a通过不了,inM可以)

And(a=instruction[15] , b=instruction[4] , out=intoD );
DRegister(in=aluOut , load=intoD , out=D ); // D 寄存器:for数据暂存

image-20250408153509769
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
40
41
42
43
44
45
46
47
 // ALU
// ALU takes input from DRegister and from ARegister or inM
// addressM 是用来确定从哪里读取数据,而 inM 是实际读取到的数据。
// ALU的zx,nx,zy,ny,f,no这些控制信号是由操作码 c1 - c6 经过一定逻辑转换得到的,所以C指令进不去A寄存器,但是ALU也是能获得其操作码进行操作的。⭐
ALU(x=D, y=AM, out=aluOut, out=outM,
zx=instruction[11],
nx=instruction[10],
zy=instruction[9],
ny=instruction[8],
f=instruction[7],
no=instruction[6],
zr=zrOut,
ng=ngOut
);

// writeM
And(a=instruction[15] , b=instruction[3] , out=writeM );


// PC
// PC 的 load设置⭐
Not(in=ngOut , out=pos );
Not(in=zrOut , out=nzr );

// 处理JGT条件(>0 jump)
And(a=instruction[15] , b=instruction[0] , out=jgt );
And(a=pos , b=nzr , out=posnzr );
And(a=jgt , b=posnzr , out=ld1 );

// 处理JEQ条件(=0 jump)
And(a=instruction[15] , b=instruction[1] , out=jeq );
And(a=jeq , b=zrOut , out=ld2 );

// 处理JLT条件(<0 jump)
And(a=instruction[15] , b=instruction[2] , out=jlt );
And(a=jlt , b=ngOut , out=ld3 );

//合并跳转条件,生成最终的load
Or(a=ld1 , b=ld2 , out=ldt );
Or(a=ld3 , b=ldt , out=loadd );


PC(in=A , load=loadd , inc=true , reset=reset , out[0..14]=pc);
}

// 内存根据 PC 提供的地址,将对应地址处的指令输出为 instruction。
// 但这并不是 PC 输出后被写入 RAM ,而是 PC 提供一个地址索引,内存根据这个索引将存储在该地址的指令数据输出。
image-20250414193356928 image-20250414130853170

Computer

image-20250414133828858

1
2
3
4
5
6
7
8
9
10
CHIP Computer {

IN reset;

PARTS:
ROM32K(address=pc , out=instruction );
CPU(inM=inM, instruction=instruction , reset=reset , outM=outM , writeM=writeM , addressM=addressM , pc=pc );
Memory(in=outM , load=writeM , address=addressM , out=inM );

}