Close

Success"ish"

A project log for Commodore CHESSmate Reproduction

The plan is to make a reproduction of the dedicated chess computer CHESSmate released by Commodore in 1978.

michael-gardiMichael Gardi 02/03/2024 at 17:392 Comments

Getting the CHESSmate emulator running was relatively straight forward, but "the devil's in the details" as they say. Took me a couple of days, partly because I had to figure out how the CHESSmate program was interacting with the original hardware, and partly because of my own silly mistakes. 

Changes To The 6502 Emulator

I started by cloning Mike Chambers excellent 6502 emulator for Arduino.  The code comes pre-configured  to run EhBASIC, so I used this example to set up the emulator to run CHESSmate. For the most part all you have to do is define the blocks of RAM and ROM that the program requires to run. Here is the relevant code from cpu.c.

// Callbacks to the CHESSmate sketch.
extern uint8_t readRRIOT(uint16_t address);
extern void writeRRIOT(uint16_t address, uint8_t value);

...

// Addresses and sizes of the various memory locations CHESSmate is expecting.
#define CHESSMATE_ROM 0xF000
#define RRIOT_REGISTERS 0x8B00
#define RRIOT_RAM_ADDR 0x8B80

#define RAM_SIZE 1536
#define ROM_SIZE 4096
#define RRIOT_REG_SIZE 12
#define RRIOT_RAM_SIZE 64

...

// Allocate space for the the expected memory locations starting with main RAM.
uint8_t RAM[RAM_SIZE];

// This is the original CHESSmate ROM.
PGM_P const CHESSMATE[ROM_SIZE] PROGMEM = {
    0xd8,0x58,0xa2,0xfe,0x9a,0xa9,0x3f,0x8d,0x03,0x8b,0xa9,0xff,0xa2,0x00,0x95,0x00,
0xd5,0x00,0xf0,0x05,0xb5,0x00,0x4c,0x14,0xf0,0xca,0xd0,0xf2,0x49,0xff,0xf0,0xee,
0xa9,0x05,0x85,0x53,0xa2,0x21,0xbd,0x52,0xfe,0x95,0x00,0xca,0x10,0xf8,0x86,0x59,
... // many lines omitted
0x53,0x00,0x00,0x40,0x08,0x40,0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,
0x53,0x00,0x00,0x40,0x08,0x00,0x00,0x00,0x43,0x48,0x45,0x53,0x53,0x4d,0x41,0x54,
0x45,0x20,0x37,0x2e,0x35,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0x4c,0xf3
};

// The 6520-24 RIOT chip on the original CHESSmate had 64 bytes of RAM.
uint8_t RRIOT_RAM[RRIOT_RAM_SIZE];

// Whenever the emulator needs to grab a byte from memory this function gets 
// called. Based on the address passed, it returns the byte from the appropriate 
// memory block.
uint8_t read6502(uint16_t address) {
  uint16_t PGMaddr;

  // Main program.
  if (address >= CHESSMATE_ROM) {
    PGMaddr = address - CHESSMATE_ROM;
      return(pgm_read_byte_near(CHESSMATE + PGMaddr));
  }

  // RAM memory.
  if (address < RAM_SIZE) return(RAM[address]);

 // RRIOT RAM memory.
  if (address >= RRIOT_RAM_ADDR && address < (RRIOT_RAM_ADDR + RRIOT_RAM_SIZE)) {
      PGMaddr = address - RRIOT_RAM_ADDR;
      return(RRIOT_RAM[PGMaddr]);
  }

  // The 6530-24 chip was controlled by a few memory mapped RRIOT registers. 
  // In this case we will call back to the CHESSmate script to fetch the appropriate 
  // byte.
  if (address >= RRIOT_REGISTERS && address < (RRIOT_REGISTERS + RRIOT_REG_SIZE)) {
      PGMaddr = address - RRIOT_REGISTERS;
      return readRRIOT(PGMaddr); [NOTE 1]
  }

  // Not a valid memory location.
  return(0);
}

// Whenever the emulator needs to write a byte to memory this function gets 
// called. Based on the address passed, it writes the byte to the appropriate 
// memory block.
void write6502(uint16_t address, uint8_t value) {
  uint16_t PGMaddr;

  // RAM memory.
  if (address < RAM_SIZE) RAM[address] = value;

  // RRIOT RAM memory.
  if (address >= RRIOT_RAM_ADDR && address < (RRIOT_RAM_ADDR + RRIOT_RAM_SIZE)) {
        PGMaddr = address - RRIOT_RAM_ADDR;
      RRIOT_RAM[PGMaddr] = value;
  }

  // The 6530-24 chip was controlled by a few memory mapped RRIOT registers. 
  // In this case we will  call back to the CHESSmate script to "write" the 
  // appropriate byte.
  if (address >= RRIOT_REGISTERS && address < (RRIOT_REGISTERS + RRIOT_REG_SIZE)) {
      PGMaddr = address - RRIOT_REGISTERS;
      writeRRIOT(PGMaddr, value);
  }
}

...


[NOTE 1] - This was one of my mistakes. For a while I was making the call 
           to readRRIOT and not returning the result. Doh.

The hardware components on the original CHESSmate (LEDs, 7-segment displays, and keys) were controlled by a 6530-24 RRIOT chip  which stands for ROM RAM I/O and Timer.  Given that one of the features of CHESSmate is a chess clock, I was surprised that the program did not take advantage of the Timer function of the chip, only the RAM and I/O. 

The I/0 feature consisted of 16 lines that can be configured as either Input or Output grouped into 2 "ports" A and B.  There are four 8-bit registers on the 6530-24 that control these I/O lines.

Port NameMemory Mapped AddressDescription
SAD0x8B00Port A Data
PADD0x8B01Port A Data Direction
SBD0x8B02Port B Data
PBDD0x8B03Port B Data Direction

On the original CHESSmate these 16 lines are connected to the LEDs, switches, and 7-segment display. The 8 lines of the A Port were cleverly used to both read keys and update the 7- segment display segments.  Cool.

Pretending To Be A 6530-24

In the CHESSmate Arduino sketch I process the callbacks and map the calls to and from the RRIOT "registers" to the actual hardware of my implementation. This is what the relevant code looks like.

/********************************************************************************************
   *   This is the key part of the CHESSmate "emulation". Pretend to be a 6530-24 RRIOT chip.
   *
   *   Translate the reads and writes to and from the program to the 6530-24 RRIOT registers
   *   and map the bits to the actual switches, 7-segment displays, and LEDs.
   *
   ********************************************************************************************/
  // Handle reads from RRIOT registers.
  uint8_t readRRIOT(uint16_t address) {
    if (address == 2) {             // Port B Data
      // Reading from PORTB. Return the previous value of PORTB.
      return sbc_value;
    } else if (address == 0) {      // Port A Data
      // Check for NEW GAME. (Reset)
      if (digitalRead(NEW_GAME_KEY) == LOW) {
        reset6502();
      }
      // Check for keypresses. Default result, no keys pressed.
      uint8_t result = 0b01111111; [NOTE 2]

      // On the original there were 2 rows when reading the keyboard.
      if (keyboard_row == 1) {
        if (digitalRead(PA7) == LOW) {
          result = 0b01011111;
        } else if (digitalRead(PA6) == LOW) {
          result = 0b01101111;
        } else if (digitalRead(PA5) == LOW) {
          result = 0b01110111;
        } else if (digitalRead(PA4) == LOW) {
          result = 0b01111011;
        } else if (digitalRead(PA3) == LOW) {
          result = 0b01111101;
        } else if (digitalRead(PA2) == LOW) {
          result = 0b01111110;
        }
      } else if (keyboard_row == 2) {
        if (digitalRead(PA1) == LOW) {
          result = 0b00111111;
        } else if (digitalRead(PA0) == LOW) {
          result = 0b01011111;
        } else if (digitalRead(CLEAR_KEY) == LOW) {
          result = 0b01101111;
        } else if (digitalRead(ENTER_KEY) == LOW) {
          result = 0b01110111;
        }
      }
      return result;
    }
  }

  // Handle writes to RRIOT registers.
  void writeRRIOT(uint16_t address, uint8_t value) {
    if (address == 0) {                 // Port A Data
      // PA0 - PA7 Data Register (SAD)
      // Maps to Pro Mini pins 0 - 7. Have to invert for my hardware.
      PORTD = ((lookup[value & 0b1111] << 4) | lookup[value >> 4]) & 0xFE;

      // Check for CHESSmate LOSES.
      if ((value & 0b10000000) > 0) {
        digitalWrite(LOSES_LED, HIGH);
      } else {
        digitalWrite(LOSES_LED, LOW);
      }
    } else if (address == 1) {          // Port A Data Direction
      // PA0 - PA7 Data Direction Register (PADD)
      if (value == 128) {
        PORTD = 0x00;     // All inputs.
        PORTD |= 0xFF;    // Input pullups.
      } else {
        DDRD = value;     // All outputs.
      }
    } else if (address == 2) {          // Port B Data
      // PB0 - PB7 Data Register (SBD)
      // Remember the last value written to this register.
      sbc_value = value;

      // The bottom 3 bits of the value control 8 IO lines. 
      // Only 1 of the 8 will be turned on. Start with everything off.
      digitalWrite(DISPLAY_1, LOW);
      digitalWrite(DISPLAY_2, LOW);
      digitalWrite(DISPLAY_3, LOW);
      digitalWrite(DISPLAY_4, LOW);
      keyboard_row = 0;
      digitalWrite(BUZZER_1, LOW);
      digitalWrite(BUZZER_2, LOW);

      // Determine which line to enable.
      switch (value & 0x07) {
        case 0:
          digitalWrite(DISPLAY_1, HIGH);
          break;
        case 1:
          digitalWrite(DISPLAY_2, HIGH);
          break;
        case 2:
          digitalWrite(DISPLAY_3, HIGH);
          break;
        case 3:
          digitalWrite(DISPLAY_4, HIGH);
          break;
        case 4:
          keyboard_row = 1;
          break;
        case 5:
          keyboard_row = 2;
          break;
        case 6:
          digitalWrite(BUZZER_1, HIGH);
          break;
        case 7:
          digitalWrite(BUZZER_2, HIGH);
          break;
        default:
          break;
      }
      // Check for CHECK LED.
      if ((value & 0b00001000) > 0) {
        digitalWrite(CHECK_LED, HIGH);
      } else {
        digitalWrite(CHECK_LED, LOW);
      }
      // Check for BLACK or WHITE LED.
      if ((value & 0b00010000) > 0) {
        digitalWrite(B_W_LED, LOW);
      } else {
        digitalWrite(B_W_LED, HIGH);
      }
    } else if (address == 3) {            // Port B Data Direction
      // PB0 - PB7 Data Direction Register (PBDD)
      // Does not directly map to specific pins. Will always be 0x7F.
    }
  }
}

[NOTE 2] - It took me a while to clue into the fact that the program was
           expecting the key pressed bit to be a 0 and all other bits 1.
           Dusting off my 6502 assembler and looking at the code helped me 
           to figure this out

So the updated working code has been posted to GitHub.

And Now For The "ish" Part

Better if I just show you.

So the emulator is running. When I uploaded the file, YouTube apparently muted the piezo speaker audio which sounds just terrible if you listen closely. This is because the application is running about 8 times slower under the emulator than the original. Disappointing since I was given to understand that the emulator, when used with the KIM-1 Uno and Pro Micro, ran pretty well. I'll have to take a look at Oscar's code to see if somehow I have introduced a bunch of overhead in my code. 

In the mean time I just got all of the parts to build a KIM-1 Uno which I have been meaning to do for a while now. That way I'll have something concrete to compare against.

I have also ordered a few ESP32s in case I have to throw more horsepower at the problem. 

Discussions

Ken Yap wrote 02/04/2024 at 05:23 point

digitalRead() and digitalWrite() cause quite a bit of overhead especially if the sketch is handling the multiplexing and key scanning hundreds of times a second. You might like to look into port manipulation. The Arduino documentation talks about it in https://docs.arduino.cc/hacking/software/PortManipulation A case study is here: https://hackaday.io/page/21811-halving-the-execution-time-of-an-arduino-sketch

  Are you sure? yes | no

Michael Gardi wrote 02/05/2024 at 23:26 point

For sure there is a lot of pin manipulation.  I did code in some port stuff but at the end of the day I though it was unrealistic to expect an 8X improvement in speed.  Switching to an ESP32 did the trick.

  Are you sure? yes | no