Commodore Maze Generator on AWS Lambda via API Gateway

The Commodore Maze Generator Program

I needed something “useful” to output from my Commodore VICE Lambda Custom Runtime that also didn’t require much input (if any) from the requester. Otherwise, I’d have to sanitize the input that is being passed to a shell script and potentially validate it if I didn’t want unpredictable behavior. I settled on a Commodore 128 version of the Commodore Maze Generator (the fast and slow will be unnecessary once we launch from the Lambda with the -warp option on VICE, but I never changed the program so it’s left in here. The fast and slow and everything before line 50 is Commodore 128 BASIC 7.0-specific, but the Commodore 64 version just requires substitution of POKE commands instead. (See vice-lambda repo on GitHub for latest version)

5 fast
10 scnclr
20 color 0,2
30 color 4,2
40 color 5,1
50 for y=1 to 25
60 for x=1 to 40
70 print chr$(205.5+rnd(1));
80 next x
90 next y
95 slow
100 goto 100

Making sure that the Lambda returns an image for Lambda

I want to be able to use Lambda Proxy Integration when setting up the API Gateway, so I need to return a format that maps the information properly. The response JSON needs to have:

  • isBase64Encoded set to true (necessary for the translation of the PNG back from base64)
  • headers with "Content-type" and "content-disposition" set (image/png and inline for this example)
  • statusCode of 200 (right now, if we have an error it’s either not caught or happens at the Lambda invocation level)
  • body containing the base64 data. Note the -w 0 argument to turn off line breaks when wrapping… Lambda Proxy Integration doesn’t handle that well.
function handler() {
  cd vice
  SEED=$((0 - `od -An -N2 -i /dev/random`))
  ./x128 -silent -sound -keybuf "
  3 i=rnd($SEED)
  `cat ../maze.bas`
  run
  " -warp -limitcycles 20000000 -exitscreenshotvicii /tmp/$1.png 2>&1 >/dev/null
  cd ..

  RESPONSE="{\"isBase64Encoded\": true, \"headers\": {\"Content-type\": \"image/png\", \"content-disposition\":\"inline\"}, \"statusCode\":200, \"body\":\"`base64 -w 0 /tmp/$1.png`\"}"

  echo $RESPONSE
}

Also note that we’re passing in a SEED value because, otherwise, the Commodore 128 will generate the same maze every time.

Repackage the Lambda and upload as in the Commodore VICE Lambda Custom Runtime blog post.

Setting up the API Gateway

Go to API Gateway in the AWS Console and select [Create API]

APIs Create API

Choose [Build] under “REST API” (we won’t be using OIDC/OAuth2/CORS…)

REST API -> Build

Choose REST, New API, and name your API:

REST, New API, API Name

Create a GET Method and check the checkmark

Create Method
API Method verb selection -> GET
Check the checkmark to commit
  • Choose “Lambda Function” for your Integration Type
  • Check “Use Lambda Proxy Integration”
  • Type the name of the Lambda that you’ve calling and select it and [Save] and confirm that you want to add role/permission for the Lambda.
GET API configuration

Deploy the API

Back on your API view, select the [Actions] dropdown and [Deploy API]

API Actions "Deploy API"

Just create a new dev stage… we’re not going to “production” with this experiment

Create new dev stage

and [Deploy]

It’s broken

If you click the Invoke URL presented, you will get a broken image

Invoke URL
Broken image from the API

Since we’re only sending back PNG files, we can just tell the API that */* is a Binary Media Type by going to Settings on the left sidebar for the API and [(+) Add Binary Media Type]

Add Binary Media Type

Be sure to save the settings, redeploy the API and be sure to use the Invoke URL for the correct API! (I had two identical and was “fixing” the wrong one… hence the different API URL snapshots)

Commodore Maze Generator PNG 1

If you refresh, you should get a new maze from the Commodore maze generator program:

Commodore Maze Generator run 2

Some Troubleshooting

  • Be sure to save your settings
  • Test your API call to Lambda in the Method Execution view (Resources -> GET -> Test [lightning bolt])
  • Be sure to redeploy your API after settings change.

Commodore 128 (VICE) Custom Runtime on AWS Lambda

Why a Commodore 128 Custom Runtime??

The first reason is wanted to do a Commodore 128 Custom Runtime on AWS Lambda is because it’s an absolutely ridiculous thing to do. The Commodore 128 runs at 1 MHz (2 MHz in “fast” mode) natively on the 8502 processor vs. multi-GHz scale.

Another reason is because I’ve had challenges with the slight differences with Amazon Linux vs. Ubuntu for EC2 instance. This seemed like a good way to exercise working through those differences.

The last reason is that I wanted a platform that would require me to dig a little deeper into how things are managed for a custom Lambda. The only directory that’s writable is /tmp. There are specific conventions used to honor the function.handler format. The responses (if you don’t have a supported runtime) are handled through callback URLs.

Compiling VICE

It’s a bit of an adventure getting VICE running on Amazon Linux. Ubuntu provides a package for VICE (of course, you’re on your own for the ROMs themselves.) With Amazon Linux, you’ll need to download the tarball and compile. Here’s my video recreating that process through to getting the Lambda running:

Compile VICE and Building the Custom Runtime

The steps… fire up an Amazon Linux EC2 instance and:

  • sudo yum update
  • sudo yum install links -y
  • links https://vice-emu.sourceforge.io and download the vice-3.5.tar.gz
  • tar zxvf vice-3.5.tar.gz from the home directory
  • sudo yum install -y gcc gcc-c++ flex bison dos2unix libpng-devel.x86_64
  • links https://www.floodgap.com/retrotech/xa/ and download xa-2.3.11.tar.gz or whatever version is available
  • tar zxvf xa-2.3.11.tar.gz
  • cd xa-2.3.11 and make
  • Add xa to the path with PATH=~/xa-2.3.11:$PATH
  • cd ~
  • cd vice-3.5 and ./configure --disable-pdf-docs --enable-headlessui --without-pulse --without-alsa and make

You should be able to cd ~/vice-3.5/data/C128 and run:

../src/x128 -silent -sound -ntsc -keybuf "10 graphic 1
20 scnclr
30 circle 1,100,100,20
run
" -warp -limitcycles 8000000 -exitscreenshotvicii image.png

and be able to scp the image.png and open it and see a screenshot something like (bump cycles to 10,000,000 if necessary): (Edit: use -warp not +warp to enable warp mode)

Testing x128 for Custom Runtime
C128 Circle

Packaging the Necessary Pieces

Make a vice-package directory or similar and grab the contents of vice-3.5/data/C128 (some files can be excluded, such as build files if you want to trim the package down) and the vice-3.5/src/x128 file and tar the vice-package directory (tar cvf vice-package.tar vice-package/*) and scp it down to your local computer.

Get the Sample Lambda for Custom Runtime

Go to Lambda -> Functions -> Create Function -> Author from Scratch and select the “Provide your own bootstrap on Amazon Linux 2” option:

Custom Runtime provide your own bootstrap
Custom Runtime template

Copy the bootstrap.sample to the root of the lambda package you are going to create and name it bootstrap and the hello.sh.sample as function or whatever the first part of your function.handler name is (see the Runtime settings below the code window):

Runtime settings where your function.handler is named.

Constructing the bootstrap

There are a couple of environment variables (XDG_CACHE_HOME and XDG_CONFIG_HOME) that have to be set to /tmp so that VICE can write to them. Be sure the handler in Runtime settings matches <script_name>.<bash_function_name> or else Lambda won’t be able to find it to invoke (actually the bootstrap below won’t… you can skip using $_HANDLER and hard code, but then the AWS console won’t help you for function configuration.) I disabled the -e option because we’re actually going to exit VICE ungracefully on purpose for simplicity. Be aware that this is the ON ERROR RESUME NEXT or “try with empty catch block” in that your code will ignore all the other potential failures along the way.

#!/bin/sh
# set -euo pipefail
# we're going to exit VICE on clock cycles so -e option would fail in this case
set -uo pipefail

# otherwise vice tries to write to the 'home' directory that isn't a [writeable] thing in Lambda
export XDG_CACHE_HOME=/tmp
export XDG_CONFIG_HOME=/tmp

# Handler format: <script_name>.<bash_function_name>
#
# The script file <script_name>.sh  must be located at the root of your
# function's deployment package, alongside this bootstrap executable.
source $(dirname "$0")/"$(echo $_HANDLER | cut -d. -f1).sh"

while true
do
    # Request the next event from the Lambda runtime
    HEADERS="$(mktemp)"
    EVENT_DATA=$(curl -v -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
    INVOCATION_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)

    # Execute the handler function from the script
    RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) $INVOCATION_ID "$EVENT_DATA")

    # Send the response to Lambda runtime
    curl -v -sS -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$INVOCATION_ID/response" -d "$RESPONSE"
done

Constructing the handler

The handler for this setup needs to only output what is intended as a response. I’m redirecting stderr and stdout to /dev/null because there are some messages that pop-up in the current state of the emulator. I am also using the -silent option to suppress all the errors about missing disk drive and other device ROMs that I don’t care about for this case.

function handler () {
  EVENT_DATA=$2

  cd vice-package

  ./x128 -silent -sound -ntsc -keybuf "10 graphic 1
    20 scnclr
    30 circle 1,100,100,20
    run
  " +warp -limitcycles 8000000 -exitscreenshotvicii /tmp/$1.png 2>&1 >/dev/null
  cd ..


  RESPONSE="{\"isBase64Encoded\": true, \"headers\": {\"Content-type\": \"image/png\", \"content-disposition\":\"attachment; filename=$1.png\"}, \"statusCode\":200, \"body\":\"`base64 /tmp/$1.png`\"}"

  echo $RESPONSE
}

The response

The above response is intended to output JSON in preparation for API Gateway Lambda integration. statusCode is required, and to convert the image back to an image isBase64Encoded and the headers Content-type is needed. The content-disposition is to tell it to download. All this gets POSTed back by the bootstrap script to the invocation response callback. The body is the base64 encoded png file, but in the current invocation in Amazon Linux, I’m getting newlines in the output, so that’s a problem to debug before attaching to API Gateway.

One more missing piece

We also need to pull libpng from our EC2 instance and place in the root of the lambda function at the same level as the bootstrap file. Just scp ec2-user@ec2IPaddress:/usr/lib64/libpng\* . for that.

Structure of the zip file and Deploy

zip up the following pieces into your lambda.zip (zip file name doesn’t really matter, just the organization of the contents):

  • bootstrap
  • function.sh
  • libpng*
  • vice-package/*

Once uploaded, you should be able to [Test] the function and check the logs. Add a set -x to your bootstrap if things aren’t behaving. You may need to chmod +x your bootstrap if you haven’t tried to run it locally for testing.

Using C to Program the Commodore 64 SID Chip

Why use C to Program the Commodore 64 SID Chip?

If you grew up with an original Commodore 64, you probably had a manual along with it that showed you how to program the SID chip. There was even a multi voice example that took forever to load its data and was impossible to read and debug. This is what it looked like (lines 1 and 2 mine) (GitHub link):

1 rem c64 programmers reference guide
2 rem pages 187-188
10 s=54272: forl=stos+24: pokel,0:next
20 dimh(2,200),l(2,200),c(2,200)
30 dimfq(11)
40 v(0)=17:v(1)=65:v(2)=33
50 pokes+10,8:pokes+22,128:pokes+23,244
60 fori=0to11:readfq(i):next
100 fork=0to2
110 i=0
120 readnm
130 ifnm=0then250
140 wa=v(k):wb=wa-1:ifnm<0thennm=-nm:wa=0:wb=0
150 dr%=nm/128:oc%=(nm-128*dr%)/16
160 nt=nm-128*dr%-16*oc%
170 fr=fq(nt)
180 ifoc%=7then200
190 forj=6tooc%step-1:fr=fr/2:next
200 hf%=fr/256:lf%=fr-256*hf%
210 ifdr%=1thenh(k,i)=hf%:l(k,i)=lf%:c(k,i)=wa:i=i+1:goto120
220 forj=1todr%-1:h(k,i)=hf%:l(k,i)=lf%:c(k,i)=wa:i=i+1:next
230 h(k,i)=hf%:l(k,i)=lf%:c(k,i)=wb
240 i=i+1:goto120
250 ifi>imthenim=i
260 next
500 pokes+5,0:pokes+6,240
510 pokes+12,85:pokes+13,133
520 pokes+19,10:pokes+20, 197
530 pokes+24,31
540 fori=0toim
550 pokes,l(0,i):pokes+7,l(1,i):pokes+14,l(2,i)
560 pokes+1,h(0,i):pokes+8,h(1,i):pokes+15,h(2,i)
570 pokes+4,c(0,i):pokes+11,c(1,i):pokes+18,c(2,i)
580 fort=1to80:next:next
590 fort=1to200:next:pokes+24,0
600 data34334,36376,38539,40830
610 data43258,45830,48556,51443
620 data54502,57743,61176,64814
1000 data594,594,594,596,596
1010 data1618,587,592,587,585,331,336
1020 data1097,583,585,585,585,587,587
1030 data1609,585,331,337,594,594,593
1040 data1618,594,596,594,592,587
1050 data1616,587,585,331,336,841,327
1060 data1607
1999 data0
2000 data583,585,583,583,327,329
2010 data1611,583,585,578,578,578
2020 data196,198,583,326,578
2030 data326,327,329,327,329,326,578,583
2040 data1606,582,322,324,582,587
2050 data329,327,1606,583
2060 data327,329,587,331,329
2070 data329,328,1609,578,834
2080 data324,322,327,585,1602
2999 data0
3000 data567,566,567,304,306,308,310
3010 data1591,567,311,310,567
3020 data306,304,299,308
3030 data304,171,176,306,291,551,306,308
3040 data310,308,310,306,295,297,299,304
3050 data1586,562,567,310,315,311
3060 data308,313,297
3070 data1586,567,560,311,309
3080 data308,309,306,308
3090 data1577,299,295,306,310,311,304
3100 data562,546, 1575
3999 data0

I copied this from a text / pdf version which was in all uppercase (because only one case is valid for BASIC keywords and such). It’s in lowercase above so that you can copy/paste into the VICE emulator, because that “one case” is the lower in lower/uppercase mode, and uppercase pastes in as graphics characters in upper/graphics mode. This example is a fairly impressive one, but it also takes 45 seconds to load all of the data and a couple of typos can mean hours of debugging. I took several hours to find what was ultimately mistakenly typing an I instead of a 1.

Spacing Matters in Commodore BASIC

While this code is not *required* to be so compact in spacing, those in-line spaces almost 100% impact the size of the program in memory and on disk. So if you’re taking up nearly all of the BASIC RAM, eliminating spaces actually matters. So you have code that reads pokes or fori or dimfq.

In BASIC, data has to be part of code but still explicitly read in every time

The DATA statements in BASIC can create a nice little data block, but it’s an implicit data block that requires explicit reading in. So you take up all of the space required for the DATA statements, but then load that data into arrays, and, in this case, take 45 seconds to read in and encode the data used.

Variable names are 2 characters max in Commodore BASIC

PIPE, PIKE, and PINE are the same variable, so if you set PIPE to 2, PIKE to 3, and PINE to 4, you will get 4 from all of them. However Commodore 64 BASIC will let you *think* that up to 5 characters is legal (you get a syntax error if you use something like THINGS=2)

Functions can only receive one argument and be named 2 chars (5 can be specified)

The def fn statement allows you to specify code like def fn xp(x) = x * 300 + 12, which then is required to be invoked by fn xp(x) or (fn xp(x)) + y * 12 in context. (The parentheses are probably unnecessary, but readability can be helped by using 2 extra bytes for the parens.)

Subroutines take no arguments and have to be referred to by line number

If you are good at modularizing your code, you’ll find Commodore 64 BASIC a bit limiting. This isn’t even compared to modern and higher level programming languages. Even a decent Assembler can at least track symbolic locations of the subroutines and variables.

Commodore BASIC is slow

Spacing and variable names have an impact on the performance of Commodore BASIC

Setting up for C programming

Install cc65 for your platform. For macOS, it’s available via brew install cc65 and in Ubuntu via sudo apt install cc65. (My Windows install of cc65 is via WSL2).

You may want to find the include files for reference for your platform. They’re in /usr/share/cc65/include for my Ubuntu install and /opt/homebrew/Cellar/cc65/2.19/share/cc65/include (your version may vary).

I created a c64_note_values.h header file for note #defines for all octave 0-7 notes. They’re proportional to their frequency values, but scaled up so that the low ranges can be defined by integer values. If you want to tweak the values and regenerate, this is the C file I created to generate it.

The rudimentary Canon in D (so far)

Thanks to Recreating the Commodore 64 User Guide code samples in cc65. Part four: Sound, I was inspired to try my own hand at the C code.

#include <stdio.h>
#include <stdlib.h>
#include <c64.h>
#include "c64_note_values.h"

const unsigned int notes[][8] = {
  { D3, A2, B2, Fs2, G2, D2, G2, A2 },
  { Fs5, E5, D5, Cs5, B4, A4, B4, Cs5 },
  { D5, Cs5, B4, A4, G4, Fs4, G4, E4 },
};


int main(void) {
  unsigned int i,d;
  unsigned int measure=0;

  SID.amp = 0x1F;
  SID.v1.ad = 0x0f;
  SID.v2.ad = 0x0f;
  SID.v3.ad = 0x0f;

  while(1) {
    for(i = 0; i < sizeof(notes[0]) / sizeof(int); i++) {
      SID.v1.freq = notes[0][i];
      SID.v1.ctrl = 0x11;
      if(measure) {
        SID.v2.freq = notes[1][i];
        SID.v2.ctrl = 0x11;
      }
      if(measure > 1) {
        SID.v3.freq = notes[2][i];
        SID.v3.ctrl = 0x11;
      }
      for(d=0; d<10000; d++) { }
      SID.v1.ctrl = 0x10;
      if(measure) {
        SID.v2.ctrl = 0x10;
      }
      if(measure > 1) {
        SID.v3.ctrl = 0x10;
      }
    }
    measure++;
    if(measure>2) { break; }
  }
  return EXIT_SUCCESS;
}

In this example, I haven’t actually created any functions yet, beyond the main.

SID is a struct pointer cast of the base memory location of the default SID chip. (See here for a “multiple SID” example if you enable in hardware or VICE emulator)

.v1 through .v3 align to the base address of the voice 1 through 3 controls.

.ad is an attack / decay value… 0-f for the two nibbles. If you want the note to start as quickly as possible, use 0, if you want the note to never fade out, use f for the second nibble. For this case, I’m using just that… 0x0f.

.freq is the scaled frequency value I created earlier from the header file which is in the notes arrays.

.ctrl value of 0x11 turns the note on and 0x10 turns it off. You’re actually setting the triangle waveform bit to true and the gate bit to on and off.

.amp is a volume number (actually volume and mode… location 54296 in the Commodore 64 memory map). LP is 1 and volume nibble is 0xf.

Compiling and running

Assuming that you’ve saved the file to canonind.c, the command to compile and link is cl65 -t c64 -O canonind.c (you may be able to remove the -t c64 parameter… my install appears to assume c64.)

Provided you have a successful build, you should have a canonind binary file. In VICE, you can use “File -> Smart attach disk/tape/cartridge…” or “Autostart disk/tape image…”. For the first version, you’ll want to click the “[Autostart]” button after selecting the extensionless filename. For the second version, you’ll want to look for All Files *.* instead of .d64 etc… Either version should autoplay once loaded.

See my video (choppy sound from VICE and morning voice warning) of the process of using C to program the Commodore 64 SID Chip here:

Trying Out the Ringo R-470, a Brazilian ZX-81 Clone

I’ve also recorded a video on YouTube where can you see a demonstration of the Ringo R-470 emulation on the EightyOne emulator:

Demonstrating the Ringo R-470 emulation in EightyOne

The Jupiter ACE was an obscure machine, partly for the fact that the primary programming environment was the Forth programming language. I found out after my Jupiter ACE post that it enjoys a small but faithful following. You can also find them for purchase on eBay if you’re willing to spend $1,000+ on a unique and obscure ZX-81 clone.

The Ringo R-470

I really only stumbled upon Ringo R-470 because there’s a ROM for it packaged with the EightyOne Sinclair Emulator. There are various copies of its original manual floating around (also available on Scribd) and a very brief wikipedia page. The emulation in the emulator mostly works, but virtual peripheral support (including the tape loader) seems shaky at best. You’re not likely to find one available for sale online, either.

R-470 Ringo - Frente

The above image which is also on the Wikipedia page is pretty standard in terms of being able to see the features of the computer clearly. This Sinclair Clones page has a few better photos, but you still can’t completely see the keyboard layout. (You can find a clearer picture of the layout on page 12 of the original manual linked above.)

Using with the EightyOne emulator

Edit cursor [>] and [K]eyword/command mode at the bottom

The functionality of the R-470 is mostly similar to the ZX-81, but the placement of the keywords and symbols (which still use English) is completely different from the ZX-81 layout. For example, L in keyword mode is LIST on the R-470 and LET on the ZX-81 (and K is LET on the R-470 and LIST on the ZX-81). However, the two layers of command mode keys and function mode keys and the graphics mode are similar, and the displayed listing has a [>] cursor for highlighting which key will get picked up if you select edit mode.

If this is your first try with a Sinclair clone (that speaks BASIC), you’ll need to keep in mind that the keywords are whole symbols on their own as opposed to being parsed from strings like Microsoft-derived BASICs. It’s actually kind of nice for editing and I’m sure it greatly simplifies the parsing, but even “TO” cannot be specified with the letters T and O and must instead be typed with the [left shift + T] (or [left shift + 4] on the ZX-81).

Another shock to the system for those unacquainted with the Sinclair way is that you get an error code + line number that it happened on at the end of execution (0 is no error/OK):

B (Integer out of range) on line 120 (because I tried to plot on row 44, and y coordinates only go up to 43)

An alphabetical guide to the keyboard mapping of keywords and symbols:

I’ve compiled an alphabetical listing of the symbols/commands below (also available in markdown here) if you want to try your hand at typing a BASIC program into the the R-470 inside the EightyOne emulator. I’m sure that typing on a modern keyboard is more functional than typing on rubber chiclet keys, but the layout is pretty challenging:

symbolkeycursor modemodifier
P[K]eyword/command modeleft shift
“”2[K]eyword/command modeleft shift
$O[K]eyword/command modeleft shift
(X[K]eyword/command modeleft shift
(edit mode)1[K]eyword/command modeleft shift
(graphics mode)9[K]eyword/command modeleft shift
)C[K]eyword/command modeleft shift
*G[K]eyword/command modeleft shift
**F[K]eyword/command modeleft shift
+K[K]eyword/command modeleft shift
J[K]eyword/command modeleft shift
/H[K]eyword/command modeleft shift
:N[K]eyword/command modeleft shift
;M[K]eyword/command modeleft shift
<V[K]eyword/command modeleft shift
<=Y[K]eyword/command modeleft shift
<>U[K]eyword/command modeleft shift
=L[K]eyword/command modeleft shift
>B[K]eyword/command modeleft shift
>=I[K]eyword/command modeleft shift
?Z[K]eyword/command modeleft shift
ABSG[F] function modeleft CTRL (and release) from [K]eyword/command mode
ACSR[F] function modeleft CTRL (and release) from [K]eyword/command mode
ANDA[K]eyword/command modeleft shift
ARCTANY[F] function modeleft CTRL (and release) from [K]eyword/command mode
ASNW[F] function modeleft CTRL (and release) from [K]eyword/command mode
ATM[F] function modeleft CTRL (and release) from [K]eyword/command mode
CHR$D[F] function modeleft CTRL (and release) from [K]eyword/command mode
CLEARZ[K]eyword/command modedefault
CLSX[K]eyword/command modedefault
CODEC[F] function modeleft CTRL (and release) from [K]eyword/command mode
CONTC[K]eyword/command modedefault
COPYQ[K]eyword/command modedefault
COSE[F] function modeleft CTRL (and release) from [K]eyword/command mode
DIMD[K]eyword/command modedefault
EXPK[F] function modeleft CTRL (and release) from [K]eyword/command mode
FASTR[K]eyword/command modeleft shift
FORF[K]eyword/command modedefault
GOSUBH[K]eyword/command modedefault
GOTOG[K]eyword/command modedefault
IFW[K]eyword/command modedefault
INKEY$I[F] function modeleft CTRL (and release) from [K]eyword/command mode
INPUTI[K]eyword/command modedefault
INTH[F] function modeleft CTRL (and release) from [K]eyword/command mode
LENX[F] function modeleft CTRL (and release) from [K]eyword/command mode
LETK[K]eyword/command modedefault
LISTL[K]eyword/command modedefault
LLIST4[K]eyword/command modeleft shift
LNL[F] function modeleft CTRL (and release) from [K]eyword/command mode
LOADA[K]eyword/command modedefault
LPRINT3[K]eyword/command modeleft shift
NEWM[K]eyword/command modedefault
NEXTN[K]eyword/command modedefault
NOTN[F] function modeleft CTRL (and release) from [K]eyword/command mode
ORQ[K]eyword/command modeleft shift
PAUSEE[K]eyword/command modedefault
PEEKB[F] function modeleft CTRL (and release) from [K]eyword/command mode
PLOTY[K]eyword/command modedefault
POKEB[K]eyword/command modedefault
PRINTP[K]eyword/command modedefault
RANDO[K]eyword/command modedefault
REMT[K]eyword/command modedefault
RETURNJ[K]eyword/command modedefault
RNDO[F] function modeleft CTRL (and release) from [K]eyword/command mode
RUNR[K]eyword/command modedefault
SAVES[K]eyword/command modedefault
SCROLLV[K]eyword/command modedefault
SGNJ[F] function modeleft CTRL (and release) from [K]eyword/command mode
SINQ[F] function modeleft CTRL (and release) from [K]eyword/command mode
SLOWE[K]eyword/command modeleft shift
SQRF[F] function modeleft CTRL (and release) from [K]eyword/command mode
STEPD[K]eyword/command modeleft shift
STOPS[K]eyword/command modeleft shift
STR$S[F] function modeleft CTRL (and release) from [K]eyword/command mode
TAB,[F] function modeleft CTRL (and release) from [K]eyword/command mode
TANT[F] function modeleft CTRL (and release) from [K]eyword/command mode
THENW[K]eyword/command modeleft shift
TOT[K]eyword/command modeleft shift
UNPLOTU[K]eyword/command modedefault
USRU[F] function modeleft CTRL (and release) from [K]eyword/command mode
VALV[F] function modeleft CTRL (and release) from [K]eyword/command mode
πP[F] function modeleft CTRL (and release) from [K]eyword/command mode

The Unique Jupiter ACE Experience: Programming Forth on a ZX-81 Like System

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

Jupiter ACE emulated in the EightyOne emulator
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:

: SIN
    (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)
        ([-x*x,x,x])
        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)
    +LOOP
    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.

8563 Chip: Plotting Characters to the Commodore 128’s 80-column Mode

Source Code for this 8563 Demonstration

You can find the below source code at https://github.com/stringsn88keys/unnecessary-computer-things/blob/main/episodes/2021/001/commodore-128/CHASXYCOSINE.BAS 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).

8563 graphing text characters

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)

Sending Data to the 8563

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

Saving your work on the Model 16 in TRSDOS-II and BASIC/BASICG

(This is emulating the Model 16 using the trs80gp emulator and saving/load from virtual disk.)

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”
Model 16 TRSDOS
DIR command output on the Model 16

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

Resources: