56 , syncDisplayStart(*this)
61 , v9990RegDebug(*this)
62 , v9990PalDebug(*this)
63 , irq(getMotherBoard(),
getName() +
".IRQ")
64 , display(getReactor().getDisplay())
65 , vram(*this, getCurrentTime())
66 , cmdEngine(*this, getCurrentTime(), display.getRenderSettings())
67 , frameStartTime(getCurrentTime())
68 , hScanSyncTime(getCurrentTime())
70 , externalVideoSource(false)
73 memset(regs, 0,
sizeof(regs));
74 setDisplayMode(calcDisplayMode());
77 for (
auto i :
xrange(64)) {
78 palette[4 * i + 0] = 0x9F;
79 palette[4 * i + 1] = 0x1F;
80 palette[4 * i + 2] = 0x1F;
81 palette[4 * i + 3] = 0x00;
92 isDisplayArea =
false;
93 displayEnabled =
false;
94 superimposing =
false;
109 return renderer->getPostProcessor();
124 syncVSync .removeSyncPoint();
125 syncDisplayStart.removeSyncPoint();
126 syncVScan .removeSyncPoint();
127 syncHScan .removeSyncPoint();
128 syncSetMode .removeSyncPoint();
129 syncCmdEnd .removeSyncPoint();
132 memset(regs, 0,
sizeof(regs));
139 setDisplayMode(calcDisplayMode());
141 isDisplayArea =
false;
142 displayEnabled =
false;
143 superimposing =
false;
146 writeIO(INTERRUPT_FLAG, 0xFF, time);
150 cmdEngine.
sync(time);
151 renderer->reset(time);
152 cmdEngine.
reset(time);
176 case REGISTER_SELECT:
179 return peekIO(port, time);
183 if (systemReset)
return result;
188 if (!(regs[VRAM_READ_ADDRESS_2] & 0x80)) {
189 vramReadPtr = getVRAMAddr(VRAM_READ_ADDRESS_0) + 1;
190 setVRAMAddr(VRAM_READ_ADDRESS_0, vramReadPtr);
192 vramReadBuffer = vram.
readVRAMCPU(vramReadPtr, time);
197 if (!(regs[PALETTE_CONTROL] & 0x10)) {
198 byte& palPtr = regs[PALETTE_POINTER];
199 switch (palPtr & 3) {
200 case 0: palPtr += 1;
break;
201 case 1: palPtr += 1;
break;
202 case 2: palPtr += 2;
break;
203 default: palPtr -= 3;
break;
209 if (!(regSelect & 0x40)) {
212 regSelect = (regSelect + 1) & ~0x40;
221 switch (port & 0x0F) {
227 return vramReadBuffer;
230 return palette[regs[PALETTE_POINTER]];
236 return readRegister(regSelect & 0x3F, time);
249 bool hr = (
x < left) || (right <=
x);
250 bool vr = (y < top) || (bottom <= y);
262 case REGISTER_SELECT:
284 unsigned addr = getVRAMAddr(VRAM_WRITE_ADDRESS_0);
286 if (!(regs[VRAM_WRITE_ADDRESS_2] & 0x80)) {
287 setVRAMAddr(VRAM_WRITE_ADDRESS_0, addr + 1);
296 writePaletteRegister(0, 0, time);
299 byte& palPtr = regs[PALETTE_POINTER];
300 writePaletteRegister(palPtr, val, time);
301 switch (palPtr & 3) {
302 case 0: palPtr += 1;
break;
303 case 1: palPtr += 1;
break;
304 case 2: palPtr += 2;
break;
305 default: palPtr -= 3;
break;
316 case REGISTER_DATA: {
328 writeRegister(regSelect & 0x3F, val, time);
329 if (!(regSelect & 0x80)) {
330 regSelect = ( regSelect & 0xC0) |
331 ((regSelect + 1) & 0x3F);
335 case REGISTER_SELECT:
355 if (!(pendingIRQs & regs[INTERRUPT_0])) {
361 case SYSTEM_CONTROL: {
364 status = (status & 0xFB) | ((val & 1) << 2);
365 syncAtNextLine(syncSetMode, time);
367 bool newSystemReset = (val & 2) != 0;
368 if (newSystemReset != systemReset) {
369 systemReset = newSystemReset;
374 for (
auto i :
xrange(64)) {
375 writeRegister(i, 0, time);
378 writeIO(INTERRUPT_FLAG, 0xFF, time);
404 void V9990::execVSync(EmuTime::param time)
407 renderer->frameEnd(time);
411 void V9990::execDisplayStart(EmuTime::param time)
413 if (displayEnabled) {
414 renderer->updateDisplayEnabled(
true, time);
416 isDisplayArea =
true;
419 void V9990::execVScan(EmuTime::param time)
422 renderer->updateDisplayEnabled(
false, time);
424 isDisplayArea =
false;
428 void V9990::execHScan()
433 void V9990::execSetMode(EmuTime::param time)
435 auto newMode = calcDisplayMode();
436 renderer->setDisplayMode(newMode, time);
438 setDisplayMode(newMode);
441 void V9990::execCheckCmdEnd(EmuTime::param time)
443 cmdEngine.
sync(time);
444 scheduleCmdEnd(time);
447 void V9990::scheduleCmdEnd(EmuTime::param time)
449 if (regs[INTERRUPT_0] & 4) {
452 syncCmdEnd.setSyncPoint(next);
461 void V9990::preVideoSystemChange() noexcept
466 void V9990::postVideoSystemChange() noexcept
469 createRenderer(time);
470 renderer->frameStart(time);
477 V9990::RegDebug::RegDebug(
V9990& v9990_)
478 : SimpleDebuggable(v9990_.getMotherBoard(),
479 v9990_.
getName() +
" regs",
"V9990 registers", 0x40)
483 byte V9990::RegDebug::read(
unsigned address)
486 return v9990.regs[address];
489 void V9990::RegDebug::write(
unsigned address,
byte value, EmuTime::param time)
492 v9990.writeRegister(address, value, time);
499 V9990::PalDebug::PalDebug(
V9990& v9990_)
500 : SimpleDebuggable(v9990_.getMotherBoard(),
502 "V9990 palette (format is R, G, B, 0).", 0x100)
506 byte V9990::PalDebug::read(
unsigned address)
509 return v9990.palette[address];
512 void V9990::PalDebug::write(
unsigned address,
byte value, EmuTime::param time)
515 v9990.writePaletteRegister(address, value, time);
522 inline unsigned V9990::getVRAMAddr(RegisterId base)
const
524 return regs[base + 0] +
525 (regs[base + 1] << 8) +
526 ((regs[base + 2] & 0x07) << 16);
529 inline void V9990::setVRAMAddr(RegisterId base,
unsigned addr)
531 regs[base + 0] = addr & 0xFF;
532 regs[base + 1] = (addr & 0xFF00) >> 8;
533 regs[base + 2] = ((addr & 0x070000) >> 16) | (regs[base + 2] & 0x80);
537 byte V9990::readRegister(
byte reg, EmuTime::param time)
const
540 if (systemReset)
return 255;
544 if (reg < CMD_PARAM_BORDER_X_0) {
548 return (reg == CMD_PARAM_BORDER_X_0)
549 ? (borderX & 0xFF) : (borderX >> 8);
556 void V9990::syncAtNextLine(SyncBase& type, EmuTime::param time)
560 EmuTime nextTime = frameStartTime + ticks;
561 type.setSyncPoint(nextTime);
564 void V9990::writeRegister(
byte reg,
byte val, EmuTime::param time)
568 static constexpr
byte regWriteMask[32] = {
569 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
570 0xFF, 0x87, 0xFF, 0x83, 0x0F, 0xFF, 0xFF, 0xFF,
571 0xFF, 0xFF, 0xDF, 0x07, 0xFF, 0xFF, 0xC1, 0x07,
572 0x3F, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
580 if (reg >= CMD_PARAM_SRC_ADDRESS_0) {
582 if (reg == CMD_PARAM_OPCODE) {
583 scheduleCmdEnd(time);
588 val &= regWriteMask[reg];
600 syncAtNextLine(syncSetMode, time);
602 case PALETTE_CONTROL:
605 case BACK_DROP_COLOR:
606 renderer->updateBackgroundColor(val & 63, time);
608 case SCROLL_CONTROL_AY0:
609 renderer->updateScrollAYLow(time);
611 case SCROLL_CONTROL_BY0:
612 renderer->updateScrollBYLow(time);
614 case SCROLL_CONTROL_AX0:
615 case SCROLL_CONTROL_AX1:
616 renderer->updateScrollAX(time);
618 case SCROLL_CONTROL_BX0:
619 case SCROLL_CONTROL_BX1:
620 renderer->updateScrollBX(time);
632 case VRAM_WRITE_ADDRESS_2:
634 vramWritePtr = getVRAMAddr(VRAM_WRITE_ADDRESS_0);
636 case VRAM_READ_ADDRESS_2:
638 vramReadPtr = getVRAMAddr(VRAM_READ_ADDRESS_0);
640 vramReadBuffer = vram.
readVRAMCPU(vramReadPtr, time);
646 scheduleCmdEnd(time);
649 irq.
set((pendingIRQs & val) != 0);
650 scheduleCmdEnd(time);
660 void V9990::writePaletteRegister(
byte reg,
byte val, EmuTime::param time)
663 case 0: val &= 0x9F;
break;
664 case 1: val &= 0x1F;
break;
665 case 2: val &= 0x1F;
break;
666 case 3: val = 0x00;
break;
670 byte index = reg / 4;
672 renderer->updatePalette(index, palette[reg + 0] & 0x1F, palette[reg + 1],
673 palette[reg + 2], ys, time);
674 if (index == regs[BACK_DROP_COLOR]) {
675 renderer->updateBackgroundColor(index, time);
681 byte r = palette[4 * index + 0] & 0x1F;
682 byte g = palette[4 * index + 1];
683 byte b = palette[4 * index + 2];
685 return {r,
g, b, ys};
688 void V9990::createRenderer(EmuTime::param time)
692 renderer->reset(time);
695 void V9990::frameStart(EmuTime::param time)
698 displayEnabled = (regs[CONTROL] & 0x80) != 0;
699 palTiming = (regs[SCREEN_MODE_1] & 0x08) != 0;
700 interlaced = (regs[SCREEN_MODE_1] & 0x02) != 0;
701 scrollAYHigh = regs[SCROLL_CONTROL_AY1];
702 scrollBYHigh = regs[SCROLL_CONTROL_BY1];
706 bool newSuperimposing = (regs[CONTROL] & 0x20) && externalVideoSource;
707 if (superimposing != newSuperimposing) {
708 superimposing = newSuperimposing;
709 renderer->updateSuperimposing(superimposing, time);
712 frameStartTime.
reset(time);
715 syncVSync.setSyncPoint(
719 syncDisplayStart.setSyncPoint(
723 syncVScan.setSyncPoint(
726 renderer->frameStart(time);
729 void V9990::raiseIRQ(IRQType irqType)
731 pendingIRQs |= irqType;
732 if (pendingIRQs & regs[INTERRUPT_0]) {
737 void V9990::setHorizontalTiming()
741 case B1:
case B3:
case B7:
744 case B0:
case B2:
case B4:
753 void V9990::setVerticalTiming()
757 case B1:
case B3:
case B7:
762 case B0:
case B2:
case B4:
775 if (!(regs[SCREEN_MODE_0] & 0x80)) {
778 switch (regs[SCREEN_MODE_0] & 0x03) {
779 case 0x00:
return BP2;
780 case 0x01:
return BP4;
782 switch (pal_ctrl & 0xC0) {
783 case 0x00:
return BP6;
784 case 0x40:
return BD8;
785 case 0x80:
return BYJK;
786 case 0xC0:
return BYUV;
789 case 0x03:
return BD16;
803 switch (regs[SCREEN_MODE_0] & 0xC0) {
810 switch(regs[SCREEN_MODE_0] & 0x30) {
811 case 0x00:
return B0;
812 case 0x10:
return B2;
813 case 0x20:
return B4;
816 switch(regs[SCREEN_MODE_0] & 0x30) {
817 case 0x00:
return B1;
818 case 0x10:
return B3;
819 case 0x20:
return B7;
831 setHorizontalTiming();
834 void V9990::scheduleHscan(EmuTime::param time)
837 if (hScanSyncTime > time) {
838 syncHScan.removeSyncPoint();
839 hScanSyncTime = time;
842 if (pendingIRQs & HOR_IRQ) {
849 if (regs[INTERRUPT_2] & 0x80) {
853 int line = regs[INTERRUPT_1] + 256 * (regs[INTERRUPT_2] & 3) +
858 int mult = (status & 0x04) ? 3 : 2;
859 offset += (regs[INTERRUPT_3] & 0x0F) * 64 * mult;
860 if (offset <= ticks) {
864 hScanSyncTime = frameStartTime + offset;
865 syncHScan.setSyncPoint(hScanSyncTime);
868 static constexpr std::initializer_list<enum_string<V9990DisplayMode>> displayModeInfo = {
870 {
"P1",
P1 }, {
"P2",
P2 },
871 {
"B0",
B0 }, {
"B1",
B1 }, {
"B2",
B2 }, {
"B3",
B3 },
872 {
"B4",
B4 }, {
"B5",
B5 }, {
"B6",
B6 }, {
"B7",
B7 }
881 template<
typename Archive>
884 ar.template serializeBase<MSXDevice>(*
this);
886 if (ar.versionAtLeast(version, 4)) {
887 ar.serialize(
"syncVSync", syncVSync,
888 "syncDisplayStart", syncDisplayStart,
889 "syncVScan", syncVScan,
890 "syncHScan", syncHScan,
891 "syncSetMode", syncSetMode);
894 {&syncVSync, &syncDisplayStart, &syncVScan,
895 &syncHScan, &syncSetMode});
897 if (ar.versionAtLeast(version, 5)) {
898 ar.serialize(
"syncCmdEnd", syncCmdEnd);
901 ar.serialize(
"displayMode", mode);
902 ar.serialize(
"vram", vram,
903 "cmdEngine", cmdEngine,
905 "frameStartTime", frameStartTime,
906 "hScanSyncTime", hScanSyncTime);
907 ar.serialize_blob(
"palette", palette,
sizeof(palette));
908 ar.serialize(
"status", status,
909 "pendingIRQs", pendingIRQs);
910 ar.serialize_blob(
"registers", regs,
sizeof(regs));
911 ar.serialize(
"regSelect", regSelect,
912 "palTiming", palTiming,
913 "interlaced", interlaced,
914 "isDisplayArea", isDisplayArea,
915 "displayEnabled", displayEnabled,
916 "scrollAYHigh", scrollAYHigh,
917 "scrollBYHigh", scrollBYHigh);
919 if (ar.versionBelow(version, 2)) {
922 ar.serialize(
"systemReset", systemReset);
925 if (ar.versionBelow(version, 3)) {
926 vramReadPtr = getVRAMAddr(VRAM_READ_ADDRESS_0);
927 vramWritePtr = getVRAMAddr(VRAM_WRITE_ADDRESS_0);
930 ar.serialize(
"vramReadPtr", vramReadPtr,
931 "vramWritePtr", vramWritePtr,
932 "vramReadBuffer", vramReadBuffer);
942 if constexpr (Archive::IS_LOADER) {
947 setHorizontalTiming();
constexpr void reset(EmuTime::param e)
Reset the clock to start ticking at the given time.
constexpr unsigned getTicksTill_fast(EmuTime::param e) const
Same as above, only faster, Though the time interval may not be too large.
void detach(VideoSystemChangeListener &listener)
void attach(VideoSystemChangeListener &listener)
void set()
Set the interrupt request on the bus.
void reset()
Reset the interrupt request on the bus.
An MSXDevice is an emulated hardware component connected to the bus of the emulated MSX.
EmuTime::param getCurrentTime() const
Abstract base class for post processors.
static void restoreOld(Archive &ar, std::vector< Schedulable * > schedulables)
void sync(EmuTime::param time)
Synchronizes the command engine with the V9990.
byte peekCmdData(EmuTime::param time) const
read the command data byte (without side-effects)
void reset(EmuTime::param time)
Re-initialise the command engine's state.
void setCmdData(byte value, EmuTime::param time)
set the data byte
void setCmdReg(byte reg, byte val, EmuTime::param time)
Set a value to one of the command registers.
byte getStatus(EmuTime::param time) const
Get command engine related status bits.
byte getCmdData(EmuTime::param time)
read the command data byte
word getBorderX(EmuTime::param time) const
EmuTime estimateCmdEnd() const
Calculate an (under-)estimation for when the command will finish.
static constexpr int UC_TICKS_PER_LINE
The number of clockticks per line is independent of the crystal used or the display mode (NTSC/PAL)
static constexpr auto displayNTSC_MCLK
NTSC display timing, when using MCLK: Normal display mode with borders.
static constexpr int getUCTicksPerFrame(bool palTiming)
Get the number of UC ticks in 1 frame.
static constexpr auto displayPAL_XTAL
PAL display timing, when using XTAL: Overscan mode without borders.
static constexpr auto lineXTAL
Horizontal (line) timing when using XTAL: 'Overscan' modes without border.
static constexpr auto displayNTSC_XTAL
NTSC display timing, when using XTAL: Overscan mode without borders.
static constexpr auto lineMCLK
Horizontal (line) timing when using MCLK: 'Normal' display modes.
static constexpr auto displayPAL_MCLK
PAL display timing, when using MCLK: Normal display mode with borders.
void writeVRAMCPU(unsigned address, byte val, EmuTime::param time)
byte readVRAMCPU(unsigned address, EmuTime::param time)
void setCmdEngine(V9990CmdEngine &cmdEngine_)
Implementation of the Yamaha V9990 VDP as used in the GFX9000 cartridge by Sunrise.
void reset(EmuTime::param time) override
This method is called on reset.
void powerUp(EmuTime::param time) override
This method is called when MSX is powered up.
void serialize(Archive &ar, unsigned version)
bool isPalTiming() const
Is PAL timing active? This setting is fixed at start of frame.
bool isSuperimposing() const
Should this frame be superimposed? This is a combination of bit 5 (YSE) in R#8 and the presence of an...
int getUCTicksThisFrame(EmuTime::param time) const
Get the number of elapsed UC ticks in this frame.
GetPaletteResult getPalette(int index) const
int getBottomBorder() const
int getLeftBorder() const
Get the number of VDP clockticks between the start of the line and the end of the left border.
void writeIO(word port, byte value, EmuTime::param time) override
Write a byte to a given IO port at a certain time to this device.
PostProcessor * getPostProcessor() const
Used by Video9000 to be able to couple the VDP and V9990 output.
bool isDisplayEnabled() const
Is the display enabled? Note this is simpler than the V99x8 version.
byte peekIO(word port, EmuTime::param time) const override
Read a byte from a given IO port.
V9990ColorMode getColorMode() const
Return the current color mode.
V9990(const DeviceConfig &config)
byte readIO(word port, EmuTime::param time) override
Read a byte from an IO port at a certain time from this device.
int getRightBorder() const
Get the number of VDP clockticks between the start of the line and the end of the right border.
std::string getName(KeyCode keyCode)
Translate key code to key name.
std::unique_ptr< V9990Renderer > createV9990Renderer(V9990 &vdp, Display &display)
Create the V9990 Renderer selected by the current renderer setting.
This file implemented 3 utility functions:
SERIALIZE_ENUM(CassettePlayer::State, stateInfo)
REGISTER_MSXDEVICE(ChakkariCopy, "ChakkariCopy")
constexpr byte ALLOW_READ
constexpr byte ALLOW_WRITE
uint16_t word
16 bit unsigned integer
constexpr KeyMatrixPosition x
Keyboard bindings.
constexpr byte regAccess[64]
uint32_t next(octet_iterator &it, octet_iterator end)
#define OUTER(type, member)
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
constexpr auto xrange(T e)