PDP-11/20 test machine can load BASIC from paper tape, but still has some trouble running it

This commit is contained in:
Jeff Parsons 2016-10-18 15:44:26 -07:00 committed by Jeff Parsons
commit 16b8b59f12
14 changed files with 383 additions and 274 deletions

View file

@ -52,7 +52,7 @@ if (NODE) {
*
* For example, opADD() passes the helper function fnADD() to the appropriate update method. This
* allows the update method to perform the entire read/modify/write operation, because the modify
* step is performed internally via the fnXXX() helper function.
* step is performed internally, via the fnXXX() helper function.
*/
/**
@ -1117,7 +1117,24 @@ PDP11.opHALT = function(opCode)
this.regErr |= PDP11.CPUERR.BADHALT;
this.trap(PDP11.TRAP.BUS_ERROR, PDP11.REASON.HALT);
} else {
this.stopCPU();
if (!this.dbg) {
/*
* This will leave the PC exactly where it's supposed to be: at the address of the HALT + 2.
*/
this.stopCPU();
} else {
/*
* When the Debugger is present, this call will rewind PC by 2 so that the HALT instruction is
* displayed, making it clear why the processor stopped; the user could also use the "dh" command
* to dump the Debugger's instruction history buffer to see why it stopped, assuming the history
* buffer is enabled, but that's more work.
*
* Because rewinding is not normal CPU behavior, attempting to Run again (or use the Debugger's
* "g" command) will cause an immediate HALT again; the work-around is simple: either set the PC to
* a new address (eg, "r pc=pc+2") or single-step the HALT instruction ("t").
*/
this.dbg.stopInstruction();
}
}
this.nStepCycles -= 7;
};

View file

@ -997,10 +997,16 @@ if (DEBUGGER) {
var sComment = "history";
var nSequence = nPrev--;
/*
* TODO: Need to some UI to control whether cycle counts are displayed as part of the history.
* It's currently disabled in checkInstruction(), so it's disable here, too.
*
if (DEBUG && dbgAddr.cycleCount != null) {
sComment = "cycles";
nSequence = dbgAddr.cycleCount;
}
*/
var sInstruction = this.getInstruction(dbgAddrNew, sComment, nSequence);
@ -1623,7 +1629,7 @@ if (DEBUGGER) {
if (opCode != null) {
var dbgAddr = this.aOpcodeHistory[this.iOpcodeHistory];
this.setAddr(dbgAddr, cpu.getPC());
if (DEBUG) dbgAddr.cycleCount = cpu.getCycles();
// if (DEBUG) dbgAddr.cycleCount = cpu.getCycles();
if (++this.iOpcodeHistory == this.aOpcodeHistory.length) this.iOpcodeHistory = 0;
}
}

View file

@ -425,8 +425,10 @@ var PDP11 = {
LKS: 0o177546, // KW11-L Clock Status
PRS: 0o177550, // PC11/PR11 Reader Status Register
PRB: 0o177552, // PC11/PR11 Reader Buffer Register
PRS: 0o177550, // PC11 (and PR11) Reader Status Register
PRB: 0o177552, // PC11 (and PR11) Reader Buffer Register
PPS: 0o177554, // PC11 Punch Status Register
PPB: 0o177556, // PC11 Punch Buffer Register
RCSR: 0o177560, // Display Terminal: Receiver Status Register
RBUF: 0o177562, // Display Terminal: Receiver Data Buffer Register
@ -511,6 +513,9 @@ var PDP11 = {
PSW: 0o177776 // 777776 17777776 0x3FFFFE Processor Status Word
},
PC11: { // High Speed Reader & Punch (PR11 is a Reader-only unit)
PRI: 4, // NOTE: reader has precedence over punch
RVEC: 0o70, // reader vector
PVEC: 0o74, // punch vector
PRS: {
RE: 0x0001, // Reader Enable (W/O)
RIE: 0x0040, // Reader Interrupt Enable (allows the DONE and ERROR bits to trigger an interrupt)
@ -518,6 +523,7 @@ var PDP11 = {
BUSY: 0x0800, // Busy (R/O)
ERROR: 0x8000, // Error (R/O)
CLEAR: 0x08C0, // bits cleared on INIT
RMASK: 0xFFFE, // bits readable (TODO: All I know for sure is that bit 0 is NOT readable; see PC11.readPRS())
WMASK: 0x0041 // bits writable
},
PRB: {

View file

@ -206,6 +206,8 @@ PC11.prototype.initBus = function(cmp, bus, cpu, dbg)
this.cpu = cpu;
this.dbg = dbg;
var pc11 = this;
this.configMount = this.cmp.getMachineParm('autoMount') || this.configMount;
if (this.configMount) {
@ -223,6 +225,12 @@ PC11.prototype.initBus = function(cmp, bus, cpu, dbg)
}
}
this.triggerReaderInterrupt = this.cpu.addTrigger(PDP11.PC11.RVEC, PDP11.PC11.PRI);
this.timerReaderAdvance = this.cpu.addTimer(function readyReader() {
pc11.advanceReader();
});
bus.addIOTable(this, PC11.UNIBUS_IOTABLE);
this.addTape("None", PC11.LOADSTATE.NONE, true);
@ -623,16 +631,57 @@ PC11.prototype.restore = function(data)
return true;
};
/**
* advanceReader()
*
* If the reader is enabled (RE is set) and there is no exceptional condition (ie, ERROR is set),
* and if the buffer register is empty (DONE is clear), then if we have more data in our internal buffer,
* store it in the buffer register, and optionally trigger an interrupt if device interrupts are enabled.
*
* @this {PC11}
*/
PC11.prototype.advanceReader = function()
{
if ((this.prs & (PDP11.PC11.PRS.RE | PDP11.PC11.PRS.ERROR)) == PDP11.PC11.PRS.RE) {
if (!(this.prs & PDP11.PC11.PRS.DONE)) {
if (this.iTapeData < this.aTapeData.length) {
this.prb = this.aTapeData[this.iTapeData++];
this.prs |= PDP11.PC11.PRS.DONE;
this.prs &= ~PDP11.PC11.PRS.BUSY;
if (this.prs & PDP11.PC11.PRS.RIE) {
this.cpu.setTrigger(this.triggerReaderInterrupt);
}
/*
* The PC11, by virtue of its "high speed", is supposed to deliver characters at 300 CPS,
* so for now, that's what we're going to deliver (ie, 1000ms / 300).
*
* TODO: Review this code. If we don't set the fReset parameter to true, the timer will eventually
* fire while the "Absolute Loader" tape is still reading bytes from, say, the "BASIC (Single User)"
* tape, causing an EXTRA advance to occur and causing a byte to be skipped. Passing true ensures
* that the timer cannot fire for AT LEAST 3ms after each advance. But again, I need to understand
* the reader's actual behavior.
*/
this.cpu.setTimer(this.timerReaderAdvance, 1000/300, true);
}
}
}
};
/**
* readPRS(addr)
*
* NOTE: We use the PRS RMASK to honor the "write-only" behavior of bit 0, the reader enable bit (RE), because
* DEC's tiny Bootstrap Loader (/apps/pdp11/boot/bootstrap/BOOTSTRAP-16KB.lst) repeatedly enables the reader using
* the INC instruction, which causes the PRS to be read, incremented, and written, so if bit 0 isn't always read
* as zero, the INC instruction would clear RE instead of setting it.
*
* @this {PC11}
* @param {number} addr (eg, PDP11.UNIBUS.PRS or 177550)
* @return {number}
*/
PC11.prototype.readPRS = function(addr)
{
return this.prs;
return this.prs & PDP11.PC11.PRS.RMASK; // RMASK honors the "write-only" nature of the RE bit by returning zero on reads
};
/**
@ -648,14 +697,16 @@ PC11.prototype.writePRS = function(data, addr)
if (this.prs & PDP11.PC11.PRS.ERROR) {
data &= ~PDP11.PC11.PRS.RE;
// if (this.prs & PDP11.PC11.PRS.RIE) {
// TODO: Generate an interrupt
// TODO: Generate an interrupt (error condition)
// }
} else {
this.prs &= ~PDP11.PC11.PRS.DONE;
this.prs |= PDP11.PC11.PRS.BUSY;
this.prb = 0;
}
}
this.prs = (this.prs & ~PDP11.PC11.PRS.WMASK) | (data & PDP11.PC11.PRS.WMASK);
this.advanceReader();
};
/**
@ -667,6 +718,12 @@ PC11.prototype.writePRS = function(data, addr)
*/
PC11.prototype.readPRB = function(addr)
{
/*
* I'm guessing that the DONE and BUSY bits always remain more-or-less inverses of each other. They definitely
* start out that way when writePRS() sets the reader enable (RE) bit, and so that's how we treat them elsewhere, too.
*/
this.prs &= ~PDP11.PC11.PRS.DONE;
this.prs |= PDP11.PC11.PRS.BUSY;
return this.prb;
};

View file

@ -475,10 +475,6 @@ SerialPortPDP11.prototype.receiveData = function(data)
return true; // for now, return true regardless, since we're buffering everything anyway
};
SerialPortPDP11.prototype.advanceRBUF = function()
{
};
/**
* transmitByte(b)
*