SPK286 · CPU core

Technical datasheet · 80286-compatible CPU core

SPK286 an 80286 in synthesizable SystemVerilog

A standalone CPU core built around project-owned microcode, with real and protected mode, task switching and a native-style 80286 bus interface, verified against hardware-captured 80286 test data.

ISA: Intel 80286 Real + protected mode Bus: 24-bit address · 16-bit data Reference target: Cyclone V License: MIT
01

Overview

A CPU, and only a CPU

SPK286 implements the Intel 80286 as RTL. The repository ends at the CPU boundary: execution core, microcode and its compiler, prefetch, segmentation and protection, interrupts, task switching, the Bus Interface Unit and the timing policy. Machine designs, whether on an FPGA platform or in custom hardware, consume it as a component.

µC

Project-owned microcode

Human-readable .mc source compiled by a deterministic Python tool into a synchronous ROM. Microcode requests functional operations; it is not a second x86 decoder.

PM

Full protected mode

Descriptors, privilege checks, call, interrupt, trap and task gates, task switching, IOPL, exception replacement, double fault and shutdown.

HW

Hardware-derived oracle

Checked against the SingleStepTests/80286 captures from a physical Harris N80C286-12, plus directed benches in two simulators and mutation audits.

307
Real-mode opcode forms verified
123 / 743
Microcode entries / ROM words
9,247
LUTs on Cyclone V (Yosys estimate)
1 M10K
Microcode ROM · no DSP blocks
i
How to read this datasheet

SPK286 is functionally complete for the documented 80286 and its two best-known undocumented instructions. It does not yet claim historical cycle accuracy: execution is dependency-driven, and the historical schedule is a provisional, separable layer.

02

Logical design

Core architecture

Every piece of state has one owner. Architectural registers, segment caches, protected-mode state, the stack and operand services and the BIU each own what they hold; microcode asks them for operations and waits for real dependencies.

Logical architecture of the SPK286 core Fetch feeds a six-byte prefetch queue, then the instruction context, decoder and microsequencer with its ROM. The micro-executor drives the register, ALU, operand, stack and protected-mode services. The BIU is the only bus arbiter for code, data, I/O and interrupt acknowledge, and drives the normalized 80286 bus. FRONTEND Fetch 1 request in flight Prefetch queue 6 bytes Instruction context Prefixes · start CS:IP · TF Decoder Entry + captured metadata CONTROL Microsequencer + ROM 123 entries · 743 words · M10K Micro-executor TMP0–TMP2 · candidate FLAGS SERVICES Registers GPRs · IP · FLAGS Segment caches Arithmetic ALU · shift · adjust Iterative mul/div Operand / EA ModR/M · one EA table Limit/rights checks Stack SP commit PUSHA/POPA preflight Protected Descriptors · TSS Task switch · LOADALL BUS Bus Interface Unit Sole arbiter · code · data · I/O · INTA · LOCK Events INTR · NMI · faults · TF Normalized 80286 bus · A23:0 · D15:0 · BHE · status · READY · HOLD/HLDA · LOCK

Code fetches and every data, I/O, descriptor, TSS and interrupt acknowledge cycle are issued through the BIU. Data access never flushes the prefetch queue.

Instruction stream

  • One outstanding code fetch; the fetch cursor may run ahead of IP by the queue capacity plus one response.
  • Refill needs two free queue slots; bytes are queued low byte first.
  • A redirect flushes the queue and discards the response of any code read still in flight.
  • The instruction start CS:IP and TF are captured at opcode consumption and become the saved IP for faults.
  • Instruction length is qualified without side effects against the ten-byte limit.

Operands and stack

  • ModR/M and displacement are captured once; later queue bytes cannot alter the executing instruction.
  • Each byte or word access is checked against the cached limit and rights before any bus cycle.
  • 16-bit offsets wrap; physical addresses are 24 bits.
  • PUSH SP pushes the original SP; POP SP loads the popped value.
  • A faulting POP to memory keeps its SP increment, as observed on physical parts.
Module map
ModuleRole
spk286Top level: connects every service and exposes the bus and debug ports.
spk286_fetchOne outstanding code fetch and one response slot; drains stale responses after a redirect.
spk286_prefetch_queueSix-byte instruction queue with simultaneous consume/refill.
spk286_instruction_contextInstruction byte and prefix lifetime (segment override, LOCK, REP).
spk286_decoder and *_decodeInstruction-form decode into a microcode entry plus captured metadata.
spk286_encoding_previewSide-effect-free instruction-length qualification.
spk286_microsequencerGeneric micro-PC sequencing over the generated synchronous ROM.
spk286_micro_executorExecutes micro-operations through the owning services.
spk286_registers, spk286_segmentsGeneral registers, IP and FLAGS; visible selectors and hidden segment caches.
spk286_modrm, spk286_effective_address, spk286_operandModR/M metadata, the single EA table and the shared byte/word operand service.
spk286_stack, spk286_stack_rangeStack addressing, SP commit and PUSHA/POPA preflight.
spk286_alu, _shift, _muldiv, _adjust, _conditionArithmetic, shift/rotate, iterative multiply/divide, decimal adjust and condition predicates.
spk286_eventsEvent arbitration, delivery-failure classification and saved context.
spk286_protected_state, _selector, _descriptorMSW, CPL, GDTR, IDTR, LDTR and TR; selector bounds and descriptor parsing.
spk286_tss_*, spk286_task_*Fixed 80286 TSS image, descriptor resolution, task segment caches and the atomic task switch.
spk286_loadall_serviceSerialized LOADALL image acquisition.
spk286_biuSole bus arbiter for code, data, I/O, INTA and LOCK.
spk286_timing_policy, spk286_timing_traceReset-time timing policy selection and non-intrusive timing observation.
03

External interface

A normalized 80286 bus

The bus is a synchronous transaction interface, not a pin-accurate model. A native wrapper can translate it to 80286 pin polarities and the full S0/S1/M-IO/COD-INTA encoding.

SignalDirMeaning
clkinSynchronous core clock.
resetinActive-high synchronous reset; inhibits bus output while asserted.
readyinCompletes the active bus cycle.
address[23:0]outPhysical address.
data_in[15:0]inRead data, sampled on the READY edge of an active read.
data_out[15:0], data_oeoutWrite data on the selected lanes and its drive enable.
bus_valid, bus_writeoutActive cycle and direction.
bus_bhe_noutActive-low upper byte enable.
bus_status[3:0]outCycle type (see below).
bus_lockoutExplicit LOCK, implicit XCHG, and the INTA/first-push chain.
intr, nmiinINTR is level sensitive; NMI is a rising edge. Synchronous to clk.
hold_request, hldain / outHOLD request and grant at a free cycle boundary.
execute_enableinPauses or resumes semantic execution without repeating bus cycles.
timing_policy_request[1:0]inTiming policy, sampled at reset.
halted, shutdownoutHLT and shutdown states.

Bus status encoding

0 · INTA 4 · halt / shutdown 5 · MEMR 6 · MEMW 9 · IOR A · IOW D · CODE F · passive
  • After reset CS=F000 with hidden base FF0000 and IP=FFF0, so the first fetch is at FFFFF0.
  • Address, status, BHE and write data hold while bus_valid && !ready.
  • An odd word is two ordered cycles (N on D15:8, then N+1 on D7:0), each waiting for its own READY, with bus ownership kept between them.
  • At a free boundary an accepted data request wins over code; once visible, a cycle keeps the bus until READY.
  • HOLD is granted only at a free boundary and never splits an odd word, a locked read-modify-write or the INTA chain.
  • Interrupt acknowledge is two READY-qualified INTA cycles; the vector is D7:0 of the second, and LOCK links both with the first stack push.
  • Halt and shutdown are indicated with status 4 and address 2 or 0, without a READY-qualified transfer.
i
Setup and debug ports

The top level also exposes setup ports (register, FLAGS, IP and segment writes plus a coordinated redirect) used by test harnesses, and read-only debug_* ports for registers, segment caches, protected-mode state, micro-PC, events, bus activity and timing counters.

04

Control store

Readable microcode, deterministic ROM

Source is authoritative

Instructions are written in microcode/source/*.mc as named functional operations. The compiler checks operands and temporary lifetimes, resolves entries and labels, and emits one ROM word per statement. Timing annotations, padding and arbitrary branches are rejected by the language.

microcode_format 21

# Undocumented 80286 SALC: AL = CF ? FFh : 00h; preserve AH and all FLAGS.
instruction LOAD_CARRY_BYTE:
    READ_FLAGS -> TMP0
    CARRY_FILL8 TMP0
    TMP0 -> DST8
    END
123 entries 743 words 10-bit address 8-bit control 0 unused words
µ

Execution contract

The decoder consumes the opcode and loads the generated entry with captured metadata. The microsequencer owns micro-PC and active state and knows nothing about x86. The executor completes an operation only when its real dependency is satisfied — a queue byte, an operand response, BIU completion — and otherwise the sequencer holds.

Reset and redirects clear microstate; pauses hold it. An invalid ROM address or word latches microcode_fault.

Reproducible artifacts

  • No timestamps or absolute paths in the output.
  • Generated files are committed and never edited by hand.
  • A stale artifact fails every build.
  • Frozen images of earlier formats keep extensions append-only.
  • Each micro-PC maps back to its source line.
Generated artifacts and compiler commands
File in microcode/generated/Content
spk286_microcode_pkg.svDimensions, operation constants and entry constants.
spk286_microcode_rom.svPortable combinational ROM with validity output.
spk286_microcode_sync_rom.svSynchronous ROM inferred as block memory; used by the core.
spk286_microcode.lstSymbolic listing with source locations.
spk286_microcode_symbols.jsonEntries, labels and per-PC source mapping.
spk286_microcode_validation.jsonFormat and encoding versions, source hashes, usage and status.
make microcode          # regenerate
make microcode-check    # fail if any artifact is stale
make microcode-test     # compiler and frozen-format tests
make microcode-audit    # independent ROM reconstruction and round-trip
python3 -m tools.microcode.compile_microcode --lookup MOV_R16_IMM16.writeback
python3 -m tools.microcode.compile_microcode --pc 0x1
05

Instruction set

The documented 80286, and then some

AreaStatusNotes
Real-mode instructions Complete Every documented form; all 307 opcode forms checked against hardware captures.
Protected mode Implemented System instructions, segment loads, gates, task switching, IOPL, #DF and shutdown.
LOADALL (0F 05) Implemented Fixed 102-byte image at 000800h, atomic commit, #GP(0) at nonzero CPL.
SALC (D6) Implemented AL = CF ? FFh : 00h; AH and FLAGS preserved.
Coprocessor (80287) Not attached ESC and WAIT follow the MSW no-NPX rules.
80386 behavior Excluded No granularity, 32-bit fields or operand-size prefixes, by design.

Real mode

  • Data transfer, byte and word ALU, TEST, INC/DEC/NEG/NOT.
  • Shifts and rotates, including immediate counts.
  • MUL/IMUL/DIV/IDIV and immediate IMUL; decimal and ASCII adjust.
  • String instructions with REP/REPE/REPNE, restartable per iteration and interruptible between iterations.
  • Port and string I/O, PUSHA/POPA, ENTER/LEAVE, BOUND.
  • Near and far transfers, LOOP/JCXZ, INT/INTO/IRET, HLT, segment override and LOCK prefixes.
  • Byte IDIV overflow follows the physical 80286 captures; invalid opcodes raise #UD.

Protected mode

  • LMSW, SMSW, LGDT, SGDT, LIDT, SIDT, LLDT, SLDT, LTR, STR, VERR, VERW, LAR, LSL, ARPL, CLTS — checks before any side effect.
  • Segment loads with selector, type, privilege, presence and limit checks, accessed-bit write-back and expand-down segments.
  • Far CALL/JMP/RET, conforming and non-conforming, through call gates with parameter copying and TSS stacks.
  • Interrupt and trap gates with error codes and outer-level stacks; same- and outer-level IRET.
  • Task switching through TSS descriptors and task gates, busy bits and NT back-link.
  • IOPL checks on CLI/STI/IN/OUT/INS/OUTS; POPF/IRET FLAGS privileges.
LOADALL image layout (102 bytes at 000800h)
OffsetSizeField
006Reserved
062MSW
088Reserved
102TR selector
122FLAGS
142IP
162LDT selector
18 / 1A / 1C / 1E2 eachDS / SS / CS / ES selectors
2016DI, SI, BP, SP, BX, DX, CX, AX
30 / 36 / 3C / 426 eachES / CS / SS / DS hidden caches
486GDTR (base low, base high, limit)
4E6LDT hidden cache
546IDTR
5A6TSS hidden cache

Each hidden cache record is base low word, base high byte, access byte and limit word. 0F 04 and the other 0F holes remain invalid.

06

Interrupts and exceptions

Intel priority at every boundary

At an instruction boundary, instruction faults come first, then the TF single-step trap, then NMI, then INTR.

RequestVectorSaved IPNotes
Instruction fault0, 5, 6, 7, 10–13Instruction start, prefixes includedNever retires; #DF (8) on a fault during delivery.
Single step (TF)1Next / target IPTF is sampled at instruction start.
NMI2Next instructionOne pending edge; blocked until IRET.
INTRINTA vectorNext instructionNeeds IF=1 and no STI/SS shadow.
INT3 / INT n / INTO3 / n / 4Next instructionRetires when entry completes; INT clears TF without trapping.
  • Real-mode entry pushes FLAGS, CS and IP, clears IF and TF, reads the IVT, then commits CS:IP and flushes the queue in one step.
  • No event is admitted inside a frame or a physical operand word.
  • A frame push that cannot complete leads to double fault or shutdown, never to unbounded recursion.
  • STI creates a one-instruction shadow only when IF goes from 0 to 1; MOV SS and POP SS inhibit interrupts for one instruction.
07

Timing model

Function first, timing as a separate layer

The number of micro-operations an instruction uses does not define its historical cycle count, and no idle or padding micro-operations exist. Timing is applied by an explicit policy, and real dependencies are honored in every mode.

Baseline

timing_policy_request = 0. Normalized, dependency-driven functional execution.

Available

Max

timing_policy_request = 2. Dependency-driven, functionally identical to the baseline.

Available

Profiled variant

spk286_profiled holds retirement until an Intel-derived per-instruction budget expires, counted in host-supplied virtual CPU clocks.

Provisional

Observation without intrusion

spk286_timing_trace counts cycles, BIU busy time, READY waits, prefetch requests and responses, queue-empty cycles, redirects, operand waits and events. Unsupported policy requests fall back to 0 and set debug_timing_policy_error. The profiled variant is a generated drop-in copy of the top level; with historical_enable=0 it behaves as the functional core.

!
No cycle-accuracy claim

The long-term modes are Accurate (validated historical timing), Fast and Max. Native Ts/Tc bus timing, prefetch overlap and named 6–25 MHz profiles are not yet validated against hardware, and a profile is not called accurate because a nominal MHz or aggregate benchmark matches.

08

Verification

Expected values never come from the RTL

Tests are part of the specification. Every meaningful RTL change goes through the same ladder before it is committed, and expectations derive from Intel documentation, physical 80286 evidence and independent reference models.

  1. Focused testpython3 -m unittest
  2. Lintmake lint
  3. Directed benchestest_units.sh both
  4. Dual simulatorVerilator + Icarus
  5. SingleSteprun_singlestep
  6. Synthesismake synth-check
  7. Regressionmake regression
1,397,996
SingleStep cases passing, zero failures
138
Directed testbenches, two simulators
24
Mutation audit families
60
Python test modules

SingleStepTests/80286 as the functional oracle

The corpus was captured from a physical Harris N80C286-12 in real mode (pinned commit 37c73ca, MIT licence). Each MOO test is normalized, loaded into the RTL, run to one target retirement and compared on final registers and the whole sparse memory image, including neighbouring bytes. Undefined FLAGS bits are masked with the corpus masks.

MOO test -> normalized state -> RTL initialization -> one target retirement
         -> final registers and sparse RAM -> masked comparison

Every admitted case runs, including prefixed and exception-terminated ones. The 307 documented real-mode forms account for 1,386,996 cases; the rest cover SALC and the no-NPX 9B/D8 captures. Three cases are revoked upstream, and one record without a captured write strobe is reported as not evaluable — never as a pass.

Directed benches

Each bench in sim/unit runs under both Verilator and Icarus Verilog. Many are driven by vectors from independent Python reference models in tools/, so the expected values do not come from the RTL under test.

Checking the checkers

Mutation audits inject isolated RTL faults into temporary copies and require the benches to reject each one. make verify-failure proves both simulators report a wrong expectation, and the MOO parser is cross-checked against the upstream reference parser.

Make targets
TargetPurpose
make testMicrocode checks, lint, every directed bench in both simulators, Python tests and smoke runs.
make regressiontest plus Yosys synthesis of the whole core.
make singlestep-fetchFetch the pinned SingleStepTests/80286 working set into .cache.
make singlestepRun every case of every supported opcode file.
make singlestep-auditCross-check the MOO parser and comparators against upstream and injected faults.
make mutation-auditRequire the directed benches to reject injected RTL faults.
make profiled-checkFreshness, lint and dual-simulator oracles of the profiled variant.
make full-checkAll of the above.
09

FPGA resources

Cyclone V footprint

Yosys 0.33 synth_intel_alm estimate for Cyclone V, the reference FPGA target. The figures are Yosys cells, not Quartus ALMs; Quartus packs and optimizes differently. Regenerate them with make resource-report.

Integrated coreLUTsArithmeticRegistersM10KMLABDSP
spk286 (flattened)9,2472,6514,793100

Largest modules by LUTs

Hierarchy and all debug ports kept, so per-module figures are an upper bound. Multiply and divide are iterative and use no DSP blocks; the single M10K block holds the microcode ROM.

micro_executor
3,185
spk286 (glue)
1,471
protected_state
1,273
task_segment_service
669
task_switch_service
516
segments
472
muldiv
456
registers
426
tss_service
318
events
266
Full per-module table
ModuleLUTsArith.RegsM10K
spk286_micro_executor3,1858849020
spk2861,471496160
spk286_protected_state1,273885400
spk286_task_segment_service669657340
spk286_task_switch_service5162966520
spk286_segments472322560
spk286_muldiv4562992020
spk286_registers426161600
spk286_tss_service3181424020
spk286_events26601290
spk286_decoder229300
spk286_descriptor (x8)200000
spk286_adjust147100840
spk286_biu14124900
spk286_fetch13991980
spk286_loadall_service12366510
spk286_prefetch_queue11116580
spk286_alu1081800
spk286_operand104020
spk286_effective_address1008000
spk286_control_decode88000
spk286_timing_trace778328370
spk286_microsequencer7545230
spk286_shift756570
spk286_alu_decode42000
spk286_operand_decode320490
spk286_stack323300
spk286_instruction_context194120
spk286_flags_decode15000
spk286_selector (x2)128600
spk286_condition4000
spk286_modrm3000
spk286_stack_range23400
spk286_timing_policy1040
spk286_microcode_sync_rom0001
Total spk286 (hierarchical)10,9313,6965,9581

spk286_timing_trace only holds debug counters. The profiled variant is not covered because Yosys 0.33 cannot parse the module-scope package import in rtl/timing.

10

Known limitations

What still needs evidence

Where Intel documentation, emulators and physical parts disagree or stay silent, SPK286 implements a documented policy and records the question as open instead of picking an answer quietly.

Functional

  • Explicit LOCK privilege rules and the protected zero-count INS/OUTS boundary.
  • LAR/LSL/VERR/VERW truth tables for absent and conforming descriptors.
  • NT after a JMP task switch keeps the incoming value; Intel table 8-2 and some emulators disagree.
  • The multi-word boundary and restart matrix is only partly covered.
  • Competing-event ordering needs a wider independent matrix.

Policies pending physical evidence

  • NMI blocking is released at IRET start, including a faulting IRET.
  • The STI shadow exists only on a 0-to-1 IF transition.
  • A faulting instruction does not also produce a single-step trap.
  • Event recognition order for zero-count REP instructions.
  • LOADALL stepping-specific details and images that clear PE.
!
Verification gap

No directed bench yet covers an owed TF, NMI or INTR event at an instruction boundary competing with a fetch fault of the next instruction. Real-mode fetch wraps at FFFF, so the case only arises at a protected-mode CS limit.

11

Scope and usage

Integrating the core

Inside SPK286

  • CPU top level, decoder, microsequencer and datapath.
  • Microcode source, compiler and generated ROM.
  • Prefetch, segmentation, protection and task switching.
  • Interrupts, exceptions and the BIU.
  • Timing policy and CPU-only verification.

Left to the machine

  • PIC, PIT, DMA, 8042, RTC/CMOS and ISA.
  • Video, audio and storage.
  • BIOS packaging and PC/AT memory map.
  • Board top levels, memory controllers and platform integration.

Instantiation

Instantiate spk286 from rtl/spk286.sv with the sources listed in scripts/rtl_sources.sh: the generated microcode package and ROMs first, then rtl/*.sv. rtl/timed/spk286_profiled.sv is a drop-in variant that adds the optional historical instruction-interval schedule.

Requirements: Verilator 5.020 or later, Icarus Verilog 12, Yosys, GNU make and Python 3 with the standard library only. On Ubuntu or WSL, make bootstrap installs them and make env checks them.

make regression        # microcode, lint, directed benches, Python tests, synthesis
make singlestep-fetch  # fetch the pinned SingleStepTests/80286 corpus
make singlestep        # run every supported SingleStep opcode file
make mutation-audit    # check that the benches reject injected RTL faults
make full-check        # everything, plus the profiled variant checks
12

Sources

References and credits

SourceUse
Intel iAPX 286 / 80286 and 80287 Programmer's Reference Manuals (210498-003, -005)Primary architectural reference.
Intel 80286 Hardware Reference Manual (210760-002)Bus, priorities, INTA, HOLD, halt and shutdown.
Intel 80286 B-2/B-3 step information (1984)INT/TF and INTA LOCK corrections.
Intel "Undocumented iAPX 286 Test Instruction"LOADALL image layout.
SingleStepTests/80286 (MIT)Hardware-derived test corpus, fetched at build time and not redistributed.
dbalsom/moo (MIT)MOO 1.1 format, implemented independently.
86Box, PCemBehavioral cross-references only; no code copied.
MCL86, 80x86, z386, ao486Architectural ideas only; no code copied.

SPK286 is directed by spark2k06 (aitorgomez.net) and developed with AI coding agents, which wrote the RTL, microcode, tools and tests. spark2k06 set the architecture and scope, defined the verification strategy, ran the testing and drove the adjustments needed to make the core work.

No external CPU source code is included: emulators and other cores are treated as evidence, not authority, and expected behavior derives from Intel documentation and physical 80286 evidence. That rule, the hardware-captured test corpus and independent reference models are what keep AI-written RTL honest.

Source code and the full technical reference: github.com/spark2k06/SPK286 · docs/SPK286.md.

SPK286 is released under the MIT License, so it can be integrated into designs under any license, including GPL machine cores. The SingleStepTests/80286 corpus (MIT) is fetched at build time and not redistributed.