Emulación del puerto $FF

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

Re: Emulación del puerto $FF

Mensajepor mcleod_ideafix » 04 Ene 2014 22:50

Último mensaje de la página anterior:

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.

Avatar de Usuario
ron
Mensajes: 16947
Registrado: 28 Oct 2010 14:20
Ubicación: retrocrypta
Agradecido : 427 veces
Agradecimiento recibido: 445 veces

Re: Emulación del puerto $FF

Mensajepor ron » 04 Ene 2014 22:53

Empecé desde el principio a leer el hilo y se ha convertido en una clase magistral. Enhorabuena a todos los participantes porque lo aprendido y asimilado es muy interesante y educativo.
Y seguro que salen más detalles, muy agradecido y es que dan ganas de leer más respuestas.

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

Re: Emulación del puerto $FF

Mensajepor antoniovillena » 04 Ene 2014 23:02

zx81 escribió: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... :)


No hombre discute lo que quieras, he intervenido porque tengo el tema más reciente en el sentido de tener que hacer una rutina de sincronización. No he leído todavía la respuesta de McLeod, probablemente lo explique todo mejor que yo, pero las lecturas a puerto distintas a la ULA (puerto A0=1) no se ven afectadas por la contención. Por tanto la probabilidad de leer un attributo durante la ráfaga de 128 ciclos de pintado de una línea (sin contar bordes) es de 2/8 para el bitmap, 2/8 para los atributos y 4/8 son lecturas a $FF. Otra cosa muy distinta y es la contención en memoria, ahí te tengo que dar la razón en el sentido de que la rutina que hice para detectar si hay o no puerto flotante no me funcionaba. La razón era que estaba ubicada en memoria baja y al sincronizarse con los ciclos de contención siempre leía $FF del puerto a pesar de estar en un 48K con puerto flotante. Fue cambiar de ubicación la rutina y se arregló el problema.

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

Re: Emulación del puerto $FF

Mensajepor mcleod_ideafix » 04 Ene 2014 23:13

zx81 escribió: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.

Eso es cierto... si el puerto de E/S está contenido, es decir, si tiene el bit A0 = 0, pero eso no ocurre con el puerto $FF, por lo que nada ni nadie para a la CPU mientras se está leyendo ese "puerto". De hecho, y como indiqué antes, ahí está el truco del puerto $FF y el por qué lee lo que lee. Si no fuera así, jamás leería nada, ni bitmap ni atributos.

El por qué lo que se lee son siempre, o casi siempre atributos y no bitmaps viene por el tiempo en que está cada cosa en el bus de datos. Mira el cronograma: el tiempo en que el valor de bitmap está presente en el bus de datos es poco menos de un ciclo de reloj de ULA (medio ciclo de reloj de CPU).

En cuanto al byte de atributos, resulta que es el último que se recoge en cada secuencia de 8 ciclos. Recuerda que en cada secuencia leemos dos bytes de bitmaps y otros dos de atributos. En la primera secuencia por tanto leemos los atributos de las posiciones de caracter 0 y 1, en la segunda secuencia, las posiciones 2 y 3, y así sucesivamente. Son los valores de los atributos de las posiciones 1,3,5,7, etc, los que permanecen más tiempo. ¿Permanecen?

Después de cargar el valor del atributo comienza un laaaaargo periodo de 8 ciclos de reloj de ULA en donde no se accede a memoria. Lo lógico sería suponer que la mitad de los accesos del puerto $FF van a caer en este intervalo, y aquí no hay memoria que se esté leyendo, por lo que debería devolverse el valor de alta impedancia del bus, es decir 255 (UPDATE: y es así, de hecho).

¿Cómo entonces es que podemos leer un valor nadie está suministrando? Mi conjetura (porque no lo he podido medir ni nada) es que lo que se lee en ese periodo es el valor residual almacenado por la propia capacitancia del bus de datos. Veamos qué es eso:
Cuando el Z80 no está escribiendo un dato al bus de datos, mantiene su bus de datos en modo input (aunque en un momento dado no haga caso a lo que hay en ese bus de datos, sólo es una forma de ponerse en modo de alta impedancia). Durante el periodo de 8 ciclos que estamos hablando, el bus de datos de la ULA está también en alta impedancia. Así que justo cuando la memoria está entregando su byte de atributos a la ULA, lo está haciendo también a la CPU, pero atravesando una resistencia (la que separa ambos buses de datos: CPU y ULA) para terminar en el driver de entrada de la CPU, que al ser NMOS, es un transistor de puerta aislada, lo que hace que se comporte como un pequeño condensador.

¿Qué tenemos? Pues para cada bit del bus de datos, un circuito RC que actúa como una especie de circuito sample-and-hold, siendo la puerta del driver NMOS de cada bit del bus de datos de la CPU el encargado de retener el dato. Muy poco tiempo, pero el suficiente como para que aguante hasta que termina el ciclo de bus de I/O (4 ciclos de reloj de CPU, 8 ciclos de ULA nada menos).
Cada vez que se implementa un sistema clásico en FPGA, Dios mata a un purista.

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

Re: Emulación del puerto $FF

Mensajepor antoniovillena » 04 Ene 2014 23:32

Acabo de leer el post de McLeod, no tiene desperdicio. Sólo decir que todo esto lo sabemos gracias al trabajo de Chris Smith que plasmó en su famoso libro. El spectrum es una máquina sencilla aunque ingeniosamente diseñada, una vez la entiendes aprendes un montón.

Ya que lo he mencionado os muestro la demo:

http://www.mojontwins.com/mojoniaplus/d ... hp?id=4180

La demo en cuestión dibuja los sprites con 3 implementaciones distintas para solventar el problema del parpadeo.
1. En una primera implementaciones emplea el puerto flotante para sincronizarnos en el ciclo 50000 más o menos, por lo que tendremos unos 20000 ciclos hasta el final del frame más los 14300 ciclos de comienzo del siguiente frame para pintar sprites libres de parpadeo. Con esto podemos mostrar 8 sprites en pantalla cada frame (a 50fps) sin parpadeos, vamos que en 34000 ciclos da tenemos el tiempo justo para borrar 8 sprites y pintarlos nuevamente.

2. En una segunda implementación hacemos uso del shadow screen o pantalla sombra. Nos sincronizamos con el ciclo 0 del frame vía interrupción y en cada frame conmutamos la página visible entre la 5 (la de toda la vida) y la 7 (la pantalla alternativa). La idea es que siempre escribimos sobre la pantalla que no se ve (pantalla sombra) de tal forma que cuando la mostramos no se aprecia ningún parpadeo. Como evitamos al 100% el parpadeo, en teoría podríamos gastar los 70000 ciclos del frame pintando y borrando sprites lo que nos da para unos 16 sprites a la vez sin parpadeo.

3. En una tercera aproximación sincronizamos vía interrupciones con el ciclo 0 y pintamos los sprites que nos dé tiempo durante los 14300 ciclos iniciales. Esta es la peor aproximación, da para imprimir 4 sprites sin parpadeos, el resto parpadearán dependiendo de la posición. Si los sprites están en la parte baja de la pantalla se evita el parpadeo, con lo que imponiendo algunas restricciones en el juego (por ejemplo colocar los enemigos en la parte baja) podríamos tener 6 ó 7 sprites sin problemas.

Lo que hace la demo es detectar el puerto flotante y los 128K al comienzo y en función de eso elegir una implementación con la que mostrar la demo. Si se dan las 2 cosas a la vez (el spectrum 128K tostadora) se elige la segunda implementación ya que es la que ofrece mejores resultados. Si se da una de las 2 cosas pues se opta por la primera o segunda implementación. Por último si no se da nada de eso (en caso de algunos clones como el Inves Spectrum) escogemos la tercera implementación.

Si tenéis un poco de idea de ensamblador os recomiendo que depuréis la demo bajo emulador sobre distintas máquinas. Lo interesante está en el modelo 48K, en este fragmento de código que sincroniza más o menos sobre el ciclo 50000, es muy probable que encontréis dicho código si mostráis el depurador de forma aleatoria (se tira muchos ciclos esperando durante esta rutina).

Código: Seleccionar todo

        DEFINE  sylo  $66
        DEFINE  syhi  $c0
..
do0     ld      bc, syhi | sylo<<8
do1     in      a, ($ff)
        cp      b
        jp      nz, do1
        ld      b, 9
do2     in      a, ($ff)
        cp      c
do3     jr      z, do5
        djnz    do2
        jr      do0

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

Re: Emulación del puerto $FF

Mensajepor antoniovillena » 04 Ene 2014 23:42

mcleod_ideafix escribió:¿Cómo entonces es que podemos leer un valor nadie está suministrando? Mi conjetura (porque no lo he podido medir ni nada) es que lo que se lee en ese periodo es el valor residual almacenado por la propia capacitancia del bus de datos. Veamos qué es eso:
Cuando el Z80 no está escribiendo un dato al bus de datos, mantiene su bus de datos en modo input (aunque en un momento dado no haga caso a lo que hay en ese bus de datos, sólo es una forma de ponerse en modo de alta impedancia). Durante el periodo de 8 ciclos que estamos hablando, el bus de datos de la ULA está también en alta impedancia. Así que justo cuando la memoria está entregando su byte de atributos a la ULA, lo está haciendo también a la CPU, pero atravesando una resistencia (la que separa ambos buses de datos: CPU y ULA) para terminar en el driver de entrada de la CPU, que al ser NMOS, es un transistor de puerta aislada, lo que hace que se comporte como un pequeño condensador.


No soy consciente de dicho fenómeno, pensaba que durante esos 4 ciclos las lecturas siempre devolvían $FF. No obstante te puedes quitar las dudas probando el ulatest3 de Jan Bobrowski desde este link y cargarlo en un spectrum real (gomas o plus):

http://wizard.ae.krakow.pl/~jb/qaop/t/ulatest3.tap

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

Re: Emulación del puerto $FF

Mensajepor mcleod_ideafix » 04 Ene 2014 23:47

antoniovillena escribió:No soy consciente de dicho fenómeno, pensaba que durante esos 4 ciclos las lecturas siempre devolvían $FF. No obstante te puedes quitar las dudas probando el ulatest3 de Jan Bobrowski desde este link y cargarlo en un spectrum real (gomas o plus):

¡Retiro lo dicho! (era una conjetura de todas formas :) ). En ese test, si lo interpreto bien, parece que el puerto $FF puede leer tanto bitmap, como atributo, como bus flotante. De los tests que he usado para la ULAplus, ese es el que menos he entendido, más que nada porque no sé qué valores son 40,41,42, etc. Los valores 00,01,02, etc entiendo que son valores de bitmap. Los otros, ¿son valores de atributos?
Cada vez que se implementa un sistema clásico en FPGA, Dios mata a un purista.

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

Re: Emulación del puerto $FF

Mensajepor antoniovillena » 04 Ene 2014 23:52

mcleod_ideafix escribió:¡Retiro lo dicho! (era una conjetura de todas formas :) ). En ese test, si lo interpreto bien, parece que el puerto $FF puede leer tanto bitmap, como atributo, como bus flotante. De los tests que he usado para la ULAplus, ese es el que menos he entendido, más que nada porque no sé qué valores son 40,41,42, etc. Los valores 00,01,02, etc entiendo que son valores de bitmap. Los otros, ¿son valores de atributos?


Exacto. Fíjate en la parte de arriba de la pantalla (primeras 8 líneas).

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

Re: Emulación del puerto $FF

Mensajepor zx81 » 05 Ene 2014 01:12

mcleod_ideafix escribió:
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.


Touche!, tanta teoría y no me doy cuenta de lo más evidente, que nada de eso se aplica a los puertos impares. Brillante por mi parte... :(

Respecto al resto del mensaje, tendré que leerlo más tranquilamente mañana porque es simplemente abrumador... :-O
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
zx81
Mensajes: 134
Registrado: 23 Feb 2013 21:31

Re: Emulación del puerto $FF

Mensajepor zx81 » 05 Ene 2014 01:22

antoniovillena escribió:
mcleod_ideafix escribió:¿Cómo entonces es que podemos leer un valor nadie está suministrando? Mi conjetura (porque no lo he podido medir ni nada) es que lo que se lee en ese periodo es el valor residual almacenado por la propia capacitancia del bus de datos. Veamos qué es eso:
Cuando el Z80 no está escribiendo un dato al bus de datos, mantiene su bus de datos en modo input (aunque en un momento dado no haga caso a lo que hay en ese bus de datos, sólo es una forma de ponerse en modo de alta impedancia). Durante el periodo de 8 ciclos que estamos hablando, el bus de datos de la ULA está también en alta impedancia. Así que justo cuando la memoria está entregando su byte de atributos a la ULA, lo está haciendo también a la CPU, pero atravesando una resistencia (la que separa ambos buses de datos: CPU y ULA) para terminar en el driver de entrada de la CPU, que al ser NMOS, es un transistor de puerta aislada, lo que hace que se comporte como un pequeño condensador.


No soy consciente de dicho fenómeno, pensaba que durante esos 4 ciclos las lecturas siempre devolvían $FF. No obstante te puedes quitar las dudas probando el ulatest3 de Jan Bobrowski desde este link y cargarlo en un spectrum real (gomas o plus):

http://wizard.ae.krakow.pl/~jb/qaop/t/ulatest3.tap


De ese test yo tengo una versión más de reveladora, me ha costado encontrarla, pero está en esta página:

Serious Timing Analysis

Es una variante del ulatest3 de Jan hecha por Chris Smith (como no). Me fue muy útil mientras desarrollaba esa parte del emulador, que cuesta horrores que todo cuadre.

Ampliación del mensaje con aclaraciones que no pude poner anoche:

El ulatest3 de Jan empezó siendo un programa que solo funcionaba en el 48k, como sus otros programas de test (no perderse el btime y el stime, fundamentales para saber si el cambio de borde y el momento de la escritura en RAM se producen donde deben, tardé mucho tiempo en lograr que el stime funcionara correctamente en JSpeccy). Cuando Jan empezó el desarrollo de su segundo emulador, el que está escrito en JavaScript, los modificó para que sirvieran para 128k y +3. Pero el ulatest3-modified de Chris sigue siendo para el 48k únicamente, aviso para navegantes.

Por otro lado he verificado el tema y, efectivamente, las primeras 32 posiciones de pantalla tienen valores entre $00-$1F y los atributos son 32 posiciones de atributo con valores entre $40-$5F. Las diferencias entre contended memory y contended I/O creo que descansan en algo que debe estar explicado también en el ULA-book, porque algo dice al respecto en la web del Harlequín, y es el diferente cronograma que existe entre un acceso a RAM y un acceso a I/O.
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
zx81
Mensajes: 134
Registrado: 23 Feb 2013 21:31

Re: Emulación del puerto $FF

Mensajepor zx81 » 05 Ene 2014 01:25

mcleod_ideafix escribió:
antoniovillena escribió:No soy consciente de dicho fenómeno, pensaba que durante esos 4 ciclos las lecturas siempre devolvían $FF. No obstante te puedes quitar las dudas probando el ulatest3 de Jan Bobrowski desde este link y cargarlo en un spectrum real (gomas o plus):

¡Retiro lo dicho! (era una conjetura de todas formas :) ). En ese test, si lo interpreto bien, parece que el puerto $FF puede leer tanto bitmap, como atributo, como bus flotante. De los tests que he usado para la ULAplus, ese es el que menos he entendido, más que nada porque no sé qué valores son 40,41,42, etc. Los valores 00,01,02, etc entiendo que son valores de bitmap. Los otros, ¿son valores de atributos?


Eso podrás verlo con algún emulador que te permita examinar la memoria en tiempo real, como JSpeccy... O:-)
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
zx81
Mensajes: 134
Registrado: 23 Feb 2013 21:31

Re: Emulación del puerto $FF

Mensajepor zx81 » 05 Ene 2014 14:36

antoniovillena escribió:Acabo de leer el post de McLeod, no tiene desperdicio. Sólo decir que todo esto lo sabemos gracias al trabajo de Chris Smith que plasmó en su famoso libro. El spectrum es una máquina sencilla aunque ingeniosamente diseñada, una vez la entiendes aprendes un montón.

Ya que lo he mencionado os muestro la demo:

http://www.mojontwins.com/mojoniaplus/d ... hp?id=4180


Acabo de ver la demo (anoche estaba con la tablet y ahí no tengo emulador) y se ve fantástica, sin un solo parpadeo (tiro de emulador, no tengo sitio para poder tener montados mis Spectrums de verdad). Gran trabajo que espero veamos a no mucho tardar reflejado en un nuevo producto mojono. :D

antoniovillena escribió:La demo en cuestión dibuja los sprites con 3 implementaciones distintas para solventar el problema del parpadeo.
1. En una primera implementaciones emplea el puerto flotante para sincronizarnos en el ciclo 50000 más o menos, por lo que tendremos unos 20000 ciclos hasta el final del frame más los 14300 ciclos de comienzo del siguiente frame para pintar sprites libres de parpadeo. Con esto podemos mostrar 8 sprites en pantalla cada frame (a 50fps) sin parpadeos, vamos que en 34000 ciclos da tenemos el tiempo justo para borrar 8 sprites y pintarlos nuevamente.

2. En una segunda implementación hacemos uso del shadow screen o pantalla sombra. Nos sincronizamos con el ciclo 0 del frame vía interrupción y en cada frame conmutamos la página visible entre la 5 (la de toda la vida) y la 7 (la pantalla alternativa). La idea es que siempre escribimos sobre la pantalla que no se ve (pantalla sombra) de tal forma que cuando la mostramos no se aprecia ningún parpadeo. Como evitamos al 100% el parpadeo, en teoría podríamos gastar los 70000 ciclos del frame pintando y borrando sprites lo que nos da para unos 16 sprites a la vez sin parpadeo.

3. En una tercera aproximación sincronizamos vía interrupciones con el ciclo 0 y pintamos los sprites que nos dé tiempo durante los 14300 ciclos iniciales. Esta es la peor aproximación, da para imprimir 4 sprites sin parpadeos, el resto parpadearán dependiendo de la posición. Si los sprites están en la parte baja de la pantalla se evita el parpadeo, con lo que imponiendo algunas restricciones en el juego (por ejemplo colocar los enemigos en la parte baja) podríamos tener 6 ó 7 sprites sin problemas.

Lo que hace la demo es detectar el puerto flotante y los 128K al comienzo y en función de eso elegir una implementación con la que mostrar la demo. Si se dan las 2 cosas a la vez (el spectrum 128K tostadora) se elige la segunda implementación ya que es la que ofrece mejores resultados. Si se da una de las 2 cosas pues se opta por la primera o segunda implementación. Por último si no se da nada de eso (en caso de algunos clones como el Inves Spectrum) escogemos la tercera implementación.

Si tenéis un poco de idea de ensamblador os recomiendo que depuréis la demo bajo emulador sobre distintas máquinas. Lo interesante está en el modelo 48K, en este fragmento de código que sincroniza más o menos sobre el ciclo 50000, es muy probable que encontréis dicho código si mostráis el depurador de forma aleatoria (se tira muchos ciclos esperando durante esta rutina).

Código: Seleccionar todo

        DEFINE  sylo  $66
        DEFINE  syhi  $c0
..
do0     ld      bc, syhi | sylo<<8
do1     in      a, ($ff)
        cp      b
        jp      nz, do1
        ld      b, 9
do2     in      a, ($ff)
        cp      c
do3     jr      z, do5
        djnz    do2
        jr      do0


La ventaja de la shadow screen es que el paginado en el Spectrum es inmediato y te da mucho más tiempo para pintarlo todo Siempre te queda la opción de hacer el juego para 128k y de esa forma solo tienes un método (y acceso a un chip se sonido más "molón"). :)
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 » 06 Ene 2014 11:19

No conocía esa versión modificada de Chris, gracias por el aporte. No tengo nada que ver con los mojonos, aunque sí que he aportado algunas herramientas a la Churrera. La idea es crear un motor muy parecido al de la Churrera, D_Skywalk se va a encargar de la parte de alto nivel.

En cuanto al sonido 128K, lo mejor es detectar las características de forma independiente. Puede haber AY en un 48K, no hay que presuponer nada. Y tampoco gastan muchos ciclos las rutinas AY, casi es mejor ni hacer detección. En un 48K (sin chip AY externo) se escribiría a un puerto inexistente pero no se colgaría el juego.

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

Re: Emulación del puerto $FF

Mensajepor zx81 » 06 Ene 2014 14:07

antoniovillena escribió:No conocía esa versión modificada de Chris, gracias por el aporte. No tengo nada que ver con los mojonos, aunque sí que he aportado algunas herramientas a la Churrera. La idea es crear un motor muy parecido al de la Churrera, D_Skywalk se va a encargar de la parte de alto nivel.


A lo largo del tiempo que llevo desarrollando el emulador he ido recopilando un montón de utilidades con propósitos muy diversos que, en algunos casos, son imposibles de encontrar ahora (o muy difíciles). Alguna vez he pensado que deberían juntarse y subirse a una web o similar, como una forma de backup de mi mismo. :)

antoniovillena escribió:En cuanto al sonido 128K, lo mejor es detectar las características de forma independiente. Puede haber AY en un 48K, no hay que presuponer nada. Y tampoco gastan muchos ciclos las rutinas AY, casi es mejor ni hacer detección. En un 48K (sin chip AY externo) se escribiría a un puerto inexistente pero no se colgaría el juego.


No me refería a que detectaras el modelo por el AY, ya sé que eso no es fiable. Más bien me refería a que si se dirige el juego a 128k exclusivamente, puedes evitarte la necesidad de tener 3 métodos de gestión de sprites y, de paso, puedes generar música (porque no hay 128k sin AY).

Por cierto, no es un tema on-topic, pero anteayer estuve intentando compilar el ticks en Linux y me costó un rato. No sé si te interesa saber lo que tuve que modificar, ni si esas modificaciones son aplicables al programa directamente para que siga compilando en Windows (que no es estándar en todo, ese es el problema). Mirándolo, dan ganas de pasarlo a Java para que fuera utilizable directamente desde cualquier plataforma. ¿Tienes algún tipo de archivos que permitan hacer tests del ticks para saber si el ejecutable generado es correcto?.
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 » 06 Ene 2014 14:48

zx81 escribió:No sé si te interesa saber lo que tuve que modificar

¿A que lo adivino? Una cosa en la línea 518 que es equivalente a un if (). Algo así como (ejemplo inventado):

Código: Seleccionar todo

n>0 && pepe=0;


En lugar de:

Código: Seleccionar todo

if (n>0)
  pepe=0;


Resulta que esta parte del código, además de parecer código ofuscado (porque no optimiza nada respecto de un IF normal y corriente), en un GCC antiguo daba un casque ¡del propio compilador!

EDITO: Ah! Vale, también lo del stricmp() . strcasecmp() existe, al menos en mi compilador de Windows. Debería usarse esa versión de la función, y no stricmp() que no es estándar POSIX.
Ah! E incluir al principio del todo los includes a stdlib.h y string.h

zx81: si lo vas a portar, ten en cuenta una cosa: todo lo que es el código que maneja ficheros TAP no está teniendo en cuenta el endian de la máquina (asume litte endian). La versión en C funciona en cualquier chisme x86. En ARM habría que consultar el endian que está configurado en la máquina (son bi-endian), y en PowerPC, directamente no funcionará. No sé si en JAVA hay que tener en cuenta estas cosas o no...
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 » 06 Ene 2014 15:41

mcleod_ideafix escribió:¿A que lo adivino? Una cosa en la línea 518 que es equivalente a un if (). Algo así como (ejemplo inventado):

Código: Seleccionar todo

n>0 && pepe=0;


En lugar de:

Código: Seleccionar todo

if (n>0)
  pepe=0;


Resulta que esta parte del código, además de parecer código ofuscado (porque no optimiza nada respecto de un IF normal y corriente), en un GCC antiguo daba un casque ¡del propio compilador!

EDITO: Ah! Vale, también lo del stricmp() . strcasecmp() existe, al menos en mi compilador de Windows. Debería usarse esa versión de la función, y no stricmp() que no es estándar POSIX.
Ah! E incluir al principio del todo los includes a stdlib.h y string.h


No, lo del if no me ha dado problemas. El mayor problema era el stricmp, efectivamente. Luego tuve que añadir includes para stdlib.h, unistd.h y string.h relacionados con funciones como malloc, memcpy, strchr y alguna más. Como además tengo la "mala" costumbre de compilarlo todo con -Wall me da como 200 o 300 avisos relacionados con recomendaciones de usar paréntesis y cosas por el estilo. Sin -Wall no se queja de nada. No he podido probarlo, pero sería conveniente porque he generado un ejecutable de 64 bits. No debería ser importante, pero ya sabemos como son estas cosas. :)

mcleod_ideafix escribió:zx81: si lo vas a portar, ten en cuenta una cosa: todo lo que es el código que maneja ficheros TAP no está teniendo en cuenta el endian de la máquina (asume litte endian). La versión en C funciona en cualquier chisme x86. En ARM habría que consultar el endian que está configurado en la máquina (son bi-endian), y en PowerPC, directamente no funcionará. No sé si en JAVA hay que tener en cuenta estas cosas o no...


La estructura interna del TAP es LSB de modo que un lector debe estar escrito de forma que eso no importe (no he mirado el código de ticks, la verdad). De todas formas, lo de "portarlo" es una manera de hablar, porque yo ya tengo escrito y probado el código del core Z80 (completo, con emulación de todo lo conocido hasta ahora), el código del TAP (que ha sido probado en máquinas LSB y MSB) y para el WAV usaría una librería que ya existiera (la tengo). Vamos, que más que un port sería una especie de reescritura. Es más, de la manera que está escrito el core Z80, permitiría a alguien con unos conocimientos básicos de Java modificar partes del código para hacer lo que se quisiera a medida. Se podría, por ejemplo, emular la contended-memory sin problemas (no sé si ticks hace eso). La JVM siempre es MSB, eso simplifica algunas cosas.

Siendo sinceros, necesitaría saber cuanta gente usa ticks, si hay mucha de esa gente que lo hace en sistemas no-Windows y si la animadversión hacia Java no hiciera que el esfuerzo resultara baldío. Una cosa es hacer herramientas útiles para la comunidad y otra hacer el ganso. No parece que haya mucho interés en sistemas no-Windows porque hasta ahora nadie había dicho ni Pamplona al respecto. Y de Java, mejor no hablamos. ;)
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 » 06 Ene 2014 16:10

zx81 escribió:La estructura interna del TAP es LSB de modo que un lector debe estar escrito de forma que eso no importe

Debería, pero en este caso no es así. En el código se deferencian punteros a enteros y a short ints, simplemente haciéndolos apuntar a determinadas partes de la cabecera del TAP.

Código: Seleccionar todo

char *cabecera;
int variable;
...
...
variable = *(int *)(cabecera+24);


Por lo que lo que se lee de ellos se guarda siempre en little endian en las variables que se asignan. En un sistema big endian, la asignación dará un resultado erróneo. Por ejemplo, si en cabecera+24 se guardan los bytes AA, BB, CC y DD, el código anterior hará que en "variable" se guarde el resultado 0xAABBCCDD en un sistema big endian, y el valor 0xDDCCBBAA en un sistema little endian.

zx81 escribió:Se podría, por ejemplo, emular la contended-memory sin problemas (no sé si ticks hace eso).

Creo que no.

zx81 escribió:No parece que haya mucho interés en sistemas no-Windows porque hasta ahora nadie había dicho ni Pamplona al respecto. Y de Java, mejor no hablamos. ;)

Hombre, interés en sistemas no-Windows creo que sí hay. Lo que no sé es cuán grande es el conjunto [ usuarios de ticks  usuarios de sistemas no Windows ]. Yo hasta que AntonioVillena no lo ha dicho en un hilo de WOS sobre optimizar una rutina para hacer la función ABS() no conocía ticks, y creo que otros usuarios de WOS t ambién la han conocido allí.

De Java... es que se presta a muchos chistes :D Por ejemplo (este ya es viejo):

- Tock, tock.
- ¿Quien es?
( pausa larga...)
- JAVA
-rofl

O este otro (que no conocía):

Decir que JAVA es bueno porque se puede ejecutar en múltiples sistemas operativos es como decir que el sexo anal es bueno porque se puede practicar con todos los géneros
-rofl

No offense intended
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: No hay usuarios registrados visitando el Foro y 1 invitado