
夏のGeektimesには、
メガプロセッサ
に関する記事がありまし
た。これは
、重量が半トンで、ケンブリッジ近くの通常のタウンハウスのリビングルーム全体を占める個別のトランジスタとLEDのプロセッサです。 私は、このメガプロジェクトに地理的に近いことを利用して、そのために提示可能なものをプログラムすることにしました。たとえば、
以前のメガプロセッサ用の「Digital Rain」ソフトウェアを使用することです。
メガプロセッサコマンドシステム
については、開発者のサイトで説明されています 。
ほとんどの命令は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;
ここで最も興味深いのは
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日一般に公開される予定であると述べました。
メガプログラミングに成功!