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
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
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
Luego, compilar el diseño con iverilog:
Código: Seleccionar todo
iverilog -osid.vvp *.v
Código: Seleccionar todo
vvp sid.vvp
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
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.
Las señales de dirección (addr) y datos (data) las formatearemos para vista en hexadecimal (sólo es por comodidad)
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.
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í:
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.
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.
Quitando más zoom, podemos ver ya la señal de audio propiamente dicha...
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
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 Supremacy Y por supuesto, los ficheros del core y testbench que hemos usado en el post: