37、P1 W5 U5.6 总结、作业5答案(待重看)

视频:
如果本次课程对应的 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、(ROM原来自带,不用HDL实现)

CPU 内部 连接图

这里的 红圈C 老师还是让自己琢磨怎么接

RAM 抽象图

也许RAM就是组装一个完整RAM24,寻址就没有疑问了

RAM 内部图




一、CPU.hdl

首先来完成CPU的部分,这块主要参考34、P1 W5 U5.3 中央处理器 的讲解。

回顾CPU内部

按着U5.3的视频

1、首先处理下图跟A Register 有关的部分。

A寄存器部分

这块儿思路,主要围绕A Register 的写入情况。A Register只有两种写入情况。

instruction 分 A指令 和 C指令

一种,如果是A指令,那么直接就是写A寄存器
一种,如果是C指令,就要分情况,如果d1=1,就是写A寄存器

为了方便描述 instruction 的各位,我们可以按照之前P1 W4 U4.4 HACK的机器语言的图表,分别命名。

仔细观察dest表:d1=1 写入A、d2=1 写入D、d3=1 写入M。comp表:a用来选择A还是M 与 D参与计算。jump表:j1=1 out小于0,j2=1 out等于0,j3=1 out大于0

// 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来完成写入

自然想出如下方案。

A指令写入A寄存器

一种,如果是C指令,就要分情况,如果d1=1,就是写A寄存器

C指令,i[15] = 1, d1 = 1(i[5]=1) 那么ALU的输出 写入A寄存器

这里自然想到 用一个And门 使两个 i[15] 和 i[5] 条件都满足时触发load。

加上之前A指令的情况也触发load,这里就想到用Or来合并两种输入。

结合后如下图。

C指令结果写A寄存器

最终可以写代码

// 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可以省略

省略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部分

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的接口:

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 都为1

PS:大于等于 和 小于等于 和上面情况是重复的。

分析 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了。用汇编器翻译一下就好啦。

你可能感兴趣的:(37、P1 W5 U5.6 总结、作业5答案(待重看))