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:
761void V9990::setVerticalTiming()
766 case B1:
case B3:
case B7:
771 case B0:
case B2:
case B4:
786 if (!(regs[SCREEN_MODE_0] & 0x80)) {
789 switch (regs[SCREEN_MODE_0] & 0x03) {
790 case 0x00:
return BP2;
791 case 0x01:
return BP4;
793 switch (pal_ctrl & 0xC0) {
794 case 0x00:
return BP6;
795 case 0x40:
return BD8;
796 case 0x80:
return BYJK;
797 case 0xC0:
return BYUV;
800 case 0x03:
return BD16;
814 switch (regs[SCREEN_MODE_0] & 0xC0) {
821 switch(regs[SCREEN_MODE_0] & 0x30) {
822 case 0x00:
return B0;
823 case 0x10:
return B2;
824 case 0x20:
return B4;
827 switch(regs[SCREEN_MODE_0] & 0x30) {
828 case 0x00:
return B1;
829 case 0x10:
return B3;
830 case 0x20:
return B7;
842 setHorizontalTiming();
845void 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);
879static constexpr std::initializer_list<enum_string<V9990DisplayMode>> displayModeInfo = {
893template<
typename Archive>
896 ar.template serializeBase<MSXDevice>(*
this);
898 if (ar.versionAtLeast(version, 4)) {
899 ar.serialize(
"syncVSync", syncVSync,
900 "syncDisplayStart", syncDisplayStart,
901 "syncVScan", syncVScan,
902 "syncHScan", syncHScan,
903 "syncSetMode", syncSetMode);
906 {&syncVSync, &syncDisplayStart, &syncVScan,
907 &syncHScan, &syncSetMode});
909 if (ar.versionAtLeast(version, 5)) {
910 ar.serialize(
"syncCmdEnd", syncCmdEnd);
913 ar.serialize(
"displayMode", mode);
914 ar.serialize(
"vram", vram,
915 "cmdEngine", cmdEngine,
917 "frameStartTime", frameStartTime,
918 "hScanSyncTime", hScanSyncTime);
919 ar.serialize_blob(
"palette", palette);
920 ar.serialize(
"status", status,
921 "pendingIRQs", pendingIRQs);
922 ar.serialize_blob(
"registers", regs);
923 ar.serialize(
"regSelect", regSelect,
924 "palTiming", palTiming,
925 "interlaced", interlaced,
926 "isDisplayArea", isDisplayArea,
927 "displayEnabled", displayEnabled,
928 "scrollAYHigh", scrollAYHigh,
929 "scrollBYHigh", scrollBYHigh);
931 if (ar.versionBelow(version, 2)) {
934 ar.serialize(
"systemReset", systemReset);
937 if (ar.versionBelow(version, 3)) {
938 vramReadPtr = getVRAMAddr(VRAM_READ_ADDRESS_0);
939 vramWritePtr = getVRAMAddr(VRAM_WRITE_ADDRESS_0);
942 ar.serialize(
"vramReadPtr", vramReadPtr,
943 "vramWritePtr", vramWritePtr,
944 "vramReadBuffer", vramReadBuffer);
954 if constexpr (Archive::IS_LOADER) {
959 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)