SFD-700 mkII Entwicklerhandbuch
SFD-700 mkII Entwicklerhandbuch
Dieser Leitfaden bietet eine gruendliche Durchsicht des Quellcodes der programmierbaren Logik des SFD-700 mkII, mit besonderem Augenmerk auf die Erklaerung von VHDL-Konzepten und -Idiomen fuer Leser, die nicht mit Hardwarebeschreibungssprachen vertraut sind. Jeder Prozess und jede nebenlaefige Signalzuweisung im CPLD-Design wird in einfachem Deutsch erklaert, zusammen mit dem VHDL, das es implementiert.
Fuer Hardwarearchitektur und Registerbelegungen siehe den Technischen Leitfaden. Fuer Installation und Benutzung siehe das Benutzerhandbuch. Um die CPLD- (und die veraltete CUPL/GAL-)Logik zu erstellen, ohne eine Toolchain von Hand einzurichten, verwenden Sie das automatisierte Setup-Skript (
setup_SFD700.sh / setup_SFD700_windows_native.ps1), das unter Automatisiertes Setup und Build im Technischen Leitfaden beschrieben ist.
Einfuehrung in VHDL fuer Nicht-VHDL-Programmierer
VHDL (VHSIC Hardware Description Language, wobei VHSIC fuer Very High Speed Integrated Circuit steht) ist keine Programmiersprache im traditionellen Sinne - sie beschreibt keine Folge von Schritten, die ein Prozessor nacheinander ausfuehrt. Stattdessen beschreibt VHDL Hardwareschaltungen: Logikgatter, Flip-Flops, Multiplexer und deren Verbindungen. Wenn ein VHDL-Design fuer ein CPLD oder FPGA kompiliert (synthetisiert) wird, uebersetzen die Tools Ihre Beschreibung in eine Netzliste tatsaechlicher Gatter und Register, die permanent in den Baustein verdrahtet werden.
Alles laeuft gleichzeitig
Das Wichtigste, was man ueber VHDL verstehen muss, ist, dass alle Prozesse und Signalzuweisungen in einer Architektur nebenlaefig laufen. Im Gegensatz zu einem Programm, wo Zeile 10 nach Zeile 9 ausgefuehrt wird, ist in VHDL jeder Logikblock aktiv und reagiert auf seine Eingaenge zu jeder Zeit - genau wie die reale Hardware, die er beschreibt.
Im SFD-700-CPLD sind der Taktteiler, das Laufwerksregister, der I/O-Dekoder und jeder andere Block alle gleichzeitig aktiv, 24 Stunden am Tag, vom Moment an, in dem das CPLD eingeschaltet wird.
Entities, Architectures und Ports
Ein VHDL-Design hat zwei Teile:
Signale
- Die Entity ist die externe Schnittstelle - sie listet die Ein- und Ausgangspins auf. Betrachten Sie sie als das Pin-Diagramm der Komponente. Im SFD-700 listet die Entity jeden physischen CPLD-Pin auf: Z80_ADDR, Z80_DATA, FDCn, DRQ, MODE, CLK_16M usw.
- Die Architecture ist die interne Implementierung - sie beschreibt die Logik, die die Pins verbindet. Hier leben die Prozesse und Signalzuweisungen.
rtl (Register Transfer Level - ein konventioneller Name, der bedeutet, dass das Design in Bezug auf Datenfluss zwischen Registern beschrieben wird) verwendet.
Signale sind die internen Leitungen des Designs. Sie werden zwischen dem
Prozesse
architecture-Schluesselwort und dem begin-Schluesselwort deklariert. Ein Signal kann ein einzelnes Bit (std_logic), einen Bus von Bits (std_logic_vector) oder eine Ganzzahl tragen. Im SFD-700 sind Signale wie FDC_SELni (die interne Leitung, die anzeigt, dass ein FDC-I/O-Zyklus stattfindet) Signale - sie sind keine externen Pins, sondern nur interne Verbindungen zwischen Logikbloecken.
Das Suffix n in Signalnamen (z.B. FDC_SELni) ist eine Konvention fuer "Active Low" - das Signal ist aktiviert (bedeutsam, aktiv), wenn es logisch Null ('0') ist, und deaktiviert, wenn es logisch Eins ('1') ist.
Ein Prozess ist ein Codeblock, der sequentielle (getaktete) oder kombinatorische Logik beschreibt. Prozesse sind in
Verwendete Datentypen
process(...) begin ... end process eingeschlossen und enthalten die bekannten Konstrukte: if, case, Variablenzuweisungen.
Ein getakteter Prozess beschreibt Flip-Flop-Register - Logik, die einen Wert zu einem bestimmten Zeitpunkt (der Taktflanke) erfasst und bis zur naechsten Taktflanke haelt. Dies ist die Grundlage aller registrierten Zustaende im CPLD: Seitenregister, Laufwerksauswahlregister, Interrupt-Enable und Speicherverwaltungsflags. Ein getakteter Prozess enthaelt immer if rising_edge(CLK) then.
Ein kombinatorischer Prozess oder eine nebenlaefige Signalzuweisung beschreibt reine Logikgatter - der Ausgang ist eine unmittelbare Funktion der Eingaenge, ohne Speicherung. Die I/O-Dekodierlogik im SFD-700 ist kombinatorisch: Immer wenn sich der Z80-Adressbus und die Steuersignale aendern, werden die Select-Ausgaenge sofort aktualisiert (innerhalb der Ausbreitungsverzoegerung des CPLD).
std_logic- ein einzelnes Binaersignal, das '0', '1', 'Z' (hochohmig / Tri-State) oder verschiedene andere Metawerte fuer die Simulation sein kann. '0' und '1' sind die beiden realen Hardwarezustaende. 'Z' bedeutet, das Signal wird nicht getrieben - bei bidirektionalen Busanwendungen bedeutet dies "Bus freigegeben".std_logic_vector(N downto 0)- ein Bus von N+1 Bits.downtobedeutet, Bit N ist das hoechstwertige Bit. Z80_ADDR iststd_logic_vector(15 downto 0)- ein 16-Bit-Adressbus, bei dem Bit 15 das MSB ist.integer range 0 to 7- eine begrenzte Ganzzahl, hier fuer IFMODE (den Maschinenmodus) verwendet, damitcase- und Vergleichsanweisungen natuerlich funktionieren.boolean- wahr oder falsch. Die Hilfsfunktionto_std_logic(L: boolean)konvertiert einen booleschen Ausdruck in ein std_logic '0' oder '1'.
Quellbaum
Die VHDL-Quelldateien des SFD-700 mkII befinden sich im Verzeichnis
CPLD/v1.2/ des Repositorys:
| Datei | Rolle |
|---|---|
sfd700_pkg.vhd |
Package - Konstanten, Maschinenmodus-Werte, Hilfsfunktionen |
sfd700_Toplevel.vhd |
Top-Level-Entity - bildet physische CPLD-Pins auf die Implementierung ab |
sfd700.vhd |
Implementierung - alle Logikprozesse und Signalzuweisungen |
build/sfd700.qpf |
Quartus II Projektdatei |
build/sfd700.qsf |
Quartus II Einstellungsdatei (Pin-Zuordnungen, Baustein-Einstellungen) |
build/output_files/sfd700.pof |
Kompilierte CPLD-Programmierdatei |
CUPL/ |
GAL CUPL-Quellen fuer v1.0/v1.1-Platinen |
Package-Datei: sfd700_pkg.vhd
Die Package-Datei ist eine gemeinsame Bibliothek von Konstanten und Funktionen, die die anderen VHDL-Dateien verwenden koennen. Betrachten Sie sie als eine C-Header-Datei - sie definiert gemeinsame Symbole, damit Sie keine magischen Zahlen im gesamten Design wiederholen muessen.
Sie wird als
Logikzustandskonstanten
package sfd700_pkg deklariert und von den anderen Dateien ueber use work.sfd700_pkg.all verwendet.
Anstatt im gesamten Design '1' oder '0' zu schreiben, definiert das Package benannte Aliase:
constant YES : std_logic := '1'; -- Assertion: yes, true, active constant NO : std_logic := '0'; -- Negation: no, false, inactive constant HI : std_logic := '1'; -- Signal is at logic high (voltage) constant LO : std_logic := '0'; -- Signal is at logic low (ground) constant ONE : std_logic := '1'; -- Binary one constant ZERO : std_logic := '0'; -- Binary zero constant HIZ : std_logic := 'Z'; -- High impedance: not driving the bus
Diese sind stilistisch -
Maschinenmodus-Konstanten
YES und '1' sind in Hardware identisch. Die Absicht ist, den Code selbstdokumentierend zu machen. Wenn Sie ROM_CSn <= NO (Chip Select inaktiv High getrieben) sehen, ist die Bedeutung sofort klar.
Die drei MODE-Jumper-Bits koennen Werte 0-7 darstellen. Anstatt das IFMODE-Signal mit nackten Zahlen zu vergleichen, gibt das Package ihnen Namen:
constant MODE_MZ1200 : integer := 0; -- Sharp MZ-1200 (alias of MZ-80A) constant MODE_MZ80A : integer := 0; -- Sharp MZ-80A constant MODE_MZ700 : integer := 1; -- Sharp MZ-700 constant MODE_MZ80B : integer := 2; -- Sharp MZ-80B constant MODE_MZ800 : integer := 3; -- Sharp MZ-800 constant MODE_MZ1500 : integer := 4; -- Sharp MZ-1500 constant MODE_MZ2000 : integer := 5; -- Sharp MZ-2000 constant MODE_MZ2200 : integer := 6; -- Sharp MZ-2200
Beachten Sie, dass MODE_MZ1200 und MODE_MZ80A beide 0 ergeben - diese beiden Maschinen sind in ihrem Bus-Timing und ihren ROM-Anforderungen hinreichend aehnlich, dass dieselbe Logik beide bedient.
Hilfsfunktionen
Mehrere Hilfsfunktionen sind definiert und werden anderswo verwendet:
IntMax(a, b) - gibt die groessere von zwei Ganzzahlen zurueck. Wird in Parameterberechnungen verwendet.
log2ceil(arg) - gibt die Anzahl der Bits zurueck, die zur Darstellung einer positiven Ganzzahl benoetigt werden. Zum Beispiel:
log2ceil(128) = 7. Wird verwendet, um Busbreiten automatisch aus Kapazitaetsparametern zu berechnen, anstatt sie fest zu kodieren.
clockTicks(period, clock) - gibt bei einer Zeitperiode in Nanosekunden und einer Taktfrequenz in Hz die Anzahl der ganzen Taktzyklen zurueck, die in diese Periode passen. Nuetzlich fuer die Berechnung von Timer-Vorladewerten oder Wait-State-Zaehlerstaenden zur Synthesezeit.
reverse_vector(slv) - kehrt die Bitreihenfolge eines Standard-Logikvektors um. Wird verwendet, wenn Busbits umgeordnet werden muessen (z.B. beim Anschluss an ein Peripheriegeraet, das die entgegengesetzte Bit-Endianness-Konvention verwendet).
to_std_logic(i) - konvertiert Ganzzahl 0 in std_logic '0' und jede andere Ganzzahl in std_logic '1'. Ermoeglicht die direkte Verwendung von Ganzzahlbedingungen, wo ein std_logic-Wert benoetigt wird.
bit_to_integer(s) - konvertiert ein std_logic-Bit in eine natuerliche Ganzzahl (0 oder 1). Nuetzlich fuer die Verwendung eines einzelnen Bits als Array-Index.
Top-Level-Datei: sfd700_Toplevel.vhd
Die Top-Level-Datei definiert die Entity
sfd700 - die Schnittstelle, die Quartus auf die physischen CPLD-Pins abbildet. Ihre Rolle ist rein strukturell: Sie deklariert dieselbe Portliste wie die Implementierungs-Entity (cpld128 in sfd700.vhd) und instanziiert dann diese Implementierung, wobei jeder Port mit einer direkten 1:1-Zuordnung durchverbunden wird.
Warum eine separate Top-Level-Datei? In der VHDL-Designpraxis ist die Trennung der Top-Level-"Chip-Grenz"-Entity von der Implementierungs-Entity gute Praxis - sie erleichtert es, eine Simulations-Testbench einzuwechseln, das Design innerhalb eines groesseren Designs zu instanziieren oder Wrapper-Logik (wie JTAG Boundary-Scan-Zellen) hinzuzufuegen, ohne die Kernlogik zu beruehren.
architecture rtl of sfd700 is
begin
cpldl128Toplevel : entity work.cpld128
port map
(
Z80_ADDR => Z80_ADDR,
Z80_DATA => Z80_DATA,
Z80_M1n => Z80_M1n,
Z80_RDn => Z80_RDn,
...
CLK_16M => CLK_16M,
CLK_FDC => CLK_FDC,
CLK_BUS0 => CLK_BUS0
);
end architecture;
Die
port map-Anweisung (mit => zwischen Portname und Signalname) verbindet die externen Pin-Namen von sfd700 mit den entsprechenden Pins der cpld128-Komponente. Da beide Entities identische Portnamen haben, ist jede Verbindung portname => portname.
Das Altera-spezifische library altera; use altera.altera_syn_attributes.all; importiert Altera-Synthese-Direktiven, die pro-Pin-Attribute (wie Treibstaerke und Pin-Sperrung) ermoeglichen. Diese werden von Quartus waehrend der Platzierung und Routing verwendet.
Haupt-Logikdatei: sfd700.vhd
Diese Datei enthaelt die Entity
Entity- und Port-Deklarationen
cpld128 und ihre rtl-Architektur - die gesamte funktionale Logik des CPLD. Jeder Prozess und jede nebenlaefige Zuweisung ist unten dokumentiert.
Die Entity-Portliste gruppiert Signale in sieben logische Kategorien:
entity cpld128 is
port (
-- Z80 Address Bus
Z80_ADDR : in std_logic_vector(15 downto 0);
-- Z80 Data Bus (bidirectional)
Z80_DATA : inout std_logic_vector(7 downto 0);
-- Z80 Control Signals
Z80_M1n : in std_logic;
Z80_RDn : in std_logic;
Z80_WRn : in std_logic;
Z80_IORQn : in std_logic;
Z80_MREQn : in std_logic;
Z80_INT : out std_logic;
Z80_EXWAITn : out std_logic;
Z80_RESETn : in std_logic;
-- Inverted Data / ROM-RAM Upper Address Bus
ID : inout std_logic_vector(7 downto 0);
-- ROM/RAM Control
ROM_A10 : out std_logic;
RAM_A10 : out std_logic;
ROM_CSn : out std_logic;
RAM_CSn : out std_logic;
RSV : out std_logic;
-- Host Machine Mode
MODE : in std_logic_vector(2 downto 0);
-- Floppy Disk Interface
FDCn : out std_logic;
INTRQ : in std_logic;
DRQ : in std_logic;
DDENn : out std_logic;
SIDE1 : out std_logic;
MOTOR : out std_logic;
DRVSAn : out std_logic;
DRVSBn : out std_logic;
DRVSCn : out std_logic;
DRVSDn : out std_logic;
-- Clocks
CLK_16M : in std_logic;
CLK_FDC : out std_logic;
CLK_BUS0 : in std_logic
);
end entity;
Port-Richtungen:
Interne Signaldeklarationen
in- Eingang zum CPLD (von externer Hardware getrieben).out- Ausgang vom CPLD (von der CPLD-Logik getrieben).inout- bidirektional: das CPLD kann das Signal sowohl lesen als auch treiben. Verwendet fuer Datenbusse (Z80_DATA, ID), bei denen die Richtung je nach Buszyklustyp wechselt.
Zwischen den Schluesselwoertern
is und begin der Architektur werden alle internen Leitungen (Signale) deklariert. Dies sind keine physischen Pins - sie sind die "Draehte", die Logikbloecke innerhalb des CPLD verbinden. Eine Auswahl der wichtigsten:
-- Address decode select signals (internal wires, not physical pins) signal FDC_SELni : std_logic; -- '0' when a WD1773 register is being accessed signal DRIVE_WR_SELni : std_logic; -- '0' when port 0xDC is being written signal DDEN_WR_SELni : std_logic; -- '0' when port 0xDE is being written signal SIDE_WR_SELni : std_logic; -- '0' when port 0xDD is being written signal INTEN_SELni : std_logic; -- '0' when port 0xDF is being accessed signal EXXX_WR_SELni : std_logic; -- '0' when port 0x60 is being written signal FXXX_WR_SELni : std_logic; -- '0' when port 0x61 is being written signal MEM_EXXX_SELni : std_logic; -- '0' when memory address is E300h-EFFFh signal MEM_FXXX_SELni : std_logic; -- '0' when memory address is F000h-FFFFh -- Registered state (these become flip-flops inside the CPLD) signal REG_DRIVEA : std_logic; signal REG_DRIVEB : std_logic; signal REG_DRIVEC : std_logic; signal REG_DRIVED : std_logic; signal REG_MOTOR : std_logic; signal REG_SIDE : std_logic; signal REG_DDEN : std_logic; signal REG_INT : std_logic; signal REG_EXXX_PAGE : std_logic_vector(7 downto 0); signal REG_FXXX_PAGE : std_logic_vector(7 downto 0); signal REG_ROMDIS : std_logic; signal REG_ROMINH : std_logic; -- Machine mode (captured from jumper on reset) signal IFMODE : integer range 0 to 7 := 0; -- Clock signal CLK_8Mi : std_logic := '0';
Prozess: FDCCLK -- Taktteiler
FDCCLK: process( CLK_16M )
begin
if(rising_edge(CLK_16M)) then
CLK_8Mi <= not CLK_8Mi;
end if;
end process;
Was es tut: Dieser Prozess enthaelt ein einzelnes Toggle-Flip-Flop, das den 16-MHz-Primaertakt durch zwei teilt und den vom WD1773 benoetigten 8-MHz-Takt erzeugt.
Wie es funktioniert: Die Sensitivity-Liste
( CLK_16M ) bedeutet "diesen Prozess neu auswerten, wenn sich CLK_16M aendert". Der Test if rising_edge(CLK_16M) bedeutet "nur auf der steigenden Flanke (0->1 Uebergang) des Takts handeln". Bei jeder steigenden Flanke wird CLK_8Mi invertiert - zwischen '0' und '1' umgeschaltet. Dies ist eine perfekte Zweiteilung: ein vollstaendiger Zyklus von CLK_8Mi erstreckt sich ueber zwei Zyklen von CLK_16M und ergibt genau 8 MHz.
Hardware-Ergebnis: Ein D-Flip-Flop mit Q zurueckverbunden zu seinem eigenen Komplementeingang (D = not Q). Der Ausgang Q ist CLK_8Mi, der dann direkt aus dem CPLD am CLK_FDC-Pin ausgegeben wird.
Warum 8 MHz? Das WD1773-Datenblatt spezifiziert einen 8-MHz-Eingangstakt fuer Standard-MFM-Floppy-Operationen. Der Chip verwendet diesen zur Zeitsteuerung der Bit-Zellen-Fenster und PLL-Synchronisation fuer die Datentrennung.
Prozess: SETMODE -- Modusregister-Latch
SETMODE: process( Z80_RESETn, MODE )
begin
if(Z80_RESETn = '0') then
IFMODE <= to_integer(unsigned(MODE));
end if;
end process;
Was es tut: Tastet die drei MODE-Jumper-Eingangspins ab und speichert ihren Wert als Ganzzahl in IFMODE, wenn das System im Reset gehalten wird.
Wie es funktioniert: Die Sensitivity-Liste umfasst sowohl Z80_RESETn als auch MODE. Wenn sich einer aendert, laeuft der Prozess. Wenn Z80_RESETn Low ist (System im Reset), wird IFMODE von den MODE-Pins geladen. Die Konvertierungskette
to_integer(unsigned(MODE)) konvertiert den Drei-Bit-std_logic_vector MODE in eine Ganzzahl 0-7.
Wichtiges Detail: Dieser Prozess hat keine Taktflanke - er ist pegelsensitiv auf Z80_RESETn = Low. Das bedeutet, IFMODE wird waehrend der gesamten Dauer eines Resets aktualisiert, nicht nur an der steigenden oder fallenden Flanke. In der Synthese erzeugt dies ein einfaches asynchrones Register-Laden anstelle eines getakteten Flip-Flops.
Warum beim Reset speichern? Der MODE-Jumper ist ein PCB-Jumper, der vor dem Einschalten gesetzt wird. Das Speichern beim Reset stellt sicher, dass das Design einen stabilen Wert liest (Jumper werden durch die Reset-Dauer entprellt) und dass Glitches waehrend des Hochfahrens die Modusauswahl nicht verfaelschen. Sobald der Reset freigegeben wird, ist IFMODE fuer die Sitzung fixiert.
Prozess: SETSIDE -- Kopfseiten-Register
SETSIDE: process( Z80_RESETn, CLK_16M, SIDE_WR_SELni )
variable SIDE_SEL_LASTni : std_logic;
begin
if(Z80_RESETn = '0') then
REG_SIDE <= '0';
SIDE_SEL_LASTni := '0';
elsif(rising_edge(CLK_16M)) then
if(SIDE_WR_SELni = '0' and SIDE_SEL_LASTni = '1') then
REG_SIDE <= not Z80_DATA(0);
end if;
SIDE_SEL_LASTni := SIDE_WR_SELni;
end if;
end process;
Was es tut: Erfasst die Kopfseiten-Auswahl (Vorder- oder Rueckseite der Floppy-Disk), wenn der Z80 auf I/O-Port 0xDD schreibt.
Wie es funktioniert: Dieser Prozess verwendet eine Flankenerkennungstechnik, um die fallende Flanke von SIDE_WR_SELni (das Active-Low-Write-Select-Signal fuer Port 0xDD) zu identifizieren. SIDE_SEL_LASTni ist eine
variable, die den Wert von SIDE_WR_SELni des vorherigen Taktzyklus haelt. Bei jeder steigenden Taktflanke, wenn SIDE_WR_SELni jetzt '0' (aktiviert) ist, aber im letzten Zyklus '1' war, ist gerade eine fallende Flanke aufgetreten - dies ist der Zeitpunkt, an dem der Z80-Schreib-Strobe frisch ist und der Datenbus gueltige Daten enthaelt.
Invertierung: REG_SIDE <= not Z80_DATA(0) - das Bit wird invertiert, weil die WD1773-Seitenauswahl-Konvention entgegengesetzt zur Z80-Schreibkonvention ist: Z80 schreibt D0=0, um Seite 1 zu waehlen, D0=1, um Seite 0 zu waehlen. Die Invertierung gleicht den physischen SIDE1-Ausgang an die Erwartung des WD1773 an.
Reset-Verhalten: REG_SIDE wird beim Reset auf '0' gesetzt, was Seite 0 (oberer Kopf) waehlt - der Standard fuer einseitige Operationen.
Prozess: SETDDEN -- Doppeldichte-Enable
SETDDEN: process( Z80_RESETn, CLK_16M, DDEN_WR_SELni )
variable DDEN_SEL_LASTni : std_logic;
begin
if(Z80_RESETn = '0') then
REG_DDEN <= '1';
DDEN_SEL_LASTni := '0';
elsif(rising_edge(CLK_16M)) then
if(DDEN_WR_SELni = '0' and DDEN_SEL_LASTni = '1') then
REG_DDEN <= not Z80_DATA(0);
end if;
DDEN_SEL_LASTni := DDEN_WR_SELni;
end if;
end process;
Was es tut: Steuert, ob der WD1773 im Doppeldichte-(MFM)- oder Einzeldichte-(FM)-Modus arbeitet. Geschrieben ueber I/O-Port 0xDE.
Struktur: Identisches Flankenerkennungsmuster wie SETSIDE. Bei der fallenden Flanke von DDEN_WR_SELni (Port 0xDE Schreiben) wird REG_DDEN als Inverse von Z80_DATA(0) geladen.
Reset-Wert: REG_DDEN = '1' beim Reset bedeutet, DDENn wird High getrieben = Einzeldichte standardmaessig ausgewaehlt. In der Praxis setzt die Firmware sofort Doppeldichte, bevor ein FDC-Befehl ausgegeben wird.
Prozess: SETDRIVE -- Laufwerks- und Motorsteuerung
SETDRIVE: process( Z80_RESETn, CLK_16M, DRIVE_WR_SELni )
variable DRIVE_SEL_LASTni: std_logic;
begin
if(Z80_RESETn = '0') then
REG_DRIVEA <= '0'; REG_DRIVEB <= '0';
REG_DRIVEC <= '0'; REG_DRIVED <= '0';
REG_MOTOR <= '0';
DRIVE_SEL_LASTni := '0';
elsif(rising_edge(CLK_16M)) then
if(DRIVE_WR_SELni = '0' and DRIVE_SEL_LASTni = '1') then
REG_DRIVEA <= '0'; REG_DRIVEB <= '0';
REG_DRIVEC <= '0'; REG_DRIVED <= '0';
REG_MOTOR <= Z80_DATA(7);
case(to_integer(unsigned(Z80_DATA(2 downto 0)))) is
when 0 =>
when 4 => REG_DRIVEA <= '1';
when 5 => REG_DRIVEB <= '1';
when 6 => REG_DRIVEC <= '1';
when 7 => REG_DRIVED <= '1';
when others =>
end case;
end if;
DRIVE_SEL_LASTni := DRIVE_WR_SELni;
end if;
end process;
Was es tut: Verarbeitet Schreibzugriffe auf I/O-Port 0xDC zur Auswahl eines von vier Floppy-Laufwerken und zur Steuerung des Spindelmotors.
Laufwerksauswahl-Kodierung: Der Z80 schreibt einen 8-Bit-Wert, bei dem Bits [2:0] die Laufwerksnummer mit einer nicht-sequentiellen Kodierung kodieren: 0 = kein Laufwerk, 4 = Laufwerk A, 5 = Laufwerk B, 6 = Laufwerk C, 7 = Laufwerk D. Werte 1-3 haben keine definierte Bedeutung.
Design-Entscheidung - immer zuerst abwaehlen: Bei jedem Schreiben auf 0xDC werden alle vier Laufwerksauswahl-Register zuerst auf '0' geloescht, bevor eines gesetzt wird. Dies bedeutet, dass ein einzelner Schreibvorgang immer sauber von "was auch immer vorher ausgewaehlt war" zu "das neu angeforderte Laufwerk" uebergeht. Dies verhindert, dass zwei Laufwerke gleichzeitig ausgewaehlt werden.
Motorsteuerung: Bit 7 des Schreibwerts steuert direkt REG_MOTOR - keine Kodierung oder Invertierung. Software muss dieses Bit auf 1 setzen, um das Laufwerk hochzudrehen, bevor WD1773-Befehle ausgegeben werden.
Prozess: SETINT -- Interrupt-Enable-Register
SETINT: process( Z80_RESETn, CLK_16M, INTEN_SELni )
variable INTEN_SEL_LASTni: std_logic;
begin
if(Z80_RESETn = '0') then
REG_INT <= '0';
INTEN_SEL_LASTni := '0';
elsif(rising_edge(CLK_16M)) then
if(INTEN_SELni = '0' and INTEN_SEL_LASTni = '1') then
REG_INT <= Z80_RDn;
end if;
INTEN_SEL_LASTni := INTEN_SELni;
end if;
end process;
Was es tut: Steuert, ob das INTRQ-Signal des WD1773 an die INT-Leitung des Z80 weitergeleitet wird.
Doppelfunktions-Port: Port 0xDF ist sowohl der "Interrupt aktivieren"-Port (beim Schreiben) als auch der "Interrupt deaktivieren"-Port (beim Lesen). Bei einem Schreibzyklus ist Z80_RDn High -> REG_INT = '1' -> Interrupt aktiviert. Bei einem Lesezyklus ist Z80_RDn Low -> REG_INT = '0' -> Interrupt deaktiviert. Ein einzelnes Registerbit und ein einzelner Prozess behandeln beide Operationen, indem Z80_RDn als Dateneingang verwendet wird.
Prozess: SETEXXXPAGE -- EXXX-Fenster-Seitenregister
SETEXXXPAGE: process( Z80_RESETn, CLK_16M, IFMODE, EXXX_WR_SELni )
variable EXXX_SEL_LASTni : std_logic;
begin
if(Z80_RESETn = '0') then
REG_EXXX_PAGE <= "00000010";
EXXX_SEL_LASTni := '0';
elsif(rising_edge(CLK_16M)) then
if(EXXX_WR_SELni = '0' and EXXX_SEL_LASTni = '1') then
REG_EXXX_PAGE <= Z80_DATA(7 downto 0);
end if;
EXXX_SEL_LASTni := EXXX_WR_SELni;
end if;
end process;
Was es tut: Speichert das 8-Bit-EXXX-Seitenregister, das auswaehlt, welche 4-KB-Seite des Flash-ROMs oder SRAMs im EXXX-Speicherfenster (E300h-EFFFh) sichtbar ist. Bit 7 (D7) waehlt den Speichertyp - D7=0 fuer Flash-ROM, D7=1 fuer SRAM. Bits [6:0] halten die Seitenadresse.
Reset-Wert: "00000010" binaer = D7=0 (Flash-ROM), Seite 2 dezimal. Beim Reset ist das EXXX-Fenster auf Flash-ROM-Seite 2 voreingestellt, die die RFS-(ROM Filing System)-Startbank enthaelt.
Prozess: SETFXXXPAGE -- FXXX-Fenster-Seitenregister
SETFXXXPAGE: process( Z80_RESETn, CLK_16M, IFMODE, FXXX_WR_SELni )
variable FXXX_SEL_LASTni : std_logic;
begin
if(Z80_RESETn = '0') then
REG_FXXX_PAGE <= (others => '0');
if(IFMODE = MODE_MZ700) then
REG_FXXX_PAGE(1 downto 0) <= "01";
end if;
elsif(rising_edge(CLK_16M)) then
if(FXXX_WR_SELni = '0' and FXXX_SEL_LASTni = '1') then
REG_FXXX_PAGE <= Z80_DATA(7 downto 0);
end if;
FXXX_SEL_LASTni := FXXX_WR_SELni;
end if;
end process;
Was es tut: Speichert das 8-Bit-FXXX-Seitenregister fuer das FXXX-Speicherfenster (F000h-FFFFh) und initialisiert es auf die korrekte AFI-(Autostart Floppy Interface)-ROM-Seite fuer die Host-Maschine.
Maschinenabhaengiger Reset-Wert: MZ-80A/MZ-1200 (MODE 0): Seite 0 = MZ-80A AFI Boot-ROM (Standard). MZ-700 (MODE 1): Seite 1 = MZ-700 AFI Boot-ROM.
RAM / ROM Auswahl (ueber Seitenregister Bit 7)
Die RAM/ROM-Auswahl wird jetzt durch Bit 7 (D7) jedes Seitenregisters gesteuert - REG_EXXX_PAGE(7) fuer das EXXX-Fenster und REG_FXXX_PAGE(7) fuer das FXXX-Fenster. D7=0 waehlt Flash-ROM; D7=1 waehlt SRAM.
Vorteil: Jedes Speicherfenster kann jetzt unabhaengig Flash-ROM oder SRAM waehlen. Die Seitenadresse und der Speichermodus werden in einem einzigen I/O-Schreibvorgang gesetzt.
Prozess: SETHIMEM -- MZ-700 / MZ-1500 Speicherverwaltung
SETHIMEM: process( Z80_RESETn, CLK_16M,
ROMINHSET_WR_SELni, ROMINHCLR_WR_SELni,
ROMDISSET_WR_SELni, ROMDISCLR_WR_SELni )
variable ROMINHSET_LAST : std_logic;
variable ROMINHCLR_LAST : std_logic;
variable ROMDISSET_LAST : std_logic;
variable ROMDISCLR_LAST : std_logic;
begin
if(Z80_RESETn = '0') then
REG_ROMINH <= '0';
REG_ROMDIS <= '0';
...
elsif(rising_edge(CLK_16M)) then
if(ROMINHSET_WR_SELni = '0' and ROMINHSET_LAST = '1') then REG_ROMINH <= '1'; end if;
if(ROMINHCLR_WR_SELni = '0' and ROMINHCLR_LAST = '1') then REG_ROMINH <= '0'; end if;
if(ROMDISSET_WR_SELni = '0' and ROMDISSET_LAST = '1') then REG_ROMDIS <= '1'; end if;
if(ROMDISCLR_WR_SELni = '0' and ROMDISCLR_LAST = '1') then REG_ROMDIS <= '0'; end if;
Was es tut: Verfolgt den Speicherverwaltungsstatus des MZ-700 und MZ-1500 und verwaltet zwei Flag-Register (REG_ROMINH und REG_ROMDIS), die der Chip-Select-Logik mitteilen, ob die ROM- und RAM-Fenster des SFD-700 fuer den Host sichtbar sein sollen.
Warum dies benoetigt wird: Der MZ-700 und MZ-1500 haben eine Hardware-Speicherverwaltungseinheit, die den 64-KB-DRAM der CPU in den oberen Adressraum (D000h-FFFFh) einblenden kann - denselben Bereich, der von den Flash-ROM- und SRAM-Fenstern des SFD-700 verwendet wird. Wenn der Host seinen DRAM hier einblendet, aber der SFD-700 weiterhin seinen Chip-Select fuer diesen Bereich aktiviert, kommt es zu einem Buskonflikt.
Auswirkung auf Chip-Selects: Die nebenlaefigen Zuweisungen ROM_SELni und RAM_SELni enthalten
REG_ROMINH = '0' and REG_ROMDIS = '0' als obligatorische Bedingungen fuer die Aktivierung der Chip-Selects. Wenn eines der Flags gesetzt ist, bleiben die Chip-Selects inaktiv (High) und halten den SFD-700 vollstaendig vom Bus fern.
Nebenlaefige Signalzuweisungen
Die nebenlaefigen Signalzuweisungen ausserhalb jedes Prozesses bilden die kombinatorische Logik des CPLD. Sie werden kontinuierlich ausgewertet - wann immer sich ein Eingangssignal aendert, wird der Ausgang innerhalb einer CPLD-Ausbreitungsverzoegerung aktualisiert. Dieser Abschnitt dokumentiert die wichtigsten.
Speicher-Adressdekoder
MEM_EXXX_SELni <= '0' when Z80_MREQn = '0'
and unsigned(Z80_ADDR(15 downto 8)) >= X"E3"
and unsigned(Z80_ADDR(15 downto 8)) < X"F0"
else '1';
MEM_FXXX_SELni <= '0' when Z80_MREQn = '0'
and unsigned(Z80_ADDR(15 downto 8)) >= X"F0"
and unsigned(Z80_ADDR(15 downto 8)) <= X"FF"
else '1';
Diese dekodieren den Z80-Adressbus fuer Speicheranfragen. MEM_EXXX_SELni geht auf Low ('0'), wenn der Z80 auf Speicher zugreift und das hohe Byte der Adresse im Bereich 0xE3-0xEF liegt. MEM_FXXX_SELni geht auf Low, wenn das hohe Byte 0xF0-0xFF ist.
ROM- und RAM-Chip-Selects
ROM_SELni <= '0' when (IFMODE = MODE_MZ700 or IFMODE = MODE_MZ1500)
and REG_ROMINH = '0' and REG_ROMDIS = '0'
and ((MEM_EXXX_SELni = '0' and REG_EXXX_PAGE(7) = '0')
or (MEM_FXXX_SELni = '0' and REG_FXXX_PAGE(7) = '0'))
else
'0' when (IFMODE = MODE_MZ1200 or IFMODE = MODE_MZ80A)
and ((MEM_EXXX_SELni = '0' and REG_EXXX_PAGE(7) = '0')
or (MEM_FXXX_SELni = '0' and REG_FXXX_PAGE(7) = '0'))
else '1';
Diese einzelne Zuweisung erfasst die vollstaendige ROM-Chip-Select-Logik. Sie wird nur aktiviert ('0'), wenn alle Bedingungen gleichzeitig erfuellt sind: die richtige Maschine, die Speicherverwaltungsflags sind geloescht (nur MZ-700/MZ-1500), Bit 7 des aktiven Fenster-Seitenregisters ist '0' (Flash-ROM ausgewaehlt), und die Z80-Adresse faellt in das EXXX- oder FXXX-Fenster.
I/O-Port-Dekoder
FDC_SELni <= '0' when Z80_IORQn = '0'
and (Z80_WRn = '0' or Z80_RDn = '0')
and unsigned(Z80_ADDR(7 downto 0)) >= X"D8"
and unsigned(Z80_ADDR(7 downto 0)) < X"DC"
else '1';
DRIVE_WR_SELni <= '0' when Z80_IORQn = '0' and Z80_WRn = '0'
and unsigned(Z80_ADDR(7 downto 0)) = X"DC"
else '1';
Jedes I/O-Port-Select-Signal ist ein einfacher kombinatorischer Ausdruck, der Z80_IORQn, Z80_WRn/Z80_RDn und den Adressvergleich prueft.
Z80-Datenbus-Multiplexer
Z80_DATA <= not ID when FDC_SELni = '0' and Z80_WRn = '1'
else REG_EXXX_PAGE when EXXX_RD_SELni = '0'
else REG_FXXX_PAGE when FXXX_RD_SELni = '0'
else "00000" & MODE when MODE_RD_SELni = '0'
else (others => 'Z');
Dies ist der Lesedaten-Multiplexer fuer den Z80-Datenbus. Der Standard ist
ID-Bus -- Invertierte Daten / ROM-RAM obere Adresse
(others => 'Z') - das CPLD versetzt den Datenbus in den Tri-State (hochohmig), damit andere Geraete den Bus ohne Konflikt treiben koennen.
ID <= not Z80_DATA when Z80_WRn = '0' and FDC_SELni = '0'
else REG_EXXX_PAGE(6 downto 0) & Z80_ADDR(11) when MEM_EXXX_SELni = '0'
else REG_FXXX_PAGE(6 downto 0) & Z80_ADDR(11) when MEM_FXXX_SELni = '0'
else (others => 'Z');
Der ID-Bus hat eine Doppelfunktion - er dient zwei voellig verschiedenen Funktionen je nachdem, was auf dem Bus passiert: FDC-Schreibzyklen (invertierte Daten) oder Speicherzyklen (obere Adressbits fuer ROM/RAM-Paging).
ROM_A10 -- Seitenadressbit / MZ-80A DRQ-Trick
ROM_A10 <= '1' when (IFMODE = MODE_MZ1200 or IFMODE = MODE_MZ80A) and DRQ = '1'
else Z80_ADDR(10);
Diese einzelne Zeile implementiert den MZ-80A-Geschwindigkeitskompensationstrick. Fuer alle Maschinen ausser MZ-80A/MZ-1200 folgt ROM_A10 einfach Z80_ADDR(10). In MODE 0 wird DRQ fuer ADDR(10) substituiert, wenn DRQ aktiviert ist ('1').
Modifikation der CPLD-Logik
Die VHDL-Quelle ist so strukturiert, dass gaengige Modifikationen unkompliziert sind:
Hinzufuegen eines neuen I/O-Ports:
- Fuegen Sie ein neues
signal NEW_PORT_SELni : std_logic;im Signaldeklarationsblock hinzu. - Fuegen Sie eine nebenlaefige Zuweisung hinzu:
NEW_PORT_SELni <= '0' when Z80_IORQn = '0' and Z80_WRn = '0' and unsigned(Z80_ADDR(7 downto 0)) = X"XX" else '1'; - Fuegen Sie einen aehnlichen Prozess wie SETDRIVE hinzu, der Daten von Z80_DATA bei der fallenden Flanke von NEW_PORT_SELni erfasst.
- Verwenden Sie den erfassten Registerwert in einer nebenlaefigen Ausgangszuweisung.
- Fuegen Sie eine neue Konstante zu
sfd700_pkg.vhdhinzu:constant MODE_NEWMACHINE : integer := 7; - Aktualisieren Sie den Reset-Block des SETFXXXPAGE-Prozesses, wenn die neue Maschine eine andere Standard-ROM-Seite benoetigt.
- Aktualisieren Sie ROM_SELni und RAM_SELni, wenn die neue Maschine Onboard-ROM/RAM benoetig.
- Fuegen Sie SETHIMEM im MZ-700-Stil Speicherverwaltung hinzu, wenn die neue Maschine inkompatibles Speicher-Paging verwendet.
GAL CUPL-Logik (v1.0 / v1.1)
Das Verzeichnis
CUPL/ enthaelt die programmierbare Logikquelle fuer die v1.0- und v1.1-Platinen. CUPL (Compiler for Universal Programmable Logic) ist eine Logikgleichungssprache fuer GAL/PAL-Bausteine - sie ist konzeptionell einfacher als VHDL, da GALs nur einstufige kombinatorische und einfache registrierte Logik implementieren.
Fuer v1.0/v1.1-Platinen sind die beiden CUPL-Dateien SFD700_1.PLD (GAL26CV12, I/O-Dekoder) und SFD700_2.PLD (GAL16V8, ROM-Dekoder). Vorkompilierte JEDEC-Dateien sind ebenfalls fuer die direkte Programmierung ohne Neukompilierung bereitgestellt.
CI/CD-Pipeline
Der SFD-700 mkII verwendet eine Jenkins-CI/CD-Pipeline fuer automatisierte CPLD-Kompilierung, RFS-ROM-Assemblierung und Release-Management. Die Pipeline wird durch Gitea-Webhooks ausgeloest und erzeugt versionierte CPLD-Bitstreams und ROM-Images zur Verteilung.
Jenkins-Pipeline-Ueberblick
Die SFD700-CI/CD-Pipeline laeuft auf einer Jenkins-Instanz, die auf dem EaW-VPS in einem Docker-Container gehostet wird. Die Pipeline wird automatisch durch Gitea-Webhooks ausgeloest, wenn Commits auf den Main- oder Master-Branch gepusht werden. Die Pipeline-Stufen sind:
- Checkout - Workspace bereinigen und das SFD700-Repository von Gitea klonen.
- Version bestimmen -
VERSIONaus dem Repository-Root lesen. Wenn sich die Version seit dem letzten Commit nicht geaendert hat, automatische Erhoehung der Versionsnummer aus dem neuesten Gitea-Release-Tag. - CPLD bauen - VHDL-Quelle mit Altera Quartus II 13.0.1 in einem Docker-Container kompilieren, erzeugt eine
sfd700.pof-Bitstream fuer das EPM7128SLC84-15 CPLD. - RFS klonen - Das RFS-(ROM Filing System)-Repository von Gitea klonen.
- RFS fuer SFD700 bauen - Das
BUILD_SFD700-Flag in den RFS-Assemblierungsdefinitionen setzen und den Z80-ROM-Code mit dem GLASS-Assembler kompilieren. - Releases paketieren - Drei Release-Archive erstellen: CPLD-Bitstream, ROM-Images und ein kombiniertes Komplettpaket.
- Gitea-Release erstellen - Die Release-Artefakte in ein neues Gitea-Release hochladen, mit der Versionsnummer getaggt.
Webhook-Konfiguration
Die Pipeline wird durch einen Gitea-Webhook ausgeloest, der eine POST-Anfrage an das Jenkins Generic Webhook Trigger-Plugin sendet.
- Webhook-URL -
http://<jenkins-container-ip>:8080/generic-webhook-trigger/invoke?token=sfd700-build-trigger - Token -
sfd700-build-trigger - Content Type -
application/json - Branch-Filter - Wird nur bei Pushes auf
refs/heads/mainoderrefs/heads/masterausgeloest.
Versionsverwaltung
Die Build-Version wird in einer
VERSION-Datei im Repository-Root im Format MAJOR.MINOR (z.B. "1.05") gespeichert.
- Wenn
VERSIONim ausloesenden Commit geaendert wurde, wird die in der Datei angegebene Version fuer das Release verwendet. - Wenn
VERSIONunveraendert ist, fragt die Pipeline die Gitea-API nach dem neuesten Release-Tag ab und erhoeht die Minor-Versionsnummer automatisch. - Die Minor-Version rollt bei 100 ueber: Version 1.99 wird zu 2.00.
- Wenn keine
VERSION-Datei existiert und keine vorherigen Releases gefunden werden, wird die Version auf 1.00 gesetzt.
Release-Artefakte
Jeder Pipeline-Lauf erzeugt drei gzip-komprimierte Release-Archive:
| Artefakt | Inhalt | Verwendungszweck |
|---|---|---|
| SFD700-CPLD-vX.XX.tar.gz | CPLD-Bitstream (sfd700.pof) |
JTAG-Programmierung des EPM7128SLC84-15 CPLD |
| SFD700-ROMs-vX.XX.tar.gz | RFS-ROM-Images (SFD700_256.bin, rfs.rom, rfs_mrom.rom) |
Flashen auf das SFD700-ROM ueber Programmer |
| SFD700-Complete-vX.XX.tar.gz | Kombiniertes CPLD + ROM-Paket | Komplettaktualisierung von CPLD und ROMs |
Referenzseiten
| Ressource | Link |
|---|---|
| SFD-700 mkII Projektseite | /de/sfd700/ |
| SFD-700 mkII Benutzerhandbuch | /de/sfd700-usermanual/ |
| SFD-700 mkII Technischer Leitfaden | /de/sfd700-technicalguide/ |
| SFD-800 (Begleitkarte fuer MZ-800) | /sfd800/ |
| VHDL-Sprachreferenzhandbuch | IEEE 1076-2008 Standard |
| Altera MAX7000S Datenblatt | Intel FPGA Produktseiten - MAX 7000S Familie |
| Quartus II 13.0.1 SP1 Web Edition | Intel FPGA Software Archive |
| WD1773 Datenblatt | Western Digital / Historisches Archiv |
| CUPL-Referenzhandbuch | Logical Devices Inc. (archiviert) |