16static constexpr byte ALLOW_READ = 1;
17static constexpr byte ALLOW_WRITE = 2;
18static constexpr byte NO_ACCESS = 0;
19static constexpr byte RD_ONLY = ALLOW_READ;
20static constexpr byte WR_ONLY = ALLOW_WRITE;
21static constexpr byte RD_WR = ALLOW_READ | ALLOW_WRITE;
22static constexpr std::array<byte, 64> regAccess = {
23 WR_ONLY, WR_ONLY, WR_ONLY,
24 WR_ONLY, WR_ONLY, WR_ONLY,
27 RD_WR, RD_WR, RD_WR, RD_WR,
32 RD_WR, RD_WR, RD_WR, RD_WR,
33 RD_WR, RD_WR, RD_WR, RD_WR,
38 NO_ACCESS, NO_ACCESS, NO_ACCESS,
39 WR_ONLY, WR_ONLY, WR_ONLY, WR_ONLY,
40 WR_ONLY, WR_ONLY, WR_ONLY, WR_ONLY,
41 WR_ONLY, WR_ONLY, WR_ONLY, WR_ONLY,
42 WR_ONLY, WR_ONLY, WR_ONLY, WR_ONLY,
43 WR_ONLY, WR_ONLY, WR_ONLY, WR_ONLY,
44 WR_ONLY, RD_ONLY, RD_ONLY,
45 NO_ACCESS, NO_ACCESS, NO_ACCESS,
46 NO_ACCESS, NO_ACCESS, NO_ACCESS,
47 NO_ACCESS, NO_ACCESS, NO_ACCESS
57 , syncDisplayStart(*this)
62 , v9990RegDebug(*this)
63 , v9990PalDebug(*this)
64 , irq(getMotherBoard(), getName() +
".IRQ")
65 , display(getReactor().getDisplay())
66 , invalidRegisterReadCallback(getCommandController(),
67 tmpStrCat(getName(),
"_invalid_register_read_callback"),
68 "Tcl proc to call when a write-only register was read from. "
69 "Input: register number (0-63)",
71 , invalidRegisterWriteCallback(getCommandController(),
72 tmpStrCat(getName(),
"_invalid_register_write_callback"),
73 "Tcl proc to call when a read-only register was written to. "
74 "Input: register number (0-63), 8-bit data",
76 , vram(*
this, getCurrentTime())
77 , cmdEngine(*
this, getCurrentTime(), display.getRenderSettings())
78 , frameStartTime(getCurrentTime())
79 , hScanSyncTime(getCurrentTime())
82 setDisplayMode(calcDisplayMode());
85 for (
auto i :
xrange(64)) {
86 palette[4 * i + 0] = 0x9F;
87 palette[4 * i + 1] = 0x1F;
88 palette[4 * i + 2] = 0x1F;
89 palette[4 * i + 3] = 0x00;
92 vram.setCmdEngine(cmdEngine);
98 EmuTime::param time = getCurrentTime();
102 display.attach(*
this);
112 return renderer->getPostProcessor();
127 syncVSync .removeSyncPoint();
128 syncDisplayStart.removeSyncPoint();
129 syncVScan .removeSyncPoint();
130 syncHScan .removeSyncPoint();
131 syncSetMode .removeSyncPoint();
132 syncCmdEnd .removeSyncPoint();
142 setDisplayMode(calcDisplayMode());
144 isDisplayArea =
false;
145 displayEnabled =
false;
146 superimposing =
false;
149 writeIO(INTERRUPT_FLAG, 0xFF, time);
153 cmdEngine.
sync(time);
154 renderer->reset(time);
155 cmdEngine.
reset(time);
179 case REGISTER_SELECT:
182 return peekIO(port, time);
186 if (systemReset)
return result;
191 if (!(regs[VRAM_READ_ADDRESS_2] & 0x80)) {
192 vramReadPtr = getVRAMAddr(VRAM_READ_ADDRESS_0) + 1;
193 setVRAMAddr(VRAM_READ_ADDRESS_0, vramReadPtr);
195 vramReadBuffer = vram.
readVRAMCPU(vramReadPtr, time);
200 if (!(regs[PALETTE_CONTROL] & 0x10)) {
201 byte& palPtr = regs[PALETTE_POINTER];
202 switch (palPtr & 3) {
203 case 0: palPtr += 1;
break;
204 case 1: palPtr += 1;
break;
205 case 2: palPtr += 2;
break;
206 default: palPtr -= 3;
break;
212 if (!(regSelect & 0x40)) {
215 regSelect = (regSelect + 1) & ~0x40;
224 switch (port & 0x0F) {
230 return vramReadBuffer;
233 return palette[regs[PALETTE_POINTER]];
239 return readRegister(regSelect & 0x3F, time);
252 bool hr = (x < left) || (right <= x);
253 bool vr = (y < top) || (bottom <= y);
265 case REGISTER_SELECT:
287 unsigned addr = getVRAMAddr(VRAM_WRITE_ADDRESS_0);
289 if (!(regs[VRAM_WRITE_ADDRESS_2] & 0x80)) {
290 setVRAMAddr(VRAM_WRITE_ADDRESS_0, addr + 1);
299 writePaletteRegister(0, 0, time);
302 byte& palPtr = regs[PALETTE_POINTER];
303 writePaletteRegister(palPtr, val, time);
304 switch (palPtr & 3) {
305 case 0: palPtr += 1;
break;
306 case 1: palPtr += 1;
break;
307 case 2: palPtr += 2;
break;
308 default: palPtr -= 3;
break;
319 case REGISTER_DATA: {
331 writeRegister(regSelect & 0x3F, val, time);
332 if (!(regSelect & 0x80)) {
333 regSelect = ( regSelect & 0xC0) |
334 ((regSelect + 1) & 0x3F);
338 case REGISTER_SELECT:
358 if (!(pendingIRQs & regs[INTERRUPT_0])) {
364 case SYSTEM_CONTROL: {
367 status =
byte((status & 0xFB) | ((val & 1) << 2));
368 syncAtNextLine(syncSetMode, time);
370 if (
bool newSystemReset = (val & 2) != 0;
371 newSystemReset != systemReset) {
372 systemReset = newSystemReset;
377 for (
auto i :
xrange(
byte(64))) {
378 writeRegister(i, 0, time);
381 writeIO(INTERRUPT_FLAG, 0xFF, time);
407void V9990::execVSync(EmuTime::param time)
410 renderer->frameEnd(time);
414void V9990::execDisplayStart(EmuTime::param time)
416 if (displayEnabled) {
417 renderer->updateDisplayEnabled(
true, time);
419 isDisplayArea =
true;
422void V9990::execVScan(EmuTime::param time)
425 renderer->updateDisplayEnabled(
false, time);
427 isDisplayArea =
false;
431void V9990::execHScan()
436void V9990::execSetMode(EmuTime::param time)
438 auto newMode = calcDisplayMode();
439 renderer->setDisplayMode(newMode, time);
441 setDisplayMode(newMode);
444void V9990::execCheckCmdEnd(EmuTime::param time)
446 cmdEngine.
sync(time);
447 scheduleCmdEnd(time);
450void V9990::scheduleCmdEnd(EmuTime::param time)
452 if (regs[INTERRUPT_0] & 4) {
455 syncCmdEnd.setSyncPoint(next);
464void V9990::preVideoSystemChange() noexcept
469void V9990::postVideoSystemChange() noexcept
472 createRenderer(time);
473 renderer->frameStart(time);
480V9990::RegDebug::RegDebug(
const V9990& v9990_)
481 : SimpleDebuggable(v9990_.getMotherBoard(),
482 v9990_.getName() +
" regs",
"V9990 registers", 0x40)
486byte V9990::RegDebug::read(
unsigned address)
488 auto& v9990 =
OUTER(V9990, v9990RegDebug);
489 return v9990.regs[address];
492void V9990::RegDebug::write(
unsigned address,
byte value, EmuTime::param time)
494 auto& v9990 =
OUTER(V9990, v9990RegDebug);
495 v9990.writeRegister(narrow<byte>(address), value, time);
502V9990::PalDebug::PalDebug(
const V9990& v9990_)
503 : SimpleDebuggable(v9990_.getMotherBoard(),
504 v9990_.getName() +
" palette",
505 "V9990 palette (format is R, G, B, 0).", 0x100)
509byte V9990::PalDebug::read(
unsigned address)
511 auto& v9990 =
OUTER(V9990, v9990PalDebug);
512 return v9990.palette[address];
515void V9990::PalDebug::write(
unsigned address,
byte value, EmuTime::param time)
517 auto& v9990 =
OUTER(V9990, v9990PalDebug);
518 v9990.writePaletteRegister(narrow<byte>(address), value, time);
525inline unsigned V9990::getVRAMAddr(RegisterId base)
const
527 return regs[base + 0] +
528 (regs[base + 1] << 8) +
529 ((regs[base + 2] & 0x07) << 16);
532inline void V9990::setVRAMAddr(RegisterId base,
unsigned addr)
534 regs[base + 0] = addr & 0xFF;
535 regs[base + 1] = (addr & 0xFF00) >> 8;
536 regs[base + 2] = ((addr & 0x070000) >> 16) | (regs[base + 2] & 0x80);
540byte V9990::readRegister(
byte reg, EmuTime::param time)
const
543 if (systemReset)
return 255;
546 if (regAccess[reg] & ALLOW_READ) {
547 if (reg < CMD_PARAM_BORDER_X_0) {
551 return (reg == CMD_PARAM_BORDER_X_0)
552 ? narrow_cast<byte>(borderX & 0xFF)
556 invalidRegisterReadCallback.
execute(reg);
561void V9990::syncAtNextLine(SyncBase& type, EmuTime::param time)
const
565 EmuTime nextTime = frameStartTime + ticks;
566 type.setSyncPoint(nextTime);
569void V9990::writeRegister(
byte reg,
byte val, EmuTime::param time)
573 static constexpr std::array<byte, 32> regWriteMask = {
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
581 if (!(regAccess[reg] & ALLOW_WRITE)) {
583 invalidRegisterWriteCallback.
execute(reg, val);
586 if (reg >= CMD_PARAM_SRC_ADDRESS_0) {
588 if (reg == CMD_PARAM_OPCODE) {
589 scheduleCmdEnd(time);
594 val &= regWriteMask[reg];
606 syncAtNextLine(syncSetMode, time);
608 case PALETTE_CONTROL:
611 case BACK_DROP_COLOR:
612 renderer->updateBackgroundColor(val & 63, time);
614 case SCROLL_CONTROL_AY0:
615 renderer->updateScrollAYLow(time);
617 case SCROLL_CONTROL_BY0:
618 renderer->updateScrollBYLow(time);
620 case SCROLL_CONTROL_AX0:
621 case SCROLL_CONTROL_AX1:
622 renderer->updateScrollAX(time);
624 case SCROLL_CONTROL_BX0:
625 case SCROLL_CONTROL_BX1:
626 renderer->updateScrollBX(time);
638 case VRAM_WRITE_ADDRESS_2:
640 vramWritePtr = getVRAMAddr(VRAM_WRITE_ADDRESS_0);
642 case VRAM_READ_ADDRESS_2:
644 vramReadPtr = getVRAMAddr(VRAM_READ_ADDRESS_0);
646 vramReadBuffer = vram.
readVRAMCPU(vramReadPtr, time);
652 scheduleCmdEnd(time);
655 irq.
set((pendingIRQs & val) != 0);
656 scheduleCmdEnd(time);
666void V9990::writePaletteRegister(
byte reg,
byte val, EmuTime::param time)
669 case 0: val &= 0x9F;
break;
670 case 1: val &= 0x1F;
break;
671 case 2: val &= 0x1F;
break;
672 case 3: val = 0x00;
break;
676 byte index = reg / 4;
678 renderer->updatePalette(index, palette[reg + 0] & 0x1F, palette[reg + 1],
679 palette[reg + 2], ys, time);
680 if (index == regs[BACK_DROP_COLOR]) {
681 renderer->updateBackgroundColor(index, time);
687 byte r = palette[4 * index + 0] & 0x1F;
688 byte g = palette[4 * index + 1];
689 byte b = palette[4 * index + 2];
691 return {r,
g, b, ys};
694void V9990::createRenderer(EmuTime::param time)
698 renderer->reset(time);
701void V9990::frameStart(EmuTime::param time)
704 displayEnabled = (regs[CONTROL] & 0x80) != 0;
705 palTiming = (regs[SCREEN_MODE_1] & 0x08) != 0;
706 interlaced = (regs[SCREEN_MODE_1] & 0x02) != 0;
707 scrollAYHigh = regs[SCROLL_CONTROL_AY1];
708 scrollBYHigh = regs[SCROLL_CONTROL_BY1];
712 if (
bool newSuperimposing = (regs[CONTROL] & 0x20) && externalVideoSource;
713 superimposing != newSuperimposing) {
714 superimposing = newSuperimposing;
715 renderer->updateSuperimposing(superimposing, time);
718 frameStartTime.
reset(time);
721 syncVSync.setSyncPoint(
725 syncDisplayStart.setSyncPoint(
729 syncVScan.setSyncPoint(
732 renderer->frameStart(time);
735void V9990::raiseIRQ(IRQType irqType)
737 pendingIRQs |= irqType;
738 if (pendingIRQs & regs[INTERRUPT_0]) {
743void V9990::setHorizontalTiming()
748 case B1:
case B3:
case B7:
751 case B0:
case B2:
case B4:
760void V9990::setVerticalTiming()
765 case B1:
case B3:
case B7:
770 case B0:
case B2:
case B4:
784 if (!(regs[SCREEN_MODE_0] & 0x80)) {
787 switch (regs[SCREEN_MODE_0] & 0x03) {
788 case 0x00:
return BP2;
789 case 0x01:
return BP4;
791 switch (pal_ctrl & 0xC0) {
792 case 0x00:
return BP6;
793 case 0x40:
return BD8;
794 case 0x80:
return BYJK;
795 case 0xC0:
return BYUV;
798 case 0x03:
return BD16;
812 switch (regs[SCREEN_MODE_0] & 0xC0) {
819 switch(regs[SCREEN_MODE_0] & 0x30) {
820 case 0x00:
return B0;
821 case 0x10:
return B2;
822 case 0x20:
return B4;
825 switch(regs[SCREEN_MODE_0] & 0x30) {
826 case 0x00:
return B1;
827 case 0x10:
return B3;
828 case 0x20:
return B7;
840 setHorizontalTiming();
843void V9990::scheduleHscan(EmuTime::param time)
846 if (hScanSyncTime > time) {
847 syncHScan.removeSyncPoint();
848 hScanSyncTime = time;
851 if (pendingIRQs & HOR_IRQ) {
858 if (regs[INTERRUPT_2] & 0x80) {
862 int line = regs[INTERRUPT_1] + 256 * (regs[INTERRUPT_2] & 3) +
867 int mult = (status & 0x04) ? 3 : 2;
868 offset += (regs[INTERRUPT_3] & 0x0F) * 64 * mult;
869 if (offset <= ticks) {
873 hScanSyncTime = frameStartTime + offset;
874 syncHScan.setSyncPoint(hScanSyncTime);
877static constexpr std::initializer_list<enum_string<V9990DisplayMode>> displayModeInfo = {
891template<
typename Archive>
894 ar.template serializeBase<MSXDevice>(*
this);
896 if (ar.versionAtLeast(version, 4)) {
897 ar.serialize(
"syncVSync", syncVSync,
898 "syncDisplayStart", syncDisplayStart,
899 "syncVScan", syncVScan,
900 "syncHScan", syncHScan,
901 "syncSetMode", syncSetMode);
904 {&syncVSync, &syncDisplayStart, &syncVScan,
905 &syncHScan, &syncSetMode});
907 if (ar.versionAtLeast(version, 5)) {
908 ar.serialize(
"syncCmdEnd", syncCmdEnd);
911 ar.serialize(
"displayMode", mode);
912 ar.serialize(
"vram", vram,
913 "cmdEngine", cmdEngine,
915 "frameStartTime", frameStartTime,
916 "hScanSyncTime", hScanSyncTime);
917 ar.serialize_blob(
"palette", palette);
918 ar.serialize(
"status", status,
919 "pendingIRQs", pendingIRQs);
920 ar.serialize_blob(
"registers", regs);
921 ar.serialize(
"regSelect", regSelect,
922 "palTiming", palTiming,
923 "interlaced", interlaced,
924 "isDisplayArea", isDisplayArea,
925 "displayEnabled", displayEnabled,
926 "scrollAYHigh", scrollAYHigh,
927 "scrollBYHigh", scrollBYHigh);
929 if (ar.versionBelow(version, 2)) {
932 ar.serialize(
"systemReset", systemReset);
935 if (ar.versionBelow(version, 3)) {
936 vramReadPtr = getVRAMAddr(VRAM_READ_ADDRESS_0);
937 vramWritePtr = getVRAMAddr(VRAM_WRITE_ADDRESS_0);
940 ar.serialize(
"vramReadPtr", vramReadPtr,
941 "vramWritePtr", vramWritePtr,
942 "vramReadBuffer", vramReadBuffer);
952 if constexpr (Archive::IS_LOADER) {
957 setHorizontalTiming();
#define REGISTER_MSXDEVICE(CLASS, NAME)
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 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
A post processor builds the frame that is displayed from the MSX frame, while applying effects such a...
static void restoreOld(Archive &ar, std::vector< Schedulable * > schedulables)
TclObject execute() const
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 clock ticks 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)
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 clock-ticks 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 clock-ticks between the start of the line and the end of the right border.
std::unique_ptr< V9990Renderer > createV9990Renderer(V9990 &vdp, Display &display)
Create the V9990 Renderer selected by the current renderer setting.
std::unique_ptr< Renderer > createRenderer(VDP &vdp, Display &display)
Create the Renderer selected by the current renderer setting.
This file implemented 3 utility functions:
uint8_t byte
8 bit unsigned integer
uint16_t word
16 bit unsigned integer
constexpr void fill(ForwardRange &&range, const T &value)
uint32_t next(octet_iterator &it, octet_iterator end)
constexpr To narrow_cast(From &&from) noexcept
#define OUTER(type, member)
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
#define SERIALIZE_ENUM(TYPE, INFO)
TemporaryString tmpStrCat(Ts &&... ts)
constexpr auto xrange(T e)