1 /***************************************************************************
2 mos6526.cpp - CIA Timer
3 -------------------
4 begin : Wed Jun 7 2000
5 copyright : (C) 2000 by Simon White
6 email : s_a_white@email.com
7 ***************************************************************************/
8
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17 /***************************************************************************
18 * $Log: mos6526.cpp,v $
19 * Revision 1.10 2002/12/16 22:12:24 s_a_white
20 * Simulate serial input from data port A to prevent kernel lockups.
21 *
22 * Revision 1.9 2002/11/20 22:50:27 s_a_white
23 * Reload count when timers are stopped
24 *
25 * Revision 1.8 2002/10/02 19:49:21 s_a_white
26 * Revert previous change as was incorrect.
27 *
28 * Revision 1.7 2002/09/11 22:30:47 s_a_white
29 * Counter interval writes now go to a new register call prescaler. This is
30 * copied to the timer latch/counter as appropriate.
31 *
32 * Revision 1.6 2002/09/09 22:49:06 s_a_white
33 * Proper idr clear if interrupt was only internally pending.
34 *
35 * Revision 1.5 2002/07/20 08:34:52 s_a_white
36 * Remove unnecessary and pointless conts.
37 *
38 * Revision 1.4 2002/03/03 22:04:08 s_a_white
39 * Tidy.
40 *
41 * Revision 1.3 2001/07/14 13:03:33 s_a_white
42 * Now uses new component classes and event generation.
43 *
44 * Revision 1.2 2001/03/23 23:21:38 s_a_white
45 * Removed redundant reset funtion. Timer b now gets initialised properly.
46 * Switch case now allows write/read from timer b.
47 *
48 * Revision 1.1 2001/03/21 22:41:45 s_a_white
49 * Non faked CIA emulation with NMI support. Removal of Hacked VIC support
50 * off CIA timer.
51 *
52 * Revision 1.8 2001/03/09 23:44:30 s_a_white
53 * Integrated more 6526 features. All timer modes and interrupts correctly
54 * supported.
55 *
56 * Revision 1.7 2001/02/21 22:07:10 s_a_white
57 * Prevent re-triggering of interrupt if it's already active.
58 *
59 * Revision 1.6 2001/02/13 21:00:01 s_a_white
60 * Support for real interrupts.
61 *
62 * Revision 1.4 2000/12/11 18:52:12 s_a_white
63 * Conversion to AC99
64 *
65 ***************************************************************************/
66
67 #include "sidendian.h"
68 #include "mos6526.h"
69
70 enum
71 {
72 INTERRUPT_TA = 1 << 0,
73 INTERRUPT_TB = 1 << 1,
74 INTERRUPT_ALARM = 1 << 2,
75 INTERRUPT_SP = 1 << 3,
76 INTERRUPT_FLAG = 1 << 4,
77 INTERRUPT_REQUEST = 1 << 7
78 };
79
80 const char *MOS6526::credit =
81 { // Optional information
82 "*MOS6526 (CIA) Emulation:\0"
83 "\tCopyright (C) 2001 Simon White <sidplay2@email.com>\0"
84 };
85
86
MOS6526(EventContext * context)87 MOS6526::MOS6526 (EventContext *context)
88 :idr(0),
89 event_context(*context),
90 event_ta(this),
91 event_tb(this)
92 {
93 reset ();
94 }
95
reset(void)96 void MOS6526::reset (void)
97 {
98 ta = ta_latch = 0xffff;
99 tb = tb_latch = 0xffff;
100 cra = crb = 0;
101 // Clear off any IRQs
102 trigger (0);
103 cnt_high = true;
104 icr = idr = 0;
105 m_accessClk = 0;
106 dpa = 0xf0;
107 }
108
read(uint_least8_t addr)109 uint8_t MOS6526::read (uint_least8_t addr)
110 {
111 event_clock_t cycles;
112 if (addr > 0x0f) return 0;
113
114 cycles = event_context.getTime (m_accessClk);
115 m_accessClk += cycles;
116
117 // Sync up timers
118 if ((cra & 0x21) == 0x01)
119 ta -= cycles;
120 if ((crb & 0x61) == 0x01)
121 tb -= cycles;
122
123 switch (addr)
124 {
125 case 0x0: // Simulate a serial port
126 dpa = ((dpa << 1) | (dpa >> 7)) & 0xff;
127 if (dpa & 0x80)
128 return 0xc0;
129 return 0;
130 case 0x4: return endian_16lo8 (ta);
131 case 0x5: return endian_16hi8 (ta);
132 case 0x6: return endian_16lo8 (tb);
133 case 0x7: return endian_16hi8 (tb);
134
135 case 0xd:
136 { // Clear IRQs, and return interrupt
137 // data register
138 uint8_t ret = idr;
139 trigger (0);
140 return ret;
141 }
142
143 case 0x0e: return cra;
144 case 0x0f: return crb;
145 default: return regs[addr];
146 }
147 }
148
write(uint_least8_t addr,uint8_t data)149 void MOS6526::write (uint_least8_t addr, uint8_t data)
150 {
151 event_clock_t cycles;
152 if (addr > 0x0f) return;
153
154 regs[addr] = data;
155 cycles = event_context.getTime (m_accessClk);
156 m_accessClk += cycles;
157
158 // Sync up timers
159 if ((cra & 0x21) == 0x01)
160 ta -= cycles;
161 if ((crb & 0x61) == 0x01)
162 tb -= cycles;
163
164 switch (addr)
165 {
166 case 0x4: endian_16lo8 (ta_latch, data); break;
167 case 0x5:
168 endian_16hi8 (ta_latch, data);
169 if (!(cra & 0x01)) // Reload timer if stopped
170 ta = ta_latch;
171 break;
172
173 case 0x6: endian_16lo8 (tb_latch, data); break;
174 case 0x7:
175 endian_16hi8 (tb_latch, data);
176 if (!(crb & 0x01)) // Reload timer if stopped
177 tb = tb_latch;
178 break;
179
180 case 0xd:
181 if (data & 0x80)
182 icr |= data & 0x1f;
183 else
184 icr &= ~data;
185 trigger (idr);
186 break;
187
188 case 0x0e:
189 // Check for forced load
190 cra = data;
191 if (data & 0x10)
192 {
193 cra &= (~0x10);
194 ta = ta_latch;
195 }
196
197 if ((data & 0x21) == 0x01)
198 { // Active
199 event_context.schedule (&event_ta, (event_clock_t) ta + 1);
200 } else
201 { // Inactive
202 ta = ta_latch;
203 event_context.cancel (&event_ta);
204 }
205 break;
206
207 case 0x0f:
208 // Check for forced load
209 crb = data;
210 if (data & 0x10)
211 {
212 crb &= (~0x10);
213 tb = tb_latch;
214 }
215
216 if ((data & 0x61) == 0x01)
217 { // Active
218 event_context.schedule (&event_tb, (event_clock_t) tb + 1);
219 } else
220 { // Inactive
221 tb = tb_latch;
222 event_context.cancel (&event_tb);
223 }
224 break;
225
226 default:
227 break;
228 }
229 }
230
trigger(int irq)231 void MOS6526::trigger (int irq)
232 {
233 if (!irq)
234 { // Clear any requested IRQs
235 if (idr & INTERRUPT_REQUEST)
236 interrupt (false);
237 idr = 0;
238 return;
239 }
240
241 idr |= irq;
242 if (icr & idr)
243 {
244 if (!(idr & INTERRUPT_REQUEST))
245 {
246 idr |= INTERRUPT_REQUEST;
247 interrupt (true);
248 }
249 }
250 }
251
ta_event(void)252 void MOS6526::ta_event (void)
253 { // Timer Modes
254 event_clock_t cycles;
255 uint8_t mode = cra & 0x21;
256
257 if (mode == 0x21)
258 {
259 if (ta--)
260 return;
261 }
262
263 cycles = event_context.getTime (m_accessClk);
264 m_accessClk += cycles;
265
266 ta = ta_latch;
267 if (cra & 0x08)
268 { // one shot, stop timer A
269 cra &= (~0x01);
270 } else if (mode == 0x01)
271 { // Reset event
272 event_context.schedule (&event_ta, (event_clock_t) ta + 1);
273 }
274 trigger (INTERRUPT_TA);
275
276 switch (crb & 0x61)
277 {
278 case 0x01: tb -= cycles; break;
279 case 0x41:
280 case 0x61:
281 tb_event ();
282 break;
283 }
284 }
285
tb_event(void)286 void MOS6526::tb_event (void)
287 { // Timer Modes
288 uint8_t mode = crb & 0x61;
289 switch (mode)
290 {
291 case 0x01:
292 break;
293
294 case 0x21:
295 case 0x41:
296 if (tb--)
297 return;
298 break;
299
300 case 0x61:
301 if (cnt_high)
302 {
303 if (tb--)
304 return;
305 }
306 break;
307
308 default:
309 return;
310 }
311
312 m_accessClk = event_context.getTime ();
313 tb = tb_latch;
314 if (crb & 0x08)
315 { // one shot, stop timer A
316 crb &= (~0x01);
317 } else if (mode == 0x01)
318 { // Reset event
319 event_context.schedule (&event_tb, (event_clock_t) tb + 1);
320 }
321 trigger (INTERRUPT_TB);
322 }
323