「プロセッサはどのように機能するのか?」という質問がありますか? はい、はい、まさにあなたのPC /ラップトップ/スマートフォンにあるもの。 この記事では、Verilogで設計された自己発明のプロセッサの例を示します。 Verilogは、見た目とまったく同じプログラミング言語ではありません。 これはハードウェア記述言語です。 記述されたコードは、(もちろんシミュレータで実行しない限り)何によっても実行されませんが、物理回路の設計、またはFPGA(Field Programmable Gate Array)によって認識される形式に変わります。
免責事項:この記事は大学でのプロジェクトでの作業の結果であるため、作業時間は限られており、プロジェクトの多くの部分はまだ開発の初期段階に過ぎません。
この記事で作成されたプロセッサーは、現在普及しているプロセッサーとほとんど共通点がないことに注意してください。しかし、私はその作成でわずかに異なる目標を達成しようとしました。
プログラミングプロセスを真に理解するには、使用する各ツールの動作を想像する必要があります。言語のコンパイラ/インタープリター、仮想マシン(存在する場合)、中間コード、そしてもちろんプロセッサー自体です。 プログラミングを学ぶ人々は、多くの場合、最初の段階に長い間います-彼らは、言語とそのコンパイラがどのように機能するかについてのみ考えます。 多くの場合、これらの問題の原因がどこから来たのかわからないため、初心者プログラマーには解決策がわからないエラーが発生します。 私自身、状況が上記のようなものであるいくつかのライブ例を見てきたので、この状況を修正し、初心者がすべてのステップを理解するのに役立つ一連のことを作成しようとすることにしました。
このキットの構成:
- 実際に発明された言語
- VS Codeのハイライトプラグイン
- コンパイラー
- 命令セット
- この一連の命令を実行できる単純なプロセッサ(Verilogで記述)
この記事は最新のリアルプロセッサに似たものではなく、詳細を説明しなくても理解しやすいモデルについて説明しています。
自分でやりたい場合に必要なもの:
CPUシミュレーションを実行するには、Intel WebサイトからダウンロードできるModelSimが必要です。
OurLangコンパイラを実行するには、Javaバージョン> = 8が必要です。
プロジェクトへのリンク:
https://github.com/IamMaxim/OurCPU
https://github.com/IamMaxim/OurLang
拡張子:
https://github.com/IamMaxim/ourlang-vscode
Verilogパーツをビルドするには、通常bashスクリプトを使用します。
#/bin/bash vlib work vlog *.v vsim -c testbench_1 -do "run; exit"
ただし、これはGUIで繰り返すことができます。
Intellij IDEAを使用してコンパイラを操作すると便利です。 主なことは、必要なモジュールが依存関係にあるモジュールを追跡することです。 読者がコンパイラのソースコードを読むことを期待しているため、既製の.jarをオープンアクセスで公開しませんでした。
起動されたモジュールは、コンパイラーとインタープリターです。 コンパイラーのすべては明確で、インタープリターはJavaのOurCPUの単なるシミュレーターですが、この記事では考慮しません。
命令セット
命令セットから始める方が良いと思います。
いくつかの命令セットアーキテクチャがあります。
- スタックベース-記事で説明されていること。 特徴的な機能は、すべてのオペランドがスタックにプッシュされ、スタックからポップされることです。これにより、実行の並列化の可能性が直ちに排除されますが、データを操作する最も簡単な方法の1つです。
- アキュムレータベース-一番下の行は、命令によって変更される値を格納するレジスタが1つしかないことです。
- レジスタベースは、実行の並列化、パイプライン処理などを含むさまざまな最適化を使用して最大のパフォーマンスを達成できるため、最新のプロセッサで使用されています。
次に、プロセッサの実装を確認することを提案します。
コードはいくつかのモジュールで構成されています。
- CPU
- RAM
- 各命令のモジュール
RAMは、メモリ自体を直接含むモジュールであり、メモリ内のデータにアクセスする方法でもあります。
CPU-プログラムの進行を直接制御するモジュール:命令を読み取り、制御を目的の命令に転送し、必要なレジスタ(現在の命令へのポインタなど)を格納します。
ほとんどすべての命令はスタックでのみ機能するため、従うだけです。 一部(putw、putb、jmp、jifなど)には、命令自体に追加の引数があります。 必要なデータを読み取ることができるように、命令全体を渡す必要があります。
プロセッサの動作の一般的な概要は次のとおりです。
命令レベルでのデバイス設計の一般原則
私は、プログラム自体から直接デバイスに精通する時だと思います。 上の図からわかるように、各命令の後、アドレスは次のアドレスに移動します。 これにより、プログラムに線形のコースが与えられます。 この線形性(条件、ループなど)を破る必要がある場合、分岐命令が使用されます(命令セットでは、これらはjmpとjifです)。
関数を呼び出すとき、すべての現在の状態を保存する必要があります。そのために、アクティベーションレコード(この情報を保存するレコード)があります。 それらは、プロセッサ自体や命令に結び付けられるものではなく、コンパイラがコードを生成するときに使用する概念にすぎません。 OurLangのアクティベーションレコードの構造は次のとおりです。
この図からわかるように、ローカル変数はアクティベーションレコードにも格納されます。これにより、実行時ではなくコンパイル時にメモリ内の変数のアドレスを計算できるため、プログラムの実行が高速化されます。
関数呼び出しの場合、この命令セットは、CPUモジュールに含まれる2つのレジスタ(操作ポインターとアクティベーションアドレスポインター)を操作するメソッドを提供します-putopa / popopa、putara / popara。
コンパイラ
次に、最終プログラマーに最も近い部分、つまりコンパイラーを見てみましょう。 一般に、プログラムとしてのコンパイラは3つの部分で構成されています。
- レクサー
- パーサー
- コンパイラ
字句解析プログラムは、プログラムのソースコードを、パーサーが理解できる字句単位に変換する役割を果たします。
パーサーは、これらの字句単位から抽象的な構文ツリーを構築します。
コンパイラはこのツリーを通過して、低レベルの命令で構成されるコードを生成します。 これは、プロセッサで実行可能なバイトコードまたはバイナリコードのいずれかです。
OurLangコンパイラでは、これらの部分はそれぞれクラスで表されます
- Lexer.java
- Parser.java
- Compiler.java
言語
OurLangはまだ初期段階にあります。つまり、機能していますが、これまでのところ多くのことはなく、言語のコア部分でさえ最終化されていません。 しかし、コンパイラの本質を理解するには、現在の状態で十分です。
構文を理解するためのプログラムの例として、次のコードが提案されています(機能のテストにも使用されます)。
// single-line comments /* * Multi-line comments */ function print(int arg) { instr(putara, 0); instr(putw, 4); instr(add, 0); instr(lw, 0); instr(printword, 0); } function func1(int arg1, int arg2): int { print(arg1); print(arg2); if (arg1 == 0) { return arg2; } else { return func1(arg1 - 1, arg2); }; } function main() { var i: int; i = func1(1, 10); if (i == 0) { i = 1; } else { i = 2; }; print(i); }
私は言語に集中せず、あなたの研究のためにそれを残します。 もちろん、コンパイラコードを通じて;)。
それを書くとき、私はコメントなしで明確な自己説明的なコードを作成しようとしたので、コンパイラコードを理解するのに問題はないはずです。
もちろん、最も興味深いのは、コードを作成して、それがどのようになるかを観察することです。 幸いなことに、OurLangコンパイラはコメント付きのアセンブリのようなコードを生成し、
内部で何が起こっているかについて混乱しないようにするのに役立ちます。
また、Visual Studio Codeの拡張機能をインストールすることをお勧めします。これにより、言語での作業が容易になります。
プロジェクトの学習を頑張ってください!