たまりば

  パソコン・インターネット パソコン・インターネット  三鷹市 三鷹市

スーパー電子手帳のカレンダーとドットマトリクス
2020年10月21日 21:49

今は別館のサイトの方に昔(2007年)書いた内容を、手直ししたり新たな考察を加えたりしたもの。
元のページはこちらだが、文字コードが指定されていないのでChromeなどの低機能なブラウザでは文字化けして見えないかもしれない。Firefoxをおすすめする。



むかーし、むかし。電子手帳というものがあったそうな。
今でもあるけど、今は「ぴーでーえー」なんて横文字で呼んだりして、過去の「電子手帳」という呼び名の方がカッコよくて自分は好きだ。大体皆PDAが何の略か知らないだろう。なにせ自分も知らないのだ。
(↑と当時書いたのだが、今はPDAも携帯電話に吸収されて無くなってしまった。諸行無常。)

その電子手帳が「ちょっと高価な子供のおもちゃ」になりだした頃の話。

思い出深い電子手帳を紹介しよう。カシオから発売されていた電子手帳だ。
「スーパー電子手帳」シリーズと、何かわからないがたぶん何か違う「スーパー電子手帳Jr.」シリーズ。
男児向け女児向けで様々な種類があり、「PKバトルリーグ」「ツインゴール」「ペットテレパシー」などある。

機能は定番の電話帳・カレンダー(スケジュール帳)・電卓。それに子供向けだから占いやらゲーム。
この電子手帳は7×7ドットのひらがなが4行×12文字表示できる。つまり普通に考えて縦32ドット×横96ドットだ。
96×32文字
ほとんどの表示は8×8単位(漢字は16×16)なのだが、例外がカレンダー。
カレンダー
こんな感じに3×5の数字を使っている。3×5は数字をまともに描ける最小のドット数と言ってよいだろう。
日曜祝日は白黒反転、数字横の点はスケジュールがあることを示す。

ここで疑問に思うことはないだろうか?
そう、カレンダーの最大行数は6行。数字は縦5ドット。つまり5*6+隙間5ドットで35ドットとなり、32ドットの画面をオーバーしてしまう。
6行ははみ出す

それでどうなっているかというと簡単なことで、その分のドットがきちんと用意されている。
しかし極限まで無駄を切り詰めているので、なかなかけったいな形になっており、
ドットパターン(実は横幅が誤り)
こうである。
もう惚れちゃいますね。
ここに、
カレンダーを表示した状態(横幅は誤り)
こうなるわけだ。

ところで、これでは日付の表示に画面全部を使ってしまい、曜日が表示できない。
分かりづらくないかって?
大丈夫。
スーパー電子手帳本体
本体の方に書いてあるから。



さて、ここからが新たな発見である。

この電子手帳、パッケージで画面の総ドット数を誇っているのを過去に見た覚えがある。
探すとこちらに発売元はツクダオリジナルだが見たところ同シリーズの「似顔絵電子手帳カラーリスト」のパッケージが見つかった。
3118ドット
「3118ドット」とある。
数えてみよう。メイン部が縦32×横96、加えてはみ出し部分が(4×9+3)が2個で、3150ドット。
おや、計算が合わない。差は3150-3118=32。32? 縦のドット数と同じだ。
まさか…と別に見つけた写真で数えてみると、確かに横は95ドットしかない。そこも切り詰めていたか。

しかし32ドット減らすと何が嬉しいのだろう。
切りの良い32×96=3072ドットには足りないし、よくあるアイコンのためにコモン線が1本多いタイプで33×96=3168なら32ドット減らさなくても足りる(ドットマトリクス外にアイコンがあるが見たところ16個であり、足りる)。
しかし、考えてみると追加のドットはすべて個別に制御する必要があるわけではない。
表示する図形は30・31の下の方と右の点だけ。
右の数字部は「31」しか表示しないので、字と地の2セグメント。左も、調べてみると「1」の字形は上で想像で書いたようなセリフ付きのものではなくデジタル数字の直線の「1」であったので、0と1を表示するには2セグメントで、地を合わせた3セグメントで済む。4つの点も足して合計わずか9セグメントで足りる。
実際には隣り合っていないドットを1つのセグメントにまとめることはできないので多少多くなるが、下図の左9セグメント右7セグメントでいけそうだ。
追加ドットパターン
このセグメントを制御する配線を考えてみよう。

…と、考えてみたら普通にはできそうにない。
何が問題かというと、この囲まれたセグメント。
囲まれたセグメント
ドットマトリクス部から横を1ドット削ることで使えるようになるセグメントは、1seg×32com分。(マトリックスの縦横のコモン線・セグメント線と個々のセグメントが紛らわしいので前者をcom・segと書く)
つまりこれだけではマトリックスにすることはできず、セグメント側を全体を1seg(使い方としてはコモン)に割り当ててcom側で制御するしかない。
これではセグメントの隙間を通しでもしない限り他のセグメントに囲まれたセグメントを個別に制御することができない。

これがcomがもう1本あればseg線を上から伸ばしてできるのだが…
1com×複数segでのパターン
もしくはsegが2本あれば2×Nのマトリックスにできてどうにかなるのだが…

と思ったが、よく考えたらそんなことをしなくてもできた!
囲まれたセグメントは1個だけ。1本だけならcom線も追加ドットに隣り合った最下段の1本を上から伸ばすことができる。
1seg×複数comでのパターン
周囲のアイコン含め、配線にも問題がない。
配線

このパターンだとうまく説明がつくことがある。
実物の表示は、YouTubeに動画があり、分かったのだが、
カレンダー実物
これを見ると、最下段でない日曜日には下に枠が付いていることが分かる。
最下段の数字が白黒反転した時は、ドット数からして下に枠がつかず、不自然である。つまり、下もう1列のドットは付けたくても付けられないのだと考えられる。
comがもう1本あって1com×複数segのパターンであれば、下もう1列は容易に追加できる。
1com×複数segで+1列
また、追加のドットを下ではなく上に付ければ、白黒反転時に端が切れるのが「30」「31」から「1」「2」になり、日曜を含まず休日表示が起こりにくい点、文字数が少ない点からこちらの方が好ましいと考えられるが、1と2を表示できるパターンは囲まれるセグメントが多く、この作戦は不可能である。0と1の下4ドットだからできたパターンなのだ。
1com×複数segのパターンであれば上に1と2のセグメントも可能だ。
1com×複数segで1・2のセグメント
もうひとつ、「1」の字形がセリフ付きでなく縦棒なのも、セリフ付きの形は不可能だったためとも考えられる。縦棒でも特におかしいわけではないが、3×5ドットの数字では1に上下や上だけにセリフを付ける方がよく見る気がする。
なお、segが2本ある場合では、下もう1列が不可、1と2のパターンが不可は同様だが、1に上だけならセリフを付けることができる。

実機は確認していないので合っているかはわからないが、今のところ観察結果を上手く説明できており、なかなか可能性が高いのではないかなと思っている。  

  • 【訂正・加筆】
    2020年10月10日 00:00

    2020/10/10 ネット契約を1MbpsにしてみたにWindows10の帯域専有が解決したことについて追記。
    2020/03/09 まぶしいものを見た後のような残像が常に見える症状に1ヶ月後の状況を追記。
    2019/09/03 ポケットプリンタ制御でコードの一部がHTMLタグ扱いで消えていたのを修正。
    2019/07/17 ファミコンで9×9ドット文字表示(ほか)に実機動作していただいたツイートを追加、およびコードのミスを修正。
    2019/07/17 ファミコンで全画面に任意の画像(ただしモノクロ)を表示のコードのミスを修正。
    2017/04/23, 2017/05/22, 2017/07/24, 2017/11/21, 2019/06/08, 2019/06/16, 2019/06/22, 2019/07/15, 2020/02/27ゼルダの伝説ブレスオブザワイルドプレイ日記(ネタバレ有) 更新。
    2016/12/06 漢点字一覧表で、別の元データとの間で不一致があったので追記。
    2016/09/10 ネット契約を1Mbpsにしてみたの読み込み時間の記述が間違っていたので訂正。
    2016/09/04 ファミコンの縦解像度224px説の考察 左8pxを隠す機能の用途が思っていたのと違ったので追記。
    2016/06/06 Windows7でのペイントの劣化具合に新しく気づいたバグ情報を追加。
    2015/08/29 ベースラインPICの注意点で、型番の間違いを訂正、OPTION2の書き込み方法の間違いを訂正。
    2015/08/28 Windows7でのペイントの劣化具合に新しく気づいたバグ情報を追加。
    2013/09/26 5×5 ひらがなフォント5×5フォント改 / JavaScriptフォント表示機から5×5ドットフォント完成版に、思えばリンクを貼っていなかったのでリンク。
    2013/05/05 最弱のPICマイコンで電子オルガン_前編の単純ミスを1ヶ所修正。
    2013/04/27 文字コード表示機が特定の環境で動かない問題を修正、RTL文字での表示崩れを修正。
    2013/03/03 5×5ドットフォント完成版が紹介されていたので少々加筆しました。
    2012/11/18 ハロウィー?ンの正規表現に訂正・加筆があります。

    【このエントリについて】
    (2012/11/18)
    今まで、記事の内容にミスを見つけた場合はその記事だけ修正していたのですが、最新の記事はともかく古い記事にミスを見つけた場合は直しても気づかれないだろうと思って直すのが億劫になっていました。
    これではいけないと、どうするべきか考えた結果、訂正を知らせるエントリを1つ作ることにしました。
    記事を訂正した際にはこのエントリを更新して最上位に持ってくるように運用しようと思います。
    (2013/03/03)
    訂正だけに限らなかったので、エントリ名を【訂正】から【訂正・加筆】に変更しました。  
    タグ :お知らせ

  • 【お知らせ】記事の変更箇所の書式を変えます
    2020年08月30日 18:55

    ネット上で公開した情報が間違っていて訂正するときなどに、変更前の内容を消して変更後の内容だけにする行為が嫌いなので、今までここでは
    誤った文章。直した文章。(yy/mm/dd訂正)
    のような表示にしていた。
    具体例を挙げればこのようなものだ。(ベースラインPICの注意点より)
    今までの編集表示
    しかしこれはこれで、内容を知るためには変更後の文だけを読めばよいので、いちいち変更箇所が示されているのは邪魔だ。
    間違った文を見てしまった人にはどこが間違っていたのかを伝えたいが、単に記事の内容を知りたい人にかつて間違っていたことを伝える必要はない。

    そんなわけで、これからはCSSで変更前を消し、変更後の内容だけが見えるようにする。
    上記の例ではこうなる。
    これからの編集表示
    変更前の内容はソースを見れば分かるようになっている。
    変更箇所のソース
    暇を見て過去の記事の書式を変更していく。

    ただ、修正直後は修正箇所が見えていたほうが良いかもしれない。
    JavaScriptで経過時間で表示を変えるようにしたい。あとインタラクティブに選んだ版間の差分を表示するようなのもやりたい。  

  • PIC16のDhrystone MIPSを測ろうとしてみた
    2020年04月29日 02:23

    PIC、中でも昔ながらのミッドレンジは演算性能が低いことで有名だ。
    ベンチマークでどれほど低い値が出るか気になる。マイコンの性能比較に広く使われているDhrystoneは整数演算だけなので低性能なCPUでも走りそうだ。

    そう思ってDhrystoneのソースコードを見てみると…
    int    Arr_2_Glob [50] [50];

    ミッドレンジPICに5000バイトもメモリは無い。最大でも368バイトだ。

    -完-



    …とここで諦めてしまうのはつまらない。
    よく見てみれば2500個の要素を全て使っているわけではない。アクセスするのはわずか4箇所。これならなんとかなるのではないか。
    なんとかなるというのはつまり、Dhrystoneをそのまま走らせることはできないにせよ、なるべく結果に影響の出ない範囲でコードをいじったり、もし本来のコードが走ったらどの程度の速度が出るかを推測することで、それなりに意味のある値を出せるのではないか。

    というわけでやってみた。
    使うコンパイラはMPLABの標準のCコンパイラ、XC8(v1.44)。無償版なのできっと最適化とかに制限があるだろうが気にしない。

    まずは何はともあれコンパイルが通るようにしなければならない。適当に配列要素の数を減らそう。50×50要素を7×6要素に。
    int Arr_2_Glob [7] [6]; //Arr_2_Glob [50] [50];
    typedef int     Arr_2_Dim [7][6]; //[50] [50];
    これが妥当かどうかは後で見ていく。
    ついでにもう1つこっちも減らさないといけない。50要素を25要素に。
    int Arr_1_Glob [25]; //Arr_1_Glob [50];
    typedef int     Arr_1_Dim [25]; //[50];
    int 50個=100バイトはあるんではないかと思うかもしれないが、コンパイラの仕様上1つのオブジェクトは1バンクに収まっていないとならない。
    また総メモリ量もわりときつく、ちょっと増やすとすぐ割り付けに失敗する。

    配列の読み書きのコードは変えていない。
    範囲外を読み書きすることになるがそこは自分の足を撃てるC言語。何の問題もない。

    他数箇所変更した。
    ・エラーが出るたび適当にそれっぽいヘッダファイルをinclude
    よく分からないまま追加しているがまあ多い分には問題ないだろう。

    ・NOSTRUCTASSIGN
    structassignで「can't generate code for this expression」が出たので、それ用に用意されたコードが使われるよう
    #define NOSTRUCTASSIGN
    したら動くようになった。
    これにともないmemcpyがコードに書かれたものが使われるようになったが、ライブラリ側のものと衝突したのでコメントアウトした。

    ・時間計測のためのTIMEだのなんだのを削除
    結果に影響の無い部分なのでバッサリと。
    このへんをPICで動くように変更するのは手間なので時間計測はシミュレータで行うことにする。
    PICは実行クロック数が確定的なので問題ない。

    ・mallocができない
    のでユーザーズガイドを見ると、
    Dynamic memory allocation, (heap-based allocation using malloc, etc.) is not supported on any 8-bit device.
    とある。
    幸いmallocが使われているのは初期化の部分、ベンチマーク本体のループの外だ。直接宣言してしまおう。
    ポインタが指す先のメモリがどう用意されたものかはプログラムを走らせる上で違いはないだろう。
    おそらく最適化を防ぐために持ってまわった定義をしているのだろうと思われ、そこまでの最適化はきっとされていないだろうと判断する。少なくともポインタ参照はされている。
    /*
      Next_Ptr_Glob = (Rec_Pointer) malloc (sizeof (Rec_Type));
      Ptr_Glob = (Rec_Pointer) malloc (sizeof (Rec_Type));
    */
      Rec_Type temp1;
      Rec_Type temp2;
      Next_Ptr_Glob = &temp1;
      Ptr_Glob = &temp2;

    なお、これで測り終えた後見つけたのだが、別の対処法として、呼び出されると事前に用意したポインタを返す疑似mallocを使っている人がいた。(後述のAVRのコード)
    こちらの方がより安全そうなので真似してみたのだが、後述のポインタ幅違いがまた出てどうしてもうまくいかなかった。
    諦めて直接宣言のままにした。

    ・よく分からないエラーが出た
     *Ptr_Ref_Par = Ptr_Glob->Ptr_Comp;
    の部分で、「error: (1466) registers unavailable for code generation of this expression」というエラーが出た。
    PICでレジスタってなんだろう。RAMとは違うのか。
    なんだかよく分からないが2分割したらエラーが消えたのでこれでよしとする。
    struct record *temp;

        temp = Ptr_Glob->Ptr_Comp;
        *Ptr_Ref_Par = temp;
    …が、後にこれは必要なくなる。後述。

    これで先へ進むようになり、コンパイルが通るか…と思いきや意外なことにプログラムメモリが足りない。結構食うんだなあ。
    RAMがミッドレンジ最大の368バイトあるものとして手持ちからPIC16F88を選んだのだが、こいつのプログラムメモリは4kワード。
    どうせシミュレータで動かすのだし最大の8kワードある石を適当に選ぶか…と思ったが、コードを見ると大量のprintfがあって容量を食っていそうだ。
    状況説明のための出力は不要なので開始と終了を1文字だけ残して消す。
    結果出力部分に未使用の変数が消されることを防ぐためのprintfがあるがこちらも極力切り詰める。
    例えばこれを
      printf ("Int_Glob:            %d\n", Int_Glob);
      printf ("        should be:   %d\n", 5);
      printf ("Bool_Glob:           %d\n", Bool_Glob);
      printf ("        should be:   %d\n", 1);
    こうする感じで。
      printf ("a:%d\n", Int_Glob);
      printf ("b:%d\n", Bool_Glob);
    こうして切り詰めたら余裕で入った。3kワード弱にまでなった。
    なおprintfから呼ばれるputchの実装は、意味のある出力をするものではなく、シミュレータで適宜止めて出力内容を見るための、引数が消されない最低限のコードにした。

    これでビルドが通り、(内容はともかく)動作をするようになった。

    出力を確かめると、配列の部分の値が違うのは想定通りだが、他に構造体のポインタ演算あたりの部分も違っている。
    調べてみるとおかしいのは例の2分割で代入したところで、16bitのポインタに8bitのポインタを代入している。
    この際データが不可解な変化を見せており、その影響でそのあとif文で通らないはずの方を通っている。

    XC8ではポインタが8bitと16bitの2種類あるのだが、使い分けは手動ではできず、コード中の全ての代入を認識して自動で最適なものを使い分けてくれるという。
    これが判定に失敗して間違った代入をしてしまっているように見える。つまりコンパイラのバグではないか。

    ループ外に適当に代入文を書いてみたところ今まで8bitだったところに16bitのポインタが使われるようになり、出力が正しくなった。
    そして例の2分割代入部分も元のコードでコンパイルが通るようになった。

    配列部分も正しく動作しているか調べよう。
    配列のアクセスの部分の数値を書き換えて配列内をアクセスするようにし、正しい結果が出るかを見る。
    出た。
    また、ここで時間計測し、配列アクセス部以外にかかる時間が変化していないことを確認した。
    よって、不正なアドレスをアクセスすることで他の実行結果に影響を与えていることは無いと考えられる。

    これで多分配列部分以外は本来のDhrystoneの想定通り動いているだろう。



    さて配列の量をいじった事によりベンチマーク結果にどれほどの影響が出ているか考えてみよう。

    1次元配列の方の要素数は、(2次元配列を[2][3]にしてメモリを空けた上で)[25][30][35][40][48]を試し、実行時間は一切変化しなかった。
    こちらの影響は無いものとみて良いだろう。

    2次元配列の要素数を[7][6]から変えて変化を見てみる。
    配列の宣言の部分と、typedefの部分。
    宣\def[50][50][50][6][7][50][7][6][2][2]
    [7][6]58005693580056935571
    [4][6]58005693580056935571
    [8][3]58005693580056935571
    [2][3]58005800580058005571

    これを見ると、typedefの方の2次元めを変えると実行時間にやや変化が見られるのに対し、
    配列の宣言の方は[2][3]でのみ値が変わるが他は要素数によらず一定である。

    本来の[50][50]に近づけられないのが気がかりだが、現状の判断として、配列宣言の要素数を大きくしてもベンチマークの実行時間には影響を与えないと考えることにする。
    typedefの方は折角なので本来の[50][50]にしておく。

    なおこの要素数だが、変えるとコンパイルに失敗することがかなり多い。
    要素数の積が48(メモリ量が96バイト)を越えるとメモリ不足でコンパイルできないのは分かるのだが、次表のようにそれ以下でも値に応じて不規則に失敗する。
    [*][2][*][3][*][4][*][5][*][6][*][7][*][8][*][9]
    [2][*]×××××
    [3][*]××××××
    [4][*]×××××
    [5][*]××××
    [6][*]××
    [7][*]
    [8][*]
    [9][*]??
    [10][*]??
    [11][*]??
    [12][*]??
    (○: コンパイル成功 / ×: コンパイルエラー / ?および表範囲外: 未チェック / 空欄: 96バイト超)

    この時のエラーは次のようなもので、調べるとメモリのバンクへの割り付けに失敗しているエラーで、PICにはよくあることのようだ。有料版だと出なくなることもあるらしい。
    D:\P\xc8\v1.44\sources\common\lwdiv.c:30: error: (1357) fixup overflow storing 0x8E in 1 byte at 0x950/0x2 -> 0x4A8 (dist/default/debug\Dhrystone88.X.debug.obj 120/0x26)

    配列の宣言の方はこれでいいとして、実際にアクセスする部分はどうか。この分をどう考えるかは難しい。

    何らかの計算をしてアドレスを求め、配列の範囲外とはいえどこかしらをアクセスしてはいる。
    しかし、ここで生成されているコードはどうあっても2500の全ての要素にアクセスすることはできない。
    そのような不完全なコードで時間を計ってよいものか。

    とはいえ2500要素5000バイト分のアドレスというものはミッドレンジではそもそも存在し得ないのだから、それを計算するコードとしてどのようなものを想定するのかの正解は無い。

    考えた末、以下2点を補正することにした。

    ・乗算
    まず、アドレスの生成部分を見ると、1バイト×1バイト=1バイトの乗算ルーチンが呼ばれている。実行時間は100サイクル程度。
    これはまずい。50×50要素の配列にアクセスするには積を2バイトで持たなければならない。
    試しに2つのchar型の変数を乗算してみると実行時間は値によって変わり、124や178サイクル。
        char temp01 = 8;
        char temp02 = 100;
        int temp03 = temp01*temp02;
    →124
        char temp01 = 38;
        char temp02 = 100;
        int temp03 = temp01*temp02;
    →178
    この分を適当に補正することにする。1回あたり50サイクル、配列アクセスは4回あるので計200サイクル追加することにする。

    ・バンク
    5000バイトのメモリは存在しないが、せめて複数バンクにわたるメモリのアドレスを算出するコードは考慮しておきたい。
    1バンクあたり96バイトという中途半端な値を使うのはさすがに大変すぎる。
    仮に大量のメモリを積むならもっとまともなアクセスができるようにするだろう(というか、実際にPIC18やEnhancedミッドレンジでされている)
    悩んだ末、「80バイトや96バイトの容量があるバンクが十分に大量にあり、バンクごとに切りの良い64バイトだけを使う」「バンク選択にかかる時間は考えない」ことを仮定し、以下のコードを書いた。
    int address = 0x1234
    char addrh = address >> 6;
    char addrl = address & 0x3F;
    これに50サイクル掛かった。2次元配列のアクセスは4箇所あるので200サイクルを追加する。

    以上より、実測値5800サイクルに2次元配列アクセスの分を想定した補正値400サイクルを加えた6200サイクルを、ミッドレンジPICにおけるDhrystone1回分の実行時間に相当する値と考える。

    これをクロック数に直して24800クロック、1MHz・1秒あたりDhrystone相当値は40.3、1757で割りDMIPS/MHzの推定値は、
    0.0229 DMIPSっぽい値/MHz
    と得られた。



    比較しよう。

    16bitや32bitのマイコンの宣伝にはよく公式にDMIPSが書かれている。見ると、比較的性能の低い(インオーダー・シングルコア)のものでも1DMIPS/MHz程度はある。
    CPUDMIPS/MHz情報ソース
    PIC32MM 1.53http://ww1.microchip.com/downloads/en/DeviceDoc/60001324b.pdf
    RL781.39https://www.renesas.com/ja-jp/products/microcontrollers-microprocessors/rl78/rl78-features.html
    ARM Cortex-M00.89 (最適化条件を変えれば1.02や1.27)https://developer.arm.com/products/processors/cortex-m/cortex-m0

    何十倍も違うとあまり比較にならない。
    もっと性能の低い、8bitCPUの値を探そう。
    探してみるとそれなりに見つかる。

    http://oneweekwonder.blogspot.jp/2013/12/z80-dhrystones.html
    Z80。定番だ。
    3.5MHzで0.142DMIPSというので、0.04DMIPS/MHz。

    http://www.homebrewcpu.com/new_stuff.htm
    65(C)02とZ80。6502はファミコンで有名だ。
    6502 - 1.02MHz: 37Dhrystone
    Z80 - 2.5MHz: 91Dhrystone
    というので、
    37/1757/1.02 = 0.02DMIPS/MHz
    91/1757/2.5 = 0.02DMIPS/MHz
    Z80の値が上の半分だ。まあ環境やコンパイラでこの程度の誤差は出るものだろう。

    http://ww2.tiki.ne.jp/~maro/AVR/project/bench1/index.html
    AVR。PICとよく比較されるマイコンだ。
    at90s8515・8MHzで、最速の結果で1.938MIPSとある。0.24DMIPS/MHzだ。
    ただしこの人、律儀に2500バイト(?)のために読み書きに3クロックかかる外部RAMを接続している。
    (*2500バイトと書いてあるのはたぶん間違いだと思う。C言語的にIntは16bit以上のはず。)
    自分のやったように必要な分のメモリだけにするなら外部RAMは要らずもっと速くなる。
    4クロックから3クロックに変えるだけで1.659MIPSから1.933MIPSに上がったというのだから、同じ割合で1クロックになれば2.920MIPS・0.365MIPS/MHzに上がる計算になる。

    http://www.ecrostech.com/Other/Resources/Dhrystone.htm
    AVRもうひとつ。
    こちらは内蔵メモリに収まるよう配列要素を半分にするなどしているようだ。先述の疑似mallocはここの記述だ。
    ATmega64で、0.328DMIPS/MHz。
    上の0.24DMIPS/MHzより速く、推定0.365MIPS/MHzよりは遅い。
    調べてみるとat90には乗算命令がなくATmegaにはあるようなので、at90より速いはずだ。してみると上の推定0.365MIPS/MHzは少々怪しいか。

    これらの値と今回のPICの推定値を比較してみる。

    Z80: 0.02~0.04DMIPS/MHz
    Z80はゲームボーイのCPUしか扱ったことがないが、1命令のクロック数は4の倍数で、概ねメモリアクセスの回数×4クロックに一致する。これはPICと似た性能である。
    本来のZ80は多少クロック数が異なり、またレジスタが多かったりループ命令があったりするが、大勢に影響は無いだろう。
    クロックあたりに行う動作がPICと同程度で、命令がZ80の方が豊富であることから、DMIPS値が1倍~2倍というのはだいたい感覚と合っている。

    6502: 0.02DMIPS/MHz
    6502はメモリアクセスあたり1クロック+α(内部処理分)といった趣で、Z80よりクロックあたりの性能は高い印象がある。
    Z80と同等か低い値というのはちょっと感覚と合わない。まあZ80も2つで2倍の差があるくらいだし、この程度は誤差なのかもしれない。

    AVR: 0.24~0.365DMIPS/MHz
    Z80や6502と1桁違う。PICの10.5~16倍。感覚的にちょっと多すぎな気もするが、よく考えてみよう。
    まずAVRは大半の命令が1命令1クロックで動く。PICは基本的に1命令4クロックなので、命令数では2.6~4倍となる。
    ミッドレンジPICとAVRのアーキテクチャを比較すると、
    ・PICは汎用レジスタが1つのみであり何をやるにもメモリに値を置く必要がある
    ・PICはプログラムメモリのページ・RAMのバンク切り替えで時間を食う
    ・PICには乗算命令が無い
    ・PICは間接アドレッシング用のレジスタが1本しか無いことやメモリのバンク分けのため、C言語に向いていない
    ・一方AVRは間接アドレッシング用のレジスタが16bit×3本もあり、大変C言語向きである
    といったあたりの要因からして、2.6倍くらいは妥当だし4倍もまあありうるのかなと思えてくる。

    Conclusion


    独自の仮定のもとにそれなりにDMIPSに近いと信じる値を算出し、いくつかの8bit CPUの値と比較して感覚とそれなりに合う結果となったので満足である。



    結論に問題があるとすれば、まず一番怪しいのが2次元配列アクセスの補正が正当かどうか。
    そしてmalloc除去を始め各所で加えた変更が正当かどうか。
    あとはPICでまともにC言語を使ったのは初めてなのでなにか手順にとんでもない間違いがあるかもしれない。
    有料版で最適化をかけて別次元の速さになったりしないかも少し心配だ。(「(PRO版なら)最大で60%小さく400%速いコードができていたんだぜ」ってコンパイルするたびに言われるのだ)
    コードはgithubに上げたので、何か疑問があれば色々試してみるとよいだろう。
    https://github.com/Ikadzuchi/Dhrystone88.X

    なお、本稿をほぼ書き終えた段階で公開用にコードを整形したところ、ループ外の不要部を削っただけなのだが、ループの実行時間は5800サイクルから5824サイクルに増えた。面倒なので文中の数値は直さない。
    コードの位置が変わることによりページをまたいだりして実行時間がこの程度変化することは十分考えられる。
    他にもループ外にテスト用の乗算コードを書いたら7016→6996に短縮したこともあった。(不正な場所を実行していた時なので実行時間は大きく違う)  

  • マリオ3でのキノコの突然変異の考察
    2020年03月10日 09:37

    スーパーマリオシリーズの代表的なアイテムにスーパーキノコ(や1UPキノコ)がある。

    今は左のようなデザインで定着しているが初代スーパーマリオブラザーズでは右のデザインだった。
    現在と初代のスーパーキノコ
    初めて初代のキノコを見たときは何だこりゃと思った。
    変わりすぎだろ何があった。
    …と、ずっと思っていた。

    先日マリオ2を初めてプレイして知ったのだが、まず顔が付いたのはマリオ2だったんだな。
    マリオ2のキノコ
    マリオコレクション(今風のデザイン)やマリオDXのおまけ(初代のデザイン)でしかマリオ2は見たことがなかったので知らなかった。
    スーパーマリオシリーズの様々な物に顔が付いていることについてはこのように言われている。
    雲や草には顔があるように見えるが、これは立体感を出すために書き込まれた線が、顔に見えていただけだった。
    しかし、これが「怖い」と言われたことから、次回作の『2』では、背景の雲や草にはっきりとした顔が書かれた。
    https://www26.atwiki.jp/gcmatome/pages/3410.html

    ただちょっと不思議なのは、初代の時からパッケージイラストにはマリオの手に持つスーパーキノコと思しきキノコにしっかりと顔が書かれていることだ。地面の花をも差し置いて。
    初代スーパーマリオのパッケージのキノコの顔
    スターやジュゲムの雲には初代から顔があるし、これは物には顔を描くというスタイルは初代からあったのを2で徹底したということか。

    にしても。
    スーパーマリオ1・2・3・ワールドのキノコ

    顔以外も2から3で変わりすぎじゃないか。
    …と思っていた。
    実はそうでもなかったのである。

    まず、実は上の画像には1つ嘘がある。
    マリオ1・2の斑点は橙~茶色・マリオ3の斑点は赤だと認識していたのだが(そして実際上の画像もそうなっているが)、斑点の色はどれも同じである。ついでにマリオの服や赤ノコノコの甲羅、スーパー木の葉も同じ色である。
    マリオ2・3のキノコの色
    今回キノコの色について調べていて初めて気づいた。
    スーパーファミコン以降の真っ赤になった色のイメージや、空の色やキノコの地の色の違いで誤認していたものと思われる。
    そして同じ条件で撮られた別のソフトの画像はあまり存在しないことから、気づけなかったのだろう。
    実際、先の画像はMarioWikiから取ってきたのだが、見ての通り色は一定していなかった。ファミコンの色はかくも曖昧なものである。

    次に地の色が白になった事について。これはアートスタイルの変化に対するハードの制限である。
    マリオ3ではキャラクターの絵に(大半は黒の)縁取りが付くようになった。
    これにより、縁に1色とられることとなり、残り2色でキノコを描くことになった。
    地の黄色を残すか柄(え)の白を残すかの選択になるが、1UPキノコとの統一や他のキャラクターとの兼ね合いを考えると、白にするのが自然だろう。
    ちなみにマリオ3の通常の(地上面での)スプライトのパレットは
    ・プレイヤーキャラ用(色はパワーアップなどで変わる。標準マリオはベージュ/赤茶/黒)
    ・白/赤茶/黒 (スーパーキノコ、スーパー木の葉、赤ノコノコの甲羅、パタクリボーなど)
    ・白/緑/黒 (1UPキノコ、緑ノコノコの甲羅、蔓/葉など)
    ・ベージュ/黄土/黒 (クリボー、ノコノコの顔など)
    の4種である。

    とはいえ縁取りを付けただけで突然3のデザインになるわけでもなかろう。…と思っていた。
    試しに2のキノコに縁取りだけ付けてみよう。
    ところで、縁取りの付け方には4連結と8連結がある。
    4連結と8連結
    不思議なことに、マリオ3ではほとんどのキャラクターのほとんどの部分は8連結で縁取りされているのだが、キノコは4連結である。
    キノコだけ最初に描かれたとか、デザイナーが別とかだろうか。何にせよ4連結で描くことにする。
    まずは外形をそのままにして描くのが自然だろう。
    マリオ2から3のキノコへ_1
    すると、妙に痩せた印象になってしまった。縁取りが無ければ物と背景との境が外形だと認識するのに対し、縁取りをすると縁の1pxの中央を外形と認識するからだな。
    1pxだけ広げよう。
    マリオ2から3のキノコへ_2

    さて、ファミコンにはスプライトの左右反転機能があるので、左右対称な物体は必要なタイル数が半分で済む。
    タイル数は節約したいので、左右対称のデザインにしよう。
    とりあえずそのまま左右対称にしてみると、
    マリオ2から3のキノコへ_3
    左はあからさまに2つの大きな斑点が並んでしまって不自然だ。右の方が望みがありそうだな。
    くっついてしまった上の斑点を切って、中央のスペースが不自然に空いているので、消えてしまった左のものくらいの大きさの斑点を1個置く。
    マリオ2から3のキノコへ_4

    ほぼマリオ3のキノコになってしまった!

    比較してみよう。
    マリオ2から3のキノコへ_比較
    特に上部の丸みが完全に一致しているのと、斑点の形の大部分が一致しているあたりがポイントが高い。
    縁取りを付けてからここまで制限に従って不自然な部分を直していっただけだ。
    ここまできたら後は笠を1px下に下ろし、柄を1px太らせ、斑点を自然に調整するだけ。その程度の変化には特に不思議はない。

    マリオはハードの制限から生まれたデザインというのは有名だが、キノコもそうだったのだなあ。
    次作のワールドで色数やスプライト数の制限が無くなったにも関わらず3のデザインを(地と斑点の色は反転したが)踏襲、更にこの時左右非対称になったのがその後左右対称に戻り、一番制限のきつかった3のデザインに固定されたというのがなかなか面白いところだ。