Sharp MZ-80A ビデオモジュール — デベロッパーズガイド
Video Module 開発者ガイド
このガイドは、Sharp MZ-80A Video Module のハードウェアおよび HDL 設計の詳細なウォークスルーです。HDL 言語に不慣れな開発者向けに VHDL の概念を説明し、設計内のすべてのソースファイルをウォークスルーし、CPLD と FPGA のアーキテクチャを詳細に解説し、ビットストリームのビルド方法、CG-ROM イメージの管理方法、新しいビデオモードの追加方法、新しいハードウェア用に CPLD インターフェイスを変更する方法を示します。
Video Module には 2 つの世代があります。ディスクリートハードウェア設計(v1.0 と v1.1)は PCB 上の 74 シリーズ TTL および CMOS IC を使用します。これらのバージョンは主に
schematics/ ディレクトリの KiCad 回路図で文書化されています。FPGA/CPLD 設計(v2.0)は Cyclone III FPGA と MAX 7000A CPLD でほとんどのディスクリートロジックを置き換え、このガイドで詳しく説明されています。
HDL 開発者でない方のための VHDL 入門
v2.0 Video Module のファームウェア全体は VHDL(VHSIC Hardware Description Language)で書かれています。これはプログラマブルロジックデバイスの内部構造と動作を記述するための言語です。C やアセンブリ言語とは異なり、プロセッサが一度に 1 つずつ実行する命令のシーケンスを記述しますが、VHDL はハードウェアを記述します:すべて同時に動作するワイヤ、レジスター、ロジックゲートです。この区別は根本的で、VHDL の書き方と読み方のすべての側面に影響します。
ENTITY と ARCHITECTURE
すべての VHDL 設計ユニットは ENTITY と ARCHITECTURE の 2 つの部分で構成されています。
ENTITY はインターフェイスを宣言します — 外部世界が接続する入力、出力、双方向ピンのセット。これは関数シグネチャまたはモジュールヘッダーに似ています。ARCHITECTURE は内部動作を記述します — 入力ピンと出力ピンの間で何が起こるか。これは関数本体に似ていますが、その中のすべてが順次ではなく並行して実行される点が異なります。
LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
ENTITY mux2to1 IS
PORT (
sel : IN std_logic;
a, b : IN std_logic;
y : OUT std_logic
);
END ENTITY;
ARCHITECTURE rtl OF mux2to1 IS
BEGIN
-- 並行信号割り当て:y は sel に基づいて継続的に駆動されます。
-- これは 1 回実行される文ではありません — 永続的なワイヤ接続です。
y <= a WHEN sel = '0' ELSE b;
END ARCHITECTURE;
上記の
シグナルと型
WHEN/ELSE 割り当ては 2:1 マルチプレクサーを記述しています。合成ツールはこれをシリコン内の実際のゲートに変換します。クロックも、ループも、シーケンスもありません — 出力 y は伝播遅延だけで sel を継続的に追います。
std_logic は IEEE VHDL の主要なシグナル型です。単一のワイヤをモデル化し、以下の値を取れます:
| 値 | 意味 |
|---|---|
'0' |
ロジック低 |
'1' |
ロジック高 |
'Z' |
高インピーダンス(トライステート — ワイヤが切断されている) |
'-' |
ドントケア(合成ツールが最適化のためにどちらかの値を選択できる) |
'U' |
未初期化(シミュレーションのみ — シグナルが一度も駆動されていない場合に表示) |
std_logic_vector は std_logic ビットの配列でバスに使用されます。例えば
PROCESS と感度リスト
SIGNAL addr : std_logic_vector(15 DOWNTO 0) は 16 ビットアドレスバスを宣言します。個々のビットは addr(15)(MSB)から addr(0)(LSB)でアクセスします。スライスは下位バイトに対して addr(7 DOWNTO 0) と書きます。
SIGNAL は名前付きの内部ワイヤまたはレジスターを宣言します。ARCHITECTURE 内のシグナル割り当て(sig <= expr;)は永続的な接続を記述します。割り当てが PROCESS 内にある場合はレジスターを記述します(値はトリガーするクロックエッジでラッチされます)。PROCESS 外(並行領域)にある場合は純粋な組み合わせロジックを記述します。
PROCESS ブロックはシグナルのセット(感度リスト)の変化に反応するロジックを記述します。感度リストのシグナルが変化するたびに、プロセス本体が(概念的に)実行されます。合成では、感度リストはロジックがクロックされるか、ゲートされるかを決定します。
同期(クロック)プロセスは
rising_edge(clk) を使用して D フリップフロップを記述します — すべての順次デジタルロジックの基本的な構成要素:
PROCESS (clk, reset)
BEGIN
IF reset = '1' THEN
-- 非同期リセット:リセットがアサートされるとクロックに関係なく
-- q はすぐに '0' になります。
q <= '0';
ELSIF rising_edge(clk) THEN
-- 同期キャプチャ:q は各立ち上がりクロックエッジで d の値を取ります。
q <= d;
END IF;
END PROCESS;
Video Module のすべての順次ロジックはこのパターンに従います。組み合わせロジック(アドレスデコード、ピクセル選択、色ルックアップ)は並行シグナル割り当て、またはクロックエッジテストのないプロセスで記述されます。
COMPONENT インスタンス化と PACKAGE
COMPONENT は大きな設計内で使用されるサブ設計です — ライブラリ関数を呼び出すことに似ています。COMPONENT 宣言はインターフェイスを指定し、インスタンス化はそれをローカルシグナルに配線します。Video Module はこのメカニズムを広く使用しています:FPGA のトップレベル(
VideoController_Toplevel.vhd)はメインコントローラー、PLL IP コア、すべての周辺ブロックをコンポーネントとしてインスタンス化します。
PACKAGE は型定義、定数、コンポーネント宣言の共有コレクションです。CPLD と FPGA の設計には独自のパッケージファイル(VideoInterface_pkg.vhd と VideoController_pkg.vhd)があり、USE work.VideoController_pkg.ALL; を使用して設計内の他のすべてのファイルにインポートされます。
ソースツリー
| パス | 内容 |
|---|---|
CPLD/ |
CPLD VHDL ソースファイル |
CPLD/VideoInterface.vhd |
CPLD メインロジック — 電圧変換、クロック分周、バス制御 |
CPLD/VideoInterface_Toplevel.vhd |
CPLD トップレベルエンティティ(I/O ピン宣言付き) |
CPLD/VideoInterface_pkg.vhd |
CPLD パッケージ — 型、定数、コンポーネント宣言 |
CPLD/build/ |
CPLD 用 Quartus Prime 13.0.1 プロジェクト |
CPLD/build/VideoInterface.qpf |
Quartus プロジェクトファイル |
CPLD/build/VideoInterface.qsf |
Quartus 設定ファイル — デバイス選択、ピン割り当て、コンパイルオプション |
CPLD/build/VideoInterface.csv |
ピン割り当てドキュメント |
CPLD/build/VideoInterface_constraints.sdc |
タイミング制約 |
CPLD/build/output_files/ |
コンパイル済みビットストリーム(.jic、.sof、.pof) |
FPGA/ |
FPGA VHDL ソースファイル |
FPGA/VideoController.vhd |
FPGA メインコントローラー — すべてのレジスターアクセス、ビデオタイミング、表示パイプライン、GPU |
FPGA/VideoController_Toplevel.vhd |
FPGA トップレベルエンティティ — 144 I/O ピン宣言と PLL インスタンス化 |
FPGA/VideoController_pkg.vhd |
FPGA パッケージ — レジスターアドレス、モードコード、色エンコーディング、タイミングレコード |
FPGA/functions.vhd |
メインコントローラーで使用される組み合わせヘルパー関数 |
FPGA/devices/ps2/ |
PS/2 キーボードインターフェイス IP ブロック |
FPGA/devices/BRAM/ |
ブロック RAM ラッパー IP ブロック |
FPGA/devices/uart/ |
UART IP ブロック |
FPGA/devices/spi/ |
SPI コントローラー IP ブロック |
FPGA/devices/timer/ |
タイマーモジュール IP ブロック |
FPGA/devices/intr/ |
割り込みコントローラー IP ブロック |
FPGA/devices/RAM/ |
RAM モジュール |
FPGA/devices/SDRAM/ |
SDRAM コントローラー IP ブロック |
FPGA/devices/SDMMC/ |
SD カード(SDMMC)コントローラー IP ブロック |
FPGA/devices/ioctl/ |
I/O 制御 IP ブロック |
FPGA/build/ |
FPGA 用 Quartus Prime 13.1 プロジェクト |
FPGA/build/VideoController.qpf |
Quartus プロジェクトファイル |
FPGA/build/VideoController.qsf |
Quartus 設定ファイル |
FPGA/build/VideoController.csv |
ピン割り当てドキュメント |
FPGA/build/VideoController_constraints.sdc |
タイミング制約 |
FPGA/build/output_files/ |
コンパイル済みビットストリーム(.sof、.jic) |
FPGA/build/core/ |
Quartus 生成 PLL IP コア(Video_Clock_*.vhd) |
FPGA/build/simulation/ |
シミュレーションテストベンチ |
schematics/v1.0/ |
v1.0 ディスクリートハードウェア設計の KiCad 回路図と PDF |
schematics/v1.1/ |
v1.1 ディスクリートハードウェア設計の KiCad 回路図と PDF |
schematics/v2.0/ |
v2.0 FPGA/CPLD 設計の KiCad 回路図と PDF |
software/tools/make_cgrom.sh |
CG-ROM イメージビルダースクリプト |
software/roms/ |
ソース ROM イメージ(mz-80acg.rom、MZ80K_cgrom.rom など) |
software/mif/ |
FPGA BRAM 用メモリ初期化ファイル |
CPLD 設計:VideoInterface.vhd
CPLD の役割
CPLD は Altera MAX 7000A デバイス(EPM7128S、128 マクロセル、84 ピン PLCC)です。5V の MZ-80A システムバスと 3.3V の FPGA の電気的な境界を占めています。5V 信号を 3.3V FPGA 入力に直接接続するとデバイスが時間の経過とともに損傷し、ロジックレベルエラーを引き起こします。CPLD の 5V トレラント I/O バッファが MZ-80A バス信号を安全に吸収します。
CPLD は 3 つの主要な機能を実行します:
クロック分周器
- 電圧レベル変換: MZ-80A バスから到着するすべての信号(アドレスバス A0–A15、データバス D0–D7、制御信号 MREQ、IORQ、RD、WR、BUSACK)は CPLD の 5V トレラント入力で受け取られ、FPGA のために 3.3V で再駆動されます。
- クロック生成: MZ-80A マスター発振器は 17.7341MHz(ゲートアレイから派生した MZ-80B ピクセルクロック)で動作します。CPLD はこのクロックを分周して 40 列モードに必要な 8.867MHz ピクセルクロックを生成します。80 列クロックはマスタークロックから直接供給されます。
- バス制御: CPLD は Z80 のアドレスと制御信号をデコードして、FPGA の内部メモリマップされた領域のチップイネーブル、読み取り、書き込み、アドレス選択信号を生成し、Z80 の非同期バスプロトコルを FPGA が確実にサンプリングできる同期信号に変換します。
VideoInterface.vhd のクロック分周器はシンプルなトグルフリップフロップです。マスタークロックが 50% デューティサイクルを持つため、トグルによる 2 分周は正確な 50% デューティサイクル出力を生成します — これは正しいピクセルタイミングにとって重要です。プロセスは次のとおりです:
-- 17.7341MHz の MZ-80A ピクセルクロックを 2 分周して
-- 40 列用の 8.867MHz ピクセルクロックを生成します。
PROCESS (MZ_CLK)
BEGIN
IF rising_edge(MZ_CLK) THEN
CLK_DIV <= NOT CLK_DIV;
END IF;
END PROCESS;
-- ピクセルクロック出力を駆動:40 列は分周クロックを使用し、80 列はマスターを使用します。
PIXEL_CLK <= CLK_DIV WHEN MODE_80COL = '0' ELSE MZ_CLK;
MODE_80COL シグナルは、アクティブな表示モードが 80 列を必要とする場合に FPGA からフィードバックピンを通じてアサートされます。CPLD は適切なクロックを FPGA の専用クロック入力にルーティングします。
Z80 バスはアクティブロー(負論理)の別々の MREQ(メモリリクエスト)と IORQ(I/O リクエスト)ストローブを使用します。CPLD はこれらをアドレスバスと組み合わせてデコードし、FPGA の内部メモリマップされた領域の選択シグナルを生成します:
| 領域 | アドレス範囲 | シグナル | 用途 |
|---|---|---|---|
| VRAM | 0xD000–0xD7FF | CS_VRAM | 文字ビデオ RAM(2KB) |
| ARAM | 0xD800–0xDFFF | CS_ARAM | 属性 RAM(2KB) |
| I/O レジスター | 0xD0–0xFF(IORQ) | CS_IO | ビデオコントローラーレジスター |
アドレスデコードは組み合わせ(クロックなし)で、CPLD パスの待ち時間を最小化します。生成されたチップセレクトシグナルは CPLD ではなく FPGA 内部で FPGA クロックに同期され、CPLD パスに余分な待ち時間を導入しません。
データバスの方向は CPLD の出力イネーブルロジックで制御されます。Z80 が読み取りを実行する(RD がアサートされ、MREQ または IORQ がアサートされる)とき、FPGA は CPLD の双方向バッファを通じてデータバスを駆動します。Z80 が書き込む場合、バッファは逆方向に駆動されます。CPLD はバス有効ウィンドウ外で方向変更が発生することを保証し、バスの競合を防ぎます。
CPLD ビットストリームのビルド
CPLD は完全な MAX 7000 サポートを持つ最後のバージョンである Quartus Prime 13.0.1 が必要です。GUI からのビルドプロセス:
- Quartus Prime 13.0.1 で
CPLD/build/VideoInterface.qpfを開きます。 - ターゲットデバイスが EPM7128S(またはお使いのボードに適切な MAX 7000 バリアント)に設定されていることを確認します。Assignments → Device で確認します。
- コンパイルを開始します:Processing → Start Compilation。MAX 7000 フィッターは通常 30 秒以内に完了します。
- コンパイルレポートを確認します。フィッターセクションで
VideoInterface_constraints.sdcのすべてのタイミング制約が満たされていること(タイミング違反がないこと)を確認します。 - 出力ファイルは
CPLD/build/output_files/に書き込まれます:VideoInterface.pof— ByteBlaster プログラミング用VideoInterface.jic— USB-Blaster JTAG プログラミング用
- プログラムするには:Tools → Programmer。.pof または .jic ファイルを追加し、USB-Blaster インターフェイスを選択して Start をクリックします。
FPGA 設計:VideoController.vhd
全体アーキテクチャ
FPGA は Altera Cyclone III EP3C25E144C8(25,000 ロジック要素、144 ピン LQFP)です。完全なビデオコントローラー — 文字表示、グラフィクス表示、GPU、レジスターインターフェイス、PS/2 キーボード、SD カード、UART、SDRAM コントローラー — を単一のデバイスに実装します。
設計は抽象化の層に直接対応する 4 つのソースファイルに分割されています:
PLL とクロックドメイン
- VideoController_Toplevel.vhd: トップレベルエンティティ。すべての 144 個の I/O ピンをその方向と IOSTANDARD 属性とともに宣言します。PLL IP コアとメインの
VideoControllerエンティティをコンポーネントとしてインスタンス化し、物理ピンシグナルを内部設計シグナルに配線します。 - VideoController_pkg.vhd: 共有パッケージ。すべての定数定義(レジスターアドレス、モードコード、色エンコーディング)、型定義(ビデオタイミングパラメーターレコード、モードテーブル)、すべての周辺 IP ブロックのコンポーネント宣言が含まれています。FPGA 設計内の他のすべてのファイルは
USE work.VideoController_pkg.ALL;でこのパッケージをインポートします。 - VideoController.vhd: メインアーキテクチャ。すべてのレジスターアクセスプロセス、ビデオタイミングジェネレーター、VRAM/GRAM マルチプレクサー、文字とグラフィクスの表示パイプライン、GPU ステートマシン、割り込みロジックがここに記述されています。
- functions.vhd: メインコントローラーで使用される組み合わせヘルパー関数のパッケージ — 主に割り込み調停の優先エンコーダーと表示パイプラインで使用されるビット操作ユーティリティ。
FPGA/devices/ の周辺 IP ブロックは標準の Altera 互換 VHDL モジュールです。VideoController_pkg.vhd で定義された内部バスシグナルを通じてメインコントローラーと相互作用します。
FPGA は、サポートされる各ビデオモードに必要なピクセルクロックを合成するために、
FPGA/build/core/ にある Quartus 生成 PLL(Phase-Locked Loop)IP コアを使用します。PLL IP コアは Quartus の MegaWizard プラグインマネージャーで生成され、VHDL ファイル(Video_Clock_*.vhd)として出力されます。CPLD からの 17.7MHz リファレンスクロックを取り込んで乗算/分周し、以下を生成します:
| ビデオモード | ピクセルクロック | ソース |
|---|---|---|
| ネイティブ MZ-80A 40 列 | 8.867MHz | CPLD で分周、FPGA にルーティング |
| ネイティブ MZ-80A 80 列 | 17.734MHz | CPLD からのマスタークロック |
| VGA 640×480 | 25.175MHz | PLL 出力 |
| VGA 800×600 | 40.000MHz | PLL 出力 |
| VGA 1024×768 | 65.000MHz | PLL 出力 |
アクティブなピクセルクロックは現在のモードレジスター値で制御されるクロックマルチプレクサーで選択されます。ビデオモードを変更すると制御レジスター 0xF8 に新しい値が書き込まれ、モード選択ロジックがピクセルクロックソースを切り替えます。ピクセルクロックの下流にあるすべての同期ロジックは、メタスタビリティを防ぐためにクロック切り替え中はリセット状態に保たれる必要があります。
FPGA は複数のクロックドメインで動作します:Z80 バスインターフェイスドメイン(CPLD 供給クロックで駆動)、ピクセルクロックドメイン、SDRAM コントローラードメイン(タイミングコンプライアンスのために異なる周波数で動作することが多い)、SD カード SPI クロックドメイン。クロックドメインを横断するすべての信号はメタスタビリティの確率を減らすためにダブルレジスタリングされます。ダブルレジスタ同期器は
レジスターアクセスプロセス
FPGA/devices/ ライブラリからインスタンス化されます。
MZ-80A の Z80 は 0xD0–0xFF の範囲の I/O ポートを通じてビデオコントローラーと通信します。
VideoController.vhd のレジスターアクセスプロセスは、CPLD が CS_IO と Z80 の RD または WR ストローブをともにアサートするたびにトリガーされます。Z80 バスクロックでクロックされる同期プロセスです:
PROCESS (clk, reset)
BEGIN
IF reset = '1' THEN
-- すべての制御レジスターをデフォルト(電源投入時)状態にリセット。
MODE_REG <= (OTHERS => '0');
COLOUR_REG <= (OTHERS => '0');
GPU_CMD <= (OTHERS => '0');
ELSIF rising_edge(clk) THEN
IF CS_IO = '1' AND WR_n = '0' THEN
-- Z80 が I/O ポートへ書き込み。
CASE addr(7 DOWNTO 0) IS
WHEN x"F8" => MODE_REG <= data_in; -- マシンモデル / 表示モード
WHEN x"F9" => COLOUR_REG <= data_in; -- 前景/背景色
WHEN x"FA" => GPU_CMD <= data_in; -- GPU コマンドトリガー
-- ... 追加レジスター
WHEN OTHERS => NULL;
END CASE;
END IF;
IF CS_IO = '1' AND RD_n = '0' THEN
-- Z80 が I/O ポートから読み取り:要求されたレジスターを data_out に置く。
CASE addr(7 DOWNTO 0) IS
WHEN x"F8" => data_out <= MODE_REG;
WHEN x"FB" => data_out <= GPU_STATUS; -- GPU ビジーフラグがビット 7 に
WHEN OTHERS => data_out <= (OTHERS => '0');
END CASE;
END IF;
END IF;
END PROCESS;
レジスターアドレスは
ビデオタイミングジェネレーター
VideoController_pkg.vhd の定数として定義されています。レジスターマップを変更する場合(例えば新しい制御レジスターを追加する場合)、まずパッケージに定数を追加し、次にこのプロセスの対応する CASE エントリーを追加します。
ビデオタイミングジェネレーターは、モニターが必要とする水平同期(H_SYNC)、垂直同期(V_SYNC)、アクティブ表示イネーブル(DE)信号を生成します。2 つのカウンター — 水平ピクセルカウンターと垂直ラインカウンター — で構成され、各ピクセルクロックでインクリメントされます。
各モードのタイミングパラメーター(H_TOTAL、H_DSP_START、H_DSP_END、H_SYNC_START、H_SYNC_END、V_TOTAL、V_DSP_START、V_DSP_END、V_SYNC_START、V_SYNC_END)は、コンパイル時に初期化されアクティブなモードコードでインデックスされるモードパラメーター RAM に格納されています。これにより、タイミングジェネレーター自体の大きな CASE 文が不要になります — カウンターは常に同じように動作し、比較値のみが変わります。
-- 水平カウンター:各ピクセルクロックでインクリメント。
-- H_TOTAL(ブランキングを含む 1 ライン当たりの総ピクセル数)でラップします。
PROCESS (pixel_clk)
BEGIN
IF rising_edge(pixel_clk) THEN
IF h_count = H_TOTAL - 1 THEN
h_count <= (OTHERS => '0');
-- 各ライン終端で垂直カウンターをインクリメント。
IF v_count = V_TOTAL - 1 THEN
v_count <= (OTHERS => '0');
ELSE
v_count <= v_count + 1;
END IF;
ELSE
h_count <= h_count + 1;
END IF;
END IF;
END PROCESS;
-- アクティブ表示イネーブル:表示ウィンドウ内のみ高。
DE <= '1' WHEN (h_count >= H_DSP_START AND h_count < H_DSP_END)
AND (v_count >= V_DSP_START AND v_count < V_DSP_END)
ELSE '0';
-- 同期パルス(標準 VGA ではアクティブロー)。
H_SYNC <= '0' WHEN h_count >= H_SYNC_START AND h_count < H_SYNC_END ELSE '1';
V_SYNC <= '0' WHEN v_count >= V_SYNC_START AND v_count < V_SYNC_END ELSE '1';
H_SYNC と V_SYNC の極性(アクティブハイまたはアクティブロー)もモードパラメーターに格納されており、出力ステージで '1' との XOR として適用されます。VGA モードはアクティブロー同期を使用しますが、ネイティブの MZ-80A モードも異なるタイミング関係ながらアクティブローを使用します。
文字表示パイプライン
文字表示パイプラインは VRAM の文字コードをリアルタイムでピクセルカラーに変換します。1 クロックサイクルあたり 1 ピクセルです。パイプラインには 4 つのステージがあり、それぞれが 1 つのピクセルクロック期間内に完了する必要があります:
ステージ 1 — VRAM フェッチ: 水平カウンターと垂直カウンターを使用して現在の文字の VRAM アドレスを計算します。80 列表示の場合:
vram_addr = (v_count / 8) * 80 + (h_count / 8)。そのアドレスの文字コードが BRAM から読み取られます(1 クロック待ち時間)。
ステージ 2 — CGRAM フェッチ: VRAM からの文字コードと垂直カウンターの 3 つの下位ビット(文字内の行)を組み合わせて CGRAM アドレスを形成します:cg_addr = char_code * 8 + (v_count MOD 8)。その文字グリフの行の 8 ピクセルビットが CGRAM BRAM から読み取られます(1 クロック待ち時間)。同時に現在の文字の属性バイトが ARAM から読み取られます。
ステージ 3 — 色デコード: 属性バイトは前景と背景の色をその下位ビットにエンコードしています(正確なエンコーディングは VideoController_pkg.vhd の定数で定義されています)。ルックアップテーブルが 3 ビットまたは 4 ビットの色コードを RGB 出力値にマッピングします。
ステージ 4 — ピクセルシフト: 水平カウンターの 3 つの下位ビットが 8 ピクセルビットのどれがこのクロックサイクルでアクティブかを選択します。選択されたビットが '1' の場合、前景色を出力し、そうでなければ背景色を出力します。
-- ステージ 4:現在のピクセルビットを選択して前景色または背景色を選択。
-- cg_data はステージ 2 で CGRAM からフェッチされた 8 ビットピクセル行。
-- h_count(2 DOWNTO 0) は文字内のピクセル(0=最も左、7=最も右)。
pixel_bit <= cg_data(7 - to_integer(unsigned(h_count(2 DOWNTO 0))));
-- RGB 出力レジスターを駆動。
PROCESS (pixel_clk)
BEGIN
IF rising_edge(pixel_clk) THEN
IF DE = '1' THEN
IF pixel_bit = '1' THEN
R_OUT <= FG_RED;
G_OUT <= FG_GREEN;
B_OUT <= FG_BLUE;
ELSE
R_OUT <= BG_RED;
G_OUT <= BG_GREEN;
B_OUT <= BG_BLUE;
END IF;
ELSE
-- アクティブ表示外:黒を駆動。
R_OUT <= (OTHERS => '0');
G_OUT <= (OTHERS => '0');
B_OUT <= (OTHERS => '0');
END IF;
END IF;
END PROCESS;
BRAM 読み取り待ち時間は、VRAM と CGRAM のフェッチを実際にピクセルが必要とされる 1 つまたは 2 つのサイクル前に開始する必要があることを意味します。パイプラインはレジスター化された中間シグナルと慎重にカウントされたパイプライン遅延を使用し、BRAM IP ブロックのインスタンス化で定義された BRAM 待ち時間パラメーターに一致させます。
グラフィクス表示パイプライン
グラフィクス表示パイプラインは文字パイプラインと並行して動作します。FPGA は 3 プレーンフレームバッファ(赤、緑、青の GRAM バンク)を実装し、各プレーンはピクセルごとに 1 ビット深度で、8 色(黒から白、そしてすべての原色と二次色)を与えます。各プレーンは別々の BRAM に格納されています。
GRAM アドレスは水平カウンターと垂直カウンターから直接計算されます:
gram_addr = v_count * (H_PIXELS / 8) + (h_count / 8)。各 GRAM バンクからフェッチされたバイトには 8 つの水平ピクセルが含まれ、バイト内のピクセルは文字パイプラインと同様に h_count(2 DOWNTO 0) で選択されます。
3 つの 1 ビットピクセル値(3 つの GRAM バンクからの R、G、B)はブレンドオペレーターを使用して文字パイプラインの出力と結合されます。ブレンドモードはカラーレジスター(0xF9)のビットで設定され、次のものになれます:
| モード | 効果 |
|---|---|
| OR | グラフィクスピクセル OR 文字ピクセル — グラフィクスが文字をオーバーレイできる |
| AND | グラフィクスピクセル AND 文字ピクセル — 色を表示するには両方がセットされている必要がある |
| NAND | 反転 AND — グラフィクスがセットされている場所で文字ピクセルがマスクアウトされる |
| XOR | 排他的 OR — グラフィクスと文字が重なる領域で交互になる |
ブレンド操作はカラープレーンごとに適用されます。ブレンドされたピクセルが 3 つのプレーンすべてで '0' の場合、代わりに属性 RAM からの背景色が使用され、文字の背景色がグラフィクスの透明な領域を透過して見えるようになります。
GPU ステートマシン
GPU(グラフィクス処理ユニット)は、Z80 CPU がピクセルを個別に書き込むよりも速く、VRAM と GRAM の一括操作(フィル、ブロックコピー、線描画)を加速できるシンプルなコマンドプロセッサーです。4 つのコマンドを実行します:
| コマンド | コード | 操作 |
|---|---|---|
| VRAM クリア | 0x01 | VRAM 全体をスペース文字(0x20)で埋める |
| VRAM 矩形フィル | 0x02 | VRAM の矩形領域を指定した文字コードで埋める |
| GRAM クリア | 0x04 | GRAM プレーン全体を 0x00(黒)で埋める |
| GRAM 矩形フィル | 0x08 | GRAM の矩形領域を指定した色で埋める |
| リセット / アイドル | 0xFF | 現在の操作を中止して即座にアイドルに戻る |
Z80 はターゲットアドレス、幅、高さ、フィル値を GPU パラメーターレジスターへの連続した書き込みを通じて 128 ビットパラメーター FIFO に書き込み、次にコマンドコードをレジスター 0xFA に書き込みます。これにより GPU_CMD シグナルが設定されステートマシンがトリガーされます。
ステートマシンは IDLE、FETCH_PARAMS、EXECUTE、DONE というステートを持つ典型的な FSM(有限状態機械)です。EXECUTE ステートではターゲットアドレス範囲を反復して、対応する BRAM に 1 クロックサイクルごとに 1 バイトを書き込みます。FPGA は BRAM への直接の内部アクセスを持っているため(Z80 バスを介さない)、フルピクセルクロック速度で書き込みができます — Z80 よりも何桁も速いです。
GPU がアクティブ(IDLE でない)の間、レジスター 0xFB(GPU_STATUS)のビット 7 が '1' として読み取られます。次の GPU コマンドを発行したり VRAM/GRAM に直接アクセスしたりする前に、ソフトウェアはこのビットをポーリングしてクリアになるのを待つ必要があります。GPU の実行中に VRAM にアクセスすると予測不可能な結果が生じます — BRAM 調停ロジックが GPU に優先権を与えます。
FPGA ビットストリームのビルド
FPGA は Quartus Prime 13.1 が必要です(Cyclone III のサポートは後のバージョンで削除されました)。完全なコンパイルはホストマシンによって 10〜20 分かかります。
- Quartus Prime 13.1 で
FPGA/build/VideoController.qpfを開きます。 - ターゲットデバイスが EP3C25E144C8(Cyclone III、25K LE、144 ピン LQFP)であることを確認します。Assignments → Device で確認します。
- コンパイルを開始します:Processing → Start Compilation。これにより分析と合成、フィッティング、アセンブラー、TimeQuest タイミング分析が順番に実行されます。
- コンパイルが完了したら、TimeQuest タイミングアナライザーのレポートを確認します。すべてのタイミングパスはポジティブなスラックを持っている必要があります。ネガティブなスラック(タイミング違反)は設計がターゲットクロック周波数でタイミング制約を確実に満たせないことを意味します — これは機能的なバグであり、単なる警告ではありません。
- 出力ファイルは
FPGA/build/output_files/にあります:VideoController.sof— JTAG プログラミング(一時的、電源サイクルで失われる)VideoController.jic— Flash ストアドプログラミング(電源サイクルをまたいで保持)
- USB-Blaster 経由でプログラムするには:Tools → Programmer。恒久的なインストールには、オンボードシリアル Flash をターゲットにする JTAG チェーンで .jic ファイルを使用します。
Docker ビルド環境
Quartus Prime 13.0.1 も 13.1 も現在の Linux ディストリビューションにクリーンにインストールされません(glibc バージョンの不一致、32 ビットライブラリの欠如)。固定の Ubuntu 14.04 または Ubuntu 16.04 ベースを持つ Docker コンテナが再現可能なビルド環境を提供します。以下の例は、対応する Quartus インストールを持つ
CPLD ビルド(Quartus 13.0.1)
quartus:13.0.1 と quartus:13.1 という名前の Docker イメージを想定しています。
# GUI アクセスのための X11 フォワーディングと USB-Blaster パススルーでコンテナを起動。 docker run --rm -it \ -e DISPLAY=$DISPLAY \ -v /tmp/.X11-unix:/tmp/.X11-unix \ -v /dvlp/Projects/MZ80A_80COLOUR:/project \ --device /dev/bus/usb \ quartus:13.0.1 bash # コンテナ内 — コマンドラインコンパイル(GUI 不要): cd /project/CPLD/build quartus_sh --flow compile VideoInterface # JTAG 経由で CPLD をプログラム(チェーン位置 1 の USB-Blaster): quartus_pgm -m JTAG -o "P;output_files/VideoInterface.pof@1"FPGA ビルド(Quartus 13.1)
# Quartus 13.1 コンテナを起動: docker run --rm -it \ -e DISPLAY=$DISPLAY \ -v /tmp/.X11-unix:/tmp/.X11-unix \ -v /dvlp/Projects/MZ80A_80COLOUR:/project \ --device /dev/bus/usb \ quartus:13.1 bash # コンテナ内 — フル FPGA コンパイル: cd /project/FPGA/build quartus_sh --flow compile VideoController # 一時的な .sof(JTAG、Flash ではない)で FPGA をプログラム: quartus_pgm -m JTAG -o "P;output_files/VideoController.sof@1" # 恒久的な .jic でオンボード Flash をプログラム: quartus_pgm -m JTAG -o "pvi;output_files/VideoController.jic"USB-Blaster udev ルール
ホスト(Docker 内ではなく)で、root 権限なしに USB-Blaster にアクセスできるよう次の udev ルールを追加します:
# /etc/udev/rules.d/51-usb-blaster.rules
SUBSYSTEM=="usb", ATTRS{idVendor}=="09fb", ATTRS{idProduct}=="6001", MODE="0666"
ルールを追加したら
sudo udevadm control --reload-rules && sudo udevadm trigger でリロードし、USB-Blaster を切断して再接続します。
--device /dev/bus/usb で Docker を実行すると、USB バス全体がコンテナにパススルーされます。システムに複数の USB バスがある場合は代わりに --privileged を使用するか、USB-Blaster の特定のバス/デバイス番号を識別してそのデバイスだけをパスします。
CG-ROM 管理
CG-ROM(キャラクタージェネレーター ROM)は MZ-80A 文字セット内のすべての 256 文字のピクセルパターンを定義します。Video Module は最大 16 個の CG-ROM イメージを 32KB Flash RAM に格納します(各イメージは 2KB)。アクティブな CG-ROM はレジスターへの書き込みで実行時に選択されます。
CG-ROM イメージのビルド
cd /dvlp/Projects/MZ80A_80COLOUR/software ./tools/make_cgrom.sh # 出力:roms/COLOURBOARD_CG.rom (正確に 32,768 バイト)
スクリプトは 16 × 2KB スロットテーブルを構築して以下の ROM イメージを順番に連結します:
| スロット | ファイル | 説明 |
|---|---|---|
| 0 | mz-80acg.rom |
標準 MZ-80A キャラクタージェネレーター |
| 1 | MZ80K_cgrom.rom |
MZ-80K キャラクタージェネレーター |
| 2 | MZ80K2E_Jap_cgrom.rom |
MZ-80K2E 日本語文字セット |
| 3 | MZFONT.rom |
代替 MZ フォント |
| 4–5 | MZ700_cgrom.rom |
MZ-700 キャラクタージェネレーター(4KB = 2 スロット) |
| 6–7 | MZ700_cgrom_jp.rom |
MZ-700 日本語キャラクタージェネレーター(4KB = 2 スロット) |
| 8–15 | (フィル) | 32,768 バイトにパディングするために未入力スロットを埋める |
- 2KB バイナリを
software/roms/に説明的なファイル名で配置します。 software/tools/make_cgrom.shを編集します。スロットイメージを順番に組み立てる連結コマンド(順番にスロットイメージを組み立てるcatの呼び出し)を見つけ、目的のスロット位置に新しいファイル名を挿入します。各ファイルは正確に 2KB(2,048 バイト)でなければなりません。挿入前にwc -cで確認します。- 既存のスロットを置き換える場合は、新しいレイアウトを反映してスクリプトのコメントドキュメントのスロット番号を調整します。
./tools/make_cgrom.shで再ビルドし、出力が正確に 32,768 バイトであることを確認します:wc -c roms/COLOURBOARD_CG.romは 32768 を返す必要があります。合計が異なる場合、ソース ROM のサイズが間違っています — Flash をプログラムする前に調査してください。- 32KB イメージを Video Module PCB 上の CG-ROM Flash チップにプログラムします。
FPGA 設計では、アクティブな CG-ROM コンテンツは CGRAM を通じてアクセス可能です — 文字パイプラインがフルクロック速度で読み取る内部 BRAM コピー。Z80 は CPU アドレス空間にページインすることで実行時に CGRAM コンテンツを上書きできます。
メモリページレジスター(I/O ポート 0xFD)のビット 7 を設定すると、CGRAM が CPU アドレス範囲 0xD000–0xDFFF にマッピングされ、VRAM が一時的にオーバーレイされます。Z80 はその範囲に任意の 8×8 ピクセルパターンを直接書き込み、カスタム文字を定義できます。ビット 7 をクリアすると 0xD000–0xDFFF が VRAM に戻ります。これにより、ハードウェアの変更なしにソフトウェア定義の文字セットが可能になります。
実行時に Flash 格納 CG-ROM イメージの 1 つを CGRAM にロードするには、ページイン操作を実行する前に CG-ROM 選択レジスターに目的のスロット番号(0–15)を書き込みます。
CG-ROM の MIF フォーマットへの変換
Quartus BRAM 初期化は MIF(メモリ初期化ファイル)フォーマットを使用します — ヘッダーを持つ 1 行に 1 つの 16 進ワード。テンプレートとして
software/mif/ の既存ファイルを使用します。ヘッダーは深度(ワード数)と幅(ワードあたりのビット)を指定します:
-- 2048 バイトの CG-ROM 用 MIF ファイルヘッダー(2048 ワード × 8 ビット): DEPTH = 2048; WIDTH = 8; ADDRESS_RADIX = HEX; DATA_RADIX = HEX; CONTENT BEGIN 000 : 00; -- 文字 0x00(ヌル)の行 0 001 : 00; -- 行 1 ... 7FF : 00; -- 最後のバイト END;
MIF ファイルは FPGA シミュレーションのみに使用されます(
FPGA/build/simulation/ のテストベンチ)。実際のハードウェアは電源投入時に外部 Flash RAM から CG-ROM コンテンツをロードします。
新しいビデオモードの追加
Video Module は制御レジスター 0xF8 経由で実行時に選択可能な複数の表示モードをサポートします。新しいモードを追加するには、パッケージ、メインコントローラー、場合によっては PLL IP コアにわたる一貫した変更が必要です。以下の手順を順番に実行してください:
ステップ 1 — モード定数とタイミングレコードの定義
FPGA/VideoController_pkg.vhd を開きます。モードコード定数を含むセクションで、新しい定数を追加します:
-- VideoController_pkg.vhd に追加: CONSTANT MODE_1280x1024 : std_logic_vector(3 DOWNTO 0) := x"8"; -- 新しいモードのタイミングパラメーターレコードを追加します。 -- (既存のモードエントリーに倣って H_TOTAL、V_TOTAL、H_SYNC タイミングなどを記入)
参考サイト
| リソース | リンク |
|---|---|
| Video Module プロジェクトページ | /sharpmz-upgrades-videomodule/ |
| tranZPUter プロジェクトページ | /sharpmz-upgrades-tranzputer/ |
| Sharp MZ エミュレーター | /sharpmz-emulator/ |
| RFS テクニカルガイド | /sharpmz-upgrades-rfs-technicalguide/ |
| Nibbles Lab Sharp MZ ミュージアム | http://retropc.net/ohishi/museum/mz1200.htm |
| Altera Quartus Prime(レガシー) | https://www.intel.com/content/www/us/en/collections/products/fpga/software/downloads.html |
| Docker ビルドリポジトリ | https://git.eaw.app/eaw/zpu |