Arduino 6502 emulator + BASIC interpreter

I wrote a 6502 CPU emulator for the Arduino! I originally wrote it a couple years ago for a NES emulator, then modified it a bit for this. One of the changes was to make cycle timing a little less accurate (doesn't check for page boundary crosses on some opcodes) for a speed increase. If anybody wants that fixed for something, let me know.

To demonstrate it, I have the ROM for "Enhanced BASIC 6502" embedded in the code. Given the Uno's tiny 2 KB RAM, I am only able to provide the CPU emulator with 1.5 KB, but it's enough to minimally run EhBASIC! You can connect to the Arduino with any terminal program (like PuTTY) and play around in the BASIC interpreter. 8)

It makes for a fun little toy, even if not all that useful. With more memory on an Arduino, you could turn this into a simple Apple ][ emulator or something like that. I might look into that actually. If anybody actually uses this for a project let me know if you need help.

arduino_6502.zip (18.3 KB)

1 Like

Brilliant...

6502.... Apple ][.... that takes the clock back a bit!

Thanks! :slight_smile:

I actually forgot to attach the project to the post, d'oh! Doing that now...

Ideas:

Store the program in EEPROM and get more than 767 bytes. It would also survive a reset.

Set up some virtual addresses to change the state of Arduino pins using poke().

fungus:
Ideas:

Store the program in EEPROM and get more than 767 bytes. It would also survive a reset.

Set up some virtual addresses to change the state of Arduino pins using poke().

The program (as in the EhBASIC ROM image) is stored in the flash memory along with the code. The emulator has access to 1536 bytes of RAM, but the first 512 bytes make up the 6502's zero page and stack page, then the next 256 are used by the BASIC interpreter itself. The 767 bytes is just what EhBASIC reports as the remaining storage for BASIC code and variables.

I wouldn't want to use EEPROM because it can only be written to so many times before it goes bad. I'm looking into getting a 32 KB SPI chip for more memory. Should be pretty simple. :slight_smile:

miker00lz:
The program (as in the EhBASIC ROM image) is stored in the flash memory along with the code. The emulator has access to 1536 bytes of RAM, but the first 512 bytes make up the 6502's zero page and stack page, then the next 256 are used by the BASIC interpreter itself. The 767 bytes is just what EhBASIC reports as the remaining storage for BASIC code and variables.

I meant store the BASIC program in EEPROM (and variables in RAM).

miker00lz:
I wouldn't want to use EEPROM because it can only be written to so many times before it goes bad.

It's a lot of times though. You could probably spend a lifetime writing BASIC and not wear it out (how long would it take you to make 100,000 edits to a program?)

More ideas: Make it so that if you connect a special pin to GND it automatically runs the BASIC program (stored in EEPROM) on boot-up. That way you could program it in BASIC and deploy it in real projects.

Map the analog ports to memory addresses so if you peek() them you read the analog value (only 8 bits, but hey...the bottom 2 bits are usually just noise anyway)

Good ideas, but I think you are misunderstanding how it works a little bit. I wrote no code of this BASIC interpreter. It is a 6502 program that already existed. I only wrote a 6502 CPU emulator that runs this interpreter. I have no way of knowing where in the 6502 memory space the relevant BASIC program information is, otherwise I might do it. This of course means I have no way to auto-load a program and start it either.

I would have to do all 6502 memory space read/write to the EEPROM, and it could be writing to memory constantly depending on the 6502 machine code being run. It may wear out parts of the EEPROM in a matter of seconds actually! :grin:

Besides, the BASIC interpreter I'm emulating is really just a simple demonstration for the core concept, the 6502 emulator. I do like your ideas, they just aren't possible to implement with the way this thing works.

miker00lz:
Good ideas, but I think you are misunderstanding how it works a little bit. I wrote no code of this BASIC interpreter. It is a 6502 program that already existed. I only wrote a 6502 CPU emulator that runs this interpreter. I have no way of knowing where in the 6502 memory space the relevant BASIC program information is, otherwise I might do it. This of course means I have no way to auto-load a program and start it either.

Oh, I see...

It might work if you put page0/page1 in RAM and the rest in EEPROM but the BASIC variables would probably end up in EEPROM too, not good.

OH, well, they were just ideas. :slight_smile:

try it on a MEGA to have more RAM?

fungus:

miker00lz:
Good ideas, but I think you are misunderstanding how it works a little bit. I wrote no code of this BASIC interpreter. It is a 6502 program that already existed. I only wrote a 6502 CPU emulator that runs this interpreter. I have no way of knowing where in the 6502 memory space the relevant BASIC program information is, otherwise I might do it. This of course means I have no way to auto-load a program and start it either.

Oh, I see...

It might work if you put page0/page1 in RAM and the rest in EEPROM but the BASIC variables would probably end up in EEPROM too, not good.

OH, well, they were just ideas. :slight_smile:

It would be cool to save the programs, for sure! :slight_smile:

robtillaart:
try it on a MEGA to have more RAM?

I was kind of thinking about that, but the again it still only has 8 KB. I think the best solution would be a cheap 23A256 for an instant 32 KB.

https://www.microchip.com/wwwproducts/Devices.aspx?dDocName=en539040

Maybe even the 23A512 for 64 KB, then an Apple ][ could be emulated. :slight_smile:

A very cool piece of code, nice work.
I have to look into it tonight.

If I'm reading your code correctly, to enable the use of external -- say SPI memory you would only need to write read6502() and write6502() for your SPI flash, right? Very cool :slight_smile:

Also: Have you thought about mapping a whole arduino I/O Port to the memory? That would enable for a HELL OF A LOT of mega cool features. Just think about printer integration, maybe even floppy.
Or maybe even go one step further: Reading programs from a cassette tape scnr

Manawyrm:
A very cool piece of code, nice work.
I have to look into it tonight.

If I'm reading your code correctly, to enable the use of external -- say SPI memory you would only need to write read6502() and write6502() for your SPI flash, right? Very cool :slight_smile:

Thanks! Yep, that's right. Those are the only two functions you need to worry about. If you were to get at least 48 KB of SPI memory you could emulate the Apple ][. :slight_smile:

Also: Have you thought about mapping a whole arduino I/O Port to the memory? That would enable for a HELL OF A LOT of mega cool features. Just think about printer integration, maybe even floppy.
Or maybe even go one step further: Reading programs from a cassette tape scnr

The thought crossed my mind. It would be cool to experiment with. You could also use a microSD shield to store Apple disk images on. You would need something like the Mega 2560 for that, though. There isn't enough program storage on an Uno to link in the SD card functions and still have the BIOS embedded with it, I tried.

Cassette should also be reasonably easy to add. I wrote an Apple emu for Windows a while back with cassette support (via WAV files), and the interface is unbelievably simple.

The program (as in the EhBASIC ROM image) is stored in the flash memory along with the code. The emulator has access to 1536 bytes of RAM, but the first 512 bytes make up the 6502's zero page and stack page, then the next 256 are used by the BASIC interpreter itself. The 767 bytes is just what EhBASIC reports as the remaining storage for BASIC code and variables.

I wouldn't want to use EEPROM because it can only be written to so many times before it goes bad. I'm looking into getting a 32 KB SPI chip for more memory. Should be pretty simple. :slight_smile:
[/quote]

Hi
I hacked around abit with your emulator and patched Read/Write6502.

I used a 24LC256 32Kb EEPROM for memorystorage.
It can take 1000000 writes before going bad.

To be safe I mapped the first 768bytes to Arduino RAM and the rest is EEPROM space.
That should keep it relative safe even though the basic variables gets written in EEPROM when the basicprogram runs.

It's just a first test any as I plan to run CBM-basic on it for Vic-20/C64 emulation.

janost:
Hi
I hacked around abit with your emulator and patched Read/Write6502.

I used a 24LC256 32Kb EEPROM for memorystorage.
It can take 1000000 writes before going bad.

To be safe I mapped the first 768bytes to Arduino RAM and the rest is EEPROM space.
That should keep it relative safe even though the basic variables gets written in EEPROM when the basicprogram runs.

It's just a first test any as I plan to run CBM-basic on it for Vic-20/C64 emulation.

I assume you can't put everything in EEPROM or it will be hellishly slow.

Zero page and stack can be in Arduino RAM, the program could be in EEPROM, but where are the BASIC variables stored? On most interpreted BASICs they start at the top of RAM and work down towards where the program is. That makes it hard to detect what goes in RAM and what goes in EEPROM. If the variables are in EEPROM it could slow things down a lot.

There might be a system variable you can look at to see where the end of the program is. Everything above that (ie. vars) goes in RAM, everything below (ie. program) goes in EEPROM.

(maybe)

It wasnt that slow.

FOR I=0 TO 1000:NEXT completes in 30sec.
I Think it should be around 1sec and the problem is the Arduino I2C that runs at 100KHz.

Can the Wire library run at 1MHz?
That would make it more up to speed?

Or read/write caching memory in RAM?
Like a CPU-cache?

Ok, the FOR I=0 TO 1000:NEXT loop is now down to 3sec.
That is a tenfold in speed :slight_smile:

I implemented a CPU FIFO L1-Cache with 4 TLB buffers of 128bytes.
It works great, even with random PEEKs all over the EEPROM.

If the data is in the cache it is returned from RAM.
Else the oldest buffer gets flushed (written if dirty) and a new is read in from the EEPROM.

But there is still a bit of penelty when there is a cache-miss on writes.

Perhaps a greater number of smaller buffers would be better?
Like 32buffers with 16bytes?

How many bytes does EhBasic use for different variables not including strings?

janost:
Ok, the FOR I=0 TO 1000:NEXT loop is now down to 3sec.
That is a tenfold in speed :slight_smile:

I implemented a CPU FIFO L1-Cache with 4 TLB buffers of 128bytes.
It works great, even with random PEEKs all over the EEPROM.

Good idea!

janost:
Perhaps a greater number of smaller buffers would be better?
Like 32buffers with 16bytes?

Only one way to find out...

I'm fairly sure the i2c can be made faster, too.

Yes, I changed the i2c speed to 400KHz with:

Wire.begin();
TWBR = 12;

It can still be optimized more by using block read/write.

Here is the code for the 4 buffer FIFO L1-Cache.
The writecache resides in the readcache function and only writes back a buffer if its dirty.

  void writeEEPROM(int deviceaddress, unsigned int eeaddress, byte data ) {
  int cache;
  int page=(eeaddress >> 7);
  if (cachepage[0]==page) {
   cacheram[(eeaddress & 127)]=data;
   cachedirty[0]=1;
   return;
  } 
  if (cachepage[1]==page) {
   cacheram[(eeaddress & 127)+128]=data;
   cachedirty[1]=1;
   return;
  }  
  if (cachepage[2]==page) {
   cacheram[(eeaddress & 127)+256]=data;
   cachedirty[2]=1;
   return;
  }
  if (cachepage[3]==page) {
   cacheram[(eeaddress & 127)+384]=data;
   cachedirty[3]=1;
   return;
  }
  readcache(page,nextcache);
  cache=nextcache;
  nextcache++;
  if (nextcache>3) nextcache=0;
  cacheram[(eeaddress & 127)+(cache << 7)]=data;
  cachedirty[cache]=1;  
}
 
byte readEEPROM(int deviceaddress, unsigned int eeaddress ) {
  int cache;
  int page=(eeaddress >> 7);
  if (cachepage[0]==page) return cacheram[(eeaddress & 127)];
  if (cachepage[1]==page) return cacheram[(eeaddress & 127)+128];
  if (cachepage[2]==page) return cacheram[(eeaddress & 127)+256];
  if (cachepage[3]==page) return cacheram[(eeaddress & 127)+384]; 
  readcache(page,nextcache);
  cache=nextcache;
  nextcache++;
  if (nextcache>3) nextcache=0;
  return cacheram[(eeaddress & 127)+(cache << 7)];
}

janost:
The writecache resides in the readcache function and only writes back a buffer if its dirty.

Your cache misses are very expensive (you have to use I2C!) so more, smaller pages is probably good.

janost:

int page=(eeaddress >> 7);

if (cachepage[0]==page) ...

That code will optimize horribly on Arduino. Bit shifting 16-bit values is really slow, the compiler usually generates a loop for it.

You can eliminate all the if statements and bit shifting by using a simple hash, eg.

// eg. Sixteen pages of sixteen bytes each
byte cachePage[16];
byte cacheRam[16][16];

byte a = byte(eeaddress);  // Bottom 8 bits of address
byte p = eeaddress>>8;     // Page of RAM we need (the compiler usually figures out how to optimize a shift by 8 bits)

byte n = a>>4;    // Top 4 bits of 'a' are cache page
byte r = a&0x0f;  // Bottom 4 bits of 'a' are cache index
if (cachePage[n] == p) return cacheRam[n][r];

Later on when you're happy you have the best performance change it to something like:

// No bit-shifting please, we're an AVR
byte a = byte(eeaddress);  // Bottom 8 bits of address
byte p = eeaddress>>8;     // Page of RAM we need (the compiler usually figures out how to optimize a shift by 8 bits)
byte r = a&0x0f;
switch (a&0xf0) {
  case 0x00:  if (cachePage[0] == p) return cacheRam[0][r];
  case 0x10:  if (cachePage[1] == p) return cacheRam[1][r];
  ...
  case 0xf0: if (cachePage[15] == p) return cacheRam[15][r];
}