Added rudimentary PDP-10 assembler capability to the built-in debugger

This commit is contained in:
Jeff Parsons 2017-02-27 18:06:08 -08:00 • committed by Jeff Parsons
commit 8863f1127c
14 changed files with 501 additions and 262 deletions

View file

@ -13,7 +13,7 @@ PCjs has archived selected software for the following machines:
* [IBM PC](pcx86/)
* [Challenger 1P](c1p/)
* [PDP-11](pdp11/)
* DEC [PDP-10](pdp10/) and [PDP-11](pdp11/)
PCjs also includes a small collection of disk images in the [Disk Libraries](/disks/):

12
apps/pdp10/README.md Normal file
View file

@ -0,0 +1,12 @@
---
layout: page
title: PDP-10 Software
permalink: /apps/pdp10/
---
PDP-10 Software
---------------
PCjs provides the following PDP-10 tests:
- [PDP-10 Opcode Tests](tests/)

122
apps/pdp10/tests/README.md Normal file
View file

@ -0,0 +1,122 @@
---
layout: page
title: DEC PDP-10 Opcode Tests
permalink: /apps/pdp10/tests/
machines:
- id: testka10
type: pdp10
debugger: true
config: /devices/pdp10/machine/ka10/test/debugger/machine.xml
---
DEC PDP-10 Opcode Tests
-----------------------
The PDP-10 machine below loads the following test:
- [PDP-10 Half-Word Opcode Test](optest-v01.json)
Details on how to run and modify the test, and perform similar tests in [SIMH](https://github.com/simh/simh),
are provided below.
{% include machine.html id="testka10" %}
The [PDP-10 Half-Word Opcode Test](optest-v01.json) file specifies that the following words:
0o505040111111, // HRLI 1,111111
0o541040444444, // HRRI 1,444444
0o544100000001, // HLR 2,1
0o504100000001, // HRL 2,1
should be loaded at address 0o100, and that the program counter (PC register) should be set to 0o100. The embedded
comments display the instructions that correspond to each word.
If you want to alter or add new instructions, the PDP-10 Debugger now includes a rudimentary assembler. Using the `a` command,
along with an optional start address, you can enter "assembler mode". For example, here's how the first four instructions from
the [PDP-10 Half-Word Opcode Test](optest-v01.json) could be entered by hand:
>> a 100
begin assemble at 000100
>> hrli 1,111111
000100: 505040 111111 HRLI 1,111111
>> hrri 1,444444
000101: 541040 444444 HRRI 1,444444
>> hlr 2,1
000102: 544100 000001 HLR 2,1
>> hrl 2,1
000103: 504100 000001 HRL 2,1
ended assemble at 000104
Entering a blank line exits "assembler mode". To disassemble the four instructions, use `u 100 l4`:
>> u 100 l4
000100: 505040 111111 HRLI 1,111111
000101: 541040 444444 HRRI 1,444444
000102: 544100 000001 HLR 2,1
000103: 504100 000001 HRL 2,1
You can also copy-and-paste a series of instructions into the PDPjs Debugger, as long as each command ends
with a semicolon:
a 100;
hrli 1,111111;
hrri 1,444444;
hlr 2,1;
hrl 2,1;
end
Individual instructions can also be assembled *without* entering "assembler mode", as long as you precede
each instruction with the `a` command and a specific address:
a 100 hrli 1,111111;
a 101 hrri 1,444444;
a 102 hlr 2,1;
a 103 hrl 2,1
Individual instructions can be executed with the `t` command and memory can be dumped with the `d` command.
After executing the first four instructions and using the `d 0 l4` command to dump first four words of memory
(also known as AC registers 0-3), you should see the following output:
>> r
000100: 505040 111111 HRLI 1,111111
>> t
000101: 541040 444444 HRRI 1,444444
>> t
000102: 544100 000001 HLR 2,1
>> t
000103: 504100 000001 HRL 2,1
>> t
000104: 000000 000000 UUO 0,0
>> d 0 l4
000000: 000000 000000 111111 444444 444444 111111 000000 000000
To assemble and test the same instructions in [SIMH](https://github.com/simh/simh), using the `pdp10` binary:
sim> dep 100 505040111111
sim> dep 101 541040444444
sim> dep 102 544100000001
sim> dep 103 504100000001
sim> ex -m 100-103
100: HRLI 1,111111
101: HRRI 1,444444
102: HLR 2,1
103: HRL 2,1
sim> dep pc 100
sim> step
Step expired, PC: 000101 (HRRI 1,444444)
sim> step
Step expired, PC: 000102 (HLR 2,1)
sim> step
Step expired, PC: 000103 (HRL 2,1)
sim> step
Step expired, PC: 000104 (000000000000)
sim> ex 0-3
0: 000000000000
1: 111111444444
2: 444444111111
3: 000000000000

View file

@ -0,0 +1,10 @@
{
"load": 0o100,
"exec": 0o100,
"data": [
0o505040111111, // HRLI 1,111111
0o541040444444, // HRRI 1,444444
0o544100000001, // HLR 2,1
0o504100000001, // HRL 2,1
]
}

View file

@ -1,43 +0,0 @@
{
"load": 0o100,
"exec": 0o100,
"data": [
0o505040111111, // HRLI 1,111111
0o541040222222, // HRRI 1,222222
0o544100000001, // HLR 2,1
0o504100000001, // HRL 2,1
//
// >> dw 0 l4
// 000000: 000000 000000 111111 222222 222222 111111 000000 000000
//
// Compare to SIMH:
//
// sim> dep 100 505040111111
// sim> dep 101 541040222222
// sim> dep 102 544100000001
// sim> dep 103 504100000001
// sim> ex -m 100-103
// 100: HRLI 1,111111
// 101: HRRI 1,222222
// 102: HLR 2,1
// 103: HRL 2,1
// sim> dep pc 100
// sim> step
//
// Step expired, PC: 000101 (HRRI 1,222222)
// sim> step
//
// Step expired, PC: 000102 (HLR 2,1)
// sim> step
//
// Step expired, PC: 000103 (HRL 2,1)
// sim> step
//
// Step expired, PC: 000104 (000000000000)
// sim> ex 0-3
// 0: 000000000000
// 1: 111111222222
// 2: 222222111111
// 3: 000000000000
]
}

View file

@ -3,7 +3,7 @@ layout: page
title: PDP-10 (Model KA10) Test Machine
permalink: /devices/pdp10/machine/ka10/test/
machines:
- id: ka10test
- id: testka10
type: pdp10
---
@ -14,4 +14,4 @@ A PDP-10 (Model KA10) is simulated below.
**WARNING**: PDP-10 emulation is still in development, so functionality will be limited until further notice.
{% include machine.html id="ka10test" %}
{% include machine.html id="testka10" %}

View file

@ -3,7 +3,7 @@ layout: page
title: PDP-10 (Model KA10) Test Machine with Debugger
permalink: /devices/pdp10/machine/ka10/test/debugger/
machines:
- id: ka10test
- id: testka10
type: pdp10
debugger: true
---
@ -15,4 +15,4 @@ A PDP-10 (Model KA10) is simulated below.
**WARNING**: PDP-10 emulation is still in development, so functionality will be limited until further notice.
{% include machine.html id="ka10test" %}
{% include machine.html id="testka10" %}

View file

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/versions/pdpjs/1.34.0/machine.xsl"?>
<machine id="ka10test" type="pdp10" border="1" pos="center" background="#FAEBD7">
<machine id="testka10" type="pdp10" border="1" pos="center" background="#FAEBD7">
<name pos="center">PDP-10 Test Machine with Debugger</name>
<computer id="computer" busWidth="18"/>
<cpu id="cpu" model="1001" cycles="1000000"/>
<ram id="ram" addr="0x0000" size="0x4000" file="/apps/pdp10/tests/optest.json"/>
<ram id="ram" addr="0x0000" size="0x4000" file="/apps/pdp10/tests/optest-v01.json"/>
<device id="default" type="default"/>
<serial id="serial" adapter="0" binding="print" upperCase="true"/>
<panel ref="/devices/pdp10/panel/test/debugger/terminal.xml"/>

View file

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/versions/pdpjs/1.34.0/machine.xsl"?>
<machine id="ka10test" type="pdp10" border="1" pos="center" background="#FAEBD7">
<machine id="testka10" type="pdp10" border="1" pos="center" background="#FAEBD7">
<name pos="center">PDP-10 Test Machine</name>
<computer id="computer" busWidth="18"/>
<cpu id="cpu" model="1001" cycles="1000000"/>
<ram id="ram" addr="0x0000" size="0x4000" file="/apps/pdp10/tests/optest.json"/>
<ram id="ram" addr="0x0000" size="0x4000" file="/apps/pdp10/tests/optest-v01.json"/>
<device id="default" type="default"/>
<serial id="serial" adapter="0" binding="print" upperCase="true"/>
<panel ref="/devices/pdp10/panel/test/terminal.xml"/>

View file

@ -337,10 +337,14 @@ class CPUStatePDP10 extends CPUPDP10 {
*/
getOpcode()
{
/*
* Technically, we don't REALLY need to mask regRA with R_MASK, because all regRA accesses
* ignore any higher opcode bits, but let's keep things tidy.
*/
if ((this.regRA & PDP10.OPCODE.I_BIT)) {
this.regRA = this.readWord(this.regEA);
this.regRA = this.readWord(this.regEA) & PDP10.OPCODE.R_MASK;
} else {
this.regRA = this.regOP = this.readWord(this.lastPC = this.regPC);
this.regRA = (this.regOP = this.readWord(this.lastPC = this.regPC)) & PDP10.OPCODE.R_MASK;
}
/*

View file

@ -227,6 +227,24 @@ class DebuggerPDP10 extends Debugger {
return {addr: addr, fPhysical: fPhysical, fTemporary: false, nBase: nBase};
}
/**
* copyAddr(dbgAddr, dbgCopy)
*
* Updates an EXISTING DbgAddrPDP10 object, initialized with specified values and/or defaults.
*
* @this {DebuggerPDP10}
* @param {DbgAddrPDP10} dbgAddr
* @param {DbgAddrPDP10} dbgCopy
* @return {DbgAddrPDP10}
*/
copyAddr(dbgAddr, dbgCopy)
{
dbgAddr.addr = dbgCopy.addr;
dbgAddr.fTemporary = dbgCopy.fTemporary;
dbgAddr.nBase = dbgCopy.nBase;
return dbgAddr;
}
/**
* setAddr(dbgAddr, addr)
*
@ -1083,7 +1101,7 @@ class DebuggerPDP10 extends Debugger {
this.println(DebuggerPDP10.PROMPT + sCmd);
}
this.dbgAddrNextCode = this.newAddr(this.cpu.getPC());
this.setAddr(this.dbgAddrNextCode, this.cpu.getPC());
/*
* this.nStep used to be a simple boolean, but now it's 0 (or undefined)
@ -1170,7 +1188,7 @@ class DebuggerPDP10 extends Debugger {
this.cInstructions = this.cInstructionsStart = 0;
this.sMessagePrev = null;
this.nCycles = 0;
this.dbgAddrNextCode = this.newAddr(this.cpu.getPC());
this.setAddr(this.dbgAddrNextCode, this.cpu.getPC());
/*
* fRunning is set by start() and cleared by stop(). In addition, we clear
* it here, so that if the CPU is reset while running, we can prevent stop()
@ -1480,21 +1498,129 @@ class DebuggerPDP10 extends Debugger {
}
/**
* parseInstruction(sOp, sOperand, addr)
*
* TODO: Unimplemented. See parseInstruction() in modules/c1pjs/lib/debugger.js for a sample implementation.
* parseInstruction(sOpCode, sOperands, addr)
*
* @this {DebuggerPDP10}
* @param {string} sOp
* @param {string|undefined} sOperand
* @param {string} sOpCode
* @param {string|undefined} sOperands
* @param {DbgAddrPDP10} dbgAddr of memory where this instruction is being assembled
* @return {Array.<number>} of opcode bytes; if the instruction can't be parsed, the array will be empty
* @return {number} (opcode, or -1 if unrecognized instruction)
*/
parseInstruction(sOp, sOperand, dbgAddr)
parseInstruction(sOpCode, sOperands, dbgAddr)
{
var aOpBytes = [];
this.println("not supported yet");
return aOpBytes;
var opCode = -1, opMask;
var sMnemonic = sOpCode.toUpperCase();
for (var mask in DebuggerPDP10.OPTABLE) {
var aModes;
opMask = +mask;
var opMasks = DebuggerPDP10.OPTABLE[mask];
switch(opMask) {
case PDP10.OPCODE.OPMODE:
aModes = DebuggerPDP10.OPMODES;
break;
case PDP10.OPCODE.OPCOMP:
aModes = DebuggerPDP10.OPCOMPS;
break;
case PDP10.OPCODE.OPTEST:
aModes = DebuggerPDP10.OPTESTS;
break;
default:
aModes = [""];
break;
}
var opMode = 0;
for (var op in opMasks) {
var opNum = opMasks[op];
for (var iMode = 0; iMode < aModes.length; iMode++) {
var sMode = aModes[iMode];
if (sMode == "S" && opNum > DebuggerPDP10.OPS.MOVM) sMode = "B";
var sCandidate = DebuggerPDP10.OPNAMES[opNum] + sMode;
if (sMnemonic == sCandidate) {
if (opMask != PDP10.OPCODE.OPTEST) {
opMode = iMode;
} else {
opMode = ((iMode & 0o3) << 1) | ((iMode & 0o14) << 2);
}
opCode = (op | (opMode << 6)) * PDP10.OPCODE.OPSHIFT;
break;
}
}
if (opCode >= 0) break;
}
if (opCode >= 0) break;
}
if (opCode >= 0) {
if (!sOperands) {
this.println("missing operand(s)");
opCode = -1;
}
else {
var aOperands = sOperands.split(',');
for (var i = 0; i < aOperands.length; i++) {
var sOperand = aOperands[i].trim();
if (!sOperand) continue;
if (i > 1) {
this.println("too many operands: " + sOperand);
opCode = -1;
break;
}
var match = sOperand.match(/(@?)([^(]*)\(?([^)]*)\)?/);
if (!match) {
this.println("unknown operand: " + sOperand);
opCode = -1;
break;
}
var operand = this.parseExpression(match[2]);
if (operand == undefined) {
opCode = -1;
break;
}
if (!i && aOperands.length > 1) {
if (operand < 0 || operand > PDP10.OPCODE.A_MASK) {
this.println("accumulator index out of range: " + match[2]);
opCode = -1;
break;
}
opCode += (operand << PDP10.OPCODE.A_SHIFT);
continue;
}
if (operand < 0 || operand > PDP10.OPCODE.Y_MASK) {
this.println("address out of range: " + match[2]);
opCode = -1;
break;
}
opCode += operand;
if (match[3]) {
operand = this.parseExpression(match[3]);
if (operand == undefined) {
opCode = -1;
break;
}
if (operand < 0 || operand > PDP10.OPCODE.X_MASK) {
this.println("index out of range: " + match[3]);
opCode = -1;
break;
}
opCode += operand << PDP10.OPCODE.X_SHIFT;
}
if (match[1]) {
opCode += PDP10.OPCODE.I_BIT;
}
}
}
}
else {
this.println("unknown instruction: " + sOpCode);
}
return opCode;
}
/**
@ -1903,7 +2029,8 @@ class DebuggerPDP10 extends Debugger {
{
var sReg = this.getRegName(iReg);
if (sReg) {
sReg += '=' + this.toStrBase(this.getRegValue(iReg)) + ' ';
var nBits = (iReg == DebuggerPDP10.REGS.RA? 23 : 18);
sReg += '=' + this.toStrBase(this.getRegValue(iReg), nBits) + ' ';
}
return sReg;
}
@ -2230,19 +2357,24 @@ class DebuggerPDP10 extends Debugger {
var dbgAddr = this.parseAddr(asArgs[1], true);
if (!dbgAddr) return;
var sOpCode = asArgs[2];
this.dbgAddrAssemble = dbgAddr;
if (asArgs[2] === undefined) {
if (sOpCode == undefined) {
this.println("begin assemble at " + this.toStrAddr(dbgAddr));
this.fAssemble = true;
this.cmp.updateDisplays();
return;
}
var aOps = this.parseInstruction(asArgs[2], asArgs[3], dbgAddr);
if (aOps.length) {
for (var i = 0; i < aOps.length; i++) {
this.setWord(dbgAddr, aOps[i], 1);
}
asArgs.shift();
asArgs.shift();
asArgs.shift();
var sOperands = asArgs.join("");
var opCode = this.parseInstruction(sOpCode, sOperands, dbgAddr);
if (opCode >= 0) {
this.setWord(dbgAddr, opCode);
/*
* Since getInstruction() also updates the specified address, dbgAddrAssemble is automatically advanced.
*/
@ -2500,8 +2632,7 @@ class DebuggerPDP10 extends Debugger {
}
if (sDump) this.println(sDump);
this.dbgAddrNextData = dbgAddr;
this.copyAddr(this.dbgAddrNextData, dbgAddr);
this.nBase = nBase;
}
@ -2532,7 +2663,7 @@ class DebuggerPDP10 extends Debugger {
for (var i = 2; i < asArgs.length; i++) {
var vNew = this.parseExpression(asArgs[i]);
if (vNew === undefined) {
this.println("unrecognized value: " + asArgs[i]);
this.println("unknown value: " + asArgs[i]);
break;
}
this.println("changing " + this.toStrAddr(dbgAddr) + " from " + this.toStrWord(fnGet.call(this, dbgAddr)) + " to " + this.toStrWord(vNew));
@ -2897,7 +3028,7 @@ class DebuggerPDP10 extends Debugger {
switch (sRegMatch) {
case "PC":
cpu.setPC(w);
this.dbgAddrNextCode = this.newAddr(cpu.getPC());
this.setAddr(this.dbgAddrNextCode, cpu.getPC());
break;
default:
this.println("unknown register: " + sReg);
@ -2911,7 +3042,7 @@ class DebuggerPDP10 extends Debugger {
this.println(this.getRegDump(fMisc));
if (fInstruction) {
this.dbgAddrNextCode = this.newAddr(cpu.getPC());
this.setAddr(this.dbgAddrNextCode, cpu.getPC());
this.doUnassemble(this.toStrAddr(this.dbgAddrNextCode));
}
}
@ -3243,7 +3374,7 @@ class DebuggerPDP10 extends Debugger {
sInstruction = this.getInstruction(dbgAddr, sComment, nSequence);
this.println(sInstruction);
this.dbgAddrNextCode = dbgAddr;
this.copyAddr(this.dbgAddrNextCode, dbgAddr);
nBytes -= dbgAddr.addr - addr;
nPrinted++;
}
@ -3295,7 +3426,7 @@ class DebuggerPDP10 extends Debugger {
if (!sCmd.length || sCmd == "end") {
if (this.fAssemble) {
this.println("ended assemble at " + this.toStrAddr(this.dbgAddrAssemble));
this.dbgAddrNextCode = this.dbgAddrAssemble;
this.copyAddr(this.dbgAddrNextCode, this.dbgAddrAssemble);
this.fAssemble = false;
}
sCmd = "";

View file

@ -147,13 +147,14 @@ var PDP10 = {
OPSHIFT: Math.pow(2, 21), // operation shift
IOSHIFT: Math.pow(2, 26), // input-output device code shift
IOMASK: 0o177, // input-output device code mask (after shift)
ACSHIFT: Math.pow(2, 23), // used to isolate the high 13 bits, with A starting at bit 0
ACSHIFT: Math.pow(2, 23), // used to shift down the high 13 bits, with A starting at bit 0
A_SHIFT: 23, // A shift
A_MASK: 0o17, // A mask (after shift)
I_BIT: 0o20000000, // indirect bit
X_SHIFT: 18, // X shift
X_MASK: 0o17, // X mask (after shift)
Y_MASK: 0o777777, // Y mask
R_MASK: 0o37777777, // used to isolate the low 23 bits (I,X,Y)
HALT: 0o5304 // operation code for HALT
},

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long