PDP-8-E-Simulator/Emulation/PDP8.m

1346 lines
34 KiB
Objective-C

/*
* PDP-8/E Simulator
*
* Copyright © 1994-2015 Bernhard Baehr
*
* PDP8.m - The PDP-8/E Emulator Class
*
* This file is part of PDP-8/E Simulator.
*
* PDP-8/E Simulator is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import <Cocoa/Cocoa.h>
#import <zlib.h>
#import <mach/mach_time.h>
#define USE_PDP8_REGISTERS_DIRECTLY 1
#import "PDP8.h"
#import "itab.h"
#import "eae.h"
#import "iot.h"
#import "opr.h"
#import "Breakpoint.h"
#import "BreakpointArray.h"
#import "Utilities.h"
#import "PluginAPI.h"
#import "NSThread+MainThread.h"
#define PDP8_PREFS_KEY @"PDP-8/E"
#define CODER_KEY_HAS_EAE @"EAE"
#define CODER_KEY_HAS_KM8E @"KM8E"
#define CODER_KEY_MEMSIZE @"MEMSIZE"
#define CODER_KEY_TIMESHARING @"TIMESHARING"
#define CODER_KEY_SR @"SR"
#define CODER_KEY_LAC @"LAC"
#define CODER_KEY_PC @"PC"
#define CODER_KEY_SC @"SC"
#define CODER_KEY_MQ @"MQ"
#define CODER_KEY_GTF @"GTF"
#define CODER_KEY_IF @"IF"
#define CODER_KEY_IB @"IB"
#define CODER_KEY_DF @"DF"
#define CODER_KEY_UF @"UF"
#define CODER_KEY_UB @"UB"
#define CODER_KEY_SF @"SF"
#define CODER_KEY_ENABLE @"ENABLE"
#define CODER_KEY_INHIBIT @"INHIBIT"
#define CODER_KEY_DELAY @"DELAY"
#define CODER_KEY_USERMASK @"USERMASK"
#define CODER_KEY_USERFLAG @"USERFLAG"
#define CODER_KEY_EAEMODE @"EAEMODE"
#define CODER_KEY_MEMORY @"MEMORY"
PDP8 *pdp8; /* the one and only instance of the PDP8 class, only used by the opcode C functions */
@implementation PDP8
#pragma mark Hardware Configuration
- (void) mountEAEforced:(BOOL)mount // mount or unmount anyway (used only on startup)
{
static void (*eaetab[])(void) = {
i7401, i7403, i7405, i7407, i7411, i7413, i7415, i7417,
i7421, i7423, i7425, i7427, i7431, i7433, i7435, i7437,
i7441, i7443, i7445, i7447, i7451, i7453, i7455, i7457,
i7461, i7463, i7465, i7467, i7471, i7473, i7475, i7477,
i7501, i7503, i7505, i7507, i7511, i7513, i7515, i7517,
i7521, i7523, i7525, i7527, i7531, i7533, i7535, i7537,
i7541, i7543, i7545, i7547, i7551, i7553, i7555, i7557,
i7561, i7563, i7565, i7567, i7571, i7573, i7575, i7577,
i7601, i7603, i7605, i7607, i7611, i7613, i7615, i7617,
i7621, i7623, i7625, i7627, i7631, i7633, i7635, i7637,
i7641, i7643, i7645, i7647, i7651, i7653, i7655, i7657,
i7661, i7663, i7665, i7667, i7671, i7673, i7675, i7677,
i7701, i7703, i7705, i7707, i7711, i7713, i7715, i7717,
i7721, i7723, i7725, i7727, i7731, i7733, i7735, i7737,
i7741, i7743, i7745, i7747, i7751, i7753, i7755, i7757,
i7761, i7763, i7765, i7767, i7771, i7773, i7775, i7777
};
static void (*noeaetab[])(void) = {
i7401, i7421, i7401, i7421, i7501, i7521, i7501, i7521,
i7601, i7621, i7601, i7621, i7701, i7721, i7701, i7721
};
int i, j;
_hw.hasEAE = mount;
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
if (mount) {
for (i = 07401, j = 0; i < 010000; i += 2)
itab[i] = eaetab[j++];
for (i = 017401, j = 0; i < 020000; i += 2)
itab[i] = eaetab[j++];
} else {
for (i = 07401, j = 0; i < 010000; i += 2)
itab[i] = noeaetab[j++ >> 3];
for (i = 017401, j = 0; i < 020000; i += 2)
itab[i] = noeaetab[j++ >> 3];
SC = 0;
GTF = 0;
eaeMode = EAE_MODE_A;
if (_state.running != GOING) {
[defaultCenter postNotificationName:SC_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:GTF_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:EAE_MODE_CHANGED_NOTIFICATION object:self];
}
}
[defaultCenter postNotificationName:EAE_MOUNT_NOTIFICATION object:self];
}
- (void) mountEAE:(BOOL)mount // mount only when needed (to avoid multiple executions due to notifications)
{
if (_hw.hasEAE != mount)
[self mountEAEforced:mount];;
}
- (BOOL) hasEAE
{
return _hw.hasEAE;
}
- (void) mountKM8Eforced:(BOOL)mount memorySize:(unsigned)memsize timesharingEnabled:(BOOL)timesharing
// mount or unmount anyway (used only on startup)
{
static const struct {
ushort addr;
void (*inst)(void);
} km8etab[] = {
{ 06201, i6201 }, { 06202, i6202 }, { 06203, i6203 }, { 06204, i6204 },
{ 06211, i6211 }, { 06212, i6212 }, { 06213, i6213 }, { 06214, i6214 },
{ 06221, i6221 }, { 06222, i6222 }, { 06223, i6223 }, { 06224, i6224 },
{ 06231, i6231 }, { 06232, i6232 }, { 06233, i6233 }, { 06234, i6234 },
{ 06241, i6241 }, { 06242, i6242 }, { 06243, i6243 }, { 06244, i6244 },
{ 06251, i6251 }, { 06252, i6252 }, { 06253, i6253 }, { 06254, i6254 },
{ 06261, i6261 }, { 06262, i6262 }, { 06263, i6263 }, { 06264, i6264 },
{ 06271, i6271 }, { 06272, i6272 }, { 06273, i6273 }, { 06274, i6274 },
{ 0, NULL }
};
int i;
NSAssert ((memsize & ~0170000) == 0, @"Bad memory size");
NSAssert ((mount && memsize > PDP8_FIELDSIZE) || memsize == PDP8_FIELDSIZE,
@"Memory size does not match KM8-E state");
NSAssert (mount || ! timesharing, @"Timesharing option does not match KM8-E state");
_hw.hasKM8E = mount;
_hw.hasKM8Etimesharing = timesharing;
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
if (mount) {
for (i = 0; km8etab[i].addr; i++)
itab[km8etab[i].addr] = km8etab[i].inst;
IMASK |= userFLAG;
} else {
for (i = 0; km8etab[i].addr; i++)
itab[km8etab[i].addr] = i7000;
DF = W_DF = 0;
IF = IB = W_IB = 0;
UF = UB = 0;
SF = 0;
IINHIBIT = 0;
IMASK &= ~userFLAG;
IOFLAGS &= ~userFLAG;
if (_state.running != GOING) {
[defaultCenter postNotificationName:DF_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:PROGRAM_COUNTER_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:UF_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:UB_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:SF_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:INHIBIT_CHANGED_NOTIFICATION object:self];
}
}
i = _hw.memsize = memsize;
W_DF = (DF < _hw.memsize) ? DF : 0100000;
W_IB = (IB < _hw.memsize) ? IB : 0100000;
while (i < 0100000)
mem[i++] = 0;
if (_state.running != GOING) {
[defaultCenter postNotificationName:IOFLAGS_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:MEMORY_CHANGED_NOTIFICATION object:self];
}
[defaultCenter postNotificationName:KM8E_MOUNT_NOTIFICATION object:self];
}
- (void) mountKM8E:(BOOL)mount memorySize:(unsigned)memsize timesharingEnabled:(BOOL)timesharing
// mount only when needed (to avoid multiple executions due to notifications)
{
if (_hw.hasKM8E != mount || _hw.hasKM8Etimesharing != timesharing || _hw.memsize != memsize)
[self mountKM8Eforced:mount memorySize:memsize timesharingEnabled:timesharing];
}
- (BOOL) hasKM8E
{
return _hw.hasKM8E;
}
- (BOOL) isTimesharingEnabled
{
return _hw.hasKM8Etimesharing;
}
- (ushort) memorySize
{
return _hw.memsize;
}
- (BOOL) isIOAddressAvailable:(int)addr
{
int i;
NSAssert1 ((addr & ~077) == 0, @"Bad I/O address %o", addr);
addr = 06000 | (addr << 3);
for (i = 0; i < 8; i++) {
if (itab[addr | i] != i7000)
return NO;
}
return YES;
}
- (void) setIOT:(NSValue *)iot forOpcode:(int)opcode
{
NSAssert1 ((opcode & ~0777) == 06000, @"Invalid IOT %o", opcode);
if (iot) {
PDP8InstructionFunctionPointer p = [iot pointerValue];
if (p)
itab[opcode] = p;
}
}
- (void) setPluginPointer:(void *)pointer forIOAddress:(int)addr
{
NSAssert1 ((addr & ~077) == 0, @"Bad I/O address %o", addr);
_state.pluginPointer[addr] = pointer;
}
#if ! defined(NS_BLOCK_ASSERTIONS)
- (id) pluginPointer:(Class)pluginClass;
{
id pluginPointer = _state.pluginPointer[(_state.currInst >> 3) & 077];
NSAssert (pluginPointer && [pluginPointer isMemberOfClass:pluginClass], @"Bad plugin pointer");
return pluginPointer;
}
#endif
#pragma mark Initialization
- (void) notifyApplicationWillFinishLaunching:(NSNotification *)notification
{
// NSLog (@"PDP8 notifyApplicationWillFinishLaunching");
NSData *data = [[NSUserDefaults standardUserDefaults] dataForKey:PDP8_PREFS_KEY];
if (data) {
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
[self initWithCoder:unarchiver];
[unarchiver finishDecoding];
[unarchiver release];
} else {
[self mountEAEforced:NO];
[self mountKM8Eforced:NO memorySize:PDP8_FIELDSIZE timesharingEnabled:NO];
[self reset];
}
}
- (void) notifyApplicationWillTerminate:(NSNotification *)notification
{
// NSLog (@"PDP8 notifyApplicationWillTerminate");
NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[self encodeWithCoder:archiver];
[archiver finishEncoding];
[archiver release];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:PDP8_PREFS_KEY];
}
- (void) awakeFromNib
{
pdp8 = self;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyApplicationWillFinishLaunching:)
name:NSApplicationWillFinishLaunchingNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyApplicationWillTerminate:)
name:NSApplicationWillTerminateNotification object:nil];
}
- (id) initWithCoder:(NSCoder *)coder
{
self = [super init];
// hardware configuration - note: redundant to prefs settings
[self mountEAEforced:[coder decodeBoolForKey:CODER_KEY_HAS_EAE]];
[self mountKM8Eforced:[coder decodeBoolForKey:CODER_KEY_HAS_KM8E]
memorySize:[coder decodeIntForKey:CODER_KEY_MEMSIZE]
timesharingEnabled:[coder decodeBoolForKey:CODER_KEY_TIMESHARING]];
// registers
[self setSR:[coder decodeIntForKey:CODER_KEY_SR]];
[self setLAC:[coder decodeIntForKey:CODER_KEY_LAC]];
[self setPC:[coder decodeIntForKey:CODER_KEY_PC]];
[self setSC:[coder decodeIntForKey:CODER_KEY_SC]];
[self setMQ:[coder decodeIntForKey:CODER_KEY_MQ]];
[self setGTF:[coder decodeIntForKey:CODER_KEY_GTF]];
[self setIF:[coder decodeIntForKey:CODER_KEY_IF]];
[self setIB:[coder decodeIntForKey:CODER_KEY_IB]];
[self setDF:[coder decodeIntForKey:CODER_KEY_DF]];
[self setUF:[coder decodeIntForKey:CODER_KEY_UF]];
[self setUB:[coder decodeIntForKey:CODER_KEY_UB]];
[self setSF:[coder decodeIntForKey:CODER_KEY_SF]];
[self setEnable:[coder decodeIntForKey:CODER_KEY_ENABLE]];
[self setInhibit:[coder decodeIntForKey:CODER_KEY_INHIBIT]];
[self setDelay:[coder decodeIntForKey:CODER_KEY_DELAY]];
[coder decodeBoolForKey:CODER_KEY_USERMASK] ?
[self setInterruptMaskBits:userFLAG] : [self clearInterruptMaskBits:userFLAG];
[coder decodeBoolForKey:CODER_KEY_USERFLAG] ?
[self setIOFlagBits:userFLAG] : [self clearIOFlagBits:userFLAG];
[self setEAEmode:[coder decodeIntForKey:CODER_KEY_EAEMODE]];
// memory
NSData *data = [coder decodeObjectForKey:CODER_KEY_MEMORY];
unsigned long memsize = _hw.memsize * sizeof(mem[0]);
int err = uncompress((void *) mem, &memsize, [data bytes], [data length]);
if (err)
NSLog (@"PDP8 decodeWithCoder memory uncompress error %d", err);
return self;
}
- (void) encodeWithCoder:(NSCoder *)coder
{
// hardware configuration - note: redundant to prefs settings
[coder encodeBool:[self hasEAE] forKey:CODER_KEY_HAS_EAE];
[coder encodeBool:[self hasKM8E] forKey:CODER_KEY_HAS_KM8E];
[coder encodeInt:[self memorySize] forKey:CODER_KEY_MEMSIZE];
[coder encodeBool:[self isTimesharingEnabled] forKey:CODER_KEY_TIMESHARING];
// registers
[coder encodeInt:[self getSR] forKey:CODER_KEY_SR];
[coder encodeInt:[self getLAC] forKey:CODER_KEY_LAC];
[coder encodeInt:[self getPC] forKey:CODER_KEY_PC];
[coder encodeInt:[self getSC] forKey:CODER_KEY_SC];
[coder encodeInt:[self getMQ] forKey:CODER_KEY_MQ];
[coder encodeInt:[self getGTF] forKey:CODER_KEY_GTF];
[coder encodeInt:[self getIF] forKey:CODER_KEY_IF];
[coder encodeInt:[self getIB] forKey:CODER_KEY_IB];
[coder encodeInt:[self getDF] forKey:CODER_KEY_DF];
[coder encodeInt:[self getUF] forKey:CODER_KEY_UF];
[coder encodeInt:[self getUB] forKey:CODER_KEY_UB];
[coder encodeInt:[self getSF] forKey:CODER_KEY_SF];
[coder encodeInt:[self getEnable] forKey:CODER_KEY_ENABLE];
[coder encodeInt:[self getInhibit] forKey:CODER_KEY_INHIBIT];
[coder encodeInt:[self getDelay] forKey:CODER_KEY_DELAY];
[coder encodeBool:[self getInterruptMaskBits:userFLAG] ? YES : NO forKey:CODER_KEY_USERMASK];
[coder encodeBool:[self getIOFlagBits:userFLAG] ? YES : NO forKey:CODER_KEY_USERFLAG];
[coder encodeInt:[self getEAEmode] forKey:CODER_KEY_EAEMODE];
// memory
unsigned long datasize = compressBound(_hw.memsize * sizeof(mem[0]));
NSMutableData *data = [NSMutableData dataWithLength:datasize];
int err = compress2([data mutableBytes], &datasize, (void *) mem,
_hw.memsize * sizeof(mem[0]), Z_BEST_COMPRESSION);
if (err)
NSLog (@"PDP8 encodeWithCoder memory compress2 error %d", err);
[data setLength:datasize];
[coder encodeObject:data forKey:CODER_KEY_MEMORY];
}
#pragma mark Utilities
- (NSString *) loadPaperTape:(NSString *)filename toField:(ushort)field
/* this code is based on a very careful analysis of the PDP-8 BIN loader */
{
ushort c, x, oldchecksum;
NSAssert1 ((field & ~07) == 0, @"Bad memory field 0%o", field);
x = oldchecksum = 0; /* to avoid compiler "used uninitialized" warning */
field <<= 12;
if (field >= _hw.memsize)
return NSLocalizedString(@"Cannot load code to non-existing memory field.", @"");
FILE *fp = fopen([filename cStringUsingEncoding:NSASCIIStringEncoding], "r");
if (! fp)
return NSLocalizedString(@"Cannot open the paper tape for reading.", @"");
ushort origin = 0;
ushort checksum = 0;
BOOL isRimTape = TRUE;
BOOL lastWordWasOrigin = FALSE;
for (c = 0200; c == 0200 && ! feof(fp); c = getc(fp)) /* leader */
;
while (c != 0200 && ! feof(fp)) {
if ((c & 0300) == 0300) { /* field setting */
if (c != 0377) { /* all holes punched are ignored */
isRimTape = FALSE; /* RIM cannot have field setting */
field = ((ushort) (c & 070)) << 9;
if (field >= _hw.memsize)
field = 0100000u;
/* field setting are not included in the checksum */
}
c = getc(fp);
} else if ((c & 0300) == 0100) { /* origin */
lastWordWasOrigin = TRUE;
origin = (c & 077) << 6;
oldchecksum = checksum;
checksum += c;
c = getc(fp);
origin += c;
checksum += c;
c = getc(fp);
} else { /* value to deposit in memory */
if (! lastWordWasOrigin)
isRimTape = FALSE;
lastWordWasOrigin = FALSE;
x = (c & 077) << 6;
oldchecksum = checksum;
checksum += c;
c = getc(fp);
x += c;
checksum += c;
c = getc(fp);
if ((c != 0200 && ! feof(fp)) || isRimTape) {
mem[field | origin] = x;
origin = (origin + 1) & 07777;
}
}
}
[[NSNotificationCenter defaultCenter]
postNotificationName:MEMORY_CHANGED_NOTIFICATION object:self];
while (c == 0200 && ! feof(fp))
c = getc(fp);
if (! feof(fp)) {
fclose (fp);
return NSLocalizedString(@"There is something wrong with the paper tape, "
@"maybe garbage before leader or after trailer.", @"");
}
fclose (fp);
if (! isRimTape && (oldchecksum & 07777) != (lastWordWasOrigin ? origin : x))
return NSLocalizedString(@"The paper tape contains a checksum error.", @"");
return nil;
}
- (void) notifyEverythingChanged
{
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
[defaultCenter postNotificationName:PROGRAM_COUNTER_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:ACCUMULATOR_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:SR_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:SC_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:GTF_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:MQ_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:EAE_MODE_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:DF_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:UF_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:UB_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:SF_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:ENABLE_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:DELAY_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:INHIBIT_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:IOFLAGS_CHANGED_NOTIFICATION object:self];
[defaultCenter postNotificationName:MEMORY_CHANGED_NOTIFICATION object:self];
}
- (void) clearAllFlags
{
[self setLAC:0];
[self setEnable:0];
[self setInhibit:0];
[self setDelay:0];
[self clearInterruptMaskBits:~userFLAG];
[self clearIOFlagBits:~0];
[self setEAEmode:EAE_MODE_A];
[self setGTF:0];
int i;
for (i = 0; i < PDP8_IOADDRS; i++) {
if (_state.pluginPointer[i])
[(PDP8Plugin *) _state.pluginPointer[i] clearAllFlags:i];
}
[[NSNotificationCenter defaultCenter] postNotificationName:CLEAR_ALL_FLAGS_NOTIFICATION object:self];
}
- (void) loadExtendedAddress
{
if (_hw.hasKM8E) {
[self setIB:(SR & 070) >> 3];
[self setIF:(SR & 070) >> 3];
[self setDF:SR & 007];
[self setUB:0];
[self setUF:0];
[self clearIOFlagBits:userFLAG];
}
}
- (void) reset
{
[self setProgramCounter:0200];
[self setLAC:0];
[self setSR:0];
[self setSC:0];
[self setMQ:0];
[self setGTF:0];
[self setDF:0];
[self setUF:0];
[self setUB:0];
[self setSF:0];
[self setEAEmode:EAE_MODE_A];
[self setEnable:0];
[self setDelay:0];
[self setInhibit:0];
[self clearInterruptMaskBits:~userFLAG];
[self clearIOFlagBits:~0];
[self clearMemory];
}
#pragma mark Step - Trace - Go
- (void) setTraceSpeed:(double)speed
{
_state.usecTraceDelay = (unsigned) (speed * 1000000.0);
}
- (void) setGoSpeed:(int)goSpeed
{
NSAssert (goSpeed == GO_AS_FAST_AS_POSSIBLE || goSpeed == GO_WITH_PDP8_SPEED ||
goSpeed == GO_WITH_PDP8_SPEED_PRECISE, @"Illegal go speed specififed");
/* reset the timer - required when the PDP-8 runs while this method is called */
_state.executionTime = 0;
_state.absoluteTime = mach_absolute_time();
_state.goSpeed = goSpeed;
}
- (int) getGoSpeed
{
return _state.goSpeed;
}
- (BOOL) isStopped
{
return _state.running == STOPPED;
}
- (BOOL) isTracing
{
return _state.running == TRACING;
}
- (BOOL) isGoing;
{
return _state.running == GOING;
}
- (BOOL) isRunning
{
return _state.running != STOPPED;
}
- (BOOL) isHalted
{
return _state.halted;
}
- (void) setHalt:(BOOL)halt
{
if (halt)
[self stop];
_state.halted = halt;
}
static void breakInstruction (void) /* used for break opcodes */
{
pdp8->_state.running = STOPPED;
pdp8->PC--;
}
- (void) setupBreakpoints:(int)stopAddress
{
NSEnumerator *enumerator;
Breakpoint *next;
NSAssert (stopAddress == -1 || (stopAddress & ~077777) == 0, @"Invalid stop address");
// breakpoints
enumerator = [breakpoints enumerator];
bzero (bp, sizeof(bp));
while ((next = (Breakpoint *)[enumerator nextObject]))
bp[[next identifier]] = [next value];
if (stopAddress >= 0)
bp[stopAddress] = BREAKPOINT;
// break opcodes
NSAssert (saveopcodes == nil, @"saveopcodes not nil");
saveopcodes = [[NSMutableDictionary alloc] init];
enumerator = [breakopcodes enumerator];
while ((next = (Breakpoint *)[enumerator nextObject])) {
unsigned opcode = [next identifier];
if ([next value] & USERMODE_BREAKOPCODE) {
[saveopcodes setObject:[NSValue valueWithPointer:itab[opcode]]
forKey:[NSNumber numberWithInt:opcode]];
itab[opcode] = breakInstruction;
}
if ([next value] & SYSTEMMODE_BREAKOPCODE) {
opcode |= 010000;
[saveopcodes setObject:[NSValue valueWithPointer:itab[opcode]]
forKey:[NSNumber numberWithInt:opcode]];
itab[opcode] = breakInstruction;
}
}
}
- (void) resetBreakpoints
{
NSNumber *next;
NSEnumerator *enumerator = [saveopcodes keyEnumerator];
while ((next = (NSNumber *)[enumerator nextObject]))
itab[[next intValue]] = [[saveopcodes objectForKey:next] pointerValue];
[saveopcodes release];
saveopcodes = nil;
}
- (void) sendStopNotification // must run on main thread
{
NSAssertRunningOnMainThread ();
[[NSNotificationCenter defaultCenter] postNotificationName:PDP8_STOP_NOTIFICATION object:self];
[self notifyEverythingChanged];
}
- (void) oneRunLoopPass
{
NSAssertRunningOnMainThread ();
[[NSRunLoop currentRunLoop] limitDateForMode:NSDefaultRunLoopMode];
}
- (void) pdp8Step
{
/* Check for interrupts */
if (IENABLE && (IOFLAGS & IMASK) && ! (IDELAY || IINHIBIT)) {
mem[0] = PC;
PC = 1;
SF = (UF >> 6) | (IF >> 9) | (DF >> 12);
W_IB = IB = IF = W_DF = DF = UB = UF = IENABLE = 0;
EXECUTION_TIME (14); /* count the execute cycle of JMS 0 */
} else {
IDELAY = 0;
/* Fetch and execute an instruction */
itab[UF | (_state.currInst = mem[IF | PC])]();
/* Update the program counter */
if (++PC & 010000)
PC &= 07777;
}
}
- (void) sendStepNotifications
{
NSAssertRunningOnMainThread ();
[[NSNotificationCenter defaultCenter] postNotificationName:PDP8_STEP_NOTIFICATION object:self];
[self notifyEverythingChanged];
}
- (void) step
{
NSAssert (_state.running == STOPPED, @"PDP-8 not stopped");
[self pdp8Step];
[self sendStepNotifications];
}
- (void) traceThread:(NSNumber *)stopAddress
{
struct timeval tv0, tv1;
struct timezone tz;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
BOOL installBreakpoints = TRUE;
[NSThread setThreadPriority:0];
// low priority for the trace thread, so the main thread can process user interactions
_state.running = TRACING;
while (_state.running) {
gettimeofday (&tv0, &tz);
[self pdp8Step];
[self performSelectorOnMainThread:@selector(sendStepNotifications)
withObject:nil waitUntilDone:YES];
if (installBreakpoints) { // one step without breakpoints
[self setupBreakpoints:[stopAddress intValue]];
installBreakpoints = FALSE;
}
if (bp[IF | PC])
_state.running = STOPPED;
else if (_state.running) {
/* Give the main thread the chance to handle events. When tracing with full
speed (state.usecTraceDelay == 0), on dual core machines, the trace thread
calls the step method so fast on the main thread that it can't handle user
events. */
[self performSelectorOnMainThread:@selector(oneRunLoopPass)
withObject:nil waitUntilDone:YES];
gettimeofday (&tv1, &tz);
timersub (&tv1, &tv0, &tv0);
if (_state.usecTraceDelay > (ulong) tv0.tv_usec)
usleep (_state.usecTraceDelay - tv0.tv_usec);
}
}
[self resetBreakpoints];
[self performSelectorOnMainThread:@selector(sendStopNotification)
withObject:nil waitUntilDone:YES];
[pool release];
}
- (void) trace:(int)stopAddress
{
NSAssert (_state.running == STOPPED, @"PDP-8 not stopped");
NSAssert (! _state.halted, @"PDP8 trace while halted");
if (_state.halted)
[self step];
else {
[[NSNotificationCenter defaultCenter] postNotificationName:PDP8_TRACE_NOTIFICATION object:self];
[NSThread detachNewThreadSelector:@selector(traceThread:) toTarget:self
withObject:[NSNumber numberWithInt:stopAddress]];
}
}
- (void) goThread:(id)object
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// _state.running = GOING; // already set, see comment below in the go method
_state.executionTime = 0;
_state.absoluteTime = mach_absolute_time();
while (_state.running) {
[self pdp8Step];
/* Check for breakpoints */
if (bp[IF | PC])
_state.running = STOPPED;
/* Realtime speed */
if (_state.goSpeed != GO_AS_FAST_AS_POSSIBLE) {
uint64_t delayUntilAbsolute = _state.absoluteTime +
nanoseconds2absolute(_state.executionTime * 100l);
if (_state.goSpeed == GO_WITH_PDP8_SPEED_PRECISE) {
// precise timing: delay after each PDP-8 instruction using busy waiting
// this delay takes about 0.1 microseconds longer than required
// (precision in the order of 0.1 PDP-8 instructions)
while (mach_absolute_time() < delayUntilAbsolute)
;
} else if (mach_absolute_time() + nanoseconds2absolute(15000l) < delayUntilAbsolute) {
// PDP-8 speed without precise timing: delay when a forerun of at least
// 15 microseconds has accumulated (to avoid high kernel load on slower Macs)
// mach_wait_until() delays about 10 microseconds longer than required
// (precision in the order of about some dozen PDP-8 instructions)
mach_wait_until (delayUntilAbsolute);
}
// consider the time lag due to too long delays for the next loop
_state.absoluteTime = delayUntilAbsolute;
_state.executionTime = 0;
}
}
[self resetBreakpoints];
[self performSelectorOnMainThread:@selector(sendStopNotification)
withObject:nil waitUntilDone:YES];
[pool release];
}
- (void) go:(int)stopAddress
{
NSAssert (_state.running == STOPPED, @"PDP-8 not stopped");
NSAssert (! _state.halted, @"PDP-8 trace while halted");
if (_state.halted)
[self step];
else {
/* set GOING right now, otherwise [self pdp8Step] might post GUI notifications
(e. g. IOFLAGS_CHANGED_NOTIFICATION via the CAF instruction) that are processed later
when the CPU is already running, and that can cause a crash, e. g. with the skip
indicator update */
_state.running = GOING;
[self pdp8Step]; // one step without breakpoints and without GUI notifications
[self setupBreakpoints:stopAddress];
[[NSNotificationCenter defaultCenter] postNotificationName:PDP8_GO_NOTIFICATION object:self];
[NSThread detachNewThreadSelector:@selector(goThread:) toTarget:self withObject:nil];
}
}
- (void) stop
{
_state.running = STOPPED;
}
#pragma mark Register Access
#define NOTIFY(notification) if (_state.running != GOING && [NSThread isMainThread]) { \
[[NSNotificationCenter defaultCenter] \
postNotificationName:(notification) object:self]; \
}
- (ushort) getPC
{
return PC;
}
- (void) setPC:(ushort)pc
{
NSAssert1 ((pc & ~07777) == 0, @"Bad PC: 0%o", pc);
PC = pc;
NOTIFY (PROGRAM_COUNTER_CHANGED_NOTIFICATION);
}
- (ushort) getProgramCounter
{
return IF | PC;
}
- (void) setProgramCounter:(ushort)programCounter
{
NSAssert1 ((programCounter & ~077777) == 0, @"Bad IF|PC: 0%o", programCounter);
NSAssert1 (programCounter < 010000 || _hw.hasKM8E, @"Can't set IF|PC to field %o without KM8-E",
programCounter >> 12);
IB = IF = programCounter & 070000;
PC = programCounter & 007777;
NOTIFY (PROGRAM_COUNTER_CHANGED_NOTIFICATION);
}
- (ushort) getIF
{
return IF >> 12;
}
- (void) setIF:(ushort)_if
{
NSAssert1 ((_if & ~07) == 0, @"Bad IF: 0%o", _if);
IF = _if << 12;
NOTIFY (PROGRAM_COUNTER_CHANGED_NOTIFICATION);
}
- (ushort) getIB
{
return IB >> 12;
}
- (void) setIB:(ushort)ib
{
NSAssert1 ((ib & ~07) == 0, @"Bad IB: 0%o", ib);
W_IB = IB = ib << 12;
if (W_IB >= _hw.memsize)
W_IB = 0100000;
NOTIFY (PROGRAM_COUNTER_CHANGED_NOTIFICATION);
}
- (ushort) getDF
{
return DF >> 12;
}
- (void) setDF:(ushort)df
{
NSAssert1 ((df & ~07) == 0, @"Bad DF: 0%o", df);
W_DF = DF = df << 12;
if (W_DF >= _hw.memsize)
W_DF = 0100000;
NOTIFY (DF_CHANGED_NOTIFICATION);
}
- (ushort) getUF
{
return UF >> 12;
}
- (void) setUF:(ushort)uf
{
NSAssert1 ((uf & ~01) == 0, @"Bad UF: 0%o", uf);
UF = uf << 12;
NOTIFY (UF_CHANGED_NOTIFICATION);
}
- (ushort) getUB
{
return UB >> 12;
}
- (void) setUB:(ushort)ub
{
NSAssert1 ((ub & ~01) == 0, @"Bad UB: %o", ub);
UB = ub << 12;
NOTIFY (UB_CHANGED_NOTIFICATION);
}
- (ushort) getSF
{
return SF;
}
- (void) setSF:(ushort)sf
{
NSAssert1 ((sf & ~0177) == 0, @"Bad SF: 0%o", sf);
SF = sf;
NOTIFY (SF_CHANGED_NOTIFICATION);
}
- (ushort) getSR
{
return SR;
}
- (void) setSR:(ushort)sr
{
NSAssert1 ((sr & ~077777) == 0, @"Bad SR: 0%o", sr);
SR = sr;
NOTIFY (SR_CHANGED_NOTIFICATION);
}
- (ushort) getL
{
return AC >> 12;
}
- (void) setL:(ushort)l
{
NSAssert1 ((l & ~01) == 0, @"Bad Link: 0%o", l);
AC = (l << 12) | (AC & 07777);
NOTIFY (ACCUMULATOR_CHANGED_NOTIFICATION);
}
- (ushort) getAC
{
return AC & 07777;
}
- (void) setAC:(ushort)ac
{
NSAssert1 ((ac & ~07777) == 0, @"Bad AC: 0%o", ac);
AC = (AC & 010000) | ac;
NOTIFY (ACCUMULATOR_CHANGED_NOTIFICATION);
}
- (ushort) getLAC
{
return AC;
}
- (void) setLAC:(ushort)lac
{
NSAssert1 ((lac & ~017777) == 0, @"Bad L|AC: 0%o", lac);
AC = lac;
NOTIFY (ACCUMULATOR_CHANGED_NOTIFICATION);
}
- (ushort) getSC
{
return SC;
}
- (void) setSC:(ushort)sc
{
NSAssert1 ((sc & ~037) == 0, @"Bad SC: 0%o", sc);
SC = sc;
NOTIFY (SC_CHANGED_NOTIFICATION);
}
- (ushort) getGTF
{
return GTF;
}
- (void) setGTF:(ushort)gtf
{
NSAssert1 ((gtf & ~01) == 0, @"Bad GTF: 0%o", gtf);
GTF = gtf;
NOTIFY (GTF_CHANGED_NOTIFICATION);
}
- (ushort) getMQ
{
return MQ;
}
- (void) setMQ:(ushort)mq
{
NSAssert1 ((mq & ~07777) == 0, @"Bad MQ: 0%o", mq);
MQ = mq;
NOTIFY (MQ_CHANGED_NOTIFICATION);
}
- (char) getEAEmode
{
return eaeMode;
}
- (void) setEAEmode:(char)mode
{
NSAssert1 (mode == 'A' || mode == 'B', @"Bad EAE mode: %c", mode);
eaeMode = mode;
NOTIFY (EAE_MODE_CHANGED_NOTIFICATION);
}
- (ushort) getEnable
{
return IENABLE;
}
- (void) setEnable:(ushort)enable
{
NSAssert1 ((enable & ~01) == 0, @"Bad Interrupt Enable Flag: 0%o", enable);
IENABLE = enable;
NOTIFY (ENABLE_CHANGED_NOTIFICATION);
}
- (ushort) getDelay
{
return IDELAY;
}
- (void) setDelay:(ushort)delay
{
NSAssert1 ((delay & ~01) == 0, @"Bad Interrupt Delay Flag: 0%o", delay);
IDELAY = delay;
NOTIFY (DELAY_CHANGED_NOTIFICATION);
}
- (ushort) getInhibit
{
return IINHIBIT;
}
- (void) setInhibit:(ushort)inhibit
{
NSAssert1 ((inhibit & ~01) == 0, @"Bad Interrupt Inhibit Flag: 0%o", inhibit);
IINHIBIT = inhibit;
NOTIFY (INHIBIT_CHANGED_NOTIFICATION);
}
- (ulong) getInterruptMaskBits:(ulong)bitmask
{
return IMASK & bitmask;
}
- (void) setInterruptMaskBits:(ulong)bitmask
{
IMASK |= bitmask;
NOTIFY (IOFLAGS_CHANGED_NOTIFICATION);
}
- (void) clearInterruptMaskBits:(ulong)bitmask
{
IMASK &= ~bitmask;
NOTIFY (IOFLAGS_CHANGED_NOTIFICATION);
}
- (ulong) getIOFlagBits:(ulong)bitmask
{
return IOFLAGS & bitmask;
}
- (void) setIOFlagBits:(ulong)bitmask
{
IOFLAGS |= bitmask;
NOTIFY (IOFLAGS_CHANGED_NOTIFICATION);
}
- (void) clearIOFlagBits:(ulong)bitmask
{
IOFLAGS &= ~bitmask;
NOTIFY (IOFLAGS_CHANGED_NOTIFICATION);
}
- (BOOL) interruptRequest
{
return (IOFLAGS & IMASK) ? YES : NO;
}
#pragma mark Memory Access
- (ushort) memoryAt:(int)address
{
NSAssert1 ((address & ~077777) == 0, @"Bad address: 0%o", address);
NSAssert2 ((mem[address] & ~07777) == 0,
@"Bad memory content: pdp8.mem[%5.5o] = 0%o", address, mem[address]);
return mem[address];
}
- (ushort) memoryAtNext:(int)address
{
NSAssert1 ((address & ~077777) == 0, @"Bad address: 0%o", address);
NSAssert2 ((mem[(address & 070000) | ((address + 1) & 07777)] & ~07777) == 0,
@"Bad memory content: pdp8.mem[%5.5o] = 0%o",
(address & 070000) | ((address + 1) & 07777),
mem[(address & 070000) | ((address + 1) & 07777)]);
return mem[(address & 070000) | ((address + 1) & 07777)];
}
- (ushort *) directMemoryAccess
{
return mem;
}
- (void) notifyMemoryChanged
{
NSAssertRunningOnMainThread ();
NSAssert (_state.running != GOING, @"PDP-8 is running");
[[NSNotificationCenter defaultCenter] postNotificationName:(MEMORY_CHANGED_NOTIFICATION) object:self];
}
- (void) directMemoryWriteFinished
{
if (_state.running != GOING)
[self performSelectorOnMainThread:@selector(notifyMemoryChanged)
withObject:nil waitUntilDone:YES];
}
- (void) setMemoryAtAddress:(int)address toValue:(int)value
{
NSAssert1 ((address & ~077777) == 0, @"Bad address: 0%o", address);
NSAssert1 (address < _hw.memsize, @"Address out of available memory: 0%o", address);
NSAssert2 ((value & ~07777) == 0, @"Bad value 0%o for pdp8.mem[%5.5o]", value, address);
mem[address] = value;
NOTIFY (MEMORY_CHANGED_NOTIFICATION);
}
- (void) setMemoryAtNextAddress:(int)address toValue:(int)value
{
NSAssert1 ((address & ~077777) == 0, @"Bad address: 0%o", address);
NSAssert2 ((value & ~07777) == 0, @"Bad value 0%o for pdp8.mem[%5.5o]",
value, (address & 070000) | ((address + 1) & 07777));
NSAssert1 (address < _hw.memsize, @"Address out of available memory: 0%o", address);
mem[(address & 070000) | ((address + 1) & 07777)] = value;
NOTIFY (MEMORY_CHANGED_NOTIFICATION);
}
- (void) setMemoryAtAddress:(int)address toValues:(NSArray *)values withMask:(BOOL)withMask
{
int i;
int count = [values count];
NSAssert (values, @"values is nil");
NSAssert1 ((address & ~077777) == 0, @"Bad start address: 0%o", address);
NSAssert1 (((address + count - 1) & ~077777) == 0, @"Bad end address: 0%o", address + count - 1);
NSAssert1 ((address + count - 1) < _hw.memsize,
@"End address out of available memory: 0%o", address);
for (i = 0; i < count; i++) {
int value = [[values objectAtIndex:i] intValue];
NSAssert2 ((value & ~(withMask ? 077777777 : 07777)) == 0,
@"Bad mask/value 0%o for pdp8.mem[%5.5o]", value, address + i);
int mask = withMask ? (value >> 12) : 07777;
mem[address + i] = (mem[address + i] & ~mask) | (value & mask);
}
NOTIFY (MEMORY_CHANGED_NOTIFICATION);
}
- (void) clearMemory
{
bzero (mem, sizeof(mem));
NOTIFY (MEMORY_CHANGED_NOTIFICATION);
}
- (ushort) getCurrentOpcode
{
return _state.currInst;
}
- (PDP8InstructionFunctionPointer) getNextInstruction
{
NSAssert (_state.running != GOING, @"PDP-8 is running");
_state.currInst = mem[IF | PC]; // the skiptest function may use the PLUGIN_POINTER() macro
return itab[UF | mem[IF | PC]];
}
#pragma mark TSC8-75
- (ushort) getTSC8ertb
{
return _tsc8.ertb;
}
- (void) setTSC8ertb:(ushort)ertb
{
NSAssert1 ((ertb & ~07777) == 0, @"Bad ERTB: %o", ertb);
_tsc8.ertb = ertb;
}
- (ushort) getTSC8eriot
{
return _tsc8.eriot;
}
- (void) setTSC8eriot:(ushort)eriot
{
NSAssert1 ((eriot & ~07777) == 0, @"Bad ERIOT: %o", eriot);
_tsc8.eriot = eriot;
NOTIFY (IOFLAGS_CHANGED_NOTIFICATION); // ESME might change its skip behaviour on new ERIOT
}
- (ushort) getTSC8ecdf
{
return _tsc8.ecdf;
}
- (void) setTSC8ecdf:(ushort)ecdf
{
NSAssert1 ((ecdf & ~01) == 0, @"Bad ECDF: %o", ecdf);
_tsc8.ecdf = ecdf;
NOTIFY (IOFLAGS_CHANGED_NOTIFICATION); // ESME might change its skip behaviour on new ECDF
}
- (ulong) getTSC8flag
{
return _tsc8.flag;
}
- (void) setTSC8flag:(ulong)flag
{
_tsc8.flag = flag;
}
- (BOOL) getTSC8esmeEnabled
{
return _tsc8.esmeEnabled;
}
- (void) setTSC8esmeEnabled:(BOOL)enabled
{
_tsc8.esmeEnabled = enabled;
NOTIFY (IOFLAGS_CHANGED_NOTIFICATION); // ESME might change its skip behaviour when enabled
}
@end