面向小白:Nand2Tetris(计算机系统要素)Hack计算机实现过程3 - 时序逻辑

1. 项目概况

1.1 项目背景

在前两章(第一章第二章)已经构建了实现布尔运算算数运算的芯片,但它们都是组合芯片,只能计算一类简单的函数:输出结果只依赖于输入变量的排列组合。这些简单芯片只能计算,不能存储,所以还必须配备记忆单元来保存数据。这些记忆单元由时序芯片组成。

1.2 需求清单

使用前述章节已经实现的芯片、原始的数据触发器DFF门、以及在DFF门基础上构建的芯片,用HDL语言实现以下芯片:

时序设备芯片名功能
1位寄存器/比特Bit用来存储1位数据
16位寄存器Register用来存储16位数据
随机存取内存RAM8可以直接访问的记忆单元,由8个16位寄存器组成,配有直接访问电路
RAM64可以直接访问的记忆单元,由64个16位寄存器组成,配有直接访问电路
RAM512可以直接访问的记忆单元,由512个16位寄存器组成,配有直接访问电路
RAM4K可以直接访问的记忆单元,由4K个16位寄存器组成,配有直接访问电路
RAM16K可以直接访问的记忆单元,由16K个16位寄存器组成,配有直接访问电路
计数器PC16位计数器,在每个时钟周期内做简单的+1操作,因此计算机能够获取程序的下一条指令

备注:
本章最基本的时序设备是数据触发器DFF门,它是用于设计所有记忆单元的基本组件。DFF包括1个比特位数据输入和1个比特位数据输出。

芯片名DFF
输入in
输出out
功能out(t) = in(t-1)
说明该DFF门有现成的内置实现版本(built-in implementation),因此没有必要自己去实现

DFF使用反馈回路来连接几个基本逻辑门,它的物理实现是个复杂的任务。因此,硬件仿真器已经将它实现,作为内置芯片使用。DFF的HDL文件位置:.\nand2tetris\tools\builtInChips\DFF.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: tools/builtIn/DFF.hdl

/**
 * Data Flip-flop: out(t) = in(t-1) 
 * where t is the current time unit, or clock cycle.
 */

CHIP DFF {

    IN  in;
    OUT out;

    BUILTIN DFF;
    CLOCKED in;
}

2. 芯片实现过程

2.1 Bit

用于实现1位寄存器的Bit芯片描述如下:

信息项描述
芯片名Bit
输入in, load
输出out
功能If load(t-1) then out(t) = in(t-1) else out(t) = out(t-1)

可以使用Mux和DFF循环实现:
out ( t ) = DFF ( Mux ( out ( t − 1 ) , i n ( t − 1 ) , l o a d ( t − 1 ) ) ) \text{out}(t)=\text{DFF}(\text{Mux}(\text{out}(t-1), in(t-1), load(t-1))) out(t)=DFF(Mux(out(t1),in(t1),load(t1)))

用HDL实现Bit(直接在已有文件中修改,位置为 .\nand2tetris\projects\03\a\Bit.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/03/a/Bit.hdl
/**
 * 1-bit register:
 * If load is asserted, the register's value is set to in;
 * Otherwise, the register maintains its current value.
 * out(t+1) = (load(t), in(t), out(t))
 */
CHIP Bit {
    IN in, load;
    OUT out;

    PARTS:
    // 时序需要循环输入,Mux的输出作为DFF的输入,反之亦然
    Mux (a = out0, b = in, sel = load, out = tempout); // 在t时刻,Mux的输入一个是in(t-1),另一个是in(t),二者择一输出,记为tempout
    DFF (in = tempout, out = out, out = out0); // 在t时刻,DFF的输入是Mux的输出tempout,在t+1时刻,输出为out(t+1),将out(t+1)反馈给Mux,进入下一次循环
}

2.2 Register

用于实现16位寄存器的Register芯片描述如下:

信息项描述
芯片名Bit
输入in[16], load
输出out[16]
功能If load(t-1) then out(t) = in(t-1) else out(t) = out(t-1)
说明“=”是16-位的赋值操作

Register与Bit类似,可以直接使用Bit实现:
out ( t ) = Bit ( i n ( t − 1 ) , l o a d ( t − 1 ) ) \text{out}(t)=\text{Bit}(in(t-1), load(t-1)) out(t)=Bit(in(t1),load(t1))

用HDL实现Register(直接在已有文件中修改,位置为 .\nand2tetris\projects\03\a\Register.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/03/a/Register.hdl
/**
 * 16-bit register:
 * If load is asserted, the register's value is set to in;
 * Otherwise, the register maintains its current value.
 * out(t+1) = (load(t), in(t), out(t))
 */
CHIP Register {
    IN in[16], load;
    OUT out[16];

    PARTS:
    Bit (in = in[0], load = load, out = out[0]);
    Bit (in = in[1], load = load, out = out[1]);
    Bit (in = in[2], load = load, out = out[2]);
    Bit (in = in[3], load = load, out = out[3]);
    
    Bit (in = in[4], load = load, out = out[4]);
    Bit (in = in[5], load = load, out = out[5]);
    Bit (in = in[6], load = load, out = out[6]);
    Bit (in = in[7], load = load, out = out[7]);
    
    Bit (in = in[8], load = load, out = out[8]);
    Bit (in = in[9], load = load, out = out[9]);
    Bit (in = in[10], load = load, out = out[10]);
    Bit (in = in[11], load = load, out = out[11]);
    
    Bit (in = in[12], load = load, out = out[12]);
    Bit (in = in[13], load = load, out = out[13]);
    Bit (in = in[14], load = load, out = out[14]);
    Bit (in = in[15], load = load, out = out[15]);
}

2.3 RAMn

随机存储内存RAMn由n个w位寄存器堆叠而来,这里使用16位寄存器。用于实现RAMn的芯片描述如下:

信息项描述
芯片名RAMn
输入in[16], address[k], load
输出out[16]
功能out(t) = RAM[address(t)](t)
说明“=”是16-位的赋值操作

用于Hack平台的具体芯片及参数如下:

芯片名nk
RAM883
RAM64646
RAM5125129
RAM4K409612
RAM16K1638414

2.3.1 RAM8

RAM8由8个16位寄存器组成,可以使用Register、DMux、Mux实现:

  1. RAM8由8个16位寄存器组成,但不论是读取还是写入操作,一次都只使用一个寄存器。利用DMux8Way,根据address寻址,并将load值存入该寄存器。
  2. 由于不清楚具体调用了哪个寄存器,需要将每个寄存器在in输入下的输出都计算一次。由于load值不同,各个寄存器的输出不同。
  3. 不管load的值是0还是1,RAM8的输出结果都已经存储在address所对应的寄存器的输出值中。因此使用Mux8Way16,根据address寻址,输出该寄存器的输出结果,即是RAM8在输入为in时的输出结果。

用HDL实现RAM8(直接在已有文件中修改,位置为 .\nand2tetris\projects\03\a\RAM8.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/03/a/RAM8.hdl
/**
 * Memory of eight 16-bit registers.
 * If load is asserted, the value of the register selected by
 * address is set to in; Otherwise, the value does not change.
 * The value of the selected register is emitted by out.
 */
CHIP RAM8 {
    IN in[16], load, address[3];
    OUT out[16];

    PARTS:
    // 根据address,将load写入某个寄存器
    DMux8Way (in = load, sel = address, a = a, b = b, c = c, d = d, e = e, f = f, g = g, h = h);
    
    // 根据各个寄存器的load值,计算各个寄存器在输入为in时,输出的情况
    Register (in = in, load = a, out = out1);
    Register (in = in, load = b, out = out2);
    Register (in = in, load = c, out = out3);
    Register (in = in, load = d, out = out4);
    
    Register (in = in, load = e, out = out5);
    Register (in = in, load = f, out = out6);
    Register (in = in, load = g, out = out7);
    Register (in = in, load = h, out = out8);
    
    // 根据address,输出某个寄存器的输出值
    // 在t时刻,当load == 1时,执行写操作,输出值为in(t);当load == 0时,执行读操作,输出值为out(t+1)
    Mux8Way16 (a = out1, b = out2, c = out3, d = out4, e = out5, f = out6, g = out7, h = out8, sel = address, out = out);
}

2.3.2 RAM64

RAM64的实现原理和RAM8类似,可以直接使用RAM8和DMux、Mux实现。
需要注意,一个RAM64由8个RAM8组成,而一个RAM8由8个寄存器组成,直接使用RAM8构建RAM64时,address中各位数值用途如下:

位置表示方法用途
前3位address[0…2]定位到某个RAM64
后3位address[3…5]定位到某个寄存器
  1. 利用DMux8Way,根据address[0…2]寻址,并将load值存入该RAM8。
  2. 由于不清楚具体调用了哪个RAM8,需要将每个RAM8中,address[3…5]位置的寄存器,在in输入下的输出都计算一次。由于load值不同,各个RAM8的输出不同。
  3. 不管load的值是0还是1,RAM64的输出结果都已经存储在address[0…2]所对应的RAM8的输出值中。因此使用Mux8Way16,根据address[0…2]寻址,输出该RAM8的输出结果,即是RAM64在输入为in时的输出结果。

用HDL实现RAM64(直接在已有文件中修改,位置为 .\nand2tetris\projects\03\a\RAM64.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/03/a/RAM64.hdl
/**
 * Memory of sixty four 16-bit registers.
 * If load is asserted, the value of the register selected by
 * address is set to in; Otherwise, the value does not change.
 * The value of the selected register is emitted by out.
 */
CHIP RAM64 {
    IN in[16], load, address[6];
    OUT out[16];

    PARTS:
    // 根据寄存器的address前三位,定位到某个RAM8,将load写入该RAM8
    DMux8Way (in = load, sel = address[0..2], a = a, b = b, c = c, d = d, e = e, f = f, g = g, h = h);

    // 根据各个RAM8的load值,计算各个RAM8在输入为in时,输出的情况
    // 根据寄存器的address后三位,定位到某个寄存器
    RAM8 (in = in, load = a, address = address[3..5], out = out1);
    RAM8 (in = in, load = b, address = address[3..5], out = out2);
    RAM8 (in = in, load = c, address = address[3..5], out = out3);
    RAM8 (in = in, load = d, address = address[3..5], out = out4);
    
    RAM8 (in = in, load = e, address = address[3..5], out = out5);
    RAM8 (in = in, load = f, address = address[3..5], out = out6);
    RAM8 (in = in, load = g, address = address[3..5], out = out7);
    RAM8 (in = in, load = h, address = address[3..5], out = out8);

    // 根据address,输出某个RAM8的输出值
    Mux8Way16 (a = out1, b = out2, c = out3, d = out4, e = out5, f = out6, g = out7, h = out8, sel = address[0..2], out = out);
}

2.3.3 RAM512

RAM512的实现原理和RAM64类似,可以直接使用RAM64和DMux、Mux实现RAM512。
需要注意,一个RAM512由8个RAM64组成,而一个RAM64由64个寄存器组成。直接使用RAM64构建RAM512时,address中各位数值用途如下:

位置表示方法用途
前3位address[0…2]定位到某个RAM64
后6位address[3…8]定位到某个寄存器

用HDL实现RAM512(直接在已有文件中修改,位置为 .\nand2tetris\projects\03\b\RAM512.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/03/b/RAM512.hdl
/**
 * Memory of 512 16-bit registers.
 * If load is asserted, the value of the register selected by
 * address is set to in; Otherwise, the value does not change.
 * The value of the selected register is emitted by out.
 */
CHIP RAM512 {
    IN in[16], load, address[9];
    OUT out[16];

    PARTS:
    // 根据寄存器的address前3位,定位到某个RAM64,将load写入该RAM64
    DMux8Way (in = load, sel = address[0..2], a = a, b = b, c = c, d = d, e = e, f = f, g = g, h = h);

    // 根据各个RAM64的load值,计算各个RAM64在输入为in时,输出的情况
    // 根据寄存器的address后6位,定位到某个寄存器
    RAM64 (in = in, load = a, address = address[3..8], out = out1);
    RAM64 (in = in, load = b, address = address[3..8], out = out2);
    RAM64 (in = in, load = c, address = address[3..8], out = out3);
    RAM64 (in = in, load = d, address = address[3..8], out = out4);
    
    RAM64 (in = in, load = e, address = address[3..8], out = out5);
    RAM64 (in = in, load = f, address = address[3..8], out = out6);
    RAM64 (in = in, load = g, address = address[3..8], out = out7);
    RAM64 (in = in, load = h, address = address[3..8], out = out8);

    // 根据address,输出某个RAM64的输出值
    Mux8Way16 (a = out1, b = out2, c = out3, d = out4, e = out5, f = out6, g = out7, h = out8, sel = address[0..2], out = out);
}

2.3.4 RAM4K

RAM4K的实现原理和RAM64、RAM512类似,可以直接使用RAM512和DMux、Mux实现RAM4K。
需要注意,一个RAM4K由8个RAM512组成,而一个RAM512由512个寄存器组成。直接使用RAM512构建RAM4K时,address中各位数值用途如下:

位置表示方法用途
前3位address[0…2]定位到某个RAM512
后9位address[3…11]定位到某个寄存器

用HDL实现RAM4K(直接在已有文件中修改,位置为 .\nand2tetris\projects\03\b\RAM512.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/03/b/RAM4K.hdl
/**
 * Memory of 4K 16-bit registers.
 * If load is asserted, the value of the register selected by
 * address is set to in; Otherwise, the value does not change.
 * The value of the selected register is emitted by out.
 */
CHIP RAM4K {
    IN in[16], load, address[12];
    OUT out[16];

    PARTS:
    // 根据寄存器的address前3位,定位到某个RAM512,将load写入该RAM512
    DMux8Way (in = load, sel = address[0..2], a = a, b = b, c = c, d = d, e = e, f = f, g = g, h = h);

    // 根据各个RAM512的load值,计算各个RAM512在输入为in时,输出的情况
    // 根据寄存器的address后9位,定位到某个寄存器
    RAM512 (in = in, load = a, address = address[3..11], out = out1);
    RAM512 (in = in, load = b, address = address[3..11], out = out2);
    RAM512 (in = in, load = c, address = address[3..11], out = out3);
    RAM512 (in = in, load = d, address = address[3..11], out = out4);
    
    RAM512 (in = in, load = e, address = address[3..11], out = out5);
    RAM512 (in = in, load = f, address = address[3..11], out = out6);
    RAM512 (in = in, load = g, address = address[3..11], out = out7);
    RAM512 (in = in, load = h, address = address[3..11], out = out8);

    // 根据address,输出某个RAM512的输出值
    Mux8Way16 (a = out1, b = out2, c = out3, d = out4, e = out5, f = out6, g = out7, h = out8, sel = address[0..2], out = out);
}

2.3.5 RAM16K

RAM16K的实现原理和RAM64、RAM512、RAM4K类似,可以直接使用RAM4K和DMux、Mux实现RAM4K。
需要注意,一个RAM16K由4个RAM4K组成,而一个RAM4K由4K个寄存器组成。直接使用RAM4K构建RAM16K时,address中各位数值用途如下:

位置表示方法用途
前2位address[0…1]定位到某个RAM4K
后12位address[2…11]定位到某个寄存器

用HDL实现RAM16K(直接在已有文件中修改,位置为 .\nand2tetris\projects\03\b\RAM512.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/03/b/RAM16K.hdl
/**
 * Memory of 16K 16-bit registers.
 * If load is asserted, the value of the register selected by
 * address is set to in; Otherwise, the value does not change.
 * The value of the selected register is emitted by out.
 */
CHIP RAM16K {
    IN in[16], load, address[14];
    OUT out[16];

    PARTS:
    // 根据寄存器的address前2位,定位到某个RAM4K,将load写入该RAM4K
    DMux4Way (in = load, sel = address[0..1], a = a, b = b, c = c, d = d);

    // 根据各个RAM4K的load值,计算各个RAM4K在输入为in时,输出的情况
    // 根据寄存器的address后12位,定位到某个寄存器
    RAM4K (in = in, load = a, address = address[2..13], out = out1);
    RAM4K (in = in, load = b, address = address[2..13], out = out2);
    RAM4K (in = in, load = c, address = address[2..13], out = out3);
    RAM4K (in = in, load = d, address = address[2..13], out = out4);

    // 根据address,输出某个RAM4K的输出值
    Mux4Way16 (a = out1, b = out2, c = out3, d = out4, sel = address[0..1], out = out);
}

2.4 PC

用于实现16位计数器的PC芯片描述如下:

信息项描述
芯片名PC
输入in[16], inc, load, reset
输出out[16]
功能if (reset(t) == 1) out(t+1) = 0
else if (load(t) == 1) out(t+1) = in(t)
else if (inc(t) == 1) out(t+1) = out(t) + 1
else out(t+1) = out(t)
说明“=”和“+”分别对16位的数值进行赋值操作和加法操作。

可以使用Mux16、Add16和Register实现:

  1. 默认输出out(t+1) = out(t)。
  2. 用Mux16判断参数inc的值,若inc(t) == 1,使用Add16芯片将输出值+1,输出值out(t+1) = out(t) + 1,否则仍输出out(t+1) = out(t)。
  3. 用Mux16判断参数load的值,若load(t) == 1,输出值为out(t+1) = in(t),否则仍按上一步骤输出。
  4. 用Mux16判断参数reset的值,若reset(t) == 1,输出值out(t+1) = 0,否则仍按上一步骤输出。
  5. 使用Register输出结果。

用HDL实现PC(直接在已有文件中修改,位置为 .\nand2tetris\projects\03\a\PC.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/03/a/PC.hdl
/**
 * A 16-bit counter with reset, load, and inc control bits.
 * if      (reset(t) == 1) out(t+1) = 0
 * else if (load(t) == 1)  out(t+1) = in(t)
 * else if (inc(t) == 1)   out(t+1) = out(t) + 1
 * else                    out(t+1) = out(t)
 */

CHIP PC {
    IN in[16], load, inc, reset;
    OUT out[16];

    PARTS:
    // inc(t) == 1时,输出值为out(t+1) = out(t) + 1,否则仍为out(t+1) = out(t)
    Inc16 (in = preout, out = tmpincout);
    Mux16 (a = preout, b = tmpincout, sel = inc, out = incout);
    
    // load(t) == 1时,输出值为out(t+1) = in(t),否则仍按上一步骤输出
    Mux16 (a = incout, b = in, sel = load, out = loadout);
    
    // reset(t) == 1时,输出值为out(t+1) = 0,否则仍按上一步骤输出
    Mux16 (a = loadout, b = false, sel = reset, out = resetout);
    
    // 输出结果
    Register (in = resetout, load = true, out = preout, out = out);
}

3. 总结

3.1 遇到的问题和解决办法

(1)实现Bit时,输出结果需要返回作为输入,一开始不知道怎么实现这个循环。
参考了其他人的代码:需要将输出拷贝一份,再作为输入来实现。其中,用于输入的必须是输出的拷贝,而不能是输出本身。
(2)实现PC时没有思路。
参考了其他人的代码:需要多次利用Mux16,来将输出结果一步步存储到单一变量中,而且和Bit一样,需要将输出作为输入来实现循环。
(3)运行测试的过程较慢,想提高效率。
一开始没有注意界面上的调节速度功能,后来感觉测试太慢,就尝试了一下把这个调节到最快,发现瞬间完成了测试!以后都要调到最快,再也不用等待了!
图1 调节测试速度

3.2 心得体会

整体来说,第三章的难度要明显大于前两章。引入时序概念后,逻辑变得复杂了一些,需要循环才能实现的部分尤其难以理解。只能多动手尝试,实在做不出来就参考他人的代码,多思考。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值