My EEPROM simulator doesn't work, and the CPU ignores jump instructions, or doesn't interpret them properly

I have made a simulation CPU, which has a very primitive instruction set. In Wokwi the EEPROM doesn't work, and IRL the jump instruction doesn't work. What did I do wrong?

Here is the link to the project.: https://wokwi.com/projects/456310100569093121

Any good help will be appreciated.

Try posting an annotated schematic showing exactly how you connected everything. The frizzy from your link does not show up very well. Also posting the code would help.

Here's the Arduino sketch:


// NOTICE!!!!! This CPU emulator uses Little-Endian byte ordering!!!

byte instruction = 0;
byte operand1 = 0;
byte operand2 = 0;
byte operand3 = 0;
unsigned int adresa = 0;  // program counter
uint8_t reljmp = 0;  // signed 8-bit integer, can jump down to -128 bytes, or up to 127 bytes relative to the address of the instruction

byte accumulator = 0; // the A register in assembly
byte xreg = 0;
byte yreg = 0;
byte statreg = 0;  // 4 flags: zero, ovf, irq disable, carry bit; 4 bottom-most bits are unused for now

int stack[258] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
byte stackpointer = 255;  // gets decremented on every PHA instruction, incremented on every PLA instruction (PHA - push A to stack, PLA - pull A from stack)

byte opmem[1026] = {0, 0, 0, 0};

unsigned long past = 0;
int clocktime = 10000;

byte var1h = 0;
byte var1l = 0;
unsigned int var2 = 0;

void setup() {

  pinMode(2, OUTPUT);  // srclk
  pinMode(3, OUTPUT);  // serial data
  pinMode(4, OUTPUT);  // ltclk
  pinMode(6, INPUT_PULLUP);
  pinMode(7, INPUT_PULLUP);
  pinMode(8, INPUT_PULLUP);
  pinMode(9, INPUT_PULLUP);
  pinMode(10, INPUT_PULLUP);
  pinMode(11, INPUT_PULLUP);
  pinMode(12, INPUT_PULLUP);
  pinMode(13, INPUT_PULLUP);
  Serial.begin(9600);
  MEMread(0);
delay(2100);

/*while(millis() < 2) {
adresa = 0b1111111111111111;
}*/
adresa = 0;
//  Serial.println((MEMread(adresa)));
//var1l = MEMread(0b0111111111111100);
//var1h = MEMread(0b0111111111111101);  // reads reset vector at address 7ffc and 7ffd (little endian)
//var2 = ((var1h << 8) | (var1l));

//adresa = var2;

}

void loop() {

if(micros() - past > clocktime) {
  adresa = adresa + 1;  // increment the program counter 1000 times per second
  adresa = adresa % 0b0111111111111111;
  past = micros();
instruction = MEMread(adresa);
 // Serial.println(instruction);

switch(instruction) {

  case 0:
   // brk - brake instruction (permanently halts execution, recoverable only through reset)
   while(1) {delay(1);}
    break;

  case 1:  // lda immediate
    accumulator = MEMread(adresa + 1);
    adresa = adresa + 1;
    break;

  case 2:  // lda from address
    var1h = MEMread(adresa + 2);
    var1l = MEMread(adresa + 1);
    var2 = ((var1h << 8) | (var1l));
    accumulator = opmem[var2];
    adresa = adresa + 2;
    break;

  case 3:  // tay
    yreg = accumulator;
    break;

  case 4:  // tax
    xreg = accumulator;
    break;

  case 5:  // tya
    accumulator = yreg;
    break;

  case 6:  // txa
    accumulator = xreg;
    break;

  case 7:  // sta (there is just one, and it stores to a RAM address)
    var1h = MEMread(adresa + 2);
    var1l = MEMread(adresa + 1);
    var2 = ((var1h << 8) | (var1l));
    opmem[var2] = accumulator;
    adresa = adresa + 2;
    break;

  case 8:  // pha
    stack[stackpointer] = accumulator;
    stackpointer = stackpointer - 1;
    break;

  case 9:  // pla
    stackpointer = stackpointer + 1;
    accumulator = stack[stackpointer];
    stack[stackpointer] = 0;
    break;

  case 10:  // send character over serial port
    Serial.write(MEMread(adresa + 1));
    adresa = adresa + 1;
    break;

  case 11:  // jsr - jump to subroutine
    stack[stackpointer] = adresa;
    stackpointer = stackpointer - 1;
    break;

  case 12:  // rts - return from subroutine
    adresa = stack[stackpointer];
    stackpointer = stackpointer + 1;
    break;

  case 13:  // bne - branch (if) not equal
    reljmp = MEMread(adresa + 1);
    if(!(statreg & 128)) {
      adresa = adresa + reljmp;
    }
    break;

  case 14:  // beq - branch (if) equal
    reljmp = MEMread(adresa + 1);
    if((statreg & 128)) {
      adresa = adresa + reljmp;
    }
    break;

  case 15:  // cmp - compare immediate (so far the only compare instruction) uses zero flag if num goes below 1
    byte comparator = MEMread(adresa + 1);
    int result = accumulator - comparator;
    if(result) {
      statreg = statreg & 127;
    } else {
      statreg = statreg | 128;
    }
    adresa = adresa + 1;
    break;

  case 16:  // adc - add immediate to accumulator with carry, save result to accumulator and carry bit
    byte toadd = MEMread(adresa + 1);
    if((toadd + accumulator) > 255) {statreg = statreg | 16;} else {statreg = statreg & 0b11101111;}  // sets or clears carry bit
    accumulator = accumulator + toadd;
    adresa = adresa + 1;
    break;

  case 17:  // sbc - subtract immediate from accumulator, and save result to accumulator (borrow the carry bit in case num becomes negative)
    byte subtr = MEMread(adresa + 1);
    statreg = statreg | 16;
    accumulator = accumulator - subtr;
    if((accumulator & 128)) {statreg = statreg & 0b11101111;}
    adresa = adresa + 1;
    break;

  case 18:  // iny - increments the Y register
    yreg = yreg + 1;
    break;

  case 19:  // dey - decrements the Y register
    yreg = yreg - 1;
    break;

  case 20:  // inc - increments a byte in memory (at an address)
    var1h = MEMread(adresa + 2);
    var1l = MEMread(adresa + 1);
    var2 = ((var1h << 8) | (var1l));
    opmem[var2] = opmem[var2] + 1;
    adresa = adresa + 2;
    break;

  case 21:  // dec - decrements a byte in memory (at an address)
    var1h = MEMread(adresa + 2);
    var1l = MEMread(adresa + 1);
    var2 = ((var1h << 8) | (var1l));
    opmem[var2] = opmem[var2] - 1;
    adresa = adresa + 2;
    break;

  case 22:  // jmp - jump to location (absolute)
    var1h = MEMread(adresa + 2);
    var1l = MEMread(adresa + 1);
    var2 = ((var1h << 8) | (var1l));
    adresa = var2;
    break;

  default:
    adresa = adresa;
    break;

}

}

}

byte dataRD(unsigned int addr) {
byte daata = 0;

  daata = (128 * digitalRead(13)) + (64 * digitalRead(12)) + (32 * digitalRead(11)) + (16 * digitalRead(10)) + (8 * digitalRead(9)) + (4 * digitalRead(8)) + (2 * digitalRead(7)) + (1 * digitalRead(6));

return daata;
}

void shift(unsigned int adres) {
  digitalWrite(4, LOW);
  byte addres1 = adres >> 8;
  unsigned int addresint = adres << 8;
  byte addres2 = addresint >> 8;

  shiftOut(3, 2, MSBFIRST, addres1);
  shiftOut(3, 2, MSBFIRST, addres2);

  digitalWrite(4, HIGH);
}

byte MEMread(unsigned int addrs) {
  // reads a byte at an address, also takes amount of bits (both values are mandatory)
  byte udaj = 0;
  shift(addrs);
  udaj = dataRD(addrs);

  return udaj;
}

Here's the chip's code:

// Wokwi Custom Chip - For docs and examples see:
// https://docs.wokwi.com/chips-api/getting-started
//
// SPDX-License-Identifier: MIT
// Copyright 2023 Variable-Voltage Variable-Frequency

#include "wokwi-api.h"
#include <stdio.h>
#include <stdlib.h>

const uint8_t pamat[33000] = {10, 50, 10, 50, 10, 50, 10, 50, 10, 50, 22, 0, 0, 0, 0};

typedef struct {
pin_t adr1;
pin_t adr2;
pin_t adr3;
pin_t adr4;
pin_t adr5;
pin_t adr6;
pin_t adr7;
pin_t adr8;
pin_t adr9;
pin_t adr10;
pin_t adr11;
pin_t adr12;
pin_t adr13;
pin_t adr14;
pin_t adr15;
pin_t data1;
pin_t data2;
pin_t data3;
pin_t data4;
pin_t data5;
pin_t data6;
pin_t data7;
pin_t data8;
pin_t hodiny;
bool lok1;
bool lok2;
bool lok3;
bool lok4;
bool lok5;
bool lok6;
bool lok7;
bool lok8;
bool lok9;
bool lok10;
bool lok11;
bool lok12;
bool lok13;
bool lok14;
bool lok15;
} chip_state_t;

static void chip_pin_change(void *user_data, pin_t pin, uint32_t value) {
  chip_state_t *chip = (chip_state_t*)user_data;

  chip->lok1 = pin_read(chip->adr1);
  chip->lok2 = pin_read(chip->adr2);
  chip->lok3 = pin_read(chip->adr3);
  chip->lok4 = pin_read(chip->adr4);
  chip->lok5 = pin_read(chip->adr5);
  chip->lok6 = pin_read(chip->adr6);
  chip->lok7 = pin_read(chip->adr7);
  chip->lok8 = pin_read(chip->adr8);
  chip->lok9 = pin_read(chip->adr9);
  chip->lok10 = pin_read(chip->adr10);
  chip->lok11 = pin_read(chip->adr11);
  chip->lok12 = pin_read(chip->adr12);
  chip->lok13 = pin_read(chip->adr13);
  chip->lok14 = pin_read(chip->adr14);
  chip->lok15 = pin_read(chip->adr15);

  long adressa = (((chip->lok1)) + (2*(chip->lok2)) + (4*(chip->lok3)) + (8*(chip->lok4)) + (16*(chip->lok5)) + (32*(chip->lok6)) + (64*(chip->lok7)) + (128*(chip->lok8)) + (256*(chip->lok9)) + (512*(chip->lok10)) + (1024*(chip->lok11)) + (2048*(chip->lok12)) + (4096*(chip->lok13)) + (8192*(chip->lok14)) + (16384*(chip->lok15)));

  uint8_t data = pamat[adressa];

  pin_write(chip->data1, (data & 1));
  pin_write(chip->data2, (data & 2));
  pin_write(chip->data3, (data & 4));
  pin_write(chip->data4, (data & 8));
  pin_write(chip->data5, (data & 16));
  pin_write(chip->data6, (data & 32));
  pin_write(chip->data7, (data & 64));
  pin_write(chip->data8, (data & 128));

}

void chip_init() {
  chip_state_t *chip = malloc(sizeof(chip_state_t));

  chip->adr1 = pin_init("ADDR1", INPUT_PULLUP);
  chip->adr2 = pin_init("ADDR2", INPUT_PULLUP);
  chip->adr3 = pin_init("ADDR3", INPUT_PULLUP);
  chip->adr4 = pin_init("ADDR4", INPUT_PULLUP);
  chip->adr5 = pin_init("ADDR5", INPUT_PULLUP);
  chip->adr6 = pin_init("ADDR6", INPUT_PULLUP);
  chip->adr7 = pin_init("ADDR7", INPUT_PULLUP);
  chip->adr8 = pin_init("ADDR8", INPUT_PULLUP);
  chip->adr9 = pin_init("ADDR9", INPUT_PULLUP);
  chip->adr10 = pin_init("ADDR10", INPUT_PULLUP);
  chip->adr11 = pin_init("ADDR11", INPUT_PULLUP);
  chip->adr12 = pin_init("ADDR12", INPUT_PULLUP);
  chip->adr13 = pin_init("ADDR13", INPUT_PULLUP);
  chip->adr14 = pin_init("ADDR14", INPUT_PULLUP);
  chip->adr15 = pin_init("ADDR15", INPUT_PULLUP);
  chip->hodiny = pin_init("CLK", INPUT_PULLUP);
  chip->data1 = pin_init("DATA1", OUTPUT);
  chip->data2 = pin_init("DATA2", OUTPUT);
  chip->data3 = pin_init("DATA3", OUTPUT);
  chip->data4 = pin_init("DATA4", OUTPUT);
  chip->data5 = pin_init("DATA5", OUTPUT);
  chip->data6 = pin_init("DATA6", OUTPUT);
  chip->data7 = pin_init("DATA7", OUTPUT);
  chip->data8 = pin_init("DATA8", OUTPUT);

  //printf("Hello from custom chip!\n");

  const pin_watch_config_t config = {
    .edge = BOTH,
    .pin_change = chip_pin_change,
    .user_data = chip,
  };

  pin_watch(chip->hodiny, &config);

}

I might be unable to provide a proper schematic within a reasonable time frame.

Also, what do you mean by "frizzy"?

The diagram.json file has all of the connections listed.
Here it is:

{
  "version": 1,
  "author": "Variable-Voltage Variable-Frequency",
  "editor": "wokwi",
  "parts": [
    { "type": "wokwi-arduino-nano", "id": "nano", "top": 4.8, "left": -0.5, "attrs": {} },
    { "type": "chip-at28c256", "id": "chip1", "top": -200.58, "left": -216, "attrs": {} },
    { "type": "wokwi-74hc595", "id": "sr1", "top": 123.6, "left": 72.64, "attrs": {} },
    { "type": "wokwi-74hc595", "id": "sr2", "top": 123.6, "left": -4.16, "attrs": {} },
    {
      "type": "wokwi-clock-generator",
      "id": "clk1",
      "top": -57.6,
      "left": -192,
      "attrs": { "frequency": "1k" }
    }
  ],
  "connections": [
    [ "chip1:VCC", "nano:5V", "red", [ "h0", "v-19.2", "h384", "v297.6", "h-48" ] ],
    [ "chip1:GND", "nano:GND.2", "black", [ "h28.8", "v-19.2", "h57.6", "v-28.8", "h259.2" ] ],
    [ "chip1:OE", "chip1:GND", "green", [ "h0" ] ],
    [ "sr2:VCC", "sr1:VCC", "red", [ "v-8.4", "h67.2" ] ],
    [ "sr1:VCC", "nano:5V", "red", [ "v-37.2", "h38.4" ] ],
    [ "sr2:GND", "sr1:GND", "black", [ "v9.6", "h76.8" ] ],
    [ "sr1:GND", "nano:GND.1", "black", [ "h9.6", "v-57.6", "h-9.1" ] ],
    [ "sr2:Q7S", "sr1:DS", "green", [ "h0", "v-18", "h28.8" ] ],
    [ "sr1:OE", "sr2:OE", "green", [ "v-18", "h-57.6" ] ],
    [ "sr2:DS", "nano:3", "green", [ "v-37.2", "h-28.8", "v-57.6", "h115.2" ] ],
    [ "sr2:SHCP", "nano:2", "green", [ "v-46.8", "h-48", "v-38.4", "h115.2", "v-33.6" ] ],
    [ "sr2:Q0", "chip1:ADDR1", "green", [ "h96", "v-354", "h-393.6" ] ],
    [ "sr2:Q1", "chip1:ADDR2", "green", [ "h115.2", "v-374.4", "h-403.2" ] ],
    [ "sr2:Q2", "chip1:ADDR3", "green", [ "h96", "v-384", "h-393.6" ] ],
    [ "sr2:Q3", "chip1:ADDR4", "green", [ "h86.4", "v-364.8", "h-393.6" ] ],
    [ "sr2:Q4", "chip1:ADDR5", "green", [ "h86.4", "v-374.4", "h-403.2" ] ],
    [ "sr2:Q5", "chip1:ADDR6", "green", [ "h67.2", "v-384", "h-393.6" ] ],
    [ "sr2:Q6", "chip1:ADDR7", "green", [ "h57.6", "v-384", "h-393.6" ] ],
    [ "sr2:Q7", "chip1:ADDR8", "green", [ "v-364.8", "h-345.6" ] ],
    [ "sr1:Q0", "chip1:ADDR9", "green", [ "v-171.6", "h-192", "v-38.4" ] ],
    [ "sr1:Q1", "chip1:ADDR10", "green", [ "v-220.8", "h-201.6" ] ],
    [ "sr1:Q2", "chip1:ADDR11", "green", [ "v0" ] ],
    [ "sr1:Q3", "chip1:ADDR12", "green", [ "h-19.2", "v-240" ] ],
    [ "sr1:Q4", "chip1:ADDR13", "green", [ "h-48", "v-249.6" ] ],
    [ "sr1:Q5", "chip1:ADDR14", "green", [ "h-67.2", "v-259.2" ] ],
    [ "sr1:Q6", "chip1:ADDR15", "green", [ "h-96", "v-268.8" ] ],
    [ "sr2:STCP", "nano:4", "green", [ "v-104.4", "h57.6" ] ],
    [ "sr2:SHCP", "sr1:SHCP", "green", [ "v-27.6", "h57.6" ] ],
    [ "sr1:STCP", "sr2:STCP", "green", [ "v-37.2", "h-57.6" ] ],
    [ "sr2:MR", "sr1:MR", "green", [ "v-18", "h76.8" ] ],
    [ "sr1:MR", "nano:5V", "green", [ "v0" ] ],
    [ "nano:6", "chip1:DATA1", "green", [ "v0" ] ],
    [ "nano:7", "chip1:DATA2", "green", [ "v0" ] ],
    [ "nano:8", "chip1:DATA3", "green", [ "v0" ] ],
    [ "nano:9", "chip1:DATA4", "green", [ "v0" ] ],
    [ "nano:10", "chip1:DATA5", "green", [ "v0" ] ],
    [ "nano:11", "chip1:DATA6", "green", [ "v0" ] ],
    [ "nano:12", "chip1:DATA7", "green", [ "v0" ] ],
    [ "nano:13", "chip1:DATA8", "green", [ "v0" ] ],
    [ "clk1:CLK", "chip1:CLK", "green", [ "v-144", "h19.2" ] ]
  ],
  "dependencies": {}
}

AT28C256 is a Parallel EEPROM.

First write a data byte (say: 0x23) at locatio 0x0010 and then read it back.
1. Assert address on: A0 - A14 lines.
2. H ----> OE/
3. L ----> CE/
4. 0x23 ---> IO0 - IO7
5. H-L-H ---> WE/ (see data sheets for write pulse timing and write delay)
//===== now read back data from the same location and compare with 0x23==

I wrote a simulation of the EEPROM, which is for now uneditable by the Arduino connected to it. I only need it to output data, which it is not doing very well. Open the Wokwi link and run it to check it yourself, and save yourself a copy.

Do you mean

  adresa = adresa & 0b0111111111111111;

???

You know that arrays exist?

No, that is supposed to do a modulo operation. Rewritten, that line is: adresa = adresa % 32767;.

I don't trust them in the chip C code when they have something to do with IO.

No wonder why it won't work ::-(

I only read the first 32768 addresses of the 16-bit address space given by my shift registers. That's why the modulo is there in the first place. EDIT: the code has a slight mistake. It only reads 32767 addresses (0 is the first addresses)

Because your code is wrong.

adresa % 32767 will return addresses from 0 to 32766.

As @DrDiettrich pointed out, you should have used adresa & 32767 which masks off the lower 15 bits of the number. That will give you the full range of addresses from 0 to 32767.

Pete

I literally pointed that out! Read what I write!

I already knew that when I wrote that reply!

OK. Just out of curiosity, why do you exclude address 32767?

Pete

I didn't mean to, it's a mistake in the code. A small oversight.

Doesn't work how?

You load adresa with the new PC value, and then at the beginning of loop() you increment it, so it won't go to quite the right place, for one thing.

The conditional branches probably have the same problem. In addition, you don't increment adresa past the offset in the cases where the branch isn't taken...

jsr never reads or changes the destination PC.

It's "traditional" to increment the PC (adresa in your case) as bytes are read , so that it's always pointing at the NEXT byte in the instruction space( which reflects how actual hardware would probably work). giving something like:

void loop() {
  adresa = adresa % 32768;  // truncate to available memory
  instruction = MEMread(adresa++);

//    :

  case 13:  // bne - branch (if) not equal
    reljmp = MEMread(adresa++);
    if(!(statreg & 128)) {
      adresa = adresa + reljmp;
    }
    break;

  case 14:  // beq - branch (if) equal
    reljmp = MEMread(adresa++);
    if((statreg & 128)) {
      adresa = adresa + reljmp;
    }
    break;

//      :

  case 22:  // jmp - jump to location (absolute)
    var1l = MEMread(adresa++);
    var1h = MEMread(adresa++);
    var2 = ((var1h << 8) | (var1l));
    adresa = var2;
    break;

(Edit: fix endianness of jmp)

The unconditional jump (decimal 22) literally doesn't happen. I think that the code reads 0x00 in the next "clock cycle" instead of reading 0x0000 in the same one as the jump instruction was read, and permanently halting the "CPU". Also, it reads 16-bit values in little endian, so when assembling for it, 0x7ffe becomes 0xfe7f in the external parallel EEPROM.