mruby/c をコンパイルする

設計を考えていて MCU を決める事が最優先事項なので、mruby/c を導入してみました. 2週間ぐらい前に試したらリンクさえ通らなかったのですが、手順をやり直しました.

目的

MCU 側のクロスコンパイラで mruby/c をリンク、簡単な動作をみて実用に値するかが目的です. 実用できないと判断した場合は MCUスクリプトシステムを選定し直します.

スクリプトシステムを使うのは MCU 経由で FPGA で自作する予定の簡単な映像機能にたいして、画面描画制御とユーザーインタフェース管理をやりたいためです. C やアセンブラで作るには向いてない用途で、便利な選択肢があるならそれを使って開発時間を節約すべきだと考えております.

ダウンロード

https://github.com/mrubyc/mrubyc/releases/tag/release2.0

今回は release したものを使いました. 前回は git で最新版を clone して意味がわからなくなってしまって諦めてしまいました.

とりあえずコンパイル

src/Makefile の CFLAGS に -DMRBC_NO_TIMER を追加します. その後 msys2 で cd mrubyc-release2.0; make であっさり通りました. これは msys2 のパソコン上でのビルドで MCU のためのクロスコンパイルの記述は別途必要です.

シミュレーション用とターゲット用で分ける

mruby 系は組み込み向けのため、調整して組み込めという要素が強いみたいですので主に Makefile を手直しします.

シミュレーション用は msys2, ターゲット用は STM32F series を想定していきます. いきなりターゲット用にコードを書いても制限が多い MCU 上では問題の切り分けに手間が多いので、ある程度はシミュレーション用として PC で作っておく方が便利なためです.

変更分

いか diff の結果を切り貼りしてコメントをつけていきます.

diff -ru /e/mrubyc-release2.0/Makefile ././Makefile
--- /e/mrubyc-release2.0/Makefile	2019-06-19 19:14:00.000000000 +0900
+++ ././Makefile	2020-03-16 09:44:51.850266900 +0900
@@ -11,16 +11,16 @@
 
 
 mrubyc_lib:
-	cd mrblib ; $(MAKE) all
-	cd src ; $(MAKE) all
+	$(MAKE) -C mrblib all
+	$(MAKE) -C src all
 
 mrubyc_bin:
-	cd sample_c ; $(MAKE) all
+	$(MAKE) -C sample_c all
 
 clean:
-	cd mrblib ; $(MAKE) clean
-	cd src ; $(MAKE) clean
-	cd sample_c ; $(MAKE) clean
+	$(MAKE) -C mrblib clean
+	$(MAKE) -C src clean
+	$(MAKE) -C sample_c clean
 
 package: clean
 	@LANG=C ;\

ここは直さなくてもよかったんですが Makefile 作った人が make の -C を知らなかった気がします.自分も最近まで知りませんでした.

--- /e/mrubyc-release2.0/sample_c/Makefile	2019-06-19 19:14:00.000000000 +0900
+++ ././sample_c/Makefile	2020-03-16 10:05:38.938544900 +0900
@@ -7,10 +7,12 @@
 #  This file is distributed under BSD 3-Clause License.
 #
 
+CC = clang
 TARGETS = mrubyc mrubyc_sample mrubyc_concurrent mrubyc_myclass
-CFLAGS += -g -I ../src -Wall -Wpointer-arith
+CFLAGS = -I../src -I../src/hal_posix -Werror -Wall -Wpointer-arith
+CFLAGS += -O2
 LDFLAGS +=
-LIBMRUBYC = ../src/libmrubyc.a
+LIBMRUBYC = ../obj_msys/libmrubyc.a
 
 all: $(TARGETS)
 
@@ -27,4 +29,4 @@
 	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ main_myclass.c $(LIBMRUBYC)
 
 clean:
-	@rm -rf $(TARGETS) *.o *.dSYM *~
+	@rm -rf $(TARGETS) *.o *.dSYM

sample_c は libmrubyc をリンクして C の実行ファイルを作る目的のディレクトリです. -g は gdb はいまのところはいらないので削除, -O../src/hal_posix は src/hal の分離で追加. -O2 は好みです.
rm *~ は emacs とかのバックアップファイルだと思いますが私は使わないので消しました.

diff -ru /e/mrubyc-release2.0/src/alloc.c ././src/alloc.c
--- /e/mrubyc-release2.0/src/alloc.c	2019-06-19 19:14:00.000000000 +0900
+++ ././src/alloc.c	2020-03-16 09:57:28.267718100 +0900
@@ -19,7 +19,7 @@
 #include <assert.h>
 #include "vm.h"
 #include "alloc.h"
-#include "hal/hal.h"
+#include "hal.h"
 
 
 // Layer 1st(f) and 2nd(s) model
diff -ru /e/mrubyc-release2.0/src/class.c ././src/class.c
--- /e/mrubyc-release2.0/src/class.c	2019-06-19 19:14:00.000000000 +0900
+++ ././src/class.c	2020-03-16 06:54:07.594869800 +0900
@@ -274,6 +274,7 @@
   // error.
   // raise TypeError.
   assert( !"TypeError" );
+  return 0;
 }
 
 
diff -ru /e/mrubyc-release2.0/src/console.h ././src/console.h
--- /e/mrubyc-release2.0/src/console.h	2019-06-19 19:14:00.000000000 +0900
+++ ././src/console.h	2020-03-16 09:59:04.210404700 +0900
@@ -17,7 +17,7 @@
 #include <stdint.h>
 #include <stdarg.h>
 #include <string.h>
-#include "hal/hal.h"
+#include "hal.h"
 
 #ifdef __cplusplus
 extern "C" {

src 上の hal (hardware abstract layer)はファイルコピーで作るみたいですが、2つの環境向けに1つのファイルでやる場合は邪魔でしたのでディレクトリ指定を消しました.

return 0; は -Werror -Wall で停まる対処です. assert で停まるならいいんでしょうが、 warning は出さない方がいいので追加しました.

diff -ru /e/mrubyc-release2.0/src/hal_posix/hal.h ././src/hal_posix/hal.h
--- /e/mrubyc-release2.0/src/hal_posix/hal.h	2019-06-19 19:14:00.000000000 +0900
+++ ././src/hal_posix/hal.h	2020-03-16 09:55:03.289404300 +0900
@@ -21,7 +21,7 @@
 /***** Feature test switches ************************************************/
 /***** System headers *******************************************************/
 #include <unistd.h>
-
+ int fsync(int fd); 
 
 /***** Local headers ********************************************************/
 /***** Constant values ******************************************************/

私の msys2 が悪いのか fsync() の定義が unistd.h にないみたいでして、その場しのぎに定義をはっつけときました. あとで悪影響があるかもしれませんが、リンクは通りました.

diff -ru /e/mrubyc-release2.0/src/vm_config.h ././src/vm_config.h
--- /e/mrubyc-release2.0/src/vm_config.h	2019-06-19 19:14:00.000000000 +0900
+++ ././src/vm_config.h	2020-03-16 09:37:43.281886700 +0900
@@ -75,9 +75,5 @@
 // #define MRBC_REQUIRE_32BIT_ALIGNMENT
 
 // Debug code.
-#if !defined(MRBC_DEBUG)
-#define MRBC_DEBUG
-#endif
-
 
 #endif

MRBC_DEBUG をつけると arm-xx-gcc で pointer の cast で文句をいってました. MRBC_DEBUG を強制する記述は害なので消しました.

追加分

src/mrubyc.mak

#
# mruby/c  src/Makefile
#
# Copyright (C) 2015-2019 Kyushu Institute of Technology.
# Copyright (C) 2015-2019 Shimane IT Open-Innovation Center.
#
#  This file is distributed under BSD 3-Clause License.
#

.PHONY: all clean
CFLAGS = $(_CFLAGS) -Wall -Werror -Wpointer-arith -std=c99
CFLAGS += #-pedantic -pedantic-errors
CFLAGS += -I$(HALDIR) -DMRBC_NO_TIMER

COMMON_SRCS = alloc.c class.c console.c global.c keyvalue.c load.c rrt0.c static.c symbol.c value.c vm.c $(HAL_C)
RUBY_LIB_SRCS = c_array.c c_hash.c c_numeric.c c_math.c c_range.c c_string.c mrblib.c

TARGET = $(OBJDIR)/libmrubyc.a
OBJS = $(addprefix $(OBJDIR)/, $(COMMON_SRCS:.c=.o) $(RUBY_LIB_SRCS:.c=.o))

all: $(OBJDIR)/$(HALDIR) $(TARGET)
$(OBJDIR)/$(HALDIR):
	mkdir -p $@
$(TARGET): $(OBJS)
	$(AR) $(ARFLAGS) $@ $?
clean:
	@rm -Rf $(TARGET) $(OBJS)

$(OBJDIR)/vm.o:
	$(CC) $(CFLAGS) -Wno-unused-variable $(CPPFLAGS) -c -o $@ $<
$(OBJDIR)/%.o: %.c
	$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
include mrubyc.d

オブジェクト出力ディレクトリとhalディレクトリの分離. 依存関係ファイルは長いので分離. -DMRBC_NO_TIMER はとりあえずでコンパイルを通すのに必要でした.

Makefile

ARMGCC_PREFIX = /d/dev/arm-gcc/bin/arm-none-eabi-
all:
	$(MAKE) -f mrubyc.mak CC=clang _CFLAGS="-O2 -DMRBC_DEBUG" OBJDIR=../obj_msys HALDIR=hal_posix HAL_C=hal_posix/hal.c
	$(MAKE) -f mrubyc.mak CC=$(ARMGCC_PREFIX)gcc.exe AR=$(ARMGCC_PREFIX)ar.exe _CFLAGS=-Os OBJDIR=../obj_arm HALDIR=hal_arm
clean:
	$(MAKE) -f mrubyc.mak OBJDIR=../obj_msys HAL_C=hal_posix/hal.c clean
	$(MAKE) -f mrubyc.mak OBJDIR=../obj_arm clean

こういうライブラリで MCU 用に CFLAGS に -g と -DMRBC_DEBUG をつけるのはよく考える必要がありますし、ユーザーレイヤのデバッグはシミュレーション上でやるのでメモリの節約の観点から MCU 向けには消しました.

mrubyc.d はファイル依存関係を抜き出したものです. xxx.o: を $(OBJDIR)/xxx.o にしただけなので省略します.

Squirrel を久しぶりに使う

今の開発には大変疲弊しており気分転換に別の事をやることにしました. 割と重要なのが自作パッチ当てツールです.これは15年ぐらい前にテキトーに作ったものを使っていて、気に入らない部分がたくさんあるけど致命的な理由がなくなんとかなっていたのですが、今回作り直すことにしました.

Squirrel は10年ぐらい前にあった Lua ブームのなか、わたしは anago で採用しましてかなり強力で満足してます. また私が最後に C で組み込む側のプログラムを使ったのが5年前らしいです. 公式の website は 2016年3月以降は更新がないみたいでちょっと寂しく、別の組み込み型スクリプト言語も探している途中です. 将来の開発のためにも mruby 系が気になるのですが調査に時間がかかりそうなので手っ取り早く Squirrel でやりたいことを作りました.

Squirrel Standard Libraries ではテキストファイルが簡単に読み込めない

今回の目的はアセンブラで生成される Motorola S record を読み込んで ROM にパッチを当てるというものです. S record の読み込みはどの言語で作ってもいいのですが、今回は Squirrel で書くことにしました.

それで件名のテキストファイルの話になります. リファレンスを読んでいたのですが、標準ライブラリは正規表現はあるのにテキストファイル読み込みがみあたらず、回りくどくない代替の方法もなぜかありませんでした. 仕方ないので標準ライブラリをいじって fgets() をいれました....

diff -r /e/squirrel3/include/sqstdio.h ./squirrel3/include/sqstdio.h
14a15,17
>     virtual SQInteger Gets(SQChar *str, SQInteger size){
> 		return -1;
> 	}

diff -r /e/squirrel3/sqstdlib/sqstdio.cpp ./squirrel3/sqstdlib/sqstdio.cpp
41a42,47
> SQInteger sqstd_gets(SQChar *str, SQInteger size, SQFILE file)
> {
> 	char *r = fgets(str, size, (FILE *)file);
>     return r == NULL ? -1 : 1;
> }
> 
87a94,96
>     SQInteger Gets(SQChar *str, SQInteger size){
> 		return sqstd_gets(str, size,_handle);
> 	}
diff -r /e/squirrel3/sqstdlib/sqstdstream.cpp ./squirrel3/sqstdlib/sqstdstream.cpp
201c201,208
< 
---
> SQInteger _stream_gets(HSQUIRRELVM v)
> {
>     SETUP_STREAM(v);
>     char str[0x200];
>     self->Gets(str, sizeof(str));
>     sq_pushstring(v, str, -1);
>     return 1;
> }
243a251
>     _DECL_STREAM_FUNC(gets,1,_SC("xn")),

str[0x200] の文字列の長さにつきましては、私がその場しのぎに入れてることをご留意ください.

S record を読むスクリプトを作る

gets 導入後は正規表現でつまづきましたが手っ取り早く作れましたし、実行速度も速いのでいいと思います.

function mot_analyse(str)
{
	local ex = regexp(@"^S([01235789])([0-9A-Fa-f]{8,80})\n?");
	local t = {result = false, error = "", address = 0, data = []};
	if(ex.match(str) == false){
		t.error = "invalid string";
		return t;
	}
	local r = ex.capture(str);
	local mot_type = str.slice(r[1].begin, r[1].end).tointeger();
	local ar = [];
	for(local i = r[2].begin; i < r[2].end; i+=2){
		ar.push(str.slice(i, i+2).tointeger(0x10));
	}
	local sum = 0;
	foreach(s in ar){
		sum += s;
	}
	if((sum & 0xff) != 0xff){
		t.error = "checksum error";
		return t;
	}
	local data_count = ar[0];
	local address_count = 0;
	local pt = 1;
	switch(mot_type){
	case 0: case 5:
	case 1: case 9: address_count = 2; break;
	case 2: case 8: address_count = 3; break;
	case 3: case 7: address_count = 4; break;
	default: t.error = "unknown type"; return t;
	}
	for(local i = 0; i < address_count; i++){
		t.address = (t.address << 8) | ar[pt];
		pt += 1;
	}
	data_count -= address_count;
	data_count -= 1; //checksum
	t.data = ar.slice(pt, pt+data_count);
	t.result = true;
	return t;
}

正規表現は末尾の $ がなくても文字列の最後までチェックしているみたいでした. 仕方ないので \n? をいれてマッチしています. それと () の文字列を取り出す capture が他の言語とちょっと違ったので理解するのに時間がかかりました.

他の言語は文字列自体を返してくれるので r で文字列が得られることを期待していたのですが、r.begin, r[].end を利用し slice で文字列の最初から最後までを取り出せということでした....

Squirrel の将来性は...?

自分だけが開発に使い、パソコンで利用し、他人が作った C ソースやライブラリを組み込んで、スクリプトでテストしまくるという用途では最高です. わたしの将来の用途は安価なワンチップマイコンでの UI の作成に使いたいのですが C++ を使ってる時点で OS なしの低機能な環境ではたぶんだめかなと思いました. いくらか重複しますが気になる点を列挙いたします.

  • website の更新が止まっている
  • インターネットを調べても使っている人が少ないのか参考になる情報が5年前と対して変わってない
  • fgets 相当が標準になく、テキストファイル読み込みがやりづらい
  • 配列,文字列の切り出しが [begin...end] 形式で [begin, length] 形式がない
  • printf がなく print(format()) と書くのが面倒

なかなか難しいところです.

pukiwiki のファイルアップロードを複数でもできるようにする

最近 pukiwiki を設置し、いままでの文書を移行中です. そこでファイルのアップロードが1個ずつでかったるいので書き換えました.

コードは php のサイト https://www.php.net/manual/ja/features.file-upload.multiple.php からぱくってきました.

$ diff pukiwiki-1.5.2_utf8/plugin/attach.inc.php ./attach.inc.php
78a79,94
> 
> function reArrayFiles(&$file_post)
> {
>     $file_ary = array();
>     $file_count = count($file_post['name']);
>     $file_keys = array_keys($file_post);
> 
>     for ($i=0; $i<$file_count; $i++) {
>         foreach ($file_keys as $key) {
>             $file_ary[$i][$key] = $file_post[$key][$i];
>         }
>     }
> 
>     return $file_ary;
> }
> 
112c128,138
< 		return attach_upload($_FILES['attach_file'], $refer, $pass);
---
> 		//return attach_upload($_FILES['attach_file'], $refer, $pass);
> 		$str = "";
> 		$t = 0;
> 		foreach(reArrayFiles($_FILES['attach_file']) as $file){
> 			$t = attach_upload($file, $refer, $pass);
> 			$str .= $file['name'] . ':'. $t['msg'] . " ";
> 			if($t['result'] == FALSE){
> 				break;
> 			}
> 		}
> 		return array('result' => $t['result'], 'msg' => $str);
417c443
<   <label for="_p_attach_file">{$_attach_messages['msg_file']}:</label> <input type="file" name="attach_file" id="_p_attach_file" />
---
>   <label for="_p_attach_file">{$_attach_messages['msg_file']}:</label> <input type="file" name="attach_file[]" id="_p_attach_file" multiple/>

わたしは PHP の書式をほとんど知らないので $t = 0; が必要がわかりません. ruby だと t = nil などと宣言する必要があります. ruby ではそれがないと foreach{} 内部だけの変数になってしまい、 return で t を使うとエラーになってしまいます.

今回の対応で複数のアップロードをすると 'msg' が長くなりまして、それが大きい文字サイズで結果が表示されてしまうので編集者はちょっと見苦しくなります.

しかしこれぐらい公式で対応してほしいですね.

memory base driver for 6280 3/3

同名の記事 1/3 の "メモリデータの受信" を高速化できました. 2/3 の "書き込みルーチン" はいろいろ試しましたが劇的な効果は得られなかったので前回の物が最適のようです.

コードは read_optimize が 1,2,4,8 で異なるマクロを使ってます.

read_optimize	equ	8 ;7.0 sec
;read_optimize	equ	1 ;7.6 sec
	cly
	ldx	#%10
read_byte_next:
(ここにシフトレジスタからの読み込みコードが入る)
	sta	(<mb128_destsrc),y
	iny
	bne	read_byte_next
	inc	<mb128_destsrc+1
	inc	<mb128_length+0
	bne	read_byte_next
	inc	<mb128_length+1
	bne	read_byte_next
	rts

read_optimize = 1,2,4

	lda	#1<<7
read_bit_next
 rept read_optimize
	stz	joypad
	stx	joypad
	nop
	nop
	lsr	joypad
	ror	a
 endm
	bcc	read_bit_next
  • 最初の lda #1<<7 は前回同様ループカウンタの初期化です.
  • 2度の nop はシフトレジスタの立ち上がりから 4 CSH clocks が必要なので挿入します. 3 CSH clocks も試しましたがダメでした.

今回はいろいろ考えて lsr joypad としました. lsr mem 命令の場合は下記の3つの処理をします.

  • joypad を read.
  • read した data をシフト. temp = {1'b0, temp[7:1]}, C = temp[0]; とする.
  • (いらない) シフトしたデータを write する.

3 番目の不要な write は動作不安定の原因になると思って前回は避けていたのですが読み込み処理としては, clock が 1->0 または 1->1 になることは次の処理として問題ないです. data はずっと don't care です.

lda joypad; から ror a の間に txa などを挟む必要がなくなりましてとてもすっきりました.

read_optimize == 8

i	eval	0
 rept read_optimize
	stz	joypad
	stx	joypad
  if i == 0
	nop
  else
	ror	a
  endif
	nop
	lsr	joypad
  if i == 7
	ror	a
  endif
i	eval	i + 1
 endm
  • この命令はループがなくマクロで繰り返すので lda #1<<7 が不要になりました.
  • 2度 nop を挟む場所に可能なら ror a をいれることで暇な時間を有効活用できます.

アセンブラの最適化はパズルとしてはおもしろい

これを組むのに時間かけすぎです. 業務としてはよくないです.

memory base driver for 6280 2/3

write driver は RAM の data をまるまるコピーではなく通信経路からの fifo のコピーとしたかったのですが、既存の機能を使い回すということで前者でてっとりばやく実装することにしました.

書き込みルーチン

先日の serial_send を使ったら処理時間がえらいおそかったので早い物をつくりました。

write_start:
	cly
write_byte_next:
	lda	(<mb128_destsrc),y
write_shift:
temp	eval	0
 rept 8
 if temp <> 7
	tax
 endif
	and	#1
	sta	joypad
	ora	#1<<1
	sta	joypad
 if temp <> 7
	txa
	lsr	a
 endif
temp	eval	temp+1
 endm
	iny
	bne	write_byte_next
write_pointer_update:
	inc	<mb128_destsrc+1
	inc	<mb128_length+0
	bne	write_byte_next
	inc	<mb128_length+1
	bne	write_byte_next
	rts

今回は RAM を使わず a,x,y で回すことにしましたので bit 単位のループをなくして rept でコードを繰り返してます. tax と txa がまどろっこしいので削りたいのですが削れるのは最後だけみたいです.
あとコード量が増えるので bne が届くのがギリギリになってきました.
1bit 単位でループを回すなら下記でしょうか. 2bit 単位でもそんなに悪くないかも.

write_byte_next:
	lda	(<mb128_destsrc),y
	phy
	ldy	#8/2
write_shift:
 rept 2
	tax
	and	#1
	sta	joypad
	ora	#1<<1
	sta	joypad
	txa
	lsr	a
 endm
	dey
	bne	write_shift
	ply
	iny
	bne	write_byte_next

さらなる高速化の失敗

送信したい data が 8'h00 である場合は %00 と %10 を交互に送るだけであること, address 0x1fff000-0x1fff3ff が mirror であることを利用して tai 命令を使ってみました.

write_byte_next:
	lda	(<mb128_destsrc),y
	bne	write_shift
	tai	data00,joypad,2*8
	iny
	bne	write_byte_next
	bra	write_pointer_update
write_shift:
	(snip)
data00:
	byt	%00,%10
dataff:
	byt	%01,%11

結果は memory base 側へデータが正しくかけてませんでした. *1 csl; tai; csh とすれば正しく転送できますが、手計算では先述のループより遅いみたいです.
8bit 単位の転送ではオーバーヘッドが大きいのでたくさん 0 で埋める場合には使えそうです. mirror 領域が 0x400 byte なので1つの tai 命令での最大転送量は 0x400 / 8 / 2 で 0x40 bytes なのに注意してください.

data 0xff の転送もできそうなんですが inc a; beq がやりづらくその関わりに cmp #$ff; beq を書くのがまどろこっしくて実用化は難しそうでした.

*1:hold 時間がだめなのでしょうか?

memory base driver for 6280 1/3

自前の資料を基に作りました. 対象はネイティブの IO port 経由です. 別の組み込み MCU を使う場合は設定条件をよくみて SPI 機能を使うとよいでしょう.

こういうシフトレジスタの処理だけならアセンブラで書くのはとても楽しいです.

送信ルーチン

serial_send:
send_bit_next:
	lsr	<$02
	ror	<$01
	ror	<$00
	cla
	rol	a
	sta	joypad
	ora	#1<<1
 rept 0
	nop
 endm
	sta	joypad
	dex
	bne	send_bit_next
	rts

解析したサブルーチンは最大 8 bits まで送れる作りでしたが、zero page を使って最大 24 bits送れるようにしました. また Carry flag を活用して分岐命令を減らしています.

rept 0 について. 送信に関しては nowait で送ってかまわないようです.

初期化ルーチン+簡単な受信

	ldx	#9
	lda	#$A8
	sta	<$00
	stz	<$01
	bsr	serial_send
	lda	joypad
	sta	<$00
	and	#$0f
	php
	ldx	#1
	stx	<$00
	bsr	serial_send
	plp
	bne	init_ng
	lda	joypad
	and	#$0f
	cmp	#1<<2
	bne	init_ng
	sec
	rts

既存のルーチンは 8bits 送信, 1 bit 送信+受信, 1bit 送信+受信でしたが、 9bits 送信+受信, 1bit 送信+受信にわけました. 送信後, IO port から受信するときは CLOCK 立ち上がりから出力まで遅いみたいです. この場合は dex; bne; rts と十分に時間が経過しているので待ち時間の挿入はいりません.

先日の解析でプログラムを読み間違えたので、間違いに従い 10bits の初期化コードを3度送ってしまいました. この場合はエラーがでるにもかかわらず、実際には初期化が成功していて input bit 0 の出力が memory data となるので上キーとIボタンが送信されなくなりました.

メモリデータの受信

	ldx	#1<<1
	cly
read_byte_next:
	lda	#1<<7
	sta	<$00
read_bit_next:
	stz	joypad
 rept 0
	nop
 endm
	stx	joypad
 rept 2 ;do not remove
	nop
 endm
	lda	joypad
	lsr	a
	ror	<$00
	bcc	read_bit_next
	lda	<$00
	sta	(<mb128_destsrc),y
;dest or src += 1; length -= val; length == 0
	bsr	rw_ptr_length_update
	bne	read_byte_next
	stz	joypad
	rts
  • zero page $00 を 8bits シフトレジスタにします.
  • bit7 = 1, bit6:0 = 7'h00 にすることでカウンタも兼ねます.
  • stz joypad (clock=0); stx joypad (clock=1)のあとの nop は必要でそのあと lda joypad です.
  • lda joypad; lsr a で取り込んだデータを Carry にいれ、シフトレジスタにいれます. lsr joypad だと予期しない書き込み処理も入るので lda 命令を使います.

最近の MCU でやる場合

  • 最近のシフトレジスタバイスは MSB->LSB がほとんどで LSB->MSB なのに注意が必要です.
  • メモリベースからのデータの更新条件はクロックの立ち下がりではなく立ち上がりのために立ち上がり直後に読み込むとデータが安定しません.
  • どこまで早いクロックでいけるのかを調べると楽しいと思います.

KOCD2001 MB128 driver

こちらのが前回よりプログラムがきれいです. ただしやってることはほとんど同じでした.

ec1b8:
	jsr	lc08e ;send 10x3+3 bits, check detection bits
	bcc	lc1c0
	lda	#$FF
	rts
lc1c0:
;operation = read
	ldx	#$01
	ldy	#$01
	jsr	lc070
;send address
	ldx	<$8d
	ldy	#$08
	jsr	lc070
	ldx	<$8e
	ldy	#$02
	jsr	lc070
;send length
	lda	<$8b
	pha
	tax
	lda	<$8c
	pha
	sta	<$8b
	stz	<$8c
	txa
;(snip)

;---- initialize ----
lc08e:
	stz	$1000
	ldy	#$03
lc093:
	phy
	ldx	#$A8
	ldy	#$08
	bsr	lc070
	clx
	ldy	#$01
	bsr	lc070
	nop
	lda	$1000
	and	#$0F
	pha
	ldx	#$01
	ldy	#$01
	bsr	lc070
	nop
	lda	$1000
	and	#$0F
	plx
	ply
	cpx	#$00
	bne	lc0bd
	cmp	#$04
	clc
	beq	lc0c7
lc0bd:
	dey
	bne	lc093
	clx
	ldy	#$03
	jsr	lc070
	sec
lc0c7:
	rts

;---- send 1 bit into MB128 ----
lc070:
	txa
	and	#$01
	sta	$1000
	nop
	nop
	nop
	ora	#$02
	sta	$1000
	pha
	txa
	lsr	a
	tax
	pla
	and	#$FD
	dey
	bne	lc070
	nop
	nop
	sta	$1000
	rts

ADCD3001 A.III memory base 128 driver

解析しました.
(2020年1月28日更新: コードの読み間違いが原因のactivation * 3 とその後の不明の 3 bits を削除)

;ADCD3001 A.III memory base 128 driver
;bits |send data   |meaning
;10   |0001_0101_01|activation
;1    |p           |set operation read or write
;10   |aaaa_aaaa_aa|memory address bit 16:7; address bit6:0 = 7'h00
;20   |l *20       |memory data bit length
;lengt|x *length   |(read) memory read data
;lengt|d *length   |(write) memory write data
;(legend)  0:low, 1:high, x:don't care, p:operation, a:address, l:length, d:write data
;---- main routine ----
;initialize -> send read command -> check memory signature
ed7ff:
	jsr	ld789 ;rts only (?)
	jsr	ld7b8 ;get activation code (A=0:ng, A=1:ok)
	cmp	#$00
	bne	ld81d
	lda	#$01
	jsr	ld885 ;send R/W(=A.1, 1:read, 0:write), address (10 bits), length (20 bits)
ld80e:
	jsr	ld863 ;read data
	lda	$36e5
	sta	($21)
	jsr	ld6f9
	bcc	ld80e
	cla
	clc
ld81d:
	jsr	ld78a
	jsr	ld901
	rts

;address 0x1ff000 assignments for memory base 128
;w0 serial write data
;w1 serial clock
;r3 always 0
;r2 memory base detection serial data
;r1 always 0
;r0 memory data

;I'm not sure how to reset serial clock count???
ld7b8:
	clx
ld7b9:
;   old -> new
;sd 0001_0101_01
;sr xxxx_xxxx_01
;sd:serial data (when serial clock sets 0->1, mb128 latch 0x1ff000.w0)
;sr:r2 = detection data, bit3, 1 and 0 must be 0
	lda	#$A8 ;serial data lsb->msb
	ldy	#$08 ;bitcount
	jsr	ld8d4
	cla
	jsr	ld8b7 ;send 1bit (A.0)
	lda	$1000 ;get activation reply
	and	#$0F
	tay
	lda	#$01
	jsr	ld8b7
	lda	$1000
	and	#$0F
	cmp	#$04
	bne	ld7e1
	tya
	cmp	#$00
	bne	ld7e1
;activation ok
	clc
	cla
	bra	ld7ec
ld7e1: ;retry max 3 times
	inx
	cpx	#$03
	bne	ld7b9
;error!
	jsr	ld7ed
	sec
	lda	#$01
ld7ec:
	rts
ld7ed:
;sd 000
	cla
	ldy	#$03
	jsr	ld8d4
	jsr	ld901 ;wait some clocks
	lda	#$01
	sta	$1000 
	jsr	ld901
	rts

ld885:
	jsr	ld730 ;tai $D91D,$36E1,$0004; rts
	jsr	ld8b7 ;send 1 bit; data=A.0
	jsr	ld738
	lda	$36d9
	ldy	#$08
	jsr	ld8d4 ;send 8bits
	lda	$36da
	ldy	#$02
	jsr	ld8d4 ;send 2bits
	lda	$36dd
	ldy	#$08
	jsr	ld8d4 ;send 8bits
	lda	$36de
	ldy	#$08
	jsr	ld8d4 ;send 8bits
	lda	$36df
	ldy	#$04
	jsr	ld8d4 ;send 4bits
	rts

PCEのCDの型番の採番ルール

リストをみていると2通りあるようです.

発売社名+CD+年+用途+連番

TJCD9001

  • TJ: 発売社名. 基本2文字だが、ハドソンだけ H の1文字を使っている.
  • CD
  • 9: 年. 198x か 199x の1桁目を示す. 1988 から 1996 までで 7 は存在していないようだ.
  • 0: おそらく用途. 0 はゲーム. 詳しくは後述する.
  • 01: シリアル番号. 01 から順番に振られる番号.

年やシリアル番号は採番時の順番であるので、発売日の順番とは異なる可能性がある.

発売社名+PR+不明+用途?+連番

NAPR-1032

  • NA: 社名. NA か NI.
  • PR
  • 1: 不明. ボナンザブラザーズだけ2. ほかは1.
  • 0: おそらく用途であると思われる
  • 32: シリアル番号.

NEC Avenue, NEC Interchannel だけがこれを使った理由は不明. NEC Home Electronics は HECD で慣例を使っている.

おそらく用途

ゲーム(0)ではないものをリストアップする.

HRCD-9101 ロムロムカラオケ ボリューム1
HRCD-9102 ロムロムカラオケ ボリューム2
HRCD-9103 ロムロムカラオケ ボリューム3
HRCD-9104 ロムロムカラオケ ボリューム4
HRCD-9105 ロムロムカラオケ ボリューム5
JCCD9501 ロムロムカラオケ VOL1 すてきに スタンダード
JCCD9502 ロムロムカラオケ VOL2 なっとく アイドル
JCCD9503 ロムロムカラオケ VOL3 やっぱし バンド
JCCD9504 ロムロムカラオケ VOL4 ちょいと おとな!?
JCCD9505 ロムロムカラオケ VOL5 カラオケ 幕の内
JCCD0601 ウルトラBOX創刊号
JCCD0602 ウルトラBOX 2号
JCCD0603 ウルトラBOX 3号
JCCD1604 ウルトラBOX 4号
JCCD1605 ウルトラBOX 5号
JCCD2606 ウルトラBOX 6号

1,5,6 が振られている. 1 と 5 はカラオケで別に振られている理由は不明.
シリアルナンバーは用途別に 01 から順番に振られるので JCCD から始まり 01 で終わるソフトは3つある.

ユナ1作目の型番

MAME の pcecd.xml で謎だったものを実物で確認しました.

HCD5078 銀河お嬢様伝説ユナ HuVIDEO同梱再販版

銀河お嬢様伝説ユナの(1作目, PCE)は 1992 年に発売された DISC 1枚のパッケージが HCD2031 で、 1995 年に発売された DISC 2 枚のパッケージが HCD5078 です.
またパッケージの型番は DISC 自体の型番も流用されており、両方のパッケージともにゲーム本編のディスクが HCD2031 で HuVideo のディスクが HCD5078 と印字されています.

この場合はパッケージとしての型番かディスクとしての型番かで話が変わってきまして、私はディスクとしての型番を優先したい立場にあります. 似たような例がうる星やつらにもあるみたいです.

データベースをみるにこのゲームディスクはバージョン違いもあるらしいのですが、ディスクへの印刷で見分けることはできないようです. このソフトではないですがときめきメモリアルはバージョン違いが多いのか見分け方が詳しくかかれているのをインターネット上で(いまのところ)見つけることができます.