8 Bitter mit 24 Bit Adressierung – eine verpasste Chance
Ich nehme an, viele der älteren Blogleser haben in den Achtziger einen Heimcomputer gehabt, Geräte wie der C64, die CPC Serie oder Sinclair Spectrum (um nur die am weitesten verbreiteten Marken zu nennen, es gab noch unzählige andere) waren selbst noch populär als Heimcomputer mit 16 Bit gab. Erst Anfang der Neunziger Jahre sanken die Preise von PC aber auch Amigas und Ataris soweit, das es keinen Sinn machte einen 8 Bitter zu kaufen.
Kurz: die 8-Bit Rechner hatten ein langes Leben, es hätte meiner Ansicht nach noch länger sein können, wenn ihr Adressspeicher größer gewesen wäre. Die drei populärsten 8 Bit Prozessoren und ihre Nachbauten/Nachfolger (Intel 8080, Mos 6502 und Motorola 6800) hatten alle einen 16 Bit Adressbus, konnten also 216 = 65536 Bytes adressieren. Der Adressbus eines Prozessors muss nicht zu seinem Datenbus passen und der wiederum nicht zur internen Registerbreite. Der erste Mikroprozessor Intel 4004 hatte einen 12 Bit Adressbus bei 4 Bit Datenbus. Der Intel 8086 hatte 16 Bit Datenbus und 20 Bit Adressbus, die Motorola 68000 16 Bit Daten- und 24 Bit Adressbus. Nur bei der 32 Bit Generation war es so, dass Daten und Adressbus gleich groß waren, aber bei den aktuellen Prozessoren gilt das auch nicht mehr. Intels iCore 13600 Prozessor unterstützt z.B. maximal 192 GByte RAM, das sind 38 Bits. Das gilt übrigens auch für Großrechner. Es ist nicht unbedingt „natürlich“, dass es bei 8 Bittern so 16 Bit für den Adressbus sind.
Doch wie kam es dazu?
Als die ersten 8 Bitter erschienen, waren Mikroprozessoren noch etwas relativ neues. Intels 4004 und Texas Instruments TMS 1000 erschienen 1971. Intels erste 8 Bit Prozessor, allerdings mit einem kruden Design erschien 1972. Die 8 Bitter folgten dann relativ schnell aufeinander: Intel 8080 und Motorola 6800 erschienen 1974, der MOS 6502 kam im Jahr 1975 und der Zilog Z80 folgte 1976. Danach setzten die Firmen eben wegen dieser schnellen Entwicklung gleich auf die nächste Generation: Intels 8086 erschien 1978, Motorolas 68000 1979 und Zilogs Z8000 ebenfalls 1979. Wenn es neue 8 Bitter gab, so waren sie graduelle Verbesserungen wie der 6802 oder 6809 von Motorola, oder der Intel 8085.
Am Adressbus wurde nichts mehr geändert und der entstand in diesen Prozessoren eben zwischen 1974 bis 1976. 1974 erschien das 4 Kbit DRAM, üblicher war damals aber noch die Vorgängergeneration mit 1 Kbit Kapazität. Mit 4 Kbit RAM, die 512 Byte speichern können, braucht man aber 128 RAM-Bausteine um den Speicher voll auszunützen. Das ist dann nicht mehr die Platine eines Heimcomputers, auch weil der Speicher 1974 noch teuer war. Noch drei Jahre später, als es schon 16 Kbit RAM gab, kostete das Aufrüsten eines Apple II (der erste Rechner, der es erlaubte den Speicherausbau voll zu nutzen) mehr als die Basisausstattung des Apple II mit 4 KByte.
So verwundert es nicht, dass die Firmen sich auf 16 Adressbits beschränkten. Bis man diesen Adressraum voll ausnutzen würde, würde es Jahre dauern und dann gäbe es schon die Nachfolgenegation mit 16 Bit, wer sollte dann noch einen veralteten 8 Bitter kaufen? So war dem auch. Die ersten 8 Bit Rechner die 64 KByte RAM hatten, erschienen erst nach den ersten 16-Bit-Prozessoren und bis 64 KByte auch für Heimcomputer erschwinglich waren sollte es noch bis 1983/84 dauern, da stand schon die 32 Bit Generation in den Startlöchern. Aber es kam anders: die Firmen meinten, ihre Prozessoren würden in der Industrie für Steueraufgaben eingesetzt werden. Das jemand auf einem 8-Bit-Prozessor eine Anwendung nutzen würde wie Textverarbeitung, Datenbanken oder Tabellenkalkulation – all das gab es schon zu 8 Bit Zeiten – war für sie unvorstellbar.
Es gab natürlich noch Nachfolger der 8 Bitter, die einen größeren Adressbereich boten, zumindest soweit ich weiß in der 6502 und Z80 Linie, Motorola konzentrierte sich, nachdem sie nicht so viele 6800 Prozessoren absetzen konnten, darauf ihn als Mikrocontroller umzubauen und waren damit sehr erfolgreich: schon vor mehr als 20 Jahren konnte die Firma über 1 Milliarde verkaufter 68xx Mikrocontroller vermelden.
Der Nachteil dieser Nachfolger war, dass sie nicht nur den Adressbereich erweiterten, sondern auch neue Features integrierten. Der HD64180 von Hitachi integriere serielle Schnittstellen, DMS, Timer, MMU und einiges mehr. Noch weiter ging der 65816 von Western Digital, der ein 16 Bit Ptrozessor mit 8 Bit Emulationsmodus war. Wie man aber schon an den Herstellern sieht: Die Entwickler Mos Technologies und Zilog hatten kein Interesse an einer Weiterentwicklung. Diese Prozessoren waren leistungsfähig, aber sie waren auch teuer und bei den 8 Bit Rechnern zählte eben vor allem der Preis.
Ich will mal skizzieren, dass es durchaus möglich gewesen wäre, einen größeren Adressraum anzubieten. Prinzipiell gibt es dafür drei technische Lösungen. Alle drei erfordern weitere Adressleitungen oder einen gemultiplexten Adressbus, darauf komme ich noch zurück.
- Ein Register, das die zusätzlichen oberen Adressleitungen beinhaltet. Der normale (64 Kilobyte) Adressraum ist dann ein Fenster innerhalb eines größeren Adressraums.
- Wie oben, nur das ein Register nicht direkt die zusätzlichen Datenleitungen beinhaltet, sondern verschoben zum Adressraum, den es schon bisher gab, addiert wird. ähnlich gegen Intel beim 8086 vor.
- Schlüsselregister in der CPU (PC, SP, Arbeitsregister) jeweils erweitert um ein weiteres Adressregister.
Alle drei Möglichkeiten haben Vor– und Nachteile. Bei Möglichkeit 1 ist der offensichtlichste Nachteil, dass es nicht möglich ist zwischen den 64 KByte großen „Kacheln“ zu kopieren, also Daten austauschen, daher würde ich sie von vorneherein ausschließen. Möglichkeit 2 hat diesen Nachteil nicht. Hier wird der Inhalt dieses neuen Adressregisters nicht einfach oben an die schon existenten 16 Adressleitungen addiert, sondern diese bzw. das Register vorher verschoben und addiert. So ging Intel vor, um beim 8086 die 64 KByte großen Segmente im 1 MByte großen Adressraum um jeweils 16 Byte zu verschieben. Diese Möglichkeit erlaubt das Kopieren zwischen den „Fenstern“, aber jeder Adresszugriff wird langsamer, weil eine Addition vorgeschaltet ist. Möglichkeit 3 ist die flexibelste und erfordert nur anstatt einem mehrere Register. Ich will dies am Beispiel des Z80 zeigen, da ich dessen Architektur kenne.
Der Z80 stammt vom 8080 ab und dieser hatte folgende Architektur:
- Es gab die 8 Bit Rechenregister A,B,C,D,E,H und L. Im Akkumulator A landeten alle Ergebnisse von 8 Bit Rechnungen und er war auch immer ein Operand bei 8 Bit Rechnungen.
- Die Register B und C, D und E und H und L konnte man für 16 Bit Rechnungen zusammenfassen, das Register HL auch für einen 16 Bit Speicherzugriff. Neben diesem indirekten Zugriff (Lade den Wert von einer Adresse, die Du in HL findest) gab es noch die Möglichkeit einen 8 oder 16 Bit Wert als Konstante direkt zu laden oder ebenfalls indirekt, dann aber über eine direkt angehängte Adresse.
- Die Register PC für den Programmcounter und SP für den Stackpointer waren von vorneherein 16 Bit Register.
Die Z80 erweiterte dies und erlaubte auch für die Kombination BC und DE einen 16 Bit Speicherzugriff und führte zwei 16 Bit Indexregister IX und IY ein, die zusätzlich noch mit einem Displacement (Versatz für Indexe) arbeiten konnten. Daneben wurde der gesamte Registersatz verdoppelt und es konnte über einen Befehl umgeschaltet werden.
Meiner Ansicht nach braucht man so mindestens drei, besser vier weitere Register für den oberen Adressbereich. Ich bin, weil die meisten Register bisher 8 Bit breit sind, von einem 8 Bit Register ausgegangen, das sind dann maximal 16 MByte – die Adressleitungen A16 bis A23.
- Ein Register, das den PC auf 24 Bit erweitert
- Ein Register das den SP erweitert (optional, man könnte nach wie vor von einem „lokalen“ Stack, der nur innerhalb eines 64 K Fensters verschoben werden kann zurechtkommen, ohne Probleme zu haben)
- Zwei Register für die Erweiterung von Speicherzugriffen über Register.
Das letzte muss ich erklären. Im Prinzip würde ja ein Register für den Speicherzugriff reichen, will man aber innerhalb des Adressraumes kopieren, so braucht man zwei Register für Quell- und Zieladresse und da hat der Z80 mit den befehlen LDIR und LDDR die in einem Befehl ein Byte von einer Sourceadresse in HL an eine Zieladresse in DE kopieren, HL und DE inkrementieren (bei lddr dekrementieren) und das Register BC dekrementieren und dies wiederholen bis BC Null ist zwei tolle Kopierbefehle.
Kleine Bemerkung an die, die bei meinem letzten Block zum Thema den Z80 so schlecht gemacht haben: folgende Befehlsfolge kopiert 16 KByte von Adresse 0000H an C000H und das in 21 x 16384 Taktzyklen = 0,087 Sekunden bei einem 4 MByte Z80 – wie lange ist die Befehlsfolge bei einem 6502 und wie lange braucht er bei der Äquivalenztaktfrequenz von 2 MHz? (Ein 6502 hat pro 1 Takt zwei interne Taktzyklen, ein Z80 nur einen).
Ld hl,0000H
ld de,c000h
ld bc,4000h
ldir ; insgesamt 8 Bytes
Sieht man zwei Register für DE und HL vor, so ist dieser und zahlreiche andere Blockbefehle möglich wie auch einfachere Speichertransfers. Vier Register (Erweiterung von PC, SP, DE und HL) und die dazu nötigen befehle kosten wenig CPU Platz, man darf nicht vergessen, dass beim Übergang vom 8080 zum Z80 12 neue 8 Bit Register eingeführt wurden und die Befehlszahl sich verdoppelte, hier wären es nur vier Register und pro Register zwei Befehle zum Laden des Registers bzw. dem Auslesen des Registers.
Auch die Kompatibilität zu bisherigen Anwendungen wäre gegeben, wenn die neuen Adressen einfach oben angehängt werden, also A16 bis A23. Benötigt man nur 64 KByte Speicher, so verbindet man die Adressen einfach nicht. Der Knackpunkt und das war wohl der Punkt, weshalb man es nicht gemacht hat, war dass so das Gehäuse der CPU in jedem falle mehr Pins gehabt hätte. Das klingt heute komisch, aber zum einen fielen die Preise für die CPUs so rasch, das die Gehäuse mit jeweils eigenen Metallpins bei CPU Preisen von wenigen Mark dann bald ein Kostenfaktor waren. Es bedeutet auch das man auf einer Platine mehr Leitungen ziehen musste und die Platine so teurer wurde. Deswegen war schon beim 8080/Z80 der Steuerbus mit dem Datenbus gemultiplext, benutzten also dieselben Leitungen und dieselbe Überlegung führte auch beim 8086 zu einem gemultiplexten Bus, und um diesen noch günstiger zu fertigen, gab es den 8088 mit weiterer Reduktion der Pins, wenngleich auf Kosten der Geschwindigkeit.
Auf der anderen Seite gab es in der Endzeit der 8 Bitter zahlreiche Rechner mit mehr als 64 KByte Speicher eie den C128, Sinclair Spectrum 128, Amstrad 6128 oder die Joyce Reihe, da benötigte man Zusatzbausteine wie die 74138 / 139 der 74 IC Serie die aus einem gespeicherten Wert weitere Adresssignale bereitstellten, um weitere Bänke anzusprechen.
Meine Erfahrung mit solchen gebankten Systemen ist, dass sie keine optimale Lösung waren. Man konnte den zusätzlichen Speicher nur für bestimmte Zwecke nutzen, so als Druckerspooler oder RAM-Floppy. Das Grundproblem war, dass eben immer ein Teil des Adressbereiches weg war und durch einen anderen Block ersetzt wurde, dass machte die Programmierung aufwendig und selbst CP/M 3.0 das als Betriebssystems Babys unterstützte hatte keinen einfachen Mechanismus für Anwendungen mehr Speicher effektiv zu nutzen. Das wäre bei einem linearen, größeren Speicher kein Problem.
Ich denke die 8 Bitter hätten noch ein paar Jahre mehr gehabt, wenn sie mehr Speicher adressieren hätten können. Denn der war knapp. Viele Anwendungen, die ich unter CP/M nutzte wie Wordstar oder DBASE waren zu groß für den Speicher. Sie lagerten Funktionen in Overlays aus, Segmenten die von Disk geladen wurden. Ich selbst habe diese Overlays genutzt. Mein Programm „Rakete“ entstand 1987 und war bald zu klein für 64 KByte. Einige Routinen sind bis heute im Programm. Spiele hätten von einem größeren Bildschirmspeicher profitiert, der bei 64 K klein war, sonst hätte man wenig Speicher fürs System gehabt, mein System hatte mit 16 KByte schon einen großen Bildschirmspeicher. Die meisten anderen Systeme hatten nur ein 6 oder 8 KByte großen Speicher.
Mit schnelleren CPUs die in den Achtzigern erschwinglich wurden, wäre auch ein schnelleres System möglich gewesen. Der Z80 erschien mit 2,5 MHz und wurde bis 8 MHz getaktet. Beim 6502 war der Sprung von 1 auf 3 MHz genauso groß.
Aus meiner Sicht würde ich eine kleine Änderung bei Ihrer Variante 3 vorschlagen:
Für Z80 und 6502 4-5 Stück 8 Bit Register für A23-A16
– ein Register, das den PC auf 24 Bit erweitert
– je ein Register, das X und Y 24 Bit erweitert
– ein Register welches für alle anderen Datenzugriffe die Adresse auf 24 Bit erweitert
– optional ein Register welches den Stackpointer auf 24 Bit erweitert
zusätzliche gäbe es alternativ noch die Variante 4:
ähnlich wie bei der PDP11:
die obersten 3 Bit einer 16 Bit Adresse selektieren einen Eintrag aus einer Adresstabelle, die dann die Bits A23-A13 enthält.Nicht so hilfreich, da man diese Adresstabelle 3x braucht, je einmal für Code, Stack und Data.
Zum Bus:
man kann einen 12 Bit Bus multiplexen:
0) Bus in Ruhe (hochohmig)
1) A11-A0 (data)
2) A23-A12 (data)
3) D7-D0 lesen
4) D7-D0 schreiben
5) I/O A11-A0
Bei deinem Vorschlag beschränkst Du wirklich nützlichen Befehl ldi,ldd,ldir,lddr,ini,inir,outi,outi auf 64K. Die IXC und IY Register habe ich relativ selten genutzt. Ihr zugriff war langsam und sie brauchten immer mehr Bytes, blähten also den Code auf, zwei Register für DE und HL wären von mir aus besser.
Wenns aber nur um Register geht – im Prinzip kann man jedes Register um 8 Bit erweitern, braucht nicht mal die Hälfte der Transistoren die mit der Register Erweiterung vom 8080 zum Z80 nötig wurden.
Beim 6502 wäre zusätzlich eine on Chip Zero Page und Stack Page interessant gewesen. Da 16k (bzw. 24k) zusätzliche Transistoren auf dem Chip doch etwas viel sind ggf. als Doppelchip Lösung mit RAM Chip im Gehäuse auf den CPU Chip geklebt, und direkt verdrahtet. (Die Treiber Transistoren für diese Kontakte sind wesentlich kleiner als die Treiber für die PINs.)
Mit passenden PROM Fuses hätte man den selben Basis Chip dann auch mit einem EPROM kombinieren können. (oder eben kein weiteres RAM auf dem Board anbinden, wie bei den Intel 8049 üblich)
Hätte man sicher alles machen können, aber… es wäre alles nur mehr oder weniger beschränkt abwärtskompatibel gewesen.
Dazu kommt das 8-bitter schon bei einfachen Rechenoperationen >8 bit massiv langsamer sind als 16-bitter (z.B. 16-bit addition muss aus mehreren Befehlen zusammengesetzt werden, Ergebnis und Operanden können nicht direkt in Registern gehalten werden sondern müssen den Umweg über den Speicher nehmen). Wenn man aber mehr Speicher hat will man ja auch was damit anfangen, also grössere Datenstrukturen bearbeiten. Da müssten die hypotetischen auf 24-bit Adressraum gepimpten 8-bitter dann wieder mit Umwegen arbeiten was wieder zu Lasten der Geschwindigkeit ginge.
Das hätte man mit mehr Takt ausgleichen können, aber da kommt das nächste Problem: Schnelle Speicher waren auch damals ein deutlicher Kostenfaktor. Man hätte dann schnellere D-RAMs gebraucht die den Kostenvorteil wieder auffressen, weil die CPU’s ja alle keinen Cache hatten musste der Speicher ja auch in jedem Bustakt der CPU was liefern oder die CPU musste wieder warten.
Übrigens war es keineswegs so das die 16-bitter durchgängig 24-bit physischen Adressraum hatten. Von der 68k Familie gab es auch welche mit weniger (20 bit beim 68008) als auch mehr (31 bit beim 68012, eine 16-bit CPU die bereits 2GB adressieren konnte). Beim 68k war das natürlich auch einfach zu machen da der Befehlssatz ja eh auf 32 bit Adressraum ausgelegt war.
1) Deswegen ja beim 6502 die zero Page und der Stack on Chip. Dann kann man in der zero Page beliebige Daten addieren, und muß nur für den Code auf das RAM zugreifen.
2) Den 31 Bit Adressraum gab es auch bei der IBM S390 und S370-XA als neuen Adressmodus.
Dort hatte man das selbe Problem mit der Software wie beim mc68k: Das freie Byte in jedem Pointer war in verschiedener Software für andere Zwecke verwendet worden. Es gab so viel Systemsoftware, die zumindest Bit 0 benötigte, das IBM auf der zSerie nie einen 32 Adressraum eingeführt hat, sondern je nach Software 24/31/64 Bit verwendet.
Interessant zu hören das auch andere Architekturen das gleiche Problem hatten das Programmierer bits zweckentfremden und damit später massive Kompatibilitätshürden geschaffen haben.
Zu deiner Idee mit Zeropage on-chip denke ich das sich das einfach nicht ausgezahlt hätte. Je nach Quelle heisst es ja das der 6510 so um die ca. 3,5k Transistoren hatte. Da stehen die 12/24k Transistoren für ZP und Stack in keinem sinnvollen Verhältnis mehr zum Nutzen. Selbst im günstigsten Fall werden nur 2/5tel der Speicherzugriffe beschleunigt, wie z.B. LDA (ZP),Y (2 Instruction reads + 3 Data reads). Und das wäre im optimistischen Fall das primär Zeropage-indirekte Befehle ausgeführt werden, diese würden ja in der Praxis nur einen Teil einer Schleife ausmachen (wo es ja mindestens noch ein INY/DEY + Branch gäbe). Für den Stack wäre die Bilanz noch schlechter, er käme nur bei JSR/RTS und den relativ selten verwendeten PHA/PLA zum tragen. Mit 3,5k + 24k transistoren wäre dieser Super-6510 auch schon von der Grössenordnung her im Bereich des Transistorbudgets des 8086 (ca. 29k Transistoren) oder 68000 (ca.56k Transistoren), also ahnlich teuer.
Falls die CPU neue Addressierungsarten/Befehle/Register bekäme (was ich beim 6510 als wahrscheinlich ansehe, da man mit 2-byte Zeiger ja keine 24 bit hätte) würde das noch mehr Transistoren benötigen. Ich denke mit den 24k transistoren könnte man besser einen 128 oder 256-byte cache mit 8/16 lines zu 16 byte konstruieren, der deutlich mehr Nutzen hätte wenn man denn schon einen 6510 so stark erweitert. Ein solcher Mini-cache würde alle Befehle in einer Schleife beschleunigen, egal welcher Speicher angesprochen wird. Verkompliziert natürlich den (externen) Adressdekoder der jetzt auch noch ein Cache-inhibit Signal generieren muss bei I/O-Adressen, die Kostenauswirkungen dafür sollten aber vernachlässigbar sein.
Ich denke der Grund warum es eben keine aufgebohrten 8-bitter gab ist einfach das die 16-bitter schon zu billig waren mitter der 80’er. Die CPU macht ja nur einen relativ kleinen Anteil an den Systemkosten aus. Ein paar Mark mehr für eine 16-bit CPU lieferten ein deutlich besseres Preis-/Leistungsverhältnis am Ende. Man muss sich ja auch erinnern das die 16-bitter schon Anfang der 90’er durch 32-bitter komplett verdrängt wurden weil deren Preis fiel und die nochmal in einer ganz anderen Leistungs-Liga spielten. Ab 1992 waren auch im Atari (Falcon)/Amiga 1200 32-bit CPU’s standard, die konnten sich aber letzlich nicht mehr gegen den x86-PC behaupten.
Ein 6502 mit 2MHz würde ungefähr 0,133 Sekunden brauchen fürs Kopieren von 16KByte, allerdings deutlich mehr Code benötigen. Durch selbstmodifizierenden Code ließe sich das noch ein klein wenig beschleunigen, aber sowas muss ja nicht sein…
Eine Z80 braucht bei 4 MHz (äquivalent 2 MHz beim 6502, da diese CPU einen Takt bei jeder Flanke hat, ein Z80 nur bei jeder steigenden Flanke) 0,087 Sekunden um 16 KByte zu kopieren. Vielleicht solltet ihr euch mit den CPUs die ihr so schlecht findet, auch mal auseinandersetzen.,…
Ich habe inzwischen den Ldir Befehl, der das macht auch mal bei meinen Systemen ausgetestet: Z80 21 Takte pro Byte Kopieren, Z180 13 Takte, eZ80 3 Takte – dann gehts bei 50 MHz in 0,001 Sekunden …
Ist der Vergleich nicht ein wenig Äpfel vs Orangen? Der Z80 hat ja 16-bittige Register im Gegensatz zum 6510, da ist es logisch das der Code kompakter und übersichtlicher ausfällt.
Die 16 Bit Register hatte ja schon der 8080. Der LDIR Befehl ist ein Spezialbefehl der in einem Rutsch einen ganzen Speicherblock (Quelladresse in HL, Zieladresse in DE, Länge in BC) kopiert.
Meine erste Bemerkung bezieht sich auf die Kommentare zum letzten Blck zu dem Thema:
https://www.bernd-leitenberger.de/blog/2024/03/14/der-schnellste-z80-rechner/
Methode 1 (die übliche) ist zwei ZP Zeiger zu nehmen und LDA (ZP),Y + STA (ZP),Y in einer Schleife bis zu 256 mal auszuführen und dann das high-byte der Zeiger zu inkrementieren. Ausschlaggebend ist dann die innere Schleife für die erreichbare Geschiwindigkeit, was auf ca. 15 takte pro byte hinausläuft. Die äussere Schleife kann man getrost vernachlässigen da sie nur wenige male durchlaufen wird. Ich nehme an darauf stützt sich deine Schätzung von 0,133s, passt gut zu den 0,123s bei 2 Mhz die ich ausgezählt habe (26 bytes code, inkl. setup).
Das kann man aber mit selbstmodifizierendem Code nicht nur ein „bisschen“ verbessern. Das ist fast Faktor 2 drin. Statt Zeiger in ZP nimmt man LDA ABS,Y + STA ABS,Y, die sind zwar länger, aber auch schneller. Kombiniert mit loop-unrolling (aka. Speedcode) kann man so die innere Schleife an zwei Stellen beschleunigen, einmal brauchen die LDA/STA nur noch 8 statt 10 takte, ausserdem kann man den loop overhead durch unrolling um die Anzahl der unrolls reduzieren. Bei 4 LDA/STA statt einem reduziert sich der loop-overhead von 5/1 auf 5/4 takte. Je nach Aufwand den man treiben möchte kommt man sehr sehr nahe an 8 takte pro kopiertem byte heran. Für einen 4x unrolled loop komme ich auf etwas weniger als 8,2 takte pro byte (inkl. setup, 84 bytes code). Das entspricht 0,067s bei 2 Mhz.
Es geht also schneller, wenn man nur will. Der Code ist dann weder schön noch kurz, aber das wurde damals trotzdem viel gemacht, Faktor 2 ist eben nicht zu verachten.
Loop unrolling hat lange Zeit eine grosse Rolle gespielt wenn es wirklich schnell sein sollte, sogar heute machen Compiler das teilweise noch auf aktuellen CPU’s.
Die innere Schleife braucht bei Methode 1 16 Takte:
LOOP
lda(src),y ;5
sta(dest),y ;6
iny ;2
bne LOOP ;3
bne bräuchte sogar 4 Zyklen, wenn der Branch eine Page Boundary überschreitet.
Die dreckige Methode mit Loop Unrolling ist m.E. auch etwas langsamer als Du errechnet hast:
lda source,y ; 4
sta dest,y ; 5
iny ; 2
Also etwas über 11 Zyklen pro Byte. Oder habe ich was übersehen?
@Bernd: Ich fand den Z80 nie schlecht, ich denke auch er ist performancetechnisch etwas überlegen. Ich bin irgendwie nur nie so wirklich mit dessen Assemblerprogrammierung warmgeworden.
Ja du hast recht, da hat mich mein Gedächtnis im Stich gelassen. STA ist bei diesen Addressierungsarten einen Takt langsamer als LDA (Einen Grund dafür habe ich nicht herausgefunden, bei einigen Addressierungsarten sind beide gleich schnell). Ich hab also für beide Methoden 1 Takt zu wenig pro byte gezählt, meine Zeiten sind um 1/15 bzw. 1/8 zu kurz.
Die Page-Boundary Penalty wird man natürlich nach Möglichkeit vermeiden wenn man optimiert, ich ging natürlich von 3 Takten für den Branch aus.
Beim Loop unrolling kommt man mit einem DEY pro Page aus wenn man die innere Schleife z.B. Y von 63 auf 0 herunterzählen lässt und die LDA/STA-paare haben dann als Adressen ($xx00, $xx40, $xx80 und $xxC0). Die äussere Schleife braucht dann nur noch die high-bytes der 4 LDA/STA-paare zu inkrementieren. D.h. es wird nicht linear kopiert sondern jedes LDA/STA arbeitet eine 64-byte block in der Schleife ab.
LDY #$3F ; 2
innerLoop
LDA $xx00,Y ; 4
STA $xx00,Y ; 5
LDA $xx40,Y
STA $xx40,Y
LDA $xx80,Y
STA $xx80,Y
LDA $xxC0,Y
STA $xxC0,Y
DEY
BPL innerLoop
Es wird dann in der Reihenfolge byte 63, 127, 191, 255, 62, 126, 190, 254… kopiert. Ist nicht intuitiv, spart aber Befehle ein. Der Loop overhead ist dann nur das eine DEY + BPL.
Wenn Quell- und Zieladresse nicht auf eine Page-Boundary fallen wie im Beispiel müsste man noch die Penalties dafür einrechnen, mit einer komplexeren Routine könnte man sie aber auch weitgehend vermeiden. Dann wäre der Code aber sehr lang und kompliziert.
Die Zeit für 8-Bit Controller in Home-Computer war einfach abgelaufen.
Eine Adresserweiterung über Banking ist nur schwere nutzbar, besonders für größere Datenstrukturen.
Im HD64180 (=Z180…) ist die MMU auch nur eine interne Banking-Logik.
Im Embedded Bereich wurden diese noch Jahrelang erfolgreich eingesetzt. In diesem Bereich werden UART und DMA fast immer gebraucht, so dass sie gleich in den Controller integriert werden.
Eine Erweiterung der internen Register hätte jede Menge Problem Bereitet und währe einer kompletten Neuentwicklung gleichgekommen.
Man kann nicht über die Segmentierung beim 8086 lamentieren und gleichzeitig das selbe für seinen Lieblings 8-Bit Controller fordern.
Auch der 6502 hatte im Embedded Bereich noch ein langes Leben. In den 90er mit 16MHz und mehrere KB internes RAM.