Close

Adding Input/Output to a PC

A project log for 8 Bit TTA Interpreter

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

agpcooperagp.cooper 05/23/2018 at 11:320 Comments

Parallel/Serial Input/Output

Dealing with asynchronous serial in software and/or in DIY hardware is not a simple exercise. I need to keep things simple. Best option is to use a Nano to talk to my PC and present a parallel interface. Its a tight fit as I/O requires 2x 8 bits for data and 2x 2 bits for handshakes, i.e. 20 I/O lines.

Here is my proposed schematic:

I have included the interrupt input port on this schematic as it is need for the "DTR" (i.e. Data Terminal Ready signals). I have not written the code for the Nano but that should not be a big deal. The Nano will be off board (a Parallel to Serial to USB box!). Eventally I will get around to a DIY serial terminal but not today or in the near future.

More OpCodes

Added "LIT" or load immediate, and "JNZ" or jump on not zero. Run out of room.

I used 78 bytes for five instructions (excluding Inline at 2 bytes), or about 16 bytes per instruction. Add 28 bytes for reset (initialisation) and the interpreter, and 14 bytes for constants. That leaves six for bytes free out of 128 bytes in the ROM.

Not much chance of replacing the current monitor with anything useful without a wider address bus or memory paging. A wider address bus (i.e. 16 bits seems to be the simplest option).

Some Useful Code

For my SubLEq compiler I wrote some useful utilities:

The last one was tough but it is possible to build an efficient algorithm with just the add instructions.

itoa (in C)

Here is the C code for atoi:

void wrtAx(int16_t Ax) {
  int digit=0;
  int minus=45;
  int zero=48;
  int zeroFlag=0;

  if (Ax<0) {
    Ax=-Ax;
    digit=minus;
    putchar(digit);
  }

  digit=0;
  while (Ax>=10000) {
      digit++;
      Ax=Ax-10000;
  }
  zeroFlag=zeroFlag+digit;
  if (zeroFlag>0) {
    digit=digit+zero;
    putchar(digit);
  }

  digit=0;
  while (Ax>=1000) {
      digit++;
      Ax=Ax-1000;
  }
  zeroFlag=zeroFlag+digit;
  if (zeroFlag>0) {
    digit=digit+zero;
    putchar(digit);
  }

  digit=0;
  while (Ax>=100) {
      digit++;
      Ax=Ax-100;
  }
  zeroFlag=zeroFlag+digit;
  if (zeroFlag>0) {
    digit=digit+zero;
    putchar(digit);
  }

  digit=0;
  while (Ax>=10) {
      digit++;
      Ax=Ax-10;
  }
  zeroFlag=zeroFlag+digit;
  if (zeroFlag>0) {
    digit=digit+zero;
    putchar(digit);
  }

  digit=Ax+zero;
  putchar(digit);
}

Not very complicated. Notice the code is delibrately simple so that it can be translated into microcode directly.

atoi (in C)

Here is the C code for atoi:

int16_t rdInt(void)
{
  int16_t Ax;
  int16_t t1;
  int16_t t3;
  char digit;
  int16_t sgn=1;
  int16_t minus=45;
  int16_t zero=48;
  int16_t nine=57;
  int16_t number=0;

  Ax=-32768;
  NEXT:
    digit=getchar();
    if (digit=='\n') goto DONE;
    if (digit==minus) {
      sgn=-sgn;
      goto NEXT;
    }
    if (digit<zero) goto ERROR;
    if (digit>nine) goto ERROR;
    t1=number+number;
    t3=t1+t1;
    t3=t3+t3;
    number=t3+t1;
    number=number+digit;
    number=number-48;
    if (number<0) goto ERROR;
    goto NEXT;
DONE:
    if (sgn<=0) {
      number=-number;
    }
    Ax=number;
ERROR:
    // Clear buffer
    while (digit!='\n') {
      digit=getchar();
    }
    return Ax;
}

Again the code is written in a way to make translation to microcode easier.

Another thing was I used the minimum integer value (i.e. -32768 for 16 bit word) as a return error code. You should not use -32768 in normal programming use anyway as -(-32768) equals -32768!

imul (in C)

Here is the C code for imul:

int16_t imul(int16_t Ax,int16_t Bx) {
  // Returns:
  //   Ax = Ax * Bx
  int16_t sgn=1;
  int16_t res=0;
  int16_t word=1;
  int16_t MIN=-32768;

  if (Ax<0) {
    Ax=-Ax;
    sgn=-sgn;
  }
  if (Bx<0) {
    Bx=-Bx;
    sgn=-sgn;
  }

  LOOP:
    res=res<<1;
    if (res<0) {
      res=MIN;
      goto ERROR;
    }

    Ax=Ax<<1;
    if (Ax<0) {
      Ax=Ax-MIN;
      res=res+Bx;
      if (res<0) {
        res=MIN;
        goto ERROR;
      }
    }

    word=word+word+1;
  if (word>0) goto LOOP;

  if (sgn<0) {
    res=-res;
  }
  ERROR:
  return res;
}

This code is straight forward except for the loop control  "word=word+word+1;"

In SubLEq this avoids the -32768 "bug" and automatically adjusts for the word width. If the -32768 bug is not an issue than "word=word+word;" can be used.

Note: "Ax=Ax<<1;" is the same as "Ax=Ax+Ax;".

Again, -32768 is used as and error code (for overflow).

Idiv (in C)

Here is the idiv routine:

void idiv(int16_t *Ax,int16_t *Bx) {
  int16_t sgn=1;
  int16_t rem=0;
  int16_t res=0;
  int16_t word=1;
  int16_t MIN=-32768;

  res=*Ax;
  if (res<0) {
    res=-res;
    sgn=-sgn;
  }
  if (*Bx<0) {
    *Bx=-*Bx;
    sgn=-sgn;
  }
  if (*Bx>0) {

    LOOP:
      res=res<<1;
      rem=rem<<1;
      if (res<0) {      // Use MIN sensitive test!
        res=res-MIN;
        rem++;
      }
      if (rem>=*Bx) {
        rem=rem-*Bx;
        res++;
      }
      word=word<<1;
      word=word+1;
    if (word>0) goto LOOP;

    if (sgn<0) {
      res=-res;
    }
    if (*Ax<0) {
      rem=-rem;
    }
    *Ax=res;
    *Bx=rem;
  } else {
    *Ax=MIN;
  }
}

 This routine is efficient but only uses the add code. So those who believe "SHR" or "SAR" or ">>" is a necessary primative for a CPU for effecient software division, they are wrong. As an aside I see XOR is mentioned as a necessary primative as well, this is also wrong.

Anyway, I played with translating the imul code in to microcode and it used about 60 bytes of code. So this four instruction will require around 300 bytes. Time to move on to the TTA16.

AlanX


Discussions