From e29180ba0133936f4071b7fce8e3c61256adf07d Mon Sep 17 00:00:00 2001 From: DTB Date: Wed, 3 Apr 2024 15:11:55 -0600 Subject: [PATCH] start of a 4004 emulator --- 3mu/Intel4004.c | 184 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 3mu/Intel4004.c diff --git a/3mu/Intel4004.c b/3mu/Intel4004.c new file mode 100644 index 0000000..88b9da1 --- /dev/null +++ b/3mu/Intel4004.c @@ -0,0 +1,184 @@ +/* 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 */ +}