1 /*
2  * This file is part of libsidplayfp, a SID player engine.
3  *
4  * Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
5  * Copyright 2009-2014 VICE Project
6  * Copyright 2007-2010 Antti Lankila
7  * Copyright 2000 Simon White
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
22  */
23 
24 #ifndef MOS6526_H
25 #define MOS6526_H
26 
27 #include <memory>
28 
29 #include <stdint.h>
30 
31 #include "interrupt.h"
32 #include "timer.h"
33 #include "tod.h"
34 #include "SerialPort.h"
35 #include "EventScheduler.h"
36 
37 #include "sidcxx11.h"
38 
39 class EventContext;
40 
41 namespace libsidplayfp
42 {
43 
44 class MOS6526;
45 
46 /**
47  * This is the timer A of this CIA.
48  *
49  * @author Ken Händel
50  *
51  */
52 class TimerA final : public Timer
53 {
54 private:
55     /**
56      * Signal underflows of Timer A to Timer B.
57      */
58     void underFlow() override;
59 
60     void serialPort() override;
61 
62 public:
63     /**
64      * Create timer A.
65      */
TimerA(EventScheduler & scheduler,MOS6526 & parent)66     TimerA(EventScheduler &scheduler, MOS6526 &parent) :
67         Timer("CIA Timer A", scheduler, parent) {}
68 };
69 
70 /**
71  * This is the timer B of this CIA.
72  *
73  * @author Ken Händel
74  *
75  */
76 class TimerB final : public Timer
77 {
78 private:
79     void underFlow() override;
80 
81 public:
82     /**
83      * Create timer B.
84      */
TimerB(EventScheduler & scheduler,MOS6526 & parent)85     TimerB(EventScheduler &scheduler, MOS6526 &parent) :
86         Timer("CIA Timer B", scheduler, parent) {}
87 
88     /**
89      * Receive an underflow from Timer A.
90      */
cascade()91     void cascade()
92     {
93         // we pretend that we are CPU doing a write to ctrl register
94         syncWithCpu();
95         state |= CIAT_STEP;
96         wakeUpAfterSyncWithCpu();
97     }
98 
99     /**
100      * Check if start flag is set.
101      *
102      * @return true if start flag is set, false otherwise
103      */
started()104     bool started() const { return (state & CIAT_CR_START) != 0; }
105 };
106 
107 /**
108  * InterruptSource that acts like new CIA
109  */
110 class InterruptSource8521 final : public InterruptSource
111 {
112 public:
InterruptSource8521(EventScheduler & scheduler,MOS6526 & parent)113     InterruptSource8521(EventScheduler &scheduler, MOS6526 &parent) :
114         InterruptSource(scheduler, parent)
115     {}
116 
117     void trigger(uint8_t interruptMask) override;
118 
119     uint8_t clear() override;
120 };
121 
122 /**
123  * InterruptSource that acts like old CIA
124  */
125 class InterruptSource6526 final : public InterruptSource
126 {
127 private:
128     /// Timer B bug
129     bool tbBug;
130 
131 private:
triggerBug()132     void triggerBug() { idr &= ~INTERRUPT_UNDERFLOW_B; }
133 
134 public:
InterruptSource6526(EventScheduler & scheduler,MOS6526 & parent)135     InterruptSource6526(EventScheduler &scheduler, MOS6526 &parent) :
136         InterruptSource(scheduler, parent),
137         tbBug(false)
138     {}
139 
140     void trigger(uint8_t interruptMask) override;
141 
142     uint8_t clear() override;
143 
144     void reset() override;
145 };
146 
147 /**
148  * This class is heavily based on the ciacore/ciatimer source code from VICE.
149  * The CIA state machine is lifted as-is. Big thanks to VICE project!
150  * The Serial Port emulation is based on Denise emu code.
151  */
152 class MOS6526
153 {
154     friend class InterruptSource;
155     friend class SerialPort;
156     friend class TimerA;
157     friend class TimerB;
158     friend class Tod;
159 
160 private:
161     static const char *credit;
162 
163 protected:
164     /// Event context.
165     EventScheduler &eventScheduler;
166 
167     /// Ports
168     //@{
169     uint8_t &pra, &prb, &ddra, &ddrb;
170     //@}
171 
172     /// These are all CIA registers.
173     uint8_t regs[0x10];
174 
175     /// Timers A and B.
176     //@{
177     TimerA timerA;
178     TimerB timerB;
179     //@}
180 
181     /// Interrupt Source
182     std::unique_ptr<InterruptSource> interruptSource;
183 
184     /// TOD
185     Tod tod;
186 
187     /// Serial Data Registers
188     SerialPort serialPort;
189 
190     /// Events
191     //@{
192     EventCallback<MOS6526> bTickEvent;
193     //@}
194 
195 private:
196     /**
197      * Trigger an interrupt from TOD.
198      */
199     void todInterrupt();
200 
201     /**
202      * Trigger an interrupt from Serial Port.
203      */
204     void spInterrupt();
205 
206     /**
207      * This event exists solely to break the ambiguity of what scheduling on
208      * top of PHI1 causes, because there is no ordering between events on
209      * same phase. Thus it is scheduled in PHI2 to ensure the b.event() is
210      * run once before the value changes.
211      *
212      * - PHI1 a.event() (which calls underFlow())
213      * - PHI1 b.event()
214      * - PHI2 bTick.event()
215      * - PHI1 a.event()
216      * - PHI1 b.event()
217      */
218     void bTick();
219 
220     /**
221      * Timer A underflow.
222      */
223     void underflowA();
224 
225     /** Timer B underflow. */
226     void underflowB();
227 
228     /**
229      * Handle the serial port.
230      */
231     void handleSerialPort();
232 
233 protected:
234     /**
235      * Create a new CIA.
236      *
237      * @param context the event context
238      */
239     MOS6526(EventScheduler &scheduler);
240 
241     /**
242      * Signal interrupt.
243      *
244      * @param state
245      *            interrupt state
246      */
247     virtual void interrupt(bool state) = 0;
248 
portA()249     virtual void portA() {}
portB()250     virtual void portB() {}
251 
252      /**
253       * Timers can appear on the port.
254       */
255      uint8_t adjustDataPort(uint8_t data);
256 
257     /**
258      * Read CIA register.
259      *
260      * @param addr
261      *            register address to read (lowest 4 bits)
262      */
263     uint8_t read(uint_least8_t addr);
264 
265     /**
266      * Write CIA register.
267      *
268      * @param addr
269      *            register address to write (lowest 4 bits)
270      * @param data
271      *            value to write
272      */
273     void write(uint_least8_t addr, uint8_t data);
274 
275 public:
276     /**
277      * Select chip model.
278      *
279      * @param newModel true for new model 8521, false for old 6526
280      */
281     void setModel(bool newModel);
282 
283     /**
284      * Reset CIA.
285      */
286     virtual void reset();
287 
288     /**
289      * Get the credits.
290      *
291      * @return the credits
292      */
293     static const char *credits();
294 
295     /**
296      * Set day-of-time event occurence of rate.
297      *
298      * @param clock
299      */
setDayOfTimeRate(unsigned int clock)300     void setDayOfTimeRate(unsigned int clock) { tod.setPeriod(clock); }
301 };
302 
303 }
304 
305 #endif // MOS6526_H
306