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]

Files at this revision

API Documentation at this revision

Comitter:
davervw
Date:
Fri Apr 17 09:15:50 2020 +0000
Parent:
8:519febdce8db
Commit message:
comment updates, and implemented optional #define LOCAL_LOAD // for loading from Mbed filesystem

Changed in this revision

emuc64.cpp Show annotated file Show diff for this revision Revisions of this file
emuc64.h Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
mbed-os.lib Show annotated file Show diff for this revision Revisions of this file
--- 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)
--- a/emuc64.h	Wed Apr 15 05:15:07 2020 +0000
+++ b/emuc64.h	Fri Apr 17 09:15:50 2020 +0000
@@ -32,6 +32,12 @@
 
 #pragma once
 
+// note: LOCAL_LOAD/StartupPRG requires a file system implementation (SD, Mbed MSD, etc.)
+//#define LOCAL_LOAD
+
 extern void C64_Init(const char* basic_file, const char* chargen_file, const char* kernal_file);
+
+#ifdef LOCAL_LOAD
 extern char* StartupPRG;
+#endif
 
--- a/main.cpp	Wed Apr 15 05:15:07 2020 +0000
+++ b/main.cpp	Fri Apr 17 09:15:50 2020 +0000
@@ -35,17 +35,33 @@
 #include "emuc64.h"
 #include "emu6502.h"
 
+#ifdef LOCAL_LOAD
+#include <LocalFileSystem.h>
+LocalFileSystem local("local");
+#endif
+
 Serial pc(USBTX, USBRX, 115200);
+//Serial pc(p9, p10, 115200);
 
 int main(/*int argc, char* argv[]*/)
 {
     pc.printf("\n");
-	pc.printf("c-simple-emu-cbm version 1.6\n");
+	pc.printf("c-simple-emu-cbm version 1.7 for Mbed\n");
 	pc.printf("Copyright (c) 2020 by David R. Van Wagner\n");
-	pc.printf("MIT License\n");
+	pc.printf("Open Source - MIT License\n");
 	pc.printf("github.com/davervw\n");
 	pc.printf("\n");
-	//StartupPRG = "/local/guess2.prg";
+	pc.printf("Contains other licensed software\n");
+	pc.printf("   ARM MBED OS\n");
+	pc.printf("\n");
+	pc.printf("Commodore ROMs not licensed\n");
+	pc.printf("\n");
+
+#ifdef LOCAL_LOAD
+    // note: requires a file system implementation (SD, Mbed MSD, etc.)
+	StartupPRG = "/local/startup.prg";
+#endif	
+	
 	C64_Init("/local/basic", "/local/chargen", "/local/kernal");
 	ResetRun(ExecutePatch);
 	return 0;
--- a/mbed-os.lib	Wed Apr 15 05:15:07 2020 +0000
+++ b/mbed-os.lib	Fri Apr 17 09:15:50 2020 +0000
@@ -1,1 +1,1 @@
-https://github.com/ARMmbed/mbed-os/#532654ebb31c7bf79601042a6fa976b85532ef47
+https://github.com/ARMmbed/mbed-os/#565ab149819481224ab43f878c3921b14b11d180