From Nand to Tetris 里的 Project 5 (Memory 部分)

背景

让我们按照 From Nand to TetrisProject 5 的要求,来完成下列的设计。

  1. Memory
  2. CPU
  3. Computer

本文只涉及 Memory 的实现

说明

我是阅读了《计算机系统要素 (第2版)》 第 5 章的内容后才去完成 Project 5 的。读者朋友在完成 Project 5 时,如果遇到不明白的地方,可以参考这本书中的描述。

正文

前往 Nand to Tetris Online IDE,选择 Project 5 里的 Memory ⬇️

我们的目标是实现 Memory (数据存储器),注释中的相关描述如下 ⬇️

text 复制代码
/**
 * 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 and reads 0. 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 lectures and the book.
 */
  • 输入是

    • i n 16 in16 in16
    • l o a d load load
    • a d d r e s s 15 address15 address15
  • 输出是

    • o u t 16 out16 out16
a d d r e s s address address 的范围 应该访问哪里 说明
0 ≤ a d d r e s s < 16384 0\le address \lt 16384 0≤address<16384 RAM \text{RAM} RAM 16384 = 2 14 = 0x4000 16384=2^{14}=\text{0x4000} 16384=214=0x4000
16384 ≤ a d d r e s s < 24576 16384\le address \lt 24576 16384≤address<24576 Screen \text{Screen} Screen 16384 = 2 14 = 0x4000 16384=2^{14}=\text{0x4000} 16384=214=0x4000 24576 = 2 14 + 2 13 = 0x6000 24576=2^{14}+2^{13}=\text{0x6000} 24576=214+213=0x6000
a d d r e s s = 24576 address = 24576 address=24576 Keyboard \text{Keyboard} Keyboard 24576 = 2 14 + 2 13 = 0x6000 24576=2^{14}+2^{13}=\text{0x6000} 24576=214+213=0x6000
a d d r e s s > 24576 address\gt 24576 address>24576 (此时的 a d d r e s s address address 无效) 24576 = 2 14 + 2 13 = 0x6000 24576=2^{14}+2^{13}=\text{0x6000} 24576=214+213=0x6000

1 如果 addressRAM 的范围内

当且仅当 0 ≤ a d d r e s s < 16384 0\le address \lt 16384 0≤address<16384 时, a d d r e s s address address 在 RAM \text{RAM} RAM 的范围内。由于 a d d r e s s address address 是 15 15 15 位的,这就等价于 a d d r e s s 14 = 0 address14=0 address14=0。 a d d r e s s 0..13 address0..13 address0..13 这些位可以是任意值。当 a d d r e s s address address 在 RAM \text{RAM} RAM 的范围内时, a d d r e s s address address 的模式可以表示如下 ⬇️ (下图中用 ? 表示这一位可以是任意值)

可以用一个 非门 来判断 a d d r e s s address address 是否在 RAM \text{RAM} RAM 的范围内。

Not ( i n = address14 , o u t = inRamRange ) \text{Not}(in= \text{address14}, out= \text{inRamRange}) Not(in=address14,out=inRamRange)

这个 非门 的输出是 inRamRange \text{inRamRange} inRamRange

  • inRamRange \text{inRamRange} inRamRange 为 true \text{true} true 时: a d d r e s s address address 在 RAM \text{RAM} RAM 的范围内
  • inRamRange \text{inRamRange} inRamRange 为 false \text{false} false 时: a d d r e s s address address 不在 RAM \text{RAM} RAM 的范围内

我们可以用一个 Mux \text{Mux} Mux 来结合 inRamRange \text{inRamRange} inRamRange 和 load \text{load} load,这个 Mux \text{Mux} Mux 的输出记为 loadRAM \text{loadRAM} loadRAM ⬇️

Mux ( a = false , b = load , s e l = inRamRange , o u t = loadRAM ) \text{Mux}(a= \text{false}, b= \text{load}, sel= \text{inRamRange}, out= \text{loadRAM}) Mux(a=false,b=load,sel=inRamRange,out=loadRAM)

现在可以将 in , loadRAM , address \text{in},\text{loadRAM},\text{address} in,loadRAM,address 连接到 RAM \text{RAM} RAM 上了(我们只需要 a d d r e s s address address 的低 14 14 14 位,即 address0..13 \text{address0..13} address0..13) ⬇️

RAM16K ( i n = in , l o a d = loadRAM , a d d r e s s = address0..13 , o u t = ramOut ) \text{RAM16K}(in= \text{in}, load= \text{loadRAM}, address= \text{address0..13}, out= \text{ramOut}) RAM16K(in=in,load=loadRAM,address=address0..13,out=ramOut)

RAM \text{RAM} RAM 的输出记为 ramOut \text{ramOut} ramOut,我们需要根据 inRamRange \text{inRamRange} inRamRange 的值来判断是否使用 ramOut \text{ramOut} ramOut ⬇️

  • inRamRange \text{inRamRange} inRamRange 为 true \text{true} true 时: 使用 ramOut \text{ramOut} ramOut
  • inRamRange \text{inRamRange} inRamRange 为 false \text{false} false 时: 忽略 ramOut \text{ramOut} ramOut

我们可以用 Mux16 \text{Mux16} Mux16 来选择是否使用 ramOut \text{ramOut} ramOut,这个 Mux16 \text{Mux16} Mux16 的输出记为 outCandidate1 \text{outCandidate1} outCandidate1 ⬇️

Mux16 ( a = false , b = ramOut , s e l = inRamRange , o u t = outCandidate1 ) \text{Mux16}(a= \text{false}, b= \text{ramOut}, sel= \text{inRamRange}, out= \text{outCandidate1}) Mux16(a=false,b=ramOut,sel=inRamRange,out=outCandidate1)

这些代码合起来是这样的 ⬇️

hdl 复制代码
// When and only when address[14] == 0, RAM is loaded
Not(in= address[14], out= inRamRange);
Mux(a= false, b= load, sel= inRamRange, out= loadRAM);
RAM16K(in= in, load= loadRAM, address= address[0..13], out= ramOut);
Mux16(a= false, b= ramOut, sel= inRamRange, out= outCandidate1);

2. 如果 addressScreen 的范围内

当且仅当 16384 ≤ a d d r e s s < 24576 16384\le address \lt 24576 16384≤address<24576 时, a d d r e s s address address 在 Screen \text{Screen} Screen 的范围内。由于 a d d r e s s address address 是 15 15 15 位的,这就等价于以下两个条件都成立 ⬇️

  • address14 = 1 \text{address14}=1 address14=1
  • address13 = 0 \text{address13}=0 address13=0

address0..12 \text{address0..12} address0..12 这些位可以是任意值,下图中用 ? 来表示任意值 ⬇️

可以用一个 非门 来判断 address13 = 0 \text{address13}=0 address13=0 是否成立(这个非门的输出记为 notA13 \text{notA13} notA13) ⬇️

Not ( i n = address13 , o u t = notA13 ) \text{Not}(in= \text{address13}, out= \text{notA13}) Not(in=address13,out=notA13)

在此基础上,可以用一个 与门 来判断以下两者是否都成立

  • address14 = 1 \text{address14}=1 address14=1
  • address13 = 0 \text{address13}=0 address13=0

这个与门的输出记为 inScreenRange \text{inScreenRange} inScreenRange ⬇️

And ( a = address14 , b = notA13 , o u t = inScreenRange ) \text{And}(a= \text{address14}, b= \text{notA13}, out= \text{inScreenRange}) And(a=address14,b=notA13,out=inScreenRange)

我们可以用一个 Mux \text{Mux} Mux 来结合 inScreenRange \text{inScreenRange} inScreenRange 和 load \text{load} load。将 Screen \text{Screen} Screen 的输出记为 screenOut \text{screenOut} screenOut,我们再用一个 Mux \text{Mux} Mux 来选择是否使用 screenOut \text{screenOut} screenOut

  • inScreenRange \text{inScreenRange} inScreenRange 为 true \text{true} true 时: outCandidate2 = screenOut \text{outCandidate2}=\text{screenOut} outCandidate2=screenOut
  • inScreenRange \text{inScreenRange} inScreenRange 为 false \text{false} false 时: outCandidate2 = 00 ⋯ 0 ⏟ n t i m e s 16 个 0 \text{outCandidate2}=\underbrace{00\cdots 0}_{n\rm\ times}^{\text{16 个 0}} outCandidate2=16 个 0 00⋯0

相关代码如下 ⬇️

hdl 复制代码
// When and only when (address[14] == 1) and (address[13] == 0), Screen is loaded
Not(in= address[13], out= notA13);
And(a= address[14], b= notA13, out= inScreenRange);
Mux(a= false, b= load, sel= inScreenRange, out= loadScreen);
Screen(in= in, load= loadScreen, address= address[0..12], out= screenOut);
Mux16(a= false, b= screenOut, sel= inScreenRange, out= outCandidate2);

3 如果 addressKeyboard 的范围内

当且仅当 a d d r e s s = 24576 address=24576 address=24576 时, a d d r e s s address address 在 Keyboard \text{Keyboard} Keyboard 的范围内。 a d d r e s s address address 的每一位的值如下 ⬇️

我们需要判断以下两个条件是否都成立

  • address13 = 0 \text{address13}=0 address13=0
  • address14 = 0 \text{address14}=0 address14=0

可以用一个 与门 来进行判断 ⬇️

And ( a = address13 , b = address14 , o u t = high2BitAnd ) \text{And}(a= \text{address13}, b= \text{address14}, out= \text{high2BitAnd}) And(a=address13,b=address14,out=high2BitAnd)

在此基础上,还需要判断 address0..12 \text{address0..12} address0..12 是否都为 0 0 0。如果逐位去判断,看起来比较繁琐。可以这样考虑,如果 address0..12 \text{address0..12} address0..12 都为 0 0 0 的话,记
sum = address0..14 + 11 ⋯ 1 ⏟ n t i m e s 13 个 1 \text{sum}=\text{address0..14} + \underbrace{11\cdots 1}{n\rm\ times}^{\text{13 个 1}} sum=address0..14+13 个 1 11⋯1
= 11 00 ⋯ 0 ⏟ n t i m e s 13 个 0 + 11 ⋯ 1 ⏟ n t i m e s 13 个 1 =11\underbrace{00\cdots 0}
{n\rm\ times}^{\text{13 个 0}}+\underbrace{11\cdots 1}{n\rm\ times}^{\text{13 个 1}} =1113 个 0 00⋯0+13 个 1 11⋯1
= 11 ⋯ 1 ⏟ n t i m e s 15 个 1 =\underbrace{11\cdots 1}
{n\rm\ times}^{\text{15 个 1}} =15 个 1 11⋯1
= 0 11 ⋯ 1 ⏟ n t i m e s 15 个 1 =0\underbrace{11\cdots 1}_{n\rm\ times}^{\text{15 个 1}} =015 个 1 11⋯1

那么 sum 15 = 0 \text{sum}15=0 sum15=0。在此基础上,可以用一个 与门 来判断以下两者是否都成立

  • high2BitAnd = true \text{high2BitAnd}=\text{true} high2BitAnd=true
  • sum 15 = 0 \text{sum}15=0 sum15=0

将这个 与门 的输出记为 inKbRange \text{inKbRange} inKbRange,对应的实现如下 ⬇️

Add16 ( a 0..14 = address , b 0..12 = true , o u t 15 = msbIsOne ) \text{Add16}(a0..14= \text{address}, b0..12= \text{true}, out15= \text{msbIsOne}) Add16(a0..14=address,b0..12=true,out15=msbIsOne)
Not ( i n = msbIsOne , o u t = msbIsZero ) \text{Not}(in= \text{msbIsOne}, out= \text{msbIsZero}) Not(in=msbIsOne,out=msbIsZero)
And ( a = high2BitAnd , b = msbIsZero , o u t = inKbRange ) \text{And}(a= \text{high2BitAnd}, b= \text{msbIsZero}, out= \text{inKbRange}) And(a=high2BitAnd,b=msbIsZero,out=inKbRange)

Keyboard \text{Keyboard} Keyboard 的输出记为 kbOut \text{kbOut} kbOut,我们可以用一个 Mux \text{Mux} Mux 来选择是否使用 kbOut \text{kbOut} kbOut

  • inKbRange \text{inKbRange} inKbRange 为 true \text{true} true 时: outCandidate3 = kbOut \text{outCandidate3}=\text{kbOut} outCandidate3=kbOut
  • inKbRange \text{inKbRange} inKbRange 为 false \text{false} false 时: outCandidate3 = 00 ⋯ 0 ⏟ n t i m e s 16 个 0 \text{outCandidate3}=\underbrace{00\cdots 0}_{n\rm\ times}^{\text{16 个 0}} outCandidate3=16 个 0 00⋯0

Mux16 ( a = false , b = kbOut , s e l = inKbRange , o u t = outCandidate3 ) \text{Mux16}(a= \text{false}, b= \text{kbOut}, sel= \text{inKbRange}, out= \text{outCandidate3}) Mux16(a=false,b=kbOut,sel=inKbRange,out=outCandidate3)

Keyboard \text{Keyboard} Keyboard 的处理相关的代码如下 ⬇️

hdl 复制代码
Keyboard(out= kbOut);
// When and only when address is 110000000000000 (there are two '1' and thirteen '0'),
// keyboard is loaded
And(a= address[13], b= address[14], out= high2BitAnd);
Add16(a[0..14]= address, b[0..12]= true, out[15]= msbIsOne);
Not(in= msbIsOne, out= msbIsZero);
And(a= high2BitAnd, b= msbIsZero, out= inKbRange);
Mux16(a= false, b= kbOut, sel= inKbRange, out= outCandidate3);

4 将 RAM/Screen/Keyboard 的输出整合在一起

  • RAM \text{RAM} RAM 有关的候选输出在 outCandidate1 \text{outCandidate1} outCandidate1 中
  • Screen \text{Screen} Screen 有关的候选输出在 outCandidate2 \text{outCandidate2} outCandidate2 中
  • Keyboard \text{Keyboard} Keyboard 有关的候选输出在 outCandidate3 \text{outCandidate3} outCandidate3 中

我们可以用两个 Or16 \text{Or16} Or16 对 outCandidate1 , outCandidate2 , outCandidate3 \text{outCandidate1},\text{outCandidate2},\text{outCandidate3} outCandidate1,outCandidate2,outCandidate3 进行或运算 ⬇️

Or16 ( a = outCandidate1 , b = outCandidate2 , o u t = outTemp ) \text{Or16}(a= \text{outCandidate1}, b= \text{outCandidate2}, out= \text{outTemp}) Or16(a=outCandidate1,b=outCandidate2,out=outTemp)
Or16 ( a = outTemp , b = outCandidate3 , o u t = out ) \text{Or16}(a= \text{outTemp}, b= \text{outCandidate3}, out= \text{out}) Or16(a=outTemp,b=outCandidate3,out=out)

将上面的代码都整合到一起,得到完整的 hdl 代码如下 ⬇️

hdl 复制代码
CHIP Memory {
    IN in[16], load, address[15];
    OUT out[16];

    PARTS:
    // When and only when address[14] == 0, RAM is loaded
    Not(in= address[14], out= inRamRange);
    Mux(a= false, b= load, sel= inRamRange, out= loadRAM);
    RAM16K(in= in, load= loadRAM, address= address[0..13], out= ramOut);
    Mux16(a= false, b= ramOut, sel= inRamRange, out= outCandidate1);
    
    // When and only when (address[14] == 1) and (address[13] == 0), Screen is loaded
    Not(in= address[13], out= notA13);
    And(a= address[14], b= notA13, out= inScreenRange);
    Mux(a= false, b= load, sel= inScreenRange, out= loadScreen);
    Screen(in= in, load= loadScreen, address= address[0..12], out= screenOut);
    Mux16(a= false, b= screenOut, sel= inScreenRange, out= outCandidate2);
    
    Keyboard(out= kbOut);
    // When and only when address is 110000000000000 (there are two '1' and thirteen '0'),
    // keyboard is loaded
    And(a= address[13], b= address[14], out= high2BitAnd);
    Add16(a[0..14]= address, b[0..12]= true, out[15]= msbIsOne);
    Not(in= msbIsOne, out= msbIsZero);
    And(a= high2BitAnd, b= msbIsZero, out= inKbRange);
    Mux16(a= false, b= kbOut, sel= inKbRange, out= outCandidate3);
    
    Or16(a= outCandidate1, b= outCandidate2, out= outTemp);
    Or16(a= outTemp, b= outCandidate3, out= out);
}

这样的代码可以通过仿真测试,具体测试步骤如下。先点击 Run \text{Run} Run 按钮,开始测试

测试过程中,会看到以下提示

Click the Keyboard icon and hold down the 'K' key (uppercase) until you see the next message...

我们把 Chip Memory 这个面板滑动到最底部,确保键盘的输入是被允许的(当你能看到 "Disable Keyboard" 时,就处于正确的状态) ⬇️ 此时通过键盘输入 K,就可以继续进行测试了

之后会看到 Screen 中出现两个短的横线,在屏幕最下方会有如下的提示

Two horizontal lines should be in the middle of the screen. Hold down 'Y' (uppercase) until you see the next message ...

此时用键盘输入 Y,就可以继续进行测试。之后会看到如下的提示

Simulation successful: The output file is identical to the compare file

这就说明测试通过了 ⬇️

其他

文中展示 RAM/Screen/Keyboard 地址范围的那些图是如何画出来的?我以 Keyboard 为例,来进行说明。

mermaid.live 页面可以绘制 block 图。具体的语法可以参考 Block Diagrams Documentation 一文。用以下代码可以画出对应的图 ⬇️

text 复制代码
block
    block
        columns 15
        p14["[14]"] p13["[13]"] p12["[12]"] p11["[11]"] p10["[10]"]
        p9["[9]"] p8["[8]"] p7["[7]"] p6["[6]"] p5["[5]"]
        p4["[4]"] p3["[3]"] p2["[2]"] p1["[1]"] p0["[0]"]
        b14["1"] b13["1"] b12["0"] b11["0"] b10["0"]
        b9["0"] b8["0"] b7["0"] b6["0"] b5["0"]
        b4["0"] b3["0"] b2["0"] b1["0"] b0["0"]
    end

style p14 fill:#fff,stroke:#fff;
style p13 fill:#fff,stroke:#fff;
style p12 fill:#fff,stroke:#fff;
style p11 fill:#fff,stroke:#fff;
style p10 fill:#fff,stroke:#fff;
style p9 fill:#fff,stroke:#fff;
style p8 fill:#fff,stroke:#fff;
style p7 fill:#fff,stroke:#fff;
style p6 fill:#fff,stroke:#fff;
style p5 fill:#fff,stroke:#fff;
style p4 fill:#fff,stroke:#fff;
style p3 fill:#fff,stroke:#fff;
style p2 fill:#fff,stroke:#fff;
style p1 fill:#fff,stroke:#fff;
style p0 fill:#fff,stroke:#fff;

style b14 fill:#0f0;
style b13 fill:#0f0;
style b12 fill:#0f0;
style b11 fill:#0f0;
style b10 fill:#0f0;
style b9 fill:#0f0;
style b8 fill:#0f0;
style b7 fill:#0f0;
style b6 fill:#0f0;
style b5 fill:#0f0;
style b4 fill:#0f0;
style b3 fill:#0f0;
style b2 fill:#0f0;
style b1 fill:#0f0;
style b0 fill:#0f0;

参考资料

相关推荐
星心源七境3 天前
七境体系全解析:从六韬兵法到AI锁颜,一套贯穿古典智慧与现代应用的成长操作系统
人工智能·设计模式·设计
广州智造5 天前
如何在HyperMesh的两片相邻体单元间批量创建RBE3实现载荷传递
人工智能·设计·建模·网格·网格划分·hypermesh·前处理
_code_bear_8 天前
如何设计 Agent 场景下的 Prompt
程序员·开源·设计
湖南精循科技8 天前
Ansys 案例研究 | 刹车片应力变形仿真
设计·仿真·ansys·机械·cae·大变形
bryant_meng10 天前
【Design Patterns】23 Design Patterns: The Ultimate Developer‘s Toolkit
设计模式·编程·计算机科学·设计·工程
用户58124415415712 天前
产品经理用AI画原型,代码怎么交付?GemDesign MCP vs Claude Design Handoff 技术对比
设计
等一场雾16 天前
升级一时爽,修 Bug 火葬场:2026 年主流框架升级兼容问题血泪全记录
设计
Yeats_Liao17 天前
5:Servlet程序-Java Web
java·后端·设计
Yunzenn18 天前
深度分析字节最新研究cola-DLM 第 08 章:工程实现评析 —— 优秀实践与改进空间
算法·架构·设计
Ailrid1 个月前
设计模式——创建型设计模式:阅读笔记与个人思考
架构·设计