アーキテクチャ
CPU
8ビットのCPUです。1フレームにつき1命令を実行します。
レジスタ
汎用レジスタ(8ビット)
| レジスタ | 説明 |
|---|---|
| A | アキュムレータ。演算命令の主要なオペランドです。 |
| B, C | 汎用レジスタ。BCレジスタペアとしても使用します。 |
| D, E | 汎用レジスタ。DEレジスタペアとしても使用します。 |
| H, L | 汎用レジスタ。HLレジスタペアとしてメモリアドレス指定に使用します。 |
特殊レジスタ
| レジスタ | ビット幅 | 説明 |
|---|---|---|
| PC | 16ビット | プログラムカウンタ。次に実行する命令のアドレスを保持します。 |
| SP | 16ビット | スタックポインタ。スタックの先頭アドレスを保持します。 |
| PTBR | 16ビット | ページテーブルベースレジスタ。ページテーブルの先頭物理アドレスを保持します。 |
| IVTBR | 16ビット | 割り込みベクタテーブルベースレジスタ。ベクタテーブルの先頭仮想アドレスを保持します。 |
| PFAR | 16ビット | ページフォルトアドレスレジスタ。ページフォルト発生時のフォルトした仮想アドレスを保持します。読み取り専用です。 |
レジスタペア
2つの8ビットレジスタを組み合わせて16ビットの値として扱います。上位バイトが先のレジスタです。
| ペア名 | 上位 | 下位 |
|---|---|---|
| BC | B | C |
| DE | D | E |
| HL | H | L |
| PSW | A | F(フラグレジスタ) |
メモリ参照(M)
命令中のオペランド「M」は、HLレジスタペアが指すメモリアドレスの内容を意味します。
フラグレジスタ(F)
演算結果に応じて自動的に更新されるフラグの集合です。条件分岐命令で参照されます。
| フラグ | ビット位置 | 説明 |
|---|---|---|
| S(サイン) | 7 | 演算結果のビット7が1のとき1になります。 |
| Z(ゼロ) | 6 | 演算結果が0のとき1になります。 |
| AC(補助キャリー) | 4 | ビット3からビット4へのキャリー(またはボロー)が発生したとき1になります。 |
| P(パリティ) | 2 | 演算結果のビット中の1の個数が偶数のとき1になります。 |
| CY(キャリー) | 0 | 演算結果でキャリー(またはボロー)が発生したとき1になります。 |
フラグレジスタのビット配置
ビット: 7 6 5 4 3 2 1 0
[S] [Z] [0] [AC] [0] [P] [1] [CY]
ビット5と3は常に0、ビット1は常に1です。
メモリ
64KB(65,536バイト)のメモリ空間を持ちます。アドレス範囲は0x0000〜0xFFFFです。
マルチバイトデータはリトルエンディアン(下位バイトが低位アドレス)で格納されます。
起動時の初期状態
起動時、メモリの先頭3バイトには0番地への無限ループが配置されています。
0x0000: 0xC3 (JMP)
0x0001: 0x00 (ジャンプ先 下位バイト)
0x0002: 0x00 (ジャンプ先 上位バイト)
それ以外のアドレスは0x00で初期化されています。
秘密データ
起動時に2バイトの秘密データがメモリ上のランダムな場所に配置されます。
さらに、1行(512バイト)空けた同じ列にその複製が配置されます。
n行目: ・・・ XXXX ・・・
n+1行目: (空白行)
n+2行目: ・・・ XXXX ・・・
配置位置は起動のたびにランダムに変わります。
秘密データの各バイトの内容はメモリエディタ上では「XX」でマスク表示され、編集もできません。
秘密データ(1つ目)の物理アドレスは、メモリエディタのヘッダー行に16進数4桁で表示されます。
スタック
スタックはメモリ上に確保され、SPレジスタが先頭を指します。スタックは下位アドレス方向に成長します。
- PUSH: SPをデクリメントしてからデータを書き込みます。
- POP: データを読み出してからSPをインクリメントします。
MMU(メモリ管理ユニット)
ページング方式の仮想メモリ機構です。有効時、CPUが発行するアドレスは仮想アドレスとして扱われ、ページテーブルを参照して物理アドレスに変換されます。
無効時は、アドレスがそのまま物理アドレスとして扱われます。
ページング仕様
| 項目 | 値 |
|---|---|
| ページサイズ | 256バイト |
| ページ数 | 256ページ |
| ページテーブルエントリ(PTE)サイズ | 2バイト |
| ページテーブルサイズ | 512バイト |
アドレス変換
仮想アドレスは以下のように分解されます。
仮想アドレス(16ビット): 上位8ビット → ページテーブル内インデックス(0〜255) 下位8ビット → ページ内オフセット(0〜255)
変換手順は以下の通りです。
- PTBRの上位8ビットをページテーブルの先頭物理アドレスとします(PTBRは物理アドレスを保持するため、MMUによるアドレス変換は行われません)。
- 先頭アドレス + ページテーブル内インデックス × 2 がPTEのアドレスです。
- PTEの上位8ビットが物理ページの先頭アドレスです。
- 物理ページの先頭アドレス + ページ内オフセット が物理アドレスです。
PTEフラグ
PTEの下位8ビットはフラグ領域です。
| ビット | 名前 | 説明 |
|---|---|---|
| 0 | Valid | 1のとき有効なページです。0のときページフォルトが発生します。 |
| 1 | (予約) | 将来のフラグ用に予約されています。 |
| 2 | U/S | 1のときユーザーページ(ユーザーモードでアクセス可能)です。0のときスーパーバイザーページ(ユーザーモードでアクセスすると例外が発生します)です。 |
| 3〜7 | (予約) | 将来のフラグ用に予約されています。 |
U/Sビットによるアクセス制御
CPUがユーザーモードで動作しているとき、U/Sビットが0のページ(スーパーバイザーページ)へのアクセスは例外を発生させます。
スーパーバイザーモードでは、U/Sビットに関係なくすべてのページにアクセスできます。
権限違反はページフォルトと同じベクタ番号1で処理されます。ハンドラ内でPFARとPTEを確認することで、ページフォルトと権限違反を区別できます。
現在の実装では、例外やIRQの発生時にスーパーバイザーモードに自動的に切り替わりますが、ハンドラからの復帰時(RET命令実行時など)に元のモードを自動的に復元する仕組みはありません。ハンドラ終了後もスーパーバイザーモードのまま実行が継続されます。
ページフォルト
MMU有効時、アドレス変換でPTEのValidビットが0のページにアクセスすると、ページフォルト例外が発生します。
ページフォルトまたはU/S権限違反が発生すると、以下の処理が行われます。
- PFARにフォルトした仮想アドレスを設定します。
- レジスタ・フラグ・SPをフォルト命令の実行前の状態に戻します。
- PCをフォルトした命令の先頭アドレスに戻し、それをスタックにプッシュします。
- 割り込みを禁止します(INTE=0)。
- スーパーバイザーモードに切り替えます。
- ベクタテーブルからハンドラアドレスを取得し、そこへジャンプします。
ページフォルトのベクタ番号は1です。ハンドラの末尾でRET命令を実行すると、フォルトした命令へ戻り、再実行できます。
ページフォルトは割り込み許可フラグ(INTE)の状態に関係なく発生します。
メモリへの書き込み副作用はロールバックされません。レジスタ・フラグ・SPのみが復元されます。
ハンドラ内でPFARの値を読み出すことで、どの仮想アドレスでフォルトが発生したかを知ることができます。PFARの読み出しにはMOV rp, PFAR命令を使用します。
ベクタテーブル
例外発生時にジャンプするハンドラのアドレスを格納するテーブルです。ベース仮想アドレスはIVTBRレジスタで指定します。
各エントリは2バイト(リトルエンディアン)で、ハンドラの16ビット仮想アドレスを格納します。読み出されたハンドラアドレスはPCに設定され、次の命令フェッチ時にMMUで物理アドレスに変換されます。
| ベクタ番号 | アドレス | 用途 |
|---|---|---|
| 0 | IVTBR+0x0000〜IVTBR+0x0001 | 予約 |
| 1 | IVTBR+0x0002〜IVTBR+0x0003 | ページフォルト |
| 2〜7 | IVTBR+0x0004〜IVTBR+0x000F | IRQ #0〜#5(外部割り込み) |
ベクタ番号0は予約、ベクタ番号1はページフォルト専用です。外部割り込み(IRQ)はベクタ番号2〜7を使用します。
ベクタテーブルのベース仮想アドレスはIVTBRレジスタで指定します。初期値は0x0000です。
IVTBRは仮想アドレスを保持するため、MMU有効時はベクタテーブルの読み出しがMMUによるアドレス変換を経由します。MMU無効時はIVTBRの値がそのまま物理アドレスとして使用されます。
MMU有効時にベクタテーブルのページがマッピングされていない場合、ページフォルトが連続して発生(ダブルフォルト)するため、CPUは停止します。
I/Oポート
256個のI/Oポート(0x00〜0xFF)を持ちます。IN命令とOUT命令でアクセスします。
現在の実装では、IN命令は常に0xFFを返し、OUT命令は何もしません。周辺機器との連携は今後追加予定です。
割り込み
割り込みの許可・禁止をEI/DI命令で制御できます。割り込み許可フラグ(INTE)は起動時に0(禁止)です。
ページフォルト等の同期例外はINTEの状態に関係なく発生します。例外発生時はINTEが0に設定されます。
外部割り込み(IRQ)
外部デバイスやUIからIRQを発生させることができます。IRQは6本(IRQ#0〜#5)あり、ベクタ番号2〜7に対応します。保留中のIRQはCPUが受け付け可能な状態(INTE=1)になるまで保持されます。
画面上の外部割り込みボタンをクリック(ボタン上でマウスボタンを離した時)すると、IRQ#0が発生します。操作方法の詳細は操作方法を参照してください。
割り込み受付の流れは以下の通りです。
- INTE=1かつ保留中のIRQがある場合、最小番号のIRQを選択します。
- スタックプッシュ先のアドレスを事前検証します。ページフォルトが発生した場合はIRQ受付をスキップして通常の命令実行フローへ遷移します(IRQは保留のまま残ります)。検証に成功した場合は、以下の手順(3〜6)に進みます。
- 現在のPCをスタックにプッシュします(CALL命令と同じ形式)。
- INTEを0に設定します。
- スーパーバイザーモードに切り替えます。
- ベクタテーブルからハンドラアドレスを取得し、PCに設定します。
ハンドラの末尾でEI命令とRET命令を実行することで、次の割り込みを受け付け可能にしつつ、割り込み前のアドレスに戻ります。
HLT命令でCPUが停止中の場合も、INTE=1かつ保留IRQがあればCPUが復帰し、割り込みを受け付けます。ただし、スタック領域のページフォルトが検出された場合はHLT状態を維持します。
ロウハンマーシミュレーション
DRAMのロウハンマー現象をシミュレートする機能です。特定のメモリ行を高速に繰り返しアクセス(ハンマリング)すると、隣接行のビットが1から0へフリップする現象を再現します。これはTrue Cellモデルに基づいており、メモリセルから電荷が漏洩する方向(1→0)のみフリップが発生します。また、フリップは対象ビットの上下の同じ桁のセルが両方とも0(放電状態)の場合にのみ発生します。
この機能はトグルボタンで有効/無効を切り替えられます。CPU停止中のみ操作可能です。
DRAM行モデル
メモリ空間(64KB)を512バイトごとの「行」に分割してモデル化しています。
| 項目 | 値 |
|---|---|
| 行サイズ | 512バイト(= 2ページ分) |
| 行数 | 128行 |
| 行番号 | アドレスの上位7ビット(0x00〜0x7F) |
アドレス空間 (64KB): 行 0x00: 0x0000 - 0x01FF (512バイト) 行 0x01: 0x0200 - 0x03FF ... 行 0x7E: 0xFC00 - 0xFDFF 行 0x7F: 0xFE00 - 0xFFFF 行番号 = (アドレス >> 9) & 0x7F
ビットフリップの仕組み
メモリの読み書きが行われるたびに、アクセスされた行の隣接行(行番号 - 1 および行番号 + 1)のカウンタが加算されます。実際のDRAMでは行のアクティベーションが隣接行に電気的干渉を与えるため、アクセスされた行自体ではなく隣接行のカウンタを増やします。隣接行のカウンタが閾値に達すると、その行内の対象ビットが1から0へフリップします。ただし、フリップが発生するのは対象ビットが1で、かつその上下の同じ桁のセルが両方とも0の場合のみです。これは、隣接セルが放電状態(0)のときに、対象セルの電荷が漏洩するという実際のDRAMの物理現象を再現しています。
- メモリアクセスが発生すると、アクセス先の隣接行(行番号 - 1 および行番号 + 1)のカウンタを加算します。
- 隣接行のカウンタが閾値(デフォルト: 256回)に達すると、その行のビットフリップが発生します。
- フリップ対象のバイトオフセットとビット位置は、行ごとに複数が予め固定値として決められています。同じ行なら毎回同じビットが対象になります。16進数1桁(4ビット)の中ではフリップしやすいビットは多くて1つです。対象ビットが1で、かつ上下の同じ桁のセルが両方とも0の場合のみクリアされます。これは現実のDRAMにおいて、製造時のばらつきにより行ごとにフリップしやすいビット位置が決まること、およびTrue Cellでは隣接セルが放電状態のときのみ電荷が漏洩することを再現しています。
- フリップ後、カウンタはリセットされます。
行0(0x0000〜0x01FF)をハンマリングした場合は上方向の隣接行が存在しないため行1のカウンタのみが加算され、行0x7F(0xFE00〜0xFFFF)をハンマリングした場合は下方向の隣接行が存在しないため行0x7Eのカウンタのみが加算されます。
メモリの読み取り・書き込みには、アクセスカウンタに加算するかどうかを制御するオプションがあります。メモリエディタの表示更新や編集操作など、UI目的のメモリアクセスはカウンタに加算されません。
ロウハンマーシミュレーションが有効な場合、メモリエディタ上でフリップしやすいビット位置がストライプ表示でハイライトされます。対象ニブル(16進数1桁)の幅を4等分し、フリップ対象のビット位置に対応するストライプを色分けして表示します(左からビット3→ビット0の順、ビット位置ごとに赤・橙・黄緑・緑)。これにより、どのバイトのどのビットがフリップ対象かを視覚的に確認できます。
DRAMリフレッシュ
実際のDRAMと同様に、一定間隔で全行のアクセスカウンタがリセットされます。リフレッシュ間隔内にハンマリングが閾値に達しなければ、ビットフリップは発生しません。
リフレッシュはフレーム単位で行われます。リフレッシュ間隔はハンマリング閾値に対してタイトに設定されており、ループ内で他の処理を行わずに集中的にアクセスしなければビットフリップを発生させることはできません。