ZX-UNO: reproductor de ficheros PZX dentro del propio clon

Sinclair QL, ZX81, +2, +3, 128K ...
Avatar de Usuario
mcleod_ideafix
Amiga 2500
Amiga 2500
Mensajes: 5316
Registrado: 06 Oct 2009, 04:12
Sistema Favorito: Spectrum 16Kb/48Kb
primer_sistema: Spectrum 16Kb/48Kb
consola_favorita: Vectrex
Primera consola: TV Games/Pong Clone
Ubicación: Jerez de la Frontera
Gracias dadas: 12 veces
Gracias recibidas: 54 veces
Contactar:

Re: ZX-UNO: reproductor de ficheros PZX dentro del propio cl

Mensajepor mcleod_ideafix » 21 Jun 2015, 17:25

antoniovillena escribió:[*]Reducir la máquina de estados para que en lugar de un 15%, el reproductor PZX tenga una ocupación en FPGA sensiblemente inferior.[/list]

Ahí está el problema: que la reducción no sea suficiente. Como esto pasará por una refactorización para extraer la ruta de datos y el controlador, veré cómo queda tras ello y cuántos recursos consume. De momento te puedo contar que de los tres tipos de bloques "mínimos" que se necesitan, uno de ellos, el de pausa, se puede implementar mediante un bloque de pulsos que contenga un solo pulso de la duración especificada en el parámetro de la pausa. Así, la implementación quedaría con dos bloques "complejos": PULS y DATA y dos triviales: STOP y FULLSTOP. De los 4, el de DATA es quizás el más complejo. Patrik me comentó que podía implementarse este bloque con un tren de bloques PULS, pero eso engordaría tremendamente el fichero PZX resultante: cada bit de datos necesitaría al menos 4 bytes para describir los dos pulsos de que consta, lo que significa un aumento de 32 veces el tamaño original de un bloque DATA, con lo que el buffer se vaciaría 32 veces más rápido y tendríamos microcortes mucho más frecuentemente.

-- Actualizado 21 Jun 2015, 17:57 --

Sigueindo con la descripción de este invento, aquí teneis la simulación del reproductor que hice en ISim, para probar que la descripción era correcta antes de pelearme con la síntesis propiamente dicha:
pzx_player_implemented_in_hardware.png
pzx_player_implemented_in_hardware.png (216.55 KiB) Visto 3659 veces


La forma de onda resaltada es la salida del reproductor, que sigue fielmente la descripción textual que hay en la parte de abajo. Patrik Rak, el creador del formato, ha desarrollado una serie de utilidades para ayudar a los escritores de emuladores a implementar este formato, y entre ellas, hay una que convierte una descripción textual de pulsos y datos a un fichero PZX listo para ser reproducido. El texto describe primero 23 pulsos de 2168T estados cada uno. El nivel (alto o bajo) cambia cada vez que se envía un pulso completo (los referidos 2168 estados). Podeis contar en la simulación que hay efectivamente 23 pulsos: un pulso bajo, uno alto, uno bajo, uno alto, etc, hasta el último que es bajo. Esto sería una especie de tono guía muy cortito.

A continuación viene un pulso (alto) de 667 estados, seguido de otro (bajo) de 735 estados. Esto sería un pulso de sincronismo.

A continuación, un bloque de datos, concretamente de 4 bytes. Después de este bloque hay un último pulso que dura 3000 estados (para poner una pequeña pausa entre el final de un bloque y el principio de otro). Este bloque de datos define cómo se han de interpretar los unos y los ceros: un cero es un pulso alto de 855 estados, seguido por otro pulso bajo también de 855 estados. Un bit uno es un pulso alto de 1710 estados seguido de otro pulso bajo de 1710 estados. Estos timings son los de la rutina de carga estándar de la ROM.

A continuación vienen los datos en sí: en binario estos datos son 11111111 00000000 10101010 01010101 . Creo que en el cronograma, muchos de vosotros podreis distinguir las formas de onda de estos cuatro bytes ;) Tras el último bit del último byte comienzan el pulso "de cola" que son 3000 ciclos de reloj. Después de eso, el nivel vuelve a ser bajo.

He puesto los cursores de medida en uno de los pulsos que representa la mitad de un bit '1': lo que dura este pulso, medido en microsegundos es 488.638948 us (en la imagen está en milisegundos por el zoom que he hecho al cronograma). Para saber cuánto es este tiempo medido en ciclos de reloj del reloj de CPU del Spectrum, basta multiplicar por 3.5 dando el resultado que se refleja en la calculadora: 1710.236318 ciclos de reloj: una desviación de menos de 0.24 ciclos de reloj respecto de la duración ideal. ¿No está mal, no? ;)

En el cronograma también se pueden ver otras señales interesantes, como los dos vectores (pequeñas memorias) que mantienen los valores de los pulsos para los bits 0 y 1, el contenido del bus de direcciones y de datos de la SRAM, con el próximo dato a leer listo, y varios contadores.

Fijaros también cómo al pasar a decodificar un bloque de datos, se producen un montón de accesos a memoria: esto ocurre cuando termina el pulso de sincronismo, y se puede ver una especie de emborronamiento en el bus de direcciones y de datos de la SRAM, debido a que sus contenidos están cambiando en esa porción de tiempo muy rapidamente. A partir de ahí también podeis ver cómo las dos pequeñas memorias que guardan los pulsos para el bit 0 y 1 ahora contienen datos válidos, y se comienza la lectura byte a byte de cada dato. Gracias a que el reloj de la máquina de estados es de 28MHz, el tiempo que se pierde en esta operación (unos 2-3 ciclos de reloj de CPU) es apenas apreciable comparado con los tiempos (cientos o miles de ciclos de reloj) que dura cada pulso.

Y así es como se depura un circuito electrónico descrito en Verilog ;)
Recuerda: cada vez que se implementa un sistema clásico en FPGA, Dios mata a un purista

Avatar de Usuario
antoniovillena
Amiga 1200
Amiga 1200
Mensajes: 2013
Registrado: 16 Abr 2012, 21:22
Gracias recibidas: 8 veces

Re: ZX-UNO: reproductor de ficheros PZX dentro del propio cl

Mensajepor antoniovillena » 21 Jun 2015, 23:34

Creo que se puede hacer algo más sencillo que una máquina de estados. El bloque más complejo sin duda es el DATA. Podemos asumir que el número de pulsos tanto para el 0 como para el 1 es de dos. p0==p1==2. Necesitamos un registro de 32 bits al que llamaremos COUNT, otro de 16 que llamaremos TAIL y 4 registros de 16 bits llamados S0L, S0H, S1L y S1H. El proceso de carga de la cabecera sería el siguiente (separamos los registros anteriores en bytes):
  • Fetch(COUNT[0])
  • Fetch(COUNT[1])
  • Fetch(COUNT[2])
  • Fetch(COUNT[3])
  • Fetch(TAIL[0])
  • Fetch(TAIL[1])
  • Fetch(NULL)
  • Fetch(NULL)
  • Fetch(S0L[0])
  • Fetch(S0L[1])
  • Fetch(S0H[0])
  • Fetch(S0H[1])
  • Fetch(S1L[0])
  • Fetch(S1L[1])
  • Fetch(S1H[0])
  • Fetch(S1H[1])

La salida de audio es un biestable de tipo T que cambia de valor en función de un contador de 16 bits, que llamaremos TIMER e inicialmente se carga con SXL y SXH y se irá decrementando en cada ciclo de reloj del Z80 hasta llegar a 0, momento que se produce la conmutación del biestable (y la carga de TIMER con el siguiente valor). La X vale es 0 ó 1 en función del valor de un registro desplazamiento al que llamaremos SHIFT. Cuando los 3 bits menos significativos de COUNT valgan 0, hacemos un Fetch(SHIFT), y en cada decremento de COUNT vamos desplazando SHIFT a la izquierda, tomando como referencia para la X el bit más significativo de SHIFT (bit 7). Cada bit necesita 2 pulsos, voy a intentar de mostrar un ejemplo a ver si se comprende. Suponemos que los valores de SXL y SXH de la cabecera son de carga estándar, es decir 855 para S0L/S0H y 1710 para S1L/S1H. Partimos de COUNT múltiplo de 8.
  • Fetch(SHIFT). SHIFT -> 01011010
  • TIMER=S0L (bit 7 de SHIFT es 0)
  • Esperamos 855 ciclos con el biestable 0, al invertirlo se pone a 1.
  • TIMER=S0H
  • Esperamos 855 ciclos a 1, e invertimos.
  • SHIFT<<=1. SHIFT -> 1011010X
  • TIMER=S1L
  • Esperamos 1710 ciclos a 0, e invertimos.
  • TIMER=S1H
  • Esperamos 1710 ciclos a 1, e invertimos.
  • SHIFT<<=1. SHIFT -> 011010XX
  • TIMER=S0L
  • Esperamos 855 ciclos a 0, e invertimos.
  • TIMER=S0H
  • Esperamos 855 ciclos a 1, e invertimos.
  • SHIFT<<=1. SHIFT -> 11010XXX
  • TIMER=S1L
  • Esperamos 1710 ciclos a 0, e invertimos.
  • TIMER=S1H
  • Esperamos 1710 ciclos a 1, e invertimos.
  • SHIFT<<=1. SHIFT -> 1010XXXX
  • TIMER=S1L
  • Esperamos 1710 ciclos a 0, e invertimos.
  • TIMER=S1H
  • Esperamos 1710 ciclos a 1, e invertimos.
  • SHIFT<<=1. SHIFT -> 010XXXXX
  • TIMER=S0L
  • Esperamos 855 ciclos a 0, e invertimos.
  • TIMER=S0H
  • Esperamos 855 ciclos a 1, e invertimos.
  • SHIFT<<=1. SHIFT -> 10XXXXXX
  • TIMER=S1L
  • Esperamos 1710 ciclos a 0, e invertimos.
  • TIMER=S1H
  • Esperamos 1710 ciclos a 1, e invertimos.
  • SHIFT<<=1. SHIFT -> 0XXXXXXX
  • TIMER=S0L
  • Esperamos 855 ciclos a 0, e invertimos.
  • TIMER=S0H
  • Esperamos 855 ciclos a 1, e invertimos.
  • Fetch(SHIFT). SHIFT -> 11100011 (siguiente byte)

Espero que se entienda, aunque soy malo explicando. Si no lo ves muy claro podría hacer un esquemático con circuitos. Este sería el bloque más difícil. Con PULS se haría algo similar pero mucho más sencillo.

-- Actualizado 21 Jun 2015, 22:37 --

Ah se me olvidaba. COUNT se irá decrementando y cuando llege a 0 se hace una última carga TIMER=TAIL sin afectar al biestable.

-- Actualizado 21 Jun 2015, 22:51 --

Para simplificar la máquina puedes hacer lo contrario a lo que habías propuesto: implementar PULS a base de DATA. Esto sería sólo un poco más ineficiente en cuanto a ocupación de BRAM. Tiene la desventaja de que hay que procesar el PZX (en código Z80) para convertir los bloques PULS a DATA. Los tonos guía los puedes meter como extensión de PAUS si no quieres que te ocupen bytes repetitivos. Esta extensión PAUS podría tener un segundo parámetro que si es 0 se comporta como PAUS y si es N saca una onda cuadrada con semiperiodo N (lo que necesitamos para el tono guía). Ejemplo:
bloque DATA-> se parsea tal cual
bloque PULS que codifica tono guía-> se transforma a PAUS extendido
bloque PULS que codifica sincronismos-> bloque DATA de un sólo bit

Avatar de Usuario
robcfg
Amiga 2500
Amiga 2500
Mensajes: 2194
Registrado: 07 May 2009, 15:34
Sistema Favorito: Amstrad CPC
primer_sistema: Atari 800XL/600XL
Ubicación: Estocolmo
Gracias dadas: 1065 veces
Gracias recibidas: 216 veces
Contactar:

Re: ZX-UNO: reproductor de ficheros PZX dentro del propio cl

Mensajepor robcfg » 22 Jun 2015, 21:19

Teneis algun documento o guia de PZX para torpes? No acabo de entender muy bien como se codifican los datos y me gustaria hacer unas pruebas con algo sencillo que no tiebe sincronias, solo espacios y bits.

Avatar de Usuario
mcleod_ideafix
Amiga 2500
Amiga 2500
Mensajes: 5316
Registrado: 06 Oct 2009, 04:12
Sistema Favorito: Spectrum 16Kb/48Kb
primer_sistema: Spectrum 16Kb/48Kb
consola_favorita: Vectrex
Primera consola: TV Games/Pong Clone
Ubicación: Jerez de la Frontera
Gracias dadas: 12 veces
Gracias recibidas: 54 veces
Contactar:

Re: ZX-UNO: reproductor de ficheros PZX dentro del propio cl

Mensajepor mcleod_ideafix » 22 Jun 2015, 21:49

robcfg escribió:Teneis algun documento o guia de PZX para torpes? No acabo de entender muy bien como se codifican los datos y me gustaria hacer unas pruebas con algo sencillo que no tiebe sincronias, solo espacios y bits.

La documentación que hay en la página oficial es lo único que existe, que yo sepa: http://zxds.raxoft.cz/pzx.html
En concreto, si quieres crear archivos PZX que tengan una información específica, lo que necesitas es escribirlo en un fichero de texto con un determinado formato, y con una utilidad que hay en esa web, convertirlo a un PZX.
Recuerda: cada vez que se implementa un sistema clásico en FPGA, Dios mata a un purista


Volver a “Sinclair/Spectrum”

¿Quién está conectado?

Usuarios navegando por este Foro: No hay usuarios registrados visitando el Foro y 7 invitados