SFD-700 mkII Entwicklerhandbuch

English

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:
  • 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.
Es koennen mehrere Architectures fuer eine Entity existieren (verschiedene Implementierungen derselben Schnittstelle), obwohl der SFD-700 eine einzelne Architecture namens rtl (Register Transfer Level - ein konventioneller Name, der bedeutet, dass das Design in Bezug auf Datenfluss zwischen Registern beschrieben wird) verwendet.

Signale
Signale sind die internen Leitungen des Designs. Sie werden zwischen dem 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.

Prozesse
Ein Prozess ist ein Codeblock, der sequentielle (getaktete) oder kombinatorische Logik beschreibt. Prozesse sind in 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).

Verwendete Datentypen
  • 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. downto bedeutet, Bit N ist das hoechstwertige Bit. Z80_ADDR ist std_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, damit case- und Vergleichsanweisungen natuerlich funktionieren.
  • boolean - wahr oder falsch. Die Hilfsfunktion to_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 package sfd700_pkg deklariert und von den anderen Dateien ueber use work.sfd700_pkg.all verwendet.

Logikzustandskonstanten
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 - 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.

Maschinenmodus-Konstanten
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 cpld128 und ihre rtl-Architektur - die gesamte funktionale Logik des CPLD. Jeder Prozess und jede nebenlaefige Zuweisung ist unten dokumentiert.

Entity- und Port-Deklarationen
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:
  • 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.

Interne Signaldeklarationen
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 (others => 'Z') - das CPLD versetzt den Datenbus in den Tri-State (hochohmig), damit andere Geraete den Bus ohne Konflikt treiben koennen.

ID-Bus -- Invertierte Daten / ROM-RAM obere Adresse
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:
  1. Fuegen Sie ein neues signal NEW_PORT_SELni : std_logic; im Signaldeklarationsblock hinzu.
  2. 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';
  3. Fuegen Sie einen aehnlichen Prozess wie SETDRIVE hinzu, der Daten von Z80_DATA bei der fallenden Flanke von NEW_PORT_SELni erfasst.
  4. Verwenden Sie den erfassten Registerwert in einer nebenlaefigen Ausgangszuweisung.
Hinzufuegen eines neuen Maschinenmodus:
  1. Fuegen Sie eine neue Konstante zu sfd700_pkg.vhd hinzu: constant MODE_NEWMACHINE : integer := 7;
  2. Aktualisieren Sie den Reset-Block des SETFXXXPAGE-Prozesses, wenn die neue Maschine eine andere Standard-ROM-Seite benoetigt.
  3. Aktualisieren Sie ROM_SELni und RAM_SELni, wenn die neue Maschine Onboard-ROM/RAM benoetig.
  4. Fuegen Sie SETHIMEM im MZ-700-Stil Speicherverwaltung hinzu, wenn die neue Maschine inkompatibles Speicher-Paging verwendet.
Aendern des Taktteiler-Verhaeltnisses: Ersetzen Sie das Toggle-Flip-Flop in FDCCLK durch einen Zaehler, um durch eine beliebige gerade Zahl zu teilen.

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:
  1. Checkout - Workspace bereinigen und das SFD700-Repository von Gitea klonen.
  2. Version bestimmen - VERSION aus 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.
  3. 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.
  4. RFS klonen - Das RFS-(ROM Filing System)-Repository von Gitea klonen.
  5. RFS fuer SFD700 bauen - Das BUILD_SFD700-Flag in den RFS-Assemblierungsdefinitionen setzen und den Z80-ROM-Code mit dem GLASS-Assembler kompilieren.
  6. Releases paketieren - Drei Release-Archive erstellen: CPLD-Bitstream, ROM-Images und ein kombiniertes Komplettpaket.
  7. 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/main oder refs/heads/master ausgeloest.

Versionsverwaltung

Die Build-Version wird in einer VERSION-Datei im Repository-Root im Format MAJOR.MINOR (z.B. "1.05") gespeichert.
  • Wenn VERSION im ausloesenden Commit geaendert wurde, wird die in der Datei angegebene Version fuer das Release verwendet.
  • Wenn VERSION unveraendert 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)