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.

ROI ISA Card Hans Hübner

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.

DCJ11 SBC Hans Hübner

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

SBC with Optional Logic

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 images

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