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 と印字されています.

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

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

作りかけの展示モードとシステムカードのパッチ

UperGrafx は何度か展示会に出しているのですが、ゲームの切り替えが手動であるために展示時間やタイトルに偏りがあるので自動で切り替えることにしました. またゲームを遊ばないが動かしたいというユーザーも一定数いる気がしますので有用な機能かもしれません.

ここで重要なのがシステムカードの起動画面で RUN button を押す操作です. これを省けば全て自動操作になります. しかしこれはオリジナルプログラムの改変が必要です.

展示モードは一般公開してもいいのですが、システムカードのパッチがいることや操作が煩雑なこと UGX-01 非対応ということでここに記載した非公式機能とする予定です.

対応方法 / UperGrafx 側

現在公開中の pac にはありませんので使えません. 対応した pac file を公開しましたらここに記載します.

システムカードのパッチ共通事項

  • システムカードは起動直後にボタンを何も押していない場合は自動的に RUN button を押した操作をすることになっています. 起動直後にボタンを押している場合は通常の動作をします.
  • CRC32 の算出方法は zlib の方法です.
  • 該当する ROM image へ IPS パッチを適用してください.

Super System Card

このファイルは CD 読み込み高速化パッチに RUN ボタン操作不要を追加したものです. UperGrafx に ROM を登録した後は ROM+RAM card の設定にしてください.

CD-ROM2 System Card version 2.0

CD 読み込み高速化パッチ非対応です. 単なる ROM card でいいので設定の変更の操作はありません.

Game Express CD Card (派手な方)

水着のおねーさんがでてくる派手な方です. CD 読み込み高速化パッチ非対応です. UperGrafx に ROM を登録した後は ROM+RAM card の設定にしてください.

Game Express CD Card (地味な方)

地味な画面のほうです. ほかの説明は派手な方と同じです.

Mingw系のgccで生成される exe ファイルの main() の argv の文字コード

タイトルが長い...
筆者は msys2 を常用し開発作業をしています. 昨日コマンドラインの引数 (main() の argv) にUTF-8の日本語文字列を渡しても正常に動かないケースがあり原因を調べました. そもそも日本語文字列をターミナルで使うということが10年間ぐらいなくてなんでいまさらと、いう話ですけど.

経緯

  • > echo.exe "お尻"
  • > hoge.exe "お尻"
  • main() の argv を調べたらなぜか SJIS になっている
  • shell の locale は関係ない
  • cmd.exe からでも関係ないし、その文字コードも関係ない

調べた

文字コードのことはかいてないのですが、Win32 ネイティブだとパス引数が変換され、互換レイヤだとパス引数が変換されないとあります. また「MSYSのコマンド群はPOSIX互換レイヤで動いています」と書かれているので echo は UTF-8 をいれても UTF-8 がでる理由もつじつまが合います.
http://7shi.hateblo.jp/entry/2012/05/05/220750

このパス引数の変換は /c/windows を c:/windows に変換してくれるというもので、msys2 の shell からは使う身としては大変便利だと思っています. しかしこれが誤動作して困るという記述は世界中にあります.

UTF-8 の文字列を SJIS に勝手に変換するのはコマンド実行から main() 実行の間にパス変換のついでに行われる根拠を見つけることはできませんでした. また変換先が SJIS になるというのも日本語設定の Windows を使っているからのはずで、別の言語だとまた別の文字コードに変換されるのかなど謎が多いです.

対策

そこにあるように msys 互換レイヤとか cygwin を使うなど exe の作成過程をかえることによって直せるものだと思われます. 本当のところはネイティブの exe リンク時に設定を変えられるのが最適なのですけど.

今回の機能は私だけがデバッグに必要なもので、仕方なく iconv を利用して変換された SJIS を main() の最初に UTF-8 に戻すということで回避しました.

高解像対応をやりたいその3

1080p を対応した後に整数を可変に循環させて平均として小数倍をだすというアイディアを教えてもらったので実装しました. 理想的な pixel aspect ratio にそしてある程度近づけることができました. *1

各種欠点は消せてませんが下記の2解像度でそれぞれ2種類の pixel aspect ratio をだすことにしています.

      |div2       |div3       |div4
理想値|0.583  --- |0.875  --- |1.167  ---
720pA |0.667 1.143|1.000 1.143|1.333 1.143
720pB | ---   --- |0.875 1.000|1.167 1.000
1080pA|0.625 1.071|1.000 1.143|1.250 1.071
1080pB|0.588 1.008|0.941 1.076|1.176 1.008

* 720pB.div2 は正常に絵が出ない. 設計ミスと性能の限界で直せない.

  • divN = vce dotlock = pce master clock / N
  • 1つ目の値: pixel aspect ratio
  • 2つ目の値: 理想との比率(倍率, 理想値 / 1つ目の値)

pixel aspect ratio の計算方法

      |vertical   |horizonal.2|horizonal.3        |horizonal.4
720pA |3          |2          |3                  |4
720pB |3          |(1+2+2+2)/4|(2+3+2+3+2+3+3+3)/8|(3+4)/2
1080pA|4          |(2+3)/2    |4                  |5
1080pB|(4+4+4+5)/4|(2+3)/2    |4                  |5

理想値は256x224 pixel の領域を 4:3 という仮定*2で下記の計算する.
224/3*N/256

切り替え可能

720p の A と B, 1080p の A と B は設定メニューで切り替えができます.
この都合で pixel aspect ratio on 4x3 での 3x3 は廃止にします.

*1:もちろん平均として小数倍なので、スクロール時の波打ちなどの違和感はあります.

*2:CRTでは横幅をユーザーが調節できるので仮定