% This is a very early openMSX design document. The general ideas remain valid
2
% today, but some of the details have changed. Especially the parts of the text
3
% that talk about implementation aspects are outdated.
4
%
5
%TODO:
6
% fully translate in English
7
% fix many(!) spelling errors
8
% corrections/additions
9
10
11
\documentclass[11pt, a4paper]{report}
12
\title{openMSX internals}
13
%Add your name here
14
\author{David Heremans}
15
\author{Wouter Vermaelen}
16
17
\begin{document}
18
\maketitle
19
\tableofcontents
20
21
\chapter{Analysis}
22
23
\section{When does a device need to be emulated}
24
25
A MSX computer has several components: a CPU, audio and video devices and
26
many others. All these devices need to be emulated.
27
28
In a real machine all devices work in parallel all the time. Of cource an
29
emulator can only emulate one device at a time. This creates some problems:
30
31
First problem, some devices need to emulated \textit{constantly}, e.g.
32
\begin{itemize}
33
\item screenrefresh by the VDP
34
\item soundbuffers for soundcards
35
\end{itemize}
36
We can approximate this by emulating those devices very regularly.
37
38
Second problem, devices can communicate with each other. When two emulated
39
devices wants to communicate we must make sure these devices have advanced
40
equally far in time. Example:
41
42
CPU changes by means of I/O-commands the palet settings of the VDP on
43
time T, therefore the VDP must calculate its screen up until time T before
44
the IO command is executed. From that moment on the VDP will use the new
45
palet data when calculating the rest of the screen.
46
47
So we need some sort of synchronization mechanism.
48
49
50
\section{Communication in MSX}
51
52
\subsection{General principles}
53
54
In a MSX communication happens only between the CPU and another device, never
55
between two non-CPUs. All communication is initiated by the CPU, except for
56
interupts. Communication happens at a specific moment in time but is not
57
instantaneous.
58
59
\subsection{(Un)predictable communication}
60
61
Communication can be either predictable or unpredictable:
62
\begin{itemize}
63
\item Predictable communication: Most interupts are predictable, example:
64
65
The VDP knows: 'if my settings remain as they are right now, I will
66
generate a VBLANK interrupt at time T'
67
68
\item Unpredicatble communication, examples:
69
\begin{itemize}
70
\item CPU executes an IO instruction
71
\item CPU executes an instruction who reads from or writes to memory (!)
72
\item an external interrupt (vb RS232)
73
\end{itemize}
74
\end{itemize}
75
76
Note that \emph{most} unpredictable communication is initiated by the CPU, this is
77
important for some optimizations (see next chapters).
78
79
80
\subsection{Communication with multiple devices at once}
81
82
Important example: memory mappers (IO 0xfc \ldots 0xff).
83
84
Writing is not a problem but reading is, since the result is not defined in the MSX
85
standard. But some programs expect a certain behaviour\ldots
86
87
88
\subsection{Non-standard behaviour}
89
90
A device its memory is addressed by slot and address signals. Normally a devices
91
should check both these signals. Some devices (e.g.: ????) ignore slot-select
92
and respond whenever they see their own address.
93
94
95
96
97
\chapter{Design}
98
99
\section{Communication}
100
101
\subsection{Initiated by the CPU}
102
103
The CPU can read or write to a memory location or to an I/O port. The
104
communication happens at a specific moment in time and might take a (short) time
105
to complete. So besides to obvious parameters (adres/port, value) and return
106
values (byte read), we also need to pass and return a timestamp.
107
108
109
\subsection{Initiated by other device (interupts)}
110
111
\subsubsection{General}
112
113
All interupts must be planned beforehand (by synchronization points, see next
114
section). This is easy for predictable interupts, but impossible for
115
unpredictable interupts.
116
117
When a device raises an interupt it must pass control to the emulated CPU before
118
it can further advance in time.
119
120
\subsubsection{Unpredictable interupts}
121
122
There are two possible solutions:
123
\label{unpredic-irq}
124
\begin{enumerate}
125
\item Polling.
126
%%
127
Bij deze oplossing zetten we zelf de SP om de bv 50 HZ T-states.
128
Blijven we bij het voorbeeld van de RS232 dan betekend dat er om de 0.02 seconden
129
gevraagd gaat worden of er een byte klaar staat. Als dit het geval is dan kan de
130
ge-emuleerde RS232 zijn volgende SP plaatsten binnen bv 100 T-States en deze tijd
131
langzaam laten toenemen als blijkt dat er geen byte meer ontvangen is.
132
133
Methode heeft het voordeel dat je zelf een vorm van snelheid begrenzing kunt
134
inbouwen door een apparaat steeds maar een lage hoeveelheid interrupts te laten
135
genereren. Tevens zou op deze manier het mogelijk eenvoudiger zijn om bv een
136
RS232 connectie te faken aan de hand van een file met een 'opgenomen' connectie.
137
Bij beide methodes moet het device er voor zorgen dat het er rekening mee houd
138
dat een geregistreerd SP wel eens tijdens een DI zou kunnen vallen en dat daarom
139
een gesette interrupt niet noodzakelijker wijze word afgehandeld. Dit wil
140
zeggen, er kunnen bv drie SP geregistreerd worden die elk een setInterrupt()
141
uitvoeren maar dat slechts na de derde setInterrupt de CPU deze pas afhandeld!.
142
143
\item Converting van Guest OS interrupts naar MSX interrupts
144
Het zou ook mogelijk zijn om als het guest OS een interrupt krijgt dat deze kan
145
ingelast worden in de datastructuur met de SP, aangezien deze al in staat is om
146
SP's tussen te lassen in de bestaande structuur zou dit geen probleem zijn.
147
\end{enumerate}
148
149
150
\subsection{Communication with multiple devices at once}
151
152
Most important example: memory mappers (IO 0xfc \ldots 0xff)
153
154
%%
155
For this kind of behaviour the most usefull principle would be a master-slave approach
156
In het geval van memmorymappers heb ik dit als volgt reeds geprogrammeert:
157
\begin{itemize}
158
\item De classe MSXMemMapDriver(=master) registreerd zich bij het MSXMotherboard voor de
159
poorten OxFC .. 0xFF.
160
\item De Driver houd per slot/sublsot bij wat de mappergrootte is. Tijdens de initfase
161
vervangt de MapperDriver alle verwijzingen naar hem in de slotstructuur door verwijzingen
162
naar verschillende MemMapSlaves.
163
\item Bij OUT activiteit zal de driver de gepaste slaves vertellen dat ze nu data moeten
164
lezen/schrijven op andere plaatsen in mapper van hun main-/sub-slot combinatie.
165
\item Bij IN activiteit is het de mapper die de waarden kent en de juiste value aan de
166
CPU terug geeft. Op deze manier is het simpel om de pull-up van de Turbo-R te
167
emuleren, of de conflicten van het lezen van meerdere mappers in de driver te
168
implementeren.
169
\end{itemize}
170
171
%%
172
Dit idee ga ik waarschijnlijk ook gebruiken bij de CPU, op die manier heb je een
173
CPU-master en twee CPU-slaves bij de turbo R. De master zal zich dan ook registreren
174
bij het MotherBoard om zo op de CPU-switch poort- van de Turbo-R te kunnen luisteren
175
en de juiste taakdelegatie te kunnen doen.
176
177
178
179
\section{Synchronization}
180
181
\subsection{Time-active and time-passive devices}
182
183
\subsubsection{Time-active devices}
184
This are devices that can request to be emulated at a future moment in time,
185
such a moment is called a \emph{synchronization point}. The reasons for such a request
186
are:
187
\begin{itemize}
188
\item Some devices need to be emulated regularly, e.g.:
189
\begin{itemize}
190
%%
191
\item VDP moet regelmatig zorgen voor beeldopbouw
192
\item soundchips moeten regelmatig nieuwe samples klaarzetten
193
\end{itemize}
194
\item Some devices can raise an IRQ, these IRQ must be announced beforehand
195
with a synchronization point. This is easy for predictable IRQ's, but must be
196
estimated for unpreditable IRQ's (see section \ref{unpredic-irq}).
197
\end{itemize}
198
199
\subsubsection{Time-passive devices}
200
These devices can be emulated at the moment the CPU tries to communicate with
201
it. Example:
202
\begin{itemize}
203
\item Keyboard
204
\item RAM/ROM
205
\end{itemize}
206
207
208
\subsection{Scheduler algorithm}
209
210
A few rules:
211
\begin{enumerate}
212
\item%%
213
geen enkel device mag in uitvoering 'voorliggen' op de CPU.
214
Dit omdat praktisch alle onvoorspelbare communicate (IO en memory) uitgaat
215
van de CPU. Als een device op een bepaald moment voorligt op de CPU en als
216
nadien de CPU tijdens zijn 'inhaalbeweging' communiceerd met dat device, ...
217
ja dan klopt er iets niet.
218
219
\item%%
220
elk device moet zijn eerstvolgende synchronisatie punt
221
aan de scheduler melden.
222
\end{enumerate}
223
224
Now the algorithm:
225
\begin{enumerate}
226
%%
227
\item De scheduler neemt het vroegste synchronisatie punt, en \textit{probeert}
228
de CPU tot op dat punt te brengen.
229
\item Als dit lukt
230
\begin{enumerate}
231
%%
232
\item\label{alg:ok}
233
dan wordt ook het device dat dit synchronisatie punt had gezet tot
234
hier gebracht (dit lukt altijd)
235
\item\label{alg:nok}
236
als het niet lukt (CPU heeft een IO of een memory instructie
237
uitgevoerd). Dan wordt eerst het device waarmee gecommuniceerd
238
werd bijgebracht (nu niet volledig tot aan synchronisatie punt, maar tot
239
waar de CPU gekomen was) en dan wordt pas de IO of memory opdracht
240
uitgevoerd. Het zou kunnen dat het device een (nieuw, vroeger)
241
synchronisatie punt heeft gezet, dus het algoritme begint weer van
242
vanvoor.
243
\end{enumerate}
244
\end{enumerate}
245
246
Note a device may not register a new SP that comes in time before the CPU's
247
current time. (Step \ref{alg:ok}, \ref{alg:nok})
248
249
250
251
252
\chapter{Implementation}
253
254
\section{Timetracking}
255
256
Every device has its own native clock frequency, for the emulation of one
257
particular device it is easy to work with native T-states. But for the
258
global emulator it is easier to work with some least-common-multiple frequency.
259
260
The class 'Emutime' offers such an interface: devices work with native T-states,
261
but they are internally converted to global emulator T-states.
262
263
All operations involving time should go through this class.
264
265
266
\section{The implementation of the scheduler in openMSX}
267
268
The MSXScheduler will keep track of the T-states. This 'scheduler'-devices
269
is 'owned' by the MSXMotherBoard. The MSXScheduler is a generic object
270
that can be reused by other devices. If for example someone would like to
271
make the VDP as a collection of internal devices each withs it's own
272
function and needs some internal scheduler ...
273
274
\subsection{Scheduler and timetracking}
275
276
The MSXScheduler keeps a simple list for timescheduling. This is a simple
277
list of the next SP and the device that has set this SP. The list is
278
ordered so that the first element always is the first SP that has to
279
reached. The scheduler will follow this simple procedure:
280
\begin{enumerate}
281
\item Set the target T-State of the cpu to the first SP in the list.
282
\item Let the CPU execute until the target T-state.
283
\item Get the device from the first SP in the list and let it reach its
284
T-state.
285
\item Remove the first elemnt from the list
286
\item Goto step 1
287
\end{enumerate}
288
289
Attention. The first element of the list in step 1 could be different from
290
the first element in step 3. This is because of the fact that during step
291
2 the device can do some I/O to other device who can set a new SP that has
292
a smaller value then the set target T-state. For example, enabling
293
line-interrupts from the VDP could change the first SP to be reached. For
294
this reasson the scheduler, when inserting a list element before the first
295
element, will call the setTargetTStates of the CPU. this will garuantee
296
that the CPU will execute until the first SP element of the list.
297
298
Note 1: To get the extra time-info during communication between CPU and
299
devices the calls (readMem,writeMem,readIO and writeIO) pas as their
300
latest parameter the current T-state of the CPU. This makes it possible
301
for devices to "catch up" with the processor if needed. So the graph of
302
Wouter could be implicetly included in the emulation by passing
303
the current time around during device communication.
304
Note 2: Also non-msx devices could be integrated in this list. for example
305
the routine that draw the actual screen on your PC could be called from
306
the scheduler. Also you could make a sort of dummy device that would be
307
called each 1/50 second and would block emulation until there is really
308
1/50 second past between it's previous execution and the current one.
309
310
\subsection{Device Sync points}
311
312
Since each device has its own natural clockfrequency the scheduler will
313
run at the speed of the lowest common multiple. In case of an MSX2 the CPU
314
is at 3.5 MHz, the VDP runs at 21 MHz. Therefore the scheduler runs at 21
315
MHz.
316
317
However we could make a MSXDevice that uses a clockfrequency of 42 MHz. In
318
this case the scheduler will need run at 42 MHz, however the CPU still
319
needs to run at 3.5 MHz and not at 7 MHz. To overcomme this problem the
320
scheduler has a method called getTimeMultiplier(nativeFreq). You can pass
321
your native frequency and it will tell you by how much you need to
322
multiply the devices native T-states to get to the frequency of the
323
scheduler.
324
325
In the first case this would result in the value 6, in the 42MHz case this
326
would return 12. If you ask the scheduler for the current T-state you will
327
need to divide them by this value to get to the native T-state value
328
329
The most used SP setting is from "now" until x T-states later. This can be
330
achieved using the method setLaterSP(getTimeMultiplier(nativeFreq) *
331
numberOfNativeTStatesLater , this ).
332
333
334
\section{For interrupts itself}
335
336
If a device generates an interrupt it should call the
337
MSXMotherboard.raiseIRQ() method. when a device doesn't activate
338
the interrupt line any longer it will call the
339
MSXMotherboard.lowerIRQ(). The emulate CPU will after each
340
instruction check the IRQ-line and respond accordingly (EI/DI,
341
interruptmode,...). In the Motherboard the IRQ-line will be
342
implemented using a counter, if irq-counter is zero then no
343
interrupts otherwise the irq-counter should contain the number of
344
devices that are generating an IRQ.
345
346
\emph{A device isn't allowed to call the raise(lower)irq twice in a row!!}
347
348
The MSXDevice classe contains a standard method setInterrupt() and
349
resetInterrupt() which take care of this restriction and call the
350
MSXMotherBoard if needed.
351
352
We could make it mandatory to set the interrupt as an SP. In this way we
353
would only need to test if the IRQ-line is active each time we call the
354
CPU from the scheduler. However for most devices this wouldn't change
355
anything, they still need to emulate up until the SP point and call the
356
raiseIRQ(). Any comments ??
357
358
\chapter{Implementing devices for openMSX}
359
Before you start implementing a new device for openMSX make sure you have read and understand the way openMSX handles eumTime. If your device is able to generate an IRQ you must follow the rules described above.
360
361
\section{The life cycle of an MSXDevice}
362
363
\subsection{In the beginning}
364
In the beginning there was nothing....
365
And then a lot of things happened and after a few billion yeas it all resulted in openMSX. And internally in openMSX also a lot of things need to happen before you get a running emulator.
366
This is what happens before a device becomes aware of its existance.
367
\begin{itemize}
368
\item The main loop creates an MSXConfig object and let it parse a configuration file.
369
\item A MSXMotherBoard is create, this object can be viewed as the bus of the MSX and keep tracks of all the devices and slot layouts.
370
\item The devicefactory creates the objects that are indicate by the MSXConfig object. It loops trough the objects that are summed up in the configuration, each time creating an object of the indicated class and adding to the deviceslist of the motherboard. Each MSXDevice also gets a pointer set towards it personal configuration object. For people who need a more vivid description of what has happended up until now: We just create all the needed chips and PCB's and tossed them all on a big pile.
371
\end{itemize}
372
373
374
The life cycle of an MSXdevice follows the following basic steps
375
\begin{itemize}
376
\item Object is instantiated
377
\item The device reads its configuration and prepares itself to run
378
\item The device waits for calls made by the scheduler/CPU.
379
\item When destroyed the object nicely releases it sources.
380
\end{itemize}
381
382
\subsection{Instantiation}
383
The life cycle starts with the birth of the object.
384
During this constructor fase the MSXDevice can set some of its internal parameters to default values. The general rule being that this initialisation must be fail-proof. Meaning that during this initialization we can not try to malloc some memory or read a file or something else that could go hay-wire. By following this simple rule we can safely asume that the device factory can contruct all of the desired devices with out bailing out.
385
If we need to bail out this will result in the termination of the entire emulator. If for instance the total system memory isn't enough to even create an object.
386
387
\subsection{Initialization}
388
Alsmost imediately after the device is instantiated, the first method that will be called is the setConfigDevice(MSXConfig::Device*). This will allow the MSXDevice to read its own configuration during the following initialization. Remember that the instantiation should be simple and fail-safe. Now it is time to begin the more dangerous internal set up. At this point all needed devices are instantiated, only there configuration isn't complete yet. Now the motherboard start calling the init() method of all of it's known devices.
389
Know you can try to read files, allocated memory and (since all devices are existing) trying some initial communication with other MSXDevices. Be aware howver that this device itself may not have yet been initialized.
390
391
During the initialisation the device should register its place in the slot structure with the MSXMotherBoard. Also registering I/O ports is done at this point.
392
393
One of the reasons to have this method seperate from the instatiation is the fact that later on we could decide that each device should be able to be re-initialized during run time. It would be advicable to write your code so that this is possible. For pointers this would mean that the contructor makes them NULL pointers and that the init should check if the pointers is empty before trying the malloc, and constructs such a like.
394
395
\subsection{Start}
396
One of the first things the device should do in his start method is asking the motherboard for the actual emutime. Remember that it should be possible to insert a device during the emulation. So it isn't smart to assume that the start method will be called when emutime is still zero. This is ofcourse only needed if the device uses time \^\_\^
397
During this start the device sould also set it's synchronisation points if needed. ex. a VDP should register it's first following interrupt.
398
399
\subsection{Stop}
400
\subsection{Reset}
401
402
403
Remark 1 : creating of MSXDevices
404
---------------------------------
405
the sequence in which MSXDevices are used is as follows
406
--if <slotted> are embeded in the <device>-block this device will be visible in the slotstructure of the emulated MSX.Multiple <slotted>blocks are possible f.e. a simple 64KB RAM expansion is visible in one slot,one subslot and the 4 pages of it.
407
--the< parameter> block is parsed and every key,value pair is passed to the setParameter methode. This method is used to set internal parameters of the MSXDevice, f.e. the amount of vram could be passed to a VDPdevice, or the number of mapper pages for a MemMapper.
408
-once athe configuration file is parsed and all devices created, all devices in the deviceslist have their init() method called. In the init() method the devices can allocate desired memmory, register I/O ports using the register\_IO\_Ini/IO\_Out method of the MSXMotherBoard.
409
-at last the virtual MSX is started, the motherboard sends a reeset signal to all devices and starts emulating, if the user requests a reset, the reset method is called for all devices and the MSX comenses al over. The reset method is used for warm-boot, so for example memory should NOT be cleared from most memory devices.