URL
https://opencores.org/ocsvn/a-z80/a-z80/trunk
Subversion Repositories a-z80
[/] [a-z80/] [trunk/] [tools/] [Arduino/] [Z80_dongle/] [Z80_dongle.ino] - Rev 15
Go to most recent revision | Compare with Previous | Blame | View Log
//------------------------------------------------------------------------
// 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");
}
}
}
}
}
Go to most recent revision | Compare with Previous | Blame | View Log