Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions MCL86/Core/MCL86_Microcode_Xilinx_Version_5.coe

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions MCL86/Core/MCL86_Microcode_Xilinx_Version_6.coe

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions MCL86/Core/MCL86_Microcode_Xilinx_Version_7.coe

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions MCL86/Core/MCL86_Microcode_Xilinx_Version_8.coe

Large diffs are not rendered by default.

240 changes: 205 additions & 35 deletions MCL86/Core/Microcode_MCL86.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,53 @@
// Revision 4.0 12/2/2022
// Assigned values to unused flags[1,3,5] for opcode LAHF
//
// Revision 5.0 5/5/2026
// STORE/RESTORE_SEG_PREFIX use r2 scratch instead of stashing the
// segment-override prefix bit in eu_flags bit 4 (AF). The original
// procedure corrupted architectural AF on every string-op call site
// (8086 spec says string ops preserve flags). r2 is dead during string
// ops since the INC16/DEC16 flag-processing path that uses r2 isn't
// called between STORE and RESTORE.
//
// Revision 6.0 5/6/2026
// Segment-load interrupt-poll fix. Real 8086 only delays interrupts by
// one instruction after MOV/POP to SS (atomic stack reload), not after
// other segment-register loads. Previously, all POP_SEGREG (POP ES/CS/
// SS/DS at 0x07/0x0F/0x17/0x1F) and all MOV Sreg (the 0x8E group)
// converged on the legacy 0x0470-0x0478 ending whose final jump skips
// the interrupt-poll section of the main fetch loop (jumps to 0x0011
// instead of 0x0006). That caused the worker to miss hardware
// interrupts taken by MartyPC after POP DS / POP ES / MOV ES / MOV DS,
// because the worker's microcode bypassed the 0x0008 intr_asserted
// test in the fetch loop following these instructions.
//
// Fix: split the common ending into two:
// * 0x0470-0x0478 (legacy): final jump to 0x0011, used by POP SS and
// MOV SS to preserve the architecturally-mandated 1-instruction
// interrupt delay for atomic stack reload.
// * 0x0FC7-0x0FCF (new): mirror of 0x0470-0x0478 with final jump to
// 0x0006 (with INT poll), used by POP ES/CS/DS and MOV ES/CS/DS.
// MOV ES/CS/DS reach the new tail via 0x0D63 redirected from 0x0474 to
// 0x0FCB (the PFQ-wait entry of the new tail). MOV SS bypasses 0x0D62
// /0x0D63 entirely by jumping from 0x0D6B to a new SS-specific
// debounce + skip-INT path at 0x0FD0-0x0FD1.
//
// Revision 7.0 5/11/2026
// IRET (0xCF) interrupt-recognition fix. Real 8086 only delays
// interrupt recognition after STI / MOV SS / POP SS; IRET does not.
// MCL86jr's microcode and EU both deferred INT after IRET for two
// reasons, both fixed here:
// 1. IRET's ending jumped to the legacy 0x0470-0x0478 skip-INT
// path. Switched to the V6 with-INT-poll path (0x0FCB).
// 2. Even with the with-INT-poll path, the EU's intr_enable_delayed
// register only updates on new_instruction edges, so the IRET
// microcode's post-FLAGS-load intr poll would still see IF=0.
// eu.v now also bypasses that delay when the microcode is at
// IRET's "OR r0 into FLAGS" step (eu_rom_address == 0x0532),
// syncing intr_enable_delayed from the ALU output's bit 9 in
// the same cycle.
// Caught running King's Quest I on PCjr: BIOS timer ISR's IRET to
// user code races the next 18.2Hz tick.
//
//------------------------------------------------------------------------
//
Expand Down Expand Up @@ -2451,15 +2498,21 @@ p 00 00000 00001 02BE
p 01 00000 00001 5FFF
p 01 00000 00001 C492
#
# Dummy = AX OR 0x0000 to populate last_alu for flags processing
# Dummy = AL only (AX AND 0x00FF) to populate last_alu for flags
# processing. AAD's flags (SF, ZF, PF) are defined on AL per Intel
# spec, not on the full AX word. AAD always zeroes AH in the body
# (0x02BB/0x02BD), so for AAD specifically AX AND 0xFF == AX, but
# calling CALCULATE_FLAGS_BYTE makes the byte-domain explicit and
# keeps the SF check on AL bit 7 (not AX bit 15).
p 00 00000 00001 02BF
p 01 00000 00001 5E0F
p 01 00000 00001 0000
p 01 00000 00001 4E0F
p 01 00000 00001 00FF
#
# Call All_Flags_WORD
# Call CALCULATE_FLAGS_BYTE (byte-domain SF/ZF/PF for AL result).
# Was: CALL All_Flags_WORD (0x03D3) — wrong domain for AAM/AAD.
p 00 00000 00001 02C0
p 01 00000 00001 1100
p 01 00000 00001 03D3
p 01 00000 00001 0990
#
# Jump unconditional to Multibyte ending
p 00 00000 00001 02C1
Expand Down Expand Up @@ -2554,15 +2607,23 @@ p 00 00000 00001 02D6
p 01 00000 00001 5FFF
p 01 00000 00001 C675
#
# Dummy = AX OR 0x0000 to populate last_alu for flags processing
# Dummy = AL only (AX AND 0x00FF) to populate last_alu for flags
# processing. AAM's flags (SF, ZF, PF) are defined on AL per Intel
# spec, NOT on the full AX word. Original code populated alu_last
# with AX, then All_Flags_WORD's bit-15 SF check and 16-bit ZF check
# tripped on the AH=quotient byte — e.g. AL_pre=0x50, AAM 10 →
# AX=0x0800, AL=0 (so ZF should be 1), but All_Flags_WORD saw
# alu_last=0x0800 ≠ 0 and left ZF=0. Caught by Lotus 1-2-3 cosim
# at op 4901806 in CS:IP F000:19CB-area.
p 00 00000 00001 02D7
p 01 00000 00001 5E0F
p 01 00000 00001 0000
p 01 00000 00001 4E0F
p 01 00000 00001 00FF
#
# Call All_Flags_WORD
# Call CALCULATE_FLAGS_BYTE (byte-domain SF/ZF/PF for AL result).
# Was: CALL All_Flags_WORD (0x03D3) — wrong domain; checked AX.
p 00 00000 00001 02D8
p 01 00000 00001 1100
p 01 00000 00001 03D3
p 01 00000 00001 0990
#
# Jump unconditional to Multibyte ending
p 00 00000 00001 02D9
Expand Down Expand Up @@ -4646,10 +4707,13 @@ p 00 00000 00001 045A
p 01 00000 00001 4DDF
p 01 00000 00001 CFF3
#
# Jump unconditional to POP_SEGREG ending
# Jump unconditional to POP_SEGREG_WITH_INT ending (0xFC7) — POP ES
# polls INT on completion, real-8086 behavior. POP SS keeps the legacy
# skip-INT ending at 0x0470 (1-instruction interrupt delay for atomic
# stack reload).
p 00 00000 00001 045B
p 01 00000 00001 1000
p 01 00000 00001 0470
p 01 00000 00001 0FC7
#
#
# -----------------------------------------------------
Expand Down Expand Up @@ -4678,10 +4742,12 @@ p 00 00000 00001 045F
p 01 00000 00001 4DDF
p 01 00000 00001 CFD3
#
# Jump unconditional to POP_SEGREG ending
# Jump unconditional to POP_SEGREG_WITH_INT ending (0xFC7) — POP CS
# polls INT on completion (POP CS isn't a real 8086 opcode in modern
# decode, but the handler exists; same fix as POP ES/DS).
p 00 00000 00001 0460
p 01 00000 00001 1000
p 01 00000 00001 0470
p 01 00000 00001 0FC7
#
#
# -----------------------------------------------------
Expand Down Expand Up @@ -4742,10 +4808,11 @@ p 00 00000 00001 0469
p 01 00000 00001 4DDF
p 01 00000 00001 CFC3
#
# Jump unconditional to POP_SEGREG ending
# Jump unconditional to POP_SEGREG_WITH_INT ending (0xFC7) — POP DS
# polls INT on completion, real-8086 behavior.
p 00 00000 00001 046A
p 01 00000 00001 1000
p 01 00000 00001 0470
p 01 00000 00001 0FC7
#
# -----------------------------------------------------
#
Expand Down Expand Up @@ -5640,7 +5707,13 @@ p 00 00000 00001 0531
p 01 00000 00001 488F
p 01 00000 00001 0000
#
# OR r0 into Flags register
# OR r0 into Flags register. eu.v detects this microcode step (when
# eu_rom_data delivers this word, which is when eu_rom_address has
# already advanced to 0x0533 due to the 1-cycle synchronous BRAM
# read) and immediately syncs intr_enable_delayed from bit 9 of the
# ALU output, so the post-load intr poll sees IF=1 without waiting
# for the next new_instruction edge. Real 8086 takes pending
# interrupts right after IRET.
p 00 00000 00001 0532
p 01 00000 00001 5889
p 01 00000 00001 0000
Expand Down Expand Up @@ -5675,10 +5748,14 @@ p 00 00000 00001 0538
p 01 00000 00001 4DDF
p 01 00000 00001 0000
#
# Jump unconditional to common code that waits for prefetch queue, then returns to main loop skipping all interrupts.
# Jump to PFQ-wait entry of the with-INT-poll ending (0xFCB). Real
# 8086 only delays interrupt recognition after STI / MOV SS / POP SS;
# IRET should NOT defer — the BIOS timer ISR returns via IRET and
# expects the NEXT 18.2Hz tick to be taken immediately if it lands
# during IRET. Was 0x0474 (legacy skip-INT path used by POP/MOV SS).
p 00 00000 00001 0539
p 01 00000 00001 1000
p 01 00000 00001 0474
p 01 00000 00001 0FCB
#
# -----------------------------------------------------
#
Expand Down Expand Up @@ -8234,7 +8311,16 @@ p 01 00000 00001 0011
#
# PROCEDURE - STORE_SEG_PREFIX
#
# Store the Segment Override Prefix bit in the AF Flag temporarily
# Stash the Segment Override Prefix bit in scratch register r2 across a
# BIU operation that needs the override cleared (e.g. MOVS dest write
# after a DS-overridden source read). Originally this used eu_flags
# bit 4 (AF) as the stash, but that corrupts the architectural AF flag
# on every string-op call site (8086 says string ops preserve flags).
# Cannot use r0/r1 as scratch — CMPSB/CMPSW load r0 with the source
# byte at 0x0645 and r1 with the dest byte at 0x064B, with the
# STORE_SEG_PREFIX call sandwiched at 0x0646. r3 is the BIU address
# register (set to SI then DI between calls), also unsafe. r2 is dead
# across all string-op STORE/RESTORE windows.
#
# --------------------------------------------------------------------
#
Expand All @@ -8248,10 +8334,10 @@ p 00 00000 00001 0751
p 01 00000 00001 1002
p 01 00000 00001 0755
#
# Set eu_alu_a flag to 1
# r2 = 1 (scratch: segment override was set)
p 00 00000 00001 0752
p 01 00000 00001 588F
p 01 00000 00001 0010
p 01 00000 00001 5BFF
p 01 00000 00001 0001
#
# Set segment_override prefix bit to 0
p 00 00000 00001 0753
Expand All @@ -8263,10 +8349,10 @@ p 00 00000 00001 0754
p 01 00000 00001 1030
p 01 00000 00001 4000
#
# B: Set eu_alu_a flag to 0
# B: r2 = 0 (scratch: segment override was clear)
p 00 00000 00001 0755
p 01 00000 00001 488F
p 01 00000 00001 FFEF
p 01 00000 00001 5BFF
p 01 00000 00001 0000
#
# Set segment_override prefix bit to 0
p 00 00000 00001 0756
Expand All @@ -8282,15 +8368,16 @@ p 01 00000 00001 4000
#
# PROCEDURE - RESTORE_SEG_PREFIX
#
# Retore the Segment Override Prefix bit from the AF Flag
# The override prefix is assumed to be 0 before entering this procedure.
# Restore the Segment Override Prefix bit from scratch register r2
# (saved by STORE_SEG_PREFIX). The override prefix is assumed to be 0
# before entering this procedure.
#
# --------------------------------------------------------------------
#
# Isolate eu_alu_a flag into Dummy
# Isolate r2 bit 0 (saved segment-override bit) into Dummy
p 00 00000 00001 075A
p 01 00000 00001 4E8F
p 01 00000 00001 0010
p 01 00000 00001 4EBF
p 01 00000 00001 0001
#
# Return to calling code if last ALU=0
p 00 00000 00001 075B
Expand Down Expand Up @@ -16047,10 +16134,13 @@ p 00 00000 00001 0D62
p 01 00000 00001 4DDF
p 01 00000 00001 0000
#
# Jump unconditional to common code that waits for prefetch queue, then returns to main loop skipping polling all interrupts
# Jump unconditional to PFQ-wait entry of POP_SEGREG_WITH_INT ending
# (0xFCB). MOV ES/CS/DS now poll INT on completion, real-8086 behavior.
# MOV SS bypasses this by going through 0xFD0 (SS-specific debounce
# that jumps to legacy skip-INT 0x0474).
p 00 00000 00001 0D63
p 01 00000 00001 1000
p 01 00000 00001 0474
p 01 00000 00001 0FCB
#
# Update the CS Segment Register
# ------------------------------
Expand Down Expand Up @@ -16085,10 +16175,12 @@ p 00 00000 00001 0D6A
p 01 00000 00001 5DFF
p 01 00000 00001 3010
#
# Jump unconditional to common code
# Jump unconditional to SS-specific debounce + skip-INT (0xFD0) so MOV
# SS keeps the 1-instruction interrupt delay for atomic stack reload.
# MOV ES/CS/DS continue through 0x0D62 → 0x0D63 → 0xFCB (with INT).
p 00 00000 00001 0D6B
p 01 00000 00001 1000
p 01 00000 00001 0D62
p 01 00000 00001 0FD0
#
# Update the DS Segment Register
# ------------------------------
Expand Down Expand Up @@ -18689,6 +18781,84 @@ p 01 00000 00001 1000
p 01 00000 00001 00C6
#
#
#
# -----------------------------------------------------
# POP/MOV ES/CS/DS "with INT poll" common ending. Mirror of legacy
# 0x0470-0x0478 ending, but final jump returns to 0x0006 (with INT)
# instead of 0x0011 (skip INT). Real 8086 only delays interrupts after
# segment-register loads to SS (POP SS / MOV SS / LSS); ES/CS/DS loads
# do not delay. POP/MOV SS keep their existing 0x0470-based path.
#
# POP ES/CS/DS handlers jump to 0xFC7 (start). MOV ES/CS/DS reach
# 0xFCB (PFQ-wait entry) via 0x0D63's redirected jump.
# -----------------------------------------------------
#
# Write 12 clocks to clock counter (== 0x0470)
p 00 00000 00001 0FC7
p 01 00000 00001 5FFF
p 01 00000 00001 C02B
#
# CALL WAIT_CLOCKS (== 0x0471)
p 00 00000 00001 0FC8
p 01 00000 00001 1100
p 01 00000 00001 03F2
#
# Strobe BIU - Fetch Opcode (== 0x0472)
p 00 00000 00001 0FC9
p 01 00000 00001 5DDF
p 01 00000 00001 1004
#
# Debounce BIU Strobe (== 0x0473)
p 00 00000 00001 0FCA
p 01 00000 00001 4DDF
p 01 00000 00001 0000
#
# AND with PFQ_EMPTY mask (== 0x0474; MOV ES/CS/DS enter here)
p 00 00000 00001 0FCB
p 01 00000 00001 4EEF
p 01 00000 00001 0080
#
# Jump back to 0xFCB if PFQ empty (== 0x0475)
p 00 00000 00001 0FCC
p 01 00000 00001 1002
p 01 00000 00001 0FCB
#
# Clear prefix flags (== 0x0476)
p 00 00000 00001 0FCD
p 01 00000 00001 488F
p 01 00000 00001 0FD5
#
# Clear BIU command (== 0x0477)
p 00 00000 00001 0FCE
p 01 00000 00001 4DDF
p 01 00000 00001 0000
#
# Jump to main loop start at 0x0006 (with INT poll). This is the only
# difference from 0x0478, which jumps to 0x0011 (skip INT).
p 00 00000 00001 0FCF
p 01 00000 00001 1000
p 01 00000 00001 0006
#

#
# -----------------------------------------------------
# MOV SS-specific debounce + skip-INT jump. MOV SS at 0x0D6B redirects
# here so it bypasses the 0x0D63 path (which now points at the with-INT
# tail) and lands on the legacy skip-INT 0x0474 ending.
# -----------------------------------------------------
#
# Debounce BIU Strobe (mirror of 0x0D62)
p 00 00000 00001 0FD0
p 01 00000 00001 4DDF
p 01 00000 00001 0000
#
# Jump to legacy skip-INT PFQ-wait (== old 0x0D63 → 0x0474)
p 00 00000 00001 0FD1
p 01 00000 00001 1000
p 01 00000 00001 0474
#



# DONE

Expand Down
Loading