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