40 行の Python コードで、CPU を作成します。

目次

I.はじめに

第二に、CPUの構成

3.動作原理

4. CPU命令作業の詳細分析

5. Python は CPU のさまざまなコンポーネントを実現します

6.統合CPU

7. 古代プログラマのワークフローを体験する、CPU 向けプログラミング

8. まとめ


I.はじめに

CPU はどのように機能しますか? 初心者ユーザーを悩ませるのは霧の問題です。プログラム カウンター、RAM、レジスタなどの単語はいくつか知っているかもしれませんが、これらの部分がどのように機能し、システム全体がどのように連携して機能するかについて、明確で一般的な理解を持っていません。

この記事では、40 行の Python コードを使用して最小限の CPU を実装します。プログラム可能にし、加算と減算の操作をサポートし、メモリの読み取りと書き込み、無条件ジャンプ、および条件付きジャンプ機能をサポートします。比較的単純な CPU が実装されている理由は、CPU 全体の動作原理を誰もが理解できるようにするためであり、時期尚早に詳細にとらわれないようにするためです。

"

実際のCPUは、シリコンウエハーにエッチングで三極管またはMOSチューブを作成することであり、これらの三極管はスイッチとして機能し、スイッチの開閉の間で加算、減算、およびストレージが実現されます。以前、スイッチから始めて、Python コードを使用して、この記事と同様の CPU をシミュレートしました。しかし、ここでは、より高いレベルで CPU をシミュレートします。コードを使用して大規模なコンポーネントをシミュレートし、誰もが原則として CPU の動作を理解できるようにします。

"

第二に、CPUの構成

下の図に示すように、CPU 全体は大きなコンポーネントで構成されています。

ハーバード アーキテクチャ CPU

このCPUはハーバードアーキテクチャ(ハーバード構造)を採用しています。ハーバードの構造は比較的シンプルで理解しやすく、実装も比較的簡単です。上の図には命令 RAM とデータ RAM があり、2 つの RAM はハーバード構造の重要なシンボルです。

"

2 つの一般的な CPU アーキテクチャは、ハーバード アーキテクチャとフォン ノイマン アーキテクチャです。ハーバード アーキテクチャは、プログラム命令とデータを同じ RAM ブロックに格納する CPU 設計です。フォン ノイマン アーキテクチャ (フォン ノイマン構造) は、プリンストン構造とも呼ばれ、プログラム命令メモリとデータ メモリを組み合わせた CPU 設計です。

"

ハーバード構造とノイマン構造の主な違いは、前者のプログラムとデータは 2 つの RAM に格納され、後者は 1 つの RAM に格納されます。

3.動作原理

各パーツが個別にどのように機能するか、そしてそれらがどのように連携して機能するかを見ていきましょう。

3.1 各コンポーネントの動作原理

上の図の各コンポーネントには、実際の CPU に対応する物理回路があり、その機能は次のとおりです。

  • pc カウンタは 0 から数えて 0, 1, 2, ... を生成します. クリアすることもできますし, 外部から数値を入力してその数値からカウントを開始することもできます. これを設定と呼びます. プログラムおよびデータのアクセス場所を示すために使用されます。

  • データを格納するためのランダム アクセス メモリである RAM は、アドレス (0x01 などのシェーピング) に応じたデータの読み取りと、アドレスと書き込み信号 w に応じたデータの書き込みをサポートします。プログラムやデータを格納するために使用されます。

  • 8ビットの情報を格納するメモリであるレジスタは、w信号が1に応じて現在のデータを書き込み、wが0は読み取りを意味します。RAM に似ていますが、8 ビットの情報しか保存できません。多くの場合、命令、アドレスを保存し、中間量を計算するために使用されます。

  • 加算器は 2 つの数値の足し算と引き算を完了するもので、sub が 1 の場合は減算を意味し、ci が 1 の場合はキャリーを意味します。このデバイスは、ALU (Arithmetic Logic Unit) を形成するために使用されるコアデバイスです。実際の CPU は、論理ゲート、乗算器、論理演算器などで構成されています。

  • 21 セレクターは単極双投スイッチに相当し、s21 信号に従って、8 ビット出力が左または右の 8 ビット入力から来るかどうかが決定されます。

3.2 共同作業の原則

上の図の矢印は、データ パス図とも呼ばれるデータ フローの方向を示しています。データ パス図から、CPU がどのように設計されているかを分析できます。

データ パス全体はプログラム カウンター pc から始まり、カウンターは 0 から 0、1、2、3、4... の番号を出力します。プログラムコードとデータは、それぞれ命令 RAM とデータ RAM に格納されます。RAM は、数値で表された場所にあるデータにアクセスして格納します。カウンタアドレス 0、1、2 などに従って、RAM のデータを命令レジスタ IR とデータレジスタ DR にそれぞれ入れます。レジスターは、R​​AM によって与えられたデータを格納するコンテナーおよび変数に相当します。

命令レジスタ内の命令コードデコードによりCPU制御命令が生成され、この0と1はそれぞれローレベルとハイレベルの信号を表し、レベル信号は加算器の桁上げの有無、減算の有効化、レジスタ書き込みの有効化などを制御します。 、21 セレクターのどの入力を出力するか、カウンターをリセットするかどうかなどを選択します。したがって、命令は実際には、CPU のさまざまなコンポーネントの調整を制御する電気信号です。

データレジスタ内のデータは、それぞれ加算器加算器に行き、加算および減算演算を実行してから 21 セレクタに流れます。または、直接 21 セレクタに流れて選択を待つ場合もあります。21 セレクタが選択した後、データは累積レジスタ AC に入ります。アキュムレータのデータは、AC 信号がハイレベル 1 であるかどうかによって、書き込むかどうかを決定します。AC アキュムレータのデータは、w 信号に従って次の計算に参加するか、データ RAM に格納されます。

この時点で、1 つの計算が完了し、プログラム カウンターが 1 つインクリメントされ、次の計算が実行されます。この命令がジャンプ命令の場合は、ジャンプ先アドレスを直接プログラムカウンタに代入し、そのアドレスからプログラムを実行します。

以下では、実際の例を使用して CPU 実行プロセスを説明します。

4. CPU命令作業の詳細分析

完全なプログラムは、命令とデータで構成されます。命令は、CPU のさまざまなコンポーネントの協調作業を制御する役割を担い、データは特定の計算に関与します。サンプル プログラムは 10+2-3 を完了し、結果が 0 になるまで 3 を減算するループを実行し、プログラムは完了します。命令レジスタは ramc、データ レジスタは ramd です。

ramc = [0x18, 0x19, 0x1d, 0x02, 0x31, 0x30, 0x00]
ramd = [10, 2, 3, 0xff, 0x06, 0x02]

命令が CPU を制御する方法を学びましょう。

4.1 指示

プログラム カウンタが 0 から出力を開始することは上記でわかっています。CPU が計算操作を完了すると、実行を継続する次の命令を取得するために、カウンター全体が 1 ずつインクリメントされます。pc が 0 のときにフェッチされた最初の命令 0x18 を例に、命令がどのようにデコードされるかを説明し、CPU の各デバイスを制御します。

0x18 はバイナリで 0b0001 1000 です。各バイナリ ビットは、デバイスのイネーブル端子を指します。いわゆるイネーブルエンドは、この部分を機能させるスイッチです。例えば、ここでの2つの1はハイレベルを表し、それぞれデータレジスタDRと蓄積レジスタACのイネーブル端子wに接続されている。このように、0x18 を読み取ると、CPU が現在のデータ パスのデータをデータ レジスタと累積レジスタに書き込むことがわかります。

具体的には、各2進ビットとCPUの各デバイスのイネーブル端子との対応関係が命令である。この記事の設計接続は次のとおりです。現在の状態は、CPU デバイスを制御する 0x18 コマンドの状態です。

命令 0x18

同様に、何らかの機能が必要な場合は、対応するデバイスのイネーブル信号接続の位置をハイレベル、つまり 1 に設定すると、次のように命令セットが取得されます。

CPU命令セット

上記の例を使用して、具体的な実行プロセスを分析してみましょう。

4.2 命令実行過程の解析

電源を入れると、PC がゼロから起動します。

命令 0x18

  • 上図では、プログラムカウンタが0のとき、命令RAMとデータRAMの0番目の空間がアクセスされ、命令レジスタとデータレジスタにそれぞれ0x18と10が格納されます。

    • 命令 0x18、バイナリ 0b0001 1000、これはロード命令で、DR レジスタと AC レジスタのイネーブル端子 w がそれぞれ 1 であることを示します。

    • データ 10 は、DR レジスタと AC レジスタにデータとして格納されます。

以上で 10 をロードする操作は完了です。

命令 0x19

  • pc が 1 のとき、命令 RAM とデータ RAM の最初の空間にアクセスし、命令レジスタとデータレジスタにそれぞれ 0x19 と 2 が格納されます。

    • 命令 0x19、バイナリ 0b0001 1001、これは Add 加算命令であり、DR レジスタがデータを保存すること、21 セレクタが選択されること、および加算器の計算結果が出力されること、結果が AC に保存されることをそれぞれ示します。

    • データ 2 はデータとして DR に格納され、前のステップで AC コンテンツ 10 に追加され、AC に格納されます。

以上で10+2の操作は完了です。

命令 0x1d

  • pc が 2 の場合、命令 RAM とデータ RAM の第 2 空間にアクセスすると、命令レジスタとデータレジスタにそれぞれ 0x1d と 3 が格納されます。

    • 命令 0x1d、バイナリ 0b0001 1101、これはサブ減算命令であり、それぞれを示します: DR レジスタがデータを保存する; 加算器によってサポートされる減算が開始する; 21 セレクターが選択される; 演算結果が AC に保存される.

    • データ3は、DRに格納されたデータと前のステップACの結果12との差としてACに格納される。

以上で12-3の操作は完了です。

命令 0x02

  • pc が 3 のとき、命令 RAM とデータ RAM の第 3 空間にアクセスし、命令レジスタに 0x02 を格納します。

    • 命令 0x02、バイナリ 0b0000 0010、これは Store 格納命令です。このとき、w 信号は 1 で、データ RAM のイネーブル信号を開くことを示しているため、AC レジスタの 9 がデータの 3 番目の位置に格納されます。羊。

    • データ 0xff。dr が 0 であるため、データ レジスタにはデータが格納されません。

以上で、データ RAM ロケーション 3 への 9 の書き込みが完了しました。

命令 0x31

  • pc が 4 の場合、命令 RAM とデータ RAM の第 4 空間にアクセスし、命令レジスタに 0x31 を格納します。

    • pre が 1 で AC が 0 の場合、pc 計算機に 0x06 を書き込むと、pc が 6 のときにプログラムがジャンプして実行されます。これは、HLT に停止を命令する最後のステップです。

    • AC が 0 でないか pre が 1 でない場合、pc+1 を下向きに実行し続けます。つまり、pc は 5 です。

    • 命令 0x31、バイナリ 0b0011 0001、これは Jz ゼロ ジャンプ命令であり、AC 結果がゼロであり、プログラム カウンタ設定信号 pre が 1 であるかどうかに従って、pc カウンタをリセットすることを示します。

    • データ、0x06 がデータとして DR に格納されます。pre 信号が 1 で AC 信号が 0 であることに基づいて、pc カウンターをリセットします。

以上で、計算結果がゼロかそうでないかによって異なる位置にジャンプする機能が完成しました。

命令 0x30

  • pc が 5 の場合、命令 RAM とデータ RAM の 5 番目の空間にアクセスし、命令レジスタに 0x30 を格納します。

    • 命令 0x30、バイナリ 0b0011 0000 は無条件ジャンプ命令 Jmp であり、pc カウンターをリセットすることを示します。

    • DR にデータとして 0x02 を格納し、pre 信号により pc カウンタをリセットします。

    • 0x02 が指す命令は 0x1d で、これは減算命令、つまり -3 演算を実行し続ける命令です。

    • このとき、pc は 0x02 を指し、再度減算計算を行います。

以上で継続-3の操作は完了です。

コマンド 0x00

  • pc が 6 の場合、命令 RAM とデータ RAM の 6 番目の空間にアクセスし、命令レジスタに 0x00 を格納します。

    • コマンド 0x00、バイナリ 0b0000 0000、これは停止を示す Hlt コマンドです。

    • コマンドデータは無意味です。

    • この命令のアドレスは、pc が 4 の場合からのみジャンプできます。

実際、上記の実行プロセスと例を理解すれば、CPU の基本的な動作原理を基本的に理解できます。これらのデバイスを Python 言語で実装してみましょう。

5. Python は CPU のさまざまなコンポーネントを実現します

5.1 RAM メモリ

リストを使用してデータを保存します。これは非常にシンプルでわかりやすいデザインです。

ramc = [0x18, 0x19, 0x1d, 0x02, 0x31, 0x30, 0x00]

pc ポインタによるメモリの読み書きは、メモリへのramc[pc]=data 書き込み、つまり読み取りを意味します ramc[pc]

5.2 加算器

def adder(a=0, b=0, ci=0, sub=0):
    return a-b+ci if sub == 1 else a+b+ci

実際の加算器は論理ゲートを使用します, これは、特定の関係で積み重ねられた一連のスイッチに相当します. ここでは、実装を大幅に簡素化する高水準言語シミュレーションを使用します. この加算器は a と b の加算を実現し、ci はキャリー、sub は減算を意味します。

5.3 登録登録

レジスターは、自由変数を使用してレジスターの最後の状態を記憶するという Python のクロージャーの概念を使用して設計されています。callを使用する AC = register() と、AC は返された内部関数 register_inner に相当します。このとき、自由変数としての temp と register_inner は同じクロージャに属します。したがって、一時変数の読み取りと書き込みは永続変数です。状態を維持することと同じです。

レジスタのイネーブル端子 w に相当する w 信号が 1 のときに書き込みます。

def register():
    temp = 0

    def register_inner(data=0, w=0):
        nonlocal temp
        if w == 1:
            temp = data
        return temp
    return register_inner
"

実際の CPU 設計では、レジスタをどのように設計するかが大きな問題です。マイコン原理講座の表面的なCPUモデル学習でも、リレーや三極管が記憶できることを理解するには、かなりの工夫が必要です。この記事では、基礎となるハードウェアをシミュレートするために高水準言語を使用します。これは、もう一度やり直すことしかできないため、ここではクロージャの概念を深く理解する必要があります。

"

5.4 8bit 21 セレクター

21 sel end が 1 の場合、セレクタは b を返します。sel がゼロの場合、a を返します。つまり、2 つの入力のうちの 1 つが出力として選択されます。

def b8_21selector(a=0, b=0, sel=0):
    return a if sel == 0 else b

6.統合CPU

CPU のさまざまなコンポーネントを統合する場合、最初に新しいコンポーネントを作成し、次にそれらを初期化し、最後に pc をゼロに設定して無限ループを開始します。

サイクル処理では、まずプログラム命令 RAM のデータを命令レジスタに書き込み、命令レジスタに従って各制御信号をデコードし、命令制御信号の制御に従って動作します。

まず、IR コマンド レジスタが HLt 停止コマンドの場合、システムはブレークします。それ以外の場合は、dr に従ってデータ信号を DR データ レジスタに書き込むかどうかを決定します。

加算器の動作は自動で、入力の 1 つは AC アキュムレータ レジスタで、もう 1 つの入力はサブ減算制御信号によって制御される DR データ レジスタです。

加算演算子が動作した後、結果はデー​​タバスから直接来るデータと共に 21 セレクタに送られ、s21 信号が選択されるのを待ってから、AC 信号に従って AC 累積レジスタに格納されます。次の計算。

zf はゼロ フラグ レジスタとして使用されます。AC アキュムレータに格納された結果がゼロの場合、zf は 1 です。このとき、pre が 1 であれば pc を DR データレジスタの値として設定することができ、演算結果が 0 の場合のジャンプ機能を実現します。それ以外の場合は、下向きに実行を続けます。

全体的な統合後、コードは次のようになります。

def adder(a=0, b=0, ci=0, sub=0):
    return a-b+ci if sub == 1 else a+b+ci
def b8_21selector(a=0, b=0, sel=0):
    return a if sel == 0 else b
def register():
    temp = 0
    def register_inner(data=0, w=0):
        nonlocal temp
        if w == 1:
            temp = data
        return temp
    return register_inner
def int2bin(data=0, length=8, tuple_=1, string=0, humanOrder=0):
    #辅助函数,整数转换为二进制字符串或者元祖。
    r = bin(data)[2:].zfill(length)
    r = r[::-1] if humanOrder == 0 else r
    return r if string == 1 else tuple(int(c) for c in r)
def cpu():
    pc = 0 # pc 计数器从 0 开始,无限循环。
    IR, DR, AC = register(), register(), register() # 新建寄存器
    ramc = [0x18, 0x19, 0x1d, 0x02, 0x31, 0x30, 0x00] # 初始化代码
    ramd = [10, 2, 3, 0xff, 0x06, 0x02] # 初始化数据

    IR(0, w=1) # 初始化寄存器
    DR(0, w=1)
    AC(0, w=1)
    while True:
        IR(ramc[pc], w=1) # 指令读写
        *_, pre, dr, ac, sub, w, s21 = int2bin(IR(), humanOrder=1) # 指令解码
        if IR() == 0:
            break # HLT信号
        DR(ramd[pc], w=dr) # 数据读写
        r = adder(AC(), DR(), sub=sub) # 加法器自动加法
        AC(b8_21selector(DR(), r, s21), w=ac) # 选择器选择后,累加寄存器读写
        ramd[pc] = AC() if w else ramd[pc] # 根据 w 信号,数据写入 RAM
        zf = (AC() == 0) # 零标志位寄存器
        pc = DR() if (pre == 1 and zf == True and s21 == 1) else pc + 1 # Jz 指令跳转
        pc = DR() if (pre == 1 and s21 == 0) else pc # 无条件跳转 Jmp
        print(AC()) 
if __name__ == '__main__':
    cpu()

出力結果が次のようになっていることがわかり10,12,9,9,9,9,6,6,6,6,3,3,3,3,0,0,0ます。プログラムは正常に動作し、CPU は動作しています。

7. 古代プログラマのワークフローを体験する、CPU 向けプログラミング

5 から 1 を引いて 0 になるおもちゃの CPU 用のプログラムを書きましょう: 最初に 5 をロードし、次に 1 を減算し、ゼロかどうかを判断し、ゼロの場合はジャンプして停止し、マイナスにジャンプし続けます。 1 か所。

コードとデータは別々に書かれています。

    ramc = [0x18, 0x1d, 0x31, 0x30, 0x00]
    ramd = [5,    1,    0x04, 0x01]

プログラム出力:

5,4,4,4,3,3,3,2,2,2,1,1,1,0,0

正常に動作します!

"

これらのデータをバイナリに変換すると、明らかに8ビットの情報であり、1単位あたり8個の小さな穴が紙テープに順次刻まれており、古代のコンピューターで実行できます。これは、第一世代のプログラマーが行ったことです。

"

8. まとめ

CPU の動作原理を理解するには、PC がアドレスをインクリメントし続け、プログラム命令を順番に実行することを理解することが重要です。ジャンプ命令に遭遇すると、pc は新しいアドレスにリセットされます。プログラム命令を順次実行する過程で、各ステップは、プログラム命令を分析し、制御信号を生成し、次にすべての CPU 関連デバイスの動作状態を制御し、プログラムの計算結果を生成し、レジスタまたは RAM に保存します。

マクロの観点から見ると、CPU の動作原理は、メモリ データを読み取り、ALU で計算を完了し、それをメモリに保存し、入出力システムが他の周辺機器との相互作用を完了することです。プログラム命令レジスタを読み取り、命令を解析し、各コンポーネントの具体的な処理を制御する、ミクロ的に見れば、PC プログラム カウンタ、ALU デジタル論理演算ユニット、RAM メモリなど、CPU に関連するすべてのコンポーネントは実際には 1 つです。トランジスタ、これらの三極管は電流の作用でオンまたはオフになり、デジタル論理演算のすべての機能を完了し、メモリ状態を維持し、パルス信号を生成します。

この記事では、メソ レベルから CPU を構築してシミュレートし、40 行の Python コードを使用して単純な玩具レベルの CPU を実装します。足し算と引き算ができるようになり、メモリーの読み書き、ジャンプ、条件付きジャンプの機能を持つ。全文は比較的乾いています、読んでくれてありがとう!

おすすめ

転載: blog.csdn.net/weixin_69999177/article/details/128541944
おすすめ