607
11.02.2026, 15:15 Uhr
Dresdenboy
|
Mal ein anderes Thema:
T-State-genaue Synchronisation auf eine Flanke
Das macht natürlich nur Sinn, wenn man es braucht und die Bedingungen stimmen, d.h. z.B. gleicher oder ein voneinander abgeleiteter Takt für die beteiligten Bausteine.
Bei HeikoS könnte das z.B. für die Sprites auf dem JU+TE relevant sein.
Ich brauche es für den BIC, natürlich nicht für eine Tabellenkalkulation, aber für Grafikeffekte mit zeilenweisen Änderungen. Ich schrieb ja in <405> schon darüber bzgl. Hblank. Falls man mit >20 T-States Jitter (IN + JR/JP) leben kann, ist das natürlich kein Problem, aber falls diese Checks oft erfolgen müssen, kostet das schon deutlich.
Neben solchen Effekten gibt es einen anderen Nutzen: Falls man genau seine Position bzgl. der Bilddarstellung kennt, braucht man z.B. auch nicht irgendwelche Statusbits checken. Z.B. beim GDC ist dann nicht nötig, auf FIFO FULL oder READ DATA zu prüfen, weil klar ist, dass es in den entspr. Zyklen so klappen wird.
Die oben beschriebene Variante braucht natürlich viele Zeilen, um sich zu synchronisieren. Das kann sich schon für folgende Frames reduzieren, indem man einen framegenauen CTC-Interrupt zu einer genauen Startzeit (nach obigem Sync) aufsetzt (siehe <450> im BIC-Austausch), sollte bei Nutzung von HALT bis zum nächsten Frame der Jitter nur noch 0-4 T-States betragen. (BIC hat 1 M1-Waitstate).
Das braucht dann nur noch wenige Zeilen, um die HBlank-Flanke zu erwischen. Aber auch die kosten schonmal >1% der Zeit, wenn man dazwischen nicht viel tun kann. Es ginge auch nebenbei innerhalb von zyklusgezähltem Code (per Hand, VSCode-Plugin oder zmac-Assembler) mit insgesamt ca. 150 T-States Overhead, aber braucht immer noch mehrere Zeilen. Und falls man aus dem Takt kommt, müsste man es wiederholen.
Daher habe ich mir, in Anlehnung an C64-Methoden (der ja richtige Rasterinterrupts hat sowie 2 Zyklen-NOPs und sogar CPU-Takt-schnelle CIA-Zähler, was einige Tricks ermöglicht), etwas für den BIC oder andere Z80-Systeme (die ja meist einen CTC haben) überlegt: Sofern sich z.B. der CTC-Timer für eine Zeile genau programmieren lässt (beim BIC: 240 T-States, also Timerwert 15 mit Vorteiler 16), kann bei entspr. Startpunkt schnell eine Synchronisation zumindest zu den CTC-Timer-Flanken erfolgen bzw. der vorliegende Jitter indirekt ermittelt werden. Falls man über Interrupt (z.B. über denselben CTC-Timer, der ja auch mit einem Vielfachen von 15 - oder mit was auch immer man benötigt - laufen kann) nach HALT an die Stelle kam, beträgt der Jitter wie oben 0-4 T-States. Dann reicht zumindest auf dem BIC als Grundgerüst eine Sequenz wie:
| Quellcode: | IN A,(80h) ; 12 LD D,A ; 5 -> 17 T-States auf dem BIC, also immer 1 mehr als der CTC-Timer-Zyklus mit Vorteiler 16 IN A,(80h) ; 12 LD E,A ; 5 IN A,(80h) ; 12 LD H,A ; 5 IN A,(80h) ; 12 ADD A,D ; 5 ADD A,E ; 5 einfach mal addieren, hier gibt es mehrere Möglichkeiten, z.B. ADD HL,DE usw. RLA ; 5 LD L,A ; 5 Summe*2 in L JP (HL) ; 5 H kann hier auch schon 2 versch. Werte haben (z.B. 14h oder 13h) ; 88 T-States |
(die T-States für BIC kann man hier unter Timing Z80+M1 sehen: https://map.grauw.nl/resources/z80instr.php ) Für Systeme ohne M1-Waitstate oder z.B. einen Z8 braucht man einen anderen Sampling-Code oder erhält einfach nur andere Zahlen als Ergebnis, da IN A,(n)+LD r,A nur 11+4 also 15 T-States braucht. Dann ist das 1 Takt kürzer als der CTC-Timerzyklus, wo man durch leichtes Verschieben die Flanke von der anderen Seite "einfängt".
Daraufhin wird in eine der 2 Speicherseiten entspr. HL gesprungen, wo sich weiterer Code zum Ausgleich des Jitters befindet. Da je nach System andere Speicherbereiche nutzbar sind oder auch der Platz knapp sein kann (auch, wenn pro Seite nur einige weitere Bytes genutzt werden), kann das entspr. angepasst werden.
Da der CTC-Timer hier gelesen wird, kann sowohl mit dessen gelesenen Werten (z.B. späteres Lesen liefert kleinere CTC-Werte) aber auch ein paar Berechnungen ein brauchbarer Adressraum geschaffen werden. Unterscheiden sich die Werte in L z.B. teils nur um 1, würden angesprungene Befehle teils überlappen. Das kann auch trickreich genutzt werden. Z.B. 18h, 18h, 20h wäre ein JR 18h überlagert mit einem JR 20h. So ähnliche Techniken werden im Sizecoding genutzt oder auch auf dem C64 in einer "NOP-Rutsche". Der JR-Sprung kann dann entspr. Zeitausgleichscode anspringen und schließlich ein finales Sprungziel.
Das ganze sollte auch nicht von System-ISRs durcheinander gebracht werden, was für eine Demo nicht so schwer zu handhaben ist. 
VG, Matthias -- ___________________________________ Produktionen im Rahmen der "The Computer Art Community" (Demoszene): https://demozoo.org/sceners/64936/, YT-Kanal: https://www.youtube.com/@4lpha0ne/videos Aktuelle Projekte: GDC-Analysen für Grafikeffekte u. Demo/Game-Framework, universelles BIC-Modul auf Pico-Basis, Packer mit sehr kleinem 6502-Dekompressor HW: BIC, MSX2+, KC87, KC85/2-4, KCC, LC-80, PC1715, C64, C16, Plus/4, A500, A1200, Mega 65, µCs ... Dieser Beitrag wurde am 12.02.2026 um 14:54 Uhr von Dresdenboy editiert. |