
メガプロセッサコマンドシステムについては、開発者のサイトで説明されています 。
ほとんどの命令は1バイトで構成され、その後に直接オペランド(1または2バイト)が続く場合があります。 汎用レジスタ(R0〜R3)は4つしかありませんが、等しくありません。たとえば、メモリアクセスコマンドの場合、アドレスはR2またはR3になければなりません。 また、オペランドは残りの2つのレジスタのいずれかにあります。
x86またはARM命令セットに慣れているプログラマーにとって、メガプロセッサ命令セットは非常に貧弱に見えます:算術命令の間接ベース+オフセットアドレスまたは直接オペランドはありません(
addq ±1
、
addq ±2
を除く)。 しかし、いくつかの予期しない可能性があります:個別の
sqrt
コマンド、およびシフトコマンド用の
.wt
モード。これにより、結果が拡張ビットの合計に置き換えられます。 したがって、たとえば、1組のコマンド
ld.b r1, #15; lsr.wt r0, r1
ld.b r1, #15; lsr.wt r0, r1
は、
r0
の単位ビット数を計算し
r0
(就職の面接官に愛されている質問です!)。 (x86またはARMでよく知られている
mov
ニーモニックではなく)レジスタに直接値をロードするコマンドの
ld
ニーモニックは、それを実行する方法を示します。実際、プロセッサの観点からは、
ld.b r1, (pc++)
が実行されます。
それでは始めましょう。
メガプロセッサのプログラムは、割り込みベクトルのテーブルで(アドレス0から)開始します。 4つのベクトルのそれぞれに4バイトが割り当てられます。 0x10から、実際のプログラムコードを見つけることができます。 64KBのアドレス空間から、前半全体(アドレス0x8000まで)をコードで使用できます。 アドレス0xA000-0xA0FFは「ディスプレイ」に対応します。これは、各ビットにLEDインジケータが装備された個別のメモリです。 「メモリ容量は256バイトです。」を書き込むときにマークが間違っていました。コードとデータのメインメモリではなく、「ビデオメモリ」の量です。
プログラムの4つの割り込みベクターのうち、
reset
ベクターのみが使用され、他のベクターには1つの
reti
命令からの「スタブ」があります。 (x86またはARMのプログラマーの場合、割り込みハンドラーからのreturnコマンドは
iret
ニーモニックではおなじみです。)プログラムのこれらの割り込みはいずれも発生しないため、「スタブ」を配置することさえできません。
reset: jmp start; nop; ext_int: reti; nop; nop; nop; div_zero: reti; nop; nop; nop; illegal: reti; nop; nop; nop;
開始後の最初のことは、スタックと変数を初期化することです。 アドレス0x2000から始めて、スタックを小さくします。これで十分な余裕があります。 必要な変数は2つだけです。現在のRNG値の
seed
と、32個の値の
position
配列です。この列の「ドロップ」がクリープする場所を監視する「表示列」ごとに1つです。 配列は32バイトのランダムバイトで初期化されます。 サブルーチン呼び出しである
jsr
コマンドは、x86の
call
またはARMの
bl
に対応し
call
。
start: ld.w r0, #0x2000; move sp, r0; // set random positions ld.b r1, #32; init_loop: jsr rand; // returns random value in r0 ld.b r2, #position; add r2, r1; st.b (r2), r0; addq r1, #-1; bne init_loop;
1つのコマンド
(#position + r1)
1バイトをアドレス
(#position + r1)
に書き込むことはできないため、最初に別の追加コマンドでアドレスを計算する必要があります。
プログラムの主要部分は、「表示列」ごとに右から左に移動し、その中の「ドロップ」を1つ下にシフトする無限のサイクルです。 「ドロップ」の下位2ビットはその色(3-「オン」、0、1または2-「オフ」)、残りの6ビット-座標(0..63)を示すため、「下方シフト」は4の追加を意味します。 「ドロップ」のみが「ディスプレイ」の下部にクロールされ(値は255を超えました)、新しいランダムバイトに置き換えられます。
busy_loop: ld.b r1, #32; next_col: ld.b r2, #position; add r2, r1; ld.b r0, (r2); addq r0, #2; addq r0, #2; btst r0, #8; beq save; jsr rand; save: st.b (r2), r0; addq r1, #-1; bmi busy_loop;
1つのコマンドで4を
addq r0, #2
ことは不可能なので、
addq r0, #2
回繰り返し、結果の8ビット目をチェックして255を超えているかどうかを判断します。超えた場合は、新しいランダム値を
position
配列に保存します。 そうでない場合は、古いものを4増やします。条件付きジャンプコマンド
bmi
は、最後のアクションの結果が負の場合、つまり
busy_loop
サイクルの先頭に
busy_loop
ます。 NULL列を処理した後。
乱数はどのように生成しますか? 32ビットのデジタルレインで使用したRANDUはもはや適切ではありません。メガプロセッサは16ビットの数値しか乗算できません。 したがって、 単純なRNGのリストから、乗数が16ビットであるものを取得します。 「ターボパスカル」と呼ばれるRNGが気に入りました。
rand: ld.w r0, seed; ld.w r1, #33797; mulu; addq r2, #1; st.w seed, r2; move r0, r2; ret;
このシンプルできれいなRNGは、生成された値をr0に返しますが、残念ながら、他のすべてのレジスタの値を台無しにします。 どちらの場合でも、
rand
を呼び出すと、
r1
「表示列」のインデックスがあり、保存および復元する必要があることに注意してください。 そして、
r2
はオフセット
(#position + r1)
r2
必要です。 したがって、このオフセットの計算を
rand
内に配置できます。
rand: push r1; // ! ld.w r0, seed; ld.w r1, #33797; mulu; addq r2, #1; st.w seed, r2; pop r1; // ! move r0, r2; ld.b r2, #position; // ! add r2, r1; // ! ret; start: ld.w r0, #0x2000; move sp, r0; // set random positions ld.b r1, #32; init_loop: jsr rand; st.b (r2), r0; addq r1, #-1; bne init_loop; busy_loop: ld.b r1, #32; next_col: ld.b r2, #position; add r2, r1; ld.b r0, (r2); addq r0, #2; addq r0, #2; btst r0, #8; beq save; jsr rand; save: st.b (r2), r0; addq r1, #-1; bmi busy_loop;
ここでの最後のトリックは、
ld.b r2, #position; add r2, r1;
を計算すること
ld.b r2, #position; add r2, r1;
ld.b r2, #position; add r2, r1;
next_col
ループの開始時に、
rand
ルーチン内のジャンプに置き換えること
next_col
でき
next_col
。
rand: push r1; ld.w r0, seed; ld.w r1, #33797; mulu; addq r2, #1; st.w seed, r2; pop r1; move r0, r2; add_position: ld.b r2, #position; add r2, r1; ret; start: <...> busy_loop: ld.b r1, #32; next_col: jsr add_position; // ! ld.b r0, (r2); addq r0, #2; addq r0, #2; btst r0, #8; beq save; jsr rand; save: st.b (r2), r0; addq r1, #-1; bmi busy_loop;
ここで最も興味深いのは
next_col
サイクルの後半で、ディスプレイに「ドロップ」を描画します。
move r3, r1; // x (0..1f) lsr r3, #3; // byte addr in row (0..3) ld.b r2, #0xfc; // y mask and r2, r0; // y * 4 (0..fc) add r3, r2; // byte addr in screen ld.w r2, #0xa000; add r3, r2; // byte addr in memory ld.b r2, #2; lsr.wt r0, r2; ld.b r2, #7; and r2, r1; // bit index in byte (0..7) lsl r2, #1; lsr r0, #2; roxr r2, #1; ld.b r0, (r3); // and now apply test r2; bpl blank; bset r0, r2; jmp apply; blank: bclr r0, r2; apply: st.b (r3), r0; jmp next_col;
目的のビットを「点灯」または「消去」するには、まず「ビデオメモリ」の対応するバイトのアドレスを計算する必要があります。 「列」の番号は
r1
に格納されており、ドロップの位置と「色」は
r0
に格納されているため、バイトアドレスは
(r1 >> 3) + (r0 & 0xfc) + 0xa000
として計算されます。 その後、コマンド
ld.b r2, #2; lsr.wt r0, r2;
ld.b r2, #2; lsr.wt r0, r2;
ドロップの色を決定します
r0
最下位ビットの両方が設定されている場合、これらのコマンドの結果として、値2は
r0
ます。 それ以外の場合、値は0または1です。最後に、
r2
下位3ビットで目的の「ビデオメモリ」ビットの番号を記憶し、シーケンス
lsl r2, #1; lsr r0, #2; roxr r2, #1;
てドロップカラーを高ビット
r2
「プッシュ」し
lsl r2, #1; lsr r0, #2; roxr r2, #1;
lsl r2, #1; lsr r0, #2; roxr r2, #1;
-2番目のコマンドは、カラービットを
r0
からCFフラグにプッシュし、最後(CFの参加による右への循環シフト)はこのビットを
r2
プッシュします。 必要なすべての値に十分なレジスタがない場合は、工夫する必要があります! 最後に、バイトが目的のアドレスの「ビデオメモリ」から取得され、カラービットに応じて、このバイトが設定されるか、目的のビットがリセットされます。
bset
および
bclr
は、第2オペランドの下位ビットのみを使用するため、上位ビット
r2
のカラービットはそれらを
r2
しません。 シーケンス
test r2; bpl blank;
この高位ビットをチェックし
test r2; bpl blank;
test r2; bpl blank;
-条件分岐コマンド
bpl
は、最後のアクションの結果が正の場合、つまり、 少し色が落ちます。
結果は次のとおりです。
コード全体
reset: jmp start; nop; ext_int: reti; nop; nop; nop; div_zero: reti; nop; nop; nop; illegal: reti; nop; nop; nop; rand: push r1; ld.w r0, seed; ld.w r1, #33797; mulu; addq r2, #1; st.w seed, r2; pop r1; move r0, r2; add_position: ld.b r2, #position; add r2, r1; ret; start: ld.w r0, #0x2000; move sp, r0; // set random positions ld.b r1, #32; init_loop: jsr rand; st.b (r2), r0; addq r1, #-1; bne init_loop; busy_loop: ld.b r1, #32; next_col: jsr add_position; ld.b r0, (r2); addq r0, #2; addq r0, #2; btst r0, #8; beq save; jsr rand; save: st.b (r2), r0; addq r1, #-1; bmi busy_loop; move r3, r1; // x (0..1f) lsr r3, #3; // byte addr in row (0..3) ld.b r2, #0xfc; // y mask and r2, r0; // y * 4 (0..fc) add r3, r2; // byte addr in screen ld.w r2, #0xa000; add r3, r2; // byte addr in memory ld.b r2, #2; lsr.wt r0, r2; ld.b r2, #7; and r2, r1; // bit index in byte (0..7) lsl r2, #1; lsr r0, #2; roxr r2, #1; ld.b r0, (r3); // and now apply test r2; bpl blank; bset r0, r2; jmp apply; blank: bclr r0, r2; apply: st.b (r3), r0; jmp next_col; seed: dw 1; position:;
最後に、GIF-KDPVのように「ドロップ」を点滅させます。 実際、これは、プログラムの実行速度が2倍遅くなることを意味します。サイクルの各繰り返しで
busy_loop
が最初に点灯し、次にすべての「ドロップ」を消します。 点火ハーフイテレーションでは、ビデオメモリの2ビットを設定する必要があります。「ドロップ」の現在の位置と前の(最後のハーフイテレーションによって消滅する)の位置です。
そのため、次の場合は「ドロップ」を点灯する必要があります。a)値の下位2ビットが両方とも設定されている場合。 b)半反復に点火します。 -そして、他のすべての場合には消火します。 これをすべて実装する最も簡単な方法は、ドロップの色を定義する一連のコマンド(
ld.b r2, #2; lsr.wt r0, r2;
)で、固定値
#2
を点火ハーフイテレーションで値2を持つ
flag
変数に置き換えること
ld.b r2, #2; lsr.wt r0, r2;
1消火時:
busy_loop: ld.b r1, #3; // ! ld.b r2, flag; // ! sub r1, r2; // ! st.b flag, r1; // ! ld.b r1, #32; next_col: jsr add_position; ld.b r0, (r2); ld.b r3, flag; // ! lsr r3, #1; // ! lsl r3, #2; // ! add r0, r3; // ! btst r0, #8; beq save; jsr rand; save: st.b (r2), r0; addq r1, #-1; bmi busy_loop; move r3, r1; // x (0..1f) lsr r3, #3; // byte addr in row (0..3) ld.b r2, #0xfc; // y mask and r2, r0; // y * 4 (0..fc) add r3, r2; // byte addr in screen ld.w r2, #0xa000; add r3, r2; // byte addr in memory ld.b r2, flag; // ! lsr.wt r0, r2;
busy_loop
サイクルの開始時に、3から現在の
flag
値を減算します。 2を1に、1を2に変更します。次に、各反復で「ドロップ」を下に移動する代わりに(
addq r0, #2; addq r0, #2;
)値
(flag >> 1) << 2
、t to
r0
を追加します.e。 点火半反復で4、消光で0。
最後に追加するのは、「ドロップ」自体から-4のオフセットでバイト単位で、点火ハーフイテレーションにもう1ビットを設定することです。
// and now apply test r2; bpl blank; bset r0, r2; st.b (r3), r0; // ! addq r3, #-2; // ! addq r3, #-2; // ! btst r3, #8; // ! bne next_col; // ! ld.b r0, (r3); // ! bset r0, r2; // ! jmp apply; blank: bclr r0, r2; apply: st.b (r3), r0; jmp next_col;
btst r3, #8; bne next_col;
確認してください
btst r3, #8; bne next_col;
btst r3, #8; bne next_col;
「ディスプレイ」の上端を超えないようにし、アドレス0x9FFxに何かを書き込もうとしないようにします。
意図したとおりにドロップが点滅します
コード全体
reset: jmp start; nop; ext_int: reti; nop; nop; nop; div_zero: reti; nop; nop; nop; illegal: reti; nop; nop; nop; rand: push r1; ld.w r0, seed; ld.w r1, #33797; mulu; addq r2, #1; st.w seed, r2; pop r1; move r0, r2; add_position: ld.b r2, #position; add r2, r1; ret; start: ld.w r0, #0x2000; move sp, r0; // set random positions ld.b r1, #32; init_loop: jsr rand; st.b (r2), r0; addq r1, #-1; bne init_loop; busy_loop: ld.b r1, #3; ld.b r2, flag; sub r1, r2; st.b flag, r1; ld.b r1, #32; next_col: jsr add_position; ld.b r0, (r2); ld.b r3, flag; lsr r3, #1; lsl r3, #2; add r0, r3; btst r0, #8; beq save; jsr rand; save: st.b (r2), r0; addq r1, #-1; bmi busy_loop; move r3, r1; // x (0..1f) lsr r3, #3; // byte addr in row (0..3) ld.b r2, #0xfc; // y mask and r2, r0; // y * 4 (0..fc) add r3, r2; // byte addr in screen ld.w r2, #0xa000; add r3, r2; // byte addr in memory ld.b r2, flag; lsr.wt r0, r2; ld.b r2, #7; and r2, r1; // bit index in byte (0..7) lsl r2, #1; lsr r0, #2; roxr r2, #1; ld.b r0, (r3); // and now apply test r2; bpl blank; bset r0, r2; st.b (r3), r0; addq r3, #-2; addq r3, #-2; btst r3, #8; bne next_col; ld.b r0, (r3); bset r0, r2; jmp apply; blank: bclr r0, r2; apply: st.b (r3), r0; jmp next_col; seed: dw 1; flag: db 2; position:;
ここで、Megaprocessorで独自のプログラムを実行するには、作成者が自宅を訪れたことに同意する必要があります。 しかし、1か月後には、メガプロセッサーはケンブリッジコンピューター歴史センターに移動し、週5日一般に公開される予定であると述べました。
メガプログラミングに成功!