Emulación del puerto $FF

jltursan
Mensajes: 1816
Registrado: 20 Sep 2011 13:59
Agradecido : 42 veces
Agradecimiento recibido: 127 veces

Emulación del puerto $FF

Mensajepor jltursan » 31 Dic 2013 12:54

Estoy tratando de confirmar que algún emulador de Spectrum "soporte" una correcta emulación del puerto $FF en Spectrum +3, es decir, que ciertos juegos no funcionen.

Por citar algunos ejemplos, estoy probando con el Speculator y el Duet, uno de los programas incompatibles con esta máquina. El TZX que estoy empleando lo carga correctamente en modo 48K, mientras que en modo +3, el juego parece colgarse en la pantalla inicial (no cambia los mensajes de la última línea).

¿Es el anterior el comportamiento real del juego en un +3?, ¿alguien recuerda que hacían en el +3 juegos de cinta como ese, el Arkanoid o el Renegade?

mcleod_ideafix
Mensajes: 925
Registrado: 13 Ene 2012 09:45

Re: Emulación del puerto $FF

Mensajepor mcleod_ideafix » 31 Dic 2013 14:04

El Duet no lo recuerdo, pero el Arkanoid pre-parcheado, se colgaba justo después de sonar la musiquita que antecede a comenzar la partida, justo cuando se realiza el efecto de destello en los bloques. Esto pasa tanto en modo +3 como en modo 48K, ya que el hardware en ambos casos es el mismo.

Por otra parte, cualquier emulador decente debería soportar la no existencia del puerto $FF en un +2A/+3. Spectaculator y Fuse lo hacen bien, que recuerde.
Cada vez que se implementa un sistema clásico en FPGA, Dios mata a un purista.

jltursan
Mensajes: 1816
Registrado: 20 Sep 2011 13:59
Agradecido : 42 veces
Agradecimiento recibido: 127 veces

Re: Emulación del puerto $FF

Mensajepor jltursan » 31 Dic 2013 14:26

¡Gracias por la pista del Arkanoid!, el Duet ya lo he conseguido probar gracias al ROMSNA y efectivamente, el +3 se cuelga en el mismo sitio que el Speculator emulando esa máquina. La emulación es clavada.

Mi explicación ha sido un poco confusa, cuando describía el comportamiento del emulador, al decir "modo 48K" me refería a que emulaba un gomas (por eso el Duet funcionaba), no el modo 48K del propio +3 -sorry

El asunto es que necesitaba una forma fiable de testear el "fix" para el problema del puerto mediante la resistencia de 470Ohm; así, una vez instalada podré comprobar cual es su efecto.

Avatar de Usuario
zx81
Mensajes: 134
Registrado: 23 Feb 2013 21:31

Re: Emulación del puerto $FF

Mensajepor zx81 » 31 Dic 2013 14:44

JSpeccy también lo emula sin problemas y diría que casi cualquier emulador que pretenda ser medianamente fiel lo emulará. Para el Arkanoid existe una segunda versión que no usa el bus flotante y además, soporta joystick Kempston, si la memoria no me falla.

Lo que no sé es cómo afectará el fix a los juegos de +2a/+3 que no esperan encontrarse nada en ese puerto. Lo normal es que nadie lo leyera, pero... quien sabe si hay algún juego o demo que lo usa para algo.
Today's robots are very primitive, capable of understanding only a few simple instructions such as 'go left', 'go right' and 'build car'.
John Sladek

Empieza a jugar sin tener que compilar: Emulador JSpeccy

jltursan
Mensajes: 1816
Registrado: 20 Sep 2011 13:59
Agradecido : 42 veces
Agradecimiento recibido: 127 veces

Re: Emulación del puerto $FF

Mensajepor jltursan » 01 Ene 2014 13:20

Pues un último apunte, una vez comprobado en el emulador el fallo producto de la lectura del puerto, apliqué el parche para solucionar el problema.

Como se puede consultar por ahí, se trata únicamente de unir los pin 17 (D7) de las RAM 4464 que están en IC4 e IC6 mediante una resistencia de 470 ohmnios. Al parecer ese valor puede variar en función de las memorias (yo tengo montadas ahora mismo de 100ns, no se si es la velocidad o la marca lo que influye); pero en principio ya he comprobado que los 470 ohmnios funcionan correctamente y al menos el "Duet" me ha cargado perfectamente en el +3 -grin

Ahora todo queda recorrer el camino a la inversa que menciona zx81, ¿me habré cargado algún otro software por haber modificado el valor leido en el puerto?, parece poco probable; pero quien sabe...

mcleod_ideafix
Mensajes: 925
Registrado: 13 Ene 2012 09:45

Re: Emulación del puerto $FF

Mensajepor mcleod_ideafix » 01 Ene 2014 18:06

jltursan escribió:Ahora todo queda recorrer el camino a la inversa que menciona zx81, ¿me habré cargado algún otro software por haber modificado el valor leido en el puerto?, parece poco probable; pero quien sabe...

Muy probablemente, no. No conozco de ningún software que se base en que en el puerto $FF siempre haya el valor $FF para funcionar o no. Alguien podría decir que un programa que necesite distinguir entre un +3 y otro equipo anterior podría usar este método, pero por lo que sé, los programas que distinguen entre diferentes modelos lo hacen leyendo determinadas secuencias de la ROM, o comprobando qué tipo de paginación soporta el equipo.
Cada vez que se implementa un sistema clásico en FPGA, Dios mata a un purista.

Avatar de Usuario
ron
Mensajes: 16928
Registrado: 28 Oct 2010 14:20
Ubicación: retrocrypta
Agradecido : 423 veces
Agradecimiento recibido: 441 veces

Re: Emulación del puerto $FF

Mensajepor ron » 01 Ene 2014 20:24

Solo una pregunta rápida.

¿ En la ROM +3E este bug no había sido eliminado ? y en su día leí o eso recuerdo, que el bug del FF afectaba al video.

Avatar de Usuario
zx81
Mensajes: 134
Registrado: 23 Feb 2013 21:31

Re: Emulación del puerto $FF

Mensajepor zx81 » 02 Ene 2014 01:04

Ese problema no puede solucionarse únicamente con software, al menos que yo sepa. E incluso afirmaría alegremente que el bug lo tienen los modelos anteriores, 48k incluido.

El problema viene porque los accesos al bus no están bien aislados eléctricamente, de modo que cuando se está actualizando la pantalla, si la CPU lee de un puerto que no existe *puede* recoger indebidamente el valor que está leyendo la ULA para actualizar la pantalla, byte que puede ser un valor del bitmap, un valor de atributo o, como debería ser, $FF. Algunos juegos usan esa característica para saber cuando está el haz de electrones del TV sobre el área dibujable y evitan así que se vean parpadeos y efectos raros. El Arkanoid es uno de ellos, pero no el único.

Técnicamente, el que lo hace bien es el +3, que por no tener no tiene ni contención durante los accesos I/O (lo único que necesitaba contención era el acceso a memoria, pero los modelos anteriores ni siquiera tenían en cuenta el tipo de acceso), pero eso causa ciertos problemas a esos juegos. En el caso del Arkanoid no es problema porque salió una reedición con el problema resuelto, pero eso no es lo normal.

Si he cometido alguna imprecisión o error, que nuestro experto en HW mcleod_ideafix tenga bien a corregirme... :)
Today's robots are very primitive, capable of understanding only a few simple instructions such as 'go left', 'go right' and 'build car'.
John Sladek

Empieza a jugar sin tener que compilar: Emulador JSpeccy

jltursan
Mensajes: 1816
Registrado: 20 Sep 2011 13:59
Agradecido : 42 veces
Agradecimiento recibido: 127 veces

Re: Emulación del puerto $FF

Mensajepor jltursan » 02 Ene 2014 19:20

En cualquier caso, el apaño sobre D7 en la RAM mimetiza bastante bien el fallo de diseño original :-). Siendo purista, hasta se podría montar un interruptor para seleccionar si está activo o no.

mcleod_ideafix
Mensajes: 925
Registrado: 13 Ene 2012 09:45

Re: Emulación del puerto $FF

Mensajepor mcleod_ideafix » 02 Ene 2014 23:18

En realidad el apaño de D7 mimetiza una octava parte del hack original. Funciona porque practicamente todos los juegos (que son muy pocos) que usan el puerto $FF lo que hacen es discernir si el bit 7 está activo o no, y esto es porque no es habitual en una pantalla de juego que toda ella esté con el flash puesto (que correspondería al bit 7 de stributos puesto a 1). Tener en cuenta que lo que detecta este puerto es el último byte de atributos leído por la ULA. Los bytes de bitmap no llegan a detectarse (en ellos sí que puede ser mucho más común el encontrarse bytes con el bit 7 a 1).

Una forma rápida de detectar si estamos en pantalla es con la secuencia:

Código: Seleccionar todo

in f,(c)
jp p,EnPantalla


Que usa la instrucción indocumentada IN F,(C) para cargar en el registro de flags el valor leído desde el puerto cuyo valor está en el registro C. El valor cargado hace que el bit 7 se almacene donde está el flag de signo del registro de flags, con lo que un salto si positivo es suficiente para indicar que estamos en la pantalla.

La razón por la que el +3 se cuelga en algunos de estos juegos es porque el juego en cuestión tiene un bucle parecido al siguiente:

Código: Seleccionar todo

Espera:  in f,(c)
         jp m,Espera


La secuencia de pintado de un scan cualquiera de los 192 en los que hay imagen es: paper, borde derecho, blanking horizontal, borde izquierdo y vuelta a empezar. Este bucle sale justo cuando se ha empezado con el borde derecho, con lo que tienes unos cuantos ciclos de reloj sin contienda para actualizar el scan siguiente antes de que lo pinte la ULA.

El bucle no termina hasta que el bit 7 del puerto leído sea 0. En un +3, ese bit siempre es 1, y de ahí el bucle infinito.
Cada vez que se implementa un sistema clásico en FPGA, Dios mata a un purista.

Avatar de Usuario
zx81
Mensajes: 134
Registrado: 23 Feb 2013 21:31

Re: Emulación del puerto $FF

Mensajepor zx81 » 03 Ene 2014 11:37

mcleod_ideafix escribió:En realidad el apaño de D7 mimetiza una octava parte del hack original. Funciona porque practicamente todos los juegos (que son muy pocos) que usan el puerto $FF lo que hacen es discernir si el bit 7 está activo o no, y esto es porque no es habitual en una pantalla de juego que toda ella esté con el flash puesto (que correspondería al bit 7 de stributos puesto a 1). Tener en cuenta que lo que detecta este puerto es el último byte de atributos leído por la ULA. Los bytes de bitmap no llegan a detectarse (en ellos sí que puede ser mucho más común el encontrarse bytes con el bit 7 a 1).


Supongo que la probabilidad de pillar un byte de pantalla es bajo, pero existir existe, ¿no?.

mcleod_ideafix escribió:La secuencia de pintado de un scan cualquiera de los 192 en los que hay imagen es: paper, borde derecho, blanking horizontal, borde izquierdo y vuelta a empezar. Este bucle sale justo cuando se ha empezado con el borde derecho, con lo que tienes unos cuantos ciclos de reloj sin contienda para actualizar el scan siguiente antes de que lo pinte la ULA.


Aprovecho esta frase para colar una pregunta de rondón, porque es una duda que tengo desde hace tiempo. Debido a mi ignorancia, no sé porqué se considera que el principio de la línea es el paper y que la secuencia es esa. Entiendo que el recorrido "natural" del haz es borde izquierdo, paper, borde derecho y blanking horizontal y eso conformaría una línea de scan completa. Pero tomando por buena esa organización (descrita por Chris Smith y mucha otra gente), si llegáramos a ver la primera línea de scan y la última (que en los TV normales no se ve) el primer scan no tendría el borde izquierdo y la última o bien llegaría hasta el borde izquierdo (de la anterior) y el resto sería negra o bien el último borde izquierdo se perdería fuera del área visible.

Lo pregunto porque, aunque en el emulador lo he implementado tal y como mandan los cánones, siempre me llamó la atención que la línea de scan empezara en el paper...
Today's robots are very primitive, capable of understanding only a few simple instructions such as 'go left', 'go right' and 'build car'.
John Sladek

Empieza a jugar sin tener que compilar: Emulador JSpeccy

mcleod_ideafix
Mensajes: 925
Registrado: 13 Ene 2012 09:45

Re: Emulación del puerto $FF

Mensajepor mcleod_ideafix » 04 Ene 2014 03:13

zx81 escribió:Supongo que la probabilidad de pillar un byte de pantalla es bajo, pero existir existe, ¿no?.


Puesssssss.... tendría que repasar los cronogramas. Diría que sí, que es posible, pero por alguna razón, esto no se da, o se da como dices con muy poca probabilidad.

zx81 escribió:no sé porqué se considera que el principio de la línea es el paper y que la secuencia es esa. Entiendo que el recorrido "natural" del haz es borde izquierdo, paper, borde derecho y blanking horizontal y eso conformaría una línea de scan completa. Pero tomando por buena esa organización (descrita por Chris Smith y mucha otra gente), si llegáramos a ver la primera línea de scan y la última (que en los TV normales no se ve) el primer scan no tendría el borde izquierdo y la última o bien llegaría hasta el borde izquierdo (de la anterior) y el resto sería negra o bien el último borde izquierdo se perdería fuera del área visible.


Sí, esto tiene su explicación por la forma en la que se genera la pantalla:

En la ULA hay dos contadores:

  • Uno que cuenta píxeles, el contador horizontal, que en el Spectrum cuenta de 0 a 447 y vuelta a empezar. La máquina de estados del interior de la ULA divide esas 448 cuentas en varias partes: 256 cuentas que se corresponden con los píxeles direccionables (si estamos en región de paper, si no, se pinta ahí el color del borde), luego una parte que corresponde al borde derecho, otra que corresponde al blanking con su sincronismo horizontal, y por último otra que corresponde al borde izquierdo.
  • El otro es el contador vertical, o de líneas, que en el Spectrum cuenta de 0 a 311 y vuelta a empezar. De nuevo, la máquina de estados de la ULA divide estas 312 cuentas en varias secciones: 192 líneas de paper, borde inferior, blanking vertical, y borde superior.

Es decir, la ULA genera una imagen como ésta. La parte de color negro es el intervalo de blanking. La parte en gris es señal de sincronismo (horizontal o vertical). La raya de color amarillo pegada al borde inferior es la duración del pulso de INT a la CPU (ahí en realidad debería llegar hasta una cuarta parte de la longitud de un scan de la pantalla, pero por un bug en el diseño Verilog que da lugar a esa imagen, la señal cubre toooooda al longitud del scan. La imagen de hecho corresponde a una simulación hardware del Harlequin 128K, primera versión, que diseñó Antonio Villena, con Superfo y un servidor :) )

Imagen
(NOTA: esta imagen tiene, como es de suponer 448 pixeles x 312 pixeles).

Por tanto, cuando los contadores tienen ambos el valor 0,0 estamos direccionando el pixel de paper de más arriba-izquierda, y así sucesivamente. ¿Para qué esto? Pues sencillamente para poder derivar, desde los valores de los dos contadores, de una forma muy sencilla, qué dirección de pantalla hay que leer para obtener los pixeles a pintar.

¿Y esto no influye en cómo se ve en la tele? Para nada. La tele no sabe que la ULA ha comenzado la generación "a medio camino". Ella se espera simplemente a las señales de sincronismo para saber dónde y cuando pintar. De esa forma ves la imagen correcta. La ves correcta excepto.... ¡el primer frame que la ULA genera nada más ser encendida! Ese frame comenzará como decimos, a "medio camino", y la tele no será capaz de procesarlo. Afortunadamente, en menos de 20 milisegundos, se ha recorrido toda la imagen y se ha llegado a la generación del pulso de sincronismo vertical, a partir del cual, la tele "engancha" la señal de video y comienza a dibujar la imagen donde debe.

Si la tele nos dejara ver toooooda la señal de video, incluyendo blankings y demás, y tardara 0 segundos en volver desde el lateral derecho al izquierdo, es esto lo que veríamos. Aquí la imagen se ha desplazado teniendo en cuenta que la tele, en cuanto recibe la señal de sincronismo horizontal, empieza a volver a la izquierda. Si tarda 0 segundos en volver, el blanking tras el sincronismo ocurre a la izquierda de la imagen. Analogamente, cuando recibe la señal de sincronismo vertical, empieza a volver para arriba, y las líneas de blanking que existen tras ella se pintan las primeras arriba del todo.

Imagen

Verás que hay un momentito en el que no hay ni una sola señal de sincronismo durante varias líneas: esto ocurre, tomando como referencia el último dibujo, en las primeras líneas, que aún son blanking (verás que no hay sincronismo horizontal en ellas, de hecho es el único momento en que no existe sincronismo horizontal). Esto es una violación del estándar PAL (una de ellas en que incurre el sistema de video del Spectrum) y hace que algunas teles pierdan sincronismo por culpa de esto, no dejando ver la imagen bien (o nada en absoluto).

Luego hay otras cosas curiosas, como que el borde izquierdo es un poco más estrecho que el borde derecho. Normalmente el borde derecho se "pierde" en la pantalla (no se ve entero) y la imagen resultante queda centrada y con los bordes laterales iguales. Si pones el Spectrum en una tele LCD, en donde no se pierde ni un pixel, verás que no son iguales.

El intervalo de blanking (back porch) tan grande que hay detrás del sincronismo horizontal (a la izquierda de la imagen) realmente no ocupa espacio en la pantalla, ya que ese tiempo es precisamente el que consume la tele en volver del lateral derecho al izquierdo. Fíjate que el blanking anterior al sincronismo (front porch) es muy muy pequeño en comparación. Lo mismo pasa con el blanking vertical antes y después del sincronismo vertical. Después de dicha señal de sincronismo, el intervalo de blanking es mucho mayor (varias líneas completas)
Cada vez que se implementa un sistema clásico en FPGA, Dios mata a un purista.

Avatar de Usuario
zx81
Mensajes: 134
Registrado: 23 Feb 2013 21:31

Re: Emulación del puerto $FF

Mensajepor zx81 » 04 Ene 2014 18:18

Gracias mcleod_ideafix por tan detallada respuesta y a las horas que la hiciste... :D

Aviso a los incautos, tocho inside!. -bRick

mcleod_ideafix escribió:
zx81 escribió:Supongo que la probabilidad de pillar un byte de pantalla es bajo, pero existir existe, ¿no?.


Puesssssss.... tendría que repasar los cronogramas. Diría que sí, que es posible, pero por alguna razón, esto no se da, o se da como dices con muy poca probabilidad.


Pensándolo bien no, no es posible que coja un byte de la pantalla. No tengo clara toda la secuencia de lo que hace el hardware, y tampoco tengo el libro de Chris Smith (de todas formas probablemente no lo entendería), pero me baso en que la ULA necesita coger dos bytes en secuencia desde la RAM de video, uno para la pantalla y el otro para el atributo y de ahí la secuencia de contención que tienen el 48k y los 128k/+2 (no los +2a/+3 que tienen otra, como bien sabes) que es 6,5,4,3,2,1,0,0. Eso hace presuponer que un IN del Z80 nunca va a acceder en medio de la contención y entonces el único byte que es posible coger por error es el de atributo. Hasta donde yo sé, un acceso a la RAM cuesta 3 ciclos, de modo que los 6 del peor caso es la suma de los dos accesos. Lo que desconozco es cuanto le cuesta a la RAM quitar el byte del bus, o si es que se solapa el último ciclo de la RAM con el primero sin contención o algo similar.

Pero cada vez que pienso en estos números, nada me cuadra. La teoría que yo conozco se basa en un axioma y una regla:

Axioma: el haz de electrones no espera ni a nada, ni a nadie. Pase lo que pase continua su infatigable recorrido a lo largo de la pantalla, como si no hubiera mañana.

Regla: La ULA tiene una especie de latches donde almacena los bytes de pantalla/atributos que va a lanzar, usando de alguna forma el latch del byte de pantalla un desplazador que va enviando los bits uno a uno, empezando por el bit 7 a razón de 2 píxeles por ciclo de reloj, es decir, tarda 4 ciclos en enviar un byte completo. Según todas las especificaciones el primer byte se escribe en el ciclo 14336 (48k) y el primer ciclo de contención es el 14335 (¿?). O algo no entiendo (probable) o algo se me escapa (posible). Antes de tirar el primer byte a la pantalla necesita haber leído primero los bytes de bitmap/atributo y eso son 6 ciclos, así que eso de leer justo un ciclo antes no me encaja. Y no puede empezar a lanzar nada a la pantalla sin haber leído también el atributo. Especulo con que el proceso empieza alrededor del ciclo 14330 o 14328 por seguir con el esquema exacto de contención. Lo que dicen los tests conocidos, como el btime, es otra cosa, pero de verdad que no me cuadra que haya un solo ciclo cuando necesitas seis solo para leer la memoria. Porqué la contención llega a 7 en los +2a/+3 ya se me escapa (quizá porque maneja de verdad las señales MREQ/IORQ).

mcleod_ideafix escribió:
zx81 escribió:no sé porqué se considera que el principio de la línea es el paper y que la secuencia es esa. Entiendo que el recorrido "natural" del haz es borde izquierdo, paper, borde derecho y blanking horizontal y eso conformaría una línea de scan completa. Pero tomando por buena esa organización (descrita por Chris Smith y mucha otra gente), si llegáramos a ver la primera línea de scan y la última (que en los TV normales no se ve) el primer scan no tendría el borde izquierdo y la última o bien llegaría hasta el borde izquierdo (de la anterior) y el resto sería negra o bien el último borde izquierdo se perdería fuera del área visible.


Sí, esto tiene su explicación por la forma en la que se genera la pantalla:

En la ULA hay dos contadores:
...


Gran y detallada explicación, muchas gracias. Es una lástima que no tengamos una especie de Wiki en español donde se pudieran poner esas explicaciones disponibles para todo el mundo. Tendré que imprimirla y archivarla (tengo una carpeta llena de documentos que he ido encontrando acerca del Spectrum), o algún día sabré que hablé contigo de esto pero no seré capaz de encontrarla...
Today's robots are very primitive, capable of understanding only a few simple instructions such as 'go left', 'go right' and 'build car'.
John Sladek

Empieza a jugar sin tener que compilar: Emulador JSpeccy

Avatar de Usuario
antoniovillena
Mensajes: 122
Registrado: 18 Ago 2012 13:06

Re: Emulación del puerto $FF

Mensajepor antoniovillena » 04 Ene 2014 19:35

Tengo que decir que sí que puede leer un byte de la pantalla, con la misma probabilidad de coger uno de atributo. Normalmente durante el paso del haz de electrones por la zona de pantalla (no durante el borde ni los retrazos horizontal y vertical) hay una secuencia de 8 ciclos que se repite. Durante esos 8 ciclos en los últimos cuatro si se lee del puerto flotante se obtendrá siempre $FF. La gracia está en los 4 primeros ciclos donde sí se leerá algo en el puerto flotante. Concretamente un byte de pantalla en los ciclos 1 y 3 y un byte de atributo en los ciclos 2 y 4.

La razón por la que se pone la resistencia en D7 y no en otro sitio es porque la probabilidad de que haya un cero leyendo del puerto flotante es más alta aquí que en ningún otro bit. Concretamente la mitad de las veces en que no se lee un FF se lee un atributo y que casi con toda seguridad será un 0. Si tenemos en cuenta que el haz de electrones pinta durante el 60% del tiempo, podemos decir que durante el 30% del tiempo se lee un 0 con el 75% de probabilidad y el 70% restante se leerá un 1 del bit 7. Más o menos se lee un cero el 22% de las veces, bastante alto con respecto al 15% de los otros 6 bits inferiores (el bit D6 tendrá algo entre un 15 y un 22%).

Digo esto con tanta seguridad porque estoy haciendo un motor de sprites en el que necesito sincronizarme justo en un punto en concreto del frame, al comienzo del borde inferior para poder pintar más sprites sin parpadeos. Conociendo la secuencia, poniendo una línea fija de 8 filas de pixeles (1 fila de caracteres) que se vería siempre negro y con una simple rutina es posible tal sincronización.

Avatar de Usuario
zx81
Mensajes: 134
Registrado: 23 Feb 2013 21:31

Re: Emulación del puerto $FF

Mensajepor zx81 » 04 Ene 2014 22:44

antoniovillena escribió:Tengo que decir que sí que puede leer un byte de la pantalla, con la misma probabilidad de coger uno de atributo. Normalmente durante el paso del haz de electrones por la zona de pantalla (no durante el borde ni los retrazos horizontal y vertical) hay una secuencia de 8 ciclos que se repite. Durante esos 8 ciclos en los últimos cuatro si se lee del puerto flotante se obtendrá siempre $FF. La gracia está en los 4 primeros ciclos donde sí se leerá algo en el puerto flotante. Concretamente un byte de pantalla en los ciclos 1 y 3 y un byte de atributo en los ciclos 2 y 4.


No voy a discutir a alguien que considero sabe más que yo. Pero eso no es óbice para que haga una reflexión de las mías, o sea, sin sentido ni razón.... :D

Cuando la CPU intenta leer un byte de I/O en medio de una secuencia de contención, la ULA quita el reloj a la CPU (a lo bruto, menos en el +2a/+3 que usa la señal WAIT civilizadamente) y no se lo vuelve a dar hasta que acaba. Como siempre lee los dos bytes en rápida secuencia, nunca le da el reloj a la CPU en medio de la contención, que sería la condición necesaria para que se pudiera leer el byte de pantalla.

A ver qué dice mcleod_ideafix acerca de este tema que me parece de lo más interesante... :)
Today's robots are very primitive, capable of understanding only a few simple instructions such as 'go left', 'go right' and 'build car'.
John Sladek

Empieza a jugar sin tener que compilar: Emulador JSpeccy

mcleod_ideafix
Mensajes: 925
Registrado: 13 Ene 2012 09:45

Re: Emulación del puerto $FF

Mensajepor mcleod_ideafix » 04 Ene 2014 22:50

zx81 escribió:Eso hace presuponer que un IN del Z80 nunca va a acceder en medio de la contención

De hecho, el truco del puerto $FF consiste precisamente en que ese puerto no está contenido y por tanto el Z80 ejecuta esa instrucción sin tener en cuenta ningún tipo de contención, con lo que el momento en que el byte se lee puede caer en cualquier momento, incluyendo el momento en que la ULA está leyendo desde memoria el byte de bitmap.

zx81 escribió:Hasta donde yo sé, un acceso a la RAM cuesta 3 ciclos

Eso es lo que cuesta un ciclo de bus de acceso de RAM por parte de la CPU, pero la ULA no usa ese tipo de ciclo, sino que usa un acceso por ráfaga (pone RAS bajo, y luego hace dos pulsos de CAS sin subir en medio RAS).
, de modo que los 6 del peor caso es la suma de los dos accesos. Lo que desconozco es cuanto le cuesta a la RAM quitar el byte del bus, o si es que se solapa el último ciclo de la RAM con el primero sin contención o algo similar. La lectura de 2 bytes de memoria en sí misma tarda unos 3 ciclos de reloj de la ULA (el reloj de la ULA es de 7MHz)

zx81 escribió:...a razón de 2 píxeles por ciclo de reloj, es decir, tarda 4 ciclos en enviar un byte completo

Para estos casos es mejor hablar en términos del reloj de la ULA, de donde toda la sincronización proviene, y decir que se envía un pixel en cada ciclo de reloj (de la ULA).

zx81 escribió:Según todas las especificaciones el primer byte se escribe en el ciclo 14336 (48k) y el primer ciclo de contención es el 14335 (¿?). O algo no entiendo (probable) o algo se me escapa (posible). Antes de tirar el primer byte a la pantalla necesita haber leído primero los bytes de bitmap/atributo

Exacto, pero eso no es todo. A ver si el siguiente mega-cronograma ayuda algo (y de paso corrijo algunas deficiencias de mi anterior explicación). Es de una implementación temprana de la ULA / ULAplus (¡de Mayo del 2012!), pero los tiempos son (eso creo) los mismos:

Imagen
(supongo que es mejor que lo abras en otra ventana mientras mantienes esta, para la explicación)

Lo que se ve ahí es el tiempo que se tarda en pintar unos 36 píxeles, pero no 36 píxeles cualquiera, sino un grupito de ellos que comienza casi al final del borde izquierdo y termina ya dentro de lo que es paper. Adelanto que el primer pixel que es realmente "paper" es el 13.

El reloj de la ULA es la señal, clk7. En mi diseño, clk7 se deriva de otros dos relojes más "finos", clk28 y clk14, que me permiten conseguir un diseño síncrono en la FPGA sin variar la temporización, aunque el diseño original de la ULA era asíncrono. Uso como señal base la señal de reloj clk7, pero además uso la señal SEQ me dice en qué "cachito" del reloj clk7 estoy (divido el ciclo en 4).

A continuación están los dos contadores, hc (horizontal counter) y vc (vertical counter). Como ves, estamos contando de 0 a 36 en el horizontal, y en el vertical tenemos la línea 1 (la segunda línea de paper, ya que comenzamos en 0). En el cronograma incluso llega a verse como el contador horizontal llega a 447 de la línea anterior, y justo después pasa a 0, a la vez que el contador vertical pasa de 0 a 1. Eso significa que estamos casi en la esquinita superior izquierda de la zona de paper (aunque ahora veremos que esto no es realmente así...)
Pero... ¿no habíamos quedado en que cuando el contador horizontal vale 0, estamos pintando el primer pixel de paper de un scan? Pues va a ser que no. Hay un retraso entre que lo que valen los contadores y el pixel que se está pintando en pantalla. En realidad el primer pixel (el que tiene coordenada X=0 dentro de un scan) se pinta un poco más tarde, cuando el contador horizontal ya vale 13. ¿Por qué? Sigamos mirando...

En el grupo "Control" tenemos a viden_n. Esta señal se dispara 8 píxeles después de que el contador horizontal haya vuelto a ser 0. Cuando esta señal es 0, se pueden generar las señales sload, datalatch_n y attrlatch_n. Si no, la única señal que se genera es aolatch_n.

Tanto para bitmap como para atributo hay dos latches, en plan cola. El primero acepta datos desde la memoria, mientras el segundo se está usando para pintar en pantalla. Cuando se termina con este segundo, el contenido del primero pasa al segundo, y el primero queda libre para aceptar un nuevo dato desde memoria. El primer latch de bitmaps se activa con datalatch_n y el primer latch de atributos, con attrlatch_n. Los segundos latches se activan, el de bitmap con sload (este latch es en realidad un registro de desplazamiento con carga en paralelo), y el de atributos con aolatch_n.

De momento dejemos los latches y continuemos con aolatch_n. Como ves, ésta se activa cada 8 píxeles, y su cometido es, como hemos adelantado, habilitar el paso del byte de bitmap y atributos de un primer latch a un segundo, que es el que realmente se usa para pintar el pixel que estás viendo AHORA.

Lo curioso de aolatch_n es que da igual que haya border o paper, ella se activa cada 8 píxeles. El dato que acepta el segundo latch de atributos es o bien un byte de parte del primer latch, que fue cargado de memoria con datalatch_n, o bien un byte generado a partir del valor actual de los 3 bits bajos del puerto 254, que contienen el valor del borde. En este último caso, los bits 0 a 2, y 6 a 7 del segundo latch se cargan con ceros. El color de borde se carga en los bits 3 a 5 del segundo latch, que se corresponde con el color de paper.

Ahora, veamos a cámara lenta lo que pasa:

Estamos generando el borde izquierdo. El segundo latch de atributos contiene el color del mismo. El segundo latch de bitmap (el registro de desplazamiento) contiene ceros. Entonces, el contador horizontal pasa a valer 0, indicando el comienzo de un nuevo scan y por consiguiente, ya no se debería seguir generando el borde, pero los contenidos de los segundos latches no han cambiado aún, por lo que el borde se sigue dibujando hasta nuevo aviso, al menos hasta el pixel 8. Recordemos que el segundo latch de bitmap contiene ceros, así que lo que se pinta es un color de paper, no de ink, que se corresponde con lo que haya en los bits 3 a 5 del segundo latch de atributos, más el bit 6 de brillo, más el bit 7 de flash (estos dos últimos valen 0).

En el pixel 8, viden_n pasa a 0 y comienza la secuencia de carga de bitmap/atributos. Se carga el primer byte de bitmap en el latch de entrada de bitmap, y el primer byte de atributos en el primer latch de atributos. Los segundos latchs aún no han sido tocados, así que se sigue pintando el borde. La señal video_read_in_progress (no presente en la ULA original, esta es cosa mía) indica, cuando vale 1, que hay un ciclo de bus de lectura de RAM en progreso.

Del pixel 8 al 12, inclusive: se activan secuencialmente estas señales: datalatch_n (carga un valor de bitmap desde la RAM), attrlatch_n (carga un valor de atributos desde la RAM) y por último, al final del pixel 12, se activan a la vez sload y aolatch_n: los datos cargados en los primeros latches pasan a los segundos latches.

Pixel 13: comienzan a verse en pantalla los píxeles y atributos cargados. Es decir, comenzamos a ver el pixel de coordenada X=0 y los siguientes. El borde izquierdo, por tanto, ha "durado" 13 píxeles más de la cuenta. Mira las señales RGB en el grupo "Ouput", y verás que a partir del pixel 13 empiezan a verse cambios en los niveles RGB, que hasta ahora se habían quedado estables.

Píxeles 13 a 15: Como los primeros latches se han quedado libres, se carga en ellos una nueva tacada de 8 píxeles + atributos. Los usaremos 8 ciclos más tarde.

Si bajas la vista, en el grupo "DRAM Control", puedes ver el ciclo de lectura en modo página que hace la ULA a la DRAM. En los 8 ciclos de reloj (de ULA) que ha durado todo esto (del pixel 8 al 15 incusive) se han realizado 4 lecturas a memoria, agrupadas en dos ráfagas. En cada ráfaga, RAS se queda a 0, y es CAS la que baja y sube dos veces.

Después de estas dos ráfagas, la señal video_read_in_progress baja a 0 para indicar que no hay accesos a memoria. Queda a nivel bajo durante otros 8 píxeles, en los que la memoria no se usa.

8 píxeles más tarde, en el pixel 21, ocurre un nuevo pulso de aolatch_n y se transfieren a los segundos latches esos nuevos 8 píxeles que habíamos cargado durante los píxeles 13 a 15.

Un poquito más tarde (comenzando en el pixel 24), y antes de que ocurra de nuevo aolatch_n, se vuelven a cargar otros 8 píxeles, comenzando de nuevo el proceso que habíamos descrito en "Del pixel 8 al 12". Entre aquel momento y éste han pasado 16 ciclos (píxeles). Es el trozo de cronograma que está enmarcado entre los dos cursores, y que según el cronograma, dura 2,285 microsegundos.

Así que, en definitiva, aunque un pixel se pinte en cierto ciclo, ha tenido que ser leído unos cuántos ciclos antes (4, para ser exactos).

zx81 escribió:pero de verdad que no me cuadra que haya un solo ciclo cuando necesitas seis solo para leer la memoria. Porqué la contención llega a 7 en los +2a/+3 ya se me escapa (quizá porque maneja de verdad las señales MREQ/IORQ).

El objetivo de la contención es evitar que un ciclo de memoria de la CPU se solape aunque sea en un ciclo, con un ciclo de lectura de RAM desde la ULA. Te puedo explicar cómo funciona la contención en el 48K, no en el +3.

Hablaremos siempre en términos de reloj de ULA. En esos términos, la CPU usa 6 ciclos de reloj para acceder a memoria. Recordemos además que la CPU tiene un reloj que cambia cada dos cambios del reloj de la ULA, que es lo mismo que decir que la CPU tiene un reloj que cambia en los ciclos 0,2,4,6, etc de la ULA.

Por otra parte, la ventana en la que la ULA usa a la DRAM es de 8 ciclos de reloj de los 16 que consta este ciclo que hemos explicado, así que a la CPU le quedan para su uso otros 8 ciclos (de ULA). Hablaremos de una ventana de uso de la CPU de 8 ciclos, numerados del 0 al 7. La CPU puede comenzar una operación en los ciclos 0,2, 4 o 6 de esa ventana.

Si una operación de lectura de la CPU comienza en el ciclo 0 de esa ventana, no hay contención (termina en el ciclo 5 de la ventana). Contención = 0 ciclos.

Si comienza en el ciclo 2 de esa ventana (en el ciclo 1 no puede empezar por lo que hemos dicho), tampoco hay contención (termina en el ciclo 7). Contención = 0 ciclos.

Pero si comienza en el ciclo 4 de esa ventana, la ULA sabe que "chocará" con ella, así que la prolonga durante los 4 ciclos que quedan + los 8 ciclos de acceso de la ULA. Eso da un total de 12 ciclos de ULA de contención, lo que traducido a ciclos de reloj de CPU, nos da 6.

Analogamente, si comienza la operación en el ciclo 6 de la ventana, la ULA prolonga el ciclo por 2 + 8 = 10 ciclos -> 5 ciclos de CPU.
Por suuesto, si la operación de acceso se intenta hacer cuando de verdad está la ULA leyendo un dato de DRAM (ciclo 8, que ya caeria "fuera" de la ventana), la operación se posterga un máximo de 8 ciclos y un mínimo de 2. Esto da una espera de entre 4 y 1 ciclos de CPU.

De ahí sale el patrón de contención: 6,5,4,3,2,1,0,0,6,5,4,3,2,1,0,0,....
Cada vez que se implementa un sistema clásico en FPGA, Dios mata a un purista.


Volver a “Software Spectrum”

¿Quién está conectado?

Usuarios navegando por este Foro: sinclair200 y 1 invitado