PC-6001: April 2009アーカイブ

AVR Tiny2313をROMとして動作させられるかどうか、ここのところ試行しているわけですが。

どうも、20MHz動作でも必ずアクセスが成功するというわけではないようです。 ごく稀にですが、誤動作することがあります。

プログラムは以下のようになっています。

_loop:
	in	ZL, PIND	;1  7bit
	lpm			;3
	out	PORTB, r0  	;1
	rjmp	_loop		;2

ここからアクセス時間が最長になってしまう状況を考えると、こうなります。 すなわち、in命令で得られたデータはまだアドレス確定前で、その直後にアドレス確定、次のinでようやく正しいアドレスが取得できる状況です。 すると、正しいデータが出るまでの最長のクロック数は、以下のようになります。

3(lpm)+1(out)+2(rjmp)+ 1(in)+3(lpm)+1(out) = 11 (clk)

これを20MHzで考えると、1clkあたり50nsなので、550nsということになります。 これは最悪の場合で、通常はここまでかからないのですが。 ただ、プログラムを読んでいるので、一回でも読み間違えれば当然のように誤動作します。

PC-6001で使われているROMのアクセスタイムは、以下のようになっています。

  • BIOS(BASIC) ROM: uPD2364 Max. 450ns (2364-45)
  • Character ROM: uPD2332 Max. 450ns (2332-45)
  • UV-EPROM: uPD2732 Max. 450ns (互換品のHN462732のデータ)

やはりこの時代の製品は450nsあたりが最大のようで、550nsでは間に合わないことがあるようです。

上記のプログラムではrjmpが無駄にクロックを消費しています。 rjmpがなければ最悪でも9clk = 450nsで動作してくれます。 そのため、in~outをたくさん並べれば誤動作の可能性は低くなります。 でもメモリは有限ですからいつかはrjmpで元に戻る必要があるため、絶対誤動作しないとは言い切れません。

誤動作といっても、ごく稀のことなのでこのまま行くか、信頼性を上げるために別の手を考えるか、悩ましいところです。


なんちゃってROMの動きを確実に確かめるために、なにかよいアプリケーションはないかと考えました。 そこで、Joystick版のSDカードアダプタからの読み込みをすることにしました。

とはいっても、Joystick版の場合、SPIの動作をすべてAY-3-8910のI/Oで実現する必要があるため、非常に多くのコードが必要になります。 たとえば、1ビットの何かを出力するためにも、

ld	a, #0x0f
out	(#0xa1), a
ld	a, #value
out	(#0xa2), a

といった具合に、愚直に実現すると8バイトもかかってしまいます。

なんちゃってROMに許されたアドレス空間は、アドレス線の本数の制約により128バイト。 先頭の4バイトはBIOSがROMの判別とジャンプアドレスを得るために必要なため、実際は124バイトしかありません。

そこで、ファイルを読み込むのは最初からあきらめ(FATなんてとても入らない)、セクタ0を読み込むことにしました。 もともとセクタ0の最初の446バイトは、ブートアップのためのコードを格納する領域ですしね。

それでも、電源ON->初期化->読み込みとあるため、結構大変です。 リリース済みのドライバを見てみたら、398バイトもありました。

それを削りに削りました。 エラーチェックやらレジスタの使い回しやら、それはもう、あらゆる手を使って。 で、なんとか124バイトの枠内に収めました。

で、できたものがこれです。 これから先は変態プログラムです。 よい子は見てはいけません。

SDDRV_DI  = 0x10		; output, pin8
SDDRV_CS  = 0x02		; output, pin7
CMD_READ	= 17 | 0x40	; command for read
REG		= 0xa0		; I/O port AY-3-8910 register latch
WRITE		= 0xa1		; I/O port AY-3-8910 write
READ		= 0xa2		; I/O port AY-3-8910 read
REG_SEL		= 0x07		; AY-3-8910 I/O select
REG_IOA		= 0x0e		; AY-3-8910 I/O A (input)
REG_IOB		= 0x0f		; AY-3-8910 I/O B (output)

	.org	0x4000
	.db	0x41		; 'A'
	.db	0x42		; 'B'
	.db	0x04
	.db	0x40		; start from 0x4004

	;; from 0x0083, Z=1, Cy=0, StackTop=0x0084
	;; A=0x42, B=0x00, C=0x3d, D=0xfb, E=0x61, H=0x40, L=0x04
_boot_start::
	ld	de, #0x0f08	; REG_IOB, counter
	ld	a, #REG_SEL	; 0x07
	out	(#REG), a
	rrca			; 0x83
 	out	(#WRITE), a	; setup AY-3-8910 I/O
	ld	a, d		; 0x0f
	out	(#REG), a
	ld	a, #(SDDRV_DI | SDDRV_CS) ;0x12
 	out	(#WRITE), a
_boot_init_loop:
 	out	(#WRITE), a	; DI | CS
	inc	a
 	out	(#WRITE), a	; DI | CS | CLK
	dec	a
	djnz	_boot_init_loop
	call	_boot_cmd	; H=0x40 (reset), H is set by caller
	inc	h
_boot_init_cmd1:	
	call	_boot_cmd	; H=0x41 (init)
	dec	c
	jr	nz, _boot_init_cmd1
	ld	h, #CMD_READ
	call	_boot_cmd	; H=0x52 (read)
	call	_boot_wait

	ld	h, #0xe2	; L=0
_boot_read_loop:
	call	_boot_bytein
	ld	(hl), c
	inc	l
	jr	nz, _boot_read_loop	; fall through

	;; AFBC________
	;; out C=input byte, B=0, A=0or1
_boot_bytein:
	ld	b, e
_boot_bytein_loop:
	;; 8 times of bit read
	ld	a, d		; 0x0f
	out	(#REG), a
	inc	a
	out	(#WRITE), a	; DI
	inc	a
	out	(#WRITE), a	; DI | CLK
	ld	a, #REG_IOA	; 0x0e
	out	(#REG), a
	in	a, (#READ)	; if A[0]=1, then data=1
	rrca			; A[0]->CY
	rl	c		; cy<-[7<-0]<-cy
	djnz	_boot_bytein_loop
	ret

	;; AFB_________
	;; in C=output byte
	;; out B=0, A=0x01/0x11
_boot_byteout:
	ld	b, e
_boot_byteout_loop:
 	ld	a, d		; 0x0f
 	out	(#REG), a
 	inc	a		; 0x10
	rlc	c
	jr	c, _boot_byteout_data
	xor	a
_boot_byteout_data:
 	out	(#WRITE), a	; (DI)
	inc	a
 	out	(#WRITE), a	; (DI) | CLK
	djnz	_boot_byteout_loop
	ret			; B=0, A=0x01/0x11, Cy=0, Z=0

	;; AFBC___L____
	;; in: H=cmd (must have 0x40 offset)
	;; out: C=result+1 (1, 2, 0xff), B=0, L=0, A=0x00/0x01, Z=0, Cy=0
_boot_cmd:
 	ld	c, #0xff
	call	_boot_byteout	; dummy
	ld	c, h		; A=0x11
	call	_boot_byteout	; cmd
	ld	c, b		; 0x00
	ld	l, #0x04
_boot_cmd_loop:
	call	_boot_byteout	; arg
	dec	l
	jr	nz, _boot_cmd_loop
	ld	c, #0x95
	call	_boot_byteout	; CRC (for CMD0)
_boot_wait:			; L=0, A=0x11
	call	_boot_bytein
 	inc	c
	ret	nz		; bytein != 0xff (0, 1, 0xfe), Z=0
	jr	_boot_wait

実際にはプログラムは122バイト(全体で126バイト)で済んでいます。 なお、タイムアウト判定を省略しているので、正常にSDカードが挿さっていないと無限ループします。 そのチェックは容量内ではできませんでした。 また、セクタ0の最初の256バイトのみ読んでいます。

まぁ、言うほど変態チックじゃないかな?

動かしてみると、正常に読んでいるため、20MHz動作なら問題ないみたいです。


先日のなんちゃってROMですが、Enable信号をモニタするのはスピード的に辛そうなので、バッファをつけてみました。

20090411-experiment.PNG

これだと、AVR側は入力されたアドレス線に対して出力するのみ。

_loop:
	in	ZL, PIND	;1  7bit
	lpm			;3
	out	PORTB, r0  	;1
	rjmp	_loop		;2

まさに「来たら打つ! 来たら打つ!」状態です。 実際の出力判断は244に任せていますので。

で、うまく動きます。 アドレスは7ビット分(A0-A6)なので、128バイトまでですが、ブートアップには十分な容量です。

ただ、ちょっと気になるのは、27C256(32kB UV-EPROM)を積むのとどちらがオトクかということです。 上記の回路はTiny2313(100円)+LS244(100円くらい)+XTAL+パスコン2個で、300円弱かかります。 一方、27C256は200円で買えます。

まぁ、244などジャンクを漁れば大量に出てくるので、実質買うことはないのですけど。 また、AVRはISPが使えるので、UV-EPROMよりは書き換えは楽ですね。

実際のSDカードインタフェース回路では、mega88も使うため、そこからのCLKO信号を入れることにより、XTALを省略することも可能かもしれません。

ただ、Tiny2313の仕様を見ると、外部クロックは16MHzまでとあります。 ためしにPC-6001の15.9744MHzでやってみたところ、誤動作しました。 微妙です。


設計変更をいろいろしていくうち、どうせAVR-SRAM間のDMAをしないのならSRAMのデータバスをCPLDに入出力させなくてよいことに気づきました。 SRAMはCSさえきちんとコントロールできれば、データバスは常にHi-Zなので、そのまま本体のバスに直結でも問題ないのです。 WEやOEでさえも。

ということで、SRAMのデータバスをCPLDの入出力から外したところ、CPLDの使用マクロセル数が激減(-16)しました。 それでわかったのですが、今までやたらマクロセルを消費していた犯人は、MUXのようなのです。 おそらく3-state bufferが8ビットx2、かつMUXが8ビットx2で、32マクロセル消費していたようです。 これではほとんどロジックは入れ込めませんね。 Xilinxの論理合成がヘボなのかもしれませんが。

というわけで、いま実験中のtiny2313の簡易ROM化がうまくいけば、あとはデコード等はXC9536(XL)が一つですべて賄えることがわかりました。

こちらはいざとなったら244を一つ挿入することになるかもしれませんが、そうすると副作用でISPができるようになるので(現在はデータバス直結なので無理)、悪いことばかりでもないかもしれません。 でも、この場合はプログラムフラッシュの自己プログラミングをサポートするほうが有効かな。


こんなものを試作してみました。

20090407-experiment.PNG

ATtiny2313をROM代わりに使おうというものです。 アドレスバスのピン数の都合で、64バイトまでしかハンドルできません。 あとは全部イメージになりますが、起動時だけなので問題ないでしょう。

やはりスピードがかなり厳しく、(初代)本体からのクロック(15.9744MHz)では間に合いませんでした。

20MHzの水晶でやってみましたが、やはり連続アクセスには難があるようです。 暴走こそしないものの、読み込みデータが抜けたりして、安定しません。

AVRのプログラムは今のところ以下のようになっています。

.org	0x0000
	ldi	R_TMP, LOW(RAMEND)
	out	SPL, R_TMP
	eor	R_ZERO, R_ZERO
	ldi	R_FF, 0xff
	ldi	ZH, 0x02 	; data start from 0x0100
	out	DDRD, R_ZERO
	out	PORTD, R_FF
	out	PORTB, R_ZERO
_loop:
	out	DDRB, R_ZERO	;1 4-8clk until PortB output disabled
_wait1:
	;; wait for read request
	sbic	PIND, 6		;1/2
	rjmp	_wait1		;2
	in	ZL, PIND	;1 Z = 0x0200 + PIND (PIND < 0x3f)
	lpm			;3
	out	DDRB, R_FF	;1
	out	PORTB, r0  	;1  8-11clk until data appears on the bus
_wait2:
	;; wait until read request has been cleared
	sbic	PIND, 6		;1/2
	rjmp	_loop		;2
	rjmp	_wait2		;2

	.org	0x0100
	.db	0x41, 0x42, 0x04, 0x40, 0x3e, 0x40, 0xcd, 0x75, 0x10, 0xc9

割り込みを使うとビットが連続したピンが使えなくなるため、busy loopで待ったほうが速そうなので、そうしています。 なんとかAVR側のプログラムを高速にできないか考えているのですが、これ以上は考え付きませんでした。 プログラムエリアからバイトを読むlpm命令は3クロックもかかりますが、代わりにSRAMから読むにしても2クロック、しかも入力アドレスに0x60を加算する必要があるので速くなりません。

データバスに3-state bufferをつけてしまえば、ウェイトなしでアドレス入力から垂れ流しができるので十分高速になるとは思いますが。 しかもPD6をアドレス線に使えます。 まぁ、搭載チップがひとつ増えてしまうので、なるべく避けたいところではあります。

ちなみに、どうせCS2がイネーブルになるときはROM読み込みだろうということで、CS2を直接PD6に入れてみたりもしましたが、症状は改善しませんでした。


思いついたアイデアをひとつ。

Tiny2313あたりを1個積んで、メモリバス(拡張ROM1の部分)をハンドルします。 いわゆるIPL ROM代わりで、CS2/MREQ/RD/D0-D7/A0-A5あたりを入出力させます。

本体に読み込まれるコードはmega88(I/Oバス)を経由してSDカードからブートプログラムを読み込むものです。 このコードを実行中は読み込みはTiny2313から、書き込みはSRAMに行くようにしておいて、いったん初期化が終わったらTiny2313を完全に切り離してSRAMのみアクセスするようにします。

これだとPLDを使わずとも、現在の実装の延長線上でもできそうな気がします。

問題は、データをバスに載せるのがタイミング的に間に合うかどうかですね。 Z80のデータシートを見ると、メモリリクエストが開始されてからデータを乗せるまでは3クロック程度のようです。 ROMエリアなので、おそらくWAITが入って4クロックくらいになるのかな? AVR/20MHzでは20クロック分くらいです。 別にUV-EPROMを使っても似たようなことは可能ですが、Tiny2313は書き換えが楽なのと、値段が安いのがアドバンテージかと思います。


漢字表示もできて、結構実用的に動くようになったSDカードインタフェースですが、皆さんにどのような形で使ってもらうかを悩んでいます。

今手元にあるのは、西田さんに作っていただいたone offの基板に実装したカードです。 この設計のままでも動作しますが、このカードのみではRAMが16kBしかないこと、自動起動しないことという制約があるので、公開するのに躊躇しています。

一応、ベルーガROMと一緒に挿せばこれらも可能なのですが、いまどき私を含めPC-6011を持っている人もいないでしょうし、1つの基板にしておきたいところです。

このプロジェクト(?)の大きな目的は2つあります。

  • 昔のソフトを簡単に実機で楽しむ
  • 新しいソフトを開発するプラットフォームにする

一つ目では、普通のプログラムの実行を妨げないように、0x8000より下位のエリアに、SDOS用のスタックやら作業領域用のRAMを積んでおきたいわけです。 二つ目では、アップデートが頻繁に行われる可能性があるので、やはり0x4000-のROMエリアはRAMにしておきたいわけです。

ということで、ハード的に今足りないのは、以下の点です。

  • 0x4000-0xbfffの(最低)32kB分のRAM
    これはSRAMを積めばいいので、多少のデコーダが必要だが簡単
  • 起動時に本体の動作とは無関係にロードする仕組み
    本体とのアドレスおよびデータの信号をバッファする必要がある。 また、ロード後に本体にそれを(拡張領域として)認識させなければならない。

どちらも、回路は技術的に難しいものではないのですが、データ線だけでなくアドレス線もバッファしなければならない関係で入出力が非常に多くなります。 CPLDを使うことを考えてはいますが、結構高いんですよね。 XC9572-TQ100で1,000円、XC95108-PC84で1,700円とかです。 44PLCCのものをを2個ということも考えられますが、2個にする関係で無駄になる入出力やマクロセル、そして余計な配線が増えるのでイマイチです。

まぁこの辺は以前も少し議論したことなのですが、未だにうまい解決法が見つからず、頭をぐるぐるしています。

ソフトウェアだと「まぁ作ってから考えるべ」となるのですが、ハード素人の私にはなかなか勇気が出ないのです。 プロトタイプを作るのもすごく大変ですし。