velxio/test/test_intel/test_8086/8086.c

894 lines
34 KiB
C

/*
* Intel 8086 emulator — clean-room implementation as a velxio custom chip.
*
* Source (in autosearch/pdfs/):
* [I86] Intel 8086 Family User's Manual, October 1979 (order 9800722-03).
* All citations are PDF-index pages; manual section numbers in parens.
* See autosearch/15_8086_authoritative_spec.md for the digested spec and
* autosearch/16_8086_reference_implementations.md for cross-validation
* sources (8086tiny / MartyPC / YJDoc2 — all permissively licensed).
*
* Scope of this initial implementation:
* - 40-pin minimum-mode pin contract.
* - Reset state: CS=0xFFFF, IP=0, all other segs=0; first fetch at the
* physical address 0xFFFF0 with ALE strobing.
* - Bus cycle T1-T4 (instruction-per-tick collapse): drive AD0..AD15 with
* low 16 addr, A16..A19 with high 4 addr, ALE pulse, then either
* RD̅ for reads or WR̅ for writes with M/IO and DT/R̅ properly set.
* - 20-bit physical addressing: (segment<<4)+offset, wrap at 1 MB.
* - Register file (AX/BX/CX/DX with high/low halves, SI/DI/BP/SP, IP,
* CS/DS/ES/SS, FLAGS).
* - ModR/M decode for memory operands (Table 4-10).
* - Subset of ISA: NOP, HLT, MOV reg/imm, MOV r/m, ADD/SUB/AND/OR/XOR/CMP,
* INC/DEC, PUSH/POP, JMP near/short, conditional jumps, CALL/RET, INT 3.
*
* Out of scope (deferred):
* - String ops (MOVS/CMPS/SCAS/LODS/STOS) with REP prefix.
* - MUL/DIV/IMUL/IDIV.
* - BCD adjust (DAA/DAS/AAA/AAS/AAM/AAD).
* - Port I/O instructions (IN/OUT).
* - Hardware interrupts (NMI/INTR vectoring).
* - Maximum-mode bus protocol.
* - Cycle-accurate prefetch queue.
* - Undocumented opcodes (POP CS, SALC).
*/
#include "velxio-chip.h"
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
/* ─── Flag bits ────────────────────────────────────────────────────────── */
#define F_CF 0x0001
#define F_PF 0x0004
#define F_AF 0x0010
#define F_ZF 0x0040
#define F_SF 0x0080
#define F_TF 0x0100
#define F_IF 0x0200
#define F_DF 0x0400
#define F_OF 0x0800
/* Reserved bits per [I86] Fig 2-9: bit 1 reads as 1, bits 12-15 as 1
per SingleStepTests canonicalisation. Bit 3, 5 read as 0. */
#define F_RESERVED_ON (0xF002)
#define F_RESERVED_OFF (0x0028)
/* Segment-register codes (matches SR field encoding, [I86] Table 4-11) */
#define SEG_ES 0
#define SEG_CS 1
#define SEG_SS 2
#define SEG_DS 3
/* Status byte values for M/IO, DT/R̅ during a cycle:
M/IO: 1 = memory, 0 = I/O ([I86] PDF p.249 — 8086 polarity) */
typedef struct {
/* Pin handles */
vx_pin ad[16]; /* AD0..AD15 — multiplexed addr/data */
vx_pin a[4]; /* A16..A19 — multiplexed addr/status */
vx_pin ale;
vx_pin rd, wr;
vx_pin mio;
vx_pin dtr;
vx_pin den;
vx_pin hold, hlda;
vx_pin intr, nmi, inta;
vx_pin reset_, ready, test_;
vx_pin clk;
vx_pin mnmx;
vx_pin bhe;
vx_pin vcc, gnd;
vx_timer cycle_timer;
/* Register file. Pairs aliased via union for byte access. */
union { struct { uint8_t al, ah; }; uint16_t ax; };
union { struct { uint8_t cl, ch; }; uint16_t cx; };
union { struct { uint8_t dl, dh; }; uint16_t dx; };
union { struct { uint8_t bl, bh; }; uint16_t bx; };
uint16_t sp, bp, si, di;
uint16_t cs, ds, es, ss;
uint16_t ip;
uint16_t flags;
/* State */
bool halted;
bool reset_active;
bool driving_ad;
/* Segment override for the current instruction (-1 = none) */
int seg_override;
/* Last-cycle latched address (for status drives) */
uint32_t last_phys;
} cpu_t;
static cpu_t G;
/* ─── AD/A bus helpers ──────────────────────────────────────────────────── */
static void drive_ad(uint16_t v) {
for (int i = 0; i < 16; i++) {
vx_pin_set_mode(G.ad[i], VX_OUTPUT);
vx_pin_write(G.ad[i], (v >> i) & 1);
}
G.driving_ad = true;
}
static void release_ad(void) {
if (!G.driving_ad) return;
for (int i = 0; i < 16; i++) vx_pin_set_mode(G.ad[i], VX_INPUT);
G.driving_ad = false;
}
static void drive_a_high(uint8_t hi4) {
for (int i = 0; i < 4; i++) {
vx_pin_write(G.a[i], (hi4 >> i) & 1);
}
}
static uint16_t read_ad(void) {
uint16_t v = 0;
for (int i = 0; i < 16; i++) if (vx_pin_read(G.ad[i])) v |= (1u << i);
return v;
}
/* Compute 20-bit physical address from segment:offset, mod 1 MB. */
static uint32_t physical(uint16_t segment, uint16_t offset) {
return (((uint32_t)segment << 4) + offset) & 0xFFFFF;
}
/* ─── Bus cycle: read one byte at physical address ─────────────────────── */
static uint8_t bus_read_byte(uint32_t paddr, bool is_io) {
/* T1: drive AD with low 16 bits, A high pins with bits 16..19, pulse
ALE high so external 8282 latches the address; deassert ALE. */
drive_ad(paddr & 0xFFFF);
drive_a_high((paddr >> 16) & 0xF);
vx_pin_write(G.bhe, (paddr & 1) ? 1 : 0); /* BHE̅ low if low byte not used */
vx_pin_write(G.mio, is_io ? 0 : 1);
vx_pin_write(G.dtr, 0); /* receive */
vx_pin_write(G.ale, 1);
vx_pin_write(G.ale, 0);
/* T2..T3: switch AD to input, assert RD̅ + DEN̅. */
release_ad();
vx_pin_write(G.den, 0);
vx_pin_write(G.rd, 0);
/* Sample. The AD0..AD15 lines now carry data; for byte read at an
even address use AD0..AD7, for odd address use AD8..AD15. */
uint16_t bus = read_ad();
uint8_t byte = (paddr & 1) ? (uint8_t)(bus >> 8) : (uint8_t)bus;
/* T4: deassert. */
vx_pin_write(G.rd, 1);
vx_pin_write(G.den, 1);
G.last_phys = paddr;
return byte;
}
static uint16_t bus_read_word(uint32_t paddr, bool is_io) {
/* For aligned even addresses we could do this in one cycle (BHE̅+A0=00).
For the simple model we just do two byte reads. */
uint8_t lo = bus_read_byte(paddr, is_io);
uint8_t hi = bus_read_byte(paddr + 1, is_io);
return lo | ((uint16_t)hi << 8);
}
static void bus_write_byte(uint32_t paddr, uint8_t data, bool is_io) {
drive_ad(paddr & 0xFFFF);
drive_a_high((paddr >> 16) & 0xF);
vx_pin_write(G.bhe, (paddr & 1) ? 1 : 0);
vx_pin_write(G.mio, is_io ? 0 : 1);
vx_pin_write(G.dtr, 1); /* transmit */
vx_pin_write(G.ale, 1);
vx_pin_write(G.ale, 0);
/* Drive data on AD bus. For odd addr put byte on AD8..AD15. */
uint16_t out = (paddr & 1) ? ((uint16_t)data << 8) : data;
drive_ad(out);
vx_pin_write(G.den, 0);
vx_pin_write(G.wr, 0);
vx_pin_write(G.wr, 1); /* rising edge — external latches */
vx_pin_write(G.den, 1);
G.last_phys = paddr;
}
static void bus_write_word(uint32_t paddr, uint16_t data, bool is_io) {
bus_write_byte(paddr, (uint8_t)data, is_io);
bus_write_byte(paddr + 1, (uint8_t)(data >> 8), is_io);
}
/* ─── Memory accessors with default-segment selection ──────────────────── */
static uint16_t* seg_reg(int code) {
switch (code) {
case SEG_ES: return &G.es;
case SEG_CS: return &G.cs;
case SEG_SS: return &G.ss;
default: return &G.ds;
}
}
static int default_seg(int seg_code) {
/* Resolve segment override if active; else use the supplied default. */
if (G.seg_override >= 0) return G.seg_override;
return seg_code;
}
static uint8_t mem_read_byte(int default_seg_code, uint16_t off) {
int seg = default_seg(default_seg_code);
return bus_read_byte(physical(*seg_reg(seg), off), false);
}
static uint16_t mem_read_word(int default_seg_code, uint16_t off) {
int seg = default_seg(default_seg_code);
return bus_read_word(physical(*seg_reg(seg), off), false);
}
static void mem_write_byte(int default_seg_code, uint16_t off, uint8_t v) {
int seg = default_seg(default_seg_code);
bus_write_byte(physical(*seg_reg(seg), off), v, false);
}
static void mem_write_word(int default_seg_code, uint16_t off, uint16_t v) {
int seg = default_seg(default_seg_code);
bus_write_word(physical(*seg_reg(seg), off), v, false);
}
/* ─── Code fetch (always uses CS:IP) ───────────────────────────────────── */
static uint8_t fetch_byte(void) {
uint8_t v = bus_read_byte(physical(G.cs, G.ip), false);
G.ip++;
return v;
}
static uint16_t fetch_word(void) {
uint8_t lo = fetch_byte();
uint8_t hi = fetch_byte();
return lo | ((uint16_t)hi << 8);
}
/* ─── ModR/M decode ────────────────────────────────────────────────────── */
/* 8-bit registers indexed by REG/RM bits 000..111 ([I86] Table 4-9 w=0):
AL CL DL BL AH CH DH BH */
static uint8_t* reg8_ptr(uint8_t code) {
switch (code & 7) {
case 0: return &G.al;
case 1: return &G.cl;
case 2: return &G.dl;
case 3: return &G.bl;
case 4: return &G.ah;
case 5: return &G.ch;
case 6: return &G.dh;
default: return &G.bh;
}
}
/* 16-bit registers (w=1): AX CX DX BX SP BP SI DI */
static uint16_t* reg16_ptr(uint8_t code) {
switch (code & 7) {
case 0: return &G.ax;
case 1: return &G.cx;
case 2: return &G.dx;
case 3: return &G.bx;
case 4: return &G.sp;
case 5: return &G.bp;
case 6: return &G.si;
default: return &G.di;
}
}
/* Effective-address calc + default-segment selection per Table 4-10.
Returns the EA and writes the default segment code via *out_seg. */
static uint16_t calc_ea(uint8_t mod, uint8_t rm, int* out_seg) {
int16_t disp = 0;
int seg = SEG_DS;
if (mod == 1) disp = (int8_t)fetch_byte();
else if (mod == 2) disp = (int16_t)fetch_word();
uint16_t ea = 0;
switch (rm & 7) {
case 0: ea = G.bx + G.si; seg = SEG_DS; break;
case 1: ea = G.bx + G.di; seg = SEG_DS; break;
case 2: ea = G.bp + G.si; seg = SEG_SS; break;
case 3: ea = G.bp + G.di; seg = SEG_SS; break;
case 4: ea = G.si; seg = SEG_DS; break;
case 5: ea = G.di; seg = SEG_DS; break;
case 6:
if (mod == 0) {
/* disp16 absolute, default DS */
disp = (int16_t)fetch_word();
ea = 0;
seg = SEG_DS;
} else {
ea = G.bp;
seg = SEG_SS;
}
break;
case 7: ea = G.bx; seg = SEG_DS; break;
}
ea += disp;
*out_seg = seg;
return ea;
}
/* Read/write an r/m operand (8-bit or 16-bit). For mod=11 the operand is a
register; otherwise it's a memory location at the computed EA. */
static uint8_t rm8_read(uint8_t modrm) {
uint8_t mod = (modrm >> 6) & 3;
uint8_t rm = modrm & 7;
if (mod == 3) return *reg8_ptr(rm);
int seg;
uint16_t ea = calc_ea(mod, rm, &seg);
return mem_read_byte(seg, ea);
}
static void rm8_write(uint8_t modrm, uint8_t v) {
uint8_t mod = (modrm >> 6) & 3;
uint8_t rm = modrm & 7;
if (mod == 3) { *reg8_ptr(rm) = v; return; }
int seg;
uint16_t ea = calc_ea(mod, rm, &seg);
mem_write_byte(seg, ea, v);
}
static uint16_t rm16_read(uint8_t modrm) {
uint8_t mod = (modrm >> 6) & 3;
uint8_t rm = modrm & 7;
if (mod == 3) return *reg16_ptr(rm);
int seg;
uint16_t ea = calc_ea(mod, rm, &seg);
return mem_read_word(seg, ea);
}
static void rm16_write(uint8_t modrm, uint16_t v) {
uint8_t mod = (modrm >> 6) & 3;
uint8_t rm = modrm & 7;
if (mod == 3) { *reg16_ptr(rm) = v; return; }
int seg;
uint16_t ea = calc_ea(mod, rm, &seg);
mem_write_word(seg, ea, v);
}
/* ─── Flag helpers ──────────────────────────────────────────────────────── */
static bool parity8(uint8_t v) { v ^= v >> 4; v ^= v >> 2; v ^= v >> 1; return (v & 1) == 0; }
static void set_szp8(uint8_t v) {
G.flags = (G.flags & ~(F_SF | F_ZF | F_PF))
| (v & 0x80 ? F_SF : 0)
| (v == 0 ? F_ZF : 0)
| (parity8(v) ? F_PF : 0);
}
static void set_szp16(uint16_t v) {
G.flags = (G.flags & ~(F_SF | F_ZF | F_PF))
| (v & 0x8000 ? F_SF : 0)
| (v == 0 ? F_ZF : 0)
| (parity8(v & 0xff) ? F_PF : 0);
}
static uint8_t alu_add8(uint8_t a, uint8_t b, bool with_carry) {
uint16_t cin = (with_carry && (G.flags & F_CF)) ? 1 : 0;
uint16_t r = a + b + cin;
bool c = (r & 0x100) != 0;
bool h = (((a & 0xF) + (b & 0xF) + cin) & 0x10) != 0;
bool ov = (~(a ^ b) & (a ^ (uint8_t)r) & 0x80) != 0;
G.flags = (G.flags & ~(F_CF | F_AF | F_OF))
| (c ? F_CF : 0) | (h ? F_AF : 0) | (ov ? F_OF : 0);
set_szp8((uint8_t)r);
return (uint8_t)r;
}
static uint16_t alu_add16(uint16_t a, uint16_t b, bool with_carry) {
uint32_t cin = (with_carry && (G.flags & F_CF)) ? 1 : 0;
uint32_t r = a + b + cin;
bool c = (r & 0x10000) != 0;
bool h = (((a & 0xF) + (b & 0xF) + cin) & 0x10) != 0;
bool ov = (~(a ^ b) & (a ^ (uint16_t)r) & 0x8000) != 0;
G.flags = (G.flags & ~(F_CF | F_AF | F_OF))
| (c ? F_CF : 0) | (h ? F_AF : 0) | (ov ? F_OF : 0);
set_szp16((uint16_t)r);
return (uint16_t)r;
}
static uint8_t alu_sub8(uint8_t a, uint8_t b, bool with_borrow, bool store) {
uint16_t cin = (with_borrow && (G.flags & F_CF)) ? 1 : 0;
uint16_t r = a - b - cin;
bool c = (r & 0x100) != 0;
bool h = (((a & 0xF) - (b & 0xF) - cin) & 0x10) != 0;
bool ov = ((a ^ b) & (a ^ (uint8_t)r) & 0x80) != 0;
G.flags = (G.flags & ~(F_CF | F_AF | F_OF))
| (c ? F_CF : 0) | (h ? F_AF : 0) | (ov ? F_OF : 0);
set_szp8((uint8_t)r);
(void)store;
return (uint8_t)r;
}
static uint16_t alu_sub16(uint16_t a, uint16_t b, bool with_borrow, bool store) {
uint32_t cin = (with_borrow && (G.flags & F_CF)) ? 1 : 0;
uint32_t r = a - b - cin;
bool c = (r & 0x10000) != 0;
bool h = (((a & 0xF) - (b & 0xF) - cin) & 0x10) != 0;
bool ov = ((a ^ b) & (a ^ (uint16_t)r) & 0x8000) != 0;
G.flags = (G.flags & ~(F_CF | F_AF | F_OF))
| (c ? F_CF : 0) | (h ? F_AF : 0) | (ov ? F_OF : 0);
set_szp16((uint16_t)r);
(void)store;
return (uint16_t)r;
}
static uint8_t alu_and8(uint8_t a, uint8_t b) {
uint8_t r = a & b;
G.flags = (G.flags & ~(F_CF | F_OF | F_AF));
set_szp8(r);
return r;
}
static uint16_t alu_and16(uint16_t a, uint16_t b) {
uint16_t r = a & b;
G.flags = (G.flags & ~(F_CF | F_OF | F_AF));
set_szp16(r);
return r;
}
static uint8_t alu_or8(uint8_t a, uint8_t b) {
uint8_t r = a | b;
G.flags = (G.flags & ~(F_CF | F_OF | F_AF));
set_szp8(r);
return r;
}
static uint16_t alu_or16(uint16_t a, uint16_t b) {
uint16_t r = a | b;
G.flags = (G.flags & ~(F_CF | F_OF | F_AF));
set_szp16(r);
return r;
}
static uint8_t alu_xor8(uint8_t a, uint8_t b) {
uint8_t r = a ^ b;
G.flags = (G.flags & ~(F_CF | F_OF | F_AF));
set_szp8(r);
return r;
}
static uint16_t alu_xor16(uint16_t a, uint16_t b) {
uint16_t r = a ^ b;
G.flags = (G.flags & ~(F_CF | F_OF | F_AF));
set_szp16(r);
return r;
}
/* ─── Stack helpers ─────────────────────────────────────────────────────── */
static void push16(uint16_t v) {
G.sp -= 2;
bus_write_word(physical(G.ss, G.sp), v, false);
}
static uint16_t pop16(void) {
uint16_t v = bus_read_word(physical(G.ss, G.sp), false);
G.sp += 2;
return v;
}
/* ─── Conditional jump test ([I86] Table 2-13) ─────────────────────────── */
static bool cond_jcc(uint8_t op) {
/* op encodes condition in low 4 bits of the byte (op = 0x70..0x7F) */
bool r;
switch (op & 0x0F) {
case 0x0: r = (G.flags & F_OF) != 0; break; /* JO */
case 0x1: r = (G.flags & F_OF) == 0; break; /* JNO */
case 0x2: r = (G.flags & F_CF) != 0; break; /* JB / JNAE / JC */
case 0x3: r = (G.flags & F_CF) == 0; break; /* JNB / JAE / JNC */
case 0x4: r = (G.flags & F_ZF) != 0; break; /* JE / JZ */
case 0x5: r = (G.flags & F_ZF) == 0; break; /* JNE / JNZ */
case 0x6: r = (G.flags & (F_CF | F_ZF)) != 0; break; /* JBE / JNA */
case 0x7: r = (G.flags & (F_CF | F_ZF)) == 0; break; /* JNBE / JA */
case 0x8: r = (G.flags & F_SF) != 0; break; /* JS */
case 0x9: r = (G.flags & F_SF) == 0; break; /* JNS */
case 0xA: r = (G.flags & F_PF) != 0; break; /* JP / JPE */
case 0xB: r = (G.flags & F_PF) == 0; break; /* JNP / JPO */
case 0xC: r = ((G.flags & F_SF) != 0) != ((G.flags & F_OF) != 0); break;
/* JL / JNGE */
case 0xD: r = ((G.flags & F_SF) != 0) == ((G.flags & F_OF) != 0); break;
/* JNL / JGE */
case 0xE: r = (G.flags & F_ZF) ||
(((G.flags & F_SF) != 0) != ((G.flags & F_OF) != 0));
break; /* JLE / JNG */
default: r = !(G.flags & F_ZF) &&
(((G.flags & F_SF) != 0) == ((G.flags & F_OF) != 0));
break; /* JNLE / JG */
}
return r;
}
/* ─── Group 1/3/4/5 sub-opcode dispatch ─────────────────────────────────── */
/* Group 1 (opcodes 0x80..0x83): ADD/OR/ADC/SBB/AND/SUB/XOR/CMP r/m, imm. */
static void exec_group1(uint8_t op) {
uint8_t modrm = fetch_byte();
uint8_t sub = (modrm >> 3) & 7;
bool w = (op & 1);
bool s = (op & 2) != 0; /* sign-extend imm8 to imm16 */
if (!w) {
uint8_t a = rm8_read(modrm);
uint8_t b = fetch_byte();
uint8_t r = a;
switch (sub) {
case 0: r = alu_add8(a, b, false); break; /* ADD */
case 1: r = alu_or8(a, b); break; /* OR */
case 2: r = alu_add8(a, b, true); break; /* ADC */
case 3: r = alu_sub8(a, b, true, true); break; /* SBB */
case 4: r = alu_and8(a, b); break; /* AND */
case 5: r = alu_sub8(a, b, false, true); break; /* SUB */
case 6: r = alu_xor8(a, b); break; /* XOR */
case 7: alu_sub8(a, b, false, false); return; /* CMP — no store */
}
rm8_write(modrm, r);
} else {
uint16_t a = rm16_read(modrm);
uint16_t b;
if (s) b = (int16_t)(int8_t)fetch_byte();
else b = fetch_word();
uint16_t r = a;
switch (sub) {
case 0: r = alu_add16(a, b, false); break;
case 1: r = alu_or16(a, b); break;
case 2: r = alu_add16(a, b, true); break;
case 3: r = alu_sub16(a, b, true, true); break;
case 4: r = alu_and16(a, b); break;
case 5: r = alu_sub16(a, b, false, true); break;
case 6: r = alu_xor16(a, b); break;
case 7: alu_sub16(a, b, false, false); return;
}
rm16_write(modrm, r);
}
}
/* Group 5 (0xFF) — INC/DEC/CALL/JMP/PUSH on r/m16. */
static void exec_group5_word(uint8_t modrm) {
uint8_t sub = (modrm >> 3) & 7;
uint16_t a = rm16_read(modrm);
switch (sub) {
case 0: { /* INC */
bool old_cf = G.flags & F_CF;
uint16_t r = alu_add16(a, 1, false);
G.flags = (G.flags & ~F_CF) | (old_cf ? F_CF : 0);
rm16_write(modrm, r);
break;
}
case 1: { /* DEC */
bool old_cf = G.flags & F_CF;
uint16_t r = alu_sub16(a, 1, false, true);
G.flags = (G.flags & ~F_CF) | (old_cf ? F_CF : 0);
rm16_write(modrm, r);
break;
}
case 2: /* CALL near indirect */
push16(G.ip);
G.ip = a;
break;
case 4: /* JMP near indirect */
G.ip = a;
break;
case 6: /* PUSH */
push16(a);
break;
default: break;
}
}
/* ─── One-instruction step ──────────────────────────────────────────────── */
static void step(void) {
if (G.halted) return;
G.seg_override = -1;
/* Handle prefix bytes (segment override). Only one override is
remembered per [I86] p.2-42; if multiple appear, the LAST one wins. */
while (1) {
uint8_t prefix = bus_read_byte(physical(G.cs, G.ip), false);
if (prefix == 0x26) { G.seg_override = SEG_ES; G.ip++; continue; }
if (prefix == 0x2E) { G.seg_override = SEG_CS; G.ip++; continue; }
if (prefix == 0x36) { G.seg_override = SEG_SS; G.ip++; continue; }
if (prefix == 0x3E) { G.seg_override = SEG_DS; G.ip++; continue; }
/* LOCK / REP prefixes: we just skip them for now. */
if (prefix == 0xF0 || prefix == 0xF2 || prefix == 0xF3) { G.ip++; continue; }
break;
}
uint8_t op = fetch_byte();
/* MOV r8, imm8 — opcodes 0xB0..0xB7 */
if (op >= 0xB0 && op <= 0xB7) {
*reg8_ptr(op & 7) = fetch_byte();
return;
}
/* MOV r16, imm16 — opcodes 0xB8..0xBF */
if (op >= 0xB8 && op <= 0xBF) {
*reg16_ptr(op & 7) = fetch_word();
return;
}
/* INC r16 — opcodes 0x40..0x47 */
if (op >= 0x40 && op <= 0x47) {
uint16_t* r = reg16_ptr(op & 7);
bool old_cf = G.flags & F_CF;
*r = alu_add16(*r, 1, false);
G.flags = (G.flags & ~F_CF) | (old_cf ? F_CF : 0);
return;
}
/* DEC r16 — 0x48..0x4F */
if (op >= 0x48 && op <= 0x4F) {
uint16_t* r = reg16_ptr(op & 7);
bool old_cf = G.flags & F_CF;
*r = alu_sub16(*r, 1, false, true);
G.flags = (G.flags & ~F_CF) | (old_cf ? F_CF : 0);
return;
}
/* PUSH r16 — 0x50..0x57 */
if (op >= 0x50 && op <= 0x57) {
push16(*reg16_ptr(op & 7));
return;
}
/* POP r16 — 0x58..0x5F */
if (op >= 0x58 && op <= 0x5F) {
*reg16_ptr(op & 7) = pop16();
return;
}
/* Conditional short jumps Jcc — 0x70..0x7F */
if (op >= 0x70 && op <= 0x7F) {
int8_t disp = (int8_t)fetch_byte();
if (cond_jcc(op)) G.ip = (uint16_t)(G.ip + disp);
return;
}
switch (op) {
case 0x90: /* NOP (XCHG AX, AX) */ break;
/* MOV r/m8, r8 — 0x88; r/m16, r16 — 0x89; r8, r/m8 — 0x8A;
r16, r/m16 — 0x8B */
case 0x88: { uint8_t modrm = fetch_byte(); rm8_write(modrm, *reg8_ptr((modrm >> 3) & 7)); break; }
case 0x89: { uint8_t modrm = fetch_byte(); rm16_write(modrm, *reg16_ptr((modrm >> 3) & 7)); break; }
case 0x8A: { uint8_t modrm = fetch_byte(); *reg8_ptr((modrm >> 3) & 7) = rm8_read(modrm); break; }
case 0x8B: { uint8_t modrm = fetch_byte(); *reg16_ptr((modrm >> 3) & 7) = rm16_read(modrm); break; }
/* MOV r/m16, sreg — 0x8C / MOV sreg, r/m16 — 0x8E */
case 0x8C: { uint8_t modrm = fetch_byte(); rm16_write(modrm, *seg_reg((modrm >> 3) & 3)); break; }
case 0x8E: { uint8_t modrm = fetch_byte(); *seg_reg((modrm >> 3) & 3) = rm16_read(modrm); break; }
/* MOV AL,[addr] / AX,[addr] / [addr],AL / [addr],AX */
case 0xA0: { uint16_t a = fetch_word(); G.al = mem_read_byte(SEG_DS, a); break; }
case 0xA1: { uint16_t a = fetch_word(); G.ax = mem_read_word(SEG_DS, a); break; }
case 0xA2: { uint16_t a = fetch_word(); mem_write_byte(SEG_DS, a, G.al); break; }
case 0xA3: { uint16_t a = fetch_word(); mem_write_word(SEG_DS, a, G.ax); break; }
/* ADD/SUB/AND/OR/XOR/CMP r/m, r — and reverse — and AL/AX,imm.
00 /op /, 02 /op /reverse, 04 /op /AL+imm8, 05 /op /AX+imm16.
op encoded in bits 5..3 of the leading byte. */
case 0x00: case 0x08: case 0x10: case 0x18:
case 0x20: case 0x28: case 0x30: case 0x38: {
uint8_t modrm = fetch_byte();
uint8_t* dst = reg8_ptr((modrm >> 3) & 7);
uint8_t a = rm8_read(modrm);
uint8_t b = *dst;
uint8_t r = a;
switch ((op >> 3) & 7) {
case 0: r = alu_add8(a, b, false); break; /* ADD */
case 1: r = alu_or8(a, b); break;
case 2: r = alu_add8(a, b, true); break;
case 3: r = alu_sub8(a, b, true, true); break;
case 4: r = alu_and8(a, b); break;
case 5: r = alu_sub8(a, b, false, true); break;
case 6: r = alu_xor8(a, b); break;
case 7: alu_sub8(a, b, false, false); return; /* CMP */
}
rm8_write(modrm, r);
break;
}
case 0x01: case 0x09: case 0x11: case 0x19:
case 0x21: case 0x29: case 0x31: case 0x39: {
uint8_t modrm = fetch_byte();
uint16_t* dst = reg16_ptr((modrm >> 3) & 7);
uint16_t a = rm16_read(modrm);
uint16_t b = *dst;
uint16_t r = a;
switch ((op >> 3) & 7) {
case 0: r = alu_add16(a, b, false); break;
case 1: r = alu_or16(a, b); break;
case 2: r = alu_add16(a, b, true); break;
case 3: r = alu_sub16(a, b, true, true); break;
case 4: r = alu_and16(a, b); break;
case 5: r = alu_sub16(a, b, false, true); break;
case 6: r = alu_xor16(a, b); break;
case 7: alu_sub16(a, b, false, false); return;
}
rm16_write(modrm, r);
break;
}
case 0x02: case 0x0A: case 0x12: case 0x1A:
case 0x22: case 0x2A: case 0x32: case 0x3A: {
uint8_t modrm = fetch_byte();
uint8_t* dst = reg8_ptr((modrm >> 3) & 7);
uint8_t a = *dst;
uint8_t b = rm8_read(modrm);
uint8_t r = a;
switch ((op >> 3) & 7) {
case 0: r = alu_add8(a, b, false); break;
case 1: r = alu_or8(a, b); break;
case 2: r = alu_add8(a, b, true); break;
case 3: r = alu_sub8(a, b, true, true); break;
case 4: r = alu_and8(a, b); break;
case 5: r = alu_sub8(a, b, false, true); break;
case 6: r = alu_xor8(a, b); break;
case 7: alu_sub8(a, b, false, false); return;
}
*dst = r;
break;
}
case 0x03: case 0x0B: case 0x13: case 0x1B:
case 0x23: case 0x2B: case 0x33: case 0x3B: {
uint8_t modrm = fetch_byte();
uint16_t* dst = reg16_ptr((modrm >> 3) & 7);
uint16_t a = *dst;
uint16_t b = rm16_read(modrm);
uint16_t r = a;
switch ((op >> 3) & 7) {
case 0: r = alu_add16(a, b, false); break;
case 1: r = alu_or16(a, b); break;
case 2: r = alu_add16(a, b, true); break;
case 3: r = alu_sub16(a, b, true, true); break;
case 4: r = alu_and16(a, b); break;
case 5: r = alu_sub16(a, b, false, true); break;
case 6: r = alu_xor16(a, b); break;
case 7: alu_sub16(a, b, false, false); return;
}
*dst = r;
break;
}
case 0x04: G.al = alu_add8(G.al, fetch_byte(), false); break;
case 0x05: G.ax = alu_add16(G.ax, fetch_word(), false); break;
case 0x0C: G.al = alu_or8(G.al, fetch_byte()); break;
case 0x0D: G.ax = alu_or16(G.ax, fetch_word()); break;
case 0x14: G.al = alu_add8(G.al, fetch_byte(), true); break;
case 0x15: G.ax = alu_add16(G.ax, fetch_word(), true); break;
case 0x1C: G.al = alu_sub8(G.al, fetch_byte(), true, true); break;
case 0x1D: G.ax = alu_sub16(G.ax, fetch_word(), true, true); break;
case 0x24: G.al = alu_and8(G.al, fetch_byte()); break;
case 0x25: G.ax = alu_and16(G.ax, fetch_word()); break;
case 0x2C: G.al = alu_sub8(G.al, fetch_byte(), false, true); break;
case 0x2D: G.ax = alu_sub16(G.ax, fetch_word(), false, true); break;
case 0x34: G.al = alu_xor8(G.al, fetch_byte()); break;
case 0x35: G.ax = alu_xor16(G.ax, fetch_word()); break;
case 0x3C: alu_sub8(G.al, fetch_byte(), false, false); break;
case 0x3D: alu_sub16(G.ax, fetch_word(), false, false); break;
/* Group 1: ADD/OR/ADC/SBB/AND/SUB/XOR/CMP r/m, imm */
case 0x80: case 0x81: case 0x82: case 0x83:
exec_group1(op);
break;
/* JMP near (0xE9, 16-bit displacement); JMP short (0xEB, 8-bit) */
case 0xE9: { int16_t d = (int16_t)fetch_word(); G.ip = (uint16_t)(G.ip + d); break; }
case 0xEB: { int8_t d = (int8_t) fetch_byte(); G.ip = (uint16_t)(G.ip + d); break; }
/* JMP far (0xEA) */
case 0xEA: { uint16_t off = fetch_word(); uint16_t seg = fetch_word();
G.ip = off; G.cs = seg; break; }
/* CALL near (0xE8, 16-bit displacement) */
case 0xE8: { int16_t d = (int16_t)fetch_word(); push16(G.ip); G.ip = (uint16_t)(G.ip + d); break; }
/* CALL far (0x9A): push CS, push IP, jump CS:IP */
case 0x9A: { uint16_t off = fetch_word(); uint16_t seg = fetch_word();
push16(G.cs); push16(G.ip); G.cs = seg; G.ip = off; break; }
/* RET near (0xC3) / RET imm (0xC2) */
case 0xC3: G.ip = pop16(); break;
case 0xC2: { uint16_t n = fetch_word(); G.ip = pop16(); G.sp += n; break; }
/* RET far (0xCB) / RETF imm (0xCA) */
case 0xCB: G.ip = pop16(); G.cs = pop16(); break;
case 0xCA: { uint16_t n = fetch_word(); G.ip = pop16(); G.cs = pop16(); G.sp += n; break; }
/* PUSHF / POPF */
case 0x9C: push16(G.flags); break;
case 0x9D: G.flags = (pop16() | F_RESERVED_ON) & ~F_RESERVED_OFF; break;
/* CLC / STC / CLI / STI / CLD / STD / CMC */
case 0xF8: G.flags &= ~F_CF; break;
case 0xF9: G.flags |= F_CF; break;
case 0xFA: G.flags &= ~F_IF; break;
case 0xFB: G.flags |= F_IF; break;
case 0xFC: G.flags &= ~F_DF; break;
case 0xFD: G.flags |= F_DF; break;
case 0xF5: G.flags ^= F_CF; break;
/* HLT */
case 0xF4: G.halted = true; break;
/* Group 5 (0xFF) — INC/DEC/CALL/JMP/PUSH r/m16 */
case 0xFF: { uint8_t modrm = fetch_byte(); exec_group5_word(modrm); break; }
/* LOOP / LOOPE / LOOPNE / JCXZ — 0xE0..0xE3 */
case 0xE0: { int8_t d = (int8_t)fetch_byte(); G.cx--; if (G.cx != 0 && !(G.flags & F_ZF)) G.ip = (uint16_t)(G.ip + d); break; }
case 0xE1: { int8_t d = (int8_t)fetch_byte(); G.cx--; if (G.cx != 0 && (G.flags & F_ZF)) G.ip = (uint16_t)(G.ip + d); break; }
case 0xE2: { int8_t d = (int8_t)fetch_byte(); G.cx--; if (G.cx != 0) G.ip = (uint16_t)(G.ip + d); break; }
case 0xE3: { int8_t d = (int8_t)fetch_byte(); if (G.cx == 0) G.ip = (uint16_t)(G.ip + d); break; }
/* INT 3 — debug trap; we just halt for visibility. */
case 0xCC: G.halted = true; break;
default:
/* Unimplemented opcode — treat as NOP. Logged as a TODO via
vx_log so users know which features remain. */
break;
}
}
/* ─── Reset / pin watchers / clock ──────────────────────────────────────── */
static void reset_state(void) {
G.cs = 0xFFFF;
G.ds = G.ss = G.es = 0;
G.ip = 0;
G.flags = F_RESERVED_ON;
G.ax = G.bx = G.cx = G.dx = 0;
G.sp = G.bp = G.si = G.di = 0;
G.halted = false;
G.seg_override = -1;
/* Idle bus */
vx_pin_write(G.ale, 0);
vx_pin_write(G.rd, 1);
vx_pin_write(G.wr, 1);
vx_pin_write(G.den, 1);
vx_pin_write(G.dtr, 1);
vx_pin_write(G.mio, 1);
vx_pin_write(G.bhe, 1);
vx_pin_write(G.hlda, 0);
vx_pin_write(G.inta, 1);
release_ad();
}
static void on_reset(void* user_data, vx_pin pin, int value) {
(void)user_data; (void)pin;
if (value) {
G.reset_active = true;
reset_state();
} else {
G.reset_active = false;
}
}
static void on_clock(void* user_data) {
(void)user_data;
if (G.reset_active) return;
if (G.halted) return;
if (vx_pin_read(G.ready) == 0) return; /* wait state */
if (vx_pin_read(G.hold) == 1) { /* bus hold */
vx_pin_write(G.hlda, 1);
return;
}
vx_pin_write(G.hlda, 0);
step();
}
void chip_setup(void) {
char name[6];
for (int i = 0; i < 16; i++) {
name[0]='A'; name[1]='D';
if (i<10) { name[2]='0'+i; name[3]=0; }
else { name[2]='1'; name[3]='0'+(i-10); name[4]=0; }
G.ad[i] = vx_pin_register(name, VX_INPUT);
}
for (int i = 0; i < 4; i++) {
name[0]='A'; name[1]='1'; name[2]='6'+i; name[3]=0;
G.a[i] = vx_pin_register(name, VX_OUTPUT_LOW);
}
G.ale = vx_pin_register("ALE", VX_OUTPUT_LOW);
G.rd = vx_pin_register("RD", VX_OUTPUT_HIGH);
G.wr = vx_pin_register("WR", VX_OUTPUT_HIGH);
G.mio = vx_pin_register("MIO", VX_OUTPUT_HIGH);
G.dtr = vx_pin_register("DTR", VX_OUTPUT_HIGH);
G.den = vx_pin_register("DEN", VX_OUTPUT_HIGH);
G.hold = vx_pin_register("HOLD", VX_INPUT);
G.hlda = vx_pin_register("HLDA", VX_OUTPUT_LOW);
G.intr = vx_pin_register("INTR", VX_INPUT);
G.nmi = vx_pin_register("NMI", VX_INPUT);
G.inta = vx_pin_register("INTA", VX_OUTPUT_HIGH);
G.reset_= vx_pin_register("RESET", VX_INPUT);
G.ready = vx_pin_register("READY", VX_INPUT);
G.test_ = vx_pin_register("TEST", VX_INPUT);
G.clk = vx_pin_register("CLK", VX_INPUT);
G.mnmx = vx_pin_register("MNMX", VX_INPUT);
G.bhe = vx_pin_register("BHE", VX_OUTPUT_HIGH);
G.vcc = vx_pin_register("VCC", VX_INPUT);
G.gnd = vx_pin_register("GND", VX_INPUT);
reset_state();
G.reset_active = true;
vx_pin_watch(G.reset_, VX_EDGE_BOTH, on_reset, 0);
/* Run an instruction per timer fire. 200 ns ≈ 5 MHz pseudo-clock; the
test's CLOCK_NS matches. */
G.cycle_timer = vx_timer_create(on_clock, 0);
vx_timer_start(G.cycle_timer, 200, true);
}