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