A project log for 8 Bit TTA Interpreter

TTA (Transport Triggered Architecture) is very simple CPU type.

agp.cooperagp.cooper 09/14/2019 at 07:100 Comments


Sometimes a project gets "long in the tooth" with no clear exit point.

In many respects I am at this point. Progress is very slow. The exit point is far away.

To reboot you need a new way?

I was looking at my SubLEq compiler and I realised that of the two huge faults: low code density and low speed, I could have fixed low code density by using a library rather than direct SubLEq code. That is just substituting OpCodes with SubLEq code.

With that in mind looking at my PL/0 compiler, it produces P-Code (27 primitives) that is converted into OpCodes (35 primitives), it struck me that 35 primitives for the TTA8 is quite doable.


PL/0 is a Pascal like language written by Niklaus Wirth. I have ported his code to C and extended the capability of the original code.

Here is an example of PL/0:

{ Program Test String }
(* Program Test String *)
/* Program Test String */
// Program Test String

const true=1,false=0;
const min=-2147483648;
var Str[12],Msg[12];

  procedure ReadStr;
  var i,test;
  { Read a Pascal string, uses Str }
    while test=true do begin
      getc Str[i];
      if Str[i]<32 then begin
        Str:=i-1; { Str == Str[0] }
      end else begin
        if i=12 then begin
        end else begin

  procedure WriteStr;
  var i;
  { Write a string, uses Str }
    while i<=Str do begin
      putc Str[i];

  procedure WriteMsg;
  var i;
  { Write Msg }
    while i<=Msg do begin
      putc Msg[i];

  Msg:="String Test";
  call WriteMsg;
  putc 10; { new line }

  Msg:="Enter str: ";
  call WriteMsg;
  call ReadStr;

  Msg:="You wrote: ";
  call WriteMsg;
  call WriteStr;
  putc 10; { new line }
  putc 10; { new line }

// End Program Test String

 And the run:

Begin PL/0:

String Test
Enter str: Hello World
You wrote: Hello World

End PL/0.

Why PL/0? Really it could be any simple P-Code compiler. I just have this one on hand. I would most likely upgrade to a C like compiler in the future.

So what are the OpCode primitives:

// CPU model 16 bit // OpCodes // System

16 Bit

Something I have to do is code the primitives as 16 bit words and linearise the address space. This will require a 16 bit PC, SP and CPU registers. These will need to be accessable to the MicroCode as AL and AH for the AX register.

I also have to code 16 bit imult and idiv in micro code.

Coding the Virtual Machine

Spent a day or so micro-coding. With practice you get better, develop new tricks. Anyway, it looks like:

The Fetch routine is 82 bytes of code and 12 constants, so a dedicated fetch for the PC, SP and AX is expensive. I also need a Deposit as well. So more work required.

Had looked at "idiv" and "imult", about two ROM pages each. Expect the same for "atoi" and "itoa".

Somewhere in the virtual machine (i.e. VM) I need to check interrupts and code the serial IO. Between ROM1 and ROM2 looks right.

Also looking to have a fixed but separate code for indirect read and write. Saves setting up the registers each time.

And I nearly forgot, need to check the front panel.

So the VM is a relatively big task compared to the Interpreter.


I have been busy coding. So far I have coded the VM:

In order to use a linearised address space the PC address needs to be converted into a Page (PP or Page Pointer) and an Address (IP or Instruction Pointer). The register pair PP:IP is a general register pair (not reserved by the PC register pair). It is also used by the Stack Pointer (SP).

To finish off the VM I have coded three pages for Opcode Jump Addresses (capacity for 48 Opcodes). Currently only 35 OpCodes are required.

Coded OpCode

The test OpCode (I.E. eq, lt, le, ne, gt and ge) were tricky. Problems with integer overflow.

Here is the C code that I used to model the micro-code:

#include <stdio.h>
#include <stdlib.h>

int main(void)
  unsigned char AL=0;
  unsigned char AH=0;
  unsigned char BL=0;
  unsigned char BH=0;
  unsigned char TL=0;
  unsigned char TH=0;
  unsigned int AX;
  unsigned int BX;
  unsigned int T=0;
  unsigned int Test=0;

  for (AX=0;AX<=65535;AX++) {
    for (BX=0;BX<=65535;BX++) {


      // TX=-BX-1

      // TX=AX+TX
      if (T>255) goto L1;

      if (T>255) goto L2;

      // TX=TX+1
      if (T>255) goto L3;
      // AH=1;
      // if (T>255) goto L4;
      //  AH=0;
      // L4:

      // Test results!
      if ((AL==1)&&(AX >BX)) Test=1;          // AX GT BX
      if ((AL==0)&&(AX<=BX)) Test=1;          // AX LE BX
      if ((AH==1)&&(AX==BX)) Test=1;          // AX EQ BX
      if ((AH==0)&&(AX!=BX)) Test=1;          // AX NE BX
      if ((AL==0)&&(AH==0)&&(AX!=BX)) Test=1; // AX LT BX
      if ((AL==1)||(AH==1)&&(AX!=BX)) Test=1; // AX GE BX
      if (Test==0) {
        printf("AX=%d > BX=%d GT=%s\n",AX,BX,AL?"true":"false");

      // printf("%5d %5d: AX-BX-1 = %3d %3d",AX,BX,TH,TL); getchar();
      // printf("AX=%d > BX=%d GT=%s",AX,BX,AL?"true":"false"); getchar();


  return 0;

Okay, it looks very weird but it mimics the micro-code. If it works then I have confidence that the micro-code works. Here is the resulting micro-code:

The code return two flags:

Depending on the test, using these two codes, AX is set  either to 0 (false) or 1 (true).

Remaining OpCode

The remaining OpCodes are:

I have written the C code for imul, idiv, atoi, itoa, so they are ready for conversion to micro-code.

Only putC and getC (serial I/O of characters) which require interrupts need further thought.

So on balance I think I am 50% done.

Update II

A busy few days:

  Coded the virtual machine.

Okay got the "Console/Serial Terminal" emulator working:

Next is to link up the OpCode Jump Table and finish off the OpCodes

Update III

I have been working on 16 bit signed multiplication and division. Unfortunately the code spans 3 ROM pages each. That is a bit much for having confidence that the code will work first time. I have elected to used 8 bit multiplication and division (only spans two ROM pages each) for testing purposes.

Another problem is that the timing interrupt (INTR7) is called every 256 micro-instructions (i.e. read/write pairs), and the multiplication/division will take about 7 timing interrupts. So two problems, a serial input character could be missed and timed delays will be "delayed".

I can fix the serial problem by porting the INTR6 signal to CTS to hold the USB-Serial converter. The missed timing interrupts will need the interrupt frequency reduced by a factor of 8 or I don't worry about it.