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

556 lines
16 KiB
Objective-C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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