福野泰介の一日一創 - create every day

マシン語が分かると楽しいC言語!
IchigoJamで楽しむC言語「c4ij」を最新API対応のバージョンアップ!

1セクター4KB、8セクターで32KBあるIchigoJamの内、2セクターを使って、最大7KBものマシン語プログラムを書き込んで、BASICから呼び出すことが可能です。

デモとして、WS2812B/WS2811用のドライバ呼び出しを使って色相環をぐるぐるまわすプログラムを作りました。

IchigoJam BASICで書いたHSV2RGB変換をC言語に移植!
割り算処理は、IchigoJam OSに含まれるものを使って、省メモリ化してます。

#include <std15.h> /* 100 @HSV2RGB:R=V:G=V:B=V:IF S=0 RTN 110 D=H/60%6:C=V-(59-H%60)*V/60*S/100:E=V-H%60*V/60*S/100:F=V*(100-S)/100 120 IF!DG=C:B=F 130 IFD=1R=E:B=F 140 IFD=2R=F:B=C 150 IFD=3R=F:G=E 160 IFD=4R=C:G=F 170 IFD=5G=F:B=E 180 RTN */ void hsv2rgb(int h, int s, int v, uint8_t* grb, uint64_t (*divfunc)()) { int r = v, g = v, b = v; if (s == 0) return; int d = divfunc((uint32_t)divfunc(h, 60), 6) >> 32; int hmod60 = divfunc(h, 60) >> 32; int c = v - (uint32_t)divfunc((59 - hmod60) * v * s, 6000); int e = v - (uint32_t)divfunc(hmod60 * v * s, 6000); int f = (uint32_t)divfunc(v * (100 - s), 100); if (d == 0) { g = c; b = f; } else if (d == 1) { r = e; b = f; } else if (d == 2) { r = f; b = c; } else if (d == 3) { r = f; g = e; } else if (d == 4) { r = c; g = f; } else { g = f; b = e; } grb[0] = g; grb[1] = r; grb[2] = b; } __attribute__ ((section(".main"))) int main(int param, int ram, int rom, uint64_t (*divfunc)()) { const int v = 50; const int n = 20; uint8_t* grb = (uint8_t*)(ram + 0x800); int h = 0; for (;;) { for (int i = 0; i < n; i++) { hsv2rgb(h + i * 5, 100, v, grb + i * 3, divfunc); } ws_led(3 * n, grb, GPIO_OUT1); if (inkey() == 27) break; h++; } return 0; }

新API、ws_ledを使って多数のWS2812Bを高速制御!最大輝度50(=v)で、20コのLEDを5度ずつまわす色相環。ESC(=27)が押されたらマシン語からBASICへ処理を戻します。64bitで返すことにして使うdivfuncは、上位32bit(=R1)があまり、下位32bit(=R0)が除数となります。

このコードで256byteをちょっと超える感じ。#700-#8FFまでを使った512byteまではプログラムに含められます(make poke)。それ以上はFILE1〜3の領域を使って、3KBまでのプログラムで書き込みます(make write)。 SAVEするのを諦め、まるっと残り7KB使ったプログラムづくりも可能です。(bas2bin機能をc4ijに統合しました)

IchigoJamで容量不足や、速度不足を感じて来たときの選択肢のひとつ!
(C言語はちょっと難しそうという方、Ruby版もあります)

「明和電機 x ギャル電」のイケてるアクセワークショップ!電子の力でキラキラさせよう!


夏休み特別企画「明和電機 超! 技能訓練所」開催! - 明和電機 - Maywa Denki明和電機 – Maywa Denki」 (オモチャは買って遊ぶより、自分で作ったほうがはるかに楽しい)

links
- 高専でなぜC言語を学ぶのか? IchigoJamマシン語生成プログラム c4ij で作る、C言語版かわくだり
- C言語で拡張するIchigoJam BASIC - 3KBマシン語をIchigoJamで動かすウラワザ
- c4ij - イチゴジャム レシピ
- IchigoJamで計算する色相環、色相・彩度・明度を光の三原色に / HSV2RGB in BASIC
- 実機転送実行まで1秒! Ruby on Jam でサクサク楽しい組み込みアジャイル開発

8/5は、日本最古級のパソコン、PC-8001の誕生日!


40周年を記念した、ちっちゃい版がお披露目!
BASICやマシン語で作られたゲームが16本、もちろん、自分で開発も可能!
細部まで再現し、BASICが動作する「PasocomMini PC-8001」 - PC Watch

今回はオリジナルのマイクロソフトのN-BASIC搭載!
33年前の私も、同マイクロソフト製のMSX-BASICで学びました!

35年後輩にあたる、IchigoJam BASICで自在に扱える国産パソコンIchigoJamは、現在バージョン1.4調整中。
こんなBASICのプログラムを入力すると・・・

10 A=RND(14)+1:T=RND(3)*20+20 15 FOR J=0 TO 3 20 FOR I=0 TO A-1 25 LET[I*3],RND(16),RND(16),RND(16) 26 NEXT 30 WS.LED A,256/A+1 40 WAIT T 42 NEXT 50 GOTO 10

オシャレな模様をランダム模様が表示されます!

こちら先週末の永平寺イベントで活躍した、WS2812Bを16x16、256コ並べた、大型ディスプレイ。

WS.LEDコマンドは配列にRGBやGRBの順に入った値を使って最大34コのWS2812BなどのLEDを制御できる1.4新設コマンド。 台二パラメータでリピート設定すれば、何千個ものフルカラーLEDも制御できます!

16x16、256コを個別に制御するには?
ちょっと背伸びしてマシン語を使いましょう。10進数の数を打ち込むだけで使えます。

10 POKE#700,16,181,6,72,9,34,18,2,17,68,3,34,18,2,1,35,238,36,36,136,160,71,16,189,112,71,0,0,4,0,1,80 50 CLS 60 S="プログラミングフェス2019 in エイヘイジ " 100 FOR K=0 TO LEN(S)-1 101 GSB200 102 C=ASC(S+K):P=1:GSB210 103 C=ASC(S+K+1):P=0:GSB210 104 U=USR(#700) 105 NEXT 110 GOTO 100 200 FORI=0TO5:[I]=RND(5)+I/3*15:NEXT:RTN 210 FOR I=0 TO 7 220 FOR J=0 TO 7 230 N=PEEK(C*8+7-I)>>J&1*3 235 A=#900+I*6+J*3*16+P*8*3*16 240 POKE A,[N],[N+1],[N+2],[N],[N+1],[N+2] 250 NEXT 260 NEXT 270 RTN

こちらが1.4で対応するAPIを呼び出す、マシン語部分のプログラム、asm15でアセンブルすると、10行の数の並び、マシン語が生成されます。

PUSH {R4,LR} R0=[@OUT1]L R2=9 ' = data address #900 R2=R2<<8 R1+=R2 R2=3 R2=R2<<8 ' = send 256*3 byte R3=1 ' = step 1 R4=#EE ' API to use WS2812B R4=[R4]W GOSUB R4 POP {R4,PC} RET @OUT1 DATA L #50010004

ただ、32byteもあって、APIを使っている割にはちょっと長い。

使うレジスタを破壊してOKなR3までにできればGOTOで呼び出しでき、PUSH/POPが不要になりそう。短縮するために数とリピートの指定を16bitシフトして1つのレジスタで渡すようにして、OUT1を表すアドレスの設定に6〜8byteも使うので、0指定ならOUT1ってことにしておくと、次のように14byteまで縮まる!

R0+=R1 R1=3 R1=R1<<(8+16) ' 256*3 R2=0 R3=#EE R3=[R3]W GOTO R3

APIづくりは、利用者想いが大事!
まずは自分で使うことだと、身にしみました。

近々バージョンアップします。
BASICで配列を使ったLED遊びはできるので、ベータ版お試しください
IchigoJam 1.4β7


APIに対応し、オールマシン語でかいた「かわくだり」、APIをC言語環境から呼び出せる環境を整えたC言語用ヘッダファイル std15.h を作って、C言語版も作ってみました。 PCとUSBシリアルケーブルを手に入れれば、BASICではできないスゴイモノが簡単に作れます!(マシン語をハンドアセンブルすれば、PCがなくても同等以上のものは作れます)

#include <std15.h> __attribute__ ((section(".main"))) int main(int param, int ram, int rom, int (*divfunc)()) { cls(); int x = 15; for (;;) { locate(x, 5); putc('O'); locate(rnd(32), 23); putc('*'); putc(10); wait(3); int c = inkey(); if (c == LEFT) x--; if (c == RIGHT) x++; if (scr(x, 5)) break; } return x; }

IchigoJam BASIC に慣れた人なら読めますね!(IchigoJam BASIC版、Ruby版
#include というのは、コンパイラ向けの命令で、その後ろに書かれた std15.h というファイル名のファイルの中身をまるっと取り込んでくれます。 このファイルの中に main cls locate などが何なのかが書いてあります。(通常のC言語では stdio.h というファイル名をincludeしますね)

std15.h の中身はこのような感じ。IDを覚えていなくても、分かりやすい名前で呼び出せて、コンパイルする際、間違った使い方は警告してくれるなど、いたれりつくせり。

typedef unsigned int uint32_t; typedef unsigned short uint16_t; typedef uint32_t (*IJFUNC_P1R)(uint32_t n); #define rnd(n) ((IJFUNC_P1R)(void*)(uint32_t)*(uint16_t*)0xC0)((n))

生成されたコードサイズは、108byte、オールマシン語で作ったものが 90byte だったので20%ほどコード量が増えてしまってますが、十分に小さいのでOKですね。 使える容量は、#700-#7FFまでで256byte、変数を使わないことにすれば512byteまで。プログラム領域#C00以降にPOKEコマンドで書き込むように変更すると、1KB近くまで使えます。

ダウンロードはこちら(IchigoJam 1.3.2b12 以降用)
IchigoJam/c4ij: C language for IchigoJam」 - src on GitHub

マシン語を読みたいときは、 make dasm とすると、マシン語コードを表示します。コンパイラがどのようなマシン語にするか辿って、どこに無駄があるか読んでみるもまたおもしろいですよ。

某現役高専生による高専生のためのC言語連載が始まりました!
C言語 - Part.0:C言語とは - 某高専生の某高専生による高専生のための...
ただ、C言語、初めてプログラミングとしていきなり学ぶのはオススメしません。

まずは、IchigoJam BASIC、JavaScript、Python、Ruby、Unity、何でもいいので、サクッと簡単に楽しいゲームが作れることを体験しましょう。 便利なツールを使って、実現したいことを最短で実現し、もっとやってみたい!と興味を持つことが何より大事です。

コンピューターの気持ちが気になったら、コンピューターの構造(アーキテクチャ)、マシン語、C言語、現代言語の順に学ぶのがオススメです。

マシン語を触ってコンピューターの真の力を体感し、ちょっとしたプログラムを作るのに苦労してもらった上で、C言語のコンパイラに触れると、もう一度コンピューターってすごい!となれます。 もっと楽しく、もっと速く作りたい!という思いが、Python、Ruby、JavaScript、Go、Javaなどの言語を産みました。

なぜC言語を学ぶのか、それは、それらのモダン言語と言われている言語自体を作った言語がC言語だからです。

すごいゲームを作りたい!が、マシン語を学ぶ原点でした。
まず体験して、興味を持った状態での座学が効率的!


オールマシン語の「かわくだりゲーム」
アドレスを書き換えて、WAITの値を0にすると、超高速!
当然、即死するので、当たり判定を無効化して無敵モード!
マシン語なので、ESCは効きません。電源を入れ直して元通り!

OSとしてのIchigoJamのテスト版を経て、拡張用にAPIに対応した、IchigoJam 1.3.2b12 公開です。
ichigojam-1.3b12.zip
機能追加:SWITCHの第二パラメータで液晶の濃さを指定可能(0:デフォルト=14、1:最も薄い、63:最も濃い)

マシン語レベルで眺めてみれば、まだまだ見つかる最適化の余地。
また数百バイト単位で空いたので、もうちょっと拡張可能になった IchigoJam!
引き続き、ご要望、お待ちしています。

APIテーブル(β)

オールマシン語かわくだりの作り方。

APIはIDで2byteの値をメモリから読み出し、そのアドレスへGOSUBする。例えば、画面クリア(cls)を呼び出すには、このようにする

PUSH {R7,LR} 'cls R7=#CC R7=[R7]W GOSUB R7 POP {R7,PC}

ハンドアセンブルするか、asm15などでアセンブルして、BASICで実行してみましょう。

POKE#700,128,181,204,39,63,136,184,71,128,189 U=USR(#700,0)

画面が消えました!

locateとputcを使って、ネコを表示するには

PUSH {R4-R7,LR} R4=15 ' X 'locate X,5 R0=R4 R1=5 R7=#CE R7=[R7]W GOSUB R7 'putc neko R0=#EC R6=#C4 R6=[R6]W GOSUB R6 POP {R4-R7,PC}

で、OK。この調子でひとつひとつ作っていく感じは、BASICでもどんな言語でも一緒です。 マシン語はひとつひとつの命令の粒度が小さいので、プログラムが長くなり勝ちです。 コンパイラが作りたくなる気持ちがわかりますね。(コンパイラはじめのいっぽ

できあがった、かわくだりゲームがこちら! (90byte)

PUSH {R4-R7,LR} 'cls R7=#CC R7=[R7]W GOSUB R7 R4=15 ' X @LOOP 'locate X,5 R0=R4 R1=5 R7=#CE R7=[R7]W GOSUB R7 'putc neko R0=#EC R6=#C4 R6=[R6]W GOSUB R6 'rnd(32) R0=32 R5=#C0 R5=[R5]W GOSUB R5 'locate R0,23 R1=23 GOSUB R7 'ptuc *, enter R0=#2A GOSUB R6 R0=10 GOSUB R6 'wait 3 R0=3 R7=#D6 R7=[R7]W GOSUB R7 'inkey() R5=#CA R5=[R5]W GOSUB R5 R0-28 IF !0 GOTO 2 R4-=1 R0-29 IF !0 GOTO 2 R4+=1 'scr(X,5) R0=R4 R1=5 R5=#D0 R5=[R5]W GOSUB R5 R0-0 IF 0 GOTO @LOOP POP {R4-R7,PC}

asm15でアセンブルして、動かしてみましょう。

10 POKE#700,240,181,204,39,63,136,184,71,15,36,32,70,5,33,206,39,63,136,184,71,236,32,196,38,54,136,176,71,32,32,192,37,45,136,168,71,23,33,184,71,42,32,176,71,10,32,176,71,3,32,214,39,63,136,184,71 20 POKE#738,202,37,45,136,168,71,28,40,0,209,1,60,29,40,0,209,1,52,32,70,5,33,208,37,45,136,168,71,0,40,216,208,240,189 30 U=USR(#700,0) RUN

オールマシン語です!

WAITの値を0にして、超高速!

POKE#730,0:U=USR(#700,0)

SCR呼び出しを R0=0 (0,32) に上書きして、当たり判定を無効化して、無敵モード。

POKE#752,0,32:U=USR(#700,0)

こちら、PC/Macで動く、C言語版かわくだりゲームも登場!

IchigoJam用のC言語環境も作ってみるのもいいかも!

links
- はじめてのマシン語 - IchigoJamではじめるArmマシン語その1

高専の情報系の学科にはOSを学ぶ授業があります。自分で使って作って遊んでみるのが近道です。

IchigoJamへの要望に応えるため、一部はIchigoJam OSの外での実装を検討中。 その手段として、マシン語やmrubyなどからの呼び出しに便利なAPI提供を行う予定にしています。公開時はAPIテーブルとして固定のアドレスに置くことになりますが、先行してAPI直接呼び出しする実験方法を紹介します。
* IchigoJam 1.3.2b11専用なのでご注意ください(他のファームウェアだと停止します)


(IchigoJam as an OS: rnd API example)

ゲームでは欠かせないランダム(rnd)はこんな風に呼び出せます。

[0]=#4700:?USR(#800,#11B9) 8423

アドレス #11B8 に、rnd関数が入っていて、+1 した #11B9 でThumbマシン語として呼び出します。返り値はR0に格納されるので、マシン語はシンプルにGOTOするだけ。

GOTO R0

inkeyは#A15、clsは#196D、wait1は#219です。

パラメーターを渡すAPI、画面表示のputcは、こんな風に使います。(UARTのみのputcは#A51)

LET[0],#4900,#4708,#18BD,0:U=USR(#800,ASC("A"))

マシン語は、このように呼び出し先アドレスをR1にセットしています

R1=#18 R1=R1<<8 R1+=#BD GOTO R1

パラメータを一つ受け取るAPIとして、数を出力するputnum(#18E1)もおもしろいです。

LET[0],#4900,#4708,#18E1,0:U=USR(#800,12345) 12345

RNDを使った応用例として画面をランダムに埋め続けるプログラムを、ステップバイステップで解説します。

'R3=#900 R3=9 R3=R3<<8 R3=R3+R1 [R3]=R0 RET

asm15でアセンブルするか、マシン語表を使ってハンドアセンブル

POKE#700,9,35,27,2,91,24,24,112,112,71 ?USR(#700,65)

Aが画面左上(#900)に表示されます。USRの第二パラメータに表示したいキャラクターコード、レジスタR0として使えます。

rndを使って、ランダムなキャラを出します。

PUSH {LR,R4} 'R4=#900 R4=9 R4=R4<<8 R4=R4+R1 'R2=#11B9 R2=#11 R2=R2<<8 R2+=#B9 GOSUB R2 'RND [R4]=R0 POP {PC,R4}

関数内で更に関数を呼び出す場合、戻り先LRをPUSHで保存しておき、POPで戻しましょう。

POKE#700,16,181,9,36,36,2,100,24,17,34,18,2,185,50,144,71,32,112,16,189 ?USR(#700,0)

ランダムな場所に出します。

PUSH {LR,R4-R6} 'R6=#11B9 (RND) R6=#11 R6=R6<<8 R6+=#B9 'R4=#900 R4=9 R4=R4<<8 R4=R4+R1 GOSUB R6 'RND R1=#FF R0&=R1 R5=R4+R0 GOSUB R6 'RND [R5]=R0 POP {PC,R4-R6}

R3の割り算を使って、%(32*24)を実現して画面全体ランダム表示。
捨て身の無限ループなので、電源を切るまで停止することはできません。戻ってこないのでPUSHもなし。

'R6=#11B9 (RND) R6=#11 R6=R6<<8 R6+=#B9 'R7=32*24 R0=32 R7=24 R7*=R0 'R12=R3 (UDIV) R12=R3 'R4=#900 R4=9 R4=R4<<8 R4=R4+R1 @LOOP GOSUB R6 'RND R1=R7 GOSUB R12 'UDIV R0=R1 R5=R4+R0 GOSUB R6 'RND [R5]=R0 GOTO @LOOP


POKE#700,17,38,54,2,185,54,32,32,24,39,71,67,156,70,9,36,36,2,100,24,176,71,57,70,224,71,8,70,37,24,176,71,40,112,247,231 ?USR(#700,0)

正式実装をお楽しみに!

see also
連載、IchigoJamではじめる、Armマシン語入門

アルゴリズムの工夫や、マシン語化で速くなることを体験しました。 今回、それぞれ実測してみましょう。

まずはシンプルに引き算を続けるBASIC版から、CLTとTICK()を使って計測します。コンピューターくんへの課題は「10000/7」とします。

10 N=10000 20 M=7 25 CLT 30 A=0 40 N=N-M:IF N>=0 A=A+1:CONT 50 N=N+M 55 ?TICK() 60 ?A;"...";N RUN 370 1428...4

BASICでは369、TICK()は1秒間に60進むので、6.1秒です。(IchigoJam上で10倍して60で割ると小数1位まで計算できます。また、IchigoJamのバージョンによって数は変わります)

?3690/60 61

BASIC筆算版はどのくらい速いでしょう?

10 N=10000 20 M=7 25 CLT 30 A=0 40 FOR I=10 TO 0 STEP -1 50 L=M<<I 60 IF N>=L N=N-L:A=A+1<<I 70 NEXT 75 ?TICK() 80 ?A;"...";N RUN 5 1428...4

なんと、0.08秒(5/60秒)。73倍も速くなってます。アルゴリズムって大事ですね。

続いて、マシン語の単純ループ版。

POKE#700,7,33,2,70,0,32,1,48,82,26,252,218,2,56,81,24,112,71 OK CLT:U=USR(#700,10000):?TICK() 0

0、つまり1/60秒未満。この時点で単純ループ版でもBASIC筆算版を上回る速度がでていることがわかります。 ただ、0ではどのくらい速いかわからないので、ループを使います。

CLT:FORI=1TO1000:U=USR(#700,10000):NEXT:?TICK() 283

1000回で283、4.7秒でした。・・・が、待ってください。BASICの処理で時間がかかっているかもしれません。そこで何もせず帰ってくるだけのマシン語(=RET #4770)でも測ります。

[0]=#4770:CLT:FORI=1TO1000:U=USR(#800,10000):NEXT:?TICK() 264 ?283-264 19

やはり、ほとんどがBASICの処理時間でした。差分は19、0.31秒/1000回。1回あたり310マイクロ秒(=0.31ミリ秒)、BASIC版の2万倍も速いことがわかります。

次に、マシン語筆算版を計測してみます。

POKE#700,7,33,16,181,2,70,0,32,1,35,27,4,12,70,92,67,162,66,1,219,18,27,24,68,91,8,247,209,17,70,16,189 OK CLT:FORI=1TO1000:U=USR(#700,10000):NEXT:?TICK() 264 ?264-264 0

速すぎて、差分が0になってしまいました。ループ回数を10倍の10000回にして比較します。

[0]=#4770:CLT:FORI=1TO10000:U=USR(#800,10000):NEXT:?TICK() 2643 CLT:FORI=1TO10000:U=USR(#700,10000):NEXT:?TICK() 2648 ?2648-2643 5 ?5*1000/60 83

10000回で0.083秒。つまり1回あたり8.3マイクロ秒。ループ版から37倍、速くなりました。
BASIC筆算版の約1万倍速と、高速化具合は似たようなものですね。

まとめ
1. アルゴリズムによる速度差は環境を変えても変わらない(工夫の価値)
2. 遅いアルゴリズムでも言語や環境を変えるとなんとかなってしまう(力技)

いくらコンピューターが高速化しようとも、同じ環境上でいかにアルゴリズムの工夫で高速化できるかが、電気代やサーバー台数など、かかる費用に直結します。 また、同じ機能のアプリなら、サクサク快適に動く方がうれしいですよね? これが流行る流行らないの決定的な差になったりします。アルゴリズム、大事。

アルゴリズムなど理論ばかり見ていてもどう使えるのかイメージが沸かずつまらないし、プログラミング言語の文法だけ学んでもアルゴリズムが弱いとせっかくのパワーが活かせません。

IchigoJam BASICのスピードは、アルゴリズム変更が人間にとってリアルにコンピューターを体感できる速度域。入門時の学習がむしろ、手軽で速いはず。

アルゴリズムやコンピューターを学ぶコツをさえ身に着けたら、数億倍速い環境が待ってます。
世界中にある様々な課題、新しい問題解決手法(アルゴリズム)を創りましょう!
Enjoy to create own Algorithm with Computers!

- 連載、IchigoJamではじめる、Armマシン語入門
1. はじめてのマシン語
2. ハンドアセンブルで超速計算!
3. マシン語メモリアクセスで画面超速表示!
4. マシン語でLEDを光らせよう!
5. 楽しさ広がるマルチバイトメモリアクセスとスタック
6. マシン語使いこなしTIPS
7. カジュアルに使うインラインマシン語
8. アセンブラを使って楽しよう
9. マシン語で高速SPI
10. マシン語を制するもの時間を制す
11. 画面をイチゴで埋め尽くす12の方法
12. レジスタ不足に上位レジスタとスタック操作
13. コンパイラはじめのいっぽ、EVAL実現法とマシン語生成
14. サイズを取るかスピードを取るか、割り算のアルゴリズムとマシン語実装
15. マシン語化で1万倍速!? セットで学ぶアルゴリズムとコンピューター

IchigoJamのCPU、LPC1114 Arm Cortex-M0のマシン語には、割り算命令がありません。
参考:マシン語表Cortex-M3以降にはあります SDIV/UDIV)

無いものは作ればOK!(IchigoJamでのR3はここでは封印

N / M = A ... R(A:商、R:あまり、ただしNもMも正の整数とする)という割り算をする場合、割り算記号を使わずに作成できればOKです。何か、得意な言語で作ってみましょう。

例えば、IchigoJam BASICなら

10 INPUT N 20 INPUT M 30 A=0 40 N=N-M:IF N>=0 A=A+1:CONT 50 N=N+M 60 ?A;"...";N RUN ?300 ?7 42...6

割り算記号を使わない割り算、できました!

これをasm15マシン語にします(USRでは引数が1つなので、割る数R1に7と設定)

R1=7 R2=R0 R0=0 @LOOP R0+=1 R2=R2-R1 IF GE GOTO @LOOP R0-=1 R1=R2+R1 RET

アセンブルしたバイナリ(18byte、R1設定を除くと16byte)を使って、動かしてみましょう

POKE#700,7,33,2,70,0,32,1,48,82,26,252,218,1,56,81,24,112,71 ?USR(#700,300) 42

これで完成! ・・・としてもいいのですが、割られる数が大きくて、割る数が小さい時、何万回もループすることになって遅そうです。

例えば、30000/1を計算する場合、1ループ5cycle3万回で15万cycle。CPUの周波数48MHzの逆数、21nsecが1cycleにかかる時間なので、63万nsec = 630usec = 0.63ミリ秒かかります。 足し算引き算かけ算の15万倍も遅いとなると、高速処理が命のゲームで使うには厳しいこともありそうです。

そこで登場、アルゴリズム(問題解決手法)!

ひとまず簡単に思いつくところで、2進数を使った筆算アルゴリズムで高速化してみます。 割り算を手で計算するときに筆算を使うように、大きな桁からかけ算して引いてを繰り返して答を求めます。 幸い、2進数の筆算は0か1かしかないので、とってもシンプル。何か書きやすいプログラミング言語でアルゴリズムを確かめます。

10 INPUT N 20 INPUT M 30 A=0 40 FOR I=10 TO 0 STEP -1 50 L=M<<I 60 IF N>=L N=N-L:A=A+1<<I 70 NEXT 80 ?A;"...";N RUN ?300 ?7 42...6

(変数が16bit用に、IchigoJam BASIC桁溢れ防止のため10bitシフトからスタート)
前の手法に比べて速くなったことが体感できましたね?では、こちらをマシン語にします。

R1=7 PUSH {LR,R4} R2=R0 R0=0 R3=16 @LOOP R4=R1 R4<<=R3 R2-R4 IF LT GOTO @SKIP R2=R2-R4 R4=1 R4<<=R3 R0+=R4 @SKIP R3-=1 IF !MI GOTO @LOOP R1=R2 POP {PC,R4}

ループ回数が16回にまで劇的に減り、先の最悪の場合と比較し、約1000倍高速です!

ただし、その代償として、マシン語の量が32byteと16byte増えてました。これが多いか少ないかは状況次第です。 また、割る数と割られる数が近い場合は毎回150cycleかかる今回の手法と違って、単純ループの実装の方が速かったりします。 状況によって使い分けましょう。

OSとしてのIchigoJamとしては、汎用的に使われることを想定して、あまりに遅い除算は残念なので、第二案でいきたいです。 ただ容量は極力小さくしたいので、かけ算が1cycleで動くことを利用して、もうひと工夫縮めてみます。

R1=7 PUSH {LR,R4} R2=R0 R0=0 R3=1 R3=R3<<16 @LOOP R4=R1 R4*=R3 R2-R4 IF LT GOTO @SKIP R2=R2-R4 R0+=R3 @SKIP R3=R3>>1 IF !0 GOTO @LOOP R1=R2 POP {PC,R4}

2byte縮み、引き算する場合のcycleが2減りました!
たった2byteと笑うかもしれませんが、その2byteを削るのに結構苦労したりするんです。

POKE#700,7,33,16,181,2,70,0,32,1,35,27,4,12,70,92,67,162,66,1,219,18,27,24,68,91,8,247,209,17,70,16,189 ?USR(#700,300) 42

実は、まだ落とし穴があります。マイナスの値で割り算の計算をさせてみてください。
おかしくなりますね。また、割る数に0を指定した時にどんな動きをしてほしいですか?
すべては、あなたの思い次第!

より高速で効率良いアルゴリズムを探してみるのも楽しそうです。Wikipediaの「除算」を見てみると、懐かしの割り算バグありPentiumの話がでてました。 試行錯誤の積み重ねで、今のコンピューター社会ができていることがわかります。

- 連載、IchigoJamではじめる、Armマシン語入門
1. はじめてのマシン語
2. ハンドアセンブルで超速計算!
3. マシン語メモリアクセスで画面超速表示!
4. マシン語でLEDを光らせよう!
5. 楽しさ広がるマルチバイトメモリアクセスとスタック
6. マシン語使いこなしTIPS
7. カジュアルに使うインラインマシン語
8. アセンブラを使って楽しよう
9. マシン語で高速SPI
10. マシン語を制するもの時間を制す
11. 画面をイチゴで埋め尽くす12の方法
12. レジスタ不足に上位レジスタとスタック操作
13. コンパイラはじめのいっぽ、EVAL実現法とマシン語生成
14. サイズを取るかスピードを取るか、割り算のアルゴリズムとマシン語実装
15. マシン語化で1万倍速!? セットで学ぶアルゴリズムとコンピューター

プログラミングしていると自分言語が作りたくなる時期が来ます。
IchigoJam BASICでコンパイラを作りたくなった時、書かれた数式を評価できたら便利です。

1 S="R0=1<<4+5"

容量の都合と優先度の関係で、数式を評価する関数 EVAL に対応していない IchigoJam BASIC ですが、実は数式を解釈するINPUTコマンドとキーバッファ操作を組み合わせれば実現できます。

まずはINPUTコマンドの実験

INPUT N:?N ?1<<4+5 261

計算してくれました!変数も関数も実は使えます。

次に予め数式をかいてみましょう

CLS:?" 3*5":LC0,0:INPUT N:?N 15

あとはエンターキーを押せば、Nに数式を評価した結果が入ります。
特殊キー、Home(18 = #12)を使って、行頭へカーソルを移動させておくこともできます。

?" 3*5";CHR$(18);:INPUT N:?N 15

あとは、メモリ内、キーバッファを直接操作して、入力したことにすれば自動化できます。

?"1<<5";CHR$(18);:POKE#1003,1,10:INPUT"",N:?N 32

メモリマップを見ると、#1003がキーバッファの数、その後がキーバッファとなっています。 POKEコマンドで、キーバッファの数を1、キー入力の値としてエンターを表す10をいれておくと、INPUTを呼び出した瞬間、帰ってきます。 (INPUTあとに空文字列指定で、?非表示に)

これを活用して、R0レジスタに数をいれるだけのコンパイラを作ってみます。

10 S="R0=1<<4+5" 20 A=#700 30 C="R0=":GSB@SCMP 40 IF F ?STR$(S+3);:GSB@EVAL:POKE A,N,32:A=A+2 50 POKE A,#70,#47:?USR(#700,0):END 100 @SCMP:F=1:FORI=0TOLEN(C)-1:F=F*(ASC(C+I)=ASC(S+I)):NEXT:RTN 110 @EVAL:?CHR$(18);:POKE#1003,1,10:INPUT"",N:RTN

40行、R0=n は、マシン語表を見ると、32(=1<<5)とレジスタ番号を足したものに、値と並べることがわかります。 Arm Cortex-M0は、下位が先に来る、リトルエンディアンなので、N,32の順番にメモリに書き込んでコンパイル完了!Armマシン語を生成されました。

ちょっと実験

10 S="R0=1<<10+12" RUN 12 OK ?N 1036

おや、Nの値はちゃんと計算されているのに、コンパイルしたプログラムはおかしいですね。
マシン語表を見ると、u8、つまり8bitまでしか対応していないわけです。

16bitまで対応させるに

R0=N>>8 R0=R0<<8 R0+=N&#FF

と、コンピューターくんに分かるように噛み砕いてあげましょう。

マシン語表を見ながらメモリに書くマシン語にします。(&#FFはなくても一緒なので省略)

N>>8,32 0,2 N,48

これをコンパイラに組み込んで、できあがり!

40 IF F ?STR$(S+3);:GSB@EVAL:POKE A,N>>8,32,0,2,N,48:A=A+6 RUN 1<<10+12 1036 OK

こんな調子で、自分だけのアセンブラ、自分だけのコンパイラを作っていきます。
Enjoy create an own computer language!

Maker Faire Bay Area では、IchigoJamで動くFORTHも人気だったとか!
モノ作りの祭典 MakerFaire は出会いの場!共に創ろう令和時代、中経連「中部圏イノベーション促進プログラム」にぜひお越しください!
キーバッファとIoTを使って、遠隔から任意コマンドを動かす技!
BASICコマンドをLTE送信!PCやスマホからIchiogoSodaを遠隔制御 | ボクにもわかる電子工作のブログ

- 連載、IchigoJamではじめる、Armマシン語入門
1. はじめてのマシン語
2. ハンドアセンブルで超速計算!
3. マシン語メモリアクセスで画面超速表示!
4. マシン語でLEDを光らせよう!
5. 楽しさ広がるマルチバイトメモリアクセスとスタック
6. マシン語使いこなしTIPS
7. カジュアルに使うインラインマシン語
8. アセンブラを使って楽しよう
9. マシン語で高速SPI
10. マシン語を制するもの時間を制す
11. 画面をイチゴで埋め尽くす12の方法
12. レジスタ不足に上位レジスタとスタック操作
13. コンパイラはじめのいっぽ、EVAL実現法とマシン語生成
14. サイズを取るかスピードを取るか、割り算のアルゴリズムとマシン語実装
15. マシン語化で1万倍速!? セットで学ぶアルゴリズムとコンピューター

IchigoJam 1.3で容量を使い切ってしまって不具合修正もできず困っていた中、C言語で書く例外処理を学んだ結果、容量の大幅圧縮に成功。なんと、864byte空きました。


複雑な計算式で計算用のメモリが溢れた際「Complex expression」の代わりに「Segmentation Fault」が発生します。たまに、当たりどころが悪く(?)固まってしまうことがあるようなので、要調査。割込中の割込じゃないかと予想。

?((((15)))) Segmentation Fault

マシン語でROM領域であるアドレス0へ無理やり書き込んでみます。 [R0]=R0; RET (asm15)

10 POKE #700,0,112,112,71 20 ?USR(#700,0) RUN Segmentation Fault

落ちません!マシン語の敷居がちょっと低くなりました。
はじめてのマシン語 - IchigoJamではじめるArmマシン語その1

LPC1114の4KBのRAMの使い方が変わって、スタック、仮想RAM、システム用データ、IAP用領域の順となっています。詳細は、以前のバージョンのメモリマップも合わせてご参照ください。
IchigoJam BASIC RAM 4kbyte のつかいかた(メモリマップ)


先日、IchigoJamでプログラミングを紹介に伺った、ネパールの学校「SHREE MADAN-AASHIRIT ADARSHA BASIC SCHOOL」より、ステキな証明書が届きました! ありがとうございます!
Let's get smiles of kids! Programming for students of Tribhuvan University in Nepal
世界中のこども達向けに、もう一弾、更なる改善が図れそうです!

IchigoJam 1.3 beta 7 のダウンロードはこちらからどうぞ。
ichigojam-1.3b07.zip
問題報告やご要望、ぜひお送りください! → IchigoJam-FAN on Facebook / #IchigoJam on Twitter

24KBに、ギリギリまで詰め込んだOS、IchigoJam BASIC。でも、もうちょっとだけ改善したい!

ということで、気になっていた4KBのRAM、特にスタックの使い方にメスをいれてみました。

普段使うMacやWindowsやLinuxに組み込まれている、怪しいアプリに大事な情報をアクセスさせないための基本機能。 RAM(LPC1114FDH28では4KB)の範囲を超えると一般保護違反として、例外「HaldFault」が発生します。

プログラマーなら押さえておきたい、基本データ構造のひとつ、後入れ先出し(LIFO)のスタック。 IchigoJamのCPU、LPC1114のアーキテクチャ、Cortex-M0のスタックは、使うほどにアドレスが小さい方向に動いていきます。 現状のIchigoJamの実装では、スタックがどのくらい使われるかはBASICで記述する数式に依存するので、超えないように時々監視しながら、Comprex expression エラーを出しています。ただ、やり口、速度面、プログラム量の面で、ちょっと不格好な点、実は気にしていました。

そこで今回、スタックの領域を一般的なRAMの後ろではなく、先頭側におき、RAMの領域を超えた際に発生する例外をうまいこと使おうという作戦。


ということで、まずはミニマムに実験! 再帰呼び出しでスタックを使い切ったら、soft_entryからリスタート!成功です。 スタート直後、わざとROM領域に書き込もうとして、例外を発生させ、スタックポインタを初期化してスタートする雛形ができました。

#include "LPC1100.h" static inline void led(int n) { GPIO1MASKED[1 << 5] = n << 5; } static inline void wait(int n) { for (volatile int i = 0; i < 50000 * n; i++); } void job(int n) { for (;;) { led(1); wait(1); led(0); wait(1); job(n++); // 再帰でstack溢れ -> HardFault -> soft_entry } } void soft_entry() { // ここからリスタートする wait(10); job(0); } void main() { // Reset_Handlerから呼び出される IOCON_PIO1_5 = 0x000000d0; GPIO1DIR = 1 << 5; *(char*)0 = 0; // アクセス違反 -> HaldFault -> soft_entry } void HardFault_Hander() { // 一般保護違反 RAMの範囲を超えたアクセスなど uint32_t* sp; // = 0x10000000 + 1024 - 32 __asm( "mov r1, #1\n" "mov %0, r1\n" "lsl %0, %0, #18\n" "add %0, %0, r1\n" "lsl %0, %0, #10\n" "sub %0, %0, #32\n" "mov sp, %0\n" : "=r" (sp) ); *(sp + 6) = (uint32_t)(void*)soft_entry; // set PC *(sp + 7) = (1 << 24); // need to set PSR, Thumb mode }

スタックを使い切ってしまっているので、通常の割り込み時にセットされているはずのデータはありません。なので、スタックを初期化して、復帰用のデータを捏造しています。 副次的に、Armマシン語でミスした際にも、復帰できる確率が上がって、よりプログラミングを楽しめるかも!? IchigoJamへの組み込み、がんばります。
はじめてのマシン語 - IchigoJamではじめるArmマシン語その1

子供も大人も楽しい学びの場、地域ICTクラブ!
入り口は広くやさしく、でも、とんでもなく深く広いIT/プログラミングの世界、じっくりみんなで楽しみましょう!


今年の福井こどもプログラミング協議会、ますます楽しくなりそうです!
Thanks: 蕎麦と旨酒と 亀蔵 - 福井県鯖江市

Tweet
クリエイティブ・コモンズ・ライセンス
この作品は「Creative Commons — CC BY 4.0」の下に提供されています。
CC BY 福野泰介 - Taisuke Fukuno / @taisukef / アイコン画像 / プロフィール画像