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"?