/* Intel 4004 emulator. 2024, DTB. Incomplete. * I didn't know anything about the i4004 before starting to write this file. I * know slightly more now. */ /* Name - Bits 11_10__9__8__7__6__5__4__3__2__1__0 * Bit - 1 | | | bit -> || * Nybble - 4 | | nybble -> |_________| * Byte - 8 | byte -> |_____________________| * Word - 12 word -> |_________________________________| */ /* 24---------------------| * * words 12---------|12---------| * bytes 8------|8------|8------| * nybbles 4--|4--|4--|4--|4--|4--| * bits 111111111111111111111111 */ #if __STDC_VERSION__ >= 199901L /* C99 type definitions */ # include typedef _Bool Bit; typedef uint8_t Nybble; typedef uint8_t Byte; typedef uint16_t Word; #else /* C89 type definitions - probably fine but may need per-platform tweaks. */ /* Must fit at least 1 bit. */ typedef unsigned char Bit; /* Must fit at least 4 bits. */ typedef unsigned char Nybble; /* Must fit at least 8 bits. */ typedef unsigned char Byte; /* Must fit at least 12 bits. */ typedef unsigned int Word; #endif #define LOW_NYBBLE(b) ((b) & 0x0F) /* 0b 0000 1111 */ #define HIGH_NYBBLE(b) ((b) & 0xF0) /* 0b 1111 0000 */ /* Intel 4004 datasheet */ /* */ struct Intel4004_State { Bit carry; Bit test; /* A pin on the chip. */ Nybble acc; /* Accumulator */ Nybble reg[16]; /* R0 through RF */ Word pc[4]; /* PC0 through PC3 */ Byte *rom; Byte *ram; }; /* Intel 4004 instruction set */ /* */ enum Intel4004_Instruction { /* Summary Mnemonic Hex Binary opcode */ /* No-op */ NOP = 0x0, /* 0b 0000 0000 */ /* INVALID 0b 0000 0001 through 0b 0000 1111 */ /* Jump conditional */ JCN = 0x10, /* 0b 0001 CCCC */ /* Fetch immediate */ FIM = 0x20, /* 0b 0010 RRR0 */ /* Send register control */ SRC = 0x21, /* 0b 0010 RRR1 */ /* Fetch indirect */ FIN = 0x30, /* 0b 0011 RRR0 */ /* Jump indirect */ JIN = 0x31, /* 0b 0011 RRR1 */ /* Jump unconditional */ JUN = 0x40, /* 0b 0100 AAAA */ /* Jump to subroutine */ JMS = 0x50, /* 0b 0101 AAAA */ /* Increment */ INC = 0x60, /* 0b 0110 RRRR */ /* Increment and skip */ ISZ = 0x70, /* 0b 0111 RRRR */ /* Add */ ADD = 0x80, /* 0b 1000 RRRR */ /* Subtract */ SUB = 0x90, /* 0b 1001 RRRR */ /* Load */ LD = 0xA0, /* 0b 1010 RRRR */ /* Exchange */ XCH = 0xB0, /* 0b 1011 RRRR */ /* Branch back and load */ BBL = 0xC0, /* 0b 1100 DDDD */ /* Load immediate */ LDM = 0xD0, /* 0b 1101 DDDD */ /* Write main memory */ WRM = 0xE0, /* 0b 1110 0000 */ /* Write RAM port */ WMP = 0xE1, /* 0b 1110 0001 */ /* Write ROM port */ WRR = 0xE2, /* 0b 1110 0010 */ /* INVALID 0xE3 0b 1110 0011 */ /* Write status char 0 */ WR0 = 0xE4, /* 0b 1110 0100 */ /* Write status char 1 */ WR1 = 0xE5, /* 0b 1110 0101 */ /* Write status char 2 */ WR2 = 0xE6, /* 0b 1110 0110 */ /* Write status char 3 */ WR3 = 0xE7, /* 0b 1110 0111 */ /* Subtract main memory */ SBM = 0xE8, /* 0b 1110 1000 */ /* Read main memory */ RDM = 0xE9, /* 0b 1110 1001 */ /* Read ROM port */ RDR = 0xEA, /* 0b 1110 1010 */ /* Add main memory */ ADM = 0xEB, /* 0b 1110 1011 */ /* Read status char 0 */ RD0 = 0xEC, /* 0b 1110 1100 */ /* Read status char 1 */ RD1 = 0xED, /* 0b 1110 1101 */ /* Read status char 2 */ RD2 = 0xEE, /* 0b 1110 1110 */ /* Read status char 3 */ RD3 = 0xEF, /* 0b 1110 1111 */ /* Clear both */ CLB = 0xF0, /* 0b 1111 0000 */ /* Clear carry */ CLC = 0xF1, /* 0b 1111 0001 */ /* Increment accumulator */ IAC = 0xF2, /* 0b 1111 0010 */ /* Complement carry */ CMC = 0xF3, /* 0b 1111 0011 */ /* Complement */ CMA = 0xF4, /* 0b 1111 0100 */ /* Rotate left */ RAL = 0xF5, /* 0b 1111 0101 */ /* Rotate right */ RAR = 0xF6, /* 0b 1111 0110 */ /* Transfer carry and clear */ TCC = 0xF7, /* 0b 1111 0111 */ /* Decrement accumulator */ DAC = 0xF8, /* 0b 1111 1000 */ /* Transfer carry subtract */ TCS = 0xF9, /* 0b 1111 1001 */ /* Set carry */ STC = 0xFA, /* 0b 1111 1010 */ /* Decimal adjust accumulator */ DAA = 0xFB, /* 0b 1111 1011 */ /* Keyboard process */ KBP = 0xFC, /* 0b 1111 1100 */ /* Designate command line */ DCL = 0xFD /* 0b 1111 1101 */ /* INVALID 0xFE 0b 1111 1110 */ /* INVALID 0xFF 0b 1111 1111 */ }; /* Does nothing. */ void Intel4004_NOP(struct Intel4004_State *state){ /* 0b 0000 0000 */ ++*state->pc; return; } #define INTEL4004_JCN_COND_INVERT 0x01 /* 0b 0000 0001 */ #define INTEL4004_JCN_COND_ACCZERO 0x02 /* 0b 0000 0010 */ #define INTEL4004_JCN_COND_CARRYON 0x04 /* 0b 0000 0100 */ #define INTEL4004_JCN_COND_TESTON 0x08 /* 0b 0000 1000 */ #define INTEL4004_JCN_COND_ISINVERT(b) ((b) & INTEL4004_JCN_COND_INVERT) #define INTEL4004_JCN_COND_ISACCZERO(b) ((b) & INTEL4004_JCN_COND_ACCZERO) #define INTEL4004_JCN_COND_ISCARRYON(b) ((b) & INTEL4004_JCN_COND_CARRYON) #define INTEL4004_JCN_COND_ISTESTON(b) ((b) & INTEL4004_JCN_COND_TESTON) /* Jumps conditionally to the address in the following byte. * If *state->pc & 0x01, the condition is inverted. * If *state->pc & 0x02, a zero accumulator satisfies the condition. * If *state->pc & 0x04, a one carry flag satisfies the condition. * If *state->pc & 0x08, a one test flag satisfies the condition. */ void Intel4004_JCN(struct Intel4004_State *state){ /* 0b 0001 CCCC */ *state->pc = !INTEL4004_JCN_COND_ISINVERT(state->rom[*state->pc]) && ( (INTEL4004_JCN_COND_ISACCZERO(state->rom[*state->pc]) && state->acc == 0) || (INTEL4004_JCN_COND_ISCARRYON(state->rom[*state->pc]) && state->carry == 1) || (INTEL4004_JCN_COND_ISTESTON(state->rom[*state->pc]) && state->test == 1)) ? state->rom[*state->pc + 1] : *state->pc + 2; return; } /* Macros to get the register pair from FIM, SRC, FIN, and JIN. * INTEL4004_REGPAIR(opcode) returns the first register, the second register is * the increment. * High nybbles tend to be stored in the first register and low nybbles in the * second. */ #define INTEL4004_REGPAIR_BYTEMASK 0x0E /* 0b 0000 1110 */ #define INTEL4004_REGPAIR(b) ((b) & INTEL4004_REGPAIR_BYTEMASK) /* Fetches an immediate byte from ROM. */ void Intel4004_FIM(struct Intel4004_State *state){ /* 0b 0010 RRR0 */ state->reg[INTEL4004_REGPAIR(state->rom[*state->pc])] = HIGH_NYBBLE(state->rom[*state->pc + 1]); state->reg[INTEL4004_REGPAIR(state->rom[*state->pc]) + 1] = LOW_NYBBLE(state->rom[*state->pc + 1]); *state->pc += 2; return; } void Intel4004_SRC(struct Intel4004_State *state){ /* 0b 0010 RRR1 */ } void Intel4004_FIN(struct Intel4004_State *state){ /* 0b 0011 RRR0 */ }