openMSX
V9990.cc
Go to the documentation of this file.
1#include "V9990.hh"
2#include "Display.hh"
3#include "RendererFactory.hh"
4#include "V9990Renderer.hh"
5#include "Reactor.hh"
6#include "narrow.hh"
7#include "serialize.hh"
8#include "unreachable.hh"
9#include "xrange.hh"
10#include <array>
11#include <cassert>
12#include <memory>
13
14namespace openmsx {
15
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, // VRAM Write Address
24 WR_ONLY, WR_ONLY, WR_ONLY, // VRAM Read Address
25 RD_WR, RD_WR, // Screen Mode
26 RD_WR, // Control
27 RD_WR, RD_WR, RD_WR, RD_WR, // Interrupt
28 WR_ONLY, // Palette Control
29 WR_ONLY, // Palette Pointer
30 RD_WR, // Back Drop Color
31 RD_WR, // Display Adjust
32 RD_WR, RD_WR, RD_WR, RD_WR, // Scroll Control A
33 RD_WR, RD_WR, RD_WR, RD_WR, // Scroll Control B
34 RD_WR, // Sprite Pattern Table Address
35 RD_WR, // LCD Control
36 RD_WR, // Priority Control
37 WR_ONLY, // Sprite Palette Control
38 NO_ACCESS, NO_ACCESS, NO_ACCESS, // 3x not used
39 WR_ONLY, WR_ONLY, WR_ONLY, WR_ONLY, // Cmd Parameter Src XY
40 WR_ONLY, WR_ONLY, WR_ONLY, WR_ONLY, // Cmd Parameter Dest XY
41 WR_ONLY, WR_ONLY, WR_ONLY, WR_ONLY, // Cmd Parameter Size XY
42 WR_ONLY, WR_ONLY, WR_ONLY, WR_ONLY, // Cmd Parameter Arg, LogOp, WrtMask
43 WR_ONLY, WR_ONLY, WR_ONLY, WR_ONLY, // Cmd Parameter Font Color
44 WR_ONLY, RD_ONLY, RD_ONLY, // Cmd Parameter OpCode, Border X
45 NO_ACCESS, NO_ACCESS, NO_ACCESS, // registers 55-63
46 NO_ACCESS, NO_ACCESS, NO_ACCESS,
47 NO_ACCESS, NO_ACCESS, NO_ACCESS
48};
49
50// -------------------------------------------------------------------------
51// Constructor & Destructor
52// -------------------------------------------------------------------------
53
55 : MSXDevice(config)
56 , syncVSync(*this)
57 , syncDisplayStart(*this)
58 , syncVScan(*this)
59 , syncHScan(*this)
60 , syncSetMode(*this)
61 , syncCmdEnd(*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)",
70 {}, Setting::SAVE)
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",
75 {}, Setting::SAVE)
76 , vram(*this, getCurrentTime())
77 , cmdEngine(*this, getCurrentTime(), display.getRenderSettings())
78 , frameStartTime(getCurrentTime())
79 , hScanSyncTime(getCurrentTime())
80{
81 // clear regs TODO find realistic init values
82 setDisplayMode(calcDisplayMode());
83
84 // initialize palette
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;
90 }
91
92 vram.setCmdEngine(cmdEngine);
93
94 // Start with NTSC timing
95 setVerticalTiming();
96
97 // Initialise rendering system
98 EmuTime::param time = getCurrentTime();
99 createRenderer(time);
100
101 powerUp(time);
102 display.attach(*this);
103}
104
106{
107 display.detach(*this);
108}
109
111{
112 return renderer->getPostProcessor();
113}
114
115// -------------------------------------------------------------------------
116// MSXDevice
117// -------------------------------------------------------------------------
118
119void V9990::powerUp(EmuTime::param time)
120{
121 vram.clear();
122 reset(time);
123}
124
125void V9990::reset(EmuTime::param time)
126{
127 syncVSync .removeSyncPoint();
128 syncDisplayStart.removeSyncPoint();
129 syncVScan .removeSyncPoint();
130 syncHScan .removeSyncPoint();
131 syncSetMode .removeSyncPoint();
132 syncCmdEnd .removeSyncPoint();
133
134 // Clear registers / ports
135 ranges::fill(regs, 0);
136 status = 0;
137 regSelect = 0xFF; // TODO check value for power-on and reset
138 vramWritePtr = 0;
139 vramReadPtr = 0;
140 vramReadBuffer = 0;
141 systemReset = false; // verified on real MSX
142 setDisplayMode(calcDisplayMode());
143
144 isDisplayArea = false;
145 displayEnabled = false;
146 superimposing = false;
147
148 // Reset IRQs
149 writeIO(INTERRUPT_FLAG, 0xFF, time);
150
151 palTiming = false;
152 // Reset sub-systems
153 cmdEngine.sync(time);
154 renderer->reset(time);
155 cmdEngine.reset(time);
156
157 // Init scheduling
158 frameStart(time);
159}
160
161byte V9990::readIO(word port, EmuTime::param time)
162{
163 port &= 0x0F;
164
165 // calculate return value (mostly uses peekIO)
166 byte result = [&] {
167 switch (port) {
168 case COMMAND_DATA:
169 return cmdEngine.getCmdData(time);
170 case VRAM_DATA:
171 case PALETTE_DATA:
172 case REGISTER_DATA:
173 case INTERRUPT_FLAG:
174 case STATUS:
175 case KANJI_ROM_0:
176 case KANJI_ROM_1:
177 case KANJI_ROM_2:
178 case KANJI_ROM_3:
179 case REGISTER_SELECT:
180 case SYSTEM_CONTROL:
181 default:
182 return peekIO(port, time);
183 }
184 }();
185 // TODO verify this, especially REGISTER_DATA
186 if (systemReset) return result; // no side-effects
187
188 // execute side-effects
189 switch (port) {
190 case VRAM_DATA:
191 if (!(regs[VRAM_READ_ADDRESS_2] & 0x80)) {
192 vramReadPtr = getVRAMAddr(VRAM_READ_ADDRESS_0) + 1;
193 setVRAMAddr(VRAM_READ_ADDRESS_0, vramReadPtr);
194 // Update read buffer. TODO: timing?
195 vramReadBuffer = vram.readVRAMCPU(vramReadPtr, time);
196 }
197 break;
198
199 case PALETTE_DATA:
200 if (!(regs[PALETTE_CONTROL] & 0x10)) {
201 byte& palPtr = regs[PALETTE_POINTER];
202 switch (palPtr & 3) {
203 case 0: palPtr += 1; break; // red
204 case 1: palPtr += 1; break; // green
205 case 2: palPtr += 2; break; // blue
206 default: palPtr -= 3; break; // checked on real V9990
207 }
208 }
209 break;
210
211 case REGISTER_DATA:
212 if (!(regSelect & 0x40)) {
213 //regSelect = ( regSelect & 0xC0) |
214 // ((regSelect + 1) & 0x3F);
215 regSelect = (regSelect + 1) & ~0x40;
216 }
217 break;
218 }
219 return result;
220}
221
222byte V9990::peekIO(word port, EmuTime::param time) const
223{
224 switch (port & 0x0F) {
225 case VRAM_DATA: {
226 // TODO in 'systemReset' mode, this seems to hang the MSX
227 // V9990 fetches from read buffer instead of directly from VRAM.
228 // The read buffer is the reason why it is impossible to fill
229 // vram by copying a block from "addr" to "addr+1".
230 return vramReadBuffer;
231 }
232 case PALETTE_DATA:
233 return palette[regs[PALETTE_POINTER]];
234
235 case COMMAND_DATA:
236 return cmdEngine.peekCmdData(time);
237
238 case REGISTER_DATA:
239 return readRegister(regSelect & 0x3F, time);
240
241 case INTERRUPT_FLAG:
242 return pendingIRQs;
243
244 case STATUS: {
245 unsigned left = getLeftBorder();
246 unsigned right = getRightBorder();
247 unsigned top = getTopBorder();
248 unsigned bottom = getBottomBorder();
249 unsigned ticks = getUCTicksThisFrame(time);
250 unsigned x = ticks % V9990DisplayTiming::UC_TICKS_PER_LINE;
251 unsigned y = ticks / V9990DisplayTiming::UC_TICKS_PER_LINE;
252 bool hr = (x < left) || (right <= x);
253 bool vr = (y < top) || (bottom <= y);
254
255 return cmdEngine.getStatus(time) |
256 (vr ? 0x40 : 0x00) |
257 (hr ? 0x20 : 0x00) |
258 (status & 0x06);
259 }
260 case KANJI_ROM_1:
261 case KANJI_ROM_3:
262 // not used in Gfx9000
263 return 0xFF; // TODO check
264
265 case REGISTER_SELECT:
266 case SYSTEM_CONTROL:
267 case KANJI_ROM_0:
268 case KANJI_ROM_2:
269 default:
270 // write-only
271 return 0xFF;
272 }
273}
274
275void V9990::writeIO(word port, byte val, EmuTime::param time)
276{
277 port &= 0x0F;
278 switch (port) {
279 case VRAM_DATA: {
280 // write VRAM
281 if (systemReset) {
282 // TODO writes in systemReset mode seem to have
283 // 'some' effect but it's not immediately clear
284 // what the exact behaviour is
285 return;
286 }
287 unsigned addr = getVRAMAddr(VRAM_WRITE_ADDRESS_0);
288 vram.writeVRAMCPU(addr, val, time);
289 if (!(regs[VRAM_WRITE_ADDRESS_2] & 0x80)) {
290 setVRAMAddr(VRAM_WRITE_ADDRESS_0, addr + 1);
291 }
292 break;
293 }
294 case PALETTE_DATA: {
295 if (systemReset) {
296 // Equivalent to writing 0 and keeping palPtr = 0
297 // The above interpretation makes it similar to
298 // writes to REGISTER_DATA, REGISTER_SELECT.
299 writePaletteRegister(0, 0, time);
300 return;
301 }
302 byte& palPtr = regs[PALETTE_POINTER];
303 writePaletteRegister(palPtr, val, time);
304 switch (palPtr & 3) {
305 case 0: palPtr += 1; break; // red
306 case 1: palPtr += 1; break; // green
307 case 2: palPtr += 2; break; // blue
308 default: palPtr -= 3; break; // checked on real V9990
309 }
310 break;
311 }
312 case COMMAND_DATA:
313 // systemReset state doesn't matter:
314 // command below has no effect in systemReset mode
315 //assert(cmdEngine);
316 cmdEngine.setCmdData(val, time);
317 break;
318
319 case REGISTER_DATA: {
320 // write register
321 if (systemReset) {
322 // In systemReset mode, write has no effect,
323 // but 'regSelect' is increased.
324 // I don't know if write is ignored or a write
325 // with val=0 is executed. Though both have the
326 // same effect and the latter is more in line
327 // with writes to PALETTE_DATA and
328 // REGISTER_SELECT.
329 val = 0;
330 }
331 writeRegister(regSelect & 0x3F, val, time);
332 if (!(regSelect & 0x80)) {
333 regSelect = ( regSelect & 0xC0) |
334 ((regSelect + 1) & 0x3F);
335 }
336 break;
337 }
338 case REGISTER_SELECT:
339 if (systemReset) {
340 // Tested on real MSX. Also when no write is done
341 // to this port, regSelect is not RESET when
342 // entering/leaving systemReset mode.
343 // This behavior is similar to PALETTE_DATA and
344 // REGISTER_DATA.
345 val = 0;
346 }
347 regSelect = val;
348 break;
349
350 case STATUS:
351 // read-only, ignore writes
352 break;
353
354 case INTERRUPT_FLAG:
355 // systemReset state doesn't matter:
356 // stuff below has no effect in systemReset mode
357 pendingIRQs &= ~val;
358 if (!(pendingIRQs & regs[INTERRUPT_0])) {
359 irq.reset();
360 }
361 scheduleHscan(time);
362 break;
363
364 case SYSTEM_CONTROL: {
365 // TODO investigate: does switching overscan mode
366 // happen at next line or next frame
367 status = byte((status & 0xFB) | ((val & 1) << 2));
368 syncAtNextLine(syncSetMode, time);
369
370 if (bool newSystemReset = (val & 2) != 0;
371 newSystemReset != systemReset) {
372 systemReset = newSystemReset;
373 if (systemReset) {
374 // Enter systemReset mode
375 // Verified on real MSX: palette data
376 // and VRAM content are NOT reset.
377 for (auto i : xrange(byte(64))) {
378 writeRegister(i, 0, time);
379 }
380 // TODO verify IRQ behaviour
381 writeIO(INTERRUPT_FLAG, 0xFF, time);
382 }
383 }
384 break;
385 }
386 case KANJI_ROM_0:
387 case KANJI_ROM_1:
388 case KANJI_ROM_2:
389 case KANJI_ROM_3:
390 // not used in Gfx9000, ignore
391 break;
392
393 default:
394 // ignore
395 break;
396 }
397}
398
399// =========================================================================
400// Private stuff
401// =========================================================================
402
403// -------------------------------------------------------------------------
404// Schedulable
405// -------------------------------------------------------------------------
406
407void V9990::execVSync(EmuTime::param time)
408{
409 // Transition from one frame to the next
410 renderer->frameEnd(time);
411 frameStart(time);
412}
413
414void V9990::execDisplayStart(EmuTime::param time)
415{
416 if (displayEnabled) {
417 renderer->updateDisplayEnabled(true, time);
418 }
419 isDisplayArea = true;
420}
421
422void V9990::execVScan(EmuTime::param time)
423{
424 if (isDisplayEnabled()) {
425 renderer->updateDisplayEnabled(false, time);
426 }
427 isDisplayArea = false;
428 raiseIRQ(VER_IRQ);
429}
430
431void V9990::execHScan()
432{
433 raiseIRQ(HOR_IRQ);
434}
435
436void V9990::execSetMode(EmuTime::param time)
437{
438 auto newMode = calcDisplayMode();
439 renderer->setDisplayMode(newMode, time);
440 renderer->setColorMode(getColorMode(), time);
441 setDisplayMode(newMode);
442}
443
444void V9990::execCheckCmdEnd(EmuTime::param time)
445{
446 cmdEngine.sync(time);
447 scheduleCmdEnd(time); // in case of underestimation
448}
449
450void V9990::scheduleCmdEnd(EmuTime::param time)
451{
452 if (regs[INTERRUPT_0] & 4) {
453 auto next = cmdEngine.estimateCmdEnd();
454 if (next > time) {
455 syncCmdEnd.setSyncPoint(next);
456 }
457 }
458}
459
460// -------------------------------------------------------------------------
461// VideoSystemChangeListener
462// -------------------------------------------------------------------------
463
464void V9990::preVideoSystemChange() noexcept
465{
466 renderer.reset();
467}
468
469void V9990::postVideoSystemChange() noexcept
470{
471 EmuTime::param time = getCurrentTime();
472 createRenderer(time);
473 renderer->frameStart(time);
474}
475
476// -------------------------------------------------------------------------
477// RegDebug
478// -------------------------------------------------------------------------
479
480V9990::RegDebug::RegDebug(const V9990& v9990_)
481 : SimpleDebuggable(v9990_.getMotherBoard(),
482 v9990_.getName() + " regs", "V9990 registers", 0x40)
483{
484}
485
486byte V9990::RegDebug::read(unsigned address)
487{
488 auto& v9990 = OUTER(V9990, v9990RegDebug);
489 return v9990.regs[address];
490}
491
492void V9990::RegDebug::write(unsigned address, byte value, EmuTime::param time)
493{
494 auto& v9990 = OUTER(V9990, v9990RegDebug);
495 v9990.writeRegister(narrow<byte>(address), value, time);
496}
497
498// -------------------------------------------------------------------------
499// PalDebug
500// -------------------------------------------------------------------------
501
502V9990::PalDebug::PalDebug(const V9990& v9990_)
503 : SimpleDebuggable(v9990_.getMotherBoard(),
504 v9990_.getName() + " palette",
505 "V9990 palette (format is R, G, B, 0).", 0x100)
506{
507}
508
509byte V9990::PalDebug::read(unsigned address)
510{
511 auto& v9990 = OUTER(V9990, v9990PalDebug);
512 return v9990.palette[address];
513}
514
515void V9990::PalDebug::write(unsigned address, byte value, EmuTime::param time)
516{
517 auto& v9990 = OUTER(V9990, v9990PalDebug);
518 v9990.writePaletteRegister(narrow<byte>(address), value, time);
519}
520
521// -------------------------------------------------------------------------
522// Private methods
523// -------------------------------------------------------------------------
524
525inline unsigned V9990::getVRAMAddr(RegisterId base) const
526{
527 return regs[base + 0] +
528 (regs[base + 1] << 8) +
529 ((regs[base + 2] & 0x07) << 16);
530}
531
532inline void V9990::setVRAMAddr(RegisterId base, unsigned addr)
533{
534 regs[base + 0] = addr & 0xFF;
535 regs[base + 1] = (addr & 0xFF00) >> 8;
536 regs[base + 2] = ((addr & 0x070000) >> 16) | (regs[base + 2] & 0x80);
537 // TODO check
538}
539
540byte V9990::readRegister(byte reg, EmuTime::param time) const
541{
542 // TODO sync(time) (if needed at all)
543 if (systemReset) return 255; // verified on real MSX
544
545 assert(reg < 64);
546 if (regAccess[reg] & ALLOW_READ) {
547 if (reg < CMD_PARAM_BORDER_X_0) {
548 return regs[reg];
549 } else {
550 word borderX = cmdEngine.getBorderX(time);
551 return (reg == CMD_PARAM_BORDER_X_0)
552 ? narrow_cast<byte>(borderX & 0xFF)
553 : narrow_cast<byte>(borderX >> 8);
554 }
555 } else {
556 invalidRegisterReadCallback.execute(reg);
557 return 0xFF;
558 }
559}
560
561void V9990::syncAtNextLine(SyncBase& type, EmuTime::param time) const
562{
564 int ticks = (line + 1) * V9990DisplayTiming::UC_TICKS_PER_LINE;
565 EmuTime nextTime = frameStartTime + ticks;
566 type.setSyncPoint(nextTime);
567}
568
569void V9990::writeRegister(byte reg, byte val, EmuTime::param time)
570{
571 // Found this table by writing 0xFF to a register and reading
572 // back the value (only works for read/write registers)
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
578 };
579
580 assert(reg < 64);
581 if (!(regAccess[reg] & ALLOW_WRITE)) {
582 // register not writable
583 invalidRegisterWriteCallback.execute(reg, val);
584 return;
585 }
586 if (reg >= CMD_PARAM_SRC_ADDRESS_0) {
587 cmdEngine.setCmdReg(reg, val, time);
588 if (reg == CMD_PARAM_OPCODE) {
589 scheduleCmdEnd(time);
590 }
591 return;
592 }
593
594 val &= regWriteMask[reg];
595
596 // This optimization is not valid for the vertical scroll registers
597 // TODO is this optimization still useful for other registers?
598 //if (!change) return;
599
600 // Perform additional tasks before new value becomes active
601 // note: no update for SCROLL_CONTROL_AY1, SCROLL_CONTROL_BY1
602 switch (reg) {
603 case SCREEN_MODE_0:
604 case SCREEN_MODE_1:
605 // TODO verify this on real V9990
606 syncAtNextLine(syncSetMode, time);
607 break;
608 case PALETTE_CONTROL:
609 renderer->setColorMode(getColorMode(val), time);
610 break;
611 case BACK_DROP_COLOR:
612 renderer->updateBackgroundColor(val & 63, time);
613 break;
614 case SCROLL_CONTROL_AY0:
615 renderer->updateScrollAYLow(time);
616 break;
617 case SCROLL_CONTROL_BY0:
618 renderer->updateScrollBYLow(time);
619 break;
620 case SCROLL_CONTROL_AX0:
621 case SCROLL_CONTROL_AX1:
622 renderer->updateScrollAX(time);
623 break;
624 case SCROLL_CONTROL_BX0:
625 case SCROLL_CONTROL_BX1:
626 renderer->updateScrollBX(time);
627 break;
628 case DISPLAY_ADJUST:
629 // TODO verify on real V9990: when exactly does a
630 // change in horizontal/vertical adjust take place
631 break;
632 }
633 // commit the change
634 regs[reg] = val;
635
636 // Perform additional tasks after new value became active
637 switch (reg) {
638 case VRAM_WRITE_ADDRESS_2:
639 // write pointer is only updated on R#2 write
640 vramWritePtr = getVRAMAddr(VRAM_WRITE_ADDRESS_0);
641 break;
642 case VRAM_READ_ADDRESS_2:
643 // write pointer is only updated on R#5 write
644 vramReadPtr = getVRAMAddr(VRAM_READ_ADDRESS_0);
645 // update read buffer immediately after read pointer changes. TODO: timing?
646 vramReadBuffer = vram.readVRAMCPU(vramReadPtr, time);
647 break;
648 case SCREEN_MODE_0:
649 case SCREEN_MODE_1:
650 case CONTROL:
651 // These influence the command timing
652 scheduleCmdEnd(time);
653 break;
654 case INTERRUPT_0:
655 irq.set((pendingIRQs & val) != 0);
656 scheduleCmdEnd(time);
657 break;
658 case INTERRUPT_1:
659 case INTERRUPT_2:
660 case INTERRUPT_3:
661 scheduleHscan(time);
662 break;
663 }
664}
665
666void V9990::writePaletteRegister(byte reg, byte val, EmuTime::param time)
667{
668 switch (reg & 3) {
669 case 0: val &= 0x9F; break;
670 case 1: val &= 0x1F; break;
671 case 2: val &= 0x1F; break;
672 case 3: val = 0x00; break;
673 }
674 palette[reg] = val;
675 reg &= ~3;
676 byte index = reg / 4;
677 bool ys = isSuperimposing() && (palette[reg] & 0x80);
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);
682 }
683}
684
686{
687 byte r = palette[4 * index + 0] & 0x1F;
688 byte g = palette[4 * index + 1];
689 byte b = palette[4 * index + 2];
690 bool ys = isSuperimposing() && (palette[4 * index + 0] & 0x80);
691 return {r, g, b, ys};
692}
693
694void V9990::createRenderer(EmuTime::param time)
695{
696 assert(!renderer);
697 renderer = RendererFactory::createV9990Renderer(*this, display);
698 renderer->reset(time);
699}
700
701void V9990::frameStart(EmuTime::param time)
702{
703 // Update setings that are fixed at the start of a frame
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];
709 setVerticalTiming();
710 status ^= 0x02; // flip EO bit
711
712 if (bool newSuperimposing = (regs[CONTROL] & 0x20) && externalVideoSource;
713 superimposing != newSuperimposing) {
714 superimposing = newSuperimposing;
715 renderer->updateSuperimposing(superimposing, time);
716 }
717
718 frameStartTime.reset(time);
719
720 // schedule next VSYNC
721 syncVSync.setSyncPoint(
722 frameStartTime + V9990DisplayTiming::getUCTicksPerFrame(palTiming));
723
724 // schedule DISPLAY_START
725 syncDisplayStart.setSyncPoint(
727
728 // schedule VSCAN
729 syncVScan.setSyncPoint(
731
732 renderer->frameStart(time);
733}
734
735void V9990::raiseIRQ(IRQType irqType)
736{
737 pendingIRQs |= irqType;
738 if (pendingIRQs & regs[INTERRUPT_0]) {
739 irq.set();
740 }
741}
742
743void V9990::setHorizontalTiming()
744{
745 switch (mode) {
746 case P1: case P2:
747 case B1: case B3: case B7:
748 horTiming = &V9990DisplayTiming::lineMCLK;
749 break;
750 case B0: case B2: case B4:
751 horTiming = &V9990DisplayTiming::lineXTAL;
752 case B5: case B6:
753 break;
754 default:
756 }
757}
758
759void V9990::setVerticalTiming()
760{
761 switch (mode) {
762 case P1: case P2:
763 case B1: case B3: case B7:
764 verTiming = isPalTiming()
767 break;
768 case B0: case B2: case B4:
769 verTiming = isPalTiming()
772 case B5: case B6:
773 break;
774 default:
776 }
777}
778
779V9990ColorMode V9990::getColorMode(byte pal_ctrl) const
780{
781 if (!(regs[SCREEN_MODE_0] & 0x80)) {
782 return BP4;
783 } else {
784 switch (regs[SCREEN_MODE_0] & 0x03) {
785 case 0x00: return BP2;
786 case 0x01: return BP4;
787 case 0x02:
788 switch (pal_ctrl & 0xC0) {
789 case 0x00: return BP6;
790 case 0x40: return BD8;
791 case 0x80: return BYJK;
792 case 0xC0: return BYUV;
793 }
794 break;
795 case 0x03: return BD16;
796 }
797 }
799}
800
802{
803 return getColorMode(regs[PALETTE_CONTROL]);
804}
805
806V9990DisplayMode V9990::calcDisplayMode() const
807{
808 switch (regs[SCREEN_MODE_0] & 0xC0) {
809 case 0x00:
810 return P1;
811 case 0x40:
812 return P2;
813 case 0x80:
814 if (status & 0x04) { // MCLK timing
815 switch(regs[SCREEN_MODE_0] & 0x30) {
816 case 0x00: return B0;
817 case 0x10: return B2;
818 case 0x20: return B4;
819 }
820 } else { // XTAL1 timing
821 switch(regs[SCREEN_MODE_0] & 0x30) {
822 case 0x00: return B1;
823 case 0x10: return B3;
824 case 0x20: return B7;
825 }
826 }
827 break;
828 }
829 // invalid display mode
830 return P1; // TODO Check
831}
832
833void V9990::setDisplayMode(V9990DisplayMode newMode)
834{
835 mode = newMode;
836 setHorizontalTiming();
837}
838
839void V9990::scheduleHscan(EmuTime::param time)
840{
841 // remove pending HSCAN, if any
842 if (hScanSyncTime > time) {
843 syncHScan.removeSyncPoint();
844 hScanSyncTime = time;
845 }
846
847 if (pendingIRQs & HOR_IRQ) {
848 // flag already set, no need to schedule
849 return;
850 }
851
852 int ticks = narrow<int>(frameStartTime.getTicksTill_fast(time));
853 int offset = [&] {
854 if (regs[INTERRUPT_2] & 0x80) {
855 // every line
856 return ticks - (ticks % V9990DisplayTiming::UC_TICKS_PER_LINE);
857 } else {
858 int line = regs[INTERRUPT_1] + 256 * (regs[INTERRUPT_2] & 3) +
859 getTopBorder();
861 }
862 }();
863 int mult = (status & 0x04) ? 3 : 2; // MCLK / XTAL1
864 offset += (regs[INTERRUPT_3] & 0x0F) * 64 * mult;
865 if (offset <= ticks) {
866 offset += V9990DisplayTiming::getUCTicksPerFrame(palTiming);
867 }
868
869 hScanSyncTime = frameStartTime + offset;
870 syncHScan.setSyncPoint(hScanSyncTime);
871}
872
873static constexpr std::initializer_list<enum_string<V9990DisplayMode>> displayModeInfo = {
874 { "INVALID", INVALID_DISPLAY_MODE },
875 { "P1", P1 }, { "P2", P2 },
876 { "B0", B0 }, { "B1", B1 }, { "B2", B2 }, { "B3", B3 },
877 { "B4", B4 }, { "B5", B5 }, { "B6", B6 }, { "B7", B7 }
878};
880
881// version 1: initial version
882// version 2: added systemReset
883// version 3: added vramReadPtr, vramWritePtr, vramReadBuffer
884// version 4: removed 'userData' from Schedulable
885// version 5: added syncCmdEnd
886template<typename Archive>
887void V9990::serialize(Archive& ar, unsigned version)
888{
889 ar.template serializeBase<MSXDevice>(*this);
890
891 if (ar.versionAtLeast(version, 4)) {
892 ar.serialize("syncVSync", syncVSync,
893 "syncDisplayStart", syncDisplayStart,
894 "syncVScan", syncVScan,
895 "syncHScan", syncHScan,
896 "syncSetMode", syncSetMode);
897 } else {
899 {&syncVSync, &syncDisplayStart, &syncVScan,
900 &syncHScan, &syncSetMode});
901 }
902 if (ar.versionAtLeast(version, 5)) {
903 ar.serialize("syncCmdEnd", syncCmdEnd);
904 }
905
906 ar.serialize("displayMode", mode); // must be deserialized before cmdEngine (because it's used to restore some derived state in cmdEngine)
907 ar.serialize("vram", vram,
908 "cmdEngine", cmdEngine,
909 "irq", irq,
910 "frameStartTime", frameStartTime,
911 "hScanSyncTime", hScanSyncTime);
912 ar.serialize_blob("palette", palette);
913 ar.serialize("status", status,
914 "pendingIRQs", pendingIRQs);
915 ar.serialize_blob("registers", regs);
916 ar.serialize("regSelect", regSelect,
917 "palTiming", palTiming,
918 "interlaced", interlaced,
919 "isDisplayArea", isDisplayArea,
920 "displayEnabled", displayEnabled,
921 "scrollAYHigh", scrollAYHigh,
922 "scrollBYHigh", scrollBYHigh);
923
924 if (ar.versionBelow(version, 2)) {
925 systemReset = false;
926 } else {
927 ar.serialize("systemReset", systemReset);
928 }
929
930 if (ar.versionBelow(version, 3)) {
931 vramReadPtr = getVRAMAddr(VRAM_READ_ADDRESS_0);
932 vramWritePtr = getVRAMAddr(VRAM_WRITE_ADDRESS_0);
933 vramReadBuffer = vram.readVRAMCPU(vramReadPtr, getCurrentTime());
934 } else {
935 ar.serialize("vramReadPtr", vramReadPtr,
936 "vramWritePtr", vramWritePtr,
937 "vramReadBuffer", vramReadBuffer);
938 }
939
940 // No need to serialize 'externalVideoSource', it will be restored when
941 // the external peripheral (e.g. Video9000) is de-serialized.
942 // TODO should 'superimposing' be serialized? It can't be recalculated
943 // from register values (it depends on the register values at the start
944 // of this frame). But it will be correct at the start of the next
945 // frame. Good enough?
946
947 if constexpr (Archive::IS_LOADER) {
948 // TODO This uses 'mode' to calculate 'horTiming' and
949 // 'verTiming'. Are these always in sync? Or can for
950 // example one change at any time and the other only
951 // at start of frame (or next line)? Does this matter?
952 setHorizontalTiming();
953 setVerticalTiming();
954
955 renderer->reset(getCurrentTime());
956 }
957}
960
961} // namespace openmsx
#define REGISTER_MSXDEVICE(CLASS, NAME)
Definition MSXDevice.hh:356
int g
constexpr void reset(EmuTime::param e)
Reset the clock to start ticking at the given time.
Definition Clock.hh:102
constexpr unsigned getTicksTill_fast(EmuTime::param e) const
Same as above, only faster, Though the time interval may not be too large.
Definition Clock.hh:70
void detach(VideoSystemChangeListener &listener)
Definition Display.cc:121
void set()
Set the interrupt request on the bus.
Definition IRQHelper.hh:76
void reset()
Reset the interrupt request on the bus.
Definition IRQHelper.hh:85
An MSXDevice is an emulated hardware component connected to the bus of the emulated MSX.
Definition MSXDevice.hh:36
EmuTime::param getCurrentTime() const
Definition MSXDevice.cc:125
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)
Definition V9990VRAM.cc:48
byte readVRAMCPU(unsigned address, EmuTime::param time)
Definition V9990VRAM.cc:41
Implementation of the Yamaha V9990 VDP as used in the GFX9000 cartridge by Sunrise.
Definition V9990.hh:35
void reset(EmuTime::param time) override
This method is called on reset.
Definition V9990.cc:125
int getTopBorder() const
Definition V9990.hh:338
void powerUp(EmuTime::param time) override
This method is called when MSX is powered up.
Definition V9990.cc:119
void serialize(Archive &ar, unsigned version)
Definition V9990.cc:887
bool isPalTiming() const
Is PAL timing active? This setting is fixed at start of frame.
Definition V9990.hh:130
bool isSuperimposing() const
Should this frame be superimposed? This is a combination of bit 5 (YSE) in R#8 and the presence of an...
Definition V9990.hh:146
int getUCTicksThisFrame(EmuTime::param time) const
Get the number of elapsed UC ticks in this frame.
Definition V9990.hh:122
~V9990() override
Definition V9990.cc:105
GetPaletteResult getPalette(int index) const
Definition V9990.cc:685
int getBottomBorder() const
Definition V9990.hh:342
int getLeftBorder() const
Get the number of VDP clock-ticks between the start of the line and the end of the left border.
Definition V9990.hh:321
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.
Definition V9990.cc:275
PostProcessor * getPostProcessor() const
Used by Video9000 to be able to couple the VDP and V9990 output.
Definition V9990.cc:110
bool isDisplayEnabled() const
Is the display enabled? Note this is simpler than the V99x8 version.
Definition V9990.hh:85
byte peekIO(word port, EmuTime::param time) const override
Read a byte from a given IO port.
Definition V9990.cc:222
V9990ColorMode getColorMode() const
Return the current color mode.
Definition V9990.cc:801
V9990(const DeviceConfig &config)
Definition V9990.cc:54
byte readIO(word port, EmuTime::param time) override
Read a byte from an IO port at a certain time from this device.
Definition V9990.cc:161
int getRightBorder() const
Get the number of VDP clock-ticks between the start of the line and the end of the right border.
Definition V9990.hh:328
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:
Definition Autofire.cc:11
uint8_t byte
8 bit unsigned integer
Definition openmsx.hh:26
@ INVALID_DISPLAY_MODE
uint16_t word
16 bit unsigned integer
Definition openmsx.hh:29
constexpr void fill(ForwardRange &&range, const T &value)
Definition ranges.hh:305
uint32_t next(octet_iterator &it, octet_iterator end)
constexpr To narrow_cast(From &&from) noexcept
Definition narrow.hh:21
#define OUTER(type, member)
Definition outer.hh:42
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
#define SERIALIZE_ENUM(TYPE, INFO)
TemporaryString tmpStrCat(Ts &&... ts)
Definition strCat.hh:742
#define UNREACHABLE
constexpr auto xrange(T e)
Definition xrange.hh:132