mirror of
https://github.com/DerekK19/PDP-8-E-Simulator.git
synced 2024-12-21 10:29:20 +00:00
383 lines
11 KiB
Mathematica
383 lines
11 KiB
Mathematica
|
/*
|
||
|
* PDP-8/E Simulator
|
||
|
*
|
||
|
* Copyright © 1994-2015 Bernhard Baehr
|
||
|
*
|
||
|
* PC8E.m - PC8-E Paper Tape Reader and Punch 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 <mach/mach_time.h>
|
||
|
|
||
|
#import "PluginFramework/PluginAPI.h"
|
||
|
#import "PluginFramework/PDP8.h"
|
||
|
#import "PluginFramework/Utilities.h"
|
||
|
#import "PluginFramework/RegisterFormCell.h"
|
||
|
#import "PluginFramework/KeepInMenuWindow.h"
|
||
|
#import "FileDropControlTargetProtocol.h"
|
||
|
#import "PluginFramework/PaperTapeController.h"
|
||
|
#import "PluginFramework/NSThread+MainThread.h"
|
||
|
|
||
|
#define USE_PC8E_REGISTERS_DIRECTLY 1
|
||
|
|
||
|
#import "PC8E.h"
|
||
|
#import "PC8Eiot.h"
|
||
|
|
||
|
|
||
|
#define CODER_KEY_RBF @"rbf"
|
||
|
#define CODER_KEY_PBF @"pbf"
|
||
|
#define CODER_KEY_INFLAG @"inflag"
|
||
|
#define CODER_KEY_INMASK @"inmask"
|
||
|
#define CODER_KEY_OUTFLAG @"outflag"
|
||
|
#define CODER_KEY_OUTMASK @"outmask"
|
||
|
|
||
|
#define NO_OUTPUT 0
|
||
|
#define OUTPUT 1
|
||
|
#define NO_INPUT 0
|
||
|
#define INPUT 1
|
||
|
|
||
|
#define READER_CONTINUOUS_DELAY 3333333ull // nanosec. * 300 = 1 sec. => delay for 300 char/sec.
|
||
|
#define READER_STARTSTOP_DELAY 40000000ull // nanosec. * 25 = 1 sec. => delay for 25 char/sec.
|
||
|
#define PUNCH_DELAY 20000000ull // nanosec. * 50 = 1 sec. => delay for 50 char/sec.
|
||
|
#define STOP_DELAY 6000000ull // "stop delay" occurs 6 ms after RFC
|
||
|
|
||
|
|
||
|
@implementation PC8E
|
||
|
|
||
|
|
||
|
API_VERSION
|
||
|
|
||
|
|
||
|
#pragma mark Plugin Methods
|
||
|
|
||
|
|
||
|
- (NSArray *) iotsForAddress:(int)ioAddress
|
||
|
{
|
||
|
return ioAddress == 01 ?
|
||
|
[NSArray arrayWithObjects:
|
||
|
[NSValue valueWithPointer:i6010],
|
||
|
[NSValue valueWithPointer:i6011],
|
||
|
[NSValue valueWithPointer:i6012],
|
||
|
[NSValue valueWithPointer:nil],
|
||
|
[NSValue valueWithPointer:i6014],
|
||
|
[NSValue valueWithPointer:nil],
|
||
|
[NSValue valueWithPointer:i6016],
|
||
|
[NSValue valueWithPointer:nil],
|
||
|
nil] :
|
||
|
[NSArray arrayWithObjects:
|
||
|
[NSValue valueWithPointer:i6020],
|
||
|
[NSValue valueWithPointer:i6021],
|
||
|
[NSValue valueWithPointer:i6022],
|
||
|
[NSValue valueWithPointer:nil],
|
||
|
[NSValue valueWithPointer:i6024],
|
||
|
[NSValue valueWithPointer:nil],
|
||
|
[NSValue valueWithPointer:i6026],
|
||
|
[NSValue valueWithPointer:nil],
|
||
|
nil];
|
||
|
}
|
||
|
|
||
|
|
||
|
- (NSArray *) skiptestsForAddress:(int)ioAddress
|
||
|
{
|
||
|
return ioAddress == 01 ?
|
||
|
[NSArray arrayWithObjects:
|
||
|
[NSValue valueWithPointer:nil],
|
||
|
[NSValue valueWithPointer:s6011],
|
||
|
[NSValue valueWithPointer:nil],
|
||
|
[NSValue valueWithPointer:nil],
|
||
|
[NSValue valueWithPointer:nil],
|
||
|
[NSValue valueWithPointer:nil],
|
||
|
[NSValue valueWithPointer:nil],
|
||
|
[NSValue valueWithPointer:nil],
|
||
|
nil] :
|
||
|
[NSArray arrayWithObjects:
|
||
|
[NSValue valueWithPointer:nil],
|
||
|
[NSValue valueWithPointer:s6021],
|
||
|
[NSValue valueWithPointer:nil],
|
||
|
[NSValue valueWithPointer:nil],
|
||
|
[NSValue valueWithPointer:nil],
|
||
|
[NSValue valueWithPointer:nil],
|
||
|
[NSValue valueWithPointer:nil],
|
||
|
[NSValue valueWithPointer:nil],
|
||
|
nil];
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) setIOFlag:(unsigned long)flag forIOFlagName:(NSString *)name;
|
||
|
{
|
||
|
if (inflag)
|
||
|
outflag = flag;
|
||
|
else
|
||
|
inflag = flag;
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) CAF:(int)ioAddress
|
||
|
{
|
||
|
if (ioAddress == 01) { // CAF is called twice, ignore second call (01 == reader I/O address)
|
||
|
RBF = 0;
|
||
|
PBF = 0;
|
||
|
[pdp8 setInterruptMaskBits:inflag | outflag];
|
||
|
[pdp8 clearIOFlagBits:inflag | outflag];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) clearAllFlags:(int)ioAddress
|
||
|
{
|
||
|
if (ioAddress == 01) // CAF is called twice, ignore second call (01 == reader I/O address)
|
||
|
[self resetDevice];
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) resetDevice
|
||
|
{
|
||
|
[self setRBF:0];
|
||
|
[self setPBF:0];
|
||
|
[pdp8 setInterruptMaskBits:inflag | outflag];
|
||
|
[pdp8 clearIOFlagBits:inflag | outflag];
|
||
|
}
|
||
|
|
||
|
|
||
|
#pragma mark Thread Handling
|
||
|
|
||
|
|
||
|
- (void) realtimeDelay:(uint64_t)matStartTime lastStart:(uint64_t)matLastStartTime
|
||
|
{
|
||
|
int speed = [pdp8 getGoSpeed];
|
||
|
if (speed != GO_AS_FAST_AS_POSSIBLE) {
|
||
|
uint64_t delay;
|
||
|
if (matLastStartTime == 0) // punch with 50 cps
|
||
|
delay = PUNCH_DELAY;
|
||
|
else { // reader
|
||
|
uint64_t delta = absolute2nanoseconds(matStartTime - matLastStartTime);
|
||
|
if (delta > READER_STARTSTOP_DELAY) // next char already buffered
|
||
|
delay = 0;
|
||
|
else
|
||
|
if (delta > STOP_DELAY) // start stop mode with 25 cps
|
||
|
delay = READER_STARTSTOP_DELAY;
|
||
|
else // continuous read with 300 cps
|
||
|
delay = READER_CONTINUOUS_DELAY;
|
||
|
}
|
||
|
if (speed == GO_WITH_PDP8_SPEED)
|
||
|
mach_wait_until (matStartTime + nanoseconds2absolute(delay));
|
||
|
else {
|
||
|
while (mach_absolute_time() < matStartTime + nanoseconds2absolute(delay))
|
||
|
;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) canContinueInput
|
||
|
{
|
||
|
if ([inputLock tryLockWhenCondition:NO_INPUT])
|
||
|
[inputLock unlockWithCondition:INPUT];
|
||
|
#if ! defined(NS_BLOCK_ASSERTIONS)
|
||
|
else
|
||
|
NSLog (@"PDP-8 software bug: RFC or RCC executed before preceding tape read finished");
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) canContinueOutput
|
||
|
{
|
||
|
if ([outputLock tryLockWhenCondition:NO_OUTPUT])
|
||
|
[outputLock unlockWithCondition:OUTPUT];
|
||
|
#if ! defined(NS_BLOCK_ASSERTIONS)
|
||
|
else
|
||
|
NSLog (@"PDP-8 software bug: PPC or PLS executed before preceding tape punch finished");
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) pc8eReaderThread:(id)object
|
||
|
{
|
||
|
[[NSAutoreleasePool alloc] init];
|
||
|
uint64_t matStartTime = mach_absolute_time();
|
||
|
uint64_t matLastStartTime;
|
||
|
for (;;) {
|
||
|
[inputLock lockWhenCondition:INPUT];
|
||
|
matLastStartTime = matStartTime;
|
||
|
matStartTime = mach_absolute_time();
|
||
|
input = [reader getChar];
|
||
|
if (input != EOF)
|
||
|
[self setRBF:input & 0377]; // strip off Unicode characters etc.
|
||
|
[inputLock unlockWithCondition:NO_INPUT];
|
||
|
[self realtimeDelay:matStartTime lastStart:matLastStartTime];
|
||
|
if (input != EOF)
|
||
|
[pdp8 setIOFlagBits:inflag];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) pc8ePunchThread:(id)object
|
||
|
{
|
||
|
[[NSAutoreleasePool alloc] init];
|
||
|
for (;;) {
|
||
|
[outputLock lockWhenCondition:OUTPUT];
|
||
|
uint64_t matStart = mach_absolute_time();
|
||
|
[self setPBF:output];
|
||
|
BOOL done = [punch putChar:output handleBackspace:NO];
|
||
|
[outputLock unlockWithCondition:NO_OUTPUT];
|
||
|
[self realtimeDelay:matStart lastStart:0];
|
||
|
if (done)
|
||
|
[pdp8 setIOFlagBits:outflag];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
#pragma mark Register Access
|
||
|
|
||
|
|
||
|
- (unsigned short) getRBF
|
||
|
{
|
||
|
return RBF;
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) notifyRBF
|
||
|
{
|
||
|
NSAssertRunningOnMainThread ();
|
||
|
[[NSNotificationCenter defaultCenter] postNotificationName:RBF_CHANGED_NOTIFICATION object:self];
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) setRBF:(unsigned short)rbf
|
||
|
{
|
||
|
NSAssert1 ((rbf & ~0377) == 0, @"Bad RBF: 0%o", rbf);
|
||
|
RBF = rbf;
|
||
|
if ([NSThread isMainThread])
|
||
|
[[NSNotificationCenter defaultCenter]
|
||
|
postNotificationName:RBF_CHANGED_NOTIFICATION object:self];
|
||
|
else
|
||
|
[self performSelectorOnMainThread:@selector(notifyRBF) withObject:self waitUntilDone:NO];
|
||
|
}
|
||
|
|
||
|
|
||
|
- (unsigned short) getPBF
|
||
|
{
|
||
|
return PBF;
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) notifyPBF
|
||
|
{
|
||
|
NSAssertRunningOnMainThread ();
|
||
|
[[NSNotificationCenter defaultCenter] postNotificationName:PBF_CHANGED_NOTIFICATION object:self];
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) setPBF:(unsigned short)pbf
|
||
|
{
|
||
|
NSAssert1 ((pbf & ~0377) == 0, @"Bad PBF: 0%o", pbf);
|
||
|
PBF = pbf;
|
||
|
if ([NSThread isMainThread])
|
||
|
[[NSNotificationCenter defaultCenter]
|
||
|
postNotificationName:PBF_CHANGED_NOTIFICATION object:self];
|
||
|
else
|
||
|
[self performSelectorOnMainThread:@selector(notifyPBF) withObject:self waitUntilDone:NO];
|
||
|
}
|
||
|
|
||
|
|
||
|
#pragma mark Notifications
|
||
|
|
||
|
|
||
|
- (void) notifyApplicationWillTerminate:(NSNotification *)notification
|
||
|
{
|
||
|
// NSLog (@"PC8E notifyApplicationWillTerminate");
|
||
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSUserDefaultsDidChangeNotification
|
||
|
object:nil];
|
||
|
NSMutableData *data = [NSMutableData data];
|
||
|
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
|
||
|
[self encodeWithCoder:archiver];
|
||
|
[archiver finishEncoding];
|
||
|
[archiver release];
|
||
|
[[NSUserDefaults standardUserDefaults] setObject:data forKey:[self pluginName]];
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) notifyPluginsLoaded:(NSNotification *)notification
|
||
|
{
|
||
|
[window orderBackFromDefaults:self];
|
||
|
[window makeFirstResponder:window];
|
||
|
}
|
||
|
|
||
|
|
||
|
#pragma mark Initialization
|
||
|
|
||
|
|
||
|
- (id) initWithCoder:(NSCoder *)coder
|
||
|
{
|
||
|
self = [super init];
|
||
|
[self setRBF:[coder decodeIntForKey:CODER_KEY_RBF]];
|
||
|
[self setPBF:[coder decodeIntForKey:CODER_KEY_PBF]];
|
||
|
[coder decodeBoolForKey:CODER_KEY_INFLAG] ?
|
||
|
[pdp8 setIOFlagBits:inflag] : [pdp8 clearIOFlagBits:inflag];
|
||
|
[coder decodeBoolForKey:CODER_KEY_INMASK] ?
|
||
|
[pdp8 setInterruptMaskBits:inflag] : [pdp8 clearInterruptMaskBits:inflag];
|
||
|
[coder decodeBoolForKey:CODER_KEY_OUTFLAG] ?
|
||
|
[pdp8 setIOFlagBits:outflag] : [pdp8 clearIOFlagBits:outflag];
|
||
|
[coder decodeBoolForKey:CODER_KEY_OUTMASK] ?
|
||
|
[pdp8 setInterruptMaskBits:outflag] : [pdp8 clearInterruptMaskBits:outflag];
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) encodeWithCoder:(NSCoder *)coder
|
||
|
{
|
||
|
[coder encodeInt:[self getRBF] forKey:CODER_KEY_RBF];
|
||
|
[coder encodeInt:[self getPBF] forKey:CODER_KEY_PBF];
|
||
|
[coder encodeBool:[pdp8 getIOFlagBits:inflag] ? YES : NO forKey:CODER_KEY_INFLAG];
|
||
|
[coder encodeBool:[pdp8 getInterruptMaskBits:inflag] ? YES : NO forKey:CODER_KEY_INMASK];
|
||
|
[coder encodeBool:[pdp8 getIOFlagBits:outflag] ? YES : NO forKey:CODER_KEY_OUTFLAG];
|
||
|
[coder encodeBool:[pdp8 getInterruptMaskBits:outflag] ? YES : NO forKey:CODER_KEY_OUTMASK];
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) pluginDidLoad
|
||
|
{
|
||
|
[rbfCell setupRegisterFor:self getRegisterValue:@selector(getRBF) setRegisterValue:@selector(setRBF:)
|
||
|
changedNotificationName:RBF_CHANGED_NOTIFICATION mask:0377 base:8];
|
||
|
[pbfCell setupRegisterFor:self getRegisterValue:@selector(getPBF) setRegisterValue:@selector(setPBF:)
|
||
|
changedNotificationName:PBF_CHANGED_NOTIFICATION mask:0377 base:8];
|
||
|
NSData *data = [[NSUserDefaults standardUserDefaults] dataForKey:[self pluginName]];
|
||
|
if (data) {
|
||
|
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
|
||
|
self = [self initWithCoder:unarchiver];
|
||
|
[unarchiver finishDecoding];
|
||
|
[unarchiver release];
|
||
|
}
|
||
|
inputLock = [[NSConditionLock alloc] initWithCondition:NO_INPUT];
|
||
|
outputLock = [[NSConditionLock alloc] initWithCondition:NO_OUTPUT];
|
||
|
[NSThread detachNewThreadSelector:@selector(pc8eReaderThread:) toTarget:self withObject:nil];
|
||
|
[NSThread detachNewThreadSelector:@selector(pc8ePunchThread:) toTarget:self withObject:nil];
|
||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||
|
selector:@selector(notifyApplicationWillTerminate:)
|
||
|
name:NSApplicationWillTerminateNotification object:nil];
|
||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||
|
selector:@selector(notifyPluginsLoaded:)
|
||
|
name:PLUGINS_LOADED_NOTIFICATION object:nil];
|
||
|
}
|
||
|
|
||
|
|
||
|
@end
|