//------------------------------------------------------------------------ // This Arduino sketch should be used with a Mega board connected to a // dongle hosting a Z80 CPU. The Arduino fully controls and senses all // Z80 CPU pins. This software runs physical Z80 CPU by providing clock // ticks and setting various control pins. // // There is a limited RAM buffer simulated by this sketch. All Z80 memory // accesses are directed to use that buffer. // // Address and data buses from Z80 are connected to analog Arduino pins. // Along with a resistor network on the dongle, this allows the software // to sense when Z80 tri-states those two buses. // // Notes: // - Use serial set to 115200 // - In the Arduino serial monitor window, set line ending to "CR" // - Memory access is simulated using a 256-byte pseudo-RAM memory // - I/O map is _not_ implemented. Reads will return whatever happens // to be on the data bus // // Copyright 2014 by Goran Devic // This source code is released under the GPL v2 software license. //------------------------------------------------------------------------ #include <stdarg.h> #include "WString.h" // Define Arduino Mega pins that are connected to a Z80 dongle board. // Pin numbers appear out-of-order, but they cleanly connect in complete // blocks to sets of pins on Arduino Mega! This will become obvious once // you start connecting them... #define DB0 A9 // DB pin line-up on a Z80 is a bit swizzled... #define DB1 A8 #define DB2 A11 #define DB3 A14 #define DB4 A15 #define DB5 A13 #define DB6 A12 #define DB7 A10 // Address bus pins from Z80 are connected to A0..A7 on Arduino. #define INT 52 // This is a block of control signals from the #define NMI 50 // bottom-left corner of Z80 #define HALT 48 #define MREQ 46 #define IORQ 44 #define RFSH 53 // This is a block of control signals from the #define M1 51 // bottom-right corner of Z80 #define RESET 49 #define BUSRQ 47 #define WAIT 45 #define BUSAK 43 #define WR 41 #define RD 39 #define CLK 13 // Clock is also toggling Arduino LED (fast, though) // Tri-state detection values: the values that are read on analog pins // sensing the "high-Z" will differ based on the resistor values that make // up your voltage divider. Print your particular readings and adjust these: #define HI_Z_LOW 50 // Upper "0" value; low tri-state boundary #define HI_Z_HIGH 600 // Low "1" value; upper tri-state boundary // Control *output* pins of Z80, we read them into these variables int halt; int mreq; int iorq; int rfsh; int m1; int busak; int wr; int rd; // Control *input* pins of Z80, we write them into the dongle int zint = 1; int nmi = 1; int reset = 1; int busrq = 1; int wait = 1; // Content of address and data wires int ab; byte db; // Clock counter after reset int clkCount; int clkCountHi; // T-cycle counter int T; int Mlast; // M1-cycle counter int m1Count; // Detection if the address or data bus is tri-stated bool abTristated = false; bool dbTristated = false; // Simulation control variables bool running = 1; // Simulation is running or is stopped int traceShowBothPhases; // Show both phases of a clock cycle int traceRefresh; // Trace refresh cycles int tracePause; // Pause for a key press every so many clocks int tracePauseCount; // Current clock count for tracePause int stopAtClk; // Stop the simulation after this many clocks int stopAtM1; // Stop at a specific M1 cycle number int stopAtHalt; // Stop when HALT signal gets active int intAtClk; // Issue INT signal at that clock number int nmiAtClk; // Issue NMI signal at that clock number int busrqAtClk; // Issue BUSRQ signal at that clock number int resetAtClk; // Issue RESET signal at that clock number int waitAtClk; // Issue WAIT signal at that clock number int clearAtClk; // Clear all control signals at that clock number byte iorqVector; // Push IORQ vector (default is FF) // Buffer containing RAM memory for Z80 to access byte ram[256]; // Temp buffer to store input line #define TEMP_SIZE 512 char temp[TEMP_SIZE]; // Temp buffer to store extra dump information char extraInfo[64] = { "" }; // Utility function to provide a meaningful printf to a serial port void p(char *fmt, ... ){ char tmp[256]; // resulting string limited to 256 chars va_list args; va_start (args, fmt ); vsnprintf(tmp, 256, fmt, args); va_end (args); Serial.print(tmp); } // Read and return one ASCII hex value from a string byte hex(char *s){ byte nibbleH = (*s - '0') & ~(1<<5); byte nibbleL = (*(s+1) - '0') & ~(1<<5); if (nibbleH>9) nibbleH -= 7; if (nibbleL>9) nibbleL -= 7; return (nibbleH << 4) | nibbleL; } // Read and return one ASCII hex value from a temp buffer given the index // of that hex number. This is used only to read Intel HEX format buffer. byte hexFromTemp(char *pTemp, int index) { int start = (index*2)+1; return hex(pTemp + start); } // ----------------------------------------------------------- // Arduino initialization entry point // ----------------------------------------------------------- void setup() { Serial.begin(115200); Serial.flush(); Serial.setTimeout(1000*60*60); ResetSimulationVars(); // By default, all Arduino pins are set as inputs // Configure all output pins, *inputs* into Z80 pinMode(CLK, OUTPUT); digitalWrite(CLK, HIGH); pinMode(INT, OUTPUT); pinMode(NMI, OUTPUT); pinMode(RESET, OUTPUT); pinMode(BUSRQ, OUTPUT); pinMode(WAIT, OUTPUT); WriteControlPins(); // Perform a Z80 CPU reset DoReset(); } // Resets all simulation variables to their defaults void ResetSimulationVars() { traceShowBothPhases = 0;// Show both phases of a clock cycle traceRefresh = 1; // Trace refresh cycles tracePause = -1; // Pause for a keypress every so many clocks stopAtClk = 40; // Stop the simulation after this many clocks stopAtM1 = -1; // Stop at a specific M1 cycle number stopAtHalt = 1; // Stop when HALT signal gets active intAtClk = -1; // Issue INT signal at that clock number nmiAtClk = -1; // Issue NMI signal at that clock number busrqAtClk = -1; // Issue BUSRQ signal at that clock number resetAtClk = -1; // Issue RESET signal at that clock number waitAtClk = -1; // Issue WAIT signal at that clock number clearAtClk = -1; // Clear all control signals at that clock number iorqVector = 0xFF; // Push IORQ vector } // Issue a RESET sequence to Z80 and reset internal counters void DoReset() { p("\r\n:Starting the clock\r\n"); digitalWrite(RESET, LOW); delay(1); // Reset should be kept low for 3 full clock cycles for(int i=0; i<3; i++) { digitalWrite(CLK, HIGH); delay(1); digitalWrite(CLK, LOW); delay(1); } p(":Releasing RESET\r\n"); digitalWrite(RESET, HIGH); delay(1); // Do not count initial 2 clocks after the reset clkCount = -2; T = 0; Mlast = 1; tracePauseCount = 0; m1Count = 0; } // Write all control pins into the Z80 dongle void WriteControlPins() { digitalWrite(INT, zint ? HIGH : LOW); digitalWrite(NMI, nmi ? HIGH : LOW); digitalWrite(RESET, reset ? HIGH : LOW); digitalWrite(BUSRQ, busrq ? HIGH : LOW); digitalWrite(WAIT, wait ? HIGH : LOW); } // Set new data value into the Z80 data bus void SetDataToDB(byte data) { pinMode(DB0, OUTPUT); pinMode(DB1, OUTPUT); pinMode(DB2, OUTPUT); pinMode(DB3, OUTPUT); pinMode(DB4, OUTPUT); pinMode(DB5, OUTPUT); pinMode(DB6, OUTPUT); pinMode(DB7, OUTPUT); digitalWrite(DB0, (data & (1<<0)) ? HIGH : LOW); digitalWrite(DB1, (data & (1<<1)) ? HIGH : LOW); digitalWrite(DB2, (data & (1<<2)) ? HIGH : LOW); digitalWrite(DB3, (data & (1<<3)) ? HIGH : LOW); digitalWrite(DB4, (data & (1<<4)) ? HIGH : LOW); digitalWrite(DB5, (data & (1<<5)) ? HIGH : LOW); digitalWrite(DB6, (data & (1<<6)) ? HIGH : LOW); digitalWrite(DB7, (data & (1<<7)) ? HIGH : LOW); db = data; dbTristated = false; } // Read Z80 data bus and store into db variable void GetDataFromDB() { pinMode(DB0, INPUT); pinMode(DB1, INPUT); pinMode(DB2, INPUT); pinMode(DB3, INPUT); pinMode(DB4, INPUT); pinMode(DB5, INPUT); pinMode(DB6, INPUT); pinMode(DB7, INPUT); digitalWrite(DB0, LOW); digitalWrite(DB1, LOW); digitalWrite(DB2, LOW); digitalWrite(DB3, LOW); digitalWrite(DB4, LOW); digitalWrite(DB5, LOW); digitalWrite(DB6, LOW); digitalWrite(DB7, LOW); // Detect if the data bus is tri-stated delay(1); int test0 = analogRead(DB0); // These numbers might need to be adjusted for each Arduino board dbTristated = test0>HI_Z_LOW && test0<HI_Z_HIGH; byte d0 = digitalRead(DB0); byte d1 = digitalRead(DB1); byte d2 = digitalRead(DB2); byte d3 = digitalRead(DB3); byte d4 = digitalRead(DB4); byte d5 = digitalRead(DB5); byte d6 = digitalRead(DB6); byte d7 = digitalRead(DB7); db = (d7<<7)|(d6<<6)|(d5<<5)|(d4<<4)|(d3<<3)|(d2<<2)|(d1<<1)|d0; } // Read a value of Z80 address bus and store it into the ab variable. // In addition, try to detect when a bus is tri-stated and write 0xFFF if so. void GetAddressFromAB() { // Detect if the address bus is tri-stated int test0 = analogRead(A0); // These numbers might need to be adjusted for each Arduino board abTristated = test0>HI_Z_LOW && test0<HI_Z_HIGH; int a0 = digitalRead(A0); int a1 = digitalRead(A1); int a2 = digitalRead(A2); int a3 = digitalRead(A3); int a4 = digitalRead(A4); int a5 = digitalRead(A5); int a6 = digitalRead(A6); int a7 = digitalRead(A7); ab = (a7<<7)|(a6<<6)|(a5<<5)|(a4<<4)|(a3<<3)|(a2<<2)|(a1<<1)|a0; } // Read all control pins on the Z80 and store them into internal variables void ReadControlState() { halt = digitalRead(HALT); mreq = digitalRead(MREQ); iorq = digitalRead(IORQ); rfsh = digitalRead(RFSH); m1 = digitalRead(M1); busak = digitalRead(BUSAK); wr = digitalRead(WR); rd = digitalRead(RD); } // Dump the Z80 state as stored in internal variables void DumpState(bool suppress) { if (!suppress) { // Select your character for tri-stated bus char abStr[4] = { "---" }; char dbStr[3] = { "--" }; if (!abTristated) sprintf(abStr, "%03X", ab); if (!dbTristated) sprintf(dbStr, "%02X", db); if (T==1 && clkCountHi) p("-----------------------------------------------------------+\r\n"); p("#%03d%c T%-2d AB:%s DB:%s %s %s %s %s %s %s %s %s |%s%s%s%s %s\r\n", clkCount<0? 0 : clkCount, clkCountHi ? 'H' : 'L', T, abStr, dbStr, m1?" ":"M1", rfsh?" ":"RFSH", mreq?" ":"MREQ", rd?" ":"RD", wr?" ":"WR", iorq?" ":"IORQ", busak?" ":"BUSAK",halt?" ":"HALT", zint?"":"[INT]", nmi?"":"[NMI]", busrq?"":"[BUSRQ]", wait?"":"[WAIT]", extraInfo); } extraInfo[0] = 0; } // ----------------------------------------------------------- // Main loop routine runs over and over again forever // ----------------------------------------------------------- void loop() { //-------------------------------------------------------- // Clock goes high //-------------------------------------------------------- delay(1); digitalWrite(CLK, HIGH); delay(1); clkCountHi = 1; clkCount++; T++; tracePauseCount++; ReadControlState(); GetAddressFromAB(); if (Mlast==1 && m1==0) T = 1, m1Count++; Mlast = m1; bool suppressDump = false; if (!traceRefresh & !rfsh) suppressDump = true; // If the number of M1 cycles has been reached, skip the rest since we dont // want to execute this M1 phase if (m1Count==stopAtM1) { sprintf(extraInfo, "Number of M1 cycles reached"), running = false; p("-----------------------------------------------------------+\r\n"); goto control; } // If the address is tri-stated, skip checking various combinations of // control signals since they may also be floating and we can't detect that if (!abTristated) { // Simulate read from RAM if (!mreq && !rd) { SetDataToDB(ram[ab & 0xFF]); if (!m1) sprintf(extraInfo, "Opcode read from %03X -> %02X", ab, ram[ab & 0xFF]); else sprintf(extraInfo, "Memory read from %03X -> %02X", ab, ram[ab & 0xFF]); } else // Simulate interrupt requesting a vector if (!m1 && !iorq) { SetDataToDB(iorqVector); sprintf(extraInfo, "Pushing vector %02X", iorqVector); } else GetDataFromDB(); // Simulate write to RAM if (!mreq && !wr) { ram[ab & 0xFF] = db; sprintf(extraInfo, "Memory write to %03X <- %02X", ab, db); } // Detect I/O read: We don't place anything on the bus if (!iorq && !rd) { sprintf(extraInfo, "I/O read from %03X", ab); } // Detect I/O write if (!iorq && !wr) { sprintf(extraInfo, "I/O write to %03X <- %02X", ab, db); } // Capture memory refresh cycle if (!mreq && !rfsh) { sprintf(extraInfo, "Refresh address %03X", ab); } } else GetDataFromDB(); DumpState(suppressDump); // If the user wanted to pause simulation after a certain number of // clocks, handle it here. If the key pressed to continue was not Enter, // stop the simulation to issue that command if (tracePause==tracePauseCount) { while(Serial.available()==0) ; if (Serial.peek()!='\r') sprintf(extraInfo, "Continue keypress was not Enter"), running = false; else Serial.read(); tracePauseCount = 0; } //-------------------------------------------------------- // Clock goes low //-------------------------------------------------------- delay(1); digitalWrite(CLK, LOW); delay(1); clkCountHi = 0; if (traceShowBothPhases) { ReadControlState(); GetAddressFromAB(); DumpState(suppressDump); } // Perform various actions at the requested clock number // if the count is positive (we start it at -2 to skip initial 2T) if (clkCount>=0) { if (clkCount==intAtClk) zint = 0; if (clkCount==nmiAtClk) nmi = 0; if (clkCount==busrqAtClk) busrq = 0; if (clkCount==resetAtClk) reset = 0; if (clkCount==waitAtClk) wait = 0; // De-assert all control pins at this clock number if (clkCount==clearAtClk) zint = nmi = busrq = reset = wait = 1; WriteControlPins(); // Stop the simulation under some conditions if (clkCount==stopAtClk) sprintf(extraInfo, "Number of clocks reached"), running = false; if (stopAtHalt&!halt) sprintf(extraInfo, "HALT instruction"), running = false; } //-------------------------------------------------------- // Trace/simulation control handler //-------------------------------------------------------- control: if (!running) { p(":Simulation stopped: %s\r\n", extraInfo); extraInfo[0] = 0; digitalWrite(CLK, HIGH); zint = nmi = busrq = wait = 1; WriteControlPins(); while(!running) { // Expect a command from the serial port if (Serial.available()>0) { memset(temp, 0, TEMP_SIZE); Serial.readBytesUntil('\r', temp, TEMP_SIZE-1); // Option ":" : this is not really a user option. This is used to // Intel HEX format values into the RAM buffer // Multiple lines may be pasted. They are separated by a space character. char *pTemp = temp; while (*pTemp==':') { byte bytes = hexFromTemp(pTemp, 0); if (bytes>0) { int address = (hexFromTemp(pTemp, 1)<<8) + hexFromTemp(pTemp, 2); byte recordType = hexFromTemp(pTemp, 3); p("%04X:", address); for (int i=0; i<bytes; i++) { ram[(address + i) & 0xFF] = hexFromTemp(pTemp, 4+i); p(" %02X", hexFromTemp(pTemp, 4+i)); } p("\r\n"); } pTemp += bytes*2 + 12; // Skip to the next possible line of hex entry } // Option "r" : reset and run the simulation if (temp[0]=='r') { // If the variable 9 (Issue RESET) is not set, perform a RESET and run the simulation. // If the variable was set, skip reset sequence since we might be testing it. if (resetAtClk<0) DoReset(); running = true; } // Option "sc" : clear simulation variables to their default values if (temp[0]=='s' && temp[1]=='c') { ResetSimulationVars(); temp[1] = 0; // Proceed to dump all variables... } // Option "s" : show and set internal control variables if (temp[0]=='s' && temp[1]!='c') { // Show or set the simulation parameters int var = 0, value; int args = sscanf(&temp[1], "%d %d\r\n", &var, &value); // Parameter for the option #12 is read in as a hex; others are decimal by default if (var==12) args = sscanf(&temp[1], "%d %x\r\n", &var, &value); if (args==2) { if (var==0) traceShowBothPhases = value; if (var==1) traceRefresh = value; if (var==2) tracePause = value; if (var==3) stopAtClk = value; if (var==4) stopAtM1 = value; if (var==5) stopAtHalt = value; if (var==6) intAtClk = value; if (var==7) nmiAtClk = value; if (var==8) busrqAtClk = value; if (var==9) resetAtClk = value; if (var==10) waitAtClk = value; if (var==11) clearAtClk = value; if (var==12) iorqVector = value & 0xFF; } p("------ Simulation variables ------\r\n"); p("#0 Trace both clock phases = %d\r\n", traceShowBothPhases); p("#1 Trace refresh cycles = %d\r\n", traceRefresh); p("#2 Pause for keypress every = %d\r\n", tracePause); p("#3 Stop after clock # = %d\r\n", stopAtClk); p("#4 Stop after # M1 cycles = %d\r\n", stopAtM1); p("#5 Stop at HALT = %d\r\n", stopAtHalt); p("#6 Issue INT at clock # = %d\r\n", intAtClk); p("#7 Issue NMI at clock # = %d\r\n", nmiAtClk); p("#8 Issue BUSRQ at clock # = %d\r\n", busrqAtClk); p("#9 Issue RESET at clock # = %d\r\n", resetAtClk); p("#10 Issue WAIT at clock # = %d\r\n", waitAtClk); p("#11 Clear all at clock # = %d\r\n", clearAtClk); p("#12 Push IORQ vector #(hex) = %2X\r\n", iorqVector); } // Option "m" : dump RAM memory if (temp[0]=='m' && temp[1]!='c') { // Dump the content of a RAM buffer p(" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\r\n"); p(" +-----------------------------------------------\r\n"); for(int i=0; i<16; i++) { p("%02X |", i); for(int j=0; j<16; j++) { p("%02X ", ram[i*16+j]); } p("\r\n"); } } // Option "mc" : clear RAM memory if (temp[0]=='m' && temp[1]=='c') { memset(ram, 0, sizeof(ram)); p("RAM cleared\r\n"); } // Option "?" : print help if (temp[0]=='?' || temp[0]=='h') { p("s - show simulation variables\r\n"); p("s #var value - set simulation variable number to a value\r\n"); p("sc - clear simulation variables to their default values\r\n"); p("r - restart the simulation\r\n"); p(":INTEL-HEX - reload RAM buffer with a given data stream\r\n"); p("m - dump the content of the RAM buffer\r\n"); p("mc - clear the RAM buffer\r\n"); } } } } }