CPUの創り方 in Python

高二 締め切りが今日です(03:00)

先に参考文献を書きます。僕が図書館で4ヶ月ぐらい延滞するぐらいには素晴らしい本です。ちゃんと買いもしました。

締め切りが迫っているので回路図から実装します(本の順に作成しません)。Pythonで実装することになんの意味があるんだと思われるかもしれませんが。でも4bitの世界にPythonで挑むのは結構面白かったです。

primitiveなICを使ってる感じを出すためにclassを使い、そしてinstance同士をくっつけるとイイ感じに配線してるぞ〜感が出ると思うのでやる気を出すためにそんな感じの野望を抱いておきます。あと早く終わらします。

ICの野望

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class IC: # 全てのICの親class
    def __init__(self, length):
        self.pins = [0]*(length+1) # 本ではpin番号がほとんど1から振られていたので0は欠番

    def set_output(self, *output: tuple):
        self.output = output # 出力先を`(instance, pin_num)`の形で記述したtuple

    def out(self):
        self.calc() # 値の更新を行う。各自のclassで定義。

        for dest, pin_num in zip(self.output, self.output_pin):
            if dest: # Noneとかを排除する
                for d in (dest if type(dest[0]) == tuple else (dest,)):
                    d[0].pins[d[1]] = self.pins[pin_num] # 出力先に値を代入

だいたい抱いた通りに実装できました。直接IC.pins[n]をtupleに入れて代入するだけだと代入できなかったのでobjectごと渡しています。

命令Decoder

74HC10(NAND)と74HC32(OR)で構成されています。ICをPythonで書くなんてことはしたことないので楽なそうなものから消化していきます。p242の真理値表を出力するよういい感じにclassを作ってinstance同士をくっつけていきたいと思います。しっかりと実装できていれば適当に拵えたtest programで一発で確かめられますが、真理値表の項目名が昇順に並んでいないことに気付くまで手間取りました。

1
2
3
4
5
6
class ExternalIC(IC):
    def calc(self):
        pins = self.pins # 参照代入

        for i, o in zip(self.input_pin, self.output_pin): # I/Oの組はclass内で定義
            pins[o] = self.func(list(pins[x] for x in i)) # func:NAND, or

この二つの関数のみ完全に重複しない数の組\((a, b, c)\)と\((o)\)をそれぞれ入力と出力にもち(つまり独立した)、完全に外部の出力(external)だけで完結するためExternalICと名付けたのですが多分違う意味に取られるだろうし後になって考えたら他のICも普通に外部の入力で決まったので名前決め失敗ですね。_74HC10classと_74HC32classはこのclassを継承させてfuncNANDORを渡すことでつくりました。

ALU

全加算機を実装してもいいのですが日付を周りそうなのでPythonの機能を使い逃げます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class _74HC283(IC):
    def __init__(self):
        self.pins = [0]*(16+1)

        self.output_pin = 9, 4, 1, 13, 10 # carry, s1, s2, s3, s4

    def calc(self):
        pins = self.pins

        A = int("".join(
            [str(pins[x]) for x in (12, 14, 3, 5)]
        ), 2) #12が四桁目、5が一桁目
        B = int("".join(
            [str(pins[x]) for x in (11, 15, 2, 6)]
        ), 2)

        result = format(A+B, "05b") ## 五桁(carry, s1, s2, s3, s4)になるように調整
        for i in range(len(self.output_pin)):
            pins[self.output_pin[i]] = int(result[i]) # 順に代入

pin番号が昇順なのか降順なのか分からなかったのと、五桁になるのに調整するのを忘れていたのでOut of Rangeと言われ少し手間取りました。

Data Selector

眠くなってきてdata selectorが何か思い出せなかったので本を少し読み返したりして時間が掛かりました。多分\(n bit\)に対して\(log_2 n\)本必要なんだろうとも考えましたが対数を忘れてしまったので分かりません。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class _74HC153(IC):
    def __init__(self):
        self.pins = [0]*(16+1)

        self.output_pin = 7, 9

    def calc(self):
        self.pins = [int(x) for x in self.pins]
        pins = self.pins

        select = int("".join([str(pins[x]) for x in (2, 14)]), 2)

        pins[7] = pins[3+select]
        pins[9] = pins[13-select]

Register

慣れてきたのでこいつが一番楽でした。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class _74HC161(IC):
    def __init__(self):
        self.pins = [0]*(16+1) # ピン番号1ずらすのがだるいので0は欠番

        self.output_pin = 14, 13, 12, 11

    def calc(self):
        pins = self.pins
        if not pins[9]:
            for i in range(4):
                pins[14-i] = pins[3+i]

Program Counter

こいつのせいで睡眠時間が30分は失われました。 二進数で数え上げをするのがダルすぎるし、clock(loop)のどこでcount upしたらいいかの勝手がわからなかったので試行錯誤する分時間を取られました。EPROMとかがあるならこれも一つのICだよねってことで(あと流石にPythonを使ってるのにROMの読み出しまで実装したら過労死してしまうので)回路図の"ROMへ"の先はすべてPythonのlistとかで実装しました。ROMの出力の昇順降順を履き違えたのも痛かったです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class EPROM(IC):
    def __init__(self):
        self.pins = [0]*(12+1) # 1-8出力、9-12入力

        self.output_pin = 1, 2, 3, 4, 5, 6, 7, 8

    def calc(self):
        pins = self.pins

        data = ["10111111",
                "10110000",
                "11110000"]
        pins[1:9] = [int(x) for x in list(data[int("".join(map(str, pins[9:])), 2)])]

dataにあるのはtestに使ったのはLチカです。

Carry Flag

clockが立ち上がった時にresetされないと思うので74HC28374HC32は直接繋いでます。時間がなくてJNC命令のtestをしていないのでまだ分かりません。

配線&debug

地獄。配線は回路図と睨めっこ。debugはtest programを走らせ、想定と違う挙動をしたら全てのICのpinの状態を出力してあるべき姿と照らし合わせます。

1
2
3
4
5
6
7
8
IC9  = _74HC283()
IC9.set_output(
    (IC8, 4),
    ((IC2, 3), (IC3, 3), (IC4, 3), (IC5, 3)),
    ((IC2, 4), (IC3, 4), (IC4, 4), (IC5, 4)),
    ((IC2, 5), (IC3, 5), (IC4, 5), (IC5, 5)),
    ((IC2, 6), (IC3, 6), (IC4, 6), (IC5, 6)),
)

配線は以上のように全部手打ちでtupleの山を記述していきます。半田付けよりは楽ですが明らかにbread boardの方が楽ですね。でも無極性電解Kondensatorを手に入れるのがダルすぎたので…

振り返り

制作時間は四時間でした。眠かったのでコードが雑ですね。mapとかはもっと使うべきだと思います。あとカタカナで横文字を書くのが嫌なので本文ではアルファベットで書いてみました。長いし打ちにくいので。でもテストとかは別にカタカナでもいいと思ったので所詮慣れかもしれません。ここで作った仮想TD4は文化祭で展示するかもしれません。クロックがめちゃくちゃ自由に設定できることぐらいしか本家に優っていませんが…

次へ物理部室の亡霊と、最後の挑戦状>
前へ色のデザイン>