Simulación de salida de audio en un diseño que implementa un chip de sonido (SID)

Diseño HDL con este lenguaje. Módulos y testbenchs. Estilos y trucos de codificación, etc. NOTA: dado que hay entornos como ISE que soportan Verilog pero no SystemVerilog, señalad dentro de un post que de lo que se va a tratar es SystemVerilog si es el caso.
Avatar de Usuario
mcleod_ideafix
Site Admin
Mensajes: 80
Registrado: 14 Ago 2018, 01:15

Simulación de salida de audio en un diseño que implementa un chip de sonido (SID)

Mensaje por mcleod_ideafix » 07 Sep 2018, 17:02

Este post es "compañero" del otro que escribí sobre simulación de salida de video. Como aquel, mi meta es demostrar cómo conseguir que una simulación de un diseño permita no sólamente ver formas de onda en un visor de cronograma (GTKWave, ModelSim, etc) sino además generar un fichero de audio que realmente podemos escuchar, y que sería el mismo audio que oiríamos en tiempo real si el diseño se implementara en una FPGA.

Para poder replicar los resultados de este post necesitareis:
- Icarus Verilog (aunque podeis usar otro simulador, como ModelSim) ( http://iverilog.icarus.com )
- sox ( http://sox.sourceforge.net )
- (opcional) Si estais en entorno Linux u OS X, un compilador de C para compilar siddump

Lo bueno de las simulaciones es que la "FPGA virtual" con la que trabajas es infinitamente rápida y tiene infinitos recursos. Además, la compilación del diseño en una forma susceptible de poder ser usada para simulación tarda muy poco, en comparación con la síntesis y el place & route. Lo malo es que la simulación en sí es muy costosa en tiempo: según el diseño, simular un segundo de tiempo de diseño puede tomar casi un minuto de tiempo real.

Es por eso que he optado por usar el core que os dejo como adjunto: es un core del MOS 6581, o sea, el chip SID del C64. Este es un chip de señal mixta, que requiere bastante lógica para implementar la parte de los filtros. Hay cores que no los implementan en absoluto, otros que los implementan de forma muy fidedigna, y otros como éste que.... bueno, hacen lo que pueden. A cambio, los tiempos de simulación son "soportables". Neuro me pasó un core del 8580 más elaborado y con mejor implementación del filtro, pero tarda muchísimo más en simular.

En el fichero de licencia se dice que originalmente este core se escribió en SystemVerilog. Yo lo he traducido a Verilog "puro", para poder así usarlo con el entorno de Icarus Verilog, pero esto también nos permitirá usarlo con iSim en Xilinx.

Bueno: a la hora de probar un chip de sonido el primer problema que se plantea es... precisamente ese: cómo probarlo. El testbench está claro que tendrá que introducir valores en registros, pero de esta forma sólo podremos hacer pruebas simples. Sería mucho más útil (y espectacular, claro) poder reproducir una melodía completa como lo haría el chip dentro de su equipo original, el C64.

Afortunadamente, hay una vasta colección de música para el SID (el archivo HVSC). El formato de esta música es precisamente .SID . Pero aquí se acaba nuestra suerte, y es que el formato .SID no es como el de un tracker, como PT3, sino que cada fichero .SID es en realidad un programa, o varios de ellos, escritos en código máquina del 6510, que implementan el player original, tal y como se extrajo del código de la demo, juego, etc. Así, un reproductor de ficheros .SID es en realidad un pequeño emulador mínimo del C64.

Lo que desearíamos es en realidad la lista de valores que darle a los registros de forma periódica, durante el tiempo que dure la melodía. Para ello voy a usar un programa que descubrí gracias a las pruebas que hicieron los del Next cuando implementaron (temporalmente) el SID en su core. El programa se llama siddump y es en sí un pequeño emulador de C64. El programa original sólo volcaba datos en CSV de los valores que se le pasaban al SID. Yo añadí el parámetro -b para grabar un fichero binario. E lcódigo fuente modificado de esta forma está incluido en el adjunto que os paso.

El programa siddump funciona así: le pasas como parámetro un fichero .SID, y el tiempo en segundos que quieres que dure la ejecución, y él te genera un fichero binario. Por ejemplo, para generar el volcado de datos para el SID correspondiente a reproducir durante 3 minutos el fichero Supremacy.sid, se haría así:

Código: Seleccionar todo

siddump -t180 -bSupremacy.bin Supremacy.sid
El fichero que se genera usa 25 bytes en cada volcado: estos 25 bytes van a los registros 0 a 24 del SID. Entre cada grupo de 25 bytes han pasado 20 milisegundos. Así, en el fichero, 1 segundo de audio son 50 volcados de registros, y como cada volcado son 25 bytes, resultan 1250 bytes. De esta manera se puede coger un editor hexadecimal y recortar un fichero de volcado previamente creado para usarlo en la simulación y simular sólo un determinado pasaje de audio que nos interese.

Por si acaso no teneis a mano compilador de C, he dejado los volcados de registros de unos cuantos ficheros .SID que también incluyo.

Con este volcado de registros, ya podemos describir cómo es el testbench que hemos creado para poder probar el funcionamiento del MOS 6581:

Código: Seleccionar todo

module tb_sid;
  reg clk;
  reg n_reset;
  wire [15:0] audio_out;
  reg [4:0] addr;
  reg [7:0] data;
  reg n_cs;
  reg rw;

  mos6581 sid (            // se instancia el chip
    .clk(clk),
    .clk_en(1'b1),
    .n_reset(n_reset),
    .addr(addr),
    .n_cs(n_cs),
    .rw(rw),
    .din(data),
    .audio_out(audio_out)
  );

  integer fdata, faudio;     // los dos manejadores de fichero
  integer res;               // variable que se usa en fputc
  integer frames;            // cuantos frames llevamos reproducidos
  initial begin
    n_cs = 1;                // chip inactivo al principio
    rw = 1;
    clk = 0;
    n_reset = 0;             // y reset activo
    addr = 0;                // direccion del MOS 6581 donde se guardará el dato leido
    frames = 0;

    fdata = $fopen ("Supremacy.bin", "rb");  // de aquí leemos el volcado de registros
    if (fdata == 0) begin
      $display ("Fichero de entrada no encontrado");
      $finish;
    end

    faudio = $fopen ("Supremacy.raw", "wb");  // aquí escribiremos las muestras de audio. Con sox las traduciremos a un WAV
    #5000;                   // todo listo. Esperamos 5 microsegundos...
    n_reset = 1;             // y quitamos el reset
    n_cs = 0;                // habilitamos el chip
    res = $fscanf (fdata, "%c", data);  // llemos el primer dato
    while (!$feof(fdata)) begin
      @(posedge clk);
      rw = 0;                // pulsamos la escritura
      @(posedge clk);        // esperamos
      @(posedge clk);        // un poquito
      rw = 1;                // y desactivamos la escritura
      @(posedge clk);        // esperamos otro poco
      addr = addr + 1;       // siguiente direccion
      if (addr == 25) begin  // si ya hemos escrito 25 direcciones...
        addr = 0;               // volvemos a empezar desde la direccion 0
        frames = frames + 1;    // contamos 1 frame más
        $display ("Frames: %0d", frames);
        #20000000;              // y esperamos 20 milisegundos
      end
      res = $fscanf (fdata, "%c", data);  // leemos el siguiente dato
    end
    $fclose (fdata);
    $fclose (faudio);
    $finish;
  end

  // este always se activa 44100 veces por segundo. Su misión es 
  // muestrear la salida de audio y guardar la muestra en un fichero
  always begin
    res = $fputc (audio_out[7:0], faudio);   // guardamos 16 bits en formato little endian. Primero los 8 bits menos significativos
    res = $fputc (audio_out[15:8], faudio);  // y luego los 8 bits más significativos
    #(1000000000.0 / 44100);                 // esperar un ciclo de reloj de 44.1 kHz
  end

  // este es el reloj de 1 MHz para el SID
  always begin
    clk = #500 ~clk;
  end
endmodule
En esencia, el testbench abre el fichero de volcado de registros (.BIN) y lo va leyendo byte a byte. Cada byte lo escribe en un registro del SID, desde el registro 0 hasta el 24, en secuencia. Cuando termina de escribir el último registro, el test se queda parado durante 20 milisegundos hasta que vuelve a comenzar. Así hasta que se termine el fichero.

Al mismo tiempo, un proceso que se ejecuta periódicamente 44100 veces por segundo va leyendo la salida de audio de 16 bits directamente desde el SID y la va guardando en otro fichero (extensión .RAW) en formato little endian. Una vez que termine la simulación (o incluso mientras se está aún ejecutando) podemos traducir el .RAW a un WAV fácilmente con la utilidad sox. Se ha dejado un fichero batch que basicamente llama a sox con estos parámetros:

Código: Seleccionar todo

sox -t raw -r 44100 -b 16 -c 1 -L -e unsigned-integer fichero.raw fichero.wav
Para usar el testbench, editar en el código los dos sitios en los que se abre respectivamente para lectura el fichero de volcado de muestras, y para escritura el nombre que querais que tenga el fichero con el audio resultante. En el ejemplo son "Supremacy.bin" y "Supremacy.raw".

Luego, compilar el diseño con iverilog:

Código: Seleccionar todo

iverilog -osid.vvp *.v
En esta ocasión, llamaré "sid.vvp" al resultado de la compilación. Para ejecutarla, usamos vvp, así:

Código: Seleccionar todo

vvp sid.vvp
Y el testbench comienza a ejecutarse. Cada vez que termina un frame, mostramos un mensaje en pantalla con $display. Así tenemos una medida de lo rápido que va la simulación. En mi PC se tarda del orden de 1 minuto en procesar 72 frames, o lo que es lo mismo, simula 1.2 frames por segundo.

D:\forofpga\simulacion_audio\prueba1>vvp sid.vvp VCD info: dumpfile dump.vcd opened for output. Frames: 1 Frames: 2 Frames: 3 Frames: 4 Frames: 5 Frames: 6 Frames: 7 Frames: 8 Frames: 9 Frames: 10

Durante la simulación se va escribiendo muestra a muestra el fichero que hemos escogido para escritura (Supremacy.raw en el ejemplo). Aunque la simulación no haya terminado, podemos ir escuchando cómo queda usando sox para convertir el fichero de audio raw en un wav. El fichero de batch convierte.bat llama a sox con los parámetros adecuados:

Código: Seleccionar todo

convierte Supremacy.raw
Nos dará el fichero Supremacy.wav con lo que haya de audio hasta el momento.

También se puede usar GTKWave para ver los primeros instantes del audio resultante de una simulación. Para ello quitar los comentarios a las dos lineas justo después del initial begin, donde se establece el fichero de volcado de ondas para GTKWave, compilar el diseño con iverilog y ejecutarlo como la vez anterior con vvp. Pero en este caso, vamos a parar la simulación prematuramente con Ctrl-C
D:\forofpga\simulacion_audio>icarus -osid.vvp *.v D:\forofpga\simulacion_audio\prueba1>vvp sid.vvp VCD info: dumpfile dump.vcd opened for output. Frames: 1 Frames: 2 Frames: 3 Frames: 4 ** VVP Stop(0) ** ** Flushing output streams. ** Current simulation time is 63138500 ticks. > finish ** Continue ** D:\forofpga\simulacion_audio\prueba1>

Arrancamos GTKWave y cargamos dumpfile.vcd . Traemos al visor de ondas algunas señales, tales como el reloj de 1 MHz, el bus de datos, el de direcciones, la señal de escritura, las tres salidas correspondientes a las tres voces del SID, y la salida final, ya habiendo pasado por el filtro.

gtkwave_sid_1.png
gtkwave_sid_1.png (64.51 KiB) Visto 4515 veces
Las señales de dirección (addr) y datos (data) las formatearemos para vista en hexadecimal (sólo es por comodidad)

gtkwave_sid_2.png
gtkwave_sid_2.png (33.32 KiB) Visto 4515 veces

Aqauí viene lo interesante: GTKWave permite interpretar una señal de varios bits como si fuera analógica, y eso es lo que haremos con las otras señales: las tres correspondientes a las tres voces del SID y la que lleva el audio final.

gtkwave_sid_3.png
gtkwave_sid_3.png (40.24 KiB) Visto 4515 veces
Por defecto, las alturas de todas las señales son iguales en GTKWave, y no existe, como si lo hay en ModelSim, posibilidad de usar el ratón para establecer la altura de una señal. La solución, pelín cutre, es añadir "alturas" a esa señal en concreto, para que ocupe más espacio. Esto lo haremos según nuestra conveniencia. Yo pondré unas cuantas "alturas" a cada una de las cuatro señales analógicas. Después de añadir tres "alturas" a cada una, la cosa queda así:

gtkwave_sid_5.png
gtkwave_sid_5.png (62.47 KiB) Visto 4515 veces
Y ya podemos empezar a ver los resultados. Al principio no vemos nada sencillamente porque el nivel de zoom está al máximo, y ni siquiera vemos las transiciones del reloj de 1 MHz. Le damos unas cuántas veces al botón de Zoom - y empezaremos a ver cosas interesantes. Por ejemplo, aquí tenemos los primeros instantes, cuando se comienzan a enviar al SID los primeros datos leídos del fichero de volcado de registros. Vereis que en el campo addr se va mostrando la dirección del SID donde se escribirá el dato, en el campo data el dato en sí, y en rw hay un pulso a nivel bajo para indicar la escritura.

gtkwave_sid_6.png
gtkwave_sid_6.png (66.9 KiB) Visto 4515 veces
En el momento en que cada una de las voces tiene datos en sus registros, el SID comienza a funcionar. Con un poquito menos de zoom se ve cómo hemos terminado de escribir en el registro 18h (24 en decimal, los registros van del 0 al 24) y ya desde antes los tres canales ya comienzan a dar salida.

gtkwave_sid_7.png
gtkwave_sid_7.png (66.34 KiB) Visto 4515 veces
Quitando más zoom, podemos ver ya la señal de audio propiamente dicha...

gtkwave_sid_8.png
gtkwave_sid_8.png (70.06 KiB) Visto 4515 veces
Como comparación, esto de aquí es un fotograma de un video de Youtube donde se muestra la "salida en osciloscopio" de las tres voces de un SID (seguramente un emulador, ya que el SID no saca las tres voces de forma independiente), comparada con lo que saca nuestra simulación:

https://youtu.be/3kgzdAfz0AE?t=403
scope_cybernoid2.png
scope_cybernoid2.png (52.94 KiB) Visto 4515 veces
gtkwave_sid_9.png
gtkwave_sid_9.png (60.9 KiB) Visto 4515 veces

Por último, y después de pasar el WAV a MP3 (también usando sox), dejo por aquí los temas completos de Cybernoid II y Supremacy, generados ambos por la simulación del core de SID que hemos ejecutado.

Cybernoid II
CybernoidII.mp3
(5.31 MiB) Descargado 406 veces
Supremacy
Supremacy.mp3
(3.54 MiB) Descargado 383 veces
Y por supuesto, los ficheros del core y testbench que hemos usado en el post:
simulacion_audio.zip
(504.66 KiB) Descargado 445 veces

Responder

Volver a “Verilog / SystemVerilog”