openMSX
MC6850.cc
Go to the documentation of this file.
1 #include "MC6850.hh"
2 #include "MidiInDevice.hh"
3 #include "MSXMotherBoard.hh"
4 #include "EmuTime.hh"
5 #include "serialize.hh"
6 #include "unreachable.hh"
7 
8 namespace openmsx {
9 
10 // MSX interface:
11 // port R/W
12 // 0 W Control Register
13 // 1 W Transmit Data Register
14 // 4 R Status Register
15 // 5 R Receive Data Register
16 
17 // control register bits
18 constexpr unsigned CR_CDS1 = 0x01; // Counter Divide Select 1
19 constexpr unsigned CR_CDS2 = 0x02; // Counter Divide Select 2
20 constexpr unsigned CR_CDS = CR_CDS1 | CR_CDS2;
21 constexpr unsigned CR_MR = CR_CDS1 | CR_CDS2; // Master Reset
22 // CDS2 CDS1
23 // 0 0 divide by 1
24 // 0 1 divide by 16
25 // 1 0 divide by 64
26 // 1 1 master reset (!)
27 
28 constexpr unsigned CR_WS1 = 0x04; // Word Select 1 (mostly parity)
29 constexpr unsigned CR_WS2 = 0x08; // Word Select 2 (mostly nof stop bits)
30 constexpr unsigned CR_WS3 = 0x10; // Word Select 3: 7/8 bits
31 constexpr unsigned CR_WS = CR_WS1 | CR_WS2 | CR_WS3; // Word Select
32 // WS3 WS2 WS1
33 // 0 0 0 7 bits - 2 stop bits - Even parity
34 // 0 0 1 7 bits - 2 stop bits - Odd parity
35 // 0 1 0 7 bits - 1 stop bit - Even parity
36 // 0 1 1 7 bits - 1 stop bit - Odd Parity
37 // 1 0 0 8 bits - 2 stop bits - No Parity
38 // 1 0 1 8 bits - 1 stop bit - No Parity
39 // 1 1 0 8 bits - 1 stop bit - Even parity
40 // 1 1 1 8 bits - 1 stop bit - Odd parity
41 
42 constexpr unsigned CR_TC1 = 0x20; // Transmit Control 1
43 constexpr unsigned CR_TC2 = 0x40; // Transmit Control 2
44 constexpr unsigned CR_TC = CR_TC1 | CR_TC2; // Transmit Control
45 // TC2 TC1
46 // 0 0 /RTS low, Transmitting Interrupt disabled
47 // 0 1 /RTS low, Transmitting Interrupt enabled
48 // 1 0 /RTS high, Transmitting Interrupt disabled
49 // 1 1 /RTS low, Transmits a Break level on the Transmit Data Output.
50 // Interrupt disabled
51 
52 constexpr unsigned CR_RIE = 0x80; // Receive Interrupt Enable: interrupt
53 // at Receive Data Register Full, Overrun, low-to-high transition on the Data
54 // Carrier Detect (/DCD) signal line
55 
56 // status register bits
57 constexpr unsigned STAT_RDRF = 0x01; // Receive Data Register Full
58 constexpr unsigned STAT_TDRE = 0x02; // Transmit Data Register Empty
59 constexpr unsigned STAT_DCD = 0x04; // Data Carrier Detect (/DCD)
60 constexpr unsigned STAT_CTS = 0x08; // Clear-to-Send (/CTS)
61 constexpr unsigned STAT_FE = 0x10; // Framing Error
62 constexpr unsigned STAT_OVRN = 0x20; // Receiver Overrun
63 constexpr unsigned STAT_PE = 0x40; // Parity Error
64 constexpr unsigned STAT_IRQ = 0x80; // Interrupt Request (/IRQ)
65 
66 
67 // Some existing Music-Module detection routines:
68 // - fac demo 5: does OUT 0,3 : OUT 0,21 : INP(4) and expects to read 2
69 // - tetris 2 special edition: does INP(4) and expects to read 0
70 // - Synthesix: does INP(4), expects 0; OUT 0,3 : OUT 0,21: INP(4) and expects
71 // bit 1 to be 1 and bit 2, 3 and 7 to be 0. Then does OUT 5,0xFE : INP(4)
72 // and expects bit 1 to be 0.
73 // I did some _very_basic_ investigation and found the following:
74 // - after a reset reading from port 4 returns 0
75 // - after initializing the control register, reading port 4 returns 0 (of
76 // course this will change when you start to actually receive/transmit data)
77 // - writing any value with the lower 2 bits set to 1 returns to the initial
78 // state, and reading port 4 again returns 0.
79 // - ?INP(4) : OUT0,3 : ?INP(4) : OUT0,21 : ? INP(4) : OUT0,3 : ?INP(4)
80 // outputs: 0, 0, 2, 0
81 
83  : MSXDevice(config)
84  , MidiInConnector(getMotherBoard().getPluggingController(), MSXDevice::getName() + "-in")
85  , syncRecv (getMotherBoard().getScheduler())
86  , syncTrans(getMotherBoard().getScheduler())
87  , txClock(EmuTime::zero())
88  , rxIRQ(getMotherBoard(), MSXDevice::getName() + "-rx-IRQ")
89  , txIRQ(getMotherBoard(), MSXDevice::getName() + "-tx-IRQ")
90  , txDataReg(0), txShiftReg(0) // avoid UMR
91  , outConnector(getMotherBoard().getPluggingController(), MSXDevice::getName() + "-out")
92 {
93  reset(EmuTime::zero());
94  setDataFormat();
95 }
96 
97 // (Re-)initialize chip to default values (Tx and Rx disabled)
98 void MC6850::reset(EmuTime::param time)
99 {
100  syncRecv .removeSyncPoint();
101  syncTrans.removeSyncPoint();
102  txClock.reset(time);
103  txClock.setFreq(500000); // 500kHz
104  rxIRQ.reset();
105  txIRQ.reset();
106  rxReady = false;
107  txShiftRegValid = false;
108  pendingOVRN = false;
109  controlReg = CR_MR;
110  statusReg = 0;
111  rxDataReg = 0;
112  setDataFormat();
113 }
114 
115 byte MC6850::readIO(word port, EmuTime::param /*time*/)
116 {
117  switch (port & 0x1) {
118  case 0:
119  return readStatusReg();
120  case 1:
121  return readDataReg();
122  }
123  UNREACHABLE;
124  return 0xFF;
125 }
126 
127 byte MC6850::peekIO(word port, EmuTime::param /*time*/) const
128 {
129  switch (port & 0x1) {
130  case 0:
131  return peekStatusReg();
132  case 1:
133  return peekDataReg();
134  }
135  UNREACHABLE;
136  return 0xFF;
137 }
138 
139 void MC6850::writeIO(word port, byte value, EmuTime::param time)
140 {
141  switch (port & 0x01) {
142  case 0:
143  writeControlReg(value, time);
144  break;
145  case 1:
146  writeDataReg(value, time);
147  break;
148  }
149 }
150 
151 byte MC6850::readStatusReg()
152 {
153  return peekStatusReg();
154 }
155 
156 byte MC6850::peekStatusReg() const
157 {
158  byte result = statusReg;
159  if (rxIRQ.getState() || txIRQ.getState()) result |= STAT_IRQ;
160  return result;
161 }
162 
163 byte MC6850::readDataReg()
164 {
165  byte result = peekDataReg();
166  statusReg &= ~(STAT_RDRF | STAT_OVRN);
167  if (pendingOVRN) {
168  pendingOVRN = false;
169  statusReg |= STAT_OVRN;
170  }
171  rxIRQ.reset();
172  return result;
173 }
174 
175 byte MC6850::peekDataReg() const
176 {
177  return rxDataReg;
178 }
179 
180 void MC6850::writeControlReg(byte value, EmuTime::param time)
181 {
182  byte diff = value ^ controlReg;
183  if (diff & CR_CDS) {
184  if ((value & CR_CDS) == CR_MR) {
185  reset(time);
186  } else {
187  // we got out of MR state
188  rxReady = true;
189  statusReg |= STAT_TDRE;
190 
191  txClock.reset(time);
192  switch (value & CR_CDS) {
193  case 0: txClock.setFreq(500000, 1); break; // 500kHz
194  case 1: txClock.setFreq(500000, 16); break; // 31250Hz (MIDI)
195  case 2: txClock.setFreq(500000, 64); break; // 7812.5Hz
196  }
197  }
198  }
199 
200  controlReg = value;
201  if (diff & CR_WS) setDataFormat();
202 
203  // update IRQ status
204  rxIRQ.set(( value & CR_RIE) && (statusReg & STAT_RDRF));
205  txIRQ.set(((value & CR_TC) == 0x20) && (statusReg & STAT_TDRE));
206 }
207 
208 // Sync data-format related parameters with the current value of controlReg
209 void MC6850::setDataFormat()
210 {
211  outConnector.setDataBits(controlReg & CR_WS3 ? DATA_8 : DATA_7);
212 
213  StopBits stopBits[8] = {
216  };
217  outConnector.setStopBits(stopBits[(controlReg & CR_WS) >> 2]);
218 
219  outConnector.setParityBit(
220  (controlReg & (CR_WS3 | CR_WS2)) != 0x10, // enable
221  (controlReg & CR_WS1) ? ODD : EVEN);
222 
223  // start-bits, data-bits, parity-bits, stop-bits
224  byte len[8] = {
225  1 + 7 + 1 + 2,
226  1 + 7 + 1 + 2,
227  1 + 7 + 1 + 1,
228  1 + 7 + 1 + 1,
229  1 + 8 + 0 + 2,
230  1 + 8 + 0 + 1,
231  1 + 8 + 1 + 1,
232  1 + 8 + 1 + 1,
233  };
234  charLen = len[(controlReg & CR_WS) >> 2];
235 }
236 
237 void MC6850::writeDataReg(byte value, EmuTime::param time)
238 {
239  if ((controlReg & CR_CDS) == CR_MR) return;
240 
241  txDataReg = value;
242  statusReg &= ~STAT_TDRE;
243  txIRQ.reset();
244 
245  if (syncTrans.pendingSyncPoint()) {
246  // We're still sending the previous character, only
247  // buffer this one. Don't accept any further characters
248  } else {
249  // We were not yet sending. Start sending at the next txClock.
250  // Important: till that time TDRE should go low
251  // (MC6850 detection routine in Synthesix depends on this)
252  txClock.advance(time); // clock edge before or at 'time'
253  txClock += 1; // clock edge strictly after 'time'
254  syncTrans.setSyncPoint(txClock.getTime());
255  }
256 }
257 
258 // Triggered between transmitted characters, including before the first and
259 // after the last character.
260 void MC6850::execTrans(EmuTime::param time)
261 {
262  assert(txClock.getTime() == time);
263  assert((controlReg & CR_CDS) != CR_MR);
264 
265  if (txShiftRegValid) {
266  txShiftRegValid = false;
267  outConnector.recvByte(txShiftReg, time);
268  }
269 
270  if (statusReg & STAT_TDRE) {
271  // No next character to send, we're done.
272  } else {
273  // There already is a next character, start sending that now
274  // and accept a next one.
275  statusReg |= STAT_TDRE;
276  if (((controlReg & CR_TC) == 0x20)) txIRQ.set();
277 
278  txShiftReg = txDataReg;
279  txShiftRegValid = true;
280 
281  txClock += charLen;
282  syncTrans.setSyncPoint(txClock.getTime());
283  }
284 }
285 
286 // MidiInConnector sends a new character.
287 void MC6850::recvByte(byte value, EmuTime::param time)
288 {
289  assert(acceptsData() && ready());
290 
291  if (statusReg & STAT_RDRF) {
292  // So, there is a byte that has to be read by the MSX still!
293  // This happens when the MSX program doesn't
294  // respond fast enough to an earlier received byte.
295  // The STAT_OVRN flag only becomes active once the prior valid
296  // character has been read from the data register.
297  pendingOVRN = true;
298  } else {
299  rxDataReg = value;
300  statusReg |= STAT_RDRF;
301  }
302  // both for OVRN and RDRF an IRQ is raised
303  if (controlReg & CR_RIE) rxIRQ.set();
304 
305  // Not ready now, but we will be in a while
306  rxReady = false;
307 
308  // The MC6850 has separate TxCLK and RxCLK inputs, but both share a
309  // common divider. This implementation hard-codes an input frequency of
310  // 500kHz for both. Below we want the receive clock period, but it's OK
311  // to calculate that as 'txClock.getPeriod()'.
312  syncRecv.setSyncPoint(time + txClock.getPeriod() * charLen);
313 }
314 
315 // Triggered when we're ready to receive the next character.
316 void MC6850::execRecv(EmuTime::param time)
317 {
318  assert(acceptsData());
319  assert(!rxReady);
320  rxReady = true;
321  getPluggedMidiInDev().signal(time); // trigger (possible) send of next char
322 }
323 
324 // MidiInDevice querries whether it can send a new character 'now'.
325 bool MC6850::ready()
326 {
327  return rxReady;
328 }
329 
330 // MidiInDevice queries whether it can send characters at all.
331 bool MC6850::acceptsData()
332 {
333  return (controlReg & CR_CDS) != CR_MR;
334 }
335 
336 // MidiInDevice informs us about the format of the data it will send
337 // (MIDI is always 1 start-bit, 8 data-bits, 1 stop-bit, no parity-bits).
338 void MC6850::setDataBits(DataBits /*bits*/)
339 {
340  // ignore
341 }
342 void MC6850::setStopBits(StopBits /*bits*/)
343 {
344  // ignore
345 }
346 void MC6850::setParityBit(bool /*enable*/, ParityBit /*parity*/)
347 {
348  // ignore
349 }
350 
351 // version 1: initial version
352 // version 2: added control
353 // version 3: actually working MC6850 with many more member variables
354 template<typename Archive>
355 void MC6850::serialize(Archive& ar, unsigned version)
356 {
357  ar.template serializeBase<MSXDevice>(*this);
358  if (ar.versionAtLeast(version, 3)) {
359  ar.template serializeBase<MidiInConnector>(*this);
360  ar.serialize("outConnector", outConnector,
361 
362  "syncRecv", syncRecv,
363  "syncTrans", syncTrans,
364 
365  "txClock", txClock,
366  "rxIRQ", rxIRQ,
367  "txIRQ", txIRQ,
368 
369  "rxReady", rxReady,
370  "txShiftRegValid", txShiftRegValid,
371  "pendingOVRN", pendingOVRN,
372 
373  "rxDataReg", rxDataReg,
374  "txDataReg", txDataReg,
375  "txShiftReg", txShiftReg,
376  "controlReg", controlReg,
377  "statusReg", statusReg);
378  } else if (ar.versionAtLeast(version, 2)) {
379  ar.serialize("control", controlReg);
380  } else {
381  controlReg = 3;
382  }
383 
384  if (ar.isLoader()) {
385  setDataFormat();
386  }
387 }
389 REGISTER_MSXDEVICE(MC6850, "MC6850");
390 
391 } // namespace openmsx
virtual void signal(EmuTime::param time)=0
constexpr unsigned CR_TC1
Definition: MC6850.cc:42
void reset(EmuTime::param time) override
This method is called on reset.
Definition: MC6850.cc:98
constexpr byte STAT_FE
Definition: I8251.cc:15
MC6850(const DeviceConfig &config)
Definition: MC6850.cc:82
void recvByte(byte value, EmuTime::param time) override
constexpr unsigned STAT_RDRF
Definition: MC6850.cc:57
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
constexpr unsigned CR_MR
Definition: MC6850.cc:21
void setDataBits(DataBits bits) override
constexpr unsigned CR_WS1
Definition: MC6850.cc:28
void setStopBits(StopBits bits) override
REGISTER_MSXDEVICE(ChakkariCopy, "ChakkariCopy")
EmuDuration getPeriod() const
Returns the length of one clock-cycle.
constexpr unsigned CR_CDS1
Definition: MC6850.cc:18
constexpr unsigned CR_CDS2
Definition: MC6850.cc:19
EmuTime::param getTime() const
Gets the time at which the last clock tick occurred.
Definition: DynamicClock.hh:37
MidiInDevice & getPluggedMidiInDev() const
constexpr unsigned CR_TC2
Definition: MC6850.cc:43
byte peekIO(word port, EmuTime::param time) const override
Read a byte from a given IO port.
Definition: MC6850.cc:127
constexpr unsigned CR_RIE
Definition: MC6850.cc:52
void advance(EmuTime::param e)
Advance this clock in time until the last tick which is not past the given time.
constexpr unsigned CR_WS3
Definition: MC6850.cc:30
constexpr unsigned CR_CDS
Definition: MC6850.cc:20
constexpr unsigned STAT_DCD
Definition: MC6850.cc:59
void setParityBit(bool enable, ParityBit parity) override
constexpr byte STAT_PE
Definition: I8251.cc:13
void setFreq(unsigned freq)
Change the frequency at which this clock ticks.
Definition: DynamicClock.hh:86
byte readIO(word port, EmuTime::param time) override
Read a byte from an IO port at a certain time from this device.
Definition: MC6850.cc:115
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
constexpr unsigned CR_WS
Definition: MC6850.cc:31
bool getState() const
Get the interrupt state.
Definition: IRQHelper.hh:116
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: MC6850.cc:139
An MSXDevice is an emulated hardware component connected to the bus of the emulated MSX...
Definition: MSXDevice.hh:31
void reset()
Reset the interrupt request on the bus.
Definition: IRQHelper.hh:96
constexpr unsigned CR_TC
Definition: MC6850.cc:44
constexpr unsigned CR_WS2
Definition: MC6850.cc:29
uint16_t word
16 bit unsigned integer
Definition: openmsx.hh:29
void set()
Set the interrupt request on the bus.
Definition: IRQHelper.hh:87
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:981
constexpr unsigned STAT_OVRN
Definition: MC6850.cc:62
constexpr unsigned STAT_CTS
Definition: MC6850.cc:60
constexpr unsigned STAT_TDRE
Definition: MC6850.cc:58
string getName(KeyCode keyCode)
Translate key code to key name.
Definition: Keys.cc:589
constexpr unsigned STAT_IRQ
Definition: MC6850.cc:64
void reset(EmuTime::param e)
Reset the clock to start ticking at the given time.
void serialize(Archive &ar, unsigned version)
Definition: MC6850.cc:355
#define UNREACHABLE
Definition: unreachable.hh:38