コンピュータシステムの理論と実装の1〜5章のハードウェアを実装しました(ネタバレ注意)

たまには低レベルなこともしたくて*1コンピュータシステムの理論と実装(以下、nand2tetris本)を始めてみました。nand2tetris本NANDゲートのみ*2からCPU/OSなどを実装していく素敵な書籍です。今回は1〜5章のハードウェア部分を実装してみたので忘れっぽい自分のためのメモです。自力で実装に挑戦してみたい人にはネタバレになると思うので注意です。

今回のコード

下記、タグv0.0.0になります。

github.com

下記で動かせます。

git clone -b v0.0.0 https://github.com/nihemak/nand2tetris.git
cd nand2tetris
# download nand2tetris environment
./setup.sh
# test all
./test.sh

1章 ブール論理

Not

真理値表は下記です。

in - out
0 - 1
1 - 0

これはNand(in, in)です。関連するNandの真理値表は下記です。

a b - out
0 0 - 1
1 1 - 0

コードは01/Not.hdlです。

And

真理値表は下記です。

a b - out
0 0 - 0
0 1 - 0
1 0 - 0
1 1 - 1

これはNot(Nand(a, b))です。

Nand(a, b) = Not(And(a, b))の両辺にNotを適用してNotを打ち消した左辺と考えることができます。

コードは01/And.hdlです。

Or

真理値表は下記です。

a b - out
0 0 - 0
0 1 - 1
1 0 - 1
1 1 - 1

これはNot(And(Not(a), Not(b)))です。

ドモルガンの法則であるNot(Or(a, b)) = And(Not(a), Not(b))の両辺にNotを適用した右辺と考えることができます。

コードは01/Or.hdlです。

Xor

真理値表は下記です。

a b - out
0 0 - 0
0 1 - 1
1 0 - 1
1 1 - 0

これはOr(And(a, Not(b)), And(Not(a), b))です。

次の2つのOrを行い(1, 0)(0, 1)1となるようにすると考えることができます。

  • (1, 0)のみが1となるAnd(a, Not(b))
  • (0, 1)のみが1となるAnd(Not(a), b)

(1, 0)のみが1となるAnd(a, Not(b))の真理値表は下記です。

a Not(b) - And(a, Not(b))
0 1 - 0
0 0 - 0
1 1 - 1
1 0 - 0

(0, 1)のみが1となるAnd(Not(a), b)の真理値表は下記です。

Not(a) b - And(Not(a), b)
1 0 - 0
1 1 - 1
0 0 - 0
1 1 - 0

Or(And(a, Not(b)), And(Not(a), b))の真理値表は下記です。

And(a, Not(b)) And(Not(a), b) - out
0 0 - 0
0 1 - 1
1 0 - 1
0 0 - 0

コードは01/Xor.hdlです。

Mux

真理値表は下記です。

a b sel - out
0 0 0 - 0
0 1 0 - 0
1 0 0 - 1
1 1 0 - 1
0 0 1 - 0
0 1 1 - 1
1 0 1 - 0
1 1 1 - 1

これはAnd(Or(a, sel), Or(b, Not(sel)))です。

sel = 0の場合はa, sel = 1の場合はbにすれば良いと考えることができます。

sel - out
0 - a
1 - b

これには次の3つが使えます。

  • Or(x, 0) = x
  • Or(x, 1) = 1
  • And(x, 1) = 1

下記のようにselによって取り出す列を選ぶことができます。

  • sel = 0の場合、Or(a, sel) = aおよびOr(b, Not(sel)) = 1となるのでAnd(a, 1) = aになる
  • sel = 1の場合、Or(a, sel) = 1およびOr(b, Not(sel)) = bとなるのでAnd(1, b) = bになる

コードは01/Mux.hdlです。

DMux

真理値表は下記です。

in sel - a b
0 0 - 0 0
1 0 - 1 0
0 1 - 0 0
1 1 - 0 1

これは下記です。

  • a = And(in, Not(sel))
  • b = And(in, sel)

sel = 0の場合はa = in, b = 0, sel = 1の場合はa = 0, b = inにすれば良いと考えることができます。

sel - a b
0 - in 0
1 - 0 in

これには次の2つが使えます。

  • And(x, 0) = 0
  • And(x, 1) = 1

下記のようにselによって取り出す列を選ぶことができます。

  • sel = 0の場合、a = And(in, 1), b = And(in, 0)となるのでa = in, b = 0になる
  • sel = 1の場合、a = And(in, 0), b = And(in, 1)となるのでa = 0, b = inになる

コードは01/DMux.hdlです。

Not16

これは次のように全ての要素にNotを適用するだけです。

  • out[0] = Not(in[0])
  • out[1] = Not(in[1])
  • ...
  • out[14] = Not(in[14])
  • out[15] = Not(in[15])

コードは01/Not16.hdlです。

And16

これは次のように全ての要素にAndを適用するだけです。

  • out[0] = And(a[0], b[0])
  • out[1] = And(a[1], b[1])
  • ...
  • out[14] = And(a[14], b[14])
  • out[15] = And(a[15], b[15])

コードは01/And16.hdlです。

Or16

これは次のように全ての要素にOrを適用するだけです。

  • out[0] = Or(a[0], b[0])
  • out[1] = Or(a[1], b[1])
  • ...
  • out[14] = Or(a[14], b[14])
  • out[15] = Or(a[15], b[15])

コードは01/Or16.hdlです。

Mux16

これは次のように全ての要素にMuxを適用するだけです。

  • out[0] = Mux(a[0], b[0], sel)
  • out[1] = Mux(a[1], b[1], sel)
  • ...
  • out[14] = Mux(a[14], b[14], sel)
  • out[15] = Mux(a[15], b[15], sel)

コードは01/Mux16.hdlです。

Or8Way

これは次のように要素すべてのOrを行うだけです。

Or(Or(in[0], in[1]), Or(in[2], in[3])), Or(Or(in[4], in[5]), Or(in[6], in[7]))

コードは01/Or8Way.hdlです。

Mux4Way16

真理値表は下記です。

sel[1] sel[0] - out
0 0 - a
0 1 - b
1 0 - c
1 1 - d

これはMux16(Mux16(a, b, sel[0]), Mux16(c, d, sel[0]), sel[1])です。

sel[0]を使ってMux16a, bから1つ、c, dから1つを選択、それらの結果からsel[1]を使って1つをMux16で選択すると求めることができます。

sel[0]を使ってMux16a, bから1つを選択する真理値表は下記です。

sel[0] - Mux16(a, b, sel[0])
0 - a
1 - b
0 - a
1 - b

sel[0]を使ってMux16c, dから1つを選択する真理値表は下記です。

sel[0] - Mux16(c, d, sel[0])
0 - c
1 - d
0 - c
1 - d

上記の2つの結果からsel[1]を使って1つをMux16で選択する真理値表は下記です。

sel[1] - Mux16(Mux16(a, b, sel[0]), Mux16(c, d, sel[0]), sel[1])
0 - Mux16(a, b, sel[0]) = a
0 - Mux16(a, b, sel[0]) = b
1 - Mux16(c, d, sel[0]) = c
1 - Mux16(c, d, sel[0]) = d

コードは01/Mux4Way16.hdlです。

Mux8Way16

真理値表は下記です。

sel[2] sel[1] sel[0] - out
0 0 0 - a
0 0 1 - b
0 1 0 - c
0 1 1 - d
1 0 0 - e
1 0 1 - f
1 1 0 - g
1 1 1 - h

これはMux16(Mux4Way16(a, b, c, d, sel[0], sel[1]), Mux4Way16(e, f, g, h, sel[0], sel[1]), sel[2])です。

考え方はMux8Way16と同じです。まずsel[0..1]を使ってMux4Way16a, b, c, de, f, g, hから1つずつ選択、それらの結果からsel[2]を使って1つをMux16で選択すると求めることができます。

sel[0..1]を使ってMux4Way16a, b, c, dから1つ選択する真理値表は下記です。

sel[1] sel[0] - Mux4Way16(a, b, c, d, sel[0], sel[1])
0 0 - a
0 1 - b
1 0 - c
1 1 - d
0 0 - a
0 1 - b
1 0 - c
1 1 - d

sel[0..1]を使ってMux4Way16e, f, g, hから1つ選択する真理値表は下記です。

sel[1] sel[0] - Mux4Way16(e, f, g, h, sel[0], sel[1])
0 0 - e
0 1 - f
1 0 - g
1 1 - h
0 0 - e
0 1 - f
1 0 - g
1 1 - h

上記の2つの結果からsel[2]を使って1つをMux16で選択する真理値表は下記です。

sel[2] - Mux16(Mux4Way16(a, b, c, d, sel[0], sel[1]), Mux4Way16(e, f, g, h, sel[0], sel[1]), sel[2])
0 - Mux4Way16(a, b, c, d, sel[0], sel[1]) = a
0 - Mux4Way16(a, b, c, d, sel[0], sel[1]) = b
0 - Mux4Way16(a, b, c, d, sel[0], sel[1]) = c
0 - Mux4Way16(a, b, c, d, sel[0], sel[1]) = d
1 - Mux4Way16(e, f, g, h, sel[0], sel[1]) = e
1 - Mux4Way16(e, f, g, h, sel[0], sel[1]) = f
1 - Mux4Way16(e, f, g, h, sel[0], sel[1]) = g
1 - Mux4Way16(e, f, g, h, sel[0], sel[1]) = h

コードは01/Mux8Way16.hdlです。

DMux4Way

真理値表は下記です。

sel[1] sel[0] - a b c d
0 0 - in 0 0 0
0 1 - 0 in 0 0
1 0 - 0 0 in 0
1 1 - 0 0 0 in

これは下記です。

  • (w0, w1) = DMux(in, sel[0])
  • (a, c) = DMux(w0, sel[1])
  • (b, d) = DMux(w1, sel[1])

sel[0]を使ってDMuxinを2つに分離します。そして、前者をsel[1]を使ってDMuxで2つに分離するとa, c、後者をsel[1]を使ってDMuxで2つに分離するとb, dを求めることができます。

sel[0]を使ってDMuxinを2つに分離する真理値表は下記です。

sel[0] - w0 w1
0 - in 0
1 - 0 in
0 - in 0
1 - 0 in

前者をsel[1]を使ってDMuxで2つに分離する真理値表は下記です。

sel[1] - a c
0 - w0 = in 0
0 - w0 = 0 0
1 - 0 w0 = in
1 - 0 w0 = 0

後者をsel[1]を使ってDMuxで2つに分離する真理値表は下記です。

sel[1] - b d
0 - w1 = 0 0
0 - w1 = in 0
1 - 0 w1 = 0
1 - 0 w1 = in

これらを組み合わせると正しい真理値表になります。

sel[1] sel[0] - a b c d
0 0 - w0 = in w1 = 0 w0 = 0 w1 = 0
0 1 - w0 = 0 w1 = in w0 = 0 w1 = 0
1 0 - w0 = 0 w1 = 0 w0 = in w1 = 0
1 1 - w0 = 0 w1 = 0 w0 = 0 w1 = in

コードは01/DMux4Way.hdlです。

DMux8Way

真理値表は下記です。

sel[2] sel[1] sel[0] - a b c d e f g h
0 0 0 - in 0 0 0 0 0 0 0
0 0 1 - 0 in 0 0 0 0 0 0
0 1 0 - 0 0 in 0 0 0 0 0
0 1 1 - 0 0 0 in 0 0 0 0
1 0 0 - 0 0 0 0 in 0 0 0
1 0 1 - 0 0 0 0 0 in 0 0
1 1 0 - 0 0 0 0 0 0 in 0
1 1 1 - 0 0 0 0 0 0 0 in

これは下記です。

  • (w0, w1, w2, w3) = DMux4Way(in, sel[0..1])
  • (a, e) = DMux(w0, sel[2])
  • (b, f) = DMux(w1, sel[2])
  • (c, g) = DMux(w2, sel[2])
  • (d, h) = DMux(w3, sel[2])

考え方はDMux4Wayと同じです。まずsel[0..1]を使ってDMux4Wayinを4つに分離します。そして、それぞれをsel[2]を使ってDMuxで2つに分離するとa, eb, fc, gd, hを求めることができます。

sel[0..1]を使ってDMux4Wayinを4つに分離する真理値表は下記です。

sel[1] sel[0] - w0 w1 w2 w3
0 0 - in 0 0 0
0 1 - 0 in 0 0
1 0 - 0 0 in 0
1 1 - 0 0 0 in
0 0 - in 0 0 0
0 1 - 0 in 0 0
1 0 - 0 0 in 0
1 1 - 0 0 0 in

sel[2]を使ってDMuxa, eに分離する真理値表は下記です。

sel[2] - a e
0 - w0 = in 0
0 - w0 = 0 0
0 - w0 = 0 0
0 - w0 = 0 0
1 - 0 w0 = in
1 - 0 w0 = 0
1 - 0 w0 = 0
1 - 0 w0 = 0

sel[2]を使ってDMuxb, fに分離する真理値表は下記です。

sel[2] - b f
0 - w1 = 0 0
0 - w1 = in 0
0 - w1 = 0 0
0 - w1 = 0 0
1 - 0 w1 = 0
1 - 0 w1 = in
1 - 0 w1 = 0
1 - 0 w1 = 0

sel[2]を使ってDMuxc, gに分離する真理値表は下記です。

sel[2] - c g
0 - w2 = 0 0
0 - w2 = 0 0
0 - w2 = in 0
0 - w2 = 0 0
1 - 0 w2 = 0
1 - 0 w2 = 0
1 - 0 w2 = in
1 - 0 w2 = 0

sel[2]を使ってDMuxd, hに分離する真理値表は下記です。

sel[2] - d h
0 - w3 = 0 0
0 - w3 = 0 0
0 - w3 = 0 0
0 - w3 = in 0
1 - 0 w3 = 0
1 - 0 w3 = 0
1 - 0 w3 = 0
1 - 0 w3 = in

これらを組み合わせると正しい真理値表になります。

sel[2,1,0] - a b c d e f g h
000 - w0 = in w1 = 0 w2 = 0 w3 = 0 w0 = 0 w1 = 0 w2 = 0 w3 = 0
001 - w0 = 0 w1 = in w2 = 0 w3 = 0 w0 = 0 w1 = 0 w2 = 0 w3 = 0
010 - w0 = 0 w1 = 0 w2 = in w3 = 0 w0 = 0 w1 = 0 w2 = 0 w3 = 0
011 - w0 = 0 w1 = 0 w2 = 0 w3 = in w0 = 0 w1 = 0 w2 = 0 w3 = 0
100 - w0 = 0 w1 = 0 w2 = 0 w3 = 0 w0 = in w1 = 0 w2 = 0 w3 = 0
101 - w0 = 0 w1 = 0 w2 = 0 w3 = 0 w0 = 0 w1 = in w2 = 0 w3 = 0
110 - w0 = 0 w1 = 0 w2 = 0 w3 = 0 w0 = 0 w1 = 0 w2 = in w3 = 0
111 - w0 = 0 w1 = 0 w2 = 0 w3 = 0 w0 = 0 w1 = 0 w2 = 0 w3 = in

コードは01/DMux8Way.hdlです。

2章 ブール算術

HalfAdder

真理値表は下記です。

a b - carry sum
0 0 - 0 0
0 1 - 0 1
1 0 - 0 1
1 1 - 1 0

これは下記です。

  • sum = Xor(a, b)
  • carry = And(a, b)

コードは02/HalfAdder.hdlです。

FullAdder

真理値表は下記です。

a b c - carry sum
0 0 0 - 0 0
0 0 1 - 0 1
0 1 0 - 0 1
0 1 1 - 1 0
1 0 0 - 0 1
1 0 1 - 1 0
1 1 0 - 1 0
1 1 1 - 0 1

これは下記です。

  • (sum0, carry0) = HalfAdder(a, b)
  • (sum, carry1) = HalfAdder(sum, c)
  • carry = Or(carry0, carry1)

suma, b, cすべてのHalfAddersumcarryは「a, bHalfAddercarry」と「a, bHalfAddersumcHalfAddercarry」のOrです。

  • sum = Sum(Sum(a, b), c) = Xor(Xor(a, b), c)
  • carry = Or(Carry(a, b), Carry(Sum(a, b), c)) = Or(And(a, b), And(Xor(a, b), c))

コードは02/FullAdder.hdlです。

Add16

表にすると下記のようになります。

a b carry - out
a[0] b[0] - - Sum(a[0], b[0])
a[1] b[1] Carry(a[0], b[0]) - Sum(a[1], b[1])
a[2] b[2] Carry(a[1], b[1]) - Sum(a[2], b[2])
a[3] b[3] Carry(a[2], b[2]) - Sum(a[3], b[3])
a[4] b[4] Carry(a[3], b[3]) - Sum(a[4], b[4])
a[5] b[5] Carry(a[4], b[4]) - Sum(a[5], b[5])
a[6] b[6] Carry(a[5], b[5]) - Sum(a[6], b[6])
a[7] b[7] Carry(a[6], b[6]) - Sum(a[7], b[7])
a[8] b[8] Carry(a[7], b[7]) - Sum(a[8], b[8])
a[9] b[9] Carry(a[8], b[8]) - Sum(a[9], b[9])
a[10] b[10] Carry(a[9], b[9]) - Sum(a[10], b[10])
a[11] b[11] Carry(a[10], b[10]) - Sum(a[11], b[11])
a[12] b[12] Carry(a[11], b[11]) - Sum(a[12], b[12])
a[13] b[13] Carry(a[12], b[12]) - Sum(a[13], b[13])
a[14] b[14] Carry(a[13], b[13]) - Sum(a[14], b[14])
a[15] b[15] Carry(a[14], b[14]) - Sum(a[15], b[15])

これは1個のHalfAdderと15個のFullAdderを組み合わせれば良いです。

ゲートの構成は下記です。

f:id:nihma:20190427094246p:plain

コードは02/Add16.hdlです。

Inc16

これはAdd16(in, %B0000000000000001)です。

コードは02/Inc16.hdlです。

ALU

これは場合分けして考えます。

まずzxnxzynyを考慮した値を求めます。

  • x1 = Add16(x, Mux16(x, %B0000000000000000, zx))
  • x2 = Mux16(x1, Not(x1), nx)
  • y1 = Add16(y, Mux16(y, %B0000000000000000, zy))
  • y2 = Mux16(y1, Not(y1), ny)

そしてfnoを考慮してoutを求めます。

  • xy = Mux16(Add16(x2, y2), And16(x2, y2), f)
  • out = Mux16(xy, Not(xy), no)

あとはoutからzrngを求めることができます。

  • zr = Not(Or(Or8Way(out[0..7]), Or8Way(out[8..15]))))
  • ng = Or(out[15], 0)

コードは02/ALU.hdlです。

3章 順序回路

Bit

これはそのままです。

ゲートの構成は下記です。

f:id:nihma:20190427140009p:plain

コードは03/a/Bit.hdlです。

Register

これはBitを16個ならべるだけです。

  • out[0] = Bit(in[0], load)
  • out[1] = Bit(in[1], load)
  • ...
  • out[14] = Bit(in[14], load)
  • out[15] = Bit(in[15], load)

コードは03/a/Register.hdlです。

RAM8

これは8個のRegisterを並べます。

下記の3ステップで求めることができます。

  • addressRegisterのみload、それ以外は書き込みをしないようにfalseRegisterに指定するためにDMux8Wayで各loadを求める
  • Registerinを適用してそれぞれのoutを求める
  • addressoutMux8Way16で求める

ゲートの構成は下記です。

f:id:nihma:20190427142402p:plain

コードは03/a/RAM8.hdlです。

RAM64

これはRAM8と同じ考え方で下記を変更すればできます。

  • DMux8Wayselにはaddress[3..5]を指定する
  • RegisterRAM8にしてaddressにはaddress[0..2]を指定する
  • Mux8Way16selにはaddress[3..5]を指定する

ゲートの構成は下記です。

f:id:nihma:20190427162013p:plain

コードは03/a/RAM64.hdlです。

RAM512

これはRAM64と同じ考え方で下記を変更すればできます。

  • DMux8Wayselにはaddress[6..8]を指定する
  • RAM8RAM64にしてaddressにはaddress[0..5]を指定する
  • Mux8Way16selにはaddress[6..8]を指定する

ゲートの構成は下記です。

f:id:nihma:20190427162147p:plain

コードは03/b/RAM512.hdlです。

RAM4K

これはRAM512と同じ考え方で下記を変更すればできます。

  • DMux8Wayselにはaddress[9..11]を指定する
  • RAM64RAM512にしてaddressにはaddress[0..8]を指定する
  • Mux8Way16selにはaddress[9..11]を指定する

コードは03/b/RAM4K.hdlです。

RAM16K

これはRAM4Kと同じ考え方で下記を変更すればできます。

  • DMux8WayDMux4Wayに変えselにはaddress[12..13]を指定する
  • RAMの数を4個に変えRAM512RAM4Kにしてaddressにはaddress[0..11]を指定する
  • Mux8Way16Mux4Way16に変えselにはaddress[12..13]を指定する

ゲートの構成は下記です。

f:id:nihma:20190427163622p:plain

コードは03/b/RAM16K.hdlです。

PC

これは下記のように考えます。

  • 前回結果に依存するのでRegisterを使う
  • Registerに記録するのはMux4Way16resetおよびloadを使い%B0000000000000000in前回結果または前回結果+1から決定する
  • 前回結果または前回結果+1Mux16incを使い決定する

結果の決定の順序はreset > load > incの優先度によります。

Mux16incを使い決定する前回結果または前回結果+1の真理値表は下記です。

sel = inc - out
0 - a = 前回結果
1 - b = 前回結果+1 = Add16(前回結果, %B0000000000000001)

Mux4Way16resetおよびloadを使い%B0000000000000000in前回結果または前回結果+1から決定する真理値表は下記です。

sel[1] = reset sel[0] = load - out
0 0 - a = 前回結果または前回結果+1 = Mux16(前回結果, Add16(前回結果, %B0000000000000001), inc)
0 1 - b = in
1 0 - c = %B0000000000000000
1 1 - d = %B0000000000000000

ゲートの構成は下記です。

f:id:nihma:20190427182518p:plain

コードは03/a/PC.hdlです。

4章 機械語

Mult

ロジックは下記です。

f:id:nihma:20190427191304p:plain

コードは04/mult/mult.asmです。

Fill

ロジックは下記です。*3

f:id:nihma:20190429140142p:plain

コードは04/fill/Fill.asmです。

5章 コンピュータアーキテクチャ

Memory

メモリマップは下記です。

用途 - 開始アドレス 終了アドレス
RAM - 0 = %B000000000000000 16383 = 2^14 - 1 = %B011111111111111
Screen - 16384 = %B100000000000000 24575 = %B101111111111111
Keyboard - 24576 = %B110000000000000 24576 = %B110000000000000

メモリマップの通りaddress[14]RAMかどうか、address[13]ScreenKeyboardを判別することができる仕様の構成になっています。

下記のステップで考えることができます。

  • DMuxaddress[14]を使ってloadRAMScreenのどちらに指定するか決定する
  • Mux4Way16address[13..14]を使ってRAMScreenKeyboardからどれを結果にするか決定する

DMuxaddress[14]を使ってloadRAMScreenのどちらに指定するか決定する真理値表は下記です。

address[14] - RAMload Screenload
0 - load 0
1 - 0 load

Mux4Way16address[13..14]を使ってRAMScreenKeyboardからどれを結果にするか決定する真理値表は下記です。

address[14] address[13] - out
0 0 - RAMの結果
0 1 - RAMの結果
1 0 - Screenの結果
1 1 - Keyboardの結果

ゲートの構成は下記です。

f:id:nihma:20190428010042p:plain

コードは05/Memory.hdlです。

CPU

これはinstructionに指定される機械語の仕様から考えると分かりやすいです。

基数 - A命令 C命令
15 - 0 1
14 - v 1
13 - v 1
12 - v a: ALUのy(0: A, 1: M)
11 - v c1: ALUのzx
10 - v c2: ALUのnx
9 - v c3: ALUのzy
8 - v c4: ALUのny
7 - v c5: ALUのf
6 - v c6: ALUのno
5 - v d1: ARegisterのload
4 - v d2: DRegisterのload
3 - v d3: writeM
2 - v j1: PCのload(ただしALUのoutが負の場合、すなわちngが1の場合)
1 - v j2: PCのload(ただしALUのoutが0の場合、すなわちzrが1の場合)
0 - v j3: PCのload(ただしALUのoutが正の場合)

その他、補足は下記です。

  • ALUのoutが正かどうかはALUのngとzrからNot(Or(ng, zr))で求めることができる
  • A命令の場合もARegisterのloadを1にする

ゲートの構成は下記です。

f:id:nihma:20190428125847p:plain

コードは05/CPU.hdlです。

Computer

これは仕様にしたがってROM32KCPUMemoryを配線します。

ゲートの構成は下記です。

f:id:nihma:20190428133935p:plain

コードは05/Computer.hdlです。

まとめ

実装よりもまとめる方がしんどかったですが理解を深められてデバッグにもなりました。

*1:いつも低レベルですが

*2:正確にはD型フリップフロップやROM、ScreenやKeybordなども基本要素です

*3:高速化などは考慮してません