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