PDP-8-E-Simulator/KC8EA/KC8EA.m

556 lines
16 KiB
Mathematica
Raw Normal View History

/*
* PDP-8/E Simulator
*
* Copyright © 1994-2015 Bernhard Baehr
*
* KC8EA.m - KC8-EA Programmers Console for the PDP-8/E Simulator
*
* 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 "PluginFramework/PluginAPI.h"
#import "PluginFramework/PDP8.h"
#import "PluginFramework/KeepInMenuWindow.h"
#import "PluginFramework/Utilities.h"
#import "KC8EA.h"
#import "StateMachine.h"
#import "BackgroundView.h"
// notifications
#define KC8EA_UPDATE_DISPLAY_NOTIFICATION @"kc8eaUpdateDisplayNotification"
#define KC8EA_SW_CHANGED_NOTIFICATION @"kc8eaSWChangedNotification"
/* Notification posted when the SW switch changes; notification object is a NSNumber
with the new state of SW (0 or 1) */
// coder keys
#define CODER_KEY_POWER_KEY @"power"
#define CODER_KEY_DISPLAY_SELECTOR_KNOB @"knob"
#define CODER_KEY_SW @"sw"
#define CODER_KEY_HALT @"halt"
#define CODER_KEY_SINGSTEP @"singstep"
// tag values of the display selection knob
#define DISPLAY_STATE 0
#define DISPLAY_STATUS 1
#define DISPLAY_AC 2
#define DISPLAY_MD 3
#define DISPLAY_MQ 4
#define DISPLAY_BUS 5
// tag values of the power key
#define KEY_OFF 0
#define KEY_POWER 1
#define KEY_PANEL_LOCK 2
// states of the console switches
#define UP 1
#define DOWN 0
// states of the lights
#define OFF 0
#define ON 1
@implementation KC8EA
API_VERSION
#pragma mark GUI
- (void) updateDisplay
{
unsigned short val = 0;
if ([key tag] == KEY_POWER) {
switch ([knob tag]) {
case DISPLAY_STATE :
val = [stateMachine state:! [sw state]];
break;
case DISPLAY_STATUS :
val = [stateMachine status];
break;
case DISPLAY_AC :
val = [pdp8 getAC];
break;
case DISPLAY_MD :
val = [stateMachine md];
break;
case DISPLAY_MQ :
val = [pdp8 getMQ];
break;
case DISPLAY_BUS :
val = [stateMachine bus];
break;
default :
NSAssert (FALSE, @"Invalid display selection knob tag");
break;
}
}
if (displayValue != val) { // optimization
[display0 setState:val & 00001];
[display1 setState:val & 00002];
[display2 setState:val & 00004];
[display3 setState:val & 00010];
[display4 setState:val & 00020];
[display5 setState:val & 00040];
[display6 setState:val & 00100];
[display7 setState:val & 00200];
[display8 setState:val & 00400];
[display9 setState:val & 01000];
[display10 setState:val & 02000];
[display11 setState:val & 04000];
displayValue = val;
}
}
- (void) updateAddress
{
unsigned short addr = 0;
if ([key tag] == KEY_POWER)
addr = [stateMachine cpma];
if (addressValue != addr) { // optimization
[addr0 setState:addr & 000001];
[addr1 setState:addr & 000002];
[addr2 setState:addr & 000004];
[addr3 setState:addr & 000010];
[addr4 setState:addr & 000020];
[addr5 setState:addr & 000040];
[addr6 setState:addr & 000100];
[addr7 setState:addr & 000200];
[addr8 setState:addr & 000400];
[addr9 setState:addr & 001000];
[addr10 setState:addr & 002000];
[addr11 setState:addr & 004000];
[addr12 setState:addr & 010000];
[addr13 setState:addr & 020000];
[addr14 setState:addr & 040000];
addressValue = addr;
}
}
- (void) update
{
[self updateDisplay];
[self updateAddress];
}
- (IBAction) srClicked:(id)sender
{
[pdp8 setSR:[pdp8 getSR] ^ (04000 >> [sender tag])];
}
- (IBAction) swClicked:(id)sender
{
[self updateDisplay];
/* The SW switch is not used by the simulator, but plugins that want to know about the
corresponding OMNIBUS signal can listen for this notification */
[[NSNotificationCenter defaultCenter] postNotificationName:KC8EA_SW_CHANGED_NOTIFICATION
object:[NSNumber numberWithInt:[sender state] == UP]];
}
- (IBAction) addrloadClicked:(id)sender
{
if (powerKeyPosition != KEY_PANEL_LOCK) {
[stateMachine loadAddress];
[self update];
}
}
- (IBAction) extdaddrloadClicked:(id)sender
{
if (powerKeyPosition != KEY_PANEL_LOCK) {
[stateMachine loadExtendedAddress];
[self update];
}
}
- (IBAction) clearClicked:(id)sender
{
if (powerKeyPosition != KEY_PANEL_LOCK)
[pdp8 clearAllFlags];
}
- (IBAction) contClicked:(id)sender
{
if (powerKeyPosition != KEY_PANEL_LOCK && [pdp8 isStopped]) {
[run setState:ON];
[window display];
if ([singstep state] == DOWN) {
[stateMachine executeSingleCycle];
[self update];
[run setState:OFF];
} else if ([halt state] == DOWN) {
do {
[stateMachine executeSingleCycle];
[self update];
[window display];
} while (! [stateMachine isInFetchCycle]);
[run setState:OFF];
} else {
while (! [stateMachine isInFetchCycle])
[stateMachine executeSingleCycle];
[[NSApp delegate] performSelector:@selector(go:) withObject:self];
}
}
}
- (IBAction) examClicked:(id)sender
{
if (powerKeyPosition != KEY_PANEL_LOCK && [pdp8 isStopped]) {
[run setState:ON];
[window display];
[stateMachine examine];
[self update];
[run setState:OFF];
}
}
- (IBAction) haltClicked:(id)sender
{
if (powerKeyPosition != KEY_PANEL_LOCK) {
[pdp8 setHalt:[sender state] == DOWN || [singstep state] == DOWN];
[NSApp setWindowsNeedUpdate:YES]; // cause Step/Trace/Go CPU window toolbar items update
}
}
- (IBAction) stingstepClicked:(id)sender
{
if (powerKeyPosition != KEY_PANEL_LOCK) {
[pdp8 setHalt:[sender state] == DOWN || [halt state] == DOWN];
[NSApp setWindowsNeedUpdate:YES]; // cause Step/Trace/Go CPU window toolbar items update
}
}
- (IBAction) depClicked:(id)sender
{
if (powerKeyPosition != KEY_PANEL_LOCK && [pdp8 isStopped]) {
[run setState:ON];
[window display];
[stateMachine deposit];
[self update];
[run setState:OFF];
}
}
- (IBAction) knobClicked:(id)sender
{
[self updateDisplay];
}
- (IBAction) keyClicked:(id)sender
{
NSEventType eventType = [[NSApp currentEvent] type];
switch ([sender tag]) {
case KEY_OFF :
if (eventType == NSLeftMouseUp || eventType == NSKeyDown) {
NSAlert *alert = [[NSAlert alloc] init];
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
[alert setMessageText:NSLocalizedStringFromTableInBundle(
@"Do you really want to power off the\nPDP-8/E Simulator?", nil, bundle, @"")];
[alert addButtonWithTitle:
NSLocalizedStringFromTableInBundle(@"No", nil, bundle, @"")];
[alert addButtonWithTitle:
NSLocalizedStringFromTableInBundle(@"Yes", nil, bundle, @"")];
if ([alert runModal] == NSAlertFirstButtonReturn) {
[[sender cell] setTag:eventType == NSKeyDown ? KEY_POWER : powerKeyPosition];
[self update];
} else
[NSApp terminate:self];
[alert release];
}
break;
case KEY_POWER :
powerKeyPosition = KEY_POWER;
[self update];
[pdp8 setHalt:[halt state] == DOWN || [singstep state] == DOWN];
[NSApp setWindowsNeedUpdate:YES]; // cause Step/Trace/Go CPU window toolbar items update
break;
case KEY_PANEL_LOCK :
powerKeyPosition = KEY_PANEL_LOCK;
[self update];
[pdp8 setHalt:NO];
[NSApp setWindowsNeedUpdate:YES]; // cause Step/Trace/Go CPU window toolbar items update
break;
default :
NSAssert (FALSE, @"Invalid power key tag");
break;
}
}
#pragma mark Notifications
- (void) goTimerFireMethod:(NSTimer *)timer
{
[stateMachine updateState:NO];
[self update];
}
- (void) notifyGo:(NSNotification *)notification
{
// hardcoded refresh rate at 60 Hz
goTimer = [NSTimer scheduledTimerWithTimeInterval:1/60.0 target:self
selector:@selector(goTimerFireMethod:) userInfo:nil repeats:YES];
[run setState:ON];
}
- (void) notifyStep:(NSNotification *)notification
{
[stateMachine updateState:NO];
[self update];
}
- (void) notifyTrace:(NSNotification *)notification
{
[run setState:YES];
}
- (void) notifyStop:(NSNotification *)notification
{
[goTimer invalidate];
goTimer = nil;
[run setState:OFF];
[stateMachine updateState:YES];
[self update];
}
- (void) notifyPDP8Changed:(NSNotification *)notification
{
// NSLog (@"KC8EA notifyPDP8Changed");
[[NSNotificationQueue defaultQueue] enqueueNotification:
[NSNotification notificationWithName:KC8EA_UPDATE_DISPLAY_NOTIFICATION object:self]
postingStyle:NSPostASAP coalesceMask:NSNotificationCoalescingOnName forModes:nil];
}
- (void) notifyUpdateDisplay:(NSNotification *) notification
{
// NSLog (@"KC8EA notifyUpdateDisplay");
[stateMachine updateState:NO];
[self update];
}
- (void) notifySRChanged:(NSNotification *)notification
{
unsigned short sreg = [pdp8 getSR];
[sr0 setState:sreg & 04000 ? 1 : 0];
[sr1 setState:sreg & 02000 ? 1 : 0];
[sr2 setState:sreg & 01000 ? 1 : 0];
[sr3 setState:sreg & 00400 ? 1 : 0];
[sr4 setState:sreg & 00200 ? 1 : 0];
[sr5 setState:sreg & 00100 ? 1 : 0];
[sr6 setState:sreg & 00040 ? 1 : 0];
[sr7 setState:sreg & 00020 ? 1 : 0];
[sr8 setState:sreg & 00010 ? 1 : 0];
[sr9 setState:sreg & 00004 ? 1 : 0];
[sr10 setState:sreg & 00002 ? 1 : 0];
[sr11 setState:sreg & 00001 ? 1 : 0];
}
- (void) notifyClearAllFlags:(NSNotification *)notification
{
[clear highlight:UP];
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self
selector:@selector(unhighliteClearTimerFireMethod:) userInfo:nil repeats:NO];
}
- (void) unhighliteClearTimerFireMethod:(NSTimer *)timer
{
[clear highlight:DOWN];
}
#pragma mark Initialization
- (void) resetDevice
{
[[key cell] setTag:KEY_POWER];
[[knob cell] setTag:DISPLAY_AC];
[sw setState:UP];
[halt setState:UP];
[singstep setState:UP];
[stateMachine updateState:NO];
[self update];
}
- (id) init
{
self = [super init];
powerKeyPosition = KEY_POWER;
[[key cell] setTag:powerKeyPosition];
[[knob cell] setTag:DISPLAY_AC];
[sw setState:UP];
addressValue = displayValue = -1;
/* dummy assignments to suppress Analyzer warning "never used" for the GUI elements not
accessed by source code; for the sake of completeness, they are connected in Interface Builder */
addrload = nil;
extdaddrload = nil;
cont = nil;
dep = nil;
exam = nil;
return self;
}
- (id) initWithCoder:(NSCoder *)coder
{
self = [super init];
powerKeyPosition = [coder decodeIntForKey:CODER_KEY_POWER_KEY];
[[key cell] setTag:powerKeyPosition];
[[knob cell] setTag:[coder decodeIntForKey:CODER_KEY_DISPLAY_SELECTOR_KNOB]];
[sw setState:[coder decodeBoolForKey:CODER_KEY_SW]];
[halt setState:[coder decodeBoolForKey:CODER_KEY_HALT]];
[singstep setState:DOWN]; // to cause the HALT cell update shaddow to be called
[singstep setState:[coder decodeBoolForKey:CODER_KEY_SINGSTEP]];
[pdp8 setHalt:[key tag] == KEY_PANEL_LOCK ? NO : ([halt state] == DOWN || [singstep state] == DOWN)];
addressValue = displayValue = -1;
return self;
}
- (void) encodeWithCoder:(NSCoder *)coder
{
[coder encodeInt:powerKeyPosition forKey:CODER_KEY_POWER_KEY];
[coder encodeInt:[knob tag] forKey:CODER_KEY_DISPLAY_SELECTOR_KNOB];
[coder encodeBool:[sw state] forKey:CODER_KEY_SW];
[coder encodeBool:[halt state] forKey:CODER_KEY_HALT];
[coder encodeBool:[singstep state] forKey:CODER_KEY_SINGSTEP];
}
- (void) notifyApplicationWillTerminate:(NSNotification *)notification
{
// NSLog (@"KC8EA notifyApplicationWillTerminate");
NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[self encodeWithCoder:archiver];
[stateMachine encodeWithCoder:archiver];
[archiver finishEncoding];
[archiver release];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:[self pluginName]];
}
- (void) notifyPluginsLoaded:(NSNotification *)notification
{
[self notifySRChanged:nil];
[self swClicked:sw];
[self notifyUpdateDisplay:nil];
[window orderBackFromDefaults:self];
}
- (void) pluginDidLoad
{
NSData *data = [[NSUserDefaults standardUserDefaults] dataForKey:[self pluginName]];
if (data) {
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
self = [self initWithCoder:unarchiver];
stateMachine = [[StateMachine alloc] initWithCoder:unarchiver pdp8:pdp8];
[unarchiver finishDecoding];
[unarchiver release];
} else {
self = [self init];
stateMachine = [[StateMachine alloc] initWithPDP8:pdp8];
}
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
[defaultCenter addObserver:self selector:@selector(notifyApplicationWillTerminate:)
name:NSApplicationWillTerminateNotification object:nil];
[defaultCenter addObserver:self selector:@selector(notifyPluginsLoaded:)
name:PLUGINS_LOADED_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyGo:) name:PDP8_GO_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyStop:) name:PDP8_STOP_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyStep:) name:PDP8_STEP_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyTrace:) name:PDP8_TRACE_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifySRChanged:) name:SR_CHANGED_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyPDP8Changed:) name:MEMORY_CHANGED_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyPDP8Changed:) name:PROGRAM_COUNTER_CHANGED_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyPDP8Changed:) name:ACCUMULATOR_CHANGED_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyPDP8Changed:) name:SC_CHANGED_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyPDP8Changed:) name:GTF_CHANGED_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyPDP8Changed:) name:MQ_CHANGED_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyPDP8Changed:) name:EAE_MODE_CHANGED_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyPDP8Changed:) name:DF_CHANGED_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyPDP8Changed:) name:UF_CHANGED_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyPDP8Changed:) name:UB_CHANGED_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyPDP8Changed:) name:SF_CHANGED_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyPDP8Changed:) name:ENABLE_CHANGED_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyPDP8Changed:) name:DELAY_CHANGED_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyPDP8Changed:) name:INHIBIT_CHANGED_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyPDP8Changed:) name:IOFLAGS_CHANGED_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyUpdateDisplay:) name:KC8EA_UPDATE_DISPLAY_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifyClearAllFlags:) name:CLEAR_ALL_FLAGS_NOTIFICATION object:nil];
[(BackgroundView *) [window contentView] setBackgroundImage:@"background" ofType:@"png"];
}
@end