/*
NeamerMon - A *Heavily* Modified Ben Eater's Monitor Program
(The only original things left are his onClock() function and the names of his pin arrays)
https://eater.net/6502
I added the ability to drive the 6502 by supplying the clock signal
*or* reacting to an external clock. I can also replicate much of the
functionality of "WozMon".
I release this code under the CC-BY-NC-SA 4.0 license.
https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en
Copyright 2023 Jeremy Thurman (Neamerjell)
Available Commands:
r addr reads data at address
w addr dd writes byte (dd) at address
l addb adde lists data between beginning and ending addresses
e addr execute starting from address
*This prompts for the choice of single step or free run modes
reset resets 6502 and this program, start from scratch
h display this list of available commands
If CLK_EN pin is tied HIGH:
Arduino-Controlled Clock (Interactive Mode)
Else:
Externally Controlled Clock; Basically Ben Eater's monitor program
Arduino Mega Pins:
******************
With the USB jack on the left, top edge:
Serial Comms >> [D21, D20], AREF, GND,
D13, D12, D11, D10, D9, D8, D7, D6, D5, D4, D3, D2, Serial Comms >> [D1, D0, D14-21]
Bottom edge:
NC, IOREF, RESET, +3.3, +5, GND. GND. VIN,
D69, D68, D67, D66, D65, D64, D63, D62, D61, D60, D59, D58, D57, D56, D55, D54
Right side, left column, top to bottom:
+5, D22, D24, D26, D28, D30, D32, D34, D36, D38, D40, D42, D44, D46, D48, D50, D52, GND
Right side, right column, top to bottom:
+5, D23, D25, D27, D29, D31, D33, D35, D37, D39, D41, D43, D45, D47, D49, D51, D53, GND
My Stackable Headers:
*********************
GND, +5, NC, NC, ACIA, VIA, PIA, RAM, ROM, RW, RST, CLK, D0, D1, D2, D3, D4, D5, D6, D7
GND, +5, IRQ, NMI, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15
Extra 6502 Pins Possibly Needed Later:
VP, RDY, SYNC
*/
// Pins
const char ADDR[] = {23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53};
const char DATA[] = {38, 40, 42, 44, 46, 48, 50, 52}; // This lines up better with my stackable header
#define CLK_EN 22 // Either +5 or GND (+5 = full functionality)
#define BE 24 // Goes directly to 6502
#define IRQ 26
#define NMI 28
#define RW 30
#define RST 32
#define CLK 34
// Globals
#define PASSIVE 0
#define INTERACTIVE 1
#define READ HIGH
#define WRITE LOW
bool PGM_MODE = INTERACTIVE; // Defines Arduino's program mode
/*
m: Monitor
p: Execute mode, entered, awating run mode input from user
s: Execute Step Mode
f: Execute Free Run Mode
*/
char run_mode = 'm';
void InitInteractive() { // PASSED TEST
pinMode(BE, OUTPUT); // Bus Enable controlled by Arduino
digitalWrite(BE, HIGH); // Bus is disabled when low
pinMode(RW, OUTPUT); // RW controlled by Arduino
digitalWrite(RW, READ); // Initialize to read mode
ToggleAddrDir(INPUT); // Initialize address to input
ToggleDataDir(INPUT); // Initialize data to input
pinMode(IRQ, INPUT); // Externally controlled IRQ
pinMode(NMI, INPUT); // Externally controlled MNI
pinMode(CLK, OUTPUT); // Clock is controlled by Arduino
pinMode(RST, OUTPUT); // Reset is controlled by Arduino
digitalWrite(RST, HIGH); // Reset is active low
digitalWrite(CLK, HIGH); // Initialize clock to high
}
// Display Help
void DisplayHelp() { // PASSED TEST
Serial.println("Available Commands:");
Serial.println(" r addr...........reads data at address");
Serial.println(" w addr dd........writes byte (dd) at address");
Serial.println(" l addb adde......lists data between beginning and ending addresses");
Serial.println();
Serial.println(" e addr...........execute starting from address");
Serial.println(" s: Stepped Clock, f: Free Run Clock, q: Quit Execute Mode");
Serial.println();
// The complexity this feature would require far outweighs its benefits
// (more trouble than it's worth)
//Serial.println(" a................assembly mode; send commands and automatically");
//Serial.println(" single step on enter key, q to exit");
Serial.println(" reset............resets 6502 and this program, start from scratch");
Serial.println(" h................display this list of available commands");
}
/* // Bus I/O - Adapted from onClock(), turned out to be unnecessary
unsigned int AddrIn() {
// Bitwise operations can be performed on any integer data type;
// in C++, the byte data type is actually an integer.
unsigned int a = 0;
for (int n = 0; n < 16; n += 1) { // Read each pin
// Initialization, assignment, and a shorthand if statement, all on one line
int bit = digitalRead(ADDR[n]) ? 1 : 0;
a = (a << 1) + bit; // Concatinate each bit to address variable
}
return a;
} */
unsigned int DataIn() { // PASSED TEST
unsigned int d = 0;
for (int n = 0; n < 8; n += 1) {
int bit = digitalRead(DATA[n]) ? 1 : 0;
d = (d << 1) + bit;
}
return d;
}
void AddrOut(String hex) { // PASSED TEST
// c_str converts string to old C character array, strtoul(char_array, ptr, base)
uint16_t numerical = strtoul(hex.c_str(), NULL, 16); // convert hex substring to numerical value
bool bit = 0;
// Is pin A0 on the 6502 the most or least significant bit?
for (int x=0; x<16; x++) {
bit = numerical & 32768; // = 1000 0000 0000 0000 binary
digitalWrite(ADDR[x], bit);
numerical = numerical << 1;
}
}
void DataOut(String hex) { // PASSED TEST
uint8_t numerical = strtoul(hex.c_str(), NULL, 16);
bool bit = 0;
for (int x=0; x<8; x++) {
bit = numerical & 128; // = 1000 0000 binary
digitalWrite(DATA[x], bit);
numerical = numerical << 1;
}
}
// Ben Eater's original monitor function - PASSED TEST
void onClock() {
// Bitwise operations can be performed on any integer data type
unsigned int address = 0;
for (int n = 0; n < 16; n += 1) { // Read each pin
int bit = digitalRead(ADDR[n]) ? 1 : 0; // Initialization, assignment, and a shorthand if statement, all on one line
Serial.print(bit); // Output each bit
address = (address << 1) + bit; // Concatinate each bit to address variable
}
Serial.print(" "); // Serial.print() continues printing on the same line until a CRLF is sent
unsigned int data = 0;
for (int n = 0; n < 8; n += 1) {
int bit = digitalRead(DATA[n]) ? 1 : 0;
Serial.print(bit);
data = (data << 1) + bit;
}
/*
sprintf "Composes a string with the same text that would be printed if format
was used on printf, but instead of being printed, the content is stored as a C string"
- https://www.programiz.com/cpp-programming/library-function/cstdio/sprintf
% begins special formatting, x converts unsigned integers to hexadecimal
I assume %04x means add leading zeros, total length is 4 characters
%c represents a single character
The output char array is more than big enough:
"___XXXX__X_XX" is 13 characters long
*/
char output[15];
sprintf(output, " %04x %c %02x", address, digitalRead(RW) ? 'r' : 'W', data);
Serial.println(output); // Serial.println() adds a CRLF to the end of the string automatically
}
// Toggle Bus Data I/O Mode - PASSED TEST
void ToggleAddrDir(uint8_t mode) { // This takes the constants INPUT, OUTPUT, same as pinMode.
for (int n = 0; n < 16; n += 1) {
pinMode(ADDR[n], mode);
}
}
void ToggleDataDir(uint8_t mode) { //PASSED TEST
for (int n = 0; n < 8; n += 1) {
pinMode(DATA[n], mode);
}
}
void PulseClock(int n) { // PASSED TEST
for ( ; n > 0; n--) {
digitalWrite(CLK, LOW);
delay(100);
digitalWrite(CLK, HIGH);
}
}
void Reset() { // PASSED TEST
/*
"The RESB signal must be held low for at least two clock cycles after VDD
reaches operating voltage." - 6502 Datasheet, pg 10
Delay is one second so it shows up for debugging purposes on the simulator
*/
digitalWrite(RST, LOW); // Trigger Reset
PulseClock(2); // Send 2 clock pulses
digitalWrite(RST, HIGH); // Release reset button
PulseClock(7); // Complete the 7-cycle reset routine
Serial.println("NeamerMon");
Serial.println("Use 'h' to display available commands.");
}
// Main Loop for Interactive Mode - PASSED TEST
void GetInput() {
while (Serial.available()) {
bool valid_cmd = false;
String cmd = Serial.readStringUntil('\n');
cmd.toLowerCase(); // toLowerCase() and trim() modify the string in place rather than returning a new one
cmd.trim();
Serial.println(cmd); // Echo the command back to the user
// "r xxxx", read data at address - PASSED TEST
if (cmd.length() == 6 && cmd.substring(0, 1) == "r" && cmd.substring(1, 2) == " ") {
valid_cmd = true;
digitalWrite(BE, LOW); // Disable CPU
ToggleAddrDir(OUTPUT); // Make sure all address pins are output...
ToggleDataDir(INPUT); // ...and data pins are input
digitalWrite(RW, READ); // Set R/W to read (high)
AddrOut(cmd.substring(2, 6)); // Put the address on the bus
digitalWrite(CLK, LOW); // Clock pulse required for VIA and others to output data to the bus
delay(1000); // This delay is apparently required for the simulator to work correctly...
unsigned int d = DataIn();
digitalWrite(CLK, HIGH);
digitalWrite(BE, HIGH); // Reenable CPU
char o[8] = {};
unsigned int a = strtoul(cmd.substring(2, 6).c_str(), NULL, 16);
sprintf(o, "%04x: %02x", a, d);
Serial.println(o);
}
// "w xxxx xx",write data at address - PASSED TEST
if (cmd.length() == 9 && cmd.substring(0, 1) == "w" && cmd.substring(1, 2) == " " && cmd.substring(6, 7) == " ") {
valid_cmd = true;
digitalWrite(BE, LOW); // Disable CPU
ToggleAddrDir(OUTPUT); // Make sure all address pins are output...
ToggleDataDir(OUTPUT); // ...and data pins are output
digitalWrite(RW, WRITE); // Set R/W to write (low)
DataOut(cmd.substring(7)); // Put the data on the bus FIRST
AddrOut(cmd.substring(2, 6)); // THEN send the address to make sure correct data is written
PulseClock(1); // Clock pulse required for VIA and others to recieve data from the bus
digitalWrite(BE, HIGH); // Reenable CPU
}
// "l addb adde", list data between addb and adde - PASSED TEST
if (cmd.length() == 11 && cmd.substring(0, 1) == "l" && cmd.substring(1, 2) == " " && cmd.substring(6, 7) == " ") {
valid_cmd = true;
digitalWrite(BE, LOW); // Disable CPU
ToggleAddrDir(OUTPUT); // Make sure all address pins are output...
ToggleDataDir(INPUT); // ...and data pins are input
String addb = cmd.substring(2, 6); // Substring beginning address
String adde = cmd.substring(7, 11); // Substring ending address
uint16_t i_addb = strtoul(addb.c_str(), NULL, 16); // Numerical beginning address
uint16_t i_adde = strtoul(adde.c_str(), NULL, 16); // Numerical ending address
bool b = 0; // Shortened bit variable (avoid possible name conflicts)
unsigned int d = 0; // Shortened data variable
String out = addb + ":"; // Initialize output to first address
char build[4] = ""; // To hold string version of data before concatination to out
uint16_t s = i_addb; // Temporary variable to do bit shifting on
/*
addr: b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be| bf
INDEX 01234567890123456789012345678901234567890123456789|012 0-52
1 2 3 4 |5 Remaining data < 50
LENGTH 12345678901234567890123456789012345678901234567890|123 1-53
1 2 3 4 5| Remaining data < 51
*/
while (i_addb <= i_adde) {
/*
Duplicated and modified part of AddrOut() here because it takes String as
input and sprintf produces a c_str, which is really just a char array
and I need to be able to increment the address with each loop
*/
for (int x=0; x<16; x++) {
b = s & 32768;
digitalWrite(ADDR[x], b);
s = s << 1;
}
digitalWrite(CLK, LOW); // Clock pulse required for VIA and others to output data to the bus
delay(1);
d = DataIn(); // Read data at address
digitalWrite(CLK, HIGH);
sprintf(build, " %02x", d); // Convert int data to string with leading space
out += build; // Concatinate the last byte
if (out.length() == 53) { // If line break is required
Serial.println(out); // Print the line
sprintf(build, "%04x:", i_addb + 1); // Reset output with next address
out = build;
build[0] = '\0'; // Reset temporary variable
build[1] = '\0';
build[2] = '\0';
build[3] = '\0';
build[4] = '\0';
}
if (i_addb == 65535) { // Adding 1 to 65535 in a uint16_t variable causes it to roll over to zero
break;
}
i_addb++; // Get next address
s = i_addb; // Save copy to bit shifting temporary variable
}
if (out.length() < 51) { // Print remaining data if necessary
Serial.println(out);
}
digitalWrite(BE, HIGH); // Reenable CPU
// list data from start to end addresses
// Loop reads, adding 1 to hex addr_start until addr_end
}
// "e xxxx", execute at address - PASSED TEST
if (cmd.length() == 6 && cmd.substring(0, 1) == "e" && cmd.substring(1, 2) == " ") {
valid_cmd = true;
ToggleAddrDir(INPUT); // 6502 needs control of the address bus
ToggleDataDir(OUTPUT); // Address to start execution needs to be manually written
pinMode(RW, INPUT); // RW controlled by 6502
String addr_h = cmd.substring(2, 4); // Get address high byte
String addr_l = cmd.substring(4); // Get address low byte
/*
Reset routine simulates pressing a button and holding for
two clock cycles, then pulsing the clock 7 times to
complete the routine. The first two cycles after that,
the 6502 reads the little endian two byte address and
loads it into the program counter.
https://www.youtube.com/watch?v=yl8vPW5hydQ&t=906s
*/
Reset(); // Trigger reset routine
DataOut(addr_l); // Put the low byte first (Little Endian)
PulseClock(1); // Send single clock pulse
DataOut(addr_h); // Put the high byte last
PulseClock(1); // Send single clock pulse
InitInteractive(); // Make sure all pins are reset to defaults
Serial.print("Run clock in (s)tepped or (f)ree run mode? ");
run_mode = 'p'; // Set mode to prompt (handled below)
}
// Alert user that while command is valid, it is only applicable to certian modes
if (((cmd == "s") || (cmd == "f") || (cmd == "q")) && (run_mode == 'm')) {
valid_cmd = true; // Supress "Invalid input..."
Serial.println("This command is only available while in Execute mode.");
}
// Get run mode from user - PASSED TEST
if (cmd.length() == 1 && run_mode == 'p' && cmd == "s") {
valid_cmd = true;
run_mode = 's'; // GetInput() checks the mode and handles this
}
if (cmd.length() == 1 && run_mode == 'p' && cmd == "f") {
valid_cmd = true;
run_mode = 'f'; // This mode is handled in loop()
}
// Trigger single step clock pulse - PASSED TEST
if (cmd.length() == 0 && (run_mode == 's' || run_mode == 'a') ) {
valid_cmd = true;
PulseClock(1);
onClock();
}
// Quit execute or assembly mode - PASSED TEST
if (cmd.length() == 1 && (run_mode == 'f' || run_mode == 's' || run_mode == 'a') && cmd == "q") {
valid_cmd = true;
run_mode = 'm';
}
/* if (cmd.length() == 1 && cmd == "q" && (run_mode != 's' || run_mode != 'f')) {
valid_cmd = true; // Supress "Invalid input..."
Serial.println("This command is only available while in Execute mode.");
}
if (cmd.length() == 0 && run_mode != 's') {
valid_cmd = true; // Supress "Invalid input..."
Serial.println("This command is only available while in Execute mode.");
} */
/* // Assembly mode
if (cmd.length() == 1 && cmd == "a") {
valid_cmd = true;
Serial.println("Not yet implemented");
} */
if (cmd.length() == 5 && cmd == "reset") { // PASSED TEST
valid_cmd = true;
InitInteractive();
Reset();
}
if (cmd.length() == 1 && cmd == "h") { // PASSED TEST
valid_cmd = true;
DisplayHelp();
}
if (valid_cmd == false) { // PASSED TEST
Serial.println("Invalid input. Use 'h' to display available commands.");
}
} // End While
} // End GetInput
// This runs only once at the beginning
void setup() { // PASSED TEST
Serial.begin(57600);
InitInteractive(); // Set pin numbers and directions
pinMode(CLK_EN, INPUT); // Tie high to enable Arduino-controlled clock
PGM_MODE = digitalRead(CLK_EN); // Determine passive or interactive mode
if (PGM_MODE == PASSIVE) { // PGM_MODE is initialized to INTERACTIVE
pinMode(CLK, INPUT); // Clock is controlled externally
pinMode(RW, INPUT); // RW controlled by 6502
pinMode(RST, INPUT); // Reset is controlled externally
attachInterrupt(digitalPinToInterrupt(CLK), onClock, RISING);
}
Reset();
}
// This runs no matter what happens in setup()
void loop() { // PASSED TEST
if (PGM_MODE == INTERACTIVE) {
GetInput();
if (run_mode == 'f') {
PulseClock(1);
onClock();
}
}
// Else: Just sit in a loop doing nothing but handling interrupts
}