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