sirmorris wrote:So now there is Clock Signal
Ha, not yet there isn't. I'm still expecting to discover such flaws in my design as to make the implementation entirely wasted. Most of the reason I started the project is that I want to gain a better understanding of how things operate at the electrical level, the corollary being that I'll probably make many missteps along the way.
Anyway, thanks to all for the kind words. I can see I've quite a lot of work ahead of me. I guess ZX81 and TZX support are the immediate next things.
A new build is attached that quite probably may work on 10.5, but isn't tested. And being a twenty minute job, it takes the shortcut of just doing everything on the main thread if blocks aren't available — a very shoddy way to do things with obvious user experience and performance problems but that's just the temporary solution. Xcode can no longer build for PowerPC but I doubt that'll be a practical issue unless I can speed the thing up. I've also thrown in a couple of gratuitous new screenshots.
Audio should also be much improved and a potential tape loading bug is fixed, so it's good for everyone really. And given my schedule for the week I don't expect to get much more done for a while.
With respect to the linked idea of potentially exposing the simulated bus state via hardware, the current header file for my Z80 implementation is copied below. Comments may not be entirely accurate since they're primarily for my own benefit; I'm not going to do a proper release until I've had a time to review all my comments.
One the design decisions was that the Z80 can be integrated into an emulation as either a passive or an active component. You can use it entirely as a polled device, toggling clock to run it and then inspecting lines or you can build an emulator around it being the de facto logical component. That duality has the effect that it doesn't fully simulate a bus (eg, setting a line input actually sets it; if multiple devices are connected to the same signal then whoever sets most recently will replace the previous value) but it has a reasonably involved idea of observers and can be told to clock itself for a specified number of half cycles. I think I may have been a little too wishy washy on principles.
I've taken the pragmatic consideration that the rest of the ZX80 is a single module, so that stuff is set up to get and set Z80 lines appropriately and to output to the CRT correctly but otherwise to act as it likes internally. So it's intended to be completely accurate in how it sets the lines on which it communicates, and clean at a logical level but it's not implemented as a series of discrete ICs.
As the header implies, the whole project is less than a month old, so it's not necessarily entirely settled. Please criticise now — especially if I'm doing anything that looks just plain silly or wrong.
Code: Select all
//
// Z80.h
// LLZ80
//
// Created by Thomas Harte on 11/09/2011.
// Copyright (c) 2011. All rights reserved.
//
#ifndef LLZ80_Z80_h
#define LLZ80_Z80_h
/*
Notes on Patterns
=================
This Z80 core is intended to be accurate to the nearest
half cycle.
External devices connect to the Z80 via its pins. This
code is written in terms of 'signals', which are the things
carried by pins. Quite a lot of them are Boolean, but the
data signal is a collection of eight of the original pins
and the address signal is a collection of sixteen of the
original pins.
Connected devices are observers. The simulated Z80 actually
has quite a lot of boiler-plate logic built in. So observers
generally request to be notified only when a given test
is satisfied.
Supplied tests are:
- when any of a given set of Boolean signals changes value
- when a given set of Boolean signals assumes a specified
state
- a combination of the above two
For the purposes of supplying debugging tools and for loading
and saving machine state files, the following functionality is
also provided:
- call outs in between every instruction fetch
- reading and writing of otherwise unexposed internal
state, including all registers
- an internal half-cycle timing count is kept, which
can be read from or written to
Note that the semantics related to active lines may initially
be confusing. To allow logic to be expressed clearly in C, an
active Boolean signal has a non-zero value and an inactive
signal has a zero value. The alternative was to give an active
line a zero value because on the Z80 lines are active low and
both zero and a low signal indicate absence.
Although I was conflicted and the aim of this simulator is to
be accurate to real hardware, I decided to favour semantics
over the voltage metaphor.
*/
#include <stdint.h>
/*
Creation and destruction. We're reference counting.
Use llz80_create to create a new Z80 instance.
You'll get back NULL on failure or an opaque
handle to a Z80, which is an owning reference.
Use llz80_retain and llz80_release to increment
and decrement the retain count. The Z80 will
be deallocated and all memory released back
to the system when the retain count gets to zero.
*/
void *llz80_create(void);
void *llz80_retain(void *z80); // returns the same instance passed in
void llz80_release(void *z80);
/*
Signal observing.
Just like a real z80, external devices
communicate with the Z80 by observing
changes in its signal lines. Arbitrarily
many observers can be added to each Z80
instance.
Observers can ask to be notified when
specified lines change, when given lines
assume a certain value or when a combination
of those two things happens. Observers are
always notified as soon as the given
condition is satisfied, then not again unless
it ceases to be satisfied and becomes
satisfeied again.
See the getters and setters below for
information on how to read and write actual
line values.
The return result when adding observers is
NULL on failure or an opaque handle. Use the
handle to remove the observer later on.
There's no need to remove all observers
manually when destroying a Z80.
*/
typedef enum
{
// read only signals
LLZ80SignalAddress = 0x8000,
LLZ80SignalInputOutputRequest = 0x2,
LLZ80SignalMachineCycleOne = 0x4,
LLZ80SignalRead = 0x8,
LLZ80SignalWrite = 0x10,
LLZ80SignalMemoryRequest = 0x20,
LLZ80SignalRefresh = 0x40,
LLZ80SignalBusAcknowledge = 0x80,
LLZ80SignalHalt = 0x100,
// read/write signals (but see notes re:clock)
LLZ80SignalData = 0x4000,
LLZ80SignalClock = 0x400,
// logically write only signals
// (though the last value written
// can be read back as a fiction
// of the emulation)
LLZ80SignalInterruptRequest = 0x800,
LLZ80SignalNonMaskableInterruptRequest = 0x1000,
LLZ80SignalReset = 0x2000,
LLZ80SignalWait = 0x1,
LLZ80SignalBusRequest = 0x200
} LLZ80Signal;
typedef void (* llz80_signalObserver)(void *z80, unsigned int changedLines, void *context);
// a standard observer is contacted every time any of the nominated lines changes
void *llz80_addSignalObserver(void *z80, llz80_signalObserver observer, unsigned int linesToObserve, void *context);
// a mask condition observer is contacted if one of the nominated lines changes,
// and subsequently the lines described by the mask have the values given
// in the value. It possibly feels a bit convoluted to have three fields, but
// the point is that you can watch some lines for any change while watching
// others for only when they carry a specific state.
void *llz80_addSignalObserverWithMaskCondition(
void *z80,
llz80_signalObserver observer,
unsigned int linesToObserve, // you'll be notified if any of these lines changes, and...
unsigned int lineMask, // ... these lines ...
unsigned int lineValues, // ... have these values (ie, list all that you want active here)
void *context);
// a test condition is a simplified mask condition; the observer fires only when
// the nominated transition so as to have the nominated value.
void *llz80_addSignalObserverWithTestCondition(
void *z80,
llz80_signalObserver observer,
unsigned int linesToObserve, // you'll be notified when these lines ...
unsigned int lineValues, // ... assume these values
void *context);
// an active condition is a simplified mask condition; the observer will fire
// when the nominated lines all become active simultaneously
void *llz80_addSignalObserverWithActiveCondition(
void *z80,
llz80_signalObserver observer,
unsigned int lineValues, // you'll be notified when these lines all go active
void *context);
// remove observer does as the name says; pass in the opaque handle you received when
// adding the observer
void llz80_removeSignalObserver(void *z80, void *observerHandle);
/*
Getters and setters.
These methods can be used to read or write
the Z80 signals. Note that Boolean lines
are set as active or inactive using the
defined constants.
Ints are used for getting and setting. The
signal lines will be either LLZ80_INACTIVE
or LLZ80_ACTIVE. Reading the data lines will
return an 8 bit value padded up to an int.
Reading the address lines will return a 16 bit
value padded up to an int. In both cases the
extra bits are 0 - this is explicitly not a
sign extension.
*/
#define LLZ80_ACTIVE -1
#define LLZ80_INACTIVE 0
void llz80_setSignal(void *z80, LLZ80Signal signal, int value);
int llz80_getSignal(void *z80, LLZ80Signal signal);
/*
Call this method to run an instance of the Z80
for the nominated number of half cycles. If the
Z80 is clocked at 1.5 Mhz, calling this with an
argument of 3,000,000 has the effect of running
the Z80 for one second.
*/
void llz80_runForHalfCycles(void *z80, unsigned int numberOfHalfCycles);
void llz80_stopRunning(void *z80);
/*
Monitoring functionality.
A bunch of unrealistic hooks that allow certain
components of the Z80's internal state to be
observed or polled.
Specifically: an observer can be notified whenever
a new instruction fetch is about to occur (ie, in
between opcodes).
Internal registers can be read or written. You can't
do that on a real Z80 and no external emulated
components should do so. This functionality is
provided primarily so that debuggers can be built
and state saves can be implemented.
*/
typedef void (* llz80_instructionObserver)(void *z80, void *context);
void *llz80_monitor_addInstructionObserver(void *z80, llz80_instructionObserver observer, void *context);
void llz80_monitor_removeInstructionObserver(void *z80, void *observer);
typedef enum
{
// normal registers
LLZ80MonitorValueARegister, LLZ80MonitorValueFRegister,
LLZ80MonitorValueBRegister, LLZ80MonitorValueCRegister,
LLZ80MonitorValueDRegister, LLZ80MonitorValueERegister,
LLZ80MonitorValueHRegister, LLZ80MonitorValueLRegister,
LLZ80MonitorValueAFRegister,
LLZ80MonitorValueBCRegister,
LLZ80MonitorValueDERegister,
LLZ80MonitorValueHLRegister,
// dash registers
LLZ80MonitorValueADashRegister, LLZ80MonitorValueFDashRegister,
LLZ80MonitorValueBDashRegister, LLZ80MonitorValueCDashRegister,
LLZ80MonitorValueDDashRegister, LLZ80MonitorValueEDashRegister,
LLZ80MonitorValueHDashRegister, LLZ80MonitorValueLDashRegister,
LLZ80MonitorValueAFDashRegister,
LLZ80MonitorValueBCDashRegister,
LLZ80MonitorValueDEDashRegister,
LLZ80MonitorValueHLDashRegister,
// miscellaneous registers
LLZ80MonitorValueRRegister,
LLZ80MonitorValueIRegister,
// special-purpose registers
LLZ80MonitorValueIXRegister,
LLZ80MonitorValueIYRegister,
LLZ80MonitorValueSPRegister,
LLZ80MonitorValuePCRegister,
// flags
LLZ80MonitorValueIFF1Flag,
LLZ80MonitorValueIFF2Flag,
// misc
LLZ80MonitorValueInterruptMode,
// fictitious
LLZ80MonitorValueHalfCyclesToDate
} LLZ80MonitorValue;
/*
The getter and setter for those 'values' listed above, which
are all registers and register pairs, the interrupt flags and
mode and the count of half cycles since this z80 began running.
The half cycle count is guaranteed to be accurate to half-a-cyle.
Most other values are guaranteed to be accurate only to within
the current whole instruction. Since these aren't exposed by a
real Z80, it's more than sufficiently accurate for a register
modified by an opcode to be modified anywhere during the time
that operation is documented to take.
The exceptions are the I and R registers. They are exposed by
a real Z80 since together they form the refresh address. They
therefore should be accurate to the half cycle.
Corollary: if you're writing values, you probably want to do
it within an instruction observer, to ensure you don't adjust
state mid-operation and end up with an unpredictable result.
*/
int llz80_monitor_getInternalValue(void *z80, LLZ80MonitorValue key);
void llz80_monitor_setInternalValue(void *z80, LLZ80MonitorValue key, int value);
#endif