WS: Improved IRQ handling after IRQ flag is set or SS segment is modified

This commit is contained in:
Sour 2025-03-01 22:17:45 +09:00
parent 5823761791
commit 211b42e91c
3 changed files with 85 additions and 45 deletions

View file

@ -49,7 +49,7 @@ void WsCpu::Exec()
if(irqPending) {
_state.Halted = false;
if(_state.Flags.Irq) {
if(_state.Flags.Irq && _suppressIrqClock != _state.CycleCount) {
Interrupt(_memoryManager->GetIrqVector(), true);
}
}
@ -232,7 +232,12 @@ void WsCpu::PopFlags()
Idle<2>();
uint16_t flags;
Pop(flags);
bool irq = _state.Flags.Irq;
_state.Flags.Set(flags);
if(!irq && _state.Flags.Irq) {
//Suppress IRQs for the next instruction when irq flag gets set
SuppressIrq(false);
}
}
void WsCpu::PopMemory()
@ -475,7 +480,12 @@ void WsCpu::RetInterrupt()
Pop(_state.CS);
uint16_t flags;
Pop(flags);
bool irq = _state.Flags.Irq;
_state.Flags.Set(flags);
if(!irq && _state.Flags.Irq) {
//Suppress IRQs for the next instruction when irq flag gets set
SuppressIrq(false);
}
ClearPrefetch();
}
@ -809,12 +819,33 @@ void WsCpu::Halt()
_state.Halted = true;
}
void WsCpu::SuppressIrq(bool suppressTrap)
{
//When the IRQ flag gets set, or when the SS segment is modified, IRQs
//are suppressed after the current instruction and will only trigger
//on the instruction after that.
_suppressIrqClock = _state.CycleCount;
if(suppressTrap) {
_suppressTrapClock = _state.CycleCount;
}
}
void WsCpu::SetFlagValue(bool& flag, bool value)
{
Idle<4>();
flag = value;
}
void WsCpu::SetIrqFlag()
{
Idle<4>();
if(!_state.Flags.Irq) {
_state.Flags.Irq = true;
//Suppress IRQs for the next instruction when irq flag gets set
SuppressIrq(false);
}
}
void WsCpu::ClearPrefetch()
{
#ifndef DUMMYCPU
@ -1070,6 +1101,10 @@ void WsCpu::MoveSegment()
if constexpr(direction) {
SetModSegRegister(_modRm.Register, param);
if(_modRm.Register == 2) {
//SS was updated, suppress IRQs after this instruction
SuppressIrq(true);
}
} else {
SetModRm(param);
}
@ -1827,7 +1862,7 @@ start:
case 0x14: ProcessAluImm<AluOp::Adc, uint8_t>(); break;
case 0x15: ProcessAluImm<AluOp::Adc, uint16_t>(); break;
case 0x16: PushSegment(_state.SS); break;
case 0x17: PopSegment(_state.SS); break;
case 0x17: PopSegment(_state.SS); SuppressIrq(true); break;
case 0x18: ProcessAluModRm<AluOp::Sbb, false, uint8_t>(); break;
case 0x19: ProcessAluModRm<AluOp::Sbb, false, uint16_t>(); break;
case 0x1A: ProcessAluModRm<AluOp::Sbb, true, uint8_t>(); break;
@ -2069,7 +2104,7 @@ start:
case 0xF8: SetFlagValue(_state.Flags.Carry, false); break; //CLC
case 0xF9: SetFlagValue(_state.Flags.Carry, true); break; //STC
case 0xFA: SetFlagValue(_state.Flags.Irq, false); break; //CLI
case 0xFB: SetFlagValue(_state.Flags.Irq, true); break; //STI
case 0xFB: SetIrqFlag(); break; //STI
case 0xFC: SetFlagValue(_state.Flags.Direction, false); break; //CLD
case 0xFD: SetFlagValue(_state.Flags.Direction, true); break; //STD
case 0xFE: Grp45ModRm<uint8_t>(); break;
@ -2080,7 +2115,7 @@ start:
_prefix = {};
}
if(_state.Flags.Trap) {
if(_state.Flags.Trap && _suppressTrapClock != _state.CycleCount) {
Interrupt(1);
}
}
@ -2129,4 +2164,7 @@ void WsCpu::Serialize(Serializer& s)
SV(_modRm.Mode);
SV(_modRm.Register);
SV(_modRm.Rm);
SV(_suppressIrqClock);
SV(_suppressTrapClock);
}

View file

@ -70,6 +70,9 @@ private:
ModRmState _modRm = {};
PrefixState _prefix = {};
uint64_t _suppressIrqClock = 0;
uint64_t _suppressTrapClock = 0;
#ifndef DUMMYCPU
WsCpuPrefetch _prefetch;
#endif
@ -253,7 +256,10 @@ private:
void Wait();
void Halt();
void SuppressIrq(bool suppressTrap);
void SetFlagValue(bool& flag, bool value);
void SetIrqFlag();
public:
WsCpu(Emulator* emu, WsMemoryManager* memoryManager);

View file

@ -59,10 +59,10 @@ void WsPpu::ProcessHblank()
_timer->TickHorizontalTimer();
if(_state.Scanline < WsConstants::ScreenHeight) {
switch(_state.Mode) {
case WsVideoMode::Monochrome: DrawScanline<WsVideoMode::Monochrome>(); break;
case WsVideoMode::Color2bpp: DrawScanline<WsVideoMode::Color2bpp>(); break;
case WsVideoMode::Color4bpp: DrawScanline<WsVideoMode::Color4bpp>(); break;
case WsVideoMode::Color4bppPacked: DrawScanline<WsVideoMode::Color4bppPacked>(); break;
case WsVideoMode::Monochrome: DrawScanline<WsVideoMode::Monochrome>(); break;
case WsVideoMode::Color2bpp: DrawScanline<WsVideoMode::Color2bpp>(); break;
case WsVideoMode::Color4bpp: DrawScanline<WsVideoMode::Color4bpp>(); break;
case WsVideoMode::Color4bppPacked: DrawScanline<WsVideoMode::Color4bppPacked>(); break;
}
}
}
@ -157,11 +157,9 @@ void WsPpu::DrawSprites()
if(x >= 224) {
continue;
}
else if(_rowData[rowIndex][x].Priority > 0) {
} else if(_rowData[rowIndex][x].Priority > 0) {
continue;
}
else if(_state.SpriteWindow.EnabledLatch && showOutsideWindow == _state.SpriteWindow.IsInsideWindow(x, scanline)) {
} else if(_state.SpriteWindow.EnabledLatch && showOutsideWindow == _state.SpriteWindow.IsInsideWindow(x, scanline)) {
//Don't draw this pixel, it's outside/inside the window and should only be drawn on the other side
continue;
}
@ -257,40 +255,39 @@ template<WsVideoMode mode>
uint16_t WsPpu::GetPixelColor(uint16_t tileAddr, uint8_t column)
{
switch(mode) {
case WsVideoMode::Monochrome: {
uint8_t tileData = _vram[tileAddr];
uint8_t tileData2 = _vram[tileAddr + 1];
return (
((tileData << column) & 0x80) >> 7 |
((tileData2 << column) & 0x80) >> 6
case WsVideoMode::Monochrome: {
uint8_t tileData = _vram[tileAddr];
uint8_t tileData2 = _vram[tileAddr + 1];
return (
((tileData << column) & 0x80) >> 7 |
((tileData2 << column) & 0x80) >> 6
);
}
}
case WsVideoMode::Color2bpp: {
uint8_t tileData = _vram[tileAddr];
uint8_t tileData2 = _vram[tileAddr + 1];
return (
((tileData << column) & 0x80) >> 7 |
((tileData2 << column) & 0x80) >> 6
case WsVideoMode::Color2bpp: {
uint8_t tileData = _vram[tileAddr];
uint8_t tileData2 = _vram[tileAddr + 1];
return (
((tileData << column) & 0x80) >> 7 |
((tileData2 << column) & 0x80) >> 6
);
}
}
case WsVideoMode::Color4bpp: {
uint8_t tileData = _vram[tileAddr];
uint8_t tileData2 = _vram[tileAddr + 1];
uint8_t tileData3 = _vram[tileAddr + 2];
uint8_t tileData4 = _vram[tileAddr + 3];
return (
((tileData << column) & 0x80) >> 7 |
((tileData2 << column) & 0x80) >> 6 |
((tileData3 << column) & 0x80) >> 5 |
((tileData4 << column) & 0x80) >> 4
case WsVideoMode::Color4bpp: {
uint8_t tileData = _vram[tileAddr];
uint8_t tileData2 = _vram[tileAddr + 1];
uint8_t tileData3 = _vram[tileAddr + 2];
uint8_t tileData4 = _vram[tileAddr + 3];
return (
((tileData << column) & 0x80) >> 7 |
((tileData2 << column) & 0x80) >> 6 |
((tileData3 << column) & 0x80) >> 5 |
((tileData4 << column) & 0x80) >> 4
);
}
}
case WsVideoMode::Color4bppPacked: {
return (_vram[tileAddr + column / 2] >> (column & 0x01 ? 0 : 4)) & 0x0F;
}
case WsVideoMode::Color4bppPacked:
return (_vram[tileAddr + column / 2] >> (column & 0x01 ? 0 : 4)) & 0x0F;
}
return 0;
@ -535,7 +532,7 @@ uint8_t WsPpu::ReadPort(uint16_t port)
return (
(_state.LcdEnabled ? 0x01 : 0) |
(_state.HighContrast ? 0x02 : 0)
);
);
case 0x15: return _state.Icons.Value;
case 0x16: return _state.LastScanline;
@ -549,14 +546,14 @@ uint8_t WsPpu::ReadPort(uint16_t port)
return (
_state.BwShades[(port - 0x1C) * 2] |
(_state.BwShades[(port - 0x1C) * 2 + 1] << 4)
);
);
default:
if(port >= 0x20 && port <= 0x3F) {
return (
_state.BwPalettes[(port - 0x20) * 2] |
(_state.BwPalettes[(port - 0x20) * 2 + 1] << 4)
);
);
}
else {
LogDebug("[Debug] PPU Read - missing handler: $" + HexUtilities::ToHex(port));
@ -647,8 +644,7 @@ void WsPpu::WritePort(uint16_t port, uint8_t value)
if(port >= 0x20 && port <= 0x3F) {
_state.BwPalettes[(port - 0x20) * 2] = value & 0x07;
_state.BwPalettes[(port - 0x20) * 2 + 1] = (value >> 4) & 0x07;
}
else {
} else {
LogDebug("[Debug] PPU Write - missing handler: $" + HexUtilities::ToHex(port) + " = " + HexUtilities::ToHex(value));
}
break;