パート4:QFTASMとCogol
アーキテクチャの概要
つまり、コンピューターには16ビットの非同期RISCハーバードアーキテクチャがあります。プロセッサを手作業で構築する場合、RISC(縮小命令セットコンピュータ)アーキテクチャは実際には要件です。私たちの場合、これは、オペコードの数が少なく、さらに重要なことには、すべての命令が非常に類似した方法で処理されることを意味します。
参考までに、Wireworldコンピューターはトランスポートトリガーアーキテクチャーを使用しました。このアーキテクチャーでは、MOV
特殊レジスターの書き込み/読み取りによって命令のみが実行され、計算が実行されました。このパラダイムは実装が非常に簡単なアーキテクチャになりますが、結果は使用できない境界線でもあります。すべての算術/論理/条件付き演算には3つの命令が必要です。難解なアーキテクチャを作成したかったのは明らかでした。
使いやすさを向上させながらプロセッサーをシンプルに保つために、いくつかの重要な設計上の決定を下しました。
- レジスタなし。RAM内のすべてのアドレスは等しく扱われ、任意の操作の任意の引数として使用できます。ある意味では、これはすべてのRAMをレジスタのように扱うことができることを意味します。これは、特別なロード/ストア命令がないことを意味します。
- 同様に、メモリマッピング。読み書きできるものはすべて、統一されたアドレス体系を共有しています。これは、プログラムカウンター(PC)がアドレス0であり、通常の命令と制御フロー命令の唯一の違いは、制御フロー命令がアドレス0を使用することです。
- データは送信ではシリアル、ストレージではパラレルです。コンピューターの「電子」ベースの性質により、データがシリアルリトルエンディアン(最下位ビットが最初)の形式で送信される場合、加算と減算は非常に簡単に実装できます。さらに、シリアルデータにより、面倒なデータバスの必要性がなくなります。データバスは、幅が広く、適切に時間を調整するのが面倒です(データをまとめるには、バスのすべての「レーン」で同じ移動遅延が発生する必要があります)。
- ハーバードアーキテクチャ。プログラムメモリ(ROM)とデータメモリ(RAM)の分割を意味します。これはプロセッサの柔軟性を低下させますが、これはサイズの最適化に役立ちます。プログラムの長さは必要なRAMの量よりもはるかに大きいため、プログラムをROMに分割し、ROMの圧縮に集中できます。 、読み取り専用の場合ははるかに簡単です。
- 16ビットのデータ幅。これは、標準のテトリスボード(10ブロック)よりも広い2の最小乗数です。これにより、データ範囲は-32768〜+32767、最大プログラム長は65536命令になります。(2 ^ 8 = 256の命令で、おもちゃのプロセッサにしたい最も単純なことには十分ですが、テトリスにはできません。)
- 非同期設計。すべてのデータには、コンピューターのタイミングを指示する中央のクロック(または同等のいくつかのクロック)が存在するのではなく、コンピューターの周りを流れるデータと並行して移動する「クロック信号」が付随します。特定のパスは他のパスよりも短くなる可能性があり、これにより中央クロックの設計では困難が生じますが、非同期設計では可変時間の操作を簡単に処理できます。
- すべての命令は同じサイズです。各命令が3つのオペランド(値の値の宛先)を持つ1つのオペコードを持つアーキテクチャが最も柔軟なオプションだと感じました。これには、条件付き移動だけでなく、バイナリデータ操作も含まれます。
- シンプルなアドレッシングモードシステム。さまざまなアドレス指定モードを持つことは、配列や再帰などをサポートするのに非常に役立ちます。比較的単純なシステムで、いくつかの重要なアドレス指定モードを実装することができました。
アーキテクチャの図は、概要の投稿に含まれています。
機能とALU操作
ここからは、プロセッサに必要な機能を決定することでした。実装の容易さと各コマンドの汎用性に特別な注意が払われました。
条件付き移動
条件付きの動きは非常に重要であり、小規模および大規模の両方の制御フローとして機能します。「小規模」は特定のデータ移動の実行を制御する能力を指し、「大規模」は制御フローを任意のコードに転送する条件付きジャンプ操作としての使用を指します。メモリマッピングのため、条件付きの移動ではデータを通常のRAMにコピーし、宛先アドレスをPCにコピーできるため、専用のジャンプ操作はありません。同様の理由で、無条件移動と無条件ジャンプの両方を放棄することも選択しました。どちらもTRUEにハードコードされた条件を持つ条件付き移動として実装できます。
2つの異なるタイプの条件付き移動を選択しました:「ゼロでない場合は移動」(MNZ
)および「ゼロ未満の場合は移動」(MLZ
)。機能的にMNZ
は、データのいずれかのビットが1であるかどうかをチェックすることになりますがMLZ
、符号ビットが1 であるかどうかをチェックすることになります。これらはそれぞれ、等価および比較に役立ちます。その理由は、我々は、「ゼロならば移動」などの他のものの上にこれら二つを選んだ(MEZ
(「ゼロより大きい場合に移動」)又はMGZ
それがあった)MEZ
が、空の信号から真の信号を作成必要とするMGZ
必要、より複雑な検査であります符号ビットは0で、少なくとも1つのビットは1です。
算術
プロセッサ設計のガイドという点で次に重要な命令は、基本的な算術演算です。前に述べたように、リトルエンディアンのシリアルデータを使用します。エンディアンの選択は、加算/減算操作の容易さによって決まります。最下位ビットを最初に到着させることにより、算術ユニットはキャリービットを簡単に追跡できます。
負の数には2の補数表現を使用することにしました。これにより、加算と減算の一貫性が向上するためです。Wireworldコンピュータが1の補数を使用したことは注目に値します。
加算と減算は、コンピューターのネイティブの算術サポートの範囲です(後述のビットシフトを除く)。乗算などの他の演算は、アーキテクチャで処理するには複雑すぎるため、ソフトウェアで実装する必要があります。
ビット演算
私たちのプロセッサがありAND
、OR
とXOR
あなたが期待する何をすべきかの指示。持っているのではなくNOT
、命令を、私たちは「と-ない」(持っていることを選んだANT
)命令を。NOT
命令の難しさは、信号の欠如から信号を作成する必要があることです。これは、セルオートマトンでは困難です。ANT
最初の引数のビットが1であり、二番目の引数のビットは、このように0である場合にのみ、命令は1を返すNOT x
と等価であるANT -1 x
(同様にXOR -1 x
)。さらに、ANT
汎用性があり、マスキングでその主な利点があります。テトリスプログラムの場合、テトロミノを消去するために使用します。
ビットシフト
ビットシフト操作は、ALUによって処理される最も複雑な操作です。シフトする値とシフトする量の2つのデータ入力を受け取ります。(可変量のシフトのため)複雑であるにもかかわらず、これらの操作は、テトリスに関係する多くの「グラフィカル」操作を含む多くの重要なタスクにとって重要です。ビットシフトは、効率的な乗算/除算アルゴリズムの基盤としても機能します。
プロセッサには、「左シフト」(SL
)、「右シフト論理」(SRL
)、「右シフト算術」(SRA
)の3ビットのシフト操作があります。最初の2ビットのシフト(SL
およびSRL
)は、新しいビットをすべてゼロで埋めます(つまり、右にシフトした負の数は負ではなくなります)。シフトの2番目の引数が0〜15の範囲外の場合、予想どおり、結果はすべてゼロになります。最後のビットシフトについてはSRA
、ビットシフトは入力の符号を保持するため、真の2除算として機能します。
命令パイプライン
ここで、アーキテクチャのざらざらした詳細について説明します。各CPUサイクルは、次の5つのステップで構成されます。
1. ROMから現在の命令を取得する
PCの現在の値は、ROMから対応する命令をフェッチするために使用されます。各命令には、1つのオペコードと3つのオペランドがあります。各オペランドは、1つのデータワードと1つのアドレッシングモードで構成されます。これらのパーツは、ROMから読み取られるときに互いに分割されます。
オペコードは4つのビットで16の一意のオペコードをサポートし、そのうち11が割り当てられます
0000 MNZ Move if Not Zero
0001 MLZ Move if Less than Zero
0010 ADD ADDition
0011 SUB SUBtraction
0100 AND bitwise AND
0101 OR bitwise OR
0110 XOR bitwise eXclusive OR
0111 ANT bitwise And-NoT
1000 SL Shift Left
1001 SRL Shift Right Logical
1010 SRA Shift Right Arithmetic
1011 unassigned
1100 unassigned
1101 unassigned
1110 unassigned
1111 unassigned
2. 前の命令の結果(必要な場合)をRAMに書き込みます。
前の命令の条件(条件付き移動の最初の引数の値など)に応じて、書き込みが実行されます。書き込みのアドレスは、前の命令の第3オペランドによって決定されます。
命令フェッチ後に書き込みが発生することに注意することが重要です。これにより、分岐遅延スロットが作成されます。このスロットでは、分岐先の最初の命令の代わりに、分岐命令(PCに書き込む操作)の直後の命令が実行されます。
特定のインスタンス(無条件ジャンプなど)では、分岐遅延スロットを最適化して削除できます。それ以外の場合は不可能であり、分岐後の命令は空のままにしておく必要があります。さらに、このタイプの遅延スロットは、発生するPC増分を考慮して、実際のターゲット命令より1アドレス少ない分岐ターゲットを使用する必要があることを意味します。
つまり、次の命令がフェッチされた後に前の命令の出力がRAMに書き込まれるため、条件付きジャンプの後に空白の命令が必要です。そうしないと、ジャンプのためにPCが正しく更新されません。
3.現在の命令の引数のデータをRAMから読み取ります
前述のように、3つのオペランドはそれぞれ、データワードとアドレッシングモードの両方で構成されています。データワードは16ビットで、RAMと同じ幅です。アドレッシングモードは2ビットです。
多くの実世界のアドレッシングモードはマルチステップ計算(オフセットの追加など)を伴うため、アドレッシングモードはこのようなプロセッサにとって非常に複雑な原因になる可能性があります。同時に、用途の広いアドレス指定モードは、プロセッサの使いやすさにおいて重要な役割を果たします。
ハードコードされた数値をオペランドとして使用し、データアドレスをオペランドとして使用するという概念を統一しようとしました。これにより、カウンターベースのアドレス指定モードが作成されました。オペランドのアドレス指定モードは、RAM読み取りループでデータを送信する回数を表す単純な数値です。これには、即時、直接、間接、および二重間接のアドレス指定が含まれます。
00 Immediate: A hard-coded value. (no RAM reads)
01 Direct: Read data from this RAM address. (one RAM read)
10 Indirect: Read data from the address given at this address. (two RAM reads)
11 Double-indirect: Read data from the address given at the address given by this address. (three RAM reads)
この逆参照が実行された後、命令の3つのオペランドは異なる役割を持ちます。通常、最初のオペランドは2項演算子の最初の引数ですが、現在の命令が条件付き移動である場合の条件としても機能します。2番目のオペランドは、2項演算子の2番目の引数として機能します。3番目のオペランドは、命令の結果の宛先アドレスとして機能します。
最初の2つの命令はデータとして機能し、3番目の命令はアドレスとして機能するため、アドレス指定モードは、使用される位置に応じて解釈が若干異なります。たとえば、固定モードのRAMアドレスからデータを読み取るには1回のRAM読み取りが必要です)が、即時モードは固定RAMアドレスへのデータの書き込みに使用されます(RAM読み取りが不要なため)。
4.結果を計算する
オペコードと最初の2つのオペランドがALUに送信され、バイナリ演算が実行されます。算術演算、ビット演算、およびシフト演算の場合、これは関連する演算を実行することを意味します。条件付き移動の場合、これは単に第2オペランドを返すことを意味します。
オペコードと第1オペランドを使用して条件を計算し、結果をメモリに書き込むかどうかを決定します。条件付き移動の場合、これは、オペランドのいずれかのビットが1(for MNZ
)かどうかを判別するか、符号ビットが1(for MLZ
)かどうかを判別することを意味します。オペコードが条件付き移動でない場合、書き込みは常に実行されます(条件は常に真です)。
5.プログラムカウンターをインクリメントする
最後に、プログラムカウンターが読み取られ、インクリメントされ、書き込まれます。
読み取られた命令と書き込まれた命令の間のPCインクリメントの位置により、これは、PCを1インクリメントする命令がノーオペレーションであることを意味します。PCをそれ自体にコピーする命令により、次の命令が連続して2回実行されます。ただし、命令パイプラインに注意を払わないと、連続した複数のPC命令が無限ループなどの複雑な影響を引き起こす可能性があることに注意してください。
テトリスアセンブリの探求
プロセッサ用にQFTASMという名前の新しいアセンブリ言語を作成しました。このアセンブリ言語は、コンピューターのROMにあるマシンコードと1対1で対応しています。
QFTASMプログラムは、1行に1つずつ、一連の命令として記述されます。各行の形式は次のとおりです。
[line numbering] [opcode] [arg1] [arg2] [arg3]; [optional comment]
オペコードリスト
前述のように、コンピューターでサポートされているオペコードは11個あり、それぞれに3つのオペランドがあります。
MNZ [test] [value] [dest] – Move if Not Zero; sets [dest] to [value] if [test] is not zero.
MLZ [test] [value] [dest] – Move if Less than Zero; sets [dest] to [value] if [test] is less than zero.
ADD [val1] [val2] [dest] – ADDition; store [val1] + [val2] in [dest].
SUB [val1] [val2] [dest] – SUBtraction; store [val1] - [val2] in [dest].
AND [val1] [val2] [dest] – bitwise AND; store [val1] & [val2] in [dest].
OR [val1] [val2] [dest] – bitwise OR; store [val1] | [val2] in [dest].
XOR [val1] [val2] [dest] – bitwise XOR; store [val1] ^ [val2] in [dest].
ANT [val1] [val2] [dest] – bitwise And-NoT; store [val1] & (![val2]) in [dest].
SL [val1] [val2] [dest] – Shift Left; store [val1] << [val2] in [dest].
SRL [val1] [val2] [dest] – Shift Right Logical; store [val1] >>> [val2] in [dest]. Doesn't preserve sign.
SRA [val1] [val2] [dest] – Shift Right Arithmetic; store [val1] >> [val2] in [dest], while preserving sign.
アドレス指定モード
各オペランドには、データ値とアドレッシング移動の両方が含まれます。データ値は、-32768〜32767の範囲の10進数で記述されます。アドレス指定モードは、データ値の1文字のプレフィックスで記述されます。
mode name prefix
0 immediate (none)
1 direct A
2 indirect B
3 double-indirect C
サンプルコード
5行のフィボナッチ数列:
0. MLZ -1 1 1; initial value
1. MLZ -1 A2 3; start loop, shift data
2. MLZ -1 A1 2; shift data
3. MLZ -1 0 0; end loop
4. ADD A2 A3 1; branch delay slot, compute next term
このコードはフィボナッチ数列を計算し、RAMアドレス1には現在の用語が含まれます。28657の後、すぐにオーバーフローします。
グレーコード:
0. MLZ -1 5 1; initial value for RAM address to write to
1. SUB A1 5 2; start loop, determine what binary number to covert to Gray code
2. SRL A2 1 3; shift right by 1
3. XOR A2 A3 A1; XOR and store Gray code in destination address
4. SUB B1 42 4; take the Gray code and subtract 42 (101010)
5. MNZ A4 0 0; if the result is not zero (Gray code != 101010) repeat loop
6. ADD A1 1 1; branch delay slot, increment destination address
このプログラムは、グレイコードを計算し、アドレス5から始まる連続したアドレスにコードを保存します。このプログラムは、間接アドレス指定や条件ジャンプなどのいくつかの重要な機能を利用します。結果のグレイコードが101010
になると停止し、アドレス56の入力51で発生します。
オンライン通訳
El'endia Starmanは、ここで非常に便利なオンライン通訳を作成しました。コードをステップ実行し、ブレークポイントを設定し、RAMへの手動書き込みを実行し、RAMをディスプレイとして視覚化できます。
コゴル
アーキテクチャとアセンブリ言語が定義されたら、プロジェクトの「ソフトウェア」側の次のステップは、テトリスに適した高レベル言語の作成でした。したがって、私はCogolを作成しました。この名前は「COBOL」の省略語であり、「C of Game of Life」の頭字語でもありますが、CogolがCであり、実際のコンピューターに対するCであることに注意する価値があります。
Cogolは、アセンブリ言語のすぐ上のレベルに存在します。一般に、Cogolプログラムのほとんどの行はそれぞれ単一のアセンブリ行に対応していますが、言語の重要な機能がいくつかあります。
- 基本的な機能には、より読みやすい構文を持つ割り当てと演算子を持つ名前付き変数が含まれます。例えば、
ADD A1 A2 3
となりz = x + y;
のアドレスへのコンパイラマッピング変数を使用して、。
- 以下のようなループ構造
if(){}
、while(){}
およびdo{}while();
そのコンパイラが分岐を処理します。
- Tetrisボードに使用される1次元配列(ポインター演算付き)。
- サブルーチンと呼び出しスタック。これらは、大量のコードの重複を防ぎ、再帰をサポートするのに役立ちます。
コンパイラ(最初から書いた)は非常に基本的で素朴ですが、いくつかの言語構成を手作業で最適化して、コンパイルされたプログラムの長さを短くしようとしました。
さまざまな言語機能がどのように機能するかの簡単な概要を次に示します。
トークン化
ソースコードは、トークン内で隣接する文字を許可する単純なルールを使用して、線形的にトークン化されます(シングルパス)。現在のトークンの最後の文字に隣接できない文字に遭遇すると、現在のトークンは完全であると見なされ、新しい文字は新しいトークンを開始します。一部の文字(例えば、{
または,
)は、他の文字に隣接し、従って、自分のトークンであることはできません。その他(のような>
または=
)は、そのクラス内の他の文字に隣接するように許可されているので、のようなトークンを形成することができ>>>
、==
または>=
好きではなく=2
。空白文字はトークン間の境界を強制しますが、それ自体は結果に含まれません。トークン化するのが最も難しいキャラクターは-
減算と単項否定の両方を表現できるため、特別なケースが必要だからです。
解析
解析もシングルパス方式で行われます。コンパイラには、さまざまな言語構造のそれぞれを処理するためのメソッドがあり、トークンはさまざまなコンパイラメソッドによって消費されるため、グローバルトークンリストからポップされます。コンパイラが予期しないトークンを検出すると、構文エラーが発生します。
グローバルメモリ割り当て
コンパイラーは、各グローバル変数(ワードまたは配列)に独自の指定RAMアドレスを割り当てます。キーワードを使用してすべての変数を宣言する必要があります。my
そのため、コンパイラーはそのスペースを割り当てることができます。名前付きグローバル変数よりも格段に優れているのは、スクラッチアドレスメモリ管理です。多くの命令(特に条件付きおよび多くの配列アクセス)では、中間計算を保存するために一時的な「スクラッチ」アドレスが必要です。コンパイルプロセス中に、コンパイラは必要に応じてスクラッチアドレスの割り当てと割り当て解除を行います。コンパイラがより多くのスクラッチアドレスを必要とする場合、より多くのRAMをスクラッチアドレスとして使用します。各スクラッチアドレスは何度も使用されますが、プログラムが必要とするスクラッチアドレスはわずかであることが一般的だと思います。
IF-ELSE
声明
if-else
ステートメントの構文は、標準のC形式です。
other code
if (cond) {
first body
} else {
second body
}
other code
QFTASMに変換されると、コードは次のように配置されます。
other code
condition test
conditional jump
first body
unconditional jump
second body (conditional jump target)
other code (unconditional jump target)
最初の本体が実行されると、2番目の本体はスキップされます。最初の本文がスキップされると、2番目の本文が実行されます。
アセンブリでは、通常、条件テストは単なる減算であり、結果の符号によってジャンプするか本体を実行するかが決まります。MLZ
命令は、次のような不平等を処理するために使用されています>
か<=
。MNZ
命令は、処理するために使用される==
(引数が等しくない場合、したがって)差がゼロでない場合、それは体の上にジャンプするので、。複数式の条件は現在サポートされていません。
場合はelse
文が省略され、無条件ジャンプも省略され、QFTASMコードは次のようになります。
other code
condition test
conditional jump
body
other code (conditional jump target)
WHILE
声明
while
ステートメントの構文も標準C形式です。
other code
while (cond) {
body
}
other code
QFTASMに変換されると、コードは次のように配置されます。
other code
unconditional jump
body (conditional jump target)
condition test (unconditional jump target)
conditional jump
other code
条件のテストと条件付きジャンプはブロックの最後にあります。つまり、ブロックの各実行後にそれらが再実行されます。条件がfalseを返す場合、本体は繰り返されず、ループは終了します。ループ実行の開始時に、制御フローはループ本体を飛び越えて条件コードにジャンプするため、条件が初めてfalseの場合、本体は実行されません。
MLZ
命令は、次のような不平等を処理するために使用されています>
か<=
。during if
ステートメントとは異なり、差がゼロでない場合(したがって、引数が等しくない場合)に本体にジャンプするため、MNZ
命令を処理!=
するために使用されます。
DO-WHILE
声明
唯一の違いwhile
とは、do-while
ということですdo-while
、それは常に少なくとも一度実行されるように、ループ本体が最初にスキップされていません。通常do-while
、ループを完全にスキップする必要がないことがわかっている場合は、ステートメントを使用してアセンブリコードを数行保存します。
配列
1次元配列は、連続したメモリブロックとして実装されます。すべての配列は、宣言に基づいて固定長です。配列は次のように宣言されます。
my alpha[3]; # empty array
my beta[11] = {3,2,7,8}; # first four elements are pre-loaded with those values
アレイの場合、これは可能なRAMマッピングであり、アドレス15〜18がアレイ用に予約されている方法を示しています。
15: alpha
16: alpha[0]
17: alpha[1]
18: alpha[2]
標識されたアドレスalpha
の位置へのポインタで満たされているalpha[0]
チエニルケースアドレス15は値16が含まれているにように、alpha
あなたはスタックとしてこの配列を使用する場合、変数はおそらくスタックポインタとして、Cogolコードの内部で使用することができます。
配列の要素へのアクセスは、標準array[index]
表記で行われます。の値がindex
定数の場合、この参照にはその要素の絶対アドレスが自動的に入力されます。それ以外の場合は、ポインター計算(加算のみ)を実行して、目的の絶対アドレスを見つけます。のようなインデックスをネストすることもできalpha[beta[1]]
ます。
サブルーチンと呼び出し
サブルーチンは、複数のコンテキストから呼び出すことができるコードのブロックであり、コードの重複を防ぎ、再帰プログラムの作成を可能にします。以下に、フィボナッチ数を生成する再帰サブルーチンを備えたプログラムを示します(基本的に最も遅いアルゴリズム)。
# recursively calculate the 10th Fibonacci number
call display = fib(10).sum;
sub fib(cur,sum) {
if (cur <= 2) {
sum = 1;
return;
}
cur--;
call sum = fib(cur).sum;
cur--;
call sum += fib(cur).sum;
}
サブルーチンはキーワードsub
で宣言され、サブルーチンはプログラム内のどこにでも配置できます。各サブルーチンには、引数のリストの一部として宣言される複数のローカル変数を含めることができます。これらの引数には、デフォルト値を指定することもできます。
再帰呼び出しを処理するために、サブルーチンのローカル変数がスタックに保存されます。RAMの最後の静的変数は呼び出しスタックポインターであり、その後のすべてのメモリは呼び出しスタックとして機能します。サブルーチンが呼び出されると、呼び出しスタック上に新しいフレームが作成されます。これには、すべてのローカル変数とリターン(ROM)アドレスが含まれます。プログラム内の各サブルーチンには、ポインターとして機能する単一の静的RAMアドレスが与えられます。このポインターは、呼び出しスタック内のサブルーチンの「現在の」呼び出しの場所を示します。ローカル変数の参照は、この静的ポインターの値とオフセットを使用して行われ、その特定のローカル変数のアドレスを提供します。呼び出しスタックには、静的ポインターの以前の値も含まれています。ここに'
RAM map:
0: pc
1: display
2: scratch0
3: fib
4: scratch1
5: scratch2
6: scratch3
7: call
fib map:
0: return
1: previous_call
2: cur
3: sum
サブルーチンについて興味深いのは、特定の値を返さないことです。むしろ、サブルーチンの実行後にサブルーチンのすべてのローカル変数を読み取ることができるため、サブルーチン呼び出しからさまざまなデータを抽出できます。これは、サブルーチンの特定の呼び出しのポインターを格納することで実現されます。このポインターを使用して、(最近割り当て解除された)スタックフレーム内からローカル変数を回復できます。
サブルーチンを呼び出す方法は複数あり、すべてcall
キーワードを使用します。
call fib(10); # subroutine is executed, no return vaue is stored
call pointer = fib(10); # execute subroutine and return a pointer
display = pointer.sum; # access a local variable and assign it to a global variable
call display = fib(10).sum; # immediately store a return value
call display += fib(10).sum; # other types of assignment operators can also be used with a return value
サブルーチン呼び出しの引数として、任意の数の値を指定できます。提供されない引数は、デフォルト値があればそれで埋められます。提供されず、デフォルト値を持たない引数はクリアされないため(命令/時間を節約するため)、サブルーチンの開始時に任意の値を取る可能性があります。
ポインターはサブルーチンの複数のローカル変数にアクセスする方法ですが、ポインターは一時的なものにすぎないことに注意することが重要です。別のサブルーチン呼び出しが行われると、ポインターが指すデータは破棄されます。
ラベルのデバッグ
任意{...}
Cogolプログラムのコードブロックは、マルチワード説明ラベルが先行することができます。このラベルは、コンパイルされたアセンブリコードにコメントとして添付され、特定のコードチャンクを見つけやすくするため、デバッグに非常に役立ちます。
分岐遅延スロットの最適化
コンパイルされたコードの速度を改善するために、CogolコンパイラーはQFTASMコードの最終パスとしていくつかの本当に基本的な遅延スロット最適化を実行します。空の分岐遅延スロットを使用した無条件ジャンプの場合、遅延スロットはジャンプ先の最初の命令で埋められ、ジャンプ先は次の命令を指すように1ずつ増加します。これにより、通常、無条件ジャンプが実行されるたびに1サイクル節約されます。
Cogolでテトリスコードを書く
最終的なTetrisプログラムはCogolで作成されており、ソースコードはこちらから入手できます。コンパイルされたQFTASMコードは、ここから入手できます。便宜上、パーマリンクがここに提供されています:QFTASMのテトリス。目標はアセンブリコード(Cogolコードではなく)をゴルフすることであったため、結果のCogolコードは扱いにくいです。通常、プログラムの多くの部分はサブルーチンに配置されますが、これらのサブルーチンは実際にはコードを複製して命令を保存するのに十分なほど短いものでしたcall
ステートメント。最終コードには、メインコードに加えて1つのサブルーチンしかありません。さらに、多くの配列が削除され、同等の長さの個々の変数のリストに置き換えられるか、プログラム内の多数のハードコードされた数値に置き換えられました。最終的にコンパイルされたQFTASMコードは300命令未満ですが、Cogolソース自体よりもわずかに長いだけです。