nand2tetris_寄存器

时间

之前的介绍里,我们理所当然的认为有一硬件电路,给其输入,得到输出;我们所构建的只是输入-输出的逻辑映射,没有任何变化的概念。在现实世界中,我们需要引入时间

引入时间的概念

计算机里为什么需要时间。主要有两个原因,我们需要硬件重复运算,而且能够保存中间结果

这又会引入一个新的问题,计算机里如何表示时间

时间的表示

计算机执行的很快,但终究是有限制的,因为电路里电流汇聚需要时间的。现实物理时间中,时间总是滴答滴答的向前走。在计算机中,每一个操作都会带来有延迟,我们不想考虑这些延迟
将连续时间分为一个一个的离散时间周期,时间周期不用设置的太快,保证在一个周期内硬件状态稳定下来即可(灰色区域)。这样在时钟周期结束时,就是我们所认为的此刻系统真实逻辑状态

时序逻辑

电路系统中是有两种电路,一种是组合逻辑(Combinational Logic),其输出只是当前输入的函数,与之前状态无关,无存储功能;另一种是时序逻辑(Sequential Logic),能够存储数据供以后使用,如触发器,寄存器(register,由多个触发器组成)

这里摘抄知乎里的例子加深理解

实现累加计算
s=0;
for (i=0;i<n;i++)
   s = s+X{i};

如果不用触发器,只用组合逻辑,设计为下图。但可以看到这个设计有问题,无法实现结果,因为无法存储中间值,无法控制状态转换

所以就需要触发器存储中间值,结构如下

所以在实际的电路系统中,是由时序逻辑与组合逻辑共同组成。时序逻辑储存中间值,可分割组合逻辑,让每个组合逻辑变成一小块

寄存器

触发器

触发器是实现时序逻辑的一种方式,他和我们之前学习的门电路都不一样,他的输出取决于之前发生的事情以及保存在芯片中的状态

这个芯片的物理实现方式不在这里介绍,实现方式有别于我们常见的电路设计。再举一个例子,说明触发器在数字电路中的作用
你手上有两个一元硬币,需要去自动售货机买一瓶价值2元的水。如果没有触发器,你永远买不到,因为电路记不住你之前投进去的硬币,不管你投多少次,电路都觉得你只投了1元。简而言之,触发器让电路有了记忆的功能
计算机中构建通用逻辑有一个通用的范例,通过触发器集合来记住中间状态,然后使用前两节课中的组合逻辑来操作他们

1比特寄存器

我们已经有触发器了,开始尝试构建寄存器,从最基础的1比特开始。这里定义了简单的API

那么,如何实现呢,这里需要判断load状态,很容易想到选择器,通过不同状态来改变触发器的输出。由于寄存器需要维持上一时刻的状态,所以触发器的输出需要多引一条线至选择器的输入

HDL描述也很简单,还记得之前介绍HDL规范时说的么,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/3/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:
 * if (load(t)) out(t+1) = in(t), else out(t+1) = out(t)
 */
CHIP Bit {
    IN in, load;
    OUT out;

    PARTS:
    Mux(a = dIn, b = in , sel = load, out = muxout);
    DFF(in = muxout, out = dIn, out = out);
}

16位寄存器

有了1bit的寄存器,将16个1bit寄存器放在一起,就组成了16位通用寄存器,尽管现在只能存储1个16位数字

/**
 * 16-bit register:
 * If load is asserted, the register's value is set to in;
 * Otherwise, the register maintains its current value:
 * if (load(t)) out(t+1) = int(t), else out(t+1) = 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]);
}

RAM unit

只能保存一个16位数字还是不够的,我们需要将多个16寄存器组合起来,以存储更多的内容

这里的key同样适用与于当前的内存规则,32位电脑的最大内存限制是4G就是这个原因。当把许多寄存器组合之后,需要考虑的难点就变成了,如何在load值改变的时候选中我们需要的寄存器

RAM读请求

RAM写请求


那么,如何做到呢。这里课程老师提示了一句,请求时,需要应用于所有的寄存器,再通过选择器、分配器来找到合适的位置。有了这样的思路,RAM8、RAM64、RAM512.. 都是一样的道理了

RAM8

/**
 * 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:
    DMux8Way(in=load,sel=address,a=r1,b=r2,c=r3,d=r4,e=r5,f=r6,g=r7,h=r8);
    Register(in=in, load=r1, out=out1);
    Register(in=in, load=r2, out=out2);
    Register(in=in, load=r3, out=out3);
    Register(in=in, load=r4, out=out4);
    Register(in=in, load=r5, out=out5);
    Register(in=in, load=r6, out=out6);
    Register(in=in, load=r7, out=out7);
    Register(in=in, load=r8, out=out8);
    Mux8Way16(a=out1,b=out2,c=out3,d=out4,e=out5,f=out6,g=out7,h=out8,sel=address,out=out);
}

RAM64

/**
 * 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:
    DMux8Way(in=load,sel=address[3..5],a=r1,b=r2,c=r3,d=r4,e=r5,f=r6,g=r7,h=r8);
    RAM8(in=in, load=r1, address=address[0..2], out=out1);
    RAM8(in=in, load=r2, address=address[0..2], out=out2);
    RAM8(in=in, load=r3, address=address[0..2], out=out3);
    RAM8(in=in, load=r4, address=address[0..2], out=out4);
    RAM8(in=in, load=r5, address=address[0..2], out=out5);
    RAM8(in=in, load=r6, address=address[0..2], out=out6);
    RAM8(in=in, load=r7, address=address[0..2], out=out7);
    RAM8(in=in, load=r8, address=address[0..2], out=out8);
    Mux8Way16(a=out1,b=out2,c=out3,d=out4,e=out5,f=out6,g=out7,h=out8,sel=address[3..5],out=out);
}

这里补一句老师的感叹,“也许这节课没有给到这个芯片足够的尊重。因为这款芯片发生了一些非常了不起的事情,不管这个芯片的大小如何,不管它有8个寄存器还是800万个寄存器,我都可以从这个芯片中选择每个寄存器,然后在完全相同的时间内对其进行操作,这确实非常了不起。”,所以数组根据下标索引数据,复杂度相同的原因是不是就来源于此

计数器

这是一个封装组件,实现CPU内部程序计数器的功能。上一节实现的递增组件,就可以应用在这里

HDL实现有点绕的,不仅要判断三个标识位的先后关系,还要考虑往寄存器里写值。我看了之前写的代码,虽然能知道在表达什么意思,但已经不知道当时是怎么想的了,才过去半个月.. 看变量命名,可以想到当时实现还是费了脑子的。所以又把测试脚本执行了一遍,再次感谢课程老师提供的软件包

/**
 * A 16-bit counter.
 * if      reset(t): out(t+1) = 0
 * else if load(t):  out(t+1) = in(t)
 * else if inc(t):   out(t+1) = out(t) + 1
 * else              out(t+1) = out(t)
 */
CHIP PC {
    IN in[16],inc, load, reset;
    OUT out[16];
    
    PARTS:
    Inc16(in=out9,out=out2);

    Mux16(a=out9,b=out2,sel=inc,out=out6);
    Mux16(a=out6,b=in,sel=load,out=out7);
    Mux16(a=out7,b=false,sel=reset,out=out8);

    Register(in=out8,load=true,out=out9);
    And16(a=out9,b=true,out=out);
}

思考

这节课引入的概念有点多,当时学的时候稀里糊涂把测验过了就看下一节。回头来写这篇博客时,才反应过来时序和触发器那里有那么多的知识点。有一点感叹,我本身是通信专业的,照理说这些应该很熟悉的,可是我仿佛是第一次遇见一样。唉,当年是真的一点没学啊

知乎回答

posted @ 2024-03-31 21:43  柠檬水请加冰  阅读(75)  评论(0编辑  收藏  举报