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 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

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 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.

Downloads

Archive of the development tree including binaries and disk images