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