-
Notifications
You must be signed in to change notification settings - Fork 23
PDP 11 Co Pro Notes
This page documents various approaches to trying to compile a Pi Spigot written in C, such that it's runnable on the PDP-11 Co Processor.
- Attempt 1 - Compiling on V7 Unix in SIMH
- Attempt 2 - Compiling on Linux using GCC PDP-11 Cross Compiler
- Attempt 3 - Compiling on Linux using PCC PDP-11 Cross Compiler
- Useful Links
I followed these instructions to get V7 Unix running on SIMH.
Here's a sample session compiling and running a Pi Spigot:
$ cat > pi.c
#include <stdio.h>
#define N 3500
main() {
short r[N + 1], i, k, b, c;
long d;
c = 0;
for (i = 1; i <= N; i++)
r[i] = 2000;
for (k = N; k > 0; k -= 14) {
d = 0;
i = k;
for(;;) {
d += r[i]*10000L;
b = i*2 - 1;
r[i] = d%b;
d /= b;
i--;
if (i == 0) break;
d *= i;
}
printf("%.4d", (int)(c + d/10000));
c = d%10000;
}
}
$ cc pi.c
$ ls -l a.out
-rwxrwxr-x 1 dmr 5294 Sep 22 08:55 a.out
$ file a.out
a.out: executable not stripped
$ nm -gn a.out
000000 T start
000074 T _main
000542 T _printf
000616 T __doprnt
001732 T pfloat
001732 T pgen
001732 T pscien
001744 T __strout
002244 T __flsbuf
002606 T _fflush
002730 T __cleanu
002766 T _fclose
003130 T _exit
003146 T _malloc
003640 T _free
003676 T _realloc
004150 T _isatty
004220 T _stty
004252 T _gtty
004304 T _close
004332 T _ioctl
004400 T _sbrk
004452 T _brk
004512 T _write
004554 T aldiv
005062 T almul
005136 T cerror
005154 T ldiv
005436 T lmul
005504 T lrem
005742 T csv
005756 T cret
006120 D __iob
006360 D __lastbu
006406 B __sobuf
007406 B __sibuf
010406 B _errno
010410 B _environ
010426 B _end
$ a.out
031410592605358097930238406264033830279502880419701693099370510508209074940459203078016400628602089098620803408253042110706709821048080651302823066407093084460955058202317025350940801284081110745002841027001938052110555096440622904895049300381906442088100975606593034460128407564082330786708316052710201909140564805669023460348061040543206648021330936007260024910412703724058700660063150588107488015200920906282092540917015360436708925090360110330503054088200466502138041460951904151016090433005727036507595091950309201861017380193206117093100511805480074460237909627049560735108857052720489102279038180301109491029830367303624040650664308600213904946039520247307190070210798609430702707053092170176209317067520384607481084670669405130200005681027140526305608027780577103427057780960901736037170872104684040900122409534030140654905853071050792027960892508923054200199506112012900219608640344018150981306297074770130909605018700721103499099990837209780049950105907317032810609603185095020445904553046900830206425022300825303446085030526109311088170101003103783087520886508753032080381402061071770669104730035980253409042087550468703115095620863808235037870593705195077810857708053021710226806610300109278076610119509092016420198
So this runs fine within Unix V7 on SIMH, but I'd like to be able to run this on the PDP-11 Co Pro.
Tere are a number of problems:
-
Executables on V7 Unix are compiled to run from address 0x0000 (000000) and are generally not position independant. The Co Pro has a table of vectors at address 0, so expects a program to run from 0x0100 (000400).
-
The executable starts with a floating point instruction (setd) that isn't present on the Co Pro.
-
Unix V7 uses TRAP instructions to trap to the Kernel, with call parameters enbedded in the code after the trap. The PDP-11 Co Pro uses EMT instructions (emulator TRAP), with call parameters passed in registers. Somewhat incompatible!
For the build notes, see PDP-11 GCC Cross Compiler
This was almost successful, except the Pi Spigot needs 32-bit longs, and the GCC PDP-11 backing generates buggy code for these, such as:
Line 122:
outhex32(*pp);
Which ends up as:
386: 1d40 fffe mov -2(r5), r0
38a: 1200 mov (r0), r0
38c: 1c01 0002 mov 2(r0), r1
390: 1066 mov r1, -(sp)
392: 1026 mov r0, -(sp)
394: 09f7 ff50 jsr pc, 2e8 <_outhex32>Compiling on Linux using GCC PDP-11 Cross Compiler
The instructions are 38a and 38c are in the wrong order!
After lots of head scratching, it turns out the bug is in pdp11_expand_operands().
This function expands operands of 32-bit Standard Int (SI) type to pairs of operands of the 16-bit Half Int (HI) type. Part of the logic is to decide the order of the two 16-bit halves. And it looks like it doesn't consider the case where the destination register is also the source register. In this case, the order of the two instructions needs to be reversed.
The code to do this looks like:
/* DMB - detect the case where source [1] is an indirect access via a register that
is also used as the destination [0], and force little endian half-word order */
if (GET_CODE (operands[0]) == REG && GET_CODE (operands[1]) == MEM) {
int dstreg = REGNO (operands[0]);
int srcreg = -1;
if (GET_CODE (XEXP (operands[1], 0)) == REG) {
srcreg = REGNO (XEXP (operands[1], 0));
} else if (GET_CODE (XEXP (operands[1], 0)) == PLUS) {
if (GET_CODE (XEXP (XEXP (operands[1], 0), 0)) == REG) {
srcreg = REGNO (XEXP (XEXP (operands[1], 0), 0));
} else if (GET_CODE (XEXP (XEXP (operands[1], 0), 1)) == REG) {
srcreg = REGNO (XEXP (XEXP (operands[1], 0), 1));
}
}
if (srcreg == dstreg) {
useorder = little;
}
}
This code is rather scary, because operand are represened as small trees of rfx nodes.
Operand[0] is the destination and needs to look like:
-->REG
Operand[1] is the source can needs to look like one of:
-->MEM-->REG
-->MEM-->PLUS-->REG
-->Address
-->MEM-->PLUS-->Address
-->REG
There are some macros which help processing these operands:
- GET_CODE(rfx) returns the type of the rfx object
- XEXP(rfx, n) follows the nth child of the rfx object
- REGNO(rfx) return the register number of the rfx object (assuming it's a REG node)
Adding this into pdp11_expand_operands() fixed this particular code generation bug, but still the 32-bit division doesn't work.
The code that's failing is:
unsigned long
__udivmodsi4(unsigned long num, unsigned long den, int modwanted)
{
unsigned long bit = 1;
unsigned long res = 0;
while (den < num && bit && !(den & (1L<<31)))
{
den <<=1;
bit <<=1;
}
while (bit)
{
if (num >= den)
{
num -= den;
res |= bit;
}
bit >>=1;
den >>=1;
}
if (modwanted) return num;
return res;
}
And specifically, this test
!(den & (1L<<31))
GCC is (legitimately) mapping this to:
((signed long) den) >= 0
Which results in the following code (when the constant operand is zero)
160: 0bc2 tst r2
162: 0201 bne 166 <_udivmodsi4+0x5a>
164: 0bc3 tst r3
166: 04e2 bge 12c <_udivmodsi4+0x20>
Notes:
- r2 is the high word of den
- r3 is the low word of den
- BGE branches if N xor V = 0, TST sets V=0 so this is effectively BPL, it also sets C=0)
There is an intuitive argument that this code is incorrect. When comparing against zero, the final value of the N flag should only depend on r2 (the high word). In the above code, when r2=0 then N = sign(r3), which is wrong.
This code is coming from the cmpsi template in pdp11.md
This template introduces a cmpsi(a,b) instruction that in the general case produces:
CMP ahi, bhi
BNE l1
CMP alo, blo
l1:
However, if b is zero, then the CMP instructions are replaced by the TST instructions.
TST ahi
BNE l1
TST alo
l1:
For unsigned comparisons (BHI, BHIS, BL, BLOS), which test the C/Z bits, the above code works fine. If the high words are equal, the base the result on the low word.
For signed comparisons (BGT, BGE, BLT, BLE), which test the N/V/Z bits, I think there is a problem with this appoach, and in practice it seems to be failing to correctly set N/V for the 32-bits as a whole.
I need to try to find some old "double precision" integer code for the PDP-11 to see how it should be done.
PCC (Portable C Compiler) is a C compiler that was written by Stephen C. Johnson of Bell Labs in the mid-1970s. A new version of PCC is now maintained by Anders Magnusson. The website is here
The source was a CVS repository archive; I prefer working with git, so started by converting it:
sudo apt-get install cvs cvs2svn
cd ~/pdp11
wget http://pcc.ludd.ltu.se/ftp/pub/pcc/pcc-cvs-20220117.tgz
tar xf pcc-cvs-20220117.tgz
export CVSROOT=~/pdp11/pcc-cvs-20220117
cvs init
cvs2git --blobfile=git-blob.dat --dumpfile=git-dump.dat --fallback-encoding=utf8 $CVSROOT
mkdir pcc.git
cd pcc.git/
git init --bare
cat ../git-blob.dat ../git-dump.dat | git fast-import
cd ..
rm git-dump.dat git-blob.dat
git clone pcc.git
Building PCC as a Cross Compiler:
Two main steps:
- Build binutils for the target
- Build PCC for the target
We already have pdp11-aout version of binutils, so we just did 2.
Configure:
git checkout $(git log --pretty=oneline | grep 20211219 | cut -c1-8)
sudo apt-get install build-essential flex bison
cd pcc
sed -i 's/MANPAGE=@BINPREFIX@cpp/MANPAGE=@BINPREFIX@pcc-cpp/' cc/cpp/Makefile.in
sed -i 's/ cxxcom//' cc/Makefile.in
./configure --target=pdp11-aout-bsd --prefix=/usr/local --libexecdir=/usr/local/libexec/pcc --with-assembler=pdp11-aout-as --with-linker=pdp11-aout-ld
make
sudo make install
Notes:
- The last commit where the PDP-11 target builds seems to be theone dates 20211219.
- The first sed just patches the manual path to avoid a conflict with cpp on Ubuntu.
- The second sed prevents the C++ compiler from being built, as this is not compatible with the PDP-11 target.
Running:
cat > pi.c
//#include <stdio.h>
#define N 3500
main() {
short r[N + 1], i, k, b, c;
long d;
c = 0;
for (i = 1; i <= N; i++)
r[i] = 2000;
for (k = N; k > 0; k -= 14) {
d = 0;
i = k;
for(;;) {
d += r[i]*10000L;
b = i*2 - 1;
r[i] = d%b;
d /= b;
i--;
if (i == 0) break;
d *= i;
}
// printf("%.4d", (int)(c + d/10000));
c = d%10000;
}
}
pdp11-bsd-pcc pi.c
pdp11-aout-as: unrecognised option '-V'
error: pdp11-aout-as terminated with status 1
Seems like an incompatibility with the assembler...
The assembler command is:
pdp11-aout-as -V -u -o /tmp/ctm.4kVtfh /tmp/ctm.iXWzuP
The -V and -u options appear to be specific to 2.11BSD: http://pdp11.nocrew.org/binutils/as-opt.html
So it looks like the pdp11 target needs to be hosted on BSD for it to work. I could continue to hack, but I expect this will be the tip of the iceberg.
- Developing for a PDP-11
- Diane's PDP-11 Page
- subgeniuskitty - PDP-11 Cross-Compiling - Building a cross compiler with GCC for pdp11-aout.
- C Programming on a bare metal PDP-11
- BBC Basic for the PDP-11 (Jonathan Harston)
- PDP-11 CoProcessor Technical Reference (Jonathan Harston)
- MMB/SSD Utils in perl (Stephen Harris)
Hardware
Software
- Build dependencies
- Running cmake
- Compiling kernel.img
- Deploying on a Pi
- Recommended config.txt and cmdline.txt options
- Validation
- Compilation flags
Implementation Notes