Phosphoric › Artículos › Phosphoric 2.0
Phosphoric 2.0: una máquina cadenciada por ciclo — y la afirmación que retiramos
Lo que habíamos dicho y por qué era falso, lo que cambió la V2, lo que cambia de forma visible — y lo que seguimos sin afirmar.
Hasta la 1.120.0-alpha, Phosphoric se presentaba como «cycle-accurate». No era cierto en el sentido que los emuladores de referencia dan a esa palabra. Este artículo dice qué se retiró, qué se construyó en su lugar y dónde termina la afirmación. Los nombres de pruebas y opciones son los del repositorio.
1. Lo que habíamos dicho, y por qué era falso
El núcleo 6502 ejecutaba una instrucción en bloque: los accesos al bus salían en el orden correcto y el total de ciclos por opcode era justo, pero los ciclos internos se recuperaban por relleno al final de la instrucción, los accesos ficticios del NMOS no existían, y las interrupciones se tomaban en las fronteras de instrucción. El VIA recibía lotes de ciclos, la ULA renderizaba una línea entera de golpe, el PSG giraba a la frecuencia de muestreo de audio.
Es un nivel honorable — lo llamamos N2, «ordenado al ciclo de bus» — pero no es «exacto al
ciclo». Retiramos la formulación, escribimos una escala verificable (docs/ACCURACY.md: N1 a N4,
cada nivel respaldado por la prueba que lo demuestra) y un guardián automático (make
test-docs-claims) que rechaza cualquier «cycle-accurate» sin matizar en los documentos de escaparate.
Luego hicimos la V2.
| Nivel | Nombre | Definición operativa |
|---|---|---|
| N1 | Contado por instrucción | El total de ciclos por opcode es exacto; los periféricos avanzan por lotes tras la instrucción. |
| N2 | Ordenado al ciclo de bus | Cada acceso real al bus cae en el ciclo intrainstrucción correcto; los ciclos internos se recuperan al final de la instrucción; IRQ en las fronteras de instrucción. |
| N3 | Paso a paso por ciclo | La unidad de avance de la máquina es el ciclo: una acción de bus por ciclo (accesos ficticios incluidos), periféricos en sincronía; IRQ/NMI en el penúltimo ciclo. |
| N4 | Subciclo (φ1/φ2) | El ciclo se subdivide; se modelan las carreras setup/hold entre tarjetas y bus. |
2. Lo que cambió la V2
Primero un oráculo
Antes de tocar el núcleo, make test-cycle reproduce los vectores SingleStepTests/65x02 —
10 000 casos por opcode, con la traza de bus esperada ciclo a ciclo — y puntúa cuatro propiedades por separado
(estado final, total de ciclos, subsecuencia de bus, secuencia de bus exacta). El punto de partida, medido y
publicado: 44,26 % de secuencias de bus exactas. El oráculo reveló además cinco defectos
lógicos del núcleo, corregidos antes incluso de empezar.
Un núcleo microsecuenciado
Cada instrucción se convierte en un plan de microoperaciones, una por ciclo, cada una haciendo exactamente su
acceso al bus — incluidos los ficticios (indexación en página cero, cruce de página, reescritura de los RMW,
lecturas muertas de pila). Resultado: 100,00 % en 2 440 000 casos. Las interrupciones se
muestrean en el penúltimo ciclo, lo que da gratis la bandera I retardada de
CLI/SEI/PLP y el desvío de un BRK por una NMI. Ambos
núcleos comparten el mismo cálculo (banderas, BCD, opcodes ilegales): solo difiere la secuenciación. El antiguo
sigue disponible (--cpu-legacy).
Un reloj maestro
emu_cycle() hace avanzar toda la máquina un ciclo, en un orden fijo: el CPU hace
su acceso al bus, los periféricos avanzan un ciclo — nunca un lote —, luego la ULA hace fetch de la celda de
ese mismo ciclo. Es el orden medido en el hardware por Mike Brown; hasta la 2.0.1 la ULA iba antes, y cada
split caía una celda demasiado a la derecha. El bucle principal ya no calcula nada: pide.
Los componentes, uno a uno
- VIA 6522: cuenta por ciclo; el Timer 1 recuperó su periodo N+2 (el modelo antiguo daba N: 20 % de error para N=10).
- ULA: hace fetch de una celda de 6 píxeles por ciclo, en el instante en que el haz la lee — una escritura a mitad de línea solo alcanza las celdas aún no barridas; los splits de raster se vuelven posibles.
- PSG AY-3-8910: gira a reloj/8 (125 kHz) y su salida está integrada — un tono programado por encima de Nyquist se atenúa en lugar de replegarse; la envolvente, dos veces demasiado lenta desde siempre, va a la velocidad correcta; la etapa de salida analógica se tomó del esquema oficial.
- WD1793: señala
LOST DATAy la protección contra escritura. - Casete a nivel de señal: paridad de trama impar, no par.
3. Lo que cambia de forma visible
- Los splits de raster: un programa que cambia la tinta o el modo a mitad de línea obtiene lo que daría el hardware, no una línea uniforme.
- El sonido: envolventes de la duración correcta, sin silbido fantasma en los tonos muy agudos, señal centrada (continua bloqueada como en la placa).
- Las IRQ: una interrupción ya no puede tomarse antes de la instrucción en curso;
SEIno protege a la instrucción siguiente de una IRQ ya pendiente. - Los registros de lectura destructiva: un
POKEBASIC al registro de datos de una ACIA pierde un byte, porqueSTA (zp),Yhace una lectura ficticia antes de escribir — en el emulador como en la máquina. Phosphoric era más permisivo que el hardware; ya no lo es. - Los savestates: un estado tomado a mitad de cuadro se reanuda exactamente donde se detuvo (posición del haz, temporizadores, muestra de interrupción).
4. Lo que aprendimos por el camino
Dos defectos nunca se habrían visto sin cambiar de método.
La envolvente del PSG se declaraba conforme «por recálculo» — un recálculo que suponía 16 estados en lugar de 32. Una hipótesis falsa es invisible en la relectura; solo se ve midiendo la señal. Las pruebas de audio miden ahora frecuencias y duraciones en lugar de comparar bytes.
El segundo es aún más instructivo. Un salto no tomado decidía un ciclo demasiado tarde, en una microoperación sin acceso al bus. El contador del CPU seguía siendo justo, así que el oráculo estaba en verde al 100 %. Pero el reloj maestro se había llamado una vez más: la ULA avanzaba un ciclo que ni el CPU ni el VIA habían vivido — unas 410 veces por cuadro en la ROM BASIC, es decir, un cuadro de deriva por segundo entre la imagen y el resto de la máquina. Un oráculo que solo mira el CPU no demuestra la sincronización de la máquina. Lo que lo reveló: una prueba de determinismo de los savestates, que exigía que las paradas de raster cayeran exactamente en el mismo ciclo.
5. Lo que no decimos
«Phosphoric es exacto al ciclo» — no. El FDC sigue cadenciado por retardos fijos sobre una imagen plana (sin flujo MFM, así que sin verdadera pérdida de byte ni CRC). La referencia horizontal, en cambio, ya no es una convención: el contador de la ULA (medido) sitúa la columna 0 en el count 0 — pero la fase absoluta entre ese contador y el CPU solo es observable en un ORIC real con el «VSYNC hack», que no emulamos. El medio ciclo del one-shot del VIA no está representado. La carga de casete por defecto sigue siendo el parche ROM, por elección: mismo contenido cargado, 2,4× menos ciclos.
La formulación exacta autorizada, y la prueba que haría caer cada una de sus líneas, están en
docs/ACCURACY.md.
6. Coste
El paso al ciclo costó, en la máquina de referencia a plena velocidad, 491 → 611 µs por cuadro emulado:
3 % del presupuesto de 20 ms. make test-bench rechaza ahora cualquier exceso
superior al 5 %.
7. Desde la 2.0.0
- 2.0.1 — build WebAssembly reparado (roto desde el port a macOS), página en línea reconstruida en 2.0.
- 2.0.2 — orden intraciclo CPU/ULA corregido según el Unofficial ULA Guide de Mike Brown: el 6502 accede primero, la ULA hace fetch después del mismo count; cada split de raster caía una celda (6 píxeles) demasiado a la derecha. La reserva «referencia horizontal no calibrada» queda levantada.
- 2.0.3 — bucle principal dividido en etapas con nombre (1 296 → 126 líneas), sin cambio visible.
- 2.0.4 — cosimulación LOCI: el botón F8 tiene un único propietario (el firmware cosimulado); pulsación larga = ROM de diagnóstico de Mike Brown.
Batería de pruebas: 1 208 pruebas en 60 suites, 100 % en verde; release automática por etiqueta (binario Linux, zip Windows); versión para navegador en línea.
Fuentes y enlaces
- Repositorio Phosphoric: Codeberg · Framagit · GitHub
- Escala de precisión y estado por componente: docs/ACCURACY.md
- Nota técnica original (en francés): docs/articles/v2-cycle-stepped.md
- Oráculo 6502: SingleStepTests/65x02; prueba funcional de Klaus Dormann.
- Mike Brown, ORIC 1/ATMOS Unofficial ULA Guide: oric.free.fr · oric.signal11.org.uk
- Probar en línea: versión WebAssembly