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