SidBerry – Raspberry Pi SID player

Un SID jukebox realizzato con RasberryPi e il SID chip originale 6581. Il chip è alloggiato in una board custom collegata direttamente ai gpio del Rasberry!

La board può riprodurre qualsiasi SID file, basta caricare i file .sid nella sdcard del Rasberry, avviare il player e collegare all’uscita jack un paio di casse preamplificate o meglio uno stereo.

SID chip

Il SID originale, nelle varianti 6581 e 8580, è controllato caricando i suoi 29 registri interni con i valori opportuni al momento opportuno. La sequenza di byte inviati genera l’effetto o la musica desiderati. Ogni registro è 1 byte ed i registri sono in tutto 29 quindi c’è bisogno di almeno 5 gpio di indirizzo (2^5=32) e 8 gpio di dati per un totale di 13 gpio. Un altro gpio è richiesto per la linea CS del chip (Chip Select).

220px-MOS6581.svg

Fortunatamente il Raspberry ha ben 17 gpio e i 14 richiesti sono perfettamente pilotabili con la libreria WiringPi. Gli altri pin del chip servono per i due condensatori dei filtri interni (CAP_a e CAP_b), alimentazione, linea R/W (che andremo a collegare direttamente a GND, siamo sempre in scrittura), entrata del segnale di clock, linea di reset (collegata sempre a VCC) e uscita audio analogica (AUDIO OUT).

Ecco la configurazione dei registri interni del SID:

SID_registers_fit

Qui per una descrizione più dettagliata del funzionamento interno del chip:

http://www.waitingforfriday.com/index.php/Commodore_SID_6581_Datasheet

Hardware

La board riproduce esattamente le “condizioni al contorno” per il SID chip come se si trovasse alloggiato in un Commodore 64 originale. L’application note originale mostra chiaramente i collegamenti da effettuare e i pochi componenti esterni richiesti (generatore di clock, condensatori e poco altro).

SID_original_schematic_fit

Unica differenza, le linee di indirizzo e dati vengono dirottate direttamente sui gpio del RasberryPi.

NOTA IMPORTANTE!: Il RasberryPi ragiona in logica CMOS a 3.3V mentre il SID chip è TTL a 5V quindi completamente incompatibili a livello di tensioni. Fortunatamente, siccome andiamo unicamente a scrivere nei registri, il RasberryPi dovrà soltanto applicare 3.3v ai capi del chip, più che sufficienti per essere interpretati come livello logico alto dal SID.

20150516_160221

Lo schematico completo:

sidberry

Software

Il grosso del lavoro. Volevo una soluzione completamente stand-alone, senza l’ausilio di player esterni (come ACID64) e quindi ho realizzato un player che emula gran parte di un C64 originale. L’emulatore è necessario in quanto i file .sid sono programmi in linguaggio macchina 6502 e come tali devono essere eseguiti. Il player è scritto in C/C++ e basato sul mio emulatore MOS CPU 6502   più un semplice array di 65535 byte come memoria RAM (il C64 originale ha infatti 64K di RAM). Il player carica il codice programma contenuto nel file .sid nella RAM virtuale più un codice assembly aggiuntivo che ho chiamato micro-player: sostanzialmente si tratta di un programma minimale scritta in linguaggio macchina per CPU 6502 che assolve a due compiti specifici:

  • installare i vettori di reset e interrupt alle locazioni corrette
  • chiamare la play routine del codice sid ad ogni interrupt

Questo perchè il codice che genera la musica in un comune C64 è una funzione chiamata ad intervalli regolari (50 volte al secondo, 50Hz). La chiamata è effettuata per mezzo di un interrupt esterno. In questo modo è possibile avere musica e un qualsiasi altro programma (videogioco ad esempio) in esecuzione nello stesso momento.

Altre componenti sono il parser per i file .sid e la libreria WiringPi per pilotare i GPIO del RaspberryPi.

Questo il layout dell’intero applicativo, a destra è la memoria RAM virtuale 64K

Screen shot 2015-05-17 at 5.41.36 PM

Questo è il codice assembler del micro-player:

// istallazione del vettore di reset (0x0000)
memory[0xFFFD] = 0x00;
memory[0xFFFC] = 0x00;</p>
// istallazione del vettore di interrupt, punta alla play routine (0x0013)
memory[0xFFFF] = 0x00;
memory[0xFFFE] = 0x13;

// codice del micro-player
memory[0x0000] = 0xA9;
memory[0x0001] = atoi(argv[2]); // il registro A viene caricato con il numero della traccia (0-16)

memory[0x0002] = 0x20; // salta alla init-routine nel codice sid
memory[0x0003] = init &amp; 0xFF; // lo addr
memory[0x0004] = (init &gt;&gt; 8) &amp; 0xFF; // hi addr

memory[0x0005] = 0x58; // abilitiamo gli interrupt
memory[0x0006] = 0xEA; // nop
memory[0x0007] = 0x4C; // loop infinito
memory[0x0008] = 0x06;
memory[0x0009] = 0x00;

// Interrupt service routine (ISR)
memory[0x0013] = 0xEA; // nop
memory[0x0014] = 0xEA; // nop
memory[0x0015] = 0xEA; // nop
memory[0x0016] = 0x20; // saltiamo alla play routine
memory[0x0017] = play &amp; 0xFF;
memory[0x0018] = (play &gt;&gt; 8) &amp; 0xFF;
memory[0x0019] = 0xEA; // nop
memory[0x001A] = 0x40; // return from interrupt

Pagina GitHub del progetto: https://github.com/gianlucag/SidBerry

MOS 6502 CPU emulator in C++

This is my C++ emulator of the MOS Technology 6502 CPU. The code emulates a fully functional 6502 CPU and it seems to be pretty fast too. Some minor tricks have been introduced to greatly reduce the overall execution time.

Interrupt and bus read/write operations are emulated as well.

Github repo: https://github.com/gianlucag/mos6502

6502-300x135

What’s a 6502???

Here a brief descrption: http://en.wikipedia.org/wiki/MOS_Technology_6502

Main features:

  • 100% coverage of legal opcodes
  • decimal mode implemented
  • read/write bus callback
  • jump table opcode selection

Still to implement

  • 100% cycle accuracy
  • illegal opcodes
  • hardware glitches, the known ones of course 🙂

The emulator was extensively tested against this test suite:

https://github.com/Klaus2m5/6502_65C02_functional_tests

and in parallel emulation with fake6502 http://rubbermallet.org/fake6502.c

so expect nearly 100% compliance with the real deal… at least on the normal behavior: as I said stuff like illegal opcodes or hardware glitches are currently not implemented.

Why yet another 6502 emulator?
Just for fun :). This CPU (and its many derivatives) powered machines such as:

  • Apple II
  • Nintendo Entertainment system (NES)
  • Atari 2600
  • Commodore 64
  • BBC micro

and many other embedded devices still used today. You can use this emulator in your machine emulator project. However cycle accuracy is not yet implemented so mid-frame register update tricks cannot be reliably emulated.

Some theory behind emulators: emulator types
You can group all the CPU emulators out there in 4 main categories:

  • switch-case based
  • jump-table based
  • PLA or microcode emulation based
  • graph based

Graph based emulators are the most accurate as they emulate the connections between transistors inside the die of the CPU. They emulate even the unwanted glitches, known and still unknown. However the complexity of such emulators is non-linear with the number of transistors: in other word, you don’t want to emulate a modern Intel quad core using this approach!!!

for an example check this out: http://visual6502.org/JSSim/index.html

The PLA/microcode based are the best as they offer both speed and limited complexity. The switch-case based are the simpler ones but also the slowest: the opcode value is thrown inside a huge switch case which selects the code snippet to execute; compilers can optimize switch case to reach near O(log(n)) complexity but they hardly do it when dealing with sparse integers (like most of the CPU opcode tables).

Emulator features
My project is a simple jump-table based emulator: the actual value of the opcode (let’s say 0x80) is used to address a function pointer table, each entry of such table is a C++ function which emulates the behavior of the corresponding real instruction.

All the 13 addressing modes are emulated:

// addressing modes
uint16_t Addr_ACC(); // ACCUMULATOR
uint16_t Addr_IMM(); // IMMEDIATE
uint16_t Addr_ABS(); // ABSOLUTE
uint16_t Addr_ZER(); // ZERO PAGE
uint16_t Addr_ZEX(); // INDEXED-X ZERO PAGE
uint16_t Addr_ZEY(); // INDEXED-Y ZERO PAGE
uint16_t Addr_ABX(); // INDEXED-X ABSOLUTE
uint16_t Addr_ABY(); // INDEXED-Y ABSOLUTE
uint16_t Addr_IMP(); // IMPLIED
uint16_t Addr_REL(); // RELATIVE
uint16_t Addr_INX(); // INDEXED-X INDIRECT
uint16_t Addr_INY(); // INDEXED-Y INDIRECT
uint16_t Addr_ABI(); // ABSOLUTE INDIRECT

All the 151 opcodes are emulated. Since the 6502 CPU uses 8 bit to encode the opcode value it also has a lot of “illegal opcodes” (i.e. opcode values other than the designed 151). Such opcodes perform weird operations, write multiple registers at the same time, sometimes are the combination of two or more “valid” opcodes. Such illegals were used to enforce software copy protection or to discover the exact CPU type.

The illegals are not supported yet, so instead a simple NOP is executed.

Inner main loop
It’s a classic fetch-decode-execute loop:

while(start + n > cycles && !illegalOpcode)
{
   // fetch
   opcode = Read(pc++);

   // decode
   instr = InstrTable[opcode];

   // execute
   Exec(instr);
}

The next instruction (the opcode value) is retrieved from memory. Then it’s decoded (i.e. the opcode is used to address the instruction table) and the resulting code block is executed.

Public methods
The emulator comes as a single C++ class with five public methods:

  • mos6502(BusRead r, BusWrite w);
  • void NMI();
  • void IRQ();
  • void Reset();
  • void Run(uint32_t n);

mos6502(BusRead r, BusWrite w);

it’s the class constructor. It requires you to pass two external functions:

uint8_t MemoryRead(uint16_t address);
void MemoryWrite(uint16_t address, uint8_t value);
respectively to read/write from/to a memory location (16 bit address, 8 bit value). In such functions you can define your address decoding logic (if any) to address memory mapped I/O, external virtual devices and such.

void NMI();
triggers a Non-Mascherable Interrupt request, as done by the external pin of the real chip

void IRQ();
triggers an Interrupt ReQuest?, as done by the external pin of the real chip

void Reset();
performs an hardware reset, as done by the external pin of the real chip

void Run(uint32_t n);
It runs the CPU for the next ‘n’ machine instructions.

Links
Some useful stuff I used…

http://en.wikipedia.org/wiki/MOS_Technology_6502

http://www.6502.org/documents/datasheets/mos/

http://www.mdawson.net/vic20chrome/cpu/mos_6500_mpu_preliminary_may_1976.pdf

http://rubbermallet.org/fake6502.c