Commit | Line | Data |
---|---|---|
c906108c SS |
1 | This is a loose collection of notes for people hacking on simulators. |
2 | If this document gets big enough it can be prettied up then. | |
3 | ||
4 | Contents | |
5 | ||
6 | - The "common" directory | |
7 | - Common Makefile Support | |
8 | - TAGS support | |
9 | - Generating "configure" files | |
10 | - tconfig.in | |
11 | - C Language Assumptions | |
12 | - "dump" commands under gdb | |
13 | \f | |
14 | The "common" directory | |
15 | ====================== | |
16 | ||
17 | The common directory contains: | |
18 | ||
19 | - common documentation files (e.g. run.1, and maybe in time .texi files) | |
20 | - common source files (e.g. run.c) | |
21 | - common Makefile fragment and configury (e.g. Make-common.in, aclocal.m4). | |
22 | ||
23 | In addition "common" contains portions of the system call support | |
24 | (e.g. callback.c, nltvals.def). | |
25 | ||
26 | Even though no files are built in this directory, it is still configured | |
27 | so support for regenerating nltvals.def is present. | |
28 | \f | |
29 | Common Makefile Support | |
30 | ======================= | |
31 | ||
32 | A common configuration framework is available for simulators that want | |
33 | to use it. The common framework exists to remove a lot of duplication | |
34 | in configure.in and Makefile.in, and it also provides a foundation for | |
35 | enhancing the simulators uniformly (e.g. the more they share in common | |
36 | the easier a feature added to one is added to all). | |
37 | ||
38 | The configure.in of a simulator using the common framework should look like: | |
39 | ||
40 | --- snip --- | |
41 | dnl Process this file with autoconf to produce a configure script. | |
42 | sinclude(../common/aclocal.m4) | |
43 | AC_PREREQ(2.5)dnl | |
44 | AC_INIT(Makefile.in) | |
45 | ||
46 | SIM_AC_COMMON | |
47 | ||
48 | ... target specific additions ... | |
49 | ||
50 | SIM_AC_OUTPUT | |
51 | --- snip --- | |
52 | ||
53 | SIM_AC_COMMON: | |
54 | ||
55 | - invokes the autoconf macros most often used by the simulators | |
56 | - defines --enable/--with options usable by all simulators | |
57 | - initializes sim_link_files/sim_link_links as the set of symbolic links | |
58 | to set up | |
59 | ||
60 | SIM_AC_OUTPUT: | |
61 | ||
62 | - creates the symbolic links defined in sim_link_{files,links} | |
63 | - creates config.h | |
64 | - creates the Makefile | |
65 | ||
66 | The Makefile.in of a simulator using the common framework should look like: | |
67 | ||
68 | --- snip --- | |
69 | # Makefile for blah ... | |
70 | # Copyright blah ... | |
71 | ||
72 | ## COMMON_PRE_CONFIG_FRAG | |
73 | ||
74 | # These variables are given default values in COMMON_PRE_CONFIG_FRAG. | |
75 | # We override the ones we need to here. | |
76 | # Not all of these need to be mentioned, only the necessary ones. | |
77 | # In fact it is better to *not* mention ones if the value is the default. | |
78 | ||
79 | # List of object files, less common parts. | |
80 | SIM_OBJS = | |
81 | # List of extra dependencies. | |
82 | # Generally this consists of simulator specific files included by sim-main.h. | |
83 | SIM_EXTRA_DEPS = | |
84 | # List of flags to always pass to $(CC). | |
85 | SIM_EXTRA_CFLAGS = | |
86 | # List of extra libraries to link with. | |
87 | SIM_EXTRA_LIBS = | |
88 | # List of extra program dependencies. | |
89 | SIM_EXTRA_LIBDEPS = | |
90 | # List of main object files for `run'. | |
91 | SIM_RUN_OBJS = run.o | |
92 | # Dependency of `all' to build any extra files. | |
93 | SIM_EXTRA_ALL = | |
94 | # Dependency of `install' to install any extra files. | |
95 | SIM_EXTRA_INSTALL = | |
96 | # Dependency of `clean' to clean any extra files. | |
97 | SIM_EXTRA_CLEAN = | |
98 | ||
99 | ## COMMON_POST_CONFIG_FRAG | |
100 | ||
101 | # Rules need to build $(SIM_OBJS), plus whatever else the target wants. | |
102 | ||
103 | ... target specific rules ... | |
104 | --- snip --- | |
105 | ||
106 | COMMON_{PRE,POST}_CONFIG_FRAG are markers for SIM_AC_OUTPUT to tell it | |
107 | where to insert the two pieces of common/Make-common.in. | |
108 | The resulting Makefile is created by doing autoconf substitions on | |
109 | both the target's Makefile.in and Make-common.in, and inserting | |
110 | the two pieces of Make-common.in into the target's Makefile.in at | |
111 | COMMON_{PRE,POST}_CONFIG_FRAG. | |
112 | ||
113 | Note that SIM_EXTRA_{INSTALL,CLEAN} could be removed and "::" targets | |
114 | could be used instead. However, it's not clear yet whether "::" targets | |
115 | are portable enough. | |
116 | \f | |
117 | TAGS support | |
118 | ============ | |
119 | ||
120 | Many files generate program symbols at compile time. | |
121 | Such symbols can't be found with grep nor do they normally appear in | |
122 | the TAGS file. To get around this, source files can add the comment | |
123 | ||
124 | /* TAGS: foo1 foo2 */ | |
125 | ||
126 | where foo1, foo2 are program symbols. Symbols found in such comments | |
127 | are greppable and appear in the TAGS file. | |
128 | \f | |
129 | Generating "configure" files | |
130 | ============================ | |
131 | ||
132 | For targets using the common framework, "configure" can be generated | |
133 | by running `autoconf'. | |
134 | ||
135 | To regenerate the configure files for all targets using the common framework: | |
136 | ||
137 | $ cd devo/sim | |
138 | $ make -f Makefile.in SHELL=/bin/sh autoconf-common | |
139 | ||
140 | To add a change-log entry to the ChangeLog file for each updated | |
141 | directory (WARNING - check the modified new-ChangeLog files before | |
142 | renaming): | |
143 | ||
144 | $ make -f Makefile.in SHELL=/bin/sh autoconf-changelog | |
145 | $ more */new-ChangeLog | |
146 | $ make -f Makefile.in SHELL=/bin/sh autoconf-install | |
147 | ||
148 | In a similar vein, both the configure and config.in files can be | |
149 | updated using the sequence: | |
150 | ||
151 | $ cd devo/sim | |
152 | $ make -f Makefile.in SHELL=/bin/sh autoheader-common | |
153 | $ make -f Makefile.in SHELL=/bin/sh autoheader-changelog | |
154 | $ more */new-ChangeLog | |
155 | $ make -f Makefile.in SHELL=/bin/sh autoheader-install | |
c93abbcc AC |
156 | |
157 | To add the entries to an alternative ChangeLog file, use: | |
158 | ||
159 | $ make ChangeLog=MyChangeLog .... | |
160 | ||
c906108c SS |
161 | \f |
162 | tconfig.in | |
163 | ========== | |
164 | ||
165 | File tconfig.in defines one or more target configuration macros | |
166 | (e.g. a tm.h file). There are very few that need defining. | |
167 | For a list of all of them, see common/tconfig.in. | |
168 | It contains them all, commented out. | |
169 | The intent is that a new port can just copy this file and | |
170 | define the ones it needs. | |
171 | \f | |
172 | C Language Assumptions | |
173 | ====================== | |
174 | ||
175 | The programmer may assume that the simulator is being built using an | |
176 | ANSI C compiler that supports a 64 bit data type. Consequently: | |
177 | ||
bdca5ee4 | 178 | o prototypes can be used |
c906108c SS |
179 | |
180 | o If sim-types.h is included, the two | |
181 | types signed64 and unsigned64 are | |
182 | available. | |
183 | ||
184 | o The type `unsigned' is valid. | |
185 | ||
186 | However, the user should be aware of the following: | |
187 | ||
188 | o GCC's `<number>LL' is NOT acceptable. | |
189 | Microsoft-C doesn't reconize it. | |
190 | ||
191 | o MSC's `<number>i64' is NOT acceptable. | |
192 | GCC doesn't reconize it. | |
193 | ||
194 | o GCC's `long long' MSC's `_int64' can | |
195 | NOT be used to define 64 bit integer data | |
196 | types. | |
197 | ||
198 | o An empty array (eg int a[0]) is not valid. | |
199 | ||
200 | When building with GCC it is effectivly a requirement that | |
201 | --enable-build-warnings=,-Werror be specified during configuration. | |
202 | \f | |
203 | "dump" commands under gdb | |
204 | ========================= | |
205 | ||
206 | gdbinit.in contains the following | |
207 | ||
208 | define dump | |
209 | set sim_debug_dump () | |
210 | end | |
211 | ||
212 | Simulators that define the sim_debug_dump function can then have their | |
213 | internal state pretty printed from gdb. | |
214 | ||
215 | FIXME: This can obviously be made more elaborate. As needed it will be. | |
216 | \f | |
217 | Rebuilding nltvals.def | |
218 | ====================== | |
219 | ||
220 | Checkout a copy of the SIM and LIBGLOSS modules (Unless you've already | |
221 | got one to hand): | |
222 | ||
223 | $ mkdir /tmp/$$ | |
224 | $ cd /tmp/$$ | |
225 | $ cvs checkout sim-no-testsuite libgloss-no-testsuite newlib-no-testsuite | |
226 | ||
227 | Configure things for an arbitrary simulator target (I've d10v for | |
228 | convenience): | |
229 | ||
230 | $ mkdir /tmp/$$/build | |
231 | $ cd /tmp/$$/build | |
232 | $ /tmp/$$/devo/configure --target=d10v-elf | |
233 | ||
234 | In the sim/common directory rebuild the headers: | |
235 | ||
236 | $ cd sim/common | |
237 | $ make headers | |
238 | ||
239 | To add a new target: | |
240 | ||
241 | devo/sim/common/gennltvals.sh | |
242 | ||
243 | Add your new processor target (you'll need to grub | |
244 | around to find where your syscall.h lives). | |
245 | ||
246 | devo/sim/<processor>/Makefile.in | |
247 | ||
248 | Add the definition: | |
249 | ||
250 | ``NL_TARGET = -DNL_TARGET_d10v'' | |
251 | ||
252 | just before the line COMMON_POST_CONFIG_FRAG. | |
253 | ||
254 | devo/sim/<processor>/*.[ch] | |
255 | ||
256 | Include targ-vals.h instead of syscall.h. | |
aba193a5 MF |
257 | \f |
258 | Tracing | |
259 | ======= | |
260 | ||
261 | For ports based on CGEN, tracing instrumentation should largely be for free, | |
262 | so we will cover the basic non-CGEN setup here. The assumption is that your | |
263 | target is using the common autoconf macros and so the build system already | |
264 | includes the sim-trace configure flag. | |
265 | ||
266 | The full tracing API is covered in sim-trace.h, so this section is an overview. | |
267 | ||
268 | Before calling any trace function, you should make a call to the trace_prefix() | |
269 | function. This is usually done in the main sim_engine_run() loop before | |
270 | simulating the next instruction. You should make this call before every | |
271 | simulated insn. You can probably copy & paste this: | |
272 | if (TRACE_ANY_P (cpu)) | |
273 | trace_prefix (sd, cpu, NULL_CIA, oldpc, TRACE_LINENUM_P (cpu), NULL, 0, ""); | |
274 | ||
275 | You will then need to instrument your simulator code with calls to the | |
276 | trace_generic() function with the appropriate trace index. Typically, this | |
277 | will take a form similar to the above snippet. So to trace instructions, you | |
278 | would use something like: | |
279 | if (TRACE_INSN_P (cpu)) | |
280 | trace_generic (sd, cpu, TRACE_INSN_IDX, "NOP;"); | |
281 | ||
282 | The exact output format is up to you. See the trace index enum in sim-trace.h | |
283 | to see the different tracing info available. | |
284 | ||
285 | To utilize the tracing features at runtime, simply use the --trace-xxx flags. | |
286 | run --trace-insn ./some-program | |
287 | \f | |
288 | Profiling | |
289 | ========= | |
290 | ||
291 | Similar to the tracing section, this is merely an overview for non-CGEN based | |
292 | ports. The full API may be found in sim-profile.h. Its API is also similar | |
293 | to the tracing API. | |
294 | ||
295 | Note that unlike the tracing command line options, in addition to the profile | |
296 | flags, you have to use the --verbose option to view the summary report after | |
297 | execution. Tracing output is displayed on the fly, but the profile output is | |
298 | only summarized. | |
299 | ||
300 | To profile core accesses (such as data reads/writes and insn fetches), add | |
301 | calls to PROFILE_COUNT_CORE() to your read/write functions. So in your data | |
302 | fetch function, you'd use something like: | |
303 | PROFILE_COUNT_CORE (cpu, target_addr, size_in_bytes, map_read); | |
304 | Then in your data write function: | |
305 | PROFILE_COUNT_CORE (cpu, target_addr, size_in_bytes, map_write); | |
306 | And in your insn fetcher: | |
307 | PROFILE_COUNT_CORE (cpu, target_addr, size_in_bytes, map_exec); | |
308 | ||
309 | To use the PC profiling code, you simply have to tell the system where to find | |
310 | your simulator's PC and its size. So in your sim_open() function: | |
311 | STATE_WATCHPOINTS (sd)->pc = address_of_cpu0_pc; | |
312 | STATE_WATCHPOINTS (sd)->sizeof_pc = number_of_bytes_for_pc_storage; | |
313 | In a typical 32bit system, the sizeof_pc will be 4 bytes. | |
314 | ||
315 | To profile branches, in every location where a branch insn is executed, call | |
316 | one of the related helpers: | |
317 | PROFILE_BRANCH_TAKEN (cpu); | |
318 | PROFILE_BRANCH_UNTAKEN (cpu); | |
319 | If you have stall information, you can utilize the other helpers too. | |
320 | \f | |
321 | Environment Simulation | |
322 | ====================== | |
323 | ||
324 | The simplest simulator doesn't include environment support -- it merely | |
325 | simulates the Instruction Set Architecture (ISA). Once you're ready to move | |
326 | on to the next level, call the common macro in your configure.ac: | |
327 | SIM_AC_OPTION_ENVIRONMENT | |
328 | ||
329 | This will support for the user, virtual, and operating environments. See the | |
330 | sim-config.h header for a more detailed description of them. The former are | |
331 | pretty straight forward as things like exceptions (making system calls) are | |
332 | handled in the simulator. Which is to say, an exception does not trigger an | |
333 | exception handler in the simulator target -- that is what the operating env | |
334 | is about. See the following userspace section for more information. | |
335 | \f | |
336 | Userspace System Calls | |
337 | ====================== | |
338 | ||
339 | By default, the libgloss userspace is simulated. That means the system call | |
340 | numbers and calling convention matches that of libgloss. Simulating other | |
341 | userspaces (such as Linux) is pretty straightforward, but let's first focus | |
342 | on the basics. The basic API is covered in include/gdb/callback.h. | |
343 | ||
344 | When an instruction is simulated that invokes the system call method (such as | |
345 | forcing a hardware trap or exception), your simulator code should set up the | |
346 | CB_SYSCALL data structure before calling the common cb_syscall() function. | |
347 | For example: | |
348 | static int | |
349 | syscall_read_mem (host_callback *cb, struct cb_syscall *sc, | |
350 | unsigned long taddr, char *buf, int bytes) | |
351 | { | |
352 | SIM_DESC sd = (SIM_DESC) sc->p1; | |
353 | SIM_CPU *cpu = (SIM_CPU *) sc->p2; | |
354 | return sim_core_read_buffer (sd, cpu, read_map, buf, taddr, bytes); | |
355 | } | |
356 | static int | |
357 | syscall_write_mem (host_callback *cb, struct cb_syscall *sc, | |
358 | unsigned long taddr, const char *buf, int bytes) | |
359 | { | |
360 | SIM_DESC sd = (SIM_DESC) sc->p1; | |
361 | SIM_CPU *cpu = (SIM_CPU *) sc->p2; | |
362 | return sim_core_write_buffer (sd, cpu, write_map, buf, taddr, bytes); | |
363 | } | |
364 | void target_sim_syscall (SIM_CPU *cpu) | |
365 | { | |
366 | SIM_DESC sd = CPU_STATE (cpu); | |
367 | host_callback *cb = STATE_CALLBACK (sd); | |
368 | CB_SYSCALL sc; | |
369 | ||
370 | CB_SYSCALL_INIT (&sc); | |
371 | ||
372 | sc.func = <fetch system call number>; | |
373 | sc.arg1 = <fetch first system call argument>; | |
374 | sc.arg2 = <fetch second system call argument>; | |
375 | sc.arg3 = <fetch third system call argument>; | |
376 | sc.arg4 = <fetch fourth system call argument>; | |
377 | sc.p1 = (PTR) sd; | |
378 | sc.p2 = (PTR) cpu; | |
379 | sc.read_mem = syscall_read_mem; | |
380 | sc.write_mem = syscall_write_mem; | |
381 | ||
382 | cb_syscall (cb, &sc); | |
383 | ||
384 | <store system call result from sc.result>; | |
385 | <store system call error from sc.errcode>; | |
386 | } | |
387 | Some targets store the result and error code in different places, while others | |
388 | only store the error code when the result is an error. | |
389 | ||
390 | Keep in mind that the CB_SYS_xxx defines are normalized values with no real | |
391 | meaning with respect to the target. They provide a unique map on the host so | |
392 | that it can parse things sanely. For libgloss, the common/nltvals.def file | |
393 | creates the target's system call numbers to the CB_SYS_xxx values. | |
394 | ||
395 | To simulate other userspace targets, you really only need to update the maps | |
396 | pointers that are part of the callback interface. So create CB_TARGET_DEFS_MAP | |
397 | arrays for each set (system calls, errnos, open bits, etc...) and in a place | |
398 | you find useful, do something like: | |
399 | ||
400 | ... | |
401 | static CB_TARGET_DEFS_MAP cb_linux_syscall_map[] = { | |
402 | # define TARGET_LINUX_SYS_open 5 | |
403 | { CB_SYS_open, TARGET_LINUX_SYS_open }, | |
404 | ... | |
405 | { -1, -1 }, | |
406 | }; | |
407 | ... | |
408 | host_callback *cb = STATE_CALLBACK (sd); | |
409 | cb->syscall_map = cb_linux_syscall_map; | |
410 | cb->errno_map = cb_linux_errno_map; | |
411 | cb->open_map = cb_linux_open_map; | |
412 | cb->signal_map = cb_linux_signal_map; | |
413 | cb->stat_map = cb_linux_stat_map; | |
414 | ... | |
415 | ||
416 | Each of these cb_linux_*_map's are manually declared by the arch target. | |
417 | ||
418 | The target_sim_syscall() example above will then work unchanged (ignoring the | |
419 | system call convention) because all of the callback functions go through these | |
420 | mapping arrays. | |
421 | \f | |
422 | Events | |
423 | ====== | |
424 | ||
425 | Events are scheduled and executed on behalf of either a cpu or hardware devices. | |
426 | The API is pretty much the same and can be found in common/sim-events.h and | |
427 | common/hw-events.h. | |
428 | ||
429 | For simulator targets, you really just have to worry about the schedule and | |
430 | deschedule functions. | |
431 | \f | |
432 | Device Trees | |
433 | ============ | |
434 | ||
435 | The device tree model is based on the OpenBoot specification. Since this is | |
436 | largely inherited from the psim code, consult the existing psim documentation | |
437 | for some in-depth details. | |
438 | http://sourceware.org/psim/manual/ | |
439 | \f | |
440 | Hardware Devices | |
441 | ================ | |
442 | ||
443 | The simplest simulator doesn't include hardware device support. Once you're | |
444 | ready to move on to the next level, call the common macro in your configure.ac: | |
445 | SIM_AC_OPTION_HARDWARE(yes,,devone devtwo devthree) | |
446 | ||
447 | The basic hardware API is documented in common/hw-device.h. | |
448 | ||
449 | Each device has to have a matching file name with a "dv-" prefix. So there has | |
450 | to be a dv-devone.c, dv-devtwo.c, and dv-devthree.c files. Further, each file | |
451 | has to have a matching hw_descriptor structure. So the dv-devone.c file has to | |
452 | have something like: | |
453 | const struct hw_descriptor dv_devone_descriptor[] = { | |
454 | {"devone", devone_finish,}, | |
455 | {NULL, NULL}, | |
456 | }; | |
457 | ||
458 | The "devone" string as well as the "devone_finish" function are not hard | |
459 | requirements, just common conventions. The structure name is a hard | |
460 | requirement. | |
461 | ||
462 | The devone_finish() callback function is used to instantiate this device by | |
463 | parsing the corresponding properties in the device tree. | |
464 | ||
465 | Hardware devices typically attach address ranges to themselves. Then when | |
466 | accesses to those addresses are made, the hardware will have its callback | |
467 | invoked. The exact callback could be a normal I/O read/write access, as | |
468 | well as a DMA access. This makes it easy to simulate memory mapped registers. | |
469 | ||
470 | Keep in mind that like a proper device driver, it may be instantiated many | |
471 | times over. So any device state it needs to be maintained should be allocated | |
472 | during the finish callback and attached to the hardware device via set_hw_data. | |
473 | Any hardware functions can access this private data via the hw_data function. | |
474 | \f | |
475 | Ports (Interrupts / IRQs) | |
476 | ========================= | |
c906108c | 477 | |
aba193a5 MF |
478 | First, a note on terminology. A "port" is an aspect of a hardware device that |
479 | accepts or generates interrupts. So devices with input ports may be the target | |
480 | of an interrupt (accept it), and/or they have output ports so that they may be | |
481 | the source of an interrupt (generate it). | |
482 | ||
483 | Each port has a symbolic name and a unique number. These are used to identify | |
484 | the port in different contexts. The output port name has no hard relationship | |
485 | to the input port name (same for the unique number). The callback that accepts | |
486 | the interrupt uses the name/id of its input port, while the generator function | |
487 | uses the name/id of its output port. | |
488 | ||
489 | The device tree is used to connect the output port of a device to the input | |
490 | port of another device. There are no limits on the number of inputs connected | |
491 | to an output, or outputs to an input, or the devices attached to the ports. | |
492 | In other words, the input port and output port could be the same device. | |
493 | ||
494 | The basics are: | |
495 | - each hardware device declares an array of ports (hw_port_descriptor). | |
496 | any mix of input and output ports is allowed. | |
497 | - when setting up the device, attach the array (set_hw_ports). | |
498 | - if the device accepts interrupts, it will have to attach a port callback | |
499 | function (set_hw_port_event) | |
500 | - connect ports with the device tree | |
501 | - handle incoming interrupts with the callback | |
502 | - generate outgoing interrupts with hw_port_event |