58 , syncDisplayStart(*this)
63 , v9990RegDebug(*this)
64 , v9990PalDebug(*this)
65 , irq(getMotherBoard(),
getName() +
".IRQ")
66 , display(getReactor().getDisplay())
67 , frameStartTime(getCurrentTime())
68 , hScanSyncTime(getCurrentTime())
70 , externalVideoSource(false)
73 memset(regs, 0,
sizeof(regs));
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;
86 vram = std::make_unique<V9990VRAM>(*
this, time);
89 cmdEngine = std::make_unique<V9990CmdEngine>(
91 vram->setCmdEngine(*cmdEngine);
99 isDisplayArea =
false;
100 displayEnabled =
false;
101 superimposing =
false;
102 createRenderer(time);
115 return renderer->getPostProcessor();
130 syncVSync .removeSyncPoint();
131 syncDisplayStart.removeSyncPoint();
132 syncVScan .removeSyncPoint();
133 syncHScan .removeSyncPoint();
134 syncSetMode .removeSyncPoint();
135 syncCmdEnd .removeSyncPoint();
138 memset(regs, 0,
sizeof(regs));
147 isDisplayArea =
false;
148 displayEnabled =
false;
149 superimposing =
false;
152 writeIO(INTERRUPT_FLAG, 0xFF, time);
156 cmdEngine->sync(time);
157 renderer->reset(time);
158 cmdEngine->reset(time);
172 return cmdEngine->getCmdData(time);
182 case REGISTER_SELECT:
185 return peekIO(port, time);
189 if (systemReset)
return result;
194 if (!(regs[VRAM_READ_ADDRESS_2] & 0x80)) {
195 vramReadPtr = getVRAMAddr(VRAM_READ_ADDRESS_0) + 1;
196 setVRAMAddr(VRAM_READ_ADDRESS_0, vramReadPtr);
198 vramReadBuffer = vram->readVRAMCPU(vramReadPtr, time);
203 if (!(regs[PALETTE_CONTROL] & 0x10)) {
204 byte& palPtr = regs[PALETTE_POINTER];
205 switch (palPtr & 3) {
206 case 0: palPtr += 1;
break;
207 case 1: palPtr += 1;
break;
208 case 2: palPtr += 2;
break;
209 default: palPtr -= 3;
break;
215 if (!(regSelect & 0x40)) {
218 regSelect = (regSelect + 1) & ~0x40;
227 switch (port & 0x0F) {
233 return vramReadBuffer;
236 return palette[regs[PALETTE_POINTER]];
239 return cmdEngine->peekCmdData(time);
242 return readRegister(regSelect & 0x3F, time);
255 bool hr = (
x < left) || (right <=
x);
256 bool vr = (y < top) || (bottom <= y);
258 return cmdEngine->getStatus(time) |
268 case REGISTER_SELECT:
290 unsigned addr = getVRAMAddr(VRAM_WRITE_ADDRESS_0);
291 vram->writeVRAMCPU(addr, val, time);
292 if (!(regs[VRAM_WRITE_ADDRESS_2] & 0x80)) {
293 setVRAMAddr(VRAM_WRITE_ADDRESS_0, addr + 1);
302 writePaletteRegister(0, 0, time);
305 byte& palPtr = regs[PALETTE_POINTER];
306 writePaletteRegister(palPtr, val, time);
307 switch (palPtr & 3) {
308 case 0: palPtr += 1;
break;
309 case 1: palPtr += 1;
break;
310 case 2: palPtr += 2;
break;
311 default: palPtr -= 3;
break;
319 cmdEngine->setCmdData(val, time);
322 case REGISTER_DATA: {
334 writeRegister(regSelect & 0x3F, val, time);
335 if (!(regSelect & 0x80)) {
336 regSelect = ( regSelect & 0xC0) |
337 ((regSelect + 1) & 0x3F);
341 case REGISTER_SELECT:
361 if (!(pendingIRQs & regs[INTERRUPT_0])) {
367 case SYSTEM_CONTROL: {
370 status = (status & 0xFB) | ((val & 1) << 2);
371 syncAtNextLine(syncSetMode, time);
373 bool newSystemReset = (val & 2) != 0;
374 if (newSystemReset != systemReset) {
375 systemReset = newSystemReset;
380 for (
auto i :
xrange(64)) {
381 writeRegister(i, 0, time);
384 writeIO(INTERRUPT_FLAG, 0xFF, time);
410 void V9990::execVSync(EmuTime::param time)
413 renderer->frameEnd(time);
417 void V9990::execDisplayStart(EmuTime::param time)
419 if (displayEnabled) {
420 renderer->updateDisplayEnabled(
true, time);
422 isDisplayArea =
true;
425 void V9990::execVScan(EmuTime::param time)
428 renderer->updateDisplayEnabled(
false, time);
430 isDisplayArea =
false;
434 void V9990::execHScan()
439 void V9990::execSetMode(EmuTime::param time)
446 void V9990::execCheckCmdEnd(EmuTime::param time)
448 cmdEngine->sync(time);
449 scheduleCmdEnd(time);
452 void V9990::scheduleCmdEnd(EmuTime::param time)
454 if (regs[INTERRUPT_0] & 4) {
455 auto next = cmdEngine->estimateCmdEnd();
457 syncCmdEnd.setSyncPoint(next);
466 void V9990::preVideoSystemChange() noexcept
471 void V9990::postVideoSystemChange() noexcept
474 createRenderer(time);
475 renderer->frameStart(time);
482 V9990::RegDebug::RegDebug(
V9990& v9990_)
483 : SimpleDebuggable(v9990_.getMotherBoard(),
484 v9990_.
getName() +
" regs",
"V9990 registers", 0x40)
488 byte V9990::RegDebug::read(
unsigned address)
491 return v9990.regs[address];
494 void V9990::RegDebug::write(
unsigned address,
byte value, EmuTime::param time)
497 v9990.writeRegister(address, value, time);
504 V9990::PalDebug::PalDebug(
V9990& v9990_)
505 : SimpleDebuggable(v9990_.getMotherBoard(),
507 "V9990 palette (format is R, G, B, 0).", 0x100)
511 byte V9990::PalDebug::read(
unsigned address)
514 return v9990.palette[address];
517 void V9990::PalDebug::write(
unsigned address,
byte value, EmuTime::param time)
520 v9990.writePaletteRegister(address, value, time);
527 inline unsigned V9990::getVRAMAddr(RegisterId base)
const
529 return regs[base + 0] +
530 (regs[base + 1] << 8) +
531 ((regs[base + 2] & 0x07) << 16);
534 inline void V9990::setVRAMAddr(RegisterId base,
unsigned addr)
536 regs[base + 0] = addr & 0xFF;
537 regs[base + 1] = (addr & 0xFF00) >> 8;
538 regs[base + 2] = ((addr & 0x070000) >> 16) | (regs[base + 2] & 0x80);
542 byte V9990::readRegister(
byte reg, EmuTime::param time)
const
545 if (systemReset)
return 255;
549 if (reg < CMD_PARAM_BORDER_X_0) {
552 word borderX = cmdEngine->getBorderX(time);
553 return (reg == CMD_PARAM_BORDER_X_0)
554 ? (borderX & 0xFF) : (borderX >> 8);
561 void V9990::syncAtNextLine(SyncBase& type, EmuTime::param time)
565 EmuTime nextTime = frameStartTime + ticks;
566 type.setSyncPoint(nextTime);
569 void V9990::writeRegister(
byte reg,
byte val, EmuTime::param time)
573 static constexpr
byte regWriteMask[32] = {
574 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
575 0xFF, 0x87, 0xFF, 0x83, 0x0F, 0xFF, 0xFF, 0xFF,
576 0xFF, 0xFF, 0xDF, 0x07, 0xFF, 0xFF, 0xC1, 0x07,
577 0x3F, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
585 if (reg >= CMD_PARAM_SRC_ADDRESS_0) {
586 cmdEngine->setCmdReg(reg, val, time);
587 if (reg == CMD_PARAM_OPCODE) {
588 scheduleCmdEnd(time);
593 val &= regWriteMask[reg];
605 syncAtNextLine(syncSetMode, time);
607 case PALETTE_CONTROL:
610 case BACK_DROP_COLOR:
611 renderer->updateBackgroundColor(val & 63, time);
613 case SCROLL_CONTROL_AY0:
614 renderer->updateScrollAYLow(time);
616 case SCROLL_CONTROL_BY0:
617 renderer->updateScrollBYLow(time);
619 case SCROLL_CONTROL_AX0:
620 case SCROLL_CONTROL_AX1:
621 renderer->updateScrollAX(time);
623 case SCROLL_CONTROL_BX0:
624 case SCROLL_CONTROL_BX1:
625 renderer->updateScrollBX(time);
637 case VRAM_WRITE_ADDRESS_2:
639 vramWritePtr = getVRAMAddr(VRAM_WRITE_ADDRESS_0);
641 case VRAM_READ_ADDRESS_2:
643 vramReadPtr = getVRAMAddr(VRAM_READ_ADDRESS_0);
645 vramReadBuffer = vram->readVRAMCPU(vramReadPtr, time);
651 scheduleCmdEnd(time);
654 irq.
set((pendingIRQs & val) != 0);
655 scheduleCmdEnd(time);
665 void V9990::writePaletteRegister(
byte reg,
byte val, EmuTime::param time)
668 case 0: val &= 0x9F;
break;
669 case 1: val &= 0x1F;
break;
670 case 2: val &= 0x1F;
break;
671 case 3: val = 0x00;
break;
675 byte index = reg / 4;
677 renderer->updatePalette(index, palette[reg + 0] & 0x1F, palette[reg + 1],
678 palette[reg + 2], ys, time);
679 if (index == regs[BACK_DROP_COLOR]) {
680 renderer->updateBackgroundColor(index, time);
686 byte r = palette[4 * index + 0] & 0x1F;
687 byte g = palette[4 * index + 1];
688 byte b = palette[4 * index + 2];
690 return {r,
g, b, ys};
693 void V9990::createRenderer(EmuTime::param time)
697 renderer->reset(time);
700 void V9990::frameStart(EmuTime::param time)
703 displayEnabled = (regs[CONTROL] & 0x80) != 0;
704 palTiming = (regs[SCREEN_MODE_1] & 0x08) != 0;
705 interlaced = (regs[SCREEN_MODE_1] & 0x02) != 0;
706 scrollAYHigh = regs[SCROLL_CONTROL_AY1];
707 scrollBYHigh = regs[SCROLL_CONTROL_BY1];
711 bool newSuperimposing = (regs[CONTROL] & 0x20) && externalVideoSource;
712 if (superimposing != newSuperimposing) {
713 superimposing = newSuperimposing;
714 renderer->updateSuperimposing(superimposing, time);
717 frameStartTime.
reset(time);
720 syncVSync.setSyncPoint(
724 syncDisplayStart.setSyncPoint(
728 syncVScan.setSyncPoint(
731 renderer->frameStart(time);
734 void V9990::raiseIRQ(IRQType irqType)
736 pendingIRQs |= irqType;
737 if (pendingIRQs & regs[INTERRUPT_0]) {
742 void V9990::setHorizontalTiming()
746 case B1:
case B3:
case B7:
749 case B0:
case B2:
case B4:
758 void V9990::setVerticalTiming()
762 case B1:
case B3:
case B7:
767 case B0:
case B2:
case B4:
780 if (!(regs[SCREEN_MODE_0] & 0x80)) {
783 switch (regs[SCREEN_MODE_0] & 0x03) {
784 case 0x00:
return BP2;
785 case 0x01:
return BP4;
787 switch (pal_ctrl & 0xC0) {
788 case 0x00:
return BP6;
789 case 0x40:
return BD8;
790 case 0x80:
return BYJK;
791 case 0xC0:
return BYUV;
794 case 0x03:
return BD16;
806 void V9990::calcDisplayMode()
809 switch (regs[SCREEN_MODE_0] & 0xC0) {
818 switch(regs[SCREEN_MODE_0] & 0x30) {
819 case 0x00: mode =
B0;
break;
820 case 0x10: mode =
B2;
break;
821 case 0x20: mode =
B4;
break;
826 switch(regs[SCREEN_MODE_0] & 0x30) {
827 case 0x00: mode =
B1;
break;
828 case 0x10: mode =
B3;
break;
829 case 0x20: mode =
B7;
break;
842 setHorizontalTiming();
845 void V9990::scheduleHscan(EmuTime::param time)
848 if (hScanSyncTime > time) {
849 syncHScan.removeSyncPoint();
850 hScanSyncTime = time;
853 if (pendingIRQs & HOR_IRQ) {
860 if (regs[INTERRUPT_2] & 0x80) {
864 int line = regs[INTERRUPT_1] + 256 * (regs[INTERRUPT_2] & 3) +
869 int mult = (status & 0x04) ? 3 : 2;
870 offset += (regs[INTERRUPT_3] & 0x0F) * 64 * mult;
871 if (offset <= ticks) {
875 hScanSyncTime = frameStartTime + offset;
876 syncHScan.setSyncPoint(hScanSyncTime);
879 static constexpr std::initializer_list<enum_string<V9990DisplayMode>> displayModeInfo = {
881 {
"P1",
P1 }, {
"P2",
P2 },
882 {
"B0",
B0 }, {
"B1",
B1 }, {
"B2",
B2 }, {
"B3",
B3 },
883 {
"B4",
B4 }, {
"B5",
B5 }, {
"B6",
B6 }, {
"B7",
B7 }
892 template<
typename Archive>
895 ar.template serializeBase<MSXDevice>(*
this);
897 if (ar.versionAtLeast(version, 4)) {
898 ar.serialize(
"syncVSync", syncVSync,
899 "syncDisplayStart", syncDisplayStart,
900 "syncVScan", syncVScan,
901 "syncHScan", syncHScan,
902 "syncSetMode", syncSetMode);
905 {&syncVSync, &syncDisplayStart, &syncVScan,
906 &syncHScan, &syncSetMode});
908 if (ar.versionAtLeast(version, 5)) {
909 ar.serialize(
"syncCmdEnd", syncCmdEnd);
912 ar.serialize(
"displayMode", mode);
913 ar.serialize(
"vram", *vram,
914 "cmdEngine", *cmdEngine,
916 "frameStartTime", frameStartTime,
917 "hScanSyncTime", hScanSyncTime);
918 ar.serialize_blob(
"palette", palette,
sizeof(palette));
919 ar.serialize(
"status", status,
920 "pendingIRQs", pendingIRQs);
921 ar.serialize_blob(
"registers", regs,
sizeof(regs));
922 ar.serialize(
"regSelect", regSelect,
923 "palTiming", palTiming,
924 "interlaced", interlaced,
925 "isDisplayArea", isDisplayArea,
926 "displayEnabled", displayEnabled,
927 "scrollAYHigh", scrollAYHigh,
928 "scrollBYHigh", scrollBYHigh);
930 if (ar.versionBelow(version, 2)) {
933 ar.serialize(
"systemReset", systemReset);
936 if (ar.versionBelow(version, 3)) {
937 vramReadPtr = getVRAMAddr(VRAM_READ_ADDRESS_0);
938 vramWritePtr = getVRAMAddr(VRAM_WRITE_ADDRESS_0);
939 vramReadBuffer = vram->readVRAMCPU(vramReadPtr,
getCurrentTime());
941 ar.serialize(
"vramReadPtr", vramReadPtr,
942 "vramWritePtr", vramWritePtr,
943 "vramReadBuffer", vramReadBuffer);
958 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)
RenderSettings & getRenderSettings()
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)
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.
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.
V9990DisplayMode getDisplayMode() const
Return the current display mode.
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.
string getName(KeyCode keyCode)
Translate key code to key name.
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)