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 as do many bootable images as 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.
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 BRK 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 work perfectly.
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 BRK 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 a 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.
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 ; We have one image created and initialized under RT-11 using the PDP-11/Hack
35 ; so we already have a valid Home block
36 ;
37 ; tu58init.dsk Empty Image
38 ;
39 ; 1. Assemble and convert the boot block
40 ; ../src/macro11 boot1.mac -o boot1.obj -l boot1.lst
41 ; perl ../obj2bin-master/obj2bin.pl --raw --rt11 --outfile=boot1.bin boot1.obj
42 ;
43 ; 2. Make a copy of the image
44 ; cp tu58forthdat.dsk tu58dat.dsk
45 ;
46 ; 3. Copy the boot loader
47 ; dd conv=notrunc if=boot1.bin of=tu58dat.dsk
48 ;
49 ; 4. Copy the forth binary to the image
50 ; dd conv=notrunc if=forth.bin seek=93 bs=512 of=tu58dat.dsk
51 ;-
52 ;+
53 ; Definitions
54 ;-
55 000010 DDCNT = 10 ; Boot Read Retry Count
56 000001 CS$BRK = 1 ; Break bit mask in XCSRS
57
58 000001 R$DATA = 1 ; TU-58 DATA Packet Flag
59 000002 R$MSG = 2 ; TU-58 Command Flag
60 000004 R$INIT = 4 ; TU-58 INIT Flag
61 000020 R$CONT = 20 ; TU-58 Continue Flag
62
63 000002 R$$RED = 2 ; TU-58 Read Command
64 000003 R$$WRT = 3 ; TU-58 Write Command
65
66 000012 R$MSIZ = 10. ; TU-58 Command Packet Size
67
68 176500 DD$CSR = 176500 ; SLU-A CSR Address (DLV11)
69 042104 B$DNAM = 400*'D+'D ; TU-58 Boot Device NAme
70 001000 B$BOOT = 1000 ; BOOT2/FORTH.BIN Entry Point
71 001000 L$STCK = 1000 ; Our Stack
72 001000 L$ADDR = 1000 ; Load Address of BOOT2/FORTH.BIN
73 000135 L$BLK = 93. ; Location of FORTH.BIN on Volume
74 015414 L$SIZE = 6924. ; Size of FORTH.BIN
75
76 .asect
77 .SBTTL BOOTSTRAP READ ROUTINE
78 ;
79 ; Boot Blocks should always start with a NOP, many hardware boot
80 ; options on device IO cards rely on this fact.
81 ;
82 000000 . = 0 ; The real boot block starts at 0
83 000000 000240 DDBOOT: NOP ; DDBOOT=0
84 000002 000416 BR BOOT ; Skip Fixed Area
85 ;+
86 ; Fixed Data Area for Next Boot Stage
87 ;
88 ;-
89 000004 000000 B$DEVN: .WORD 0 ; Device Name
90 000006 000000 B$DEVU: .WORD 0 ; Device Unit
91 000010 000000 B$READ: .WORD 0 ; Address of Read Routine for BOOT2
92 000012 000000 B$WRIT: .WORD 0 ; Address of Write Routine for BOOT2
93 000014 000000 B$FLAG: .WORD 0 ; Status Flags
94 000001 BF$SUC = 1 ; Last Operation was successfull
95 ;
96 000040 . = DDBOOT+40
97 ;+
98 ; 2nd Stage Boot Loader
99 ;-
100 000040 012706 001000 BOOT: MOV #L$STCK,SP ; Setup Stack
101 000044 010037 000006 MOV R0,@#B$DEVU ; Store Unit in Message, R0 is setup by bootstrap
102 000050 005037 000014 CLR @#B$FLAG ;
103 000054 010046 MOV R0,-(SP) ; Save Unit
104 000056 012700 000135 MOV #L$BLK,R0 ; R0=Starting Block Nbr
105 000062 012701 006606 MOV #L$SIZE/2,R1 ; R1=Word Count
106 000066 012702 001000 MOV #L$ADDR,R2 ; R2=Address
107 000072 004767 000122' CALL READ
108 000076 012737 000122 000010 MOV #READ-DDBOOT,@#B$READ
109 000104 012737 042104 000004 MOV #B$DNAM,@#B$DEVN
110 000112 012637 000006 MOV (SP)+,@#B$DEVU
111 000116 000137 001000 JMP @#B$BOOT ; Jump to next boot stage
112 ;+
113 ; Read Routine
114 ;
115 ; R0=Starting Block Nbr
116 ; R1=Word Count
117 ; R2=Address
118 ;-
119 000122 012767 000010 000506' READ: MOV #DDCNT,RTRCNT ; Setup Retry Count
120 000130 013767 000006 000474' MOV @#B$DEVU,DDUNIT ; Save Unit to Message Area
121 000136 006301 ASL R1 ; Convert Word Count to Block Count
122 000140 010067 000502' MOV R0,DDBLK ; Save Block in Message Area
123 000144 010167 000500' MOV R1,DDBTCT ; Save Byte Count in Message Area
124 000150 010246 BRESTR: MOV R2,-(SP) ; Save Address
125 000152 012700 176504 MOV #DD$CSR+4,R0 ; XCSR->R0
126 000156 032737 000001 000014 BIT #BF$SUC, @#B$FLAG ;
127 000164 001025 BNE BRDPKT
128 000166 052710 000001 BIS #CS$BRK,(R0) ; BRK
129 000172 012703 MOV (PC)+,R3 ;
130 ;
131 ; The DLV11 hardware implements the break signal as a simple gate to set the
132 ; serial output of the UART to low. For a break to be recognised SO must be
133 ; low longer than the duration of the transmission of a character. The UART
134 ; function is completely independant of the BRK bit, therefore when sending
135 ; a character the UART will still shift the data using the baud-rate
136 ; configured. We now send a word, i.e. two bytes during the time BRK has
137 ; been activated, and then wait until the second character has been
138 ; transferred from the transmit buffer to the transmit register. Because the
139 ; transmitter is buffered the characters we send must be selected carefully,
140 ; because the moment TBRE after we wrote the second character only means
141 ; that the second character has been transferred to the transmit register
142 ; and the buffer register is empty again, i.e. the second character is still
143 ; being sent. When we de-assert break serial output will reflect the current
144 ; state of the bit just being sent at this moment. This could result in a
145 ; character that breaks the TU-58 protocol. To avoid this situation we send
146 ; the character 0377.
147 ;
148 ; When writing the second character TBRE stays cleared until the buffer
149 ; register has been transferred to the transmit register and at least one
150 ; transmit cycle (the time it takes for one bit) later only TBRE is set,
151 ; which is when the start bit of the second character is being shifted out
152 ; which means 10 or more transmit cycles after BRK has been asserted.
153 ; asserted. However the 10th bit is supposed to be the stop bit and hence
154 ; the transmitter will detect a framing error. When we now deassert the BRK
155 ; bit it will be any time during the transmission of the second character,
156 ; 0377 will then be seen by the receiver as a single rising edge and he will
157 ; not detect this as a character, for the receiver it's just a break of 11
158 ; or more SPACE bits, hence a BRK.
159 ;
160 000174 177777 .WORD 177777 ; Send two 0377 characters
161 000176 004767 000452' CALL BCHROS ; Write Word
162 000202 105710 CONRD1: TSTB @R0 ; Wait for transmit buffer register empty
163 000204 100376 BPL CONRD1 ; before ending BREAK
164 000206 042710 000001 BIC #CS$BRK,@R0 ; ~BRK
165 000212 012703 MOV (PC)+,R3
166 000214 004 004 .BYTE R$INIT,R$INIT ; Send two Init Commands to sync TU-58
167 000216 004767 000452' CALL BCHROS
168 000222 005760 177776 TST -2(R0) ; Remove potential input character
169 000226 004767 000432' CALL BICHR ;
170 000232 120327 000020 CMPB R3,#R$CONT ; Expect CONT from TU-58
171 000236 001045 BNE BFATAL
172 ;
173 ; The TU-58 now sends data packets in chunks of up to 128 bytes, when the
174 ; number of bytes requested has been sent, the TU-58 sends an end packet
175 ; !!Note how the packet size counter is only using the lower byte or R4!!
176 ;
177 000240 004767 000372' BRDPKT: CALL BCMD
178 000244 004767 000424' 1$: CALL BICHP2 ; Get First Word, R1=Received Word, R3=Last Byte
179 000250 110304 MOVB R3,R4 ; Get Message Length (bytes)
180 000252 106004 RORB R4 ; Convert to Word
181 000254 010146 MOV R1,-(SP) ; Initialise Checksum
182 000256 120127 000001 CMPB R1,#R$DATA ; Is it a data packet?
183 000262 001012 BNE BEND ; No, probably/hopefully an end packet
184 000264 004715 2$: CALL (R5) ; Get Word (note BICHP2 sets up address in R5)
185 000266 010122 MOV R1,(R2)+ ; Store to Buffer
186 000270 060116 ADD R1,(SP) ; Update Checksum
187 000272 005516 ADC (SP)
188 000274 105304 DECB R4 ; More to read?
189 000276 003372 BGT 2$ ; Yes
190 000300 004715 CALL (R5) ; Get Checksum
191 000302 020126 CMP R1,(SP)+ ; =equal=
192 000304 001022 BNE BFATAL ; Oh no we have a read error
193 000306 000756 BR 1$ ; Fetch next Data Packet
194 ;
195 ; We now expect the End Message
196 ;
197 000310 004715 BEND: CALL (R5) ; Get Word
198 000312 105703 TSTB R3 ; Error?
199 000314 100415 BMI BOTH ; (BOTH clean-up both stack words)
200 000316 060116 2$: ADD R1,(SP) ; Update Check Sum
201 000320 005516 ADC (SP) ;
202 000322 004715 CALL (R5) ; Get another word
203 000324 105304 DECB R4 ; More to read?
204 000326 003373 BGT 2$ ; Yes
205 000330 020126 CMP R1,(SP)+ ; =equal=
206 000332 001007 BNE BFATAL ; Oh no we have a read error
207 000334 012602 MOV (SP)+,R2 ; Drop Address
208 000336 000241 CLC ; Success
209 000340 052737 000001 000014 BIS #BF$SUC, @#B$FLAG ;
210 000346 000207 RETURN ; Done
211
212 000350 005726 BOTH: TST (SP)+ ; Checksum
213 000352 012602 BFATAL: MOV (SP)+,R2 ; Address
214 000354 042737 000001 000014 BIC #BF$SUC, @#B$FLAG ;
215 000362 005367 000506' DEC RTRCNT
216 000366 001270 BNE BRESTR
217 000370 000447 BR BIOERR
218
219 .SBTTL SUB ROUTINES
220 ;+
221 ; General sub-routines used by BOOT, READ and WRITE
222 ;
223 ;
224 ;-
225
226 ;
227 ; Send Read Command Packet
228 ;
229 000372 012704 000504 BCMD: MOV #B$CHK-DDBOOT,R4 ;
230 000376 005014 CLR (R4) ; Clear Checksum, R4=Pointer to Checksum
231 000400 012705 000470 MOV #B$PKT-DDBOOT,R5 ; R5=Pointer to Packet
232 000404 012503 1$: MOV (R5)+,R3 ; Get Next Word from Packet
233 000406 060314 ADD R3,(R4) ;
234 000410 005514 ADC (R4) ; Update Checksum
235 000412 004767 000452' CALL BCHROS ; Write Byte
236 000416 020504 CMP R5,R4 ; until we have a complete message packet
237 000420 101771 BLOS 1$
238 000422 000207 RETURN
239
240 .SBTTL BYTE INPUT ROUTINES (BOOT)
241 ;+
242 ; Byte Input Routines
243 ;
244 ; This routine has two entry to read either a word or a byte
245 ;
246 ; Input
247 ; R0 XCSR
248 ; Output
249 ; R1 Word only valid when a word has been read
250 ; R3 Last Byte
251 ; R5 Address to word input route for later use
252 ;
253 ;-
254 000424 010705 BICHP2: MOV PC,R5 ; Word Input Character
255 000426 005001 CLR R1 ; Initialise Return Word
256 000430 004717 CALL @PC ; Call Byte input and Return to Byte Input -> Word Input
257 000432 105760 177774 BICHR: TSTB -4(R0) ; Byte Input Character, Received Character?
258 000436 100375 BPL BICHR ; Nope
259 000440 116003 177776 MOVB -2(R0),R3 ; Get Character
260 000444 150301 BISB R3,R1 ; Copy to output register (only byte)
261 000446 000301 SWAB R1 ; Prepare for High Byte in case of Word Read
262 000450 000207 RETURN ; Return to Caller
263 ;+
264 ;
265 ; Write one word to the device
266 ; R0 XCSR
267 ; R3 Word
268 ;-
269 .SBTTL BYTE OUTPUT ROUTINES (BOOT)
270 000452 004717 BCHROS: CALL @PC ; CALL 1$
271 000454 105710 1$: TSTB @R0 ; Transmit Buffer Register Empty?
272 000456 100376 BPL 1$ ; nope
273 000460 110360 000002 MOVB R3,2(R0) ; Write Byte
274 000464 000303 SWAB R3 ; Swap byte
275 000466 000207 RETURN ; Return to caller
276 ;
277 ; Command Message Packet
278 ;
279 000470 002 012 002 B$PKT: .BYTE R$MSG,R$MSIZ,R$$RED,0 ; Message, Size, Opcode, Modifier
000473 000
280 000474 000000 DDUNIT: .WORD 0 ; Unit
281 000476 000 000 .BYTE 0,0 ; Sequence
282 000500 000000 DDBTCT: .WORD 0 ; Byte Count
283 000502 000000 DDBLK: .WORD 0 ; Block Nbr
284 000504 000000 B$CHK: .WORD 0 ; Checksum
285 000506 000000 RTRCNT: .WORD 0
286
287 000510 000000 BIOERR: HALT
288 ;+++.DREND DD
289 000776 . = 776 ; Top of boot block area used as stack
290 000776 177777 .WORD 177777 ; Last Word should be -1
291 .END
292
292
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.