P/ECE研究室〜S1C33分室



* Tue Aug 06 21:50:00 JST 2002 Naoyuki Sawa




フラッシュメモリの話題・最終回です。

前回、フラッシュメモリの消去・書き込みの実験を行いました。今回は、消去・書き込みの手順を再確認します。




P/ECEのフラッシュメモリ「SST39VF400A」は、三種類の消去モードを持っています。 ・セクタ消去モード  (  4キロバイト単位で消去します) ・ブロック消去モード ( 64キロバイト単位で消去します) ・チップ消去モード  (フラッシュメモリ全体を消去します) 三種類の消去モードのうち、P/ECEカーネルが実際に使っているのは、「セクタ消去モード」だけです。 「セクタ」単位は、pceFileReadSct()APIの引数や、P/ECEコミュニケータの残りサイズ表示などでお馴染みだと思います。 一番細かい単位で消去できるので使い勝手がよく、P/ECEのシステムプログラムがこのモードを選択した理由も納得できます。 ところが実は4KB単位の消去ができるフラッシュメモリは非常に稀で、事実上SST社の製品でしか使えない消去単位だそうです。 富士通社の製品やシャープ社の製品を調べてみると、確かに4KB単位の消去モードを持っていません。 一般的なフラッシュメモリの消去単位は、64キロバイト。 P/ECEカーネルは4キロバイト単位の消去モードに深く依存しているので、他社のフラッシュメモリへの換装はかなり難しいそうです。 SST39VF400Aでは、「ブロック消去モード」が一般的な64キロバイト単位の消去となっています。 [情報源]みゃん☆みゃん☆ふぁくとりぃさんのきまぐれ雑文 2002/01/05      六波羅さんのスペースさんのめっちゃ幸せFLASHメモリ4倍増計画 ※セクタやブロックの用語定義は、各社の資料ごとにまちまちみたいです。  富士通社では1セクタ=64KB。シャープ社では1ブロック=64KB。SST社では1セクタ=4KB・1ブロック=64KBとなっています。 恐怖の全消去「チップ消去モード」は、問答無用でフラッシュメモリ全体を消去します。 P/ECEラボラトリさんの瀕死のP/ECEを救え復旧プログラムがこの消去モードを使っていますが、特殊ケースです。 ちゃんと動いているP/ECEにチップ消去モードを適用すると、逆に瀕死のP/ECEになってしまうので、要注意です。 P/ECEのフラッシュメモリ「SST39VF400A」は、単純な一種類の書き込みモードしか持っていません。 ・ワード書き込みモード (2バイト単位で書き込みます) 2バイト単位と書きましたが、実際には前回実験した通り、同じアドレスに対して何度も書き込みを行うことができます。 いったん0にしたビットを1に戻すことはできませんが、1のビットを0にすることはいつでもできます。 そう考えれば、ビット書き込みモード言っても良さそうです。 他社のフラッシュメモリは、高速な連続書き込みや、時間のかかる消去処理を途中で中断する機能を備えているものが多いようです。 起動プログラムなどの入った重要な領域を保護し、このような領域への消去・書き込みを禁止する機能を備えている製品もあります。 しかし、P/ECEのフラッシュメモリ「SST39VF400A」は、そのような高度な機能を一切備えていません。 一見、欠点のようにも見えますが、僕のようなフラッシュメモリ入門者には覚えることが少なくってかえってありがたいです。 高度な機能がない代りに、扱いやすいセクタ消去モードを持っている点など、SST39VF400Aは単純で小回りの効く製品のようです。
それでは、消去・書き込みの具体的な手順を再確認します。 今回も、フラッシュメモリ全体をハーフワードの配列と見なして説明します。 unsigned short FMEM[256 * 1024]; /* 2バイト×256K = 512Kバイト */ まずは消去。データシートどおりなので、一気に行きます。 各消去モードの手順の前半は、セクタ消去モードもブロック消去モードもチップ消去モードも共通です。 FMEM[0x5555] に 0xaa を書き込む FMEM[0x2aaa] に 0x55 を書き込む FMEM[0x5555] に 0x80 を書き込む FMEM[0x5555] に 0xaa を書き込む FMEM[0x2aaa] に 0x55 を書き込む 最後の手順だけが異なります。 セクタ消去モードの場合:  消去したいセクタの任意のワードに 0x30 を書き込む  例えば 0xc70000〜0xc70fff のセクタを消去するには、0xc70000に0x30を書き込みます。  0xc70002や0xc70004や...0xc70ffeも同じセクタに属するので、これらのアドレスへの書き込みでも構いません。 ブロック消去モードの場合:  消去したいブロックの任意のワードに 0x50 を書き込む  例えば 0xc70000〜0xc7ffff のブロックを消去するには、0xc70000に0x50を書き込みます。  0xc70002や0xc70004や...0xc7fffeも同じブロックに属するので、これらのアドレスへの書き込みでも構いません。 チップ消去モードの場合:  FMEM[0x5555] に 0x10 を書き込む 消去モードの手順の途中で、別のフラッシュメモリアドレスへの読み書きが発生すると、手順がキャンセルされてしまします。 だから、ソフトウェアIDモードやCFIクエリーモードの時と同じように、全ての割り込みを禁止しておかなければいけません。 消去を行うプログラム自身がフラッシュメモリ上にあると、プログラムの読み出しによって手順がキャンセルされてしまうので、 消去を行うプログラムはCPU内蔵RAMか、SRAM上に置いておく必要があります。 P/ECEカーネルでは、フラッシュメモリ上にある消去プログラムを、いったんフラッシュメモリからCPU内蔵RAMにコピーし、 コピー先のプログラムを呼び出してフラッシュメモリの消去を行っています。
問題はここからです。 フラッシュメモリの消去には、少し時間がかかります。 フラッシュメモリの消去が完了するまで、フラッシュメモリからデータを読み出すことはできなくなっています。 読み出せないのは、いま消去を行ったセクタ(またはブロック)だけではありません。 あるセクタの消去が完了するまでは、別のセクタからの読み出し・消去・書き込みもできません。 当然、あるセクタの消去が完了する前に、同じセクタへの書き込みを開始することもできません。 つまり、フラッシュメモリの消去が完了するまでは、何もせずに待つ必要があります。 それでは、いつまで待てばいいのでしょうか。 一つ目の方法として、消去に要する最大時間、空ループを回して待つ、という方法があります。 消去に要する時間は、データシートに記載されています。 また、CFIクエリーモードを使って、消去に要する時間を実行時に取得することもできます。 SST39VF400Aの場合、セクタまたはブロック消去に要する時間は32ミリ秒、チップ全体なら128ミリ秒、となっています。 意外と長い時間が必要なのですね。 規定の時間だけ空ループを回して待つというのは一番単純な方法ではありますが、いざ作ってみると結構手間がかかります。 フラッシュメモリの消去を行っている間、フラッシュメモリ上にあるシステムプログラム(API)は使えないからです。 pceTimerGetCount()やpceTimerGetPrecisionCount()、pceTimerAdjustPrecisionCount()を使って時間を計ることができません。 (そもそも消去中はNMIを止めているので、仮にこれらのAPIを使えたとしても、タイマがカウントアップされていません) 時間を計るには、8ビットタイマや16ビットタイマ、あるいはリアルタイムクロックの値を直接読み出すしかありません。 別の方法として、フラッシュメモリの消去完了通知を利用する、という方法があります。 SST39VF400Aには、「Toggle Bit(DQ6)」「Data# Polling(DQ7)」という、二種類の消去完了通知があります。 「Toggle Bit(DQ6)」とは、 消去が完了するまでの間、フラッシュメモリからデータを読み出す度に、読み出した値のビット6が反転しつづける というものです。ビット6が反転しなくなったら、消去が完了したことになります。 「Data# Polling(DQ7)」とは、 消去が完了するまでの間、フラッシュメモリから読み出したデータのビット7は、最終的な値のビット7を反転した値になっている というものです。ビット7が最終値と同じになったら、消去が完了したことになります。 ちなみに、消去が完了するまでの間の、ビット6と7以外の値は規定されていません。 実験したところ、消去が完了するまでの間は、ビット6と7以外は0として読み出されるようですが、不定と考えた方がいいでしょう。 消去したメモリは0xffffになりますので、最終値は0xffffです。 ビット6は読み出す度に反転し続け(1→0→1→0→...)、ビット7は最終値を反転した値(0)ですので、二進数で表すと次のようになります。 ビット番号:fedcba9876543210       ----------------       ????????01?????? ←消去開始       ????????00??????       ????????01??????       ????????00??????       ????????01??????       ????????00??????         (中略)         ????????01??????       ????????00??????       1111111111111111 ←「Data# Polling(DQ7)」による消去完了判定       1111111111111111 ←「Toggle Bit(DQ6)」による消去完了判定 「Toggle Bit(DQ6)」による判定のほうが、「Data# Polling(DQ7)」よりも読み出し一回分だけ遅れることに注目してください。 「Toggle Bit(DQ6)」による判定条件は、“ビット6が反転しなくなること”ですので、 変化しなくなった値すなわち最終値を二回連続で読み出してから、やっと消去完了が判定できます。 対して「Data# Polling(DQ7)」は、“ビット7が期待する最終値になったか”調べるだけなので、すぐに判定できます。 それに、ビット7が最終値の逆ということは、ワード単位で見ても最終値とは異なっているはずなので、 単にワード単位で期待する最終値と比較し、一致したら消去完了と見なすことができます。 どう考えても「Data# Polling(DQ7)」による判定の方が簡単で、これだけで充分に思えます。
ではなぜ「Toggle Bit(DQ6)」による判定方法も用意されているのでしょうか? あくまで想像ですが、フラッシュメモリの一部が壊れた場合への対策ではないかと推測します。 例えば、0xc70000〜0xc70001のハーフワードのビット6と7に相当するセルが壊れ、消去できなくなった(0固定)と仮定します。 0xc70000〜0xc70fffのセクタを消去し、0xc70000を読み出して消去完了判定をしようとすると、 ビット番号:fedcba9876543210       ----------------       ????????01?????? ←消去開始       ????????00??????  ※消去中のビット6と7の値はフラッシュメモリ内のコントローラが返していると思うので、       ????????01??????   セルが壊れていても期待通りの値を返すと推測。       ????????00??????       ????????01??????       ????????00??????         (中略)         ????????01??????       ????????00??????       1111111100111111 ←消去完了したが、ビット6と7が壊れて1に戻らない       1111111100111111 ←「Toggle Bit(DQ6)」による消去完了判定は可能 「Data# Polling(DQ7)」で消去完了判定を行ったのでは、永遠に終わりません。 対して「Toggle Bit(DQ6)」で消去完了判定を行った場合は、ビット6の値が期待する最終値と異なっていても、 ビット6が反転しなくなることに変わりはないので、消去完了を判定することはできます。 消去完了判定後に、期待する値と比較して同じになっているかどうかを調べれば、フラッシュメモリが壊れているか判定できます。 フラッシュメモリは数万回程度の書き換えで壊れてしまうそうなので、このような対策も必要なのだと思います。 ところが! 実際に試したところ、「Toggle Bit(DQ6)」は期待通りの動作をせず、ビット6がぜんぜん反転していないようなのです。 検証プログラムを作ってみました。 セクタ消去開始後、そのセクタ内の特定ワードを連続で読み出し、SRAMに格納しておいて、消去完了後にダンプ表示します。 ソースはこちら
ごらんの通り、消去中の読み出し値のビット6が、ぜんぜん反転していません。 タイミングの問題かと思って、連続読み出しのタイミングを変えてみても、一向に反転する様子がありませんでした。 なぜデータシートどおりの動作をしないのか、まったく原因不明です。(僕がデータシートを読み誤っているだけかも知れませんが…) 「Toggle Bit(DQ6)」を正しく使う方法をご存知のかたがおられましたら、ぜひ教えてください。よろしくお願いいたします。 実はP/ECEカーネルのソースにも、完了判定の方法を「Toggle Bit(DQ6)」から「Data# Polling(DQ7)」に変更した形跡があります。 (c:\usr\PIECE\sysdev\pcekn\fmacc2.c 53行目。これは書き込み完了判定ですが、後述するように、書き込みも消去も完了判定の方法は同じです。) たぶん最初は、フラッシュメモリが壊れていても確実に完了判定できる「Toggle Bit(DQ6)」を採用する予定だったのに、 「Toggle Bit(DQ6)」がうまく動かないので、「Data# Polling(DQ7)」に変更したのではないかと推測します。 もっとも現在のP/ECEカーネルでは、フラッシュメモリの壊れた部分を回避するような仕組みは用意されていません。 フラッシュメモリのたった1ビットでも壊れてしまえば、もうおしまいです。 どうせおしまいなのだから、完了検出できずに無限ループに陥ってしまっても同じといえば同じなのかも知れません。
次に書き込み手順。こちらもデータシートどおりです。 前述のとおり、SST39VF400Aは単純なワード書き込みモードを一種類しか備えていないので、説明も簡単です。 FMEM[0x5555] に 0xaa を書き込む FMEM[0x2aaa] に 0x55 を書き込む FMEM[0x5555] に 0xa0 を書き込む 書き込みたいアドレスにワード値を書き込む フラッシュメモリの消去と同じく、書き込み処理にも少し時間がかかります。 SST39VF400Aでは、1ワードの書き込み完了に、最大32マイクロ秒を要します。 消去の場合と同様に、規定時間だけ空ループを回して待つか、または「Data# Polling(DQ7)」の方法を使って、書き込み完了を待つ必要があります。 空ループは時間計測が面倒ですし、「Toggle Bit(DQ6)」はやはりうまく動かないので、「Data# Polling(DQ7)」の方法を使うことになりそうです。 消去モードでの最終値は0xffffでしたので、消去したセクタの任意のアドレスのデータが0xffffになるまで待ちました。 対して書き込みモードでの最終値は書き込んだ値ですので、書き込んだアドレスから実際に書き込んだ値が読み出せるようになるまで待ちます。 それ以外の点については消去モードと全く同じですので、消去モードの説明をご参照ください。
消去・書き込み手順の再確認は以上です。 前回の最後に、「0のビットに対する1の書き込みのふるまいも、もう少し調査してみようと思います」と書きましたが、残念ながら進展はありません。 今回使ったサンプルプログラムのと同様の方法で、読み出し値のログをダンプしてみようと思ったのですが、なぜかハングアップしてしまいます。 挙動が予測できないので、「Toggle Bit(DQ6)」や「Data# Polling(DQ7)」による完了待ちではなく、空ループで完了待ちしてみたのですが、 数秒間もの空ループを行っても書き込みが完了していないらしく、空ループから抜けたところでハングアップしてしまうようなのです。 書き込みモードが完了しない限り、フラッシュメモリ上のシステムプログラムは使えませんので、 読み出しログをダンプ表示するには、システムプログラムと同様の処理を行うルーチンをすべてSRAM上に用意しなければいけません。 さすがに手間がかかりすぎるので、今回は調査を断念しました。 フラッシュメモリの話題は、今回でおしまいです。 そして、長らくお送りしてまいりました「P/ECE研究室〜S1C33分室」も、今回でひと区切りです。 ハワーダウン制御など、まだまだ面白そうな機能が残っていますので、またいずれS1C33分室も再開することになりそうです。 次回より「P/ECE研究室〜USB分室」にて、P/ECEのUSBコントローラPDIUSBD12の使い方を見て行こうと思います。 オリジナルカーネル作成のための最重要項目は、フラッシュメモリとUSBコントローラ。 フラッシュメモリに関してはある程度理解できましたので、あとはUSBコントローラが理解できれば・・・! ちょっとだけ、オリジナルカーネルが現実味を帯びてきたように思います。 (「P/ECE研究室〜USB分室」に続きます)

nsawa@piece-me.org