FF2 の皇帝呼び出しを調べた

原理説明 (簡素)

  • 2人目がブリンクの本を武器として熟練度を上げたときに CPU address $62BA に data 0x0f (10 進数で 15) が代入される.
  • CPU address $62BA は聖堂で復活するときに起きるイベント番号で 0x0f は皇帝が出てきて最終戦となる.

イベント番号が代入される理由

  • CPU address $6200-$62ff は大半はプレイヤーキャラのパラメータ置き場.
  • その中で 0x40 byte ごとにキャラ #0, #1, #2, #3 (以降p) となる.
  • 武器としての熟練度は $6200-$620f + p * 0x40 にある.
  • 更新アドレスの計算は $6200 + p * 0x40 + index * 2. index の正常な値は 0 から 7 だが、魔法の本の武器としての index は不正な値で不連続で最小値 0 で最大値 0x64.
  • ブリンクのほんの index は 0x3d でキャラ #1 (2人目) だと $62ba が算出される.
  • CPU address $62ba にはもともと data 0x15 が入っているが熟練度レベルとしての最大値は数値で 0x0f (画面表示の数字は 16) なので 0x0f が代入される
  • CPU address $62bb も熟練度経験値として計算する. この変数は宿屋で宿泊したときに起きるイベント番号. 番号の振り方は CPU address $62ba と同じ. cheap 氏の 2021/2/23 での記述が参考になる.

変数領域の発掘

  • キャラのパラメータは 0x40 byte ごとと書いたが厳密には offset 0x34-0x3f の末尾の方は関係のないパラメータで CPU address $62ba がその例.
  • 自分が見つけたところでは CPU address $62f4 が 4 人目がいるかというフラグだった. ほかは用途が不明.

不正な武器での熟練度更新 address

下記2条件で総当りで調べた.

0 0xac 0x6234 | 2 0xba 0x6314
1 0xac 0x6274 | 3 0xb6 0x6332
0 0xb1 0x627a | 3 0xb1 0x633a
2 0xac 0x62b4 | 2 0x9b 0x6348
1 0xb1 0x62ba | 2 0xaa 0x6348
3 0xac 0x62f4 | 2 0xb4 0x6348
2 0xb1 0x62fa | 2 0xb9 0x6348
1 0x9b 0x6308 | 3 0xaf 0x634c
1 0xaa 0x6308 | 3 0xba 0x6354
1 0xb4 0x6308 | 3 0x9b 0x6388
1 0xb9 0x6308 | 3 0xaa 0x6388
2 0xaf 0x630c | 3 0xb4 0x6388
3 0x98 0x6310 | 3 0xb9 0x6388
3 0xa0 0x6310
3 0xa5 0x6310

アドレスの定義

  • CPU address $6044 ($6344) data bit2 = 1 とするとネリーが救出されたことになるが上記の方法では更新できない.
  • CPU address $6300-$631f は大雑把には位置、お金あたりが入っている.
  • CPU address $6020-$6047 ($6310-$6347) 辺りは大雑把にはゲーム進行のフラグになっている.
  • CPU address $6080-$60ff ($6380-$63ff) 辺りは大雑把には宝箱を開けたかのフラグになっている.

考察

  • 熟練度レベル最大値が 15 であることと皇帝が出てくる数値が 15 であることは偶然の一致で奇跡.
  • ほかの数値は進行上初期化している値があるような前提なので無理やり選んでも止まってしまうのに、皇帝が出てくる場面は必要条件がない. これも奇跡.
  • ブリンクの本を装備したときに更新するアドレスがイベント番号であることも偶然の一致.
  • バグの挙動に詳しい cheap さんの進め方は説得力がある. 長期間丹念に調べていることは驚異で彼の功績のおかげで発展できている.

パラレル 5V の flash memory の datasheet をみた

anago 相当の flash programmer を mruby で作ってみようとしました. anago で最近気になったことを直してみようと調べてみました.

  • GUI でデバイスを動かすときにスレッドをまともに扱っていない → 適切な直し方は知っているが C++ でつくるのめんどい
  • flash programming で ROM image が 2 の n 乗ではないとかけない → ROM としてありえない数値なので解釈に困るので手動で補正するしかない
  • flash device ID を拾えないか → 後述
  • chip erase ではなく sector erase を使えないか → 後述

ROM dump はそんなに難しくないのですが flash programming で昔見てた AM29F040B, W29C040 などのデバイスを確認してます. flash の command 関連は当時のわたしが面倒そうだから実装しなかったと推測していたですが、デバイスによって仕様がかなりバラバラで実装が不可能なのが事実でした...

SPI ではなくパラレルの flash は +12V がいるのは除外して +5V だけのものでも西暦 2000年以前に設計された古いものがあり、コマンド体型が統一されていないので難しいです. 当時(2009年頃)、ファミカセに flash memory を載せる実験をしたきっかけはその5年前にバイト先で廃棄するだったパソコンのマザーボードBIOS 用に載っていた W29C020 を拝借したことから始まります*1.

実験に成功した後 AM29F040B が新品で手に入ることを知り、秋月電子でも EN29F002 と MX29F040 が売っていることを知り...そんな流れです.

flash device ID を拾えないか

1998年設計の W29C040 には command で flash device ID を得る方法がありません. device ID 取得には 12V が必要の上、アドレスバスを全ビットを適切に入力する必要があります.

sector erase を使えないか

AM29F040B と MX29F040C は sector size が 0x10000 bytes 均一です.

W29C040 には sector erase はありません.

EN29F002 は sector の並びが均一ではない上、上位ビットはファミカセ基板で楽に配線できる方をつなげと当時指示を書いたので入力したアドレスバスから sector を指定/把握する方法がありません.

SST39SF040 は sector size が 0x1000 bytes 均一のようです. データシートにあまり細かく書いてありません. このデバイスがなぜか一番供給が安定しているみたいなのですが、コマンドアドレス幅が広いとかで癖があります.

導ける答え

  • command を打たずに address: device ID の data を取得する
  • command を打って同じ address を取得して値が変わったら command から device ID を取得する
  • device ID を取得不能な場合は手動入力の上 chip erase をする
    • W29C040 は chip erase がいらない
  • device ID でも EN29F002 は chip erase
  • AM29F040B 系と SST39SF040 系は sector size を見ながら sector erase + program

こんなところでしょうか...?

*1:許可をもらって拝借しました

mruby - mpsse 経由で SPI Flash/ I2C EEPROM programmer を作るその2

mruby でエラーの行番号を出す方法を下記を参考にいれました. API ドキュメントは読んでないので本当にいいのか保証がありません.
https://tyfkda.github.io/blog/2013/09/21/mruby-backtrace.html

ソースコードは整備ができましたので公開いたします. プロジェクト名は ezp201x という怪しい programmer をもらったけどソフトを使いたくなかったのでケースだけを流用しようと思ったからです.
gitlab.com

ruby script を書いて感じたのはやはり Squirrel より便利なのでこちらを乗り換えようと思います. もう 10 年以上放置している anago もこれで作り直して、libusb を触るところだけ C, 基本的な処理はコンパイル済みの mruby, カートリッジハード個別のドライバは rb ファイル直接とすると 楽しくプログラムをかけそうです.

anago の場合はユーザー数がかなり多いので GUI をつけろと要求が来るのは面倒なんですよね... 自分は GUI 制作に向いてない*1し、wxWidgets 使うにしろ C++ を書くのが大変に苦痛(言語として難しすぎる)なので気軽にかければ本当にそれがいいのですけど.

これは個人的な信仰で、 C++ は一応書けるけど難しく手間が多いので書きたくない、 Python はインデントが慣れないので*2やろうと思ったことさえない、結果として Ruby がいいということです.

anago を作り直すことに GUI で協力してくれる人がいればコメントなりで連絡ください.

*1:twitter で upergrafx control panel が使いにくいと言われて気にしてます

*2:C では {} を絶対に省略しない人です

mruby - mpsse 経由で SPI Flash/ I2C EEPROM programmer を作るその1

ICE40 専用ではなく汎用の 8 pin memory programmer を作ることにしました.

24 系 I2C EEPROM と 25 系 SPI flash のピン配置

1つの socket で対応ということで下記にしました.

socket         SPI flash    I2C EEPROM
1 AD4 8 +3.3V |1 CS# 8 Vcc |1 A0  8 Vcc
2 DO  7 AD5   |2 DO  7 HOLD|2 A1  7 WP
3 PU  6 AD0   |3 WP  6 CLK |3 A2  6 SCL
4 GND 5 AD1+PU|4 GND 5 DI  |4 GND 5 SDA

74157
AD2 = mode == 0 ? I2C.SDA : SPI.DO

AD
0 SPI.CLK, I2C.SCL
1 SPI.DI, I2C.SDA(input)
2 SPI.DO, I2C.SDA(output)
3 未使用
4 SPI.CS
5 mode (0:I2C, 1:SDA)
* PU はプルアップ

I2C では SDA を AD1 と AD2 につなぐようになる回路図が多いですが SPI と共用する場合は multiplexer を利用します. I2C では WP = 0 で書き込み可能ですが、SPI の WP は逆ですのでここも注意が必要です.

93 系の EEPROM は配線が全く違うので共用は不可能としてソケットのあまりに別途 8 pin の場所を用意して, CS# を個別出力にして切り替えようと思います. Vcc ラインは P-MOS FET で電源を切り替えるようにするつもりです.

mpsse の driver

前回の ICE40 の programmer のソースに書き足して対応しました. I2C は野良ソースコードと FTDI の技術文書を見て作りました. 野良ソースコードは SDA の出力が wired or ではなく high と low を出すようでしたが、こちらの実装では hiz と low の2通りとしました.

mpsse driver とグルー言語 mruby をつなぐ

  • 毎度ながら Squirrel でもいいのですが新規開拓として mruby にしました.
  • mruby の取得は msys2 なら pacman -S mruby でできます.
  • mruby のリンクは -lmruby -lws2_32 でできました.

一番の問題は C の関数と mruby の API で、公式がまともなドキュメントを出していないのでインターネット上の野良情報なり自分でヘッダやソースを読むことが求められます. 一応いままでこの手のグルー言語なりなんらかのライブラリの API を見ましたがここまで雑なのはとても不満です.

公式のドキュメント: http://mruby.org/docs/api/header_list.html

2013 年 3 月に書かれた下記の野良情報が便利というのは 8 年間放置している証拠だと思います.
https://tyfkda.github.io/blog/2013/03/11/mruby-api.html

話がそれました. 私の実装は ruby から呼び出された method の引数を配列とみなし、 uint8_t * の配列を作って mpsse の driver と渡すものです. mruby の配列長を得る方法は信じられないことにどこにも書いてませんでした.

自分が書いたソースは下記です.

struct array{
	mrb_value mrb_array;
	mrb_int size;
	uint8_t *data;
};

static int array_into_uint8_t(mrb_state *m, struct array *t)
{
	t->size = ARY_LEN(RARRAY(t->mrb_array));
	if(t->size == 0){
		t->data = NULL;
		return 1;
	}
	t->data = malloc(t->size);
	for(mrb_int i = 0; i < t->size; i++){
		mrb_int v = mrb_fixnum(mrb_ary_ref(m, t->mrb_array, i));
		if(v < 0 || v >= 0x100){
			free(t->data);
			t->data = NULL;
			return 0;
		}
		t->data[i] = v;
	}
	return 1;
}

<mruby/array.h> にあったそれっぽい ARY_LEN(RARRAY()) を組み合わせたらたまたま動きました. この2つは C のマクロでキャストしていて型保証もないのでうさんくさいのですが動きました. だから、もしこれをみた mruby の関係者はまともな解説を目に付く場所に書いてほしいです.

あとは慣れなのでしょうが C->mruby, mruby->C での API の名称も解説が不十分かつ関数名が推測しづらいのはいい印象ではありません.

mruby の組み込み後

今の所、 mruby->C の method は引数1個、型は整数か上記のuint8_tにいれるような配列だけです. これだけわかればあとは登録した C の関数を rubyスクリプトで組むことになります.

ここに達すると C で考えるべき型やタグやマクロや固定配列長やstaticとか面倒なことを考えなくてよくなり急にスピードアップを感じられます. (たぶん string.h にある memcpy, memcmp, memset がいらなくなるのが大きい) そして先程まで不満だった mruby の API ドキュメントの雑さも忘れられてしまいます. *1

こうなると Squirrel の言語との比較が多くなりますが、書き慣れている ruby script のほうが簡単にやれることが多いのでさらに楽になれます. 例としては下記です.

  • g++, clang++ でリンクしなくて良い
  • &=, <<=, |= 演算子が使える
  • Array.push だけなく Array << が使える. shift が使えるのもうれしい.
  • hash がある
  • File の扱いが楽で blob とかがない

今のところでの不満はエラーメッセージが標準ででてこないので自前でAPIからとってくることです.

もうちょっと mruby での調べ事なり雑なエラーチェックをまともにして満足できたならグルー言語として mruby を使うのは結構いい気がしています.

*1:だからといってそれでやってみようとしたのに諦めた無数の無名の挑戦者がいるのは忘れないでほしいです

アーケード用ストリートファイターIIの日本語文字列

https://dev.upergrafx.com/sf2message/
気になったので ROM image から抜き出しました. いくつか仕様をかきます.

  • 位置、色、改行の制御は抜きました.
  • 改行=文字列の終端です.
  • ----xxxx----, #x はわたしが独自にいれました.
  • 濁音/半濁音記号とかな文字は分離しているのはそのままの表記です.
  • 文字化けしていたら文字コードUTF-8 にしてください.
  • 斜めの矢印は Unicode の絵文字です.

Street Figher II - World Warrior

https://dev.upergrafx.com/sf2message/sf2ww_message.txt

  • ---- advice ---- コンティニュー画面の文字列で, #x はキャラ別です.
    • キャラ別に12個 (10個?) の文字列がありますが重複する文字列は省略しました.
    • #4 はなにもないのは #0 と全部同じで省略されたからです.
    • 解析が不十分で [xxxx] などの制御記号をうまく処理できませんでした.
  • ---- message ---- は試合後のセリフとエンディングが一緒になっています

Street Figher II' - Champion Edition

https://dev.upergrafx.com/sf2message/sf2ce_message.txt

  • ---- advice ---- は同じです.
  • タ"メー[8891], [890a]ヒ"エト大統領 などの表記はそこに文字データがありませんでした. おそらく未使用文字列で重要ではないことだとおもいます.
  • フ"レスレっト, ナっシュはゲーム画面でもその表記です.
  • 追加キャラのエンディングの文字列は別に増えました.

Street Figher II' Turbo - Hyper Fighting

  • すべての文字列が前バージョンと同じなので省略します.
  • バルログのバックステップはコマンドが変わったので、該当文字列は未使用になったはずです(未確認)

Super Street Fighter II - The New Challengers

https://dev.upergrafx.com/sf2message/supersf2_message.txt

  • 濁音/半濁音文字が登録されました.
  • 変換元は通常版でトーナメント版ではありません.
  • ---- advice ---- はキャラ別のデータ管理が別になったのでこちらでは表記していません.
  • すべてのキャラで試合後のセリフとエンディングが別管理になりました. キャラ別の分類用データは見つけていません.

Super Street Fighter IIX - Grand Master Challenge

https://dev.upergrafx.com/sf2message/x_message.txt
https://dev.upergrafx.com/sf2message/super_x_messege_diff.txt (差分)
大半が前作と同じなので差分をみたほうが便利だと思われます.

開発後記

Super SF2 で大量の漢字が登録されたので Google Drive -> 同 Docs で OCR にかけてみましたが、中国語にない漢字(売、与、写など多数)の識別がほとんどできていなくて、日本語認識モードがほしいと思いました.

Hyper SF2 や他の機種の解析はやりません.

ICE40 の spi programmer と jtag debugger その4

d2xx と libftdi1 の共有

d2xx のほう.

static int x_ft_write(void *tt, uint8_t *data, int length)
{
	struct d2xx *t = tt;
	DWORD rc;
	FT_STATUS s = FT_Write(t->h, data, length, &rc);
	assert(s == FT_OK);
	return rc;
}

static int x_ft_read(void *tt, uint8_t *data, int length)
{
	struct d2xx *t = tt;
	DWORD rc;
	FT_STATUS s = FT_Read(t->h, data, length, &rc);
	assert(s == FT_OK);
	return rc;
}

static void x_ft_close(void *tt)
{
	struct d2xx *t = tt;
	FT_STATUS s = FT_SetBitMode(t->h, 0, FT_BITMODE_RESET);
	assert(s == FT_OK);
	FT_Close(t->h);
}

static const struct mpsse_driver d2xx_driver = {
	x_ft_write, x_ft_read, x_ft_close
};

libftdi1 のほう.

static int mou_write(void *tt, uint8_t *data, int length)
{
	struct ftdi1 *t = tt;
	return ftdi_write_data(&t->mpsse_ftdic, data, length);
}

static int mou_read(void *tt, uint8_t *data, int length)
{
	struct ftdi1 *t = tt;
	return ftdi_read_data(&t->mpsse_ftdic, data, length);
}

static void mou_close(void *tt)
{
	struct ftdi1 *t = tt;
	if(t->mpsse_ftdic_open){
		if(t->mpsse_ftdic_latency_set){
			ftdi_set_latency_timer(&t->mpsse_ftdic, t->mpsse_ftdi_latency);
		}
		ftdi_disable_bitbang(&t->mpsse_ftdic);
		ftdi_usb_close(&t->mpsse_ftdic);
	}
	ftdi_deinit(&t->mpsse_ftdic);
}

static const struct mpsse_driver ftdi1_driver = {
	mou_write, mou_read, mou_close
};

こんな感じにクラスもどきを作って, 切り替える.

これの対応自体は大したことがないが C 故に void * を使うのでエラーチェックが雑で原因究明に困るとか、グローバル変数をローカル変数にかえる(必須ではないがやりたくなる)のが手間. void * は &data と書くべきところを data としていたので動かない系統のミスが多すぎるし予防できない.

flash_wait() の修正

verbose の都合なのか status register を待ち時間をいれて 2 度確認する仕様になってたので書き直した.

static void flash_wait(struct mpsse *t, const useconds_t first, const useconds_t every)
{
	if(verbose){
		fprintf(stderr, "waiting..");
	}

	useconds_t waitus = first;
	uint8_t data[2];
	do{
		usleep(waitus);
		waitus = every;
		data[0] = FC_RSR1;
		data[1] = 0;
		flash_xfer(t, data, sizeof(data));
	}while(data[1] & 1);

	if(verbose){
		fprintf(stderr, "R\n");
		fflush(stderr);
	}
}

戻り値 void, 引数 void の関数とか while(1) がやたらと多いソースはもしかするとダメなんじゃと予感がするがほぼ的中する.

flash_read() の分離

他に flash_read_status とかがあったので prefix を flash_read_data に改名. flash_read() のなかで [CS=L, コマンド発行, data 取得, CS=H ]の流れを毎回やっていたので flash_read_data_start() で [CS=L, コマンド発行], flash_read_data_continue() で [data 取得] に分離して、ループの内部での無駄な手続きを削除.

妙に長い待機時間を削減

ループの外とはいえ 200ms 待つ処理がコピペされてたので技術文書と確認. iceprog も C_RESET_B = 0 の期間にすべて転送していているので、規定時間というのが場当たり的対応になっている.
それでも 200 ms は明らかに長いのでこちらも場当たり的に削減.

根本的には時間が明言されている C_RESET_B = 0 をして SS = 0 で C_RESET_B = 1 にして slave mode に入ることが求められる. これの実装はソフトとハードも後回し.

総括

これらの対応で前回 6.9 秒かかった処理が 4.2 秒にまで縮んだ. これでとりあえず満足したので本来の目的に戻る.

ICE40 の spi programmer と jtag debugger その3

Radiant Programmer の処理速度がとても遅い理由

実際に動くまでに約10秒かかる理由

FTDI 提供の libmpsse を自分でプログラムを書いてみたら、libmpsee の関数を呼ぶだけで3秒もかかった. それらを3つぐらい呼んでいることが原因かもしれない.

FT_SetLatencyTimer() の値を小さくするとそれが軽減できる傾向があるようだ.

program に約16秒かかる

flash memory address:0 を program するまえに chip erase をしているらしい. 他のセクタにいれていたデータもまるまる消えていることが判明. 利用している flash memory (4M bytes) の chip erase time は typ で 10 秒なので時間がかかるのは当然. 0x20000 bytes にも満たない data に chip erase (全領域初期化) は気が狂ってる.

address 0 ではないデータは sector erase かけてるのかも気になっている.

とにかく Radiant Programmer の品質はひどいもので捨てることにした.

opensource 開発環境についてる iceprog を利用してみたが遅かった

https://github.com/YosysHQ/icestorm/tree/master/iceprog

簡単なソースなのでとりあえずコンパイルした -> FT232H を認識しない -> zadig で FTDI ドライバから WinUSB 系ドライバに書き換え -> 動いたがおそい.

これは初期化は遅くないのだが、0x20000 bytes の data の program と verify で 36 秒もかかったので原因を調べた. 1 番の原因は flash_wait() にあった.

static void flash_wait()
{
	if (verbose)
		fprintf(stderr, "waiting..");

	int count = 0;
	while (1)
	{
		uint8_t data[2] = { FC_RSR1 };

		flash_chip_select();
		mpsse_xfer_spi(data, 2);
		flash_chip_deselect();

		if ((data[1] & 0x01) == 0) {
			if (count < 2) {
				count++;
				if (verbose) {
					fprintf(stderr, "r");
					fflush(stderr);
				}
			} else {
				if (verbose) {
					fprintf(stderr, "R");
					fflush(stderr);
				}
				break;
			}
		} else {
			if (verbose) {
				fprintf(stderr, ".");
				fflush(stderr);
			}
			count = 0;
		}

		usleep(1000);
	}

	if (verbose)
		fprintf(stderr, "\n");

}

どの flash command に対しても usleep(1000); なのに対して使っている flash memory の page programming time は typ で 700 us である. chip erase の場合は 1000 us 単位で polling するのは無駄なのは目に見えている.

直したのがこれ.

static void flash_wait(const useconds_t first, const useconds_t every)
{
	if(verbose){
		fprintf(stderr, "waiting..");
	}

	useconds_t waitus = first;
	int count = 0;
	while(1){
		uint8_t data[2] = {
			FC_RSR1
		};

		flash_chip_select();
		mpsse_xfer_spi(data, 2);
		flash_chip_deselect();

		if((data[1] & 0x01) == 0){
			if(count < 2){
				count++;
				if(verbose){
					fprintf(stderr, "r");
					fflush(stderr);
				}
			}else{
				if(verbose){
					fprintf(stderr, "R");
					fflush(stderr);
				}
				break;
			}
		}else{
			if(verbose){
				fprintf(stderr, ".");
				fflush(stderr);
			}
			count = 0;
		}

		usleep(waitus);
		waitus = every;
	}

	if(verbose){
		fprintf(stderr, "\n");
	}
}

試しに usleep(700) にしたら programming はとても早くなった. そこで待ち時間を引数に変え,1度目は typ の値, 2 度目以降は小刻みに polling するほうがいい.

実際の運用例はこれ. もともとはクッソ長い main() の中にあったが気持ち悪かった(個人の意見です)ので分離.

static void flash_program_main(const struct options *t, FILE *f, const int file_size)
{
	if(t->disable_protect){
		flash_write_enable();
		flash_disable_protection();
	}
	if(!t->dont_erase && t->bulk_erase){
		flash_write_enable();
		flash_bulk_erase();
		//M25P40: typ 4.5 s, max 10 s
		//SST25PF040: typ 0.25 s, max 2 s
		//W25Q32JV: typ typ 10 s, max 50 s
		flash_wait(250 * 1000, 1000 * 1000);
	}
	if(t->dont_erase){
		return;
	}

	fprintf(stderr, "file size: %d\n", file_size);

	int block_size = t->erase_block_size << 10;
	int block_mask = block_size - 1;
	void (*eraser)(int) = NULL;
	useconds_t first, every;
/*
W25Q32JV      |SST25PF040 |M25P40    |
kb, typ [ms], max [ms]|
 4,  45,  400| 4,  40, 150|64, 600, 3000
32, 120, 1600|64,  80, 250
64, 150, 2000|
*/
	switch(t->erase_block_size){
	case 4:
		first = 40;
		every = 5;
		eraser = flash_4kB_sector_erase;
		break;
	case 32:
		first = 100;
		every = 10;
		eraser = flash_32kB_sector_erase;
		break;
	case 64:
		first = 80;
		every = 50;
		eraser = flash_64kB_sector_erase;
		break;
	}
	first *= 1000;
	every *= 1000;
	fprintf(stderr, "programming..\n");
	fflush(stderr);

	uint8_t allff[0x100];
	memset(allff, 0xff, sizeof(allff));
	
	for(int file_offset = 0, rc; file_offset < file_size; file_offset += rc){
		const int flash_address = t->rw_offset + file_offset;
		fprintf(stderr, "\r0x%05x", flash_address);
		fflush(stderr);

		if(!t->bulk_erase && (flash_address & ~block_mask) == flash_address){
			if(verbose){
				fprintf(stderr, "Status after block erase:\n");
				flash_read_status();
			}
			flash_write_enable();
			(*eraser)(flash_address);
			flash_wait(first, every);
		}
		
		uint8_t buffer[0x100];
		int page_size = 0x100 - (flash_address) % 0x100;
		rc = fread(buffer, 1, page_size, f);
		assert(page_size == rc);
		assert(rc > 0);

		if(memcmp(allff, buffer, page_size) == 0){
			continue;
		}
		flash_write_enable();
		flash_program(flash_address, buffer, rc);

		//M25P40: typ 0.8 ms, max 10 ms
		//SST25PF040: typ 4 ms, max 5 ms
		//W25Q32JV: typ 0.7 ms, max 3 ms
		flash_wait(500, 100);
	}

	/* seek to the beginning for second pass */
	fseek(f, 0, SEEK_SET);
}

flash_wait() の引数にはコマンド別に手元にある 3 デバイスのdatasheetをみて無難な時間をいれたがコマンドラインの引数にするなり、デバイスIDから引いてくるということも将来的にはしたほうがいい.

もとの処理は erase (複数) -> program だったが、 program のループで該当アドレスが入るときだけ erase をかける処理に変更した. 入力ファイル末尾に合わせて自動で小さい erase をすることも考えていたが、 flash device によって erase command が2種類や1種類だったのでそれはなくした.

さらに page size の data の中で全部が 0xff で埋まっていたら program の命令を送る必要はないので飛ばす処理を追加. これでかなりの時間節約ができる.

verify/read も何故か 256 bytes*1 単位で取りに行ってたので 0x200 bytes にしたら処理時間が短くなった, さらに 0x800 bytes 単位にしたらそれはできなかったので 0x400 bytes 単位にした.

これらの対応で約36秒の処理時間が約6秒になった.

iceprog を libftdi1 から D2XX ライブラリに変更する

Radiant Programmer は捨てられるが、 Radiant 内の Riveal Analyzer は FTDI のドライバがいる. iceprog の libftdi1 関数を D2XX 関数に書き換えてみて、ドライバの変更なしに動かせるか試した.

libftdi1 が D2XX を参考に作っているようなので似た関数名に書き換えて引数をあわせたらそれで動いた.

例. この関数は iceprog.c から読んでなかったので関数属性に static をつけて mpsse.h の宣言からは消した.

static void mpsse_send_data(uint8_t *data, int length)
{
	//int rc = ftdi_write_data(&mpsse_ftdic, data, length);
	DWORD rc;
	FT_STATUS s = FT_Write(mpsse_ftdic, data, length, &rc);
	assert(s == FT_OK);
	if(rc != length){
		fprintf(stderr, "Write error (%s, rc=%d, expected %d).\n", __FUNCTION__, rc, 1);
		mpsse_error(2);
	}
}

後日どちらのライブラリからでも使えるように変更する. device open などの初期化は勝手がかなり違うので、これはよく考えて作る必要がある.

あと.... ほかの terminal ならいいかもしれないが msys2 は printf してもすぐにでないので時間のかかる処理の手前に fflush(stdout); をいれる必要がありこれが結構面倒くさい.

1つ言えるのは Altera の programmer はわりと優秀だったのでこういうことに時間を取られることがなかったので、 ICE が敬遠される理由がわかってきた.

*1:256 とか 4096 とか直書きするのは意味のある数値に見えないので 0x100 としてほしい

mantisbt のID が 7 桁で多すぎる

core/bug_api.php: 2180

function bug_format_id( $p_bug_id ) {
	#$t_padding = config_get( 'display_bug_padding' );
	$t_padding = 3;
	$t_string = sprintf( '%0' . (int)$t_padding . 'd', $p_bug_id );
	return event_signal( 'EVENT_DISPLAY_BUG_ID', $t_string, array( $p_bug_id ) );
}

設定は3にしているはずなのになぜか読まれてない. (int) ってかいてあるから t_padding は3(数値)ではなく'3'(数字) の気がするが自分は php の言語仕様を知らないし、php も文句いえばいいのに.

ICE40 の spi programmer と jtag debugger その2

昨日書いた回路で SS が双方向で共有信号でないので tristate buffer は経由せず AD4 = SS 直結としました. その回路を組んだところちゃんと動きませんでした.

理由は Radiant Programmer が出す波形が CRESET_B を L レベルのまま FT232H から SPI を操作していました. Lattice の Technical Note も守ってないし、起動前の謎の待ち時間、ちゃんと回路を組んだ時間の浪費などでうんざりしています.

CRESET_B = L のままなら ICE40 側は tristate を維持するようですので、動作としては納得しましたが、Lattice のソフト担当者への信用は減りました.

とりあえずは SLAVE = ~(CRESET_B == 0 && C_DONE == 0) = CRESET_B | C_DONE とすれば想定通り動くはずです.

ICE40 の spi programmer と jtag debugger その1

UPduino を昨年少し触っていてある程度使い勝手がわかったのですべて自分で設計した基板を動かしはじめました. いまのところは programming の部分で停滞しているのでその先の動作にはまだ至りません.

FT232H と ADbus

UPduino では FT232H を利用していましたので、秋月電子の AE-FT232HL を利用します. FT232H からは AD7:0 を ICE40 につなぎます.

ADbus と SPI flash

programmer は CRESET_B を制御し, ICE40 を停止し SPI で flash memory に書き込むことができます. 配線は下記となります.

AD
0 - SCK
1 - SDI
2 - SDO
3 - (未使用)
4 - SS
5 - (未使用)
6 - CDONE
7 - CRESET-B

ICE40 は restart 後に回路データを取りに行くために SPI で flash memory に読み込みます. SPI のピン番号は ICE40 から指定されているのでユーザーが自由に決められません. ユーザーの回路データを読み込んだあとは SPI のピンは自由に IO として利用できますが、flash memory の誤動作を防ぐために SS (CS) は完全に自由には利用できません.

ADbus と JTAG

ICE40 では内部ロジックアナライザ(reveral)を利用し JTAG にて通信ができます. ADbus の配線は下記となります.

AD
0 - TCK
1 - TDI
2 - TDO
3 - TMS
4 から 7 - 未使用

これらはユーザーの回路データを読み込んだあとに有効になります. JTAG のピンはユーザーが自由に決められます. 他種ではあまりみられない気がします.
内部ロジックアナライザは任意のもので、他の計測手段があり限界までピン数を使いたいとなればなしにできます.

SPI と JTAG の同居 (簡易版)

ここからが問題となります. この似たような2種類のピンで重複する xCK, xDI, xDO を同じピン番号に割り振ると1つの FT232H で両方利用できます. できますが、ユーザー回路で SPI flash memory が全く使えなくなります.

ADbus から SPI と JTAG の分離/結合

現在停滞している部分です. 1つの FT232H で自動に切り替える回路を模索中です. これらの回路は汎用ロジックを組み合わせるのが現状マシな状態です. GAL などの簡易PLDは開発環境が古すぎて動作が安定しないとか、供給側がまともなサポートをしておらずたぶん10年前のほうが便利でいまは逆に技術が退化している気がしています.

話がそれてしまいました. 3つのモードの切り替えは今の所下記を考えています.

  • CDONE == 1 なら JTAG, ユーザー回路での flash 制御
  • CDONE == 0 で CRESET_B の立ち上がり検出
    • AD4 == 1 なら ICE から startup の flash 読み込み
    • AD4 == 0 なら FT232H から flash の書き込みほか各種制御

FT232H から SPI bus を触る場合は下記とします.

  • AD0 -> SCK, AD1 -> SDI, AD4 -> SS は tristate buffer で出力を制御
  • AD2 <- SDO は selector で入力を選択

FT232H から JTAG を触る場合は下記とします.

  • AD0 -> TCK, AD1 -> TDI は共有だが制御ゲートなしでいいと思う(まだ未確認)
  • AD2 <- TDO は selector で入力を選択
  • AD3 -> TMS は共有はないので直結

立ち上がり検出は register (74HC74)を注文中でそれが届き次第ブレッドボードに差し込んで確認しますが、 tristate buffer (74HC244) と selector (74HC157) は配線済みでそれら経由での SPI bus の制御はできています.
ただ 74HC series を Vcc = 3.3V で動作させてるので伝搬遅延が無視できなくなり TCK Divider Setting >= 2 で動作します.

このような状態で標準的なプログラマがないので自身がプログラマを開発しているのが現状です. 専用基板には 74HC series は使いませんけど、本来の回路の開発にできないのは楽しくありません. なぜだが知りませんが Radiant Programmer は PC から操作をして 10 秒から 15 秒の待機時間があるのも楽しくありません.