让我们按照 From Nand to Tetris 里 Project 5 的要求,来完成下列的设计。
Memory
CPU
Computer
本文只涉及 Memory 的实现
说明
我是阅读了《计算机系统要素 (第2版)》 第 5 章的内容后才去完成 Project 5 的。读者朋友在完成 Project 5 时,如果遇到不明白的地方,可以参考这本书中的描述。书中第 65 页有如下内容,可供参考。
正文
前往 Nand to Tetris Online IDE ,选择 Project 5 里的 Memory ⬇️
我们的目标是实现 Memory (数据存储器),注释中的相关描述如下 ⬇️
/**
* 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 ] in[16] in [ 16 ]
l o a d load l o a d
a d d r e s s [ 15 ] address[15] a dd ress [ 15 ]
输出是
a d d r e s s address a dd ress 的范围应该访问哪里 说明 0 ≤ a d d r e s s < 16384 0\le address \lt 16384 0 ≤ a dd ress < 16384 RAM \text{RAM} RAM 16384 = 2 14 = 0x4000 16384=2^{14}=\text{0x4000} 16384 = 2 14 = 0x4000 16384 ≤ a d d r e s s < 24576 16384\le address \lt 24576 16384 ≤ a dd ress < 24576 Screen \text{Screen} Screen 16384 = 2 14 = 0x4000 16384=2^{14}=\text{0x4000} 16384 = 2 14 = 0x4000 24576 = 2 14 + 2 13 = 0x6000 24576=2^{14}+2^{13}=\text{0x6000} 24576 = 2 14 + 2 13 = 0x6000 a d d r e s s = 24576 address = 24576 a dd ress = 24576 Keyboard \text{Keyboard} Keyboard 24576 = 2 14 + 2 13 = 0x6000 24576=2^{14}+2^{13}=\text{0x6000} 24576 = 2 14 + 2 13 = 0x6000 a d d r e s s > 24576 address\gt 24576 a dd ress > 24576 (此时的 a d d r e s s address a dd ress 无效) 24576 = 2 14 + 2 13 = 0x6000 24576=2^{14}+2^{13}=\text{0x6000} 24576 = 2 14 + 2 13 = 0x6000
1 如果 address 在 RAM 的范围内
1.1 判断 address 是否在 RAM 的范围内
当且仅当 0 ≤ a d d r e s s < 16384 0\le address \lt 16384 0 ≤ a dd ress < 16384 时,a d d r e s s address a dd ress 在 RAM \text{RAM} RAM 的范围内。由于 a d d r e s s address a dd ress 是 15 15 15 位的,这就等价于 a d d r e s s [ 14 ] = 0 address[14]=0 a dd ress [ 14 ] = 0 。a d d r e s s [ 0..13 ] address[0..13] a dd ress [ 0..13 ] 这些位可以是任意值。当 a d d r e s s address a dd ress 在 RAM \text{RAM} RAM 的范围内时,a d d r e s s address a dd ress 的模式可以表示如下 ⬇️ (下图中用 ? 表示这一位可以是任意值)
可以用一个 非门 来判断 a d d r e s s address a dd ress 是否在 RAM \text{RAM} RAM 的范围内。
Not ( i n = address[14] , o u t = inRamRange ) \text{Not}(in= \text{address[14]}, out= \text{inRamRange}) Not ( in = address[14] , o u t = inRamRange )
这个 非门 的输出是 inRamRange \text{inRamRange} inRamRange
inRamRange \text{inRamRange} inRamRange 为 true \text{true} true 时: a d d r e s s address a dd ress 在 RAM \text{RAM} RAM 的范围内
inRamRange \text{inRamRange} inRamRange 为 false \text{false} false 时: a d d r e s s address a dd ress 不在 RAM \text{RAM} RAM 的范围内
1.2 判断是否要对 RAM 进行 load 操作
我们可以用一个 Mux \text{Mux} Mux 来结合 inRamRange \text{inRamRange} inRamRange 和 load \text{load} load ,将这个 Mux \text{Mux} Mux 的输出记为 loadRAM \text{loadRAM} loadRAM ,用 loadRAM \text{loadRAM} loadRAM 判断是否对 RAM 进行 load 操作 ⬇️
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 , se l = inRamRange , o u t = 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 a dd ress 的低 14 14 14 位,即 address[0..13] \text{address[0..13]} address[0..13] ),将 RAM \text{RAM} RAM 的输出记为 ramOut \text{ramOut} ramOut ⬇️
RAM16K ( i n = in , l o a d = loadRAM , a d d r e s s = address[0..13] , o u t = ramOut ) \text{RAM16K}(in= \text{in}, load= \text{loadRAM}, address= \text{address[0..13]}, out= \text{ramOut}) RAM16K ( in = in , l o a d = loadRAM , a dd ress = address[0..13] , o u t = ramOut )
1.3 判断是否要使用 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 , se l = inRamRange , o u t = outCandidate1 )
1.4 将这些代码合在一起
与 RAM 相关的代码合起来是这样的 ⬇️
// 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. 如果 address 在 Screen 的范围内
2.1 判断 address 是否在 Screen 的范围内
当且仅当 16384 ≤ a d d r e s s < 24576 16384\le address \lt 24576 16384 ≤ a dd ress < 24576 时, a d d r e s s address a dd ress 在 Screen \text{Screen} Screen 的范围内。由于 a d d r e s s address a dd ress 是 15 15 15 位的,这就等价于以下两个条件都成立 ⬇️
address[14] = 1 \text{address[14]}=1 address[14] = 1
address[13] = 0 \text{address[13]}=0 address[13] = 0
address[0..12] \text{address[0..12]} address[0..12] 这些位可以是任意值。当 a d d r e s s address a dd ress 在 Screen \text{Screen} Screen 的范围内时,a d d r e s s address a dd ress 的模式可以表示如下 ⬇️ (下图中用 ? 表示这一位可以是任意值)
可以用一个 非门 来判断 address[13] = 0 \text{address[13]}=0 address[13] = 0 是否成立(这个非门的输出记为 notA13 \text{notA13} notA13 ) ⬇️
Not ( i n = address[13] , o u t = notA13 ) \text{Not}(in= \text{address[13]}, out= \text{notA13}) Not ( in = address[13] , o u t = notA13 )
在此基础上,可以用一个 与门 来判断以下两者是否都成立
address[14] = 1 \text{address[14]}=1 address[14] = 1
address[13] = 0 \text{address[13]}=0 address[13] = 0
这个与门的输出记为 inScreenRange \text{inScreenRange} inScreenRange ⬇️
And ( a = address[14] , b = notA13 , o u t = inScreenRange ) \text{And}(a= \text{address[14]}, b= \text{notA13}, out= \text{inScreenRange}) And ( a = address[14] , b = notA13 , o u t = inScreenRange )
2.2 判断是否要对 Screen 进行 load 操作
我们可以用一个 Mux \text{Mux} Mux 来结合 inScreenRange \text{inScreenRange} inScreenRange 和 load \text{load} load 。
Mux ( a = false , b = load , s e l = inScreenRange , o u t = loadScreen ) \text{Mux}(a= \text{false}, b= \text{load}, sel= \text{inScreenRange}, out= \text{loadScreen}) Mux ( a = false , b = load , se l = inScreenRange , o u t = loadScreen )
现在可以将 in , loadScreen , address \text{in},\text{loadScreen},\text{address} in , loadScreen , address 连接到 Screen \text{Screen} Screen 上了(我们只需要 a d d r e s s address a dd ress 的低 13 13 13 位,即 address[0..12] \text{address[0..12]} address[0..12] ),将 Screen \text{Screen} Screen 的输出记为 screenOut \text{screenOut} screenOut ⬇️
Screen ( i n = in , l o a d = loadScreen , a d d r e s s = address[0..12] , o u t = screenOut ) \text{Screen}(in= \text{in}, load= \text{loadScreen}, address= \text{address[0..12]}, out= \text{screenOut}) Screen ( in = in , l o a d = loadScreen , a dd ress = address[0..12] , o u t = screenOut )
2.3 判断是否要使用 screenOut \text{screenOut} screenOut
我们需要根据 inScreenRange \text{inScreenRange} inScreenRange 的值来判断是否使用 screenOut \text{screenOut} screenOut ⬇️
inScreenRange \text{inScreenRange} inScreenRange 为 true \text{true} true 时: 使用 screenOut \text{screenOut} screenOut
inScreenRange \text{inScreenRange} inScreenRange 为 false \text{false} false 时: 忽略 screenOut \text{screenOut} screenOut
因此可以用 Mux16 \text{Mux16} Mux16 来选择是否使用 screenOut \text{screenOut} screenOut ,这个 Mux16 \text{Mux16} Mux16 的输出记为 outCandidate2 \text{outCandidate2} outCandidate2 ⬇️
Mux16 ( a = false , b = screenOut , s e l = inScreenRange , o u t = outCandidate2 ) \text{Mux16}(a= \text{false}, b= \text{screenOut}, sel= \text{inScreenRange}, out= \text{outCandidate2}) Mux16 ( a = false , b = screenOut , se l = inScreenRange , o u t = outCandidate2 )
2.4 将这些代码合在一起
与 Screen 相关的代码合起来是这样的 ⬇️
// 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 如果 address 在 Keyboard 的范围内
通过如下方式可以获取 Keyboard 的输出 ⬇️ 记这个输出为 kbOut \text{kbOut} kbOut
Keyboard ( o u t = kbOut ) \text{Keyboard}(out= \text{kbOut}) Keyboard ( o u t = kbOut )
3.1 判断 address 是否在 Keyboard 的范围内
当且仅当 a d d r e s s = 24576 address=24576 a dd ress = 24576 时,a d d r e s s address a dd ress 在 Keyboard \text{Keyboard} Keyboard 的范围内。当 a d d r e s s address a dd ress 在 Keyboard \text{Keyboard} Keyboard 的范围内时,a d d r e s s address a dd ress 的模式可以表示如下 ⬇️
我们需要判断以下两个条件是否都成立
address[13] = 1 \text{address[13]}=1 address[13] = 1 且address[14] = 1 \text{address[14]}=1 address[14] = 1
address[0..12] \text{address[0..12]} address[0..12] 都是 0 0 0
可以用一个 与门 来判断 address[13] = 1 \text{address[13]}=1 address[13] = 1 且address[14] = 1 \text{address[14]}=1 address[14] = 1 是否同时成立 ⬇️
And ( a = address[13] , b = address[14] , o u t = high2BitAnd ) \text{And}(a= \text{address[13]}, b= \text{address[14]}, out= \text{high2BitAnd}) And ( a = address[13] , b = address[14] , o u t = high2BitAnd )
在此基础上,还需要判断 address[0..12] \text{address[0..12]} address[0..12] 是否都为 0 0 0 。如果逐位去判断,看起来比较繁琐。可以这样考虑,如果 address[0..12] \text{address[0..12]} address[0..12] 都为 0 0 0 的话,记
sum = address[0..14] + 11 ⋯ 1 ⏟ n t i m e s 13 个 1 \text{sum}=\text{address[0..14]} + \underbrace{11\cdots 1}_{n\rm\ times}^{\text{13 个 1}} sum = address[0..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}} = 11 13 个 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}} = 0 15 个 1 11 ⋯ 1
那么 sum [ 15 ] = 0 \text{sum}[15]=0 sum [ 15 ] = 0 。满足 address[13] = 1 \text{address[13]}=1 address[13] = 1 和 address[14] = 1 \text{address[14]}=1 address[14] = 1 的 a d d r e s s address a dd ress 共有 2 13 2^{13} 2 13 个。这些 a d d r e s s address a dd ress 加 11 ⋯ 1 ⏟ n t i m e s 13 个 1 \underbrace{11\cdots 1}_{n\rm\ times}^{\text{13 个 1}} 13 个 1 11 ⋯ 1 的结果汇总如下 ⬇️
15 15 15 位的 address \text{address} address 把 address \text{address} address 视为 16 16 16 位 十进制表示的address \text{address} address address \text{address} address 加 11 ⋯ 1 ⏟ n t i m e s 13 个 1 \underbrace{11\cdots 1}_{n\rm\ times}^{\text{13 个 1}} 13 个 1 11 ⋯ 1 之后得到的 16 16 16 位的 s u m sum s u m 用十六进制表示 s u m sum s u m 11000000000000 0 2 110000000000000_2 11000000000000 0 2 011000000000000 0 2 0110000000000000_2 011000000000000 0 2 24576 24576 24576 011111111111111 1 2 0111111111111111_2 011111111111111 1 2 0x7FFF \text{0x7FFF} 0x7FFF 11000000000000 1 2 110000000000001_2 11000000000000 1 2 011000000000000 1 2 0110000000000001_2 011000000000000 1 2 24577 24577 24577 100000000000000 0 2 1000000000000000_2 100000000000000 0 2 0x8000 \text{0x8000} 0x8000 ⋯ \cdots ⋯ ⋯ \cdots ⋯ ⋯ \cdots ⋯ ⋯ \cdots ⋯ ⋯ \cdots ⋯ 11111111111111 1 2 111111111111111_2 11111111111111 1 2 011111111111111 1 2 0111111111111111_2 011111111111111 1 2 32767 32767 32767 100111111111111 0 2 1001111111111110_2 100111111111111 0 2 0x9FFE \text{0x9FFE} 0x9FFE
对这些 a d d r e s s address a dd ress 而言,只有 11000000000000 0 2 110000000000000_2 11000000000000 0 2 算出的 s u m sum s u m ,其 s u m [ 15 ] = 0 sum[15]=0 s u m [ 15 ] = 0 。可以用一个 与门 来判断以下两者是否都成立(如果以下两者同时成立,则说明 address = 11000000000000 0 2 \text{address}=110000000000000_2 address = 11000000000000 0 2 )
high2BitAnd = true \text{high2BitAnd}=\text{true} high2BitAnd = true
sum [ 15 ] = 0 \text{sum}[15]=0 sum [ 15 ] = 0
将这个 与门 的输出记为 inKbRange \text{inKbRange} inKbRange ,对应的实现如下 ⬇️
Add16 ( a [ 0..14 ] = address , b [ 0..12 ] = true , o u t [ 15 ] = msbIsOne ) \text{Add16}(a[0..14]= \text{address}, b[0..12]= \text{true}, out[15]= \text{msbIsOne}) Add16 ( a [ 0..14 ] = address , b [ 0..12 ] = true , o u t [ 15 ] = msbIsOne )
Not ( i n = msbIsOne , o u t = msbIsZero ) \text{Not}(in= \text{msbIsOne}, out= \text{msbIsZero}) Not ( in = msbIsOne , o u t = 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 , o u t = inKbRange )
3.2 判断是否要使用 kbOut \text{kbOut} kbOut
我们需要根据 inKbRange \text{inKbRange} inKbRange 的值来判断是否使用 kbOut \text{kbOut} kbOut ⬇️
inKbRange \text{inKbRange} inKbRange 为 true \text{true} true 时: 使用 kbOut \text{kbOut} kbOut
inKbRange \text{inKbRange} inKbRange 为 false \text{false} false 时: 忽略 kbOut \text{kbOut} kbOut
因此可以用 Mux16 \text{Mux16} Mux16 来选择是否使用 kbOut \text{kbOut} kbOut ,这个 Mux16 \text{Mux16} Mux16 的输出记为 outCandidate3 \text{outCandidate3} outCandidate3 ⬇️
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 , se l = inKbRange , o u t = outCandidate3 )
3.3 将这些代码合在一起
与 Keyboard 相关的代码合起来是这样的 ⬇️
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 , o u t = 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 , o u t = out )
将上面的代码都整合到一起,得到完整的 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 一文。用以下代码可以画出对应的图 ⬇️
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:#ff0;
style b11 fill:#ff0;
style b10 fill:#ff0;
style b9 fill:#ff0;
style b8 fill:#ff0;
style b7 fill:#ff0;
style b6 fill:#ff0;
style b5 fill:#ff0;
style b4 fill:#ff0;
style b3 fill:#ff0;
style b2 fill:#ff0;
style b1 fill:#ff0;
style b0 fill:#ff0;
参考资料