视频:
如果本次课程对应的 Coursera 的视频打不开,可以点击下面链接
P1W5U5.6 - Project 5 Perspectives
1、冯诺依曼架构 (The Von Neumann architecture)与 哈佛架构 (The Harvard architecture)
Hack小电脑就是 哈佛架构的
程序存储 ROM 和 数据存储 RAM 是分开的。
取指令和取数据可以在一个周期里完成(回顾U5.2),教学追求简单,所以选择这个。
一般用在程序不会改变的嵌入式设备里。
不过经典的冯诺依曼架构。
程序和数据 是在一个存储空间里。
需要两个周期来完成一个指令操作,略微复杂。
这个冯诺伊曼架构就比较通用了。
小结一下,目前为止知道:
1、晶振频率以及DFF、RAM跟时序相关的硬件实现电路都被课程隐藏了。
2、BUS硬件实现被隐藏了。
3、屏幕刷新显示、刷新读取SCREEN区的功能被隐藏了。
4、KBD区循环读,以及对应表的硬件实现被隐藏了。
5、ROM被隐藏了,程序是写死的,一个周期内完成取指令和取数据的哈佛结构,如何变成更通用的冯诺伊曼架构呢?
2、有限状态机
没听懂。
3、外设
提到各种 I/O设备、提到CPU会有硬件控制来减少CPU没必要的运算资源、提到显卡也是一种辅助硬件控制设备,有空回顾。
作业:
CPU.hdl
Memory.hdl
Computer.hdl
1、回顾
CPU、RAM、ROM 连接图
CPU 内部 连接图
RAM 抽象图
RAM 内部图
一、CPU.hdl
首先来完成CPU的部分,这块主要参考34、P1 W5 U5.3 中央处理器 的讲解。
按着U5.3的视频
1、首先处理下图跟A Register 有关的部分。
这块儿思路,主要围绕A Register 的写入情况。A Register只有两种写入情况。
一种,如果是A指令,那么直接就是写A寄存器
一种,如果是C指令,就要分情况,如果d1=1,就是写A寄存器
为了方便描述 instruction 的各位,我们可以按照之前P1 W4 U4.4 HACK的机器语言的图表,分别命名。
// instruction[15] = 1 时,为 C指令
And(a=instruction[15], b=instruction[0], out=j3); // j3
And(a=instruction[15], b=instruction[1], out=j2); // j2
And(a=instruction[15], b=instruction[2], out=j1); // j1
And(a=instruction[15], b=instruction[3], out=d3); // d3
And(a=instruction[15], b=instruction[4], out=d2); // d2
And(a=instruction[15], b=instruction[5], out=d1); // d1
And(a=instruction[15], b=instruction[6], out=c6); // c6
And(a=instruction[15], b=instruction[7], out=c5); // c5
And(a=instruction[15], b=instruction[8], out=c4); // c4
And(a=instruction[15], b=instruction[9], out=c3); // c3
And(a=instruction[15], b=instruction[10], out=c2); // c2
And(a=instruction[15], b=instruction[11], out=c1); // c1
And(a=instruction[15], b=instruction[12], out=ca); // a
//instruction[13] 忽略,默认给1
//instruction[14] 忽略,默认给1
一种,如果是A指令,那么直接就是写A寄存器
A指令,i[15] = 0, Mux 需要 sel=1选择b为输出,A寄存器需要 load置1来完成写入
自然想出如下方案。
一种,如果是C指令,就要分情况,如果d1=1,就是写A寄存器
C指令,i[15] = 1, d1 = 1(i[5]=1) 那么ALU的输出 写入A寄存器
这里自然想到 用一个And门 使两个 i[15] 和 i[5] 条件都满足时触发load。
加上之前A指令的情况也触发load,这里就想到用Or来合并两种输入。
结合后如下图。
最终可以写代码
// A Register 部分 好理解版
Not( in = instruction[15], out = not1out );
And( a = instruction[15], b = d1, out = andout );
Mux16( a = ALUout, b = instruction, sel = not1out, out = mux1out );
Or( a = not1out, b = andout, out = or1out );
ARegister( in = mux1out, load = or1out, out = ARout );
发现And可以省略
// A Register 部分 简化版
Not( in = instruction[15], out = not1out );
//And( a = instruction[15], b = d1, out = andout );
Mux16( a = ALUout, b = instruction, sel = not1out, out = mux1out );
Or( a = not1out, b = d1, out = or1out );
ARegister( in = mux1out, load = or1out, out = ARout );
2、然后来看和ALU相关的部分
ALU回顾
P1 W2 U2.4 ALU 算术逻辑单元
这块没有额外电路参与,直接按着ALU的输入输出写就代码,如下
// ALU 部分
DRegister( in = ALUout,load = d2,out = DRout);
Mux16(a = ARout, b = inM, sel = ca, out = mux2out);
ALU(x = DRout, y = mux2out, zx = c1,nx = c2,zy = c3,ny = c4,f = c5,no = c6, out = ALUout, out = outM, zr = zr, ng = ng);
3、最后就是PC部分了。
回顾 PC的接口:
PC 先判断 reset
再判断load
最后reset和load都未触发,就执行inc
// 这里 load 和 inc 需要确认。
PC(in = ARout , reset = reset, load = xxx, inc = xxx, out = PCout)
分析 ALU 的 zr、 ng
ALU出来的 zr =1 代表 ALU的运算结果为 “等于0”
ALU出来的 ng = 1 代表 ALU的运算结果为 “小于0”
ALU的 zr = 0 和 ng = 0,代表ALU的运算结果为 “大于0”
且zr和ng不能都为1
分析 C指令 的 j1,j2,j3
j1 代表 小于0 跳转
j2 代表 等于0 跳转
j3 代表 大于0 跳转
还有一种无条件跳转情况,j1、j2、j3 都为1PS:大于等于 和 小于等于 和上面情况是重复的。
分析 load
load 触发条件就是
zr和ng的情况 和 C指令的 j1j2j3跳转条件吻合就触发。
也就是:
满足 zr=1,ng=0 (等于0)情况 又 满足 是C指令 且 j2 =1,load就触发。
满足 zr=0,ng=1 (小于0)情况 又 满足 是C指令 且 j1 =1,load就触发。
满足 zr=0,ng=0 (大于0)情况 又 满足 是C指令 且 j3 =1,load就触发。
满足 是C指令 且 j1j2j3 都为 1,load就触发。
最后四种情况一“Or”就行了
分析 inc
触发load了,就没有inc了。
要inc了,那load肯定没触发 。
所以inc从load取反就能得到。
PC部分代码如下:
// PC 部分
Not(in = zr, out = notzr);
Not(in = ng, out = notng);
// ALU输出结果 判断
And(a = notzr, b = notng, out = positive);// zr=0, ng=0时,结果大于0
And(a = zr, b = notng, out = zero); // zr=1, ng=0 时,结果等于0
And(a = notzr, b = ng, out = negative); // zr=0,ng=1 时,结果小于0
// 三种跳转情况
And(a = j3, b = positive, out = POSjump);
And(a = j2, b = zero, out = ZRjump);
And(a = j1, b = negative, out = NEGjump);
// 无条件跳转
And(a = j1, b = j2 ,out = and1out);
And(a = and1out , b = j3, out = UCDTjump); //uncondition jump
// 四种跳转情况 满足一种就跳转
Or(a = POSjump, b = ZRjump, out = or2out);
Or(a = or2out, b = NEGjump, out = or3out);
Or(a = or3out, b = UCDTjump, out = or4out);
And(a = instruction[15], b = or4out, out = load);
// 不是跳转,那就自加
Not(in = load, out = inc);
PC(in = ARout , reset = reset, load = load, inc = inc, out[0..14] = pc);
4、其它部分:(outM,writeM,addressM)
分析
outM 代表 ALU的计算结果
writeM 代表 C指令是否要将ALU的计算结果写入RAM
addressM 代表 写入RAM的数据存在哪里(还记得给M赋值,需要A指令参与指定哪个M,所以这块addressM是从ARegister来的)
// 最后有三个跟RAM有关的部分
// ALU输出的结果 是否写入M
And(a=instruction[15], b=d3, out=writeM);
// ALU输出的结果
//And16(a=ALUout, b=ALUout, out=outM); // 这块直接写在ALU里了
// ALU输出的结果 存在哪
And16(a=ARout, b=ARout, out[0..14]=addressM); //这个也可以直接写在ARegister里
5、最终 CPU.hdl 代码:
测试成功
// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/05/CPU.hdl
/**
* The Hack CPU (Central Processing unit), consisting of an ALU,
* two registers named A and D, and a program counter named PC.
* The CPU is designed to fetch and execute instructions written in
* the Hack machine language. In particular, functions as follows:
* Executes the inputted instruction according to the Hack machine
* language specification. The D and A in the language specification
* refer to CPU-resident registers, while M refers to the external
* memory location addressed by A, i.e. to Memory[A]. The inM input
* holds the value of this location. If the current instruction needs
* to write a value to M, the value is placed in outM, the address
* of the target location is placed in the addressM output, and the
* writeM control bit is asserted. (When writeM==0, any value may
* appear in outM). The outM and writeM outputs are combinational:
* they are affected instantaneously by the execution of the current
* instruction. The addressM and pc outputs are clocked: although they
* are affected by the execution of the current instruction, they commit
* to their new values only in the next time step. If reset==1 then the
* CPU jumps to address 0 (i.e. pc is set to 0 in next time step) rather
* than to the address resulting from executing the current instruction.
*/
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:
// Put your code here:
// instruction[15] = 1 时,为 C指令
And(a=instruction[15], b=instruction[0], out=j3); // j3
And(a=instruction[15], b=instruction[1], out=j2); // j2
And(a=instruction[15], b=instruction[2], out=j1); // j1
And(a=instruction[15], b=instruction[3], out=d3); // d3
And(a=instruction[15], b=instruction[4], out=d2); // d2
And(a=instruction[15], b=instruction[5], out=d1); // d1
And(a=instruction[15], b=instruction[6], out=c6); // c6
And(a=instruction[15], b=instruction[7], out=c5); // c5
And(a=instruction[15], b=instruction[8], out=c4); // c4
And(a=instruction[15], b=instruction[9], out=c3); // c3
And(a=instruction[15], b=instruction[10], out=c2); // c2
And(a=instruction[15], b=instruction[11], out=c1); // c1
And(a=instruction[15], b=instruction[12], out=ca); // a
//instruction[13] 忽略,默认给1
//instruction[14] 忽略,默认给1
// A Register 部分 简化版
Not( in = instruction[15], out = not1out );
//And( a = instruction[15], b = d1, out = andout );
Mux16( a = ALUout, b = instruction, sel = not1out, out = mux1out );
Or( a = not1out, b = d1, out = or1out );
ARegister( in = mux1out, load = or1out, out = ARout );
// ALU 部分
DRegister( in = ALUout,load = d2,out = DRout);
Mux16(a = ARout, b = inM, sel = ca, out = mux2out);
ALU(x = DRout, y = mux2out, zx = c1,nx = c2,zy = c3,ny = c4,f = c5,no = c6, out = ALUout, out = outM, zr = zr, ng = ng);
// PC 部分
Not(in = zr, out = notzr);
Not(in = ng, out = notng);
// ALU输出结果 判断
And(a = notzr, b = notng, out = positive);// zr=0, ng=0时,结果大于0
And(a = zr, b = notng, out = zero); // zr=1, ng=0 时,结果等于0
And(a = notzr, b = ng, out = negative); // zr=0,ng=1 时,结果小于0
// 三种跳转情况
And(a = j3, b = positive, out = POSjump);
And(a = j2, b = zero, out = ZRjump);
And(a = j1, b = negative, out = NEGjump);
// 无条件跳转
And(a = j1, b = j2 ,out = and1out);
And(a = and1out , b = j3, out = UCDTjump); //uncondition jump
// 四种跳转情况 满足一种就跳转
Or(a = POSjump, b = ZRjump, out = or2out);
Or(a = or2out, b = NEGjump, out = or3out);
Or(a = or3out, b = UCDTjump, out = or4out);
And(a = instruction[15], b = or4out, out = load); //最后判断一下是不是C指令
// 不是跳转,那就自加
Not(in = load, out = inc);
PC(in = ARout , reset = reset, load = load, inc = inc, out[0..14] = pc);
// 最后有三个跟RAM有关的部分
// ALU输出的结果 是否写入M
And(a=instruction[15], b=d3, out=writeM);
// ALU输出的结果
//And16(a=ALUout, b=ALUout, out=outM); // 这块直接写在ALU里了
// ALU输出的结果 存在哪
And16(a=ARout, b=ARout, out[0..14]=addressM); //这个也可以直接写在ARegister里
}
二、Memory.hdl
Memory 主要由 一个 RAM16K,一个 RAM8K,一个 Register,组成。
老师已经提供了内置的Screen来代替RAM8K,Keyboard来代替Register。
这里关键在如何根据送进来的 15位 address,来选择不同的RAM。
分析
RAM16K (16K 用 14个二进制可以表示)
起始地址:000 0000 0000 0000 (0)
001 xxxx xxxx xxxx
010 xxxx xxxx xxxx
结束地址:011 1111 1111 1111(16383)
SCREEN8K(8K 用 13个二进制可是表示)
起始地址:100 0000 0000 0000(16384)
结束地址:101 1111 1111 1111(24575)
KEYBOARD
单个地址:110 0000 0000 0000(24576)
对比上面看出
address[14] = 0 ,address[13] = 0
address[14] = 0 ,address[13]= 1
时,都是RAM16K的地址范围。
判断完后,address再送入RAM16K地址为address[0..13] //保留14个二进制地址(16k地址范围)
address[14] = 1,address[13] = 0
时,都是Screen的地址范围。
判断完后,address再送入Screen8K地址为address[0..12] //保留13个二进制地址(8k地址范围)
address[14] = 1, address[13] = 1
时,就是Keyboard的地址了
address[14],address[13]
00 //选RAM
01 //选RAM
10 //选Screen
11 //选Keyboard
正好可以用一个 DMux4Way来区分
DMux4Way(in = load, sel = address[13..14], a = loadR1, b = loadR2, c = loadS, d = loadK);
Or(a = loadR1, b = loadR2, out = loadR); // 00、01都是RAM16K
回顾RAM 的接口
RAM16K(in = in, load = loadR, address = address[0..13], out = outR);
Screen(in = in, load = loadS, address = address[0..12], out = outS);
Keyboard( out = outK); //没有load
最后再根据选择的芯片,对应选择谁的输出
Mux4Way16(a = outR, b = outR, c = outS, d = outK, sel = address[13..14], out = out);
Memory完成代码:
测试成功
PS:这里在测试的时候,调成View -> Screen模式,需要检查屏幕中心的两个横条。
// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/05/Memory.hdl
/**
* The complete address space of the Hack computer's memory,
* including RAM and memory-mapped I/O.
* The chip facilitates read and write operations, as follows:
* Read: out(t) = Memory[address(t)](t)
* Write: if load(t-1) then Memory[address(t-1)](t) = in(t-1)
* In words: the chip always outputs the value stored at the memory
* location specified by address. If load==1, the in value is loaded
* into the memory location specified by address. This value becomes
* available through the out output from the next time step onward.
* Address space rules:
* Only the upper 16K+8K+1 words of the Memory chip are used.
* Access to address>0x6000 is invalid. Access to any address in
* the range 0x4000-0x5FFF results in accessing the screen memory
* map. Access to address 0x6000 results in accessing the keyboard
* memory map. The behavior in these addresses is described in the
* Screen and Keyboard chip specifications given in the book.
*/
CHIP Memory {
IN in[16], load, address[15];
OUT out[16];
PARTS:
// Put your code here:
//address[14],address[13]
//00 选RAM
//01 选RAM
//10 选Screen
//11 选Keyboard
//正好可以用一个 DMux4Way来区分
DMux4Way(in = load, sel = address[13..14], a = loadR1, b = loadR2, c = loadS, d = loadK);
Or(a = loadR1, b = loadR2, out = loadR); // 00、01都是RAM16K
//回顾RAM 的接口
RAM16K(in = in, load = loadR, address = address[0..13], out = outR);
Screen(in = in, load = loadS, address = address[0..12], out = outS);
Keyboard(out = outK); //没有load...
//最后再根据选择的芯片,对应选择谁的输出
Mux4Way16(a = outR, b = outR, c = outS, d = outK, sel = address[13..14], out = out);
}
三、Computer.hdl
最后的HACK小电脑就要组装完成啦~
ROM老师给
CPU和Memory刚写完,只要按下图串起来就行了
Computer代码
ROM32K(address=pc, out=instruction);
CPU(inM=memOut, instruction=instruction, reset=reset, outM=outM, writeM=writeM, addressM=addressM, pc=pc);
Memory(in=outM, load=writeM, address=addressM, out=memOut);
测试成功
PS:测试时需要拷贝CPU.hdl 和 Memory.hdl到同目录。另外同目录下除了.tst .cmp文件,还需要三个.hack文件(一个测试ADD,一个测试MAX,一个测试屏幕画方块)。
完整代码:
// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/05/Computer.hdl
/**
* The HACK computer, including CPU, ROM and RAM.
* When reset is 0, the program stored in the computer's ROM executes.
* When reset is 1, the execution of the program restarts.
* Thus, to start a program's execution, reset must be pushed "up" (1)
* and "down" (0). From this point onward the user is at the mercy of
* the software. In particular, depending on the program's code, the
* screen may show some output and the user may be able to interact
* with the computer via the keyboard.
*/
CHIP Computer {
IN reset;
PARTS:
// Put your code here:
ROM32K(address=pc, out=instruction);
CPU(inM=memOut, instruction=instruction, reset=reset, outM=outM, writeM=writeM, addressM=addressM, pc=pc);
Memory(in=outM, load=writeM, address=addressM, out=memOut);
}
WOW,至此我们小电脑就组装完成了。下周就是造出Hack小电脑的汇编器了。这样我们就不用给小电脑直接输入0101111100001010了。用汇编器翻译一下就好啦。