gen: implement H-ints

gen: implement Vram/Vram DMA copy (badly)
gen: fix dumb sprite rendering bug
gen: fix crash bug with certain WINDOW settings
This commit is contained in:
beirich 2012-09-01 18:40:52 +00:00
parent 35ec42073f
commit d586876f40
4 changed files with 138 additions and 60 deletions

View File

@ -24,9 +24,9 @@ namespace BizHawk.Emulation.Consoles.Sega
bool DmaFillModePending; bool DmaFillModePending;
void ExecuteDmaFill(ushort data) void ExecuteVramFill(ushort data)
{ {
//Log.Error("VDP","DMA FILL REQD, WRITE {0:X4}, {1:X4} times, at {2:X4}", data, DmaLength, VdpDataAddr); Log.Note("VDP", "DMA VRAM FILL REQD, WRITE {0:X4}, {1:X4} times, at {2:X4}", data, DmaLength, VdpDataAddr);
// TODO: It should spread this out, not do it all at once. // TODO: It should spread this out, not do it all at once.
// TODO: DMA can go to places besides just VRAM (eg CRAM, VSRAM) ??? can it? // TODO: DMA can go to places besides just VRAM (eg CRAM, VSRAM) ??? can it?
@ -50,7 +50,7 @@ namespace BizHawk.Emulation.Consoles.Sega
void Execute68000VramCopy() void Execute68000VramCopy()
{ {
//Log.Error("VDP", "DMA 68000 -> VRAM COPY REQ'D. LENGTH {0:X4}, SOURCE {1:X4}", DmaLength, DmaSource); Log.Note("VDP", "DMA 68000 -> VRAM COPY REQ'D. LENGTH {0:X4}, SOURCE {1:X4}", DmaLength, DmaSource);
int length = DmaLength; int length = DmaLength;
if (length == 0) if (length == 0)
@ -68,5 +68,26 @@ namespace BizHawk.Emulation.Consoles.Sega
// TODO: find correct number of 68k cycles to burn // TODO: find correct number of 68k cycles to burn
} }
void ExecuteVramVramCopy()
{
Log.Note("VDP", "DMA VRAM -> VRAM COPY REQ'D. LENGTH {0:X4}, SOURCE {1:X4}", DmaLength, DmaSource);
int length = DmaLength;
if (length == 0)
length = 0x10000;
int source = DmaSource;
do
{
ushort value = (ushort)ReadVdpData();
source += 2;
// TODO funky source behavior
WriteVdpData(value);
} while (--length > 0);
// TODO: find correct number of 68k cycles to burn
}
} }
} }

View File

@ -18,17 +18,22 @@ namespace BizHawk.Emulation.Consoles.Sega
public void RenderLine() public void RenderLine()
{ {
Array.Clear(PriorityBuffer, 0, 320);
if (DisplayEnabled) if (DisplayEnabled)
{ {
Array.Clear(PriorityBuffer, 0, 320);
RenderScrollA(); RenderScrollA();
RenderScrollB(); RenderScrollB();
RenderSpritesScanline(); RenderSpritesScanline();
} }
else
{
// If display is disabled, fill in with background color.
for (int i = 0; i < FrameWidth; i++)
FrameBuffer[(ScanLine * FrameWidth) + i] = BackgroundColor;
}
//if (ScanLine == 223) // shrug //if (ScanLine == 223) // shrug
//RenderPalette(); // RenderPalette();
} }
void RenderPalette() void RenderPalette()
@ -106,7 +111,7 @@ namespace BizHawk.Emulation.Consoles.Sega
int data = Registers[0x11]; int data = Registers[0x11];
int windowHPosition = (data & 31) * 2; // Window H position is set in 2-cell increments int windowHPosition = (data & 31) * 2; // Window H position is set in 2-cell increments
bool fromLeft = (data & 0x80) == 0; bool fromLeft = (data & 0x80) == 0;
if (windowHPosition == 0) if (windowHPosition == 0)
{ {
startPixel = -1; startPixel = -1;
@ -118,11 +123,18 @@ namespace BizHawk.Emulation.Consoles.Sega
{ {
startPixel = 0; startPixel = 0;
endPixel = (windowHPosition * 8); endPixel = (windowHPosition * 8);
if (endPixel > FrameWidth)
endPixel = FrameWidth;
} }
else else
{ {
startPixel = windowHPosition * 8; startPixel = windowHPosition * 8;
endPixel = FrameWidth; endPixel = FrameWidth;
if (startPixel > FrameWidth)
{
startPixel = -1;
endPixel = -1;
}
} }
} }
@ -263,6 +275,7 @@ namespace BizHawk.Emulation.Consoles.Sega
continue; continue;
if (sprite.Priority == false && PriorityBuffer[sprite.X + xi] >= 3) continue; if (sprite.Priority == false && PriorityBuffer[sprite.X + xi] >= 3) continue;
if (PriorityBuffer[sprite.X + xi] == 9) continue;
int pixel = PatternBuffer[((pattern + ((-xi / 8) * sprite.HeightCells)) * 64) + ((yline & 7) * 8) + (7 - (xi & 7))]; int pixel = PatternBuffer[((pattern + ((-xi / 8) * sprite.HeightCells)) * 64) + ((yline & 7) * 8) + (7 - (xi & 7))];
if (pixel != 0) if (pixel != 0)

View File

@ -57,6 +57,8 @@ namespace BizHawk.Emulation.Consoles.Sega
public const int StatusSpriteOverflow = 0x40; public const int StatusSpriteOverflow = 0x40;
public const int StatusVerticalInterruptPending = 0x80; public const int StatusVerticalInterruptPending = 0x80;
public bool VdpDebug = false;
public GenVDP() public GenVDP()
{ {
WriteVdpRegister(00, 0x04); WriteVdpRegister(00, 0x04);
@ -140,14 +142,13 @@ namespace BizHawk.Emulation.Consoles.Sega
switch (Registers[23] >> 6) switch (Registers[23] >> 6)
{ {
case 2: case 2:
Log.Note("VDP", "VRAM FILL");
DmaFillModePending = true; DmaFillModePending = true;
break; break;
case 3: case 3:
Log.Error("VDP", "VRAM COPY **** UNIMPLEMENTED ***"); Log.Error("VDP", "VRAM COPY **** UNIMPLEMENTED ***");
ExecuteVramVramCopy();
break; break;
default: default:
Log.Note("VDP", "68k->VRAM COPY");
Execute68000VramCopy(); Execute68000VramCopy();
break; break;
} }
@ -179,7 +180,7 @@ namespace BizHawk.Emulation.Consoles.Sega
if (DmaFillModePending) if (DmaFillModePending)
{ {
ExecuteDmaFill(data); ExecuteVramFill(data);
} }
switch (VdpDataCode & 7) switch (VdpDataCode & 7)
@ -187,21 +188,23 @@ namespace BizHawk.Emulation.Consoles.Sega
case CommandVramWrite: // VRAM Write case CommandVramWrite: // VRAM Write
VRAM[VdpDataAddr & 0xFFFE] = (byte) data; VRAM[VdpDataAddr & 0xFFFE] = (byte) data;
VRAM[(VdpDataAddr & 0xFFFE) + 1] = (byte) (data >> 8); VRAM[(VdpDataAddr & 0xFFFE) + 1] = (byte) (data >> 8);
//Log.Error("VDP", "VRAM[{0:X4}] = {1:X4}", VdpDataAddr, data); if (VdpDebug)
Log.Note("VDP", "VRAM[{0:X4}] = {1:X4}", VdpDataAddr, data);
UpdatePatternBuffer(VdpDataAddr & 0xFFFE); UpdatePatternBuffer(VdpDataAddr & 0xFFFE);
UpdatePatternBuffer((VdpDataAddr & 0xFFFE) + 1); UpdatePatternBuffer((VdpDataAddr & 0xFFFE) + 1);
//Console.WriteLine("Wrote VRAM[{0:X4}] = {1:X4}", VdpDataAddr, data);
VdpDataAddr += Registers[0x0F]; VdpDataAddr += Registers[0x0F];
break; break;
case CommandCramWrite: // CRAM write case CommandCramWrite: // CRAM write
CRAM[(VdpDataAddr / 2) % 64] = data; CRAM[(VdpDataAddr / 2) % 64] = data;
//Console.WriteLine("Wrote CRAM[{0:X2}] = {1:X4}", (VdpDataAddr / 2) % 64, data); if (VdpDebug)
Log.Note("VDP", "Wrote CRAM[{0:X2}] = {1:X4}", (VdpDataAddr / 2) % 64, data);
ProcessPalette((VdpDataAddr/2)%64); ProcessPalette((VdpDataAddr/2)%64);
VdpDataAddr += Registers[0x0F]; VdpDataAddr += Registers[0x0F];
break; break;
case CommandVsramWrite: // VSRAM write case CommandVsramWrite: // VSRAM write
VSRAM[(VdpDataAddr / 2) % 40] = data; VSRAM[(VdpDataAddr / 2) % 40] = data;
//Console.WriteLine("Wrote VSRAM[{0:X2}] = {1:X4}", (VdpDataAddr / 2) % 40, data); if (VdpDebug)
Log.Note("VDP", "Wrote VSRAM[{0:X2}] = {1:X4}", (VdpDataAddr / 2) % 40, data);
VdpDataAddr += Registers[0x0F]; VdpDataAddr += Registers[0x0F];
break; break;
default: default:
@ -243,7 +246,8 @@ namespace BizHawk.Emulation.Consoles.Sega
// TODO dont tie this to musashi cycle count. // TODO dont tie this to musashi cycle count.
// Figure out a "clean" way to get cycle counter information available to VDP. // Figure out a "clean" way to get cycle counter information available to VDP.
// Oh screw that. The VDP and the cpu cycle counters are going to be intertwined pretty tightly. // Oh screw that. The VDP and the cpu cycle counters are going to be intertwined pretty tightly.
int hcounter = (487 - Native68000.Musashi.GetCyclesRemaining()) * 255 / 487; int hcounter = (488 - Native68000.Musashi.GetCyclesRemaining()) * 255 / 488;
// FIXME: totally utterly wrong.
ushort res = (ushort) ((vcounter << 8) | (hcounter & 0xFF)); ushort res = (ushort) ((vcounter << 8) | (hcounter & 0xFF));
//Console.WriteLine("READ HVC: V={0:X2} H={1:X2} ret={2:X4}", vcounter, hcounter, res); //Console.WriteLine("READ HVC: V={0:X2} H={1:X2} ret={2:X4}", vcounter, hcounter, res);
@ -253,59 +257,72 @@ namespace BizHawk.Emulation.Consoles.Sega
public void WriteVdpRegister(int register, byte data) public void WriteVdpRegister(int register, byte data)
{ {
if (VdpDebug)
Log.Note("VDP", "Register {0}: {1:X2}", register, data); Log.Note("VDP", "Register {0}: {1:X2}", register, data);
switch (register) switch (register)
{ {
case 0x00: // Mode Set Register 1 case 0x00: // Mode Set Register 1
Registers[register] = data; Registers[register] = data;
Log.Error("VDP", "HINT enabled: " + HInterruptsEnabled); //if (VdpDebug)
Log.Note("VDP", "HINT enabled: " + HInterruptsEnabled);
break; break;
case 0x01: // Mode Set Register 2 case 0x01: // Mode Set Register 2
Registers[register] = data; if (VdpDebug)
//Log.Note("VDP", "DisplayEnabled: " + DisplayEnabled); {
//Log.Note("VDP", "DmaEnabled: " + DmaEnabled); Registers[register] = data;
//Log.Note("VDP", "VINT enabled: " + VInterruptEnabled); Log.Note("VDP", "DisplayEnabled: " + DisplayEnabled);
Log.Note("VDP", "DmaEnabled: " + DmaEnabled);
Log.Note("VDP", "VINT enabled: " + VInterruptEnabled);
}
break; break;
case 0x02: // Name Table Address for Layer A case 0x02: // Name Table Address for Layer A
NameTableAddrA = (ushort) ((data & 0x38) << 10); NameTableAddrA = (ushort) ((data & 0x38) << 10);
//Log.Error("VDP", "SET NTa A = {0:X4}", NameTableAddrA); if (VdpDebug)
Log.Note("VDP", "SET NTa A = {0:X4}", NameTableAddrA);
break; break;
case 0x03: // Name Table Address for Window case 0x03: // Name Table Address for Window
NameTableAddrWindow = (ushort) ((data & 0x3E) << 10); NameTableAddrWindow = (ushort) ((data & 0x3E) << 10);
//Log.Error("VDP", "SET NTa W = {0:X4}", NameTableAddrWindow); if (VdpDebug)
Log.Note("VDP", "SET NTa W = {0:X4}", NameTableAddrWindow);
break; break;
case 0x04: // Name Table Address for Layer B case 0x04: // Name Table Address for Layer B
NameTableAddrB = (ushort) (data << 13); NameTableAddrB = (ushort) (data << 13);
//Log.Error("VDP", "SET NTa B = {0:X4}", NameTableAddrB); if (VdpDebug)
Log.Note("VDP", "SET NTa B = {0:X4}", NameTableAddrB);
break; break;
case 0x05: // Sprite Attribute Table Address case 0x05: // Sprite Attribute Table Address
SpriteAttributeTableAddr = (ushort) (data << 9); SpriteAttributeTableAddr = (ushort) (data << 9);
//Log.Error("VDP", "SET SAT attr = {0:X4}", SpriteAttributeTableAddr); if (VdpDebug)
Log.Note("VDP", "SET SAT attr = {0:X4}", SpriteAttributeTableAddr);
break; break;
case 0x0A: // H Interrupt Register case 0x0A: // H Interrupt Register
//Log.Note("VDP", "HInt occurs every {0} lines.", data); if (VdpDebug)
Log.Note("VDP", "HInt occurs every {0} lines.", data);
break; break;
case 0x0B: // VScroll/HScroll modes case 0x0B: // VScroll/HScroll modes
/*if ((data & 4) != 0) if (VdpDebug)
Log.Note("VDP", "VSCroll Every 2 Cells Enabled"); {
else if ((data & 4) != 0)
Log.Note("VDP", "Full Screen VScroll");*/ Log.Note("VDP", "VSCroll Every 2 Cells Enabled");
else
Log.Note("VDP", "Full Screen VScroll");
int hscrollmode = data & 3; int hscrollmode = data & 3;
//switch (hscrollmode) switch (hscrollmode)
//{ {
// case 0: Log.Note("VDP", "Full Screen HScroll"); break; case 0: Log.Note("VDP", "Full Screen HScroll"); break;
// case 1: Log.Note("VDP", "Prohibited HSCROLL mode!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); break; case 1: Log.Note("VDP", "Prohibited HSCROLL mode!!! But it'll work."); break;
// case 2: Log.Note("VDP", "HScroll every 1 cell"); break; case 2: Log.Note("VDP", "HScroll every 1 cell"); break;
// case 3: Log.Note("VDP", "HScroll every 2 cell"); break; case 3: Log.Note("VDP", "HScroll every line"); break;
//} }
}
break; break;
case 0x0C: // Mode Set #4 case 0x0C: // Mode Set #4
@ -317,7 +334,7 @@ namespace BizHawk.Emulation.Consoles.Sega
{ {
FrameBuffer = new int[256*224]; FrameBuffer = new int[256*224];
FrameWidth = 256; FrameWidth = 256;
//Log.Note("VDP", "SWITCH TO 32 CELL WIDE MODE"); Log.Note("VDP", "SWITCH TO 32 CELL WIDE MODE");
} }
} else { } else {
// Display is 40 cells wide // Display is 40 cells wide
@ -325,18 +342,20 @@ namespace BizHawk.Emulation.Consoles.Sega
{ {
FrameBuffer = new int[320*224]; FrameBuffer = new int[320*224];
FrameWidth = 320; FrameWidth = 320;
//Log.Note("VDP", "SWITCH TO 40 CELL WIDE MODE"); Log.Note("VDP", "SWITCH TO 40 CELL WIDE MODE");
} }
} }
break; break;
case 0x0D: // H Scroll Table Address case 0x0D: // H Scroll Table Address
HScrollTableAddr = (ushort) (data << 10); HScrollTableAddr = (ushort) (data << 10);
//Log.Note("VDP", "SET HScrollTab attr = {0:X4}", HScrollTableAddr); if (VdpDebug)
Log.Note("VDP", "SET HScrollTab attr = {0:X4}", HScrollTableAddr);
break; break;
case 0x0F: // Auto Address Register Increment case 0x0F: // Auto Address Register Increment
//Log.Note("VDP", "Set Data Increment to " + data); //if (VdpDebug)
Log.Note("VDP", "Set Data Increment to " + data);
break; break;
case 0x10: // Nametable Dimensions case 0x10: // Nametable Dimensions
@ -359,36 +378,38 @@ namespace BizHawk.Emulation.Consoles.Sega
case 0x11: // Window H Position case 0x11: // Window H Position
int whp = data & 31; int whp = data & 31;
bool fromright = (data & 0x80) != 0; bool fromright = (data & 0x80) != 0;
//Log.Error("VDP", "Window H is {0} units from {1}", whp, fromright ? "right" : "left"); if (VdpDebug)
Log.Note("VDP", "Window H is {0} units from {1}", whp, fromright ? "right" : "left");
break; break;
case 0x12: // Window V case 0x12: // Window V
//whp = data & 31; whp = data & 31;
//fromright = (data & 0x80) != 0; fromright = (data & 0x80) != 0;
//Log.Error("VDP", "Window V is {0} units from {1}", whp, fromright ? "lower" : "upper"); if (VdpDebug)
Log.Note("VDP", "Window V is {0} units from {1}", whp, fromright ? "lower" : "upper");
break; break;
case 0x13: // DMA Length Low case 0x13: // DMA Length Low
Registers[register] = data; Registers[register] = data;
//Log.Note("VDP", "DMA Length = {0:X4}", DmaLength); Log.Note("VDP", "DMA Length = {0:X4}", DmaLength);
break; break;
case 0x14: // DMA Length High case 0x14: // DMA Length High
Registers[register] = data; Registers[register] = data;
//Log.Note("VDP", "DMA Length = {0:X4}", DmaLength); Log.Note("VDP", "DMA Length = {0:X4}", DmaLength);
break; break;
case 0x15: // DMA Source Low case 0x15: // DMA Source Low
Registers[register] = data; Registers[register] = data;
//Log.Note("VDP", "DMA Source = {0:X6}", DmaSource); Log.Note("VDP", "DMA Source = {0:X6}", DmaSource);
break; break;
case 0x16: // DMA Source Mid case 0x16: // DMA Source Mid
Registers[register] = data; Registers[register] = data;
//Log.Note("VDP", "DMA Source = {0:X6}", DmaSource); Log.Note("VDP", "DMA Source = {0:X6}", DmaSource);
break; break;
case 0x17: // DMA Source High case 0x17: // DMA Source High
Registers[register] = data; Registers[register] = data;
//Log.Note("VDP", "DMA Source = {0:X6}", DmaSource); Log.Note("VDP", "DMA Source = {0:X6}", DmaSource);
break; break;
} }

View File

@ -147,18 +147,31 @@ namespace BizHawk.Emulation.Consoles.Sega
{ {
//Log.Error("VDP","FRAME {0}, SCANLINE {1}", Frame, VDP.ScanLine); //Log.Error("VDP","FRAME {0}, SCANLINE {1}", Frame, VDP.ScanLine);
if (VDP.ScanLine < 224) if (VDP.ScanLine < VDP.FrameHeight)
VDP.RenderLine(); VDP.RenderLine();
Exec68k(487); Exec68k(365);
if (Z80Runnable) RunZ80(171);
// H-Int now?
VDP.HIntLineCounter--;
if (VDP.HIntLineCounter < 0)
{ {
SoundCPU.ExecuteCycles(228); VDP.HIntLineCounter = VDP.Registers[10];
SoundCPU.Interrupt = false; VDP.VdpStatusWord |= GenVDP.StatusHorizBlanking;
} else {
SoundCPU.TotalExecutedCycles += 228; // I emulate the YM2612 synced to Z80 clock, for better or worse. Keep the timer going even if Z80 isn't running. if (VDP.HInterruptsEnabled)
{
Set68kIrq(4);
//Console.WriteLine("Fire hint!");
}
} }
Exec68k(488 - 365);
RunZ80(228 - 171);
if (VDP.ScanLine == 224) if (VDP.ScanLine == 224)
{ {
VDP.VdpStatusWord |= GenVDP.StatusVerticalInterruptPending; VDP.VdpStatusWord |= GenVDP.StatusVerticalInterruptPending;
@ -168,8 +181,7 @@ namespace BizHawk.Emulation.Consoles.Sega
if (VDP.VInterruptEnabled) if (VDP.VInterruptEnabled)
Set68kIrq(6); Set68kIrq(6);
if (Z80Runnable) SoundCPU.Interrupt = true;
SoundCPU.Interrupt = true;
//The INT output is asserted every frame for exactly one scanline, and it can't be disabled. A very short Z80 interrupt routine would be triggered multiple times if it finishes within 228 Z80 clock cycles. I think (but cannot recall the specifics) that some games have delay loops in the interrupt handler for this very reason. //The INT output is asserted every frame for exactly one scanline, and it can't be disabled. A very short Z80 interrupt routine would be triggered multiple times if it finishes within 228 Z80 clock cycles. I think (but cannot recall the specifics) that some games have delay loops in the interrupt handler for this very reason.
} }
} }
@ -196,6 +208,17 @@ namespace BizHawk.Emulation.Consoles.Sega
#endif #endif
} }
void RunZ80(int cycles)
{
// I emulate the YM2612 synced to Z80 clock, for better or worse.
// So we still need to keep the Z80 cycle count accurate even if the Z80 isn't running.
if (Z80Runnable)
SoundCPU.ExecuteCycles(cycles);
else
SoundCPU.TotalExecutedCycles += cycles;
}
void Set68kIrq(int irq) void Set68kIrq(int irq)
{ {
#if MUSASHI #if MUSASHI