Programming Forth on the Jupiter ACE

What’s the Jupiter ACE?

The Jupiter ACE was an early 1980s home computer that had similarities to the Sinclair ZX-81, especially in the bottom input entry

Entering commands and code on the bottom line of the Jupiter ACE (using EightyOne emulator), much like the ZX-80 and ZX-81

in the keyboard layout (By the way, symbol shift can be shift to right-shift in EightyOne emulator or is otherwise the right CTRL key… also note that the double quote, /, *, and others are reached by the symbol shift + a letter key.)

Keyboard Layout view from SpudACE emulator

and the extremely limited 1KB of base RAM.

What’s Forth?

Forth is a stack-based programming language that relies on a data stack and reverse Polish notation, much like the HP calculators are known for. A basic example would be 6 2 * which would perform the operation 6 * 2 and push the value 12 back onto the data stack. If you wanted to actually retrieve/pop the result off the stack, you would issue a 6 2 * . sequence, in which the . would pop the last value and return/print it.

Special considerations for the Jupiter ACE’s Forth

The data stack on the Jupiter ACE assumes 16-bit values, and the default data type assumed is a signed integer, so you have values from -32768 to 32767 that are allowed per single stack slot:

256 * 256 is 0 (because that would require 17 bits. 128 * 256 is -32768. 128 * 256 – 1 is 32767

Floating point numbers are possible, but they require two stack slots, so you have on operate on two consecutive stack locations at the same time to work with floating point numbers. Another challenge with the Jupiter ACE’s Forth implementation is that you’re missing some advanced math functions and you have to write your own. The following is an annotated version (the comments are in parentheses) of the SIN routine found on page 93 from the first edition Jupiter ACE manual found here:

    (x - sine of x)
    2DUP 2DUP 2DUP F* FNEGATE ([x,x,-x*x])
    2ROT 2ROT ([-x*x,x,x])
    27 2 ([-x*x,x,x, 27 2])

    DO (initial value of 2, limit 27)
        6 PICK 6 PICK ([-x*x,x,x,-x*x])
        F* I I 1+ * ([-x*x,x,-x^3,6])
        UFLOAT F/ ([-x*x,x,-x^3/6.])
        2DUP 2ROT F+ 2SWAP ([-x*x,-x^3/6.+x,-x^3/6.])

        (3 down the stack of floats is the multiplier of the
        numerator terms for the Newton method, 2 down the
        stack is the total sum, top of the stack is the last
        term, and the counter (I) tracks the denominator 
        multipliers (I and I+1))

        2 (add 2 to the stack for the loop increment)
    2DROP 2SWAP 2DROP ([x-x^3/6. ... etc])

2DUP, 2ROT, and 2SWAP are methods that are defined on previous pages to basically work with floats on the stack in the same way as 16-bit integers. The algorithm is Newton’s method to estimate a sine, through 14 terms of the method. If you map out the stack locations by hand, you can follow how the method is making creative use of the top 3 – 4 (6 – 8) stack positions to keep the progress, the multiplicand of the numerator of the terms, and the current term. However, due to floating point representation, this information is harder to trace through in the emulator because you will see 16-bit values in the data stack.

Trying for yourself

I’ve tried out the SpudACE emulator (strictly Jupiter ACE) and the EightyOne emulator (Sinclair, Timex, and others also emulated) which can be found on this Jupiter ACE Emulators for Windows page. I’ve found the EightyOne emulator a bit more stable and usable. You’ll want to make sure to mute your sound when saving to tape, because sound *may* output when saving to your virtual tape. Watch a demonstration here and see the source code for the demo in this folder.

Plotting Characters to the Commodore 128 80-column (8563) chip.

You can find the below source code at and watch here for demonstration of the run time vs. the TRS-80 Model 16 emulator (a computer for which I never realized had a graphics mode when I had access to one). The BASIC and disassembly is also available on page 312 of the Commodore 128: Programmer’s Reference Guide… but beware, the OCR versions translate the “1”s occasionally to lowercase “l”s (which wouldn’t exist in a Commodore program listing unless all lowercase) and the “O”s to “0”s (but inconsistently).

Why is this a post?

If you’re here, you probably wrote some Commodore screen plotting code in which the screen was mappable via POKE commands for the contiguous video RAM (22×23 for VIC-20, 80×25 for Commodore 64/128) or by DRAW commands in graphics mode for the Commodore 128. (Also bitmap POKEable for the Commodore 64, if I recall correctly… haven’t sorted out the VIC-20’s situation yet… that’s another day.)

Well, the 80-column MOS 8563 chip has its own video ram. And given the 16-bit address space (yeah, there are BANKs to switch for the 128), it’s not readily addressable in contiguous address pointer space. Actually, it’s NOT ADDRESSABLE AT ALL by address space. Instead, there’s a convoluted process to write to it. (Thanks to this video for helping me “get it” fully)

The process

  • High memory byte write
    • Store video register number 18 for the 8563 high memory address byte in register X (register X = 18)
    • Store 8563 high memory address in register A (register A = [8563 address] / 256)
    • Write value of X to location $D600 (location 54784 in decimal)
    • Wait until $D600‘s high bit flips (instructions: do a bit test, check for “bit plus” (sign bit is bit 7, so loop if it’s still zero)
    • Write value of A to location $D601 (location 54785 in decimal)
  • Low memory byte write
    • Store video register number 19 for the 8563 low memory address byte in register X (register X = 19)
    • Store 8563 high memory address in register A (register A = [8563 address] AND 255)
    • Write value of X to location $D600
    • Wait until $D600‘s high bit flips
    • Write value of A to location $D601
  • Write the actual content!
    • Store video register number 31 in register X to signal that you want to interact with data in the address set by the last two steps.
    • Store byte you want to write in register A
    • Write value of X to location $D600
    • Wait until $D600‘s high bit flips
    • Write value of A to location $D601

The code

This can probably be done with POKE and PEEK, but this process is tedious enough for machine code. You can assemble it this way:

0180c 8e 00 d6  stx $d600
0180f 2c 00 d6  bit $d600
01812 10 fb     bpl $180f
01814 8d 01 d6  sta $d601
01817 60        rts

Or store it in data and have your basic routine load it into memory. The former is a lot saner for actual entry because you at least get the assembly mnemonics.

Invoking the code

100 addr = (x * 80) + y : rem x = 0 to 79, y = 0 to 24
110 c = 209 : rem the filled disc
120 gosub 11010
9999 end
10000 vo=dec("180c")
11000 rem vo is output routine location, addr address, c character to output
11010 sys vo, addr/256,18
11020 sys vo, addrand255,19
11030 sys vo, c, 31
11040 return

Further Challenges

You’ll notice in the video that the filled disc characters are reversed. That’s because while the characters in video RAM are in $0000$07FF (0-2047), the attributes are in $0800$0FFF. I haven’t confirmed, but I believe there are three different registers to set those as well.

Self-Modifying Code on a Commodore VIC-20

Note: All code listings are in lower case so that they are pastable into the VICE emulator. Otherwise, you will get graphics/uppercase PETSCII characters on paste.

Examining the structure of how the BASIC code is stored

User program RAM is in locations 4096 to 7680 (decimal) on a VIC 20. The storage format of the basic programs can be dumped with the following BASIC:

for i=4096 to 7680 - fre(1): ? i,chr$(peek(i));peek(i): next i 

I’ve taken the extra step up adding a slightly more sophisticated version of the above at line 10000 in the below code so that I can RUN 10000 to dump memory locations with paging and skipping control and non-printable characters.

10 print "hi"
20 n=peek(4104)
30 x=peek(4105)
40 if n >= 90 then n=65
50 n=n+1
60 x=int(26*rnd(1)+65)
70 poke 4104,n
80 poke 4105,x
90 goto 10
9999 end
10000 b=4096:i=b
10010 e=7680-fre(0)
10020 c=0
10030 ls=20
10040 ? i,
10050 ch=peek(i)
10060 ? ch;
10070 if(ch>=32 and ch<=127)or(ch>=160 and ch<=254)then ? chr$(ch);
10075 ?
10080 if c>ls then ? "continue";: input wt$: c=0
10090 c=c+1
10100 i=i+1
10110 if i>e then end
10120 goto 10040
User program RAM dump

You’ll notice in the above that we start with a null character (0) followed by 12, 16, 10 and 0. 12 and 16 are a pointer to the the memory location of the next line of code (in “little endian” order, so 16 * 256 + 12 = 4108)

The next bytes, at location 4099 and 4100, are 10 and 0. This is the line number for that line of code (again, in little endian format).

Once you get past these 2 2 byte numbers, you have a code…. 153: 153 is the VIC 20 BASIC Keyword Code for the PRINT statement. All syntactically significant tokens (keywords and symbols) are reduced to a single byte (and TAB and SPC functions actually include their left parenthesis as part of this code). The VIC-20 Programmer’s Reference Guide lists out these values (some of these are just their PETSCII codes if individual characters):

VIC 20 BASIC Keyword Codes

You’ll notice that space (32) and double quote (34) are explicitly expressed, as are the individual digits of any number literals.

At the very end of the line is a 0/null again to terminate the line. (Fun part of this experiment: Setting a byte in the middle of the line to 0 makes the rest of the line unreadable by the BASIC interpreter!)

Modifying the code

For an easy first attempt at this, I’m going to just change location 4105 and 4106, which are the letters in HI

10 print "hi"
HI at 4104 and 4105

In the below code, I’m cycling the original H through the alphabet (65-90) and setting the original I with random values:

20 n=peek(4104)
30 x=peek(4105)
40 if n >= 90 then n=65
50 n=n+1
60 x=int(26*rnd(1)+65)
70 poke 4104,n
80 poke 4105,x
90 goto 10
The changing 2-letter strings from the self-modifying code

If you BREAK out of the program (Esc key in VICE emulator) after running and list the first few lines, you’ll see that the initial PRINT statement’s string has indeed changed:

The print statement has had its string changed.

What’s Next?

This is obviously a very trivial exercise of self-modifying code, but any modifications that require anything aside from 1:1 in-place replacement requires more planning: The lines of a program are variable in length, which means that inserting code requires shifting subsequent code in memory. Also, shifting code in memory requires updating all pointers that pointed to the original locations. The next exercise will probably be adding code to the end of the program rather than trying to insert it in the middle.

trs80gp emulator (Model 16 emulation) – saving/reloading your work from BASIC

Setting up a fresh disk

If you’re already in BASIC, just open another trs80gp emulator to create the new disk first.

  • Insert a new disk by going to Diskette -> :1 <empty> -> <<unformatted dmk>>
  • FORMAT 1 and “Y” when asked to Continue?
Formatting progress
  • Once formatting completes select Diskette -> :1 * <<unformatted>> -> Export… -> [fill out “File name:” field] -> [Save]
  • Now load the saved disk with Diskette -> :1 * <<unformatted>> -> Replace… -> [select the disk file you just created] -> [Open]
  • DIR 1 should show the empty disk in drive 1

Saving your work

  • Compose your program in BASIC or BASICG (graphics BASIC)
  • The filespec of TRSDOS-II files is filename/ext.password:drive_number (if you’re used to DOS 8.3 names or Windows, your habitual “extension” would be a “password” in TRSDOS-II)
  • To save a program named “COS” with a “BAS” extension on Drive 1, type SAVE "COS/BAS:1"
  • SYSTEM to exit basic.
  • DIR 1 should should the directory of your disk with the COS/BAS showing. In my example below, I also tried to save “COS.BAS” which is actually “COS” with a password of “BAS”
DIR command output

Loading your work

  • You can PRINT or DUMP your saved file with the COS/BAS filespec as before, but it will be a somewhat binary output and not plain text like you might expect from modern programming language files.
  • Ultimately, you will have to go back to BASIC to reload with the same filespec (LOAD "COS/BAS:1“)