Skip to content

CornN64/C64-SID-on-a-FPGA

Repository files navigation

C64 SID audio chip on a FPGA which allows to play classic SID tunes through a computers COM port. This SID VHDL implementation is based on source from http://papilio.gadgetfactory.net/index.php?n=Playground.C64SID. Adding a digital filter to emulate the analog one present on the real SID and the undocumented modes when several waveforms are selected for a SID voice which some sounds rely on. A 3rd order 16bit Delta Sigma DAC was added to the audio output to give some nice quality audio reproduction. Some minor changes was done to the noise generator as well. The implementation basically takes writes to the SID register and allows to stream SID data directly to the SID through a 115k2 baud RS232 interface. An ACTEL A3P1500 flash FPGA was used for testing of which about 25% was occupied by the logic. The non pipelined multiplers used for volume and filtering uses quite a bit of logic and makes the solution "slow"
A youtube clip can be found here of the FPGA SID playing a tune https://www.youtube.com/watch?v=a_OK4_-QRLo&t
(Sorry fo the crappy phone recording...)

The FPGA expects a main clock of 40MHz but can be tweaked to other rates (The real SID works at 1MHz after all)
The fastest clock is for the Delta Sigma converter to produce a clean audio up to ~20kHz
The COM port need to run as fast as possible but 115200 baud seem to run well enough for most SIDs tunes
The audio pin require a simple RC filter with 1k5 resistor and 4n7 capacitor to ground to smooth out the DS modulator noise.
Below you can see the interface signals need to make this work.

RESET : in std_logic; -- active low reset
OSC : in std_logic; -- main clock 40Mhz
Aout : out std_logic; -- audio out
RX : in std_logic; -- RS232 data to FPGA
TX : out std_logic -- RS232 data from FPGA

A three pin UART FTDI USB to TTL 3.3V cable can be used to connect the computer to the FPGA RX & TX pins

First I used siddump.exe (https://csdb.dk/release/?id=152422) to dump a SID track to something that looks like this

|     0 | 0000  ... ..  00 0000 000 | 0000  ... ..  00 0000 000 | 0000  ... ..  00 0000 000 | 0000 00 Off 0 |
|     1 | 0116  ... ..  .. .... ... | 0116  ... ..  .. .... ... | 0116  ... ..  .. .... ... | .... .. ... F |
|     2 | ....  ... ..  .. .... ... | ....  ... ..  .. .... ... | ....  ... ..  .. .... ... | .... .. ... . |
|     3 | 02EA  ... ..  08 076F 0A0 | ....  ... ..  .. .... ... | ....  ... ..  .. .... ... | .... .. ... . |
|     4 | 7517  A-6 D1  81 .... 140 | ....  ... ..  .. .... ... | ....  ... ..  .. .... ... | .... .. ... . |
|     5 | 0BA1 (F-3 A9) 41 .... 800 | ....  ... ..  .. .... ... | ....  ... ..  .. .... ... | .... .. ... . |
|     6 | 057E (E-2 9C) .. .... ... | ....  ... ..  .. .... ... | ....  ... ..  .. .... ... | .... .. ... . |
|     7 | 02EA (F-1 91) .. .... 220 | ....  ... ..  .. .... ... | ....  ... ..  .. .... ... | .... .. ... . |

Then this file is parsed, translated to SID register writes and transmitted through UART/RS232 with a matlab script (see below)
All in all a fun project to hear the good old (and new) SID tunes.
Enjoy (^.^)

if ~exist('s','var')
    s = serial('COM5','BaudRate',115200);
    fopen(s);
    %s = 1;	%write to screen
end

fileID = fopen('R-type.txt');	%siddump file name to parse

C = textscan(fileID,'%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s');
fclose(fileID);

fwrite(s,'W1500');
flt = 0;
vol = '0';
upd = false;
tic;

for i=1:numel(C{1,1})
    %Volume/filter
    if C{1,26}{i}(1)~='.'	%Res/ch
		fwrite(s,['w' char(23) char(hex2dec(C{1,26}{i}))]);
    end
    
    if C{1,25}{i}(1)~='.'	%Fcut
		fwrite(s,['w' char(22) char(hex2dec(C{1,25}{i}(1:2)))]);
    end
    
    if C{1,28}{i}(1)~='.'	%volume
        upd=true;
        vol=C{1,28}{i}(1);
    end

    if C{1,27}{i}(1)~='.'	%Filter mode
        upd = true;
        switch C{1,27}{i}
            case 'Off'
                flt=0;
            case 'Low'
                flt=1;
            case 'Bnd'
                flt=2;
            case 'L+B'
                flt=3;
            case 'Hi '
                flt=4;
            case 'L+H'
                flt=5;
            case 'B+H'
                flt=6;
            case 'LBH'
                flt=7;
        end
    end

    if upd == true  %Set volume and filter mode
        upd = false;
		fwrite(s,['w' char(24) char(16*flt+hex2dec(vol))]);
    end
    
    %Voice 1
    if C{1,4}{i}(1)~='.'	%Freq
        fwrite(s,['w' char(0) char(hex2dec(C{1,4}{i}(3:4))) 'w' char(1) char(hex2dec(C{1,4}{i}(1:2)))]);
    end
    if C{1,9}{i}(1)~='.'	%PWM
        fwrite(s,['w' char(3) char(hex2dec(C{1,9}{i}(1))) 'w' char(2) char(hex2dec(C{1,9}{i}(2:3)))]);
    end
    if C{1,8}{i}(1)~='.'	%ADSR
        fwrite(s,['w' char(6) char(hex2dec(C{1,8}{i}(3:4))) 'w' char(5) char(hex2dec(C{1,8}{i}(1:2)))]);
    end
    if C{1,7}{i}(1)~='.'	%CTRL
		fwrite(s,['w' char(4) char(hex2dec(C{1,7}{i}))]);
    end
    
    %Voice 2
    if C{1,11}{i}(1)~='.'	%Freq
        fwrite(s,['w' char(7) char(hex2dec(C{1,11}{i}(3:4))) 'w' char(8) char(hex2dec(C{1,11}{i}(1:2)))]);
    end
    if C{1,16}{i}(1)~='.'	%PWM
        fwrite(s,['w' char(10) char(hex2dec(C{1,16}{i}(1))) 'w' char(9) char(hex2dec(C{1,16}{i}(2:3)))]);
    end
    if C{1,15}{i}(1)~='.'	%ADSR
        fwrite(s,['w' char(13) char(hex2dec(C{1,15}{i}(3:4))) 'w' char(12) char(hex2dec(C{1,15}{i}(1:2)))]);
    end
    if C{1,14}{i}(1)~='.'	%CTRL
		fwrite(s,['w' char(11) char(hex2dec(C{1,14}{i}))]);
    end
    
    %Voice 3
    if C{1,18}{i}(1)~='.'	%Freq
        fwrite(s,['w' char(14) char(hex2dec(C{1,18}{i}(3:4))) 'w' char(15) char(hex2dec(C{1,18}{i}(1:2)))]);
    end
    if C{1,23}{i}(1)~='.'	%PWM
        fwrite(s,['w' char(17) char(hex2dec(C{1,23}{i}(1))) 'w' char(16) char(hex2dec(C{1,23}{i}(2:3)))]);
    end
    if C{1,22}{i}(1)~='.'	%ADSR
        fwrite(s,['w' char(20) char(hex2dec(C{1,22}{i}(3:4))) 'w' char(19) char(hex2dec(C{1,22}{i}(1:2)))]);
    end
    if C{1,21}{i}(1)~='.'	%CTRL
		fwrite(s,['w' char(18) char(hex2dec(C{1,21}{i}))]);
    end
    
    %fwrite(s,sprintf('\n'));
    while toc < 0.02; end;	%wait until 20ms has passed (SID update rate PAL->20ms, NTSC->16.67ms)
    tic;
end

fwrite(s,'W0410');  fwrite(s,'W0B10');  fwrite(s,'W1210'); %WAVE/GATE
%fclose(s);

About

VHDL code for the C64 SID chip

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages