6502 emulator for Commodore 64 ROMs, serial terminal edition for MBED. Recommend terminal echo on, line edit on, caps lock, 115200bps, implicit carriage return on newline, currently non-buffered so don't paste lots of stuff

More details at:

[https://github.com/davervw] [https://techwithdave.davevw.com/2020/03/simple-emu-c64.html]

Revision:
9:b4293b01083b
Parent:
8:519febdce8db
--- a/emuc64.cpp	Wed Apr 15 05:15:07 2020 +0000
+++ b/emuc64.cpp	Fri Apr 17 09:15:50 2020 +0000
@@ -36,98 +36,119 @@
 //
 // LIMITATIONS:
 // Only keyboard/console I/O.  No text pokes, no graphics.  Just stdio.  
-//   No asynchronous input (GET K$), but INPUT S$ works
+//   No asynchronous input (GET K$), but INPUT S$ works.  No key scan codes.
 // No keyboard color switching.  No border displayed.  No border color.
+// No background screen color.  No reverse colors implemented in this version.
 // No screen editing (gasp!) Just short and sweet for running C64 BASIC in 
-//   terminal/console window via 6502 chip emulation in software
-// No PETSCII graphic characters, only supports printables CHR$(32) to CHR$(126), and CHR$(147) clear screen
-// No memory management.  Full 64K RAM not accessible via banking despite startup screen.
-//   Just 44K RAM, 16K ROM, 1K VIC-II color RAM nybbles
+//   terminal/console window via 6502 chip emulation in software.
+// No PETSCII graphic characters, only supports printables CHR$(32) to CHR$(126), 
+//   and CHR$(147) clear screen and HOME/LEFT/RIGHT/UP/DOWN (see cbmconsole.cpp)
 // No timers.  No interrupts except BRK.  No NMI/RESTORE key.  No STOP key.
-// No loading of files implemented.
+//   IRQ is specifically commented out because breaks terminal I/O.
+// Loading of files at startup is optional depending on availability of
+//   a local file system store (e.g. Mbed MSD)
+// Device I/O not implemented.  No tape/serial/printer/disk/joystick.
+// No VIC II.
+// No CIA1/CIA2.
+// No sound.  Sorry no SID.
+// No cartridges.
+// Simple means simple.  This is a simple emulator using terminal console.
 //
-//   $00/$01     (DDR and banking and I/O of 6510 missing), just RAM
-//   $0000-$9FFF RAM (199=reverse if non-zero, 646=foreground color)
-//   $A000-$BFFF BASIC ROM (write to RAM underneath, but haven't implemented read/banking)
+// MEMORY MAP
+//   $00         (data direction missing)
+//   $01         Banking implemented (tape sense/controls missing)
+//   $0000-$9FFF RAM (upper limit may vary based on MCU SRAM available)
+//   $A000-$BFFF BASIC ROM
+//   $A000-$BFFF Banked LORAM (may not be present based on MCU SRAM limits)
 //   $C000-$CFFF RAM
-//   $D000-$DFFF (missing I/O and character ROM and RAM banks), just zeros except...
-//   $D021       Background Screen Color
-//   $D800-$DFFF VIC-II color RAM nybbles (note: haven't implemented RAM banking)
-//   $E000-$FFFF KERNAL ROM (write to RAM underneath, but haven't implemented read/banking)
-//
-// Requires user provided Commodore 64 BASIC/KERNAL ROMs (e.g. from VICE)
-//   as they are not provided, others copyrights may still be in effect.
+//   $D000-$D7FF (I/O missing, reads as zeros)
+//   $D800-$DFFF VIC-II color RAM nybbles in I/O space (1K x 4bits)
+//   $D000-$DFFF Banked RAM (may not be present based on MCU SRAM limits)
+//   $D000-$DFFF Banked Character ROM
+//   $E000-$FFFF KERNAL ROM
+//   $E000-$FFFF Banked HIRAM (may not be present based on MCU SRAM limits)
 //
 ////////////////////////////////////////////////////////////////////////////////
 // ROMs copyright Commodore or their assignees
 ////////////////////////////////////////////////////////////////////////////////
 
 #include <mbed.h>
-//#include <LocalFileSystem.h>
 #include "emu6502.h"
 #include "cbmconsole.h"
+#include "emuc64.h"
+
+// for limited SRAM MCUs, use a smaller number than 64.  3 <= RAM_SIZE <= 64
+#define RAM_SIZE 16
+//#define RAM_SIZE 64
 
 // global references
 extern Serial pc;
 
 // globals
+#ifdef LOCAL_LOAD
 char* StartupPRG = 0;
+#endif
 
 // locals
-//static int startup_state = 0;
-//LocalFileSystem local("local");
+#ifdef LOCAL_LOAD
+static int startup_state = 0;
+#endif
 
-//static void File_ReadAllBytes(byte* bytes, unsigned int size, const char* filename)
-//{
-//	int file;
-//	file = open(filename, O_RDONLY);
-//	if (file < 0)
-//	{
-//		pc.printf("file ""%s"", errno=%d\n", filename, errno);
-//		exit(1);
-//	}
-//	read(file, bytes, size);
-//	close(file);
-//}
+#ifdef LOCAL_LOAD
+static void File_ReadAllBytes(byte* bytes, unsigned int size, const char* filename)
+{
+	int file;
+	file = open(filename, O_RDONLY);
+	if (file < 0)
+	{
+		pc.printf("file ""%s"", errno=%d\n", filename, errno);
+		exit(1);
+	}
+	read(file, bytes, size);
+	close(file);
+}
+#endif
 
-//// returns true if BASIC
-//static bool LoadPRG(const char* filename)
-//{
-//	bool result;
-//	byte lo, hi;
-//	int file;
-//	ushort loadaddr;
-//	
-//	file = open(filename, O_RDONLY);
-//	if (file < 0
-//		|| read(file, &lo, 1) != 1
-//		|| read(file, &hi, 1) != 1
-//		)
-//	{
-//		pc.printf("file ""%s"", errno=%d\n", filename, errno);
-//		exit(1);
-//	}
-//	if (lo == 1)
-//	{
-//		loadaddr = (ushort)(GetMemory(43) | (GetMemory(44) << 8));
-//		result = true;
-//	}
-//	else
-//	{
-//		loadaddr = (ushort)(lo | (hi << 8));
-//		result = false;
-//	}
-//	while (true)
-//	{
-//		byte value;
-//		if (read(file, &value, 1) == 1)
-//			SetMemory(loadaddr++, value);
-//		else
-//			break;
-//	}
-//	close(file);
-//	return result;
-//}
+#ifdef LOCAL_LOAD
+// returns true if BASIC
+static bool LoadPRG(const char* filename)
+{
+	bool result;
+	byte lo, hi;
+	int file;
+	ushort loadaddr;
+	
+	file = open(filename, O_RDONLY);
+	if (file < 0
+		|| read(file, &lo, 1) != 1
+		|| read(file, &hi, 1) != 1
+		)
+	{
+		pc.printf("file ""%s"", errno=%d\n", filename, errno);
+		exit(1);
+	}
+	if (lo == 1)
+	{
+		loadaddr = (ushort)(GetMemory(43) | (GetMemory(44) << 8));
+		result = true;
+	}
+	else
+	{
+		loadaddr = (ushort)(lo | (hi << 8));
+		result = false;
+	}
+	while (true)
+	{
+		byte value;
+		if (read(file, &value, 1) == 1)
+			SetMemory(loadaddr++, value);
+		else
+			break;
+	}
+	close(file);
+	return result;
+}
+#endif
 
 bool ExecutePatch(void)
 {
@@ -152,75 +173,78 @@
 
 		return true; // overriden, and PC changed, so caller should reloop before execution to allow breakpoint/trace/ExecutePatch/etc.
 	}
-//	else if (PC == 0xA474) // READY
-//	{
-//		if (StartupPRG != 0 && strlen(StartupPRG) > 0) // User requested program be loaded at startup
-//		{
-//			const char* filename = StartupPRG;
-//			StartupPRG = 0;
-//
-//			if (LoadPRG(filename))
-//			{
-//				//UNNEW that I used in late 1980s, should work well for loang a program too, probably gleaned from BASIC ROM
-//				//ldy #0
-//				//lda #1
-//				//sta(43),y
-//				//iny
-//				//sta(43),y
-//				//jsr $a533 ; LINKPRG
-//				//clc
-//				//lda $22
-//				//adc #2
-//				//sta 45
-//				//lda $23
-//				//adc #0
-//				//sta 46
-//				//lda #0
-//				//jsr $a65e ; CLEAR/CLR
-//				//jmp $a474 ; READY
-//
-//				// This part shouldn't be necessary as we have loaded, not recovering from NEW, bytes should still be there
-//				ushort addr = (ushort)(GetMemory(43) | (GetMemory(44) << 8));
-//				SetMemory(addr, 1);
-//				SetMemory((ushort)(addr + 1), 1);
-//
-//				// JSR equivalent
-//				ushort retaddr = (ushort)(PC - 1);
-//				Push(HI(retaddr));
-//				Push(LO(retaddr));
-//				PC = 0xA533; // LINKPRG
-//
-//				startup_state = 1; // should be able to regain control when returns...
-//
-//				return true; // overriden, and PC changed, so caller should reloop before execution to allow breakpoint/trace/ExecutePatch/etc.
-//			}
-//		}
-//		else if (startup_state == 1)
-//		{
-//			ushort addr = (ushort)(GetMemory(0x22) | (GetMemory(0x23) << 8) + 2);
-//			SetMemory(45, (byte)addr);
-//			SetMemory(46, (byte)(addr >> 8));
-//
-//			// JSR equivalent
-//			ushort retaddr = (ushort)(PC - 1);
-//			Push(HI(retaddr));
-//			Push(LO(retaddr));
-//			PC = 0xA65E; // CLEAR/CLR
-//			A = 0;
-//
-//			startup_state = 2; // should be able to regain control when returns...
-//
-//			return true; // overriden, and PC changed, so caller should reloop before execution to allow breakpoint/trace/ExecutePatch/etc.
-//		}
-//		else if (startup_state == 2)
-//		{
-//			CBM_Console_Push("RUN\r");
-//			PC = 0xA47B; // skip READY message, but still set direct mode, and continue to MAIN
-//			C = false;
-//			startup_state = 0;
-//			return true; // overriden, and PC changed, so caller should reloop before execution to allow breakpoint/trace/ExecutePatch/etc.
-//		}
-//	}
+#ifdef LOCAL_LOAD	
+	else if (PC == 0xA474) // READY
+	{
+		if (StartupPRG != 0 && strlen(StartupPRG) > 0) // User requested program be loaded at startup
+		{
+			const char* filename = StartupPRG;
+			StartupPRG = 0;
+
+			if (LoadPRG(filename))
+			{
+				//UNNEW that I used in late 1980s, should work well for loang a program too, probably gleaned from BASIC ROM
+				//ldy #0
+				//lda #1
+				//sta(43),y
+				//iny
+				//sta(43),y
+				//jsr $a533 ; LINKPRG
+				//clc
+				//lda $22
+				//adc #2
+				//sta 45
+				//lda $23
+				//adc #0
+				//sta 46
+				//lda #0
+				//jsr $a65e ; CLEAR/CLR
+				//jmp $a474 ; READY
+
+				// This part shouldn't be necessary as we have loaded, not recovering from NEW, bytes should still be there
+				ushort addr = (ushort)(GetMemory(43) | (GetMemory(44) << 8));
+				SetMemory(addr, 1);
+				SetMemory((ushort)(addr + 1), 1);
+
+				// JSR equivalent
+				ushort retaddr = (ushort)(PC - 1);
+				Push(HI(retaddr));
+				Push(LO(retaddr));
+				PC = 0xA533; // LINKPRG
+
+				startup_state = 1; // should be able to regain control when returns...
+
+				return true; // overriden, and PC changed, so caller should reloop before execution to allow breakpoint/trace/ExecutePatch/etc.
+			}
+		}
+		else if (startup_state == 1)
+		{
+			ushort addr = (ushort)(GetMemory(0x22) | (GetMemory(0x23) << 8) + 2);
+			SetMemory(45, (byte)addr);
+			SetMemory(46, (byte)(addr >> 8));
+
+			// JSR equivalent
+			ushort retaddr = (ushort)(PC - 1);
+			Push(HI(retaddr));
+			Push(LO(retaddr));
+			PC = 0xA65E; // CLEAR/CLR
+			A = 0;
+
+			startup_state = 2; // should be able to regain control when returns...
+
+			return true; // overriden, and PC changed, so caller should reloop before execution to allow breakpoint/trace/ExecutePatch/etc.
+		}
+		else if (startup_state == 2)
+		{
+			CBM_Console_Push("RUN\r");
+			PC = 0xA47B; // skip READY message, but still set direct mode, and continue to MAIN
+			C = false;
+			startup_state = 0;
+			return true; // overriden, and PC changed, so caller should reloop before execution to allow breakpoint/trace/ExecutePatch/etc.
+		}
+	}
+#endif // LOCAL_LOAD
+	
 	return false; // execute normally
 }
 
@@ -873,7 +897,7 @@
 '\xF0','\xF0','\xF0','\xF0','\xFF','\xFF','\xFF','\xFF','\xE7','\xE7','\xE7','\x07','\x07','\xFF','\xFF','\xFF','\x0F','\x0F','\x0F','\x0F','\xFF','\xFF','\xFF','\xFF','\x0F','\x0F','\x0F','\x0F','\xF0','\xF0','\xF0','\xF0'
 };
 
-static byte ram[24 * 1024]; // MAX: 64 * 1024 if you have the SRAM, allows RAM banking
+static byte ram[RAM_SIZE * 1024]; // MAX: 64 * 1024 if you have the SRAM, allows RAM banking
 static byte color_nybles[1024];
 
 // note ram starts at 0x0000
@@ -888,6 +912,7 @@
 void C64_Init(const char* basic_file, const char* chargen_file, const char* kernal_file)
 {
 	//File_ReadAllBytes(basic_rom, sizeof(basic_rom), basic_file);
+	//File_ReadAllBytes(char_rom, sizeof(char_rom), chargen_file);
 	//File_ReadAllBytes(kernal_rom, sizeof(kernal_rom), kernal_file);
 
 	for (int i = 0; i < sizeof(ram); ++i)
@@ -943,8 +968,6 @@
 			)
 		)
 		ram[addr] = value;
-	else if (addr == 0xD021) // background
-		;
 	else if (addr >= color_addr && addr < color_addr + sizeof(color_nybles))
 		color_nybles[addr - color_addr] = value;
 	//else if (addr >= io_addr && addr < io_addr + io.Length)