FIG-Forth V 1.3.3
Forth
Forth is a well known programming language and development environment and there are implementations for many microcontrollers including the PDP-11, which according to the inventor of Forth had a strong influence on the design of Forth. The PDP-11 version exists as task running under RT-11 and RSX-11M and as well as stand-alone program. Initially only available as bootable floppy disk, there is also a stand alone version that boots from a RL02. The stand alone Version of Forth requires only a minimal system. As it turned out, the DCJ11 SBC could run the binary out of the box. But without mass storage such a system is only half the fun. Here is the story how stand alone Forth learned to use the TU-58 as mass storage. An additional serial interface, similar to the console UART, is all you need.
The idea of running Forth on the DCJ11 SBC
Hans Hübner, proud owner of a ISA card featuring a DCJ11 and a DC319, was looking for an application of this card, but unfortunately there was not much information and software that would allow to make any use of this card even when having an old PC with ISA slots.

So he asked me if I could send him one of the PCBs to build his own DCJ11 SBC using the components of the ISA card. And here the result. He had a hard time to find a 614.4kHz crystal, so he built his own adapter using a more common 4.9152MHz oszillator and counter to divide the frequency by eight.

After he got his DCJ11 SBC up and running he soon expressed his desire to run FORTH on the computer. I was aware that FORTH is running on RT-11 and therefore I told him, that the RT-11 expansion will be ready soon. However he got impatient and dig more into the Forth subject and found that the MACRO-11 source code for Forth could be assembled as a standalone application and the only thing needed was some tools to load the application into the memory.
First he used ODT to load the complete stand-alone application via the console to the memory. This took approximatively 5 minutes. He made a very nice python script and provided all the information so it should be pretty easy to load Forth to the DCJ11 SBC and at least have a decent programming environment to play with the hardware. He also provides scripts to assemble the source code and make a binary loadable file from the binary created on RT-11 using SIMH.
Slow load using ODT
After assembling the source in an emulated PDP-11 running RT-11 and converting the LDA file into a binary he was able to use his python script to load the complete Forth binary via ODT to the memory of the DCJ11 SBC.
peter@MacBook-Air-von-Peter ~ % cd Fig-Forth
peter@MacBook-Air-von-Peter Fig-Forth % python3 -m venv .venv
peter@MacBook-Air-von-Peter Fig-Forth % . .venv/bin/activate
(.venv) peter@MacBook-Air-von-Peter Fig-Forth % ls
forth.bin odt-uploader-main odt-uploader.py requirements.txt
(.venv) peter@MacBook-Air-von-Peter Fig-Forth % pip install -r requirements.txt
Collecting pyserial==3.5 (from -r requirements.txt (line 1))
Downloading pyserial-3.5-py2.py3-none-any.whl.metadata (1.6 kB)
Collecting tqdm==4.66.1 (from -r requirements.txt (line 2))
Downloading tqdm-4.66.1-py3-none-any.whl.metadata (57 kB)
Downloading pyserial-3.5-py2.py3-none-any.whl (90 kB)
Downloading tqdm-4.66.1-py3-none-any.whl (78 kB)
Installing collected packages: pyserial, tqdm
Successfully installed pyserial-3.5 tqdm-4.66.1
[notice] A new release of pip is available: 25.0 -> 25.1.1
[notice] To update, run: pip install --upgrade pip
(.venv) peter@MacBook-Air-von-Peter Fig-Forth % python odt-uploader.py /dev/cu.usbserial-D34N2YUF forth.bin 1000
12:30:49 - INFO - File size: 7132 bytes
12:30:49 - INFO - Opened serial port /dev/cu.usbserial-D34N2YUF at 38400 bps, 8N1
12:30:49 - INFO - Sending initial CR to get ODT's attention...
12:30:49 - INFO - Setting start address to 001000
Uploading: 100%|█████████████████████████████████████████████████████████████████| 3566/3566 [04:58<00:00, 11.93words/s]
12:35:48 - INFO -
Upload complete: 3566 words uploaded
@1000g
FIG-FORTH V 1.3.3
OK
1 3 . 3 OK
. 1 OK
1 3 + OK
. 4 OK
5 6 * OK
. 30 OK
As we have seen, the DCJ11 SBC supports the stand alone Forth out of the box. The main reason this works is because Forth does not make use of interrupts, which the DCJ11 SBC does not implement without an extension card.
How to improve the situation
He then wanted to add Battery Backup for the RAM so he would not have to always wait to load Forth and also wanted to use the unused memory as mass storage.
There are options to use an RL02 or a RX01 as the mass storage in the Forth stand alone version, however these devices use DMA, which is not possible with the DCJ11 SBC. So we were investigating other possibilities, like a microcontroller attached to the expansion board providing mass storage capabilities, or an expansion board with some flash memory etc.
Of course this requires the development of the mass storage IO routines for Forth, something which is not really difficult. Forth, just reads and writes chunks of 1kbyte, called screens, to and from the mass storage device.
To make as much use of existing work as possible I came up with the idea to use a TU-58, or a TU-58 emulator, as the mass storage device. First it does not require DMA and it only requires a DLV11 serial interface. Second there are many implementations of TU-58 emulators, some of them have very nice features and extensive debugging possibilities.
A DLV11 or its hackers version is already used as the console, so the hardware
required would be just another serial interface of the same type and a decoder
for the device address in the IO page to select either the console or the second
serial interface.
It was expected that the development of the TU-58 interface routines to Forth would require multiple iterations, so the first steps was modifying the odt-uploader to load Forth over the console much faster than with just using ODT.
Fast load using small loader
Instead of using ODT to load the binary, ODT is now used to load a small bootstrap loader. In a first trial I just entered such a program via ODT by hand and then used the send binary feature the the terminal program. Such a simple bootstrap loader only takes 13 words. Once you have entered the bootstrap loader you just execute the program and send the Forth binary over the console. The only thing you need to make sure is that the address of the bootstrap loader is outside the address range used by the binary. Forth starts at address 10008 as do many bootable images. The first 512. bytes of memory are typically used by the boot block. In our case we just use address 1008 for the bootstrap loader. In the following example the load address and length of the binary to be expected are hardcoded. After the binary has been uploaded the DCJ11 executes a HALT instructions and returns to ODT, now you just need to start the loaded binary.
00000100/012700 ; MOV #1000, R0 ; Start Address of Fig Forth
00000102/001000
00000104/012701 ; MOV #15734, R1 ; Length of binary in octal
00000106/015734
00000110/032737 ; BIT #200, @#RCSR ; Test for Byte Ready
00000112/000200
00000114/177560
00000116/001774 ; BEQ 110 ; No, try again
00000120/113720 ; MOVB @#RBUF, (R0)+ ; Copy Byte to Memory
00000122/177562
00000124/077107 ; SOB R1, 110 ; Do for the length of binary
00000126/000000 ; HALT
00000130/000765
@100g ; Start the loader
000130 ; Returns after few seconds an stops after HALT
@1000g ; Start loaded binary
FIG-FORTH V 1.3.3 ; Prompt
3 3 + . 6 OK ; It works
As soon as I told Hans about this idea, he immediately integrated this process into his python script which now loads Forth blazingly fast to the DCJ11 SBC
(.venv) peter@MacBook-Air-von-Peter Fig-Forth % python odt-uploader.py /dev/cu.usbserial-FTHBRLCS forth.bin 1000
12:08:57 - INFO - File size: 7132 bytes
12:08:57 - INFO - Opened serial port /dev/cu.usbserial-FTHBRLCS at 38400 bps, 8N1
12:08:57 - INFO - Sending initial CR to get ODT's attention...
12:08:57 - INFO - Uploading loader program...
12:08:58 - INFO - Starting loader program...
12:08:59 - INFO - Sending binary data...
Uploading: 100%|█████████████████████████████████████████████████████████████████| 7132/7132 [00:02<00:00, 2815.35bytes/s]
12:09:01 - INFO - Waiting for loader to complete...
12:09:01 - INFO - Upload complete
(.venv) peter@MacBook-Air-von-Peter Fig-Forth %
You then connect to the console with a terminal program and only need to start Forth.
@?
@1000g
FIG-FORTH V 1.3.3
5 5 * . 25 OK
This was a nice start and allows to load and test new versions of Forth with TU-58 read and write routines faster, i.e. only takes a few seconds.
Note that this odt-uploader script now requires also the baud-rate with which to connect to the console. Also it automatically adjusts the bootstrap loader to match the address given and the length of the binary. In fact this script can upload any binary to any place as long as it does not conflict with the bootstrap loader and fits within the 56kbyte addressable by the bootstrap loader.
Second Serial Interface
At the same time I was designing the breadboard interface and it arrived just in time to be used for the second serial interface on a breadboard

To keep things simple and to avoid too much glue logic, this second serial interface also uses a DC319 to have 100% compatibility with a real DLV11 in case I wanted to reuse existing TU-58 routines without having to debug the hardware. The right choice as I found out later. And with this I had my development environment in order to boot FIG Forth from a TU-58 emulator.
Intelligent Expansion Card
Hans initially wanted to build his own Pico based mass storage device, but I could convince him that using an existing and proven interface is much less critical than developing everything from scratch. Even if you are not using a serial interface you could still use a Pico that implements the register of a DLV11 and even emulate the TU-58 protocol at the same time.
This enables the independant development of a Pico interface and the development of the necessary drivers for Forth. Stay tuned as the Pico interface card will enable a lot of interesting projects to the DCJ11 SBC.
Current status
Currently the TU-58 routines are ready and integrated into the Forth source code and can be activated in stand alone mode. A RT-11 formatted image bootable via TU-58 emulator and integrated TU-58 routines is available. In Block 0 a boot loader will read screens # 40-47 form FORTH.DAT which is expected to be the first file on the RT-11 volume. Screens # 40-47 are literally just a copy of the previously used FORTH.BIN with TU-58 IO routines included. After it has loaded FORTH.BIN it will jump to address 1000(8) and cold start Forth. You can use LOAD to add additional functionality using the standard screens as with the other stand-alone versions.
Currently missing is the support to create copies of the TU-58 image using Forth itself and also the boot loaders in screens 36ff are still the RX01 boot loaders so you cannot create a bootable TU-58 using Forth. This will most likely take more time to develop. On the other hand it is not important for the full experience of a stand alone Forth machine with mass storage.
All you need is a DCJ11 SBC with a second serial interface and a TU-58 emulator. You must be carefull when using USB-Serial adapters to connect the DCJ11 SBC to a TU-58 emulator, especially when using macOS, as many USB drivers do not properly handle the BREAK signal which is crucial to the TU-58 protocol. In my case the original FTDI USB-Serial cables do not work even so the driver is native in macOS. However cheap no-name USB-Serial adapters using the Prolific chip set for 2USD worked perfectly. Strange enough, after installing the Prolific 2303 Serial drivers, available in the App Store, even the FTDI based USB Adapters properly handled the BREAK signal.
Instructions to setup Forth
In the following I will give an overview of all individual steps needed to build your own Forth machine based on the DCJ11 SBC.
Hardware
Of course the base is a working DCJ11 SBC. For the second serial interface you can either use the Multi IO card described in a previous article or build your own serial interface that is compatible with a DLV11 and implements the BRK signal. If you are using a DC319 this is the case.
If you are using USB to Serial TTL adapters to connect the physical UART of the DCJ11 SBC and the Multi IO card make sure they support the BREAK signal via the driver. FTDI chips tend to be an issue with macOS and Ubuntu. Prolific based adapters seem to work out-of-the-box.
Software
If you want to build all the necessary parts from scratch you need to download and install several software tools
ODT Uploader
Hans has written a python script that allows to upload binaries to the DCJ11 SBC via the console. You can use the script to upload a bootstrap loader to memory above 32kbyte, which is not touched by forth.
https://github.com/hanshuebner/odt-uploader
pdp11 from simh
To assemble FORTH we need a real MACRO-11 assembler. The easiest way to be able to use a real MACRO-11 assembler with all necessary features to assemble the Forth source code is using an emulated PDP-11 using simh. You will also need a bootable RT-11 system with all the necessary tools inlcuded. And last but not least a tool which is able to exchange files between the emulated PDP-11 and the host.
Assembling DDBOOT and BOOT1
To assemble the bootstrap loader and the boot block you don’t need the full capabilities of the original PDP-11 MACRO-11 assembler. The fork of MACRO11 by Jörg Hoppe is all you need
https://github.com/j-hoppe/MACRO11
Converting MACRO11 object files to absolute Binary
MACRO11 generates object files. In case you have a single source with no external references and only absolute addresses you can convert this object into a binary file that can be executed by the DCJ11 SBC.
https://github.com/AK6DN/obj2bin
TU-58 Emulator
You can use literally any TU-58 emulator and even stand-alone TU-58 emulators. Here I have a list of various options. I personally often use tu58fs from Jörg Hoppe. Here is an incomplete list of TU-58 emulators I use
Don North https://github.com/AK6DN/tu58em
Jörg Hoppe https://github.com/j-hoppe/tu58fs
Béla Török https://www.torok.info/computing/pdp11/tu58/
Joe Lang https://github.com/joelang/tu58v3
Make sure you use a TU-58 emulator which is capable to handle 10Mb disk images, the same size as an RL02.
PDPFS
PDPFS form David Caldwell is also used in the docker image configured via the scripts of Hans Hübner, but in fact it is a very useful tool to exchange files from RT-11 diskimages and your host system
https://github.com/caldwell/pdpfs
pdpfs supports raw RX01 and RX02 disk images in IMD format as well as any flat RT-11 disk image with at least 1Mbyte in size. The 1Mbyte constraint is actually not really due to limitations of the logic but rather an explicit limitation by pdpfs to avoid handling small images as flat images. In our case this is not an issue as we will not use standard TU-58 sized images but rather use 10Mbyte images with the same format as used by the RL02 stand alone Forth. As we will see this makes things much easier.
FIG Forth
This TU-58 Forth will follow the same conventions regarding disk format as the existing stand-alone Forth for RX01 and RL02. TU-58 Forth will use disk images that are the same size as RL02 volumes and in the following guidelines the disk image is based on the bootable RL02 Fig Forth image available form http://www.stackosaurus.com/figforth.html which also has a lot of additional information about FIG Forth for the PDP-11 and has been a great inspiration and source of information for this project.
The main reason I did not use the standard size of TU-58 tapes but rather the size of an RL02 image is mainly because initially I was not able to use a DLV11 in simh to be used as an TU-58 interface and because I wanted to have enough screens in Forth.
Once I understood the reason TU-58 was not workgin in simh I opened an issue on github ( https://github.com/simh/simh/issues/1220 ) , which was quickly resolved and now TU-58 works again as expected.
So if you want to use TU-58 in simh you need to use the newest version from github.
Here is an example how to configure simh with a TU-58 tape image attached
peter@MacBook-Air-von-Peter DCJ11-SBC-RT-11 % cat pdp11.txt
set cpu 11/73 128K
set tto 8b
set tti 8b
set rl0 writeenabled
set rl0 rl02
attach rl0 rt11v53dd.dsk
set tdc enable
attach tdc0 dd.dsk
boot rl0
peter@MacBook-Air-von-Peter DCJ11-SBC-RT-11 %
Here is the log of using the above configuration with a TU-58 tape image attached.
peter@MacBook-Air-von-Peter DCJ11-SBC-RT-11 % ./simh-master/BIN/pdp11 pdp11.txt
PDP-11 simulator V4.0-0 Current git commit id: b305252b
./pdp11.txt-6> attach rl0 rt11v53dd.dsk
%SIM-INFO: RL0: './rt11v53dd.dsk' Contains RT11 partitions
%SIM-INFO: 1 valid partition, Type: V05, Sectors On Disk: 20450
./pdp11.txt-8> attach tdc0 dd.dsk
%SIM-INFO: TDC0: './dd.dsk' Contains RT11 partitions
%SIM-INFO: 1 valid partition, Type: V05, Sectors On Disk: 512
%SIM-INFO: TDC0: buffering file in memory
RT-11SJ V05.03
.show all
RT-11SJ V05.03
Booted from DL0:RT11SJ
USR is set SWAP
EXIT is set SWAP
KMON is set NOIND
TT is set NOQUIET
ERROR is set ERROR
SL is set OFF
EDIT is set KED
KMON nesting depth is 3
PDP 11/73A Processor
128KB of memory
Floating Point Microcode
Extended Instruction Set (EIS)
Memory Management Unit
Cache Memory
60 Cycle System Clock
FPU support
Device Status CSR Vector(s)
------ ------ --- ---------
CR Not installed 177160 230
CT Installed 000000 260
DD Installed 176500 300 304
DL Resident 174400 160
DM Installed 177440 210
DP Installed 176710 254
DS Not installed 172040 204
DT Not installed 177340 214
DU Installed 172150 154
DU0: is set UNIT=0, PART=0, PORT= 0
DU1: is set UNIT=1, PART=0, PORT= 0
DU2: is set UNIT=2, PART=0, PORT= 0
DU3: is set UNIT=3, PART=0, PORT= 0
DU4: is set UNIT=4, PART=0, PORT= 0
DU5: is set UNIT=5, PART=0, PORT= 0
DU6: is set UNIT=6, PART=0, PORT= 0
DU7: is set UNIT=7, PART=0, PORT= 0
DW Not installed 000000
DX Installed 177170 264
DY Not installed 177170 264
DZ Not installed 000000
PD Not installed 000000
RF Not installed 177460 204
RK Installed 177400 220
LD Installed 000000 000
LP Installed 177514 200
LS Installed 176500 470 474 300 304
MM Not installed 172440 224
MS Installed 172522 224 300
MT Installed 172520 224
NL Installed 000000 000
PC Installed 177550 070 074
PI Not installed 000000 000
SL Installed 000000 000
SP Installed 000000 110
TT Installed 000000 000
VM Installed 177572 250
XC Not installed 173300 210 214
XL Installed 176500 300 304
TT
DL (Resident)
DL0 = DK , SY
RK
LD
DU
SL
DM
DP
DX
VM
SP
XL
DD
MT
MS
CT
LP
LS
PC
NL
9 free slots
Job Name Console Level State Low High Impure
--- ---- ------- ----- ----- --- ---- ------
0 RESORC 0 0 Run 000000 145016 N/A
No multi-terminal support
Address Module Words
------- ------ -----
160000 IOPAGE 4096.
156062 DL 487.
145060 RMON 2305.
001000 ..BG.. 25624.
No LD units mounted
.dir dd:
RT11SJ.SYS 79P 20-Dec-85 SWAP .SYS 27P 20-Dec-85
DD .SYS 5P 20-Dec-85 TT .SYS 2P 20-Dec-85
PIP .SAV 30P 20-Dec-85 DIR .SAV 19P 20-Dec-85
RESORC.SAV 25P 20-Dec-85
7 Files, 187 Blocks
317 Free blocks
.
Bootstrap
Note that this bootstrap loader is a generic TU-58 bootstrap loader and can be used to boot any PDP-11 from any TU-58 volume. So it is not specific to boot Forth and at the same time any bootstrap loader used to boot from DD or TU-58 should work to boot the TU-58 stand-alone Forth.
I use the bootstrap loader described in the Multi IO Card article. Without boot ROM you can use the odt-uploader.py script from Hans Hübner. Note that Forth uses only the first 32kbyte of the memory and therefore you should place the bootstrap above the 32kbytes used by Forth. As long as you don’t power cycle the DCJ11 SBC the bootstrap loader will stay in memory and can be repeatedly used to boot new Forth images. Depending on the environment you must first disconnect your terminal from the console. Make sure that ODT is active and shows the prompt. Now you can upload the bootstrap loader.
peter@MacBook-Air-von-Peter % python odt-uploader.py /dev/cu.usbserial-D34N2YUF 38400 ../MACRO11-master/ddrw/ddboot5.bin 140000
15:07:53 - INFO - File size: 70 bytes
15:07:53 - INFO - Opened serial port /dev/cu.usbserial-D34N2YUF at 38400 bps, 8N1
15:07:53 - INFO - Sending initial CR to get ODT's attention...
15:07:53 - INFO - Uploading loader program...
15:07:54 - INFO - Starting loader program...
15:07:55 - INFO - Sending binary data...
Uploading: 100%|████████████████████████████████████████████████████████████████████████| 70/70 [00:00<00:00, 2934.63bytes/s]
15:07:55 - INFO - Waiting for loader to complete...
15:07:55 - INFO - Upload complete
(.venv) peter@MacBook-Air-von-Peter Fig-Forth %
After that you need to connect the TU-58 emulator to the second serial interface with the appropriate image
peter@MacBook-Air-von-Peter bootblock % tu58fs -dbg -v -p /dev/cu.usbserial-1420 -b 38400 -s 10M -d 0 w tu58figforth-1.3.3.1.dsk
[13:36:04 info] Unit 0 read/write fmt=none size=10240KB=20480 blocks, img file="tu58figforth-1.3.3.1.dsk"
[13:36:04 info] tu58fs - DEC TU58 tape emulator with File Sharing v1.3.0
(compile May 18 2025 15:24:13)
[13:36:04 info] (C) 2017 Joerg Hoppe <j_hoppe@t-online.de>,
(C) 2005-2017 Don North <ak6dn@mindspring.com>,
(C) 1984 Dan Ts'o <Rockefeller University>
[13:36:04 info] Using serial port /dev/cu.usbserial-1420 at 38400 baud with 8N1 format.
[13:36:04 info] TU58 emulation start
[13:36:04 info] R restart, S toggle send init, V toggle verbose, D toggle debug, Q quit
[13:36:04 info] emulator started
............................[13:36:07 info] unit 0 sync
............................[13:36:10 info] unit 0 sync
.............................[13:36:13 info] unit 0 sync
...........................[13:36:16 info] unit 0 sync d
and last you reconnect to the console and start the previously loaded bootstrap loader, which will boot your system from the first unit of the TU-58 emulator.
@140000g
FIG-FORTH V 1.3.3
5 5 5 * * . 125 OK
1 list
SCR # 1
0 ( LOAD SCREEN)
1 DECIMAL
2 1 WARNING ! ( GET ERR MSGS, NOT #S)
3
4 CR ." LOADING EDITOR... " 6 LOAD 7 LOAD 8 LOAD 9 LOAD
5 CR ." LOADING ASSEMBLER... " 10 LOAD 11 LOAD 12 LOAD 13 LOAD
6 14 LOAD 15 LOAD
7 CR ." LOADING STRING PACKAGE... " 19 LOAD 20 LOAD 21 LOAD
8 22 LOAD
9 CR ." LOADING STRING EXTENSIONS... " 18 LOAD 23 LOAD
10 CR
11 : BYE FLUSH CR ." LEAVING FORTH. HAVE A GOOD DAY." CR BYE ;
12 CR
13
14
15 --> ( CONTINUE ON NEXT SCREEN )
OK
Building TU-58 Standalone Forth
We assume that you have installed all the previously described tools.
Setup
Use the zip file and unpack at your preferred location
peter@MacBook-Air-von-Peter bootblock % tree ..
..
├── bootblock
│ ├── boot1.bin
│ ├── boot1.lst
│ ├── boot1.mac
│ ├── boot1.obj
│ ├── ddboot5.bin
│ ├── ddboot5.lst
│ ├── ddboot5.mac
│ ├── ddboot5.obj
│ ├── Makefile
│ └── obj2bin.pl
├── disks
│ ├── rl02_figforth-1.3.3.1.dsk
│ ├── rl02empty.img
│ ├── rt11v57dl4.img
│ └── rx01_figforth-1.3.2.dsk
├── README.txt
└── src
├── FORTH.BIN
├── FORTH.COM
├── FORTH.LDA
├── FORTH.LST
├── FORTH.MAC
├── Makefile
├── pdp11.ini
├── rl02source.img
├── rl02zero.img
├── rt11v57dl4.img
└── strtxmcom.txt
4 directories, 26 files
peter@MacBook-Air-von-Peter bootblock %
Assemble Forth
Change to the src directory and assemble the source.
peter@MacBook-Air-von-Peter src % make forth
cp ../disks/rl02empty.img rl02source.img
pdpfs -i rl02source.img cp ./FORTH.MAC FORTH.MAC
pdpfs -i rl02source.img cp ./FORTH.COM FORTH.COM
pdp11
PDP-11 simulator V3.12-3
RT-11XM V05.07
.IND DL1:FORTH.COM
.MACRO /LIST:DL1:FORTH.LST /OBJECT:DL1:FORTH.OBJ /ALLOCATE:400 DL1:FORTH.MAC
.LINK /LDA /EXECUTE:DL1:FORTH.LDA DL1:FORTH.OBJ
.BOOT DL2:/FOREIGN
HALT instruction, PC: 000004 (HALT)
Goodbye
pdpfs -i rl02source.img cp FORTH.LST ./FORTH.LST
FORTH.LST -> ./FORTH.LST... Successfully copied 345 blocks (176640 bytes)
pdpfs -i rl02source.img cp FORTH.LDA ./FORTH.LDA
FORTH.LDA -> ./FORTH.LDA... Successfully copied 16 blocks (8192 bytes)
lda2bin < FORTH.LDA > FORTH.BIN
Memory range is [001000, 016413]
Execution begins at 001000
perl -pi -e 's/\r//g' FORTH.LST
peter@MacBook-Air-von-Peter src %
Note that there is a subtle difference between the RL02 images rt11v57dl4.img
in the src and disks directories. The image in the disks directory has
still the original STRTXM.COM command file from the distribution disk where the
image in src has a single command IND DL1:FORTH.COM to start assembling
the Forth source code and then boots into the zeroized RL02 image which will
stop the emulation.
Bootblock
The bootblock is just a modified boot block from a bootable TU-58 RT-11 image. The bootblock makes the following assumptions
- The TU-58 image uses that standard RT-11 settings as the other stand alone Forth for RX01 and RL02 that is it uses exactly 4 directory segments
- FORTH.DAT is the first file on the RT-11 volume, i.e. it starts with block 14.
- FORTH.BIN is saved as screens #40-47 in FORTH.DAT
First you need to assemble and convert the source code into a bootblock.
To create the bootstrap loader and the boot block change to the directory
bootblock and assemble the bootstrap and boot block
peter@MacBook-Air-von-Peter bootblock % make ddboot
macro11 ddboot5.mac -o ddboot5.obj -l ddboot5.lst
perl obj2bin.pl --raw --rt11 --outfile=ddboot5.bin ddboot5.obj
peter@MacBook-Air-von-Peter bootblock % make boot1
macro11 boot1.mac -o boot1.obj -l boot1.lst
perl obj2bin.pl --raw --rt11 --outfile=boot1.bin boot1.obj
peter@MacBook-Air-von-Peter bootblock %
As a result you will have a assembler listing and the binary boot block
peter@MacBook-Air-von-Peter bootblock % ls -l boot1.*
-rw-r--r--@ 1 peter staff 512 Jun 9 14:07 boot1.bin
-rw-r--r--@ 1 peter staff 22260 Jun 9 14:07 boot1.lst
-rw-r--r--@ 1 peter staff 10498 May 24 16:28 boot1.mac
-rw-r--r--@ 1 peter staff 511 Jun 9 14:07 boot1.obj
peter@MacBook-Air-von-Peter bootblock %
Here is the listing as a reference.
1 ;+
2 ; Boot Block for TU-58 to load Forth
3 ;
4 ; History of saved versions
5 ; v1-0 First successfull assembly
6 ; v1-1 First boot
7 ; v1-2 First stable version
8 ; v1-3 Initial Version with no RT-11 directory
9 ;
10 ; Change Blog
11 ; 2025-05-21 Peter Schranz
12 ; Compact the code an no longer follow the structure of RT-11
13 ; boot code, there is not need for that, instead we now fix
14 ; the data area for start values and vectors. FORTH only needs
15 ; a read and a write SCREEN routine so we first try to fit
16 ; both into the boot block.
17 ;
18 ; 2025-05-24 Peter Schranz
19 ; We now follow the RX01 disk format of stand-alone FORTH we use
20 ; a RT-11 formatted volume with exactly 4 directory segments which
21 ; means data starts at LBN 14.
22 ; I used SIMH with the TU-58 image attached to DU: as the TU-58
23 ; image is like any MSCP disk just a block-by-block copy of disk.
24 ; On the RX01 floppy FORTH.DAT is 150kbytes in size and must be
25 ; the first file on the RT-11 volume. FORTH.BIN is part of FORTH.DAT
26 ; and starts a block 79 of FORTH.DAT. As FORTH.DAT starts at LBN 14.
27 ; on the RT-11 volume, FORTH.DAT starts at LBN 93.
28 ;
29 ; These will just be copied to an empty TU-58 Image
30 ;
31 ; boot1.bin BLOCK 0
32 ; forth.bin BLOCK 93 & ff
33 ;
34 ; 2025-06-01 Peter Schranz
35 ; Increased the standard TU-58 image size to 10Mbyte, which
36 ; is the same size as RL02. Most TU-58 emulators support such
37 ; large image and the TU-58 protocol would even support 32Mbyte
38 ; large images. With this the format of the TU-58 image is exactly
39 ; the same as the RL02 image for the RL02 stand alone Forth and
40 ; we also use the same forth file. This makes it possible to
41 ; attach the image in simh as RL0 and copy files if needed.
42 ; Also we use the FORTH.DAT from the RL02 stand alone forth which
43 ; supports 8192. screens.
44 ;
45 ; 2025-06-23 Peter Schranz
46 ; The size of FORTH.BIN is now written into a file named forth.size
47 ; when assembling the FORTH source and is now included in this source
48 ; file to define the length of the image to load from disk.
49 ;
50 ; We have one image created and initialized under RT-11 using the PDP-11/Hack
51 ; so we already have a valid Home block
52 ;
53 ; tu58init.dsk Empty Image
54 ;
55 ; 1. Assemble and convert the boot block
56 ; ../src/macro11 boot1.mac -o boot1.obj -l boot1.lst
57 ; perl ../obj2bin-master/obj2bin.pl --raw --rt11 --outfile=boot1.bin boot1.obj
58 ;
59 ; 2. Make a copy of the image
60 ; cp tu58forthdat.dsk tu58dat.dsk
61 ;
62 ; 3. Copy the boot loader
63 ; dd conv=notrunc if=boot1.bin of=tu58dat.dsk
64 ;
65 ; 4. Copy the forth binary to the image
66 ; dd conv=notrunc if=forth.bin seek=93 bs=512 of=tu58dat.dsk
67 ;-
68 ;+
69 ; Definitions
70 ;-
71 000010 DDCNT = 10 ; Boot Read Retry Count
72 000001 CS$BRK = 1 ; Break bit mask in XCSRS
73
74 000001 R$DATA = 1 ; TU-58 DATA Packet Flag
75 000002 R$MSG = 2 ; TU-58 Command Flag
76 000004 R$INIT = 4 ; TU-58 INIT Flag
77 000020 R$CONT = 20 ; TU-58 Continue Flag
78
79 000002 R$$RED = 2 ; TU-58 Read Command
80 000003 R$$WRT = 3 ; TU-58 Write Command
81
82 000012 R$MSIZ = 10. ; TU-58 Command Packet Size
83
84 176500 DD$CSR = 176500 ; SLU-A CSR Address (DLV11)
85 042104 B$DNAM = 400*'D+'D ; TU-58 Boot Device NAme
86 001000 B$BOOT = 1000 ; BOOT2/FORTH.BIN Entry Point
87 001000 L$STCK = 1000 ; Our Stack
88 001000 L$ADDR = 1000 ; Load Address of BOOT2/FORTH.BIN
89 000135 L$BLK = 93. ; Location of FORTH.BIN on Volume
90 ; Size of FORTH.BIN
91 .include ../src/forth.size
1 015414 L$SIZE = 6924.
1
92
93 .asect
94 .SBTTL BOOTSTRAP READ ROUTINE
95 ;
96 ; Boot Blocks should always start with a NOP, many hardware boot
97 ; options on device IO cards rely on this fact.
98 ;
99 000000 . = 0 ; The real boot block starts at 0
100 000000 000240 DDBOOT: NOP ; DDBOOT=0
101 000002 000416 BR BOOT ; Skip Fixed Area
102 ;+
103 ; Fixed Data Area for Next Boot Stage
104 ;
105 ;-
106 000004 000000 B$DEVN: .WORD 0 ; Device Name
107 000006 000000 B$DEVU: .WORD 0 ; Device Unit
108 000010 000000 B$READ: .WORD 0 ; Address of Read Routine for BOOT2
109 000012 000000 B$WRIT: .WORD 0 ; Address of Write Routine for BOOT2
110 000014 000000 B$FLAG: .WORD 0 ; Status Flags
111 000001 BF$SUC = 1 ; Last Operation was successfull
112 ;
113 000040 . = DDBOOT+40
114 ;+
115 ; 2nd Stage Boot Loader
116 ;-
117 000040 012706 001000 BOOT: MOV #L$STCK,SP ; Setup Stack
118 000044 010037 000006 MOV R0,@#B$DEVU ; Store Unit in Message, R0 is setup by bootstrap
119 000050 005037 000014 CLR @#B$FLAG ;
120 000054 010046 MOV R0,-(SP) ; Save Unit
121 000056 012700 000135 MOV #L$BLK,R0 ; R0=Starting Block Nbr
122 000062 012701 006606 MOV #L$SIZE/2,R1 ; R1=Word Count
123 000066 012702 001000 MOV #L$ADDR,R2 ; R2=Address
124 000072 004767 000122' CALL READ
125 000076 012737 000122 000010 MOV #READ-DDBOOT,@#B$READ
126 000104 012737 042104 000004 MOV #B$DNAM,@#B$DEVN
127 000112 012637 000006 MOV (SP)+,@#B$DEVU
128 000116 000137 001000 JMP @#B$BOOT ; Jump to next boot stage
129 ;+
130 ; Read Routine
131 ;
132 ; R0=Starting Block Nbr
133 ; R1=Word Count
134 ; R2=Address
135 ;-
136 000122 012767 000010 000506' READ: MOV #DDCNT,RTRCNT ; Setup Retry Count
137 000130 013767 000006 000474' MOV @#B$DEVU,DDUNIT ; Save Unit to Message Area
138 000136 006301 ASL R1 ; Convert Word Count to Block Count
139 000140 010067 000502' MOV R0,DDBLK ; Save Block in Message Area
140 000144 010167 000500' MOV R1,DDBTCT ; Save Byte Count in Message Area
141 000150 010246 BRESTR: MOV R2,-(SP) ; Save Address
142 000152 012700 176504 MOV #DD$CSR+4,R0 ; XCSR->R0
143 000156 032737 000001 000014 BIT #BF$SUC, @#B$FLAG ;
144 000164 001025 BNE BRDPKT
145 000166 052710 000001 BIS #CS$BRK,(R0) ; BRK
146 000172 012703 MOV (PC)+,R3 ;
147 ;
148 ; The DLV11 hardware implements the break signal as a simple gate to set the
149 ; serial output of the UART to low. For a break to be recognised SO must be
150 ; low longer than the duration of the transmission of a character. The UART
151 ; function is completely independant of the BRK bit, therefore when sending
152 ; a character the UART will still shift the data using the baud-rate
153 ; configured. We now send a word, i.e. two bytes during the time BRK has
154 ; been activated, and then wait until the second character has been
155 ; transferred from the transmit buffer to the transmit register. Because the
156 ; transmitter is buffered the characters we send must be selected carefully,
157 ; because the moment TBRE after we wrote the second character only means
158 ; that the second character has been transferred to the transmit register
159 ; and the buffer register is empty again, i.e. the second character is still
160 ; being sent. When we de-assert break serial output will reflect the current
161 ; state of the bit just being sent at this moment. This could result in a
162 ; character that breaks the TU-58 protocol. To avoid this situation we send
163 ; the character 0377.
164 ;
165 ; When writing the second character TBRE stays cleared until the buffer
166 ; register has been transferred to the transmit register and at least one
167 ; transmit cycle (the time it takes for one bit) later only TBRE is set,
168 ; which is when the start bit of the second character is being shifted out
169 ; which means 10 or more transmit cycles after BRK has been asserted.
170 ; asserted. However the 10th bit is supposed to be the stop bit and hence
171 ; the transmitter will detect a framing error. When we now deassert the BRK
172 ; bit it will be any time during the transmission of the second character,
173 ; 0377 will then be seen by the receiver as a single rising edge and he will
174 ; not detect this as a character, for the receiver it's just a break of 11
175 ; or more SPACE bits, hence a BRK.
176 ;
177 000174 177777 .WORD 177777 ; Send two 0377 characters
178 000176 004767 000452' CALL BCHROS ; Write Word
179 000202 105710 CONRD1: TSTB @R0 ; Wait for transmit buffer register empty
180 000204 100376 BPL CONRD1 ; before ending BREAK
181 000206 042710 000001 BIC #CS$BRK,@R0 ; ~BRK
182 000212 012703 MOV (PC)+,R3
183 000214 004 004 .BYTE R$INIT,R$INIT ; Send two Init Commands to sync TU-58
184 000216 004767 000452' CALL BCHROS
185 000222 005760 177776 TST -2(R0) ; Remove potential input character
186 000226 004767 000432' CALL BICHR ;
187 000232 120327 000020 CMPB R3,#R$CONT ; Expect CONT from TU-58
188 000236 001045 BNE BFATAL
189 ;
190 ; The TU-58 now sends data packets in chunks of up to 128 bytes, when the
191 ; number of bytes requested has been sent, the TU-58 sends an end packet
192 ; !!Note how the packet size counter is only using the lower byte of R4!!
193 ;
194 000240 004767 000372' BRDPKT: CALL BCMD
195 000244 004767 000424' 1$: CALL BICHP2 ; Get First Word, R1=Received Word, R3=Last Byte
196 000250 110304 MOVB R3,R4 ; Get Message Length (bytes)
197 000252 106004 RORB R4 ; Convert to Word
198 000254 010146 MOV R1,-(SP) ; Initialise Checksum
199 000256 120127 000001 CMPB R1,#R$DATA ; Is it a data packet?
200 000262 001012 BNE BEND ; No, probably/hopefully an end packet
201 000264 004715 2$: CALL (R5) ; Get Word (note BICHP2 sets up address in R5)
202 000266 010122 MOV R1,(R2)+ ; Store to Buffer
203 000270 060116 ADD R1,(SP) ; Update Checksum
204 000272 005516 ADC (SP)
205 000274 105304 DECB R4 ; More to read?
206 000276 003372 BGT 2$ ; Yes
207 000300 004715 CALL (R5) ; Get Checksum
208 000302 020126 CMP R1,(SP)+ ; =equal=
209 000304 001022 BNE BFATAL ; Oh no we have a read error
210 000306 000756 BR 1$ ; Fetch next Data Packet
211 ;
212 ; We now expect the End Message
213 ;
214 000310 004715 BEND: CALL (R5) ; Get Word
215 000312 105703 TSTB R3 ; Error?
216 000314 100415 BMI BOTH ; (BOTH clean-up both stack words)
217 000316 060116 2$: ADD R1,(SP) ; Update Check Sum
218 000320 005516 ADC (SP) ;
219 000322 004715 CALL (R5) ; Get another word
220 000324 105304 DECB R4 ; More to read?
221 000326 003373 BGT 2$ ; Yes
222 000330 020126 CMP R1,(SP)+ ; =equal=
223 000332 001007 BNE BFATAL ; Oh no we have a read error
224 000334 012602 MOV (SP)+,R2 ; Drop Address
225 000336 000241 CLC ; Success
226 000340 052737 000001 000014 BIS #BF$SUC, @#B$FLAG ;
227 000346 000207 RETURN ; Done
228
229 000350 005726 BOTH: TST (SP)+ ; Checksum
230 000352 012602 BFATAL: MOV (SP)+,R2 ; Address
231 000354 042737 000001 000014 BIC #BF$SUC, @#B$FLAG ;
232 000362 005367 000506' DEC RTRCNT
233 000366 001270 BNE BRESTR
234 000370 000447 BR BIOERR
235
236 .SBTTL SUB ROUTINES
237 ;+
238 ; General sub-routines used by BOOT, READ and WRITE
239 ;
240 ;
241 ;-
242
243 ;
244 ; Send Read Command Packet
245 ;
246 000372 012704 000504 BCMD: MOV #B$CHK-DDBOOT,R4 ;
247 000376 005014 CLR (R4) ; Clear Checksum, R4=Pointer to Checksum
248 000400 012705 000470 MOV #B$PKT-DDBOOT,R5 ; R5=Pointer to Packet
249 000404 012503 1$: MOV (R5)+,R3 ; Get Next Word from Packet
250 000406 060314 ADD R3,(R4) ;
251 000410 005514 ADC (R4) ; Update Checksum
252 000412 004767 000452' CALL BCHROS ; Write Byte
253 000416 020504 CMP R5,R4 ; until we have a complete message packet
254 000420 101771 BLOS 1$
255 000422 000207 RETURN
256
257 .SBTTL BYTE INPUT ROUTINES (BOOT)
258 ;+
259 ; Byte Input Routines
260 ;
261 ; This routine has two entry to read either a word or a byte
262 ;
263 ; Input
264 ; R0 XCSR
265 ; Output
266 ; R1 Word only valid when a word has been read
267 ; R3 Last Byte
268 ; R5 Address to word input route for later use
269 ;
270 ;-
271 000424 010705 BICHP2: MOV PC,R5 ; Word Input Character
272 000426 005001 CLR R1 ; Initialise Return Word
273 000430 004717 CALL @PC ; Call Byte input and Return to Byte Input -> Word Input
274 000432 105760 177774 BICHR: TSTB -4(R0) ; Byte Input Character, Received Character?
275 000436 100375 BPL BICHR ; Nope
276 000440 116003 177776 MOVB -2(R0),R3 ; Get Character
277 000444 150301 BISB R3,R1 ; Copy to output register (only byte)
278 000446 000301 SWAB R1 ; Prepare for High Byte in case of Word Read
279 000450 000207 RETURN ; Return to Caller
280 ;+
281 ;
282 ; Write one word to the device
283 ; R0 XCSR
284 ; R3 Word
285 ;-
286 .SBTTL BYTE OUTPUT ROUTINES (BOOT)
287 000452 004717 BCHROS: CALL @PC ; CALL 1$
288 000454 105710 1$: TSTB @R0 ; Transmit Buffer Register Empty?
289 000456 100376 BPL 1$ ; nope
290 000460 110360 000002 MOVB R3,2(R0) ; Write Byte
291 000464 000303 SWAB R3 ; Swap byte
292 000466 000207 RETURN ; Return to caller
293 ;
294 ; Command Message Packet
295 ;
296 000470 002 012 002 B$PKT: .BYTE R$MSG,R$MSIZ,R$$RED,0 ; Message, Size, Opcode, Modifier
000473 000
297 000474 000000 DDUNIT: .WORD 0 ; Unit
298 000476 000 000 .BYTE 0,0 ; Sequence
299 000500 000000 DDBTCT: .WORD 0 ; Byte Count
300 000502 000000 DDBLK: .WORD 0 ; Block Nbr
301 000504 000000 B$CHK: .WORD 0 ; Checksum
302 000506 000000 RTRCNT: .WORD 0
303
304 000510 000000 BIOERR: HALT
305 ;+++.DREND DD
306 000776 . = 776 ; Top of boot block area used as stack
307 000776 177777 .WORD 177777 ; Last Word should be -1
308 .END
309
309
The file forth.size included in the source code of the boot block is generated
when the source code for the TU-58 stand alone Forth is assembled as the size
the boot block loads must match the size of FORTH.BIN.
Now you can create the TU-58 Forth image
peter@MacBook-Air-von-Peter bootblock % make forthtu58
macro11 boot1.mac -o boot1.obj -l boot1.lst
perl obj2bin.pl --raw --rt11 --outfile=boot1.bin boot1.obj
cp ../disks/rl02_figforth-1.3.3.1.dsk tu58figforth-1.3.3.1.dsk
chmod +w tu58figforth-1.3.3.1.dsk
dd conv=notrunc if=boot1.bin of=tu58figforth-1.3.3.1.dsk
1+0 records in
1+0 records out
512 bytes transferred in 0.000023 secs (22260870 bytes/sec)
dd conv=notrunc if=../src/FORTH.BIN seek=93 bs=512 of=tu58figforth-1.3.3.1.dsk
13+1 records in
13+1 records out
6924 bytes transferred in 0.000221 secs (31330317 bytes/sec)
peter@MacBook-Air-von-Peter bootblock %
which is the image to be used with the TU-58 emulator as already show before
peter@MacBook-Air-von-Peter bootblock % tu58fs -dbg -v -p /dev/cu.usbserial-1420 -b 38400 -s 10M -d 0 w tu58figforth-1.3.3.1.dsk
[13:36:04 info] Unit 0 read/write fmt=none size=10240KB=20480 blocks, img file="tu58figforth-1.3.3.1.dsk"
[13:36:04 info] tu58fs - DEC TU58 tape emulator with File Sharing v1.3.0
(compile May 18 2025 15:24:13)
[13:36:04 info] (C) 2017 Joerg Hoppe <j_hoppe@t-online.de>,
(C) 2005-2017 Don North <ak6dn@mindspring.com>,
(C) 1984 Dan Ts'o <Rockefeller University>
[13:36:04 info] Using serial port /dev/cu.usbserial-1420 at 38400 baud with 8N1 format.
[13:36:04 info] TU58 emulation start
[13:36:04 info] R restart, S toggle send init, V toggle verbose, D toggle debug, Q quit
[13:36:04 info] emulator started
............................[13:36:07 info] unit 0 sync
............................[13:36:10 info] unit 0 sync
.............................[13:36:13 info] unit 0 sync
...........................[13:36:16 info] unit 0 sync
Remark
This stand alone Forth for TU-58 should actually work with any PDP-11 that has a console, a second serial DL11 at its standard base address 1765008, 32kbyte of RAM and can be booted from a TU-58. If you need to enter a TU-58 bootstrap loader manually make sure you use an address higher than 10008. If your PDP-11 has enough memory, use an address above 100008 because then you can reuse the bootstrap loader as FORT uses only the first 32kbyte.
Downloads
Archive of the development tree including binaries and disk imagesUseful links
https://www.forth.org/fig-forth/contents.html http://www.stackosaurus.com/figforth.html
https://www.forth.org/fig-forth/fig-forth_PDP-11_Users_Guide.pdf