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

CPUの世界にもオープンデータ。オープンソースでオープンライセンス(BSDライセンス)なCPUアーキテクチャ「RISC-V(リスク・ファイブ)」の実機が続々登場! 1981年に発表されたRISC-Iからスタートして5世代目のRISC-V、Google / NVIDIA / NXP / Qualcomm など、数多くの企業をメンバー企業に支えられて躍進前夜。

Amazonが発表した自社開発CPU、Graviton2はArmベースでしたが、次世代がRISC-Vになっても不思議はありません。(現時点でAmazonはまだメンバーではないが、オープンに誰でも使えるため、使用するのにメンバーである必要もない)

そんな RISC-V というオープンなコンピューターの気持ちになってみましょう!
IchigoJamで試した、はじめてマシン語Arm編のように、はじめのいっぽはハンドアセンブル!
Specifications - RISC-V Foundation(RISC-V仕様書 P130より)

Instructionsとは、RISC-Vというコンピューターができること一覧、基本は32bit(4byte)、メモリに書かれた命令にしたがって、どんどん計算していきます。 レジスタは基本32コ、そのうち0番レジスタX0は0固定。実行位置を表すプログラムカウンタ(PC)は別となっています。

ひとまず、1+1をさせてみましょう。
レジスタ同士の足し算は ADD (上の図の一番下)

ADD rd,rs1,rs2

rd = rs1 + rs2 という足し算をします。
RISC-Vは、32コあるレジスタ(X0-X31)の内、x10,x11...を引数や、返り値に使うことになっています(x10,x11は別名a0,a1)

ADD x10,x10,x11

こちらをマシン語表を見て、二進法32桁の数値に変換します。(RISC-Vのマシン語の基本は32bit、16bitの圧縮版もあります)

0000000 01011 01010 000 01010 0110011         rs2 rs1 rd

これを16進数に表すと

-> #00b50533

この4byteをメモリに書き込めばOKです!(RISC-Vは、little endian なので、#33,#05,#b5,#00 の順)

ただ、これだけではプログラムの終わりがわからずどんどんよくわからない計算が進んでしまうので、戻す(RETURN)命令を加えます。 RISC-Vでは、戻り先がレジスタ1番に入っているので、そちらへのジャンプ(GOTO)すればOK。 命令表の中から Brunch の JALR x1 を上記同様に16進数に変換します。(x1は別名ra)

JALR x1 (JALR rd,rs1) 0b00000000000 000001 000 00000 1100111         rs1 rd -> #00008067

できました!

実機で動かしてみたいところですが、今回は誰でも使えるエミュレーターを使います。

WebAssemblyにも対応し、ブラウザ上でRISC-V版Linux(JSLinux)まで動かしてしまうTinyEMUをベースにしたコンパクトなエミュレーターrv32emuを使います。

いろいろ組み込みに便利なようにコア部分を分離するように改造した rv32emu を作りました。
emu-rv32i.h をインクルードし、下記のように使います。

#include "emu-rv32i.h" #include <stdio.h> int main(int argc, char** argv) { uint32_t start = 0; ram_start = 0; uint32_t end = 0xfffffffe; *(uint*)(ram + start + 0) = 0x00b50533; // ADD x10,x10,x11 *(uint*)(ram + start + 4) = 0x00008067; // JALR x1 pc = start; reg[2] = ram_start + RAM_SIZE; // sp - stack pointer reg[1] = end; // ra - return adderss reg[10] = 1; // a0 reg[11] = 1; // a1 while (machine_running) { next_pc = pc + 4; insn = get_insn32(pc); printf("[%08x]=%08x\n", pc, insn); execute_instruction(); pc = next_pc; if (pc == end) break; } printf("x10 a0: %08x\n", reg[10]); return 0; }

結果はこちら

x10 a0: 00000002

見事、x10に 1+1 の結果、2が入っていますね!
reg[10]やreg[11]の初期値を変えたり、他の命令をハンドアセンブルしてみたりと、いろいろ試してみましょう。

MacでRISC-Vの開発環境を入れるなら、Homebrewを使うと楽ですよ!

$ brew tap riscv/riscv $ brew install riscv-tools

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

結論、できる!
ただし、コンピューター自体の仕様であるマシン語へ都度翻訳する、インタプリタ型の IchigoJam BASIC では無理。

真の力を引き出す、マシン語と、プログラミング言語の世界をのぞいてみよう! for 福井高専電子情報工学科1年生。


IchigoJamのハーフキット、はんだづけからスタート!


つないで、LED1、光った!完成! (90分1コマちょっとでほぼ全員完成、はんだごての準備などをしておけば短縮可能、失敗した人用完成版もいくつかあるといい)


授業としてははじめてのプログラミング!


LEDが点滅するロボットの仕組み、かわくだりゲームによるアプリづくりの基本を小学生向けの3倍速で紹介!
IchigoJamは、本当に1秒に5000万回計算できるのか!? 〜IchigoJam マシン語入門〜(keynote/PDF)」
はんだづけから、はじめのいっぽ、ゲームづくりまでの資料はこちら!アレンジ自在のオープンデータ。
一度やったら大丈夫、きっと先生として小学生に教えられますよ!


IchigoJamは、本当に1秒に5000万回計算できるのか!? 〜IchigoJam マシン語入門〜(PDF)」
今回初公開、マシン語入門!かわくだりゲームまでやった人向け、小学生でもきっとOK!


100円のコンピューターがなんと1秒間に5000万回!・・・でも、本当に?


実際測ってみると、VIDEO0で表示を消してもせいぜい1秒に1000回・・・、遅い!
その理由は、IchigoJam BASICがインタプリタ型のプログラミング言語だから。


マシン語ではなそう!


IchigoJamのCPU、NXP LPC1114の説明書と、Armの説明書をまとめてできた、マシン語表


足し算は、Rd += u8 を使う!


2進数、10進数、16進数の変換は IchigoJam BASICのコマンドでも簡単!


マシン語をメモリに書き込んで、USRを使って呼び出し!(呼び出し元にかえる #4770 がないと、どうなっちゃう!?)


マシン語で足し算のループを作ろう!


1000回でも1万回でもCLTとTICK()では1/60秒未満で測定不能なので、ループ回数を100倍に!


ビデオ表示を消して、1ループ1000万回になった!プログラムとマシン語表のサイクルを見てみると、確かに1秒間に5000万回計算できてることが判明!


実は、CPUだけだと、最新のCPUでも、1つだけならたかだか40倍くらいしか変わらない。
3D計算やAIなど、特定の計算に特化したハードウェア(GPU)の活用が、圧倒的計算力を活かす鍵!


圧倒的パワーが魅力なコンピューター。いろいろたのしいものつくっちゃおう!


好評だったNT鯖江2019、来年のNT鯖江2020の開催は、2020/10/3-4が有力!
見に来るだけでももちろん歓迎、でも、せっかくなら、なにかつくって出展しよう!

links
- はじめてのマシン語 - IchigoJamではじめるArmマシン語その1
- 福井で初開催!なにかつくろう、NT鯖江2019! ダイジェスト動画と交互連打で目指せ世界記録ゲーム!

IoT人流センサーでも活躍、超音波で距離を測るセンサー「HC-SR04」は、デジタル入力を使ってアナログ量が測れます。
0か1かしかないデジタルでどうやって、量がわかるのでしょう?
1になるまでの時間を見るのです。

改めて、このセンサーの使うプログラムを見てみましょう。
(測距センサーHC-SR04:IchigoJam、Trig:OUT1、Echo:IN1、GND:GND、Vcc:5V)

10 OUT1,1:OUT1,0:A=0 20 A=A+1:IF IN(1) CONT ELSE ?A 30 WAIT 5:GOTO 10

OUT1としてしまうと、他のOUTポートもまとめてコントロールしてしまうので、OUT1,1:OUT1,0と変更しました。
これがセンサーのTrigとなり、超音波センサーの2つの丸めの1つから超音波のパルス(短い時間の1)が発射されます。
この超音波が返ってくるまでの時間を変数Aを使って、IN1が0になるのを待つ。Aが距離。
以上が使い方の基本です。

OUT1が埋まっている時は、TrigをOUT2から6まで空いているところに挿して、例えば10行をOUT3,1:OUT3,0とかすればOK!
IN1が埋まっている時は、EchoをIN2から4までの空いているところに挿して、例えば20行をIN(2)とかすればOK!

(測距センサーHC-SR04:IchigoJam、Trig:OUT3、Echo:IN2、GND:GND、Vcc:5V)

10 OUT3,1:OUT3,0:A=0 20 A=A+1:IF IN(2) CONT ELSE ?A 30 WAIT 5:GOTO 10

IchigoJamが速いほどに、近いものが認識でき、細かく測れます。高速化した1.4β、お試しください!

いろんなセンサーやモーターを組み合わせて、誰も作ったこと無いものつくっちゃいましょう!


HC-SR04 x IchigoJam S

更に、精度が必要な場合や、ファームウェアの書き換えが面倒な場合、マシン語を使って時間計測を高速化します。

R0=0 R1=[@IN3]L @LOOP R2=[R1]L R0=R0+1 R2-0 IF !0 GOTO @LOOP R0=R0>>9 RET @IN3 DATA L #50000080

IN1のアドレスは #50001000 でした。
IN2はアナログ入力設定になっているので切り替えが必要なため、少しややこしい。
IN3のアドレスは #50000080
IN4のアドレスは #50000100
となります。

これをasm15を使ってアセンブルするか、マシン語表を見ながらハンドアセンブルしてできるマシン語がこちら。

10 POKE#700,0,32,3,73,10,104,64,28,0,42,251,209,64,10,112,71,128,0,0,80 20 OUT1,1:OUT1,0:?USR(#700,0) 30 WAIT 5:GOTO 20

このアドレスの求め方、IchigoJamのCPU、LPC1114の取説によると、PIO0のベースアドレスは #50000000
IN3は、PIO0_5の場所なので ?"#5000";HEX$(1<<(5+2),4) → #50000080
IN4は、PIO0_6の場所なので ?"#5000";HEX$(1<<(6+2),4) → #50000100
(参考、マシン語でLEDを光らせよう! - IchigoJamではじめるArmマシン語その4


Hana道場、越前がにロボコンに向けて開発が進んでます!超音波センサーを違うポートで使いたい?


2つのモーター制御に、たまごを惑星においてくるためのサーボ制御、ライントレース用の2つセンサー、もりもりです!
これにレアメタル判定用に、超音波センサーを追加!


福井の小学生向けご当地ロボコン「越前がにロボコン」いよいよ今週末11/3開催
11/3日 9:00-17:00 ショッピングシティ ベル、あじさいホールにて!(Googleマップ

マシン語が分かると楽しい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 */ static 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をちょっと超える感じ(追記、hsv2rgbをstatic関数にすることで下回りました。Thanks > @fujitanozomu さん)。#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万倍速!? セットで学ぶアルゴリズムとコンピューター

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