| 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 |
| 156 | |
| 157 | To add the entries to an alternative ChangeLog file, use: |
| 158 | |
| 159 | $ make ChangeLog=MyChangeLog .... |
| 160 | |
| 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 | |
| 178 | o prototypes can be used (although using |
| 179 | PARAMS() and K&R declarations wouldn't |
| 180 | go astray). |
| 181 | |
| 182 | o If sim-types.h is included, the two |
| 183 | types signed64 and unsigned64 are |
| 184 | available. |
| 185 | |
| 186 | o The type `unsigned' is valid. |
| 187 | |
| 188 | However, the user should be aware of the following: |
| 189 | |
| 190 | o GCC's `<number>LL' is NOT acceptable. |
| 191 | Microsoft-C doesn't reconize it. |
| 192 | |
| 193 | o MSC's `<number>i64' is NOT acceptable. |
| 194 | GCC doesn't reconize it. |
| 195 | |
| 196 | o GCC's `long long' MSC's `_int64' can |
| 197 | NOT be used to define 64 bit integer data |
| 198 | types. |
| 199 | |
| 200 | o An empty array (eg int a[0]) is not valid. |
| 201 | |
| 202 | When building with GCC it is effectivly a requirement that |
| 203 | --enable-build-warnings=,-Werror be specified during configuration. |
| 204 | \f |
| 205 | "dump" commands under gdb |
| 206 | ========================= |
| 207 | |
| 208 | gdbinit.in contains the following |
| 209 | |
| 210 | define dump |
| 211 | set sim_debug_dump () |
| 212 | end |
| 213 | |
| 214 | Simulators that define the sim_debug_dump function can then have their |
| 215 | internal state pretty printed from gdb. |
| 216 | |
| 217 | FIXME: This can obviously be made more elaborate. As needed it will be. |
| 218 | \f |
| 219 | Rebuilding nltvals.def |
| 220 | ====================== |
| 221 | |
| 222 | Checkout a copy of the SIM and LIBGLOSS modules (Unless you've already |
| 223 | got one to hand): |
| 224 | |
| 225 | $ mkdir /tmp/$$ |
| 226 | $ cd /tmp/$$ |
| 227 | $ cvs checkout sim-no-testsuite libgloss-no-testsuite newlib-no-testsuite |
| 228 | |
| 229 | Configure things for an arbitrary simulator target (I've d10v for |
| 230 | convenience): |
| 231 | |
| 232 | $ mkdir /tmp/$$/build |
| 233 | $ cd /tmp/$$/build |
| 234 | $ /tmp/$$/devo/configure --target=d10v-elf |
| 235 | |
| 236 | In the sim/common directory rebuild the headers: |
| 237 | |
| 238 | $ cd sim/common |
| 239 | $ make headers |
| 240 | |
| 241 | To add a new target: |
| 242 | |
| 243 | devo/sim/common/gennltvals.sh |
| 244 | |
| 245 | Add your new processor target (you'll need to grub |
| 246 | around to find where your syscall.h lives). |
| 247 | |
| 248 | devo/sim/<processor>/Makefile.in |
| 249 | |
| 250 | Add the definition: |
| 251 | |
| 252 | ``NL_TARGET = -DNL_TARGET_d10v'' |
| 253 | |
| 254 | just before the line COMMON_POST_CONFIG_FRAG. |
| 255 | |
| 256 | devo/sim/<processor>/*.[ch] |
| 257 | |
| 258 | Include targ-vals.h instead of syscall.h. |
| 259 | \f |
| 260 | Tracing |
| 261 | ======= |
| 262 | |
| 263 | For ports based on CGEN, tracing instrumentation should largely be for free, |
| 264 | so we will cover the basic non-CGEN setup here. The assumption is that your |
| 265 | target is using the common autoconf macros and so the build system already |
| 266 | includes the sim-trace configure flag. |
| 267 | |
| 268 | The full tracing API is covered in sim-trace.h, so this section is an overview. |
| 269 | |
| 270 | Before calling any trace function, you should make a call to the trace_prefix() |
| 271 | function. This is usually done in the main sim_engine_run() loop before |
| 272 | simulating the next instruction. You should make this call before every |
| 273 | simulated insn. You can probably copy & paste this: |
| 274 | if (TRACE_ANY_P (cpu)) |
| 275 | trace_prefix (sd, cpu, NULL_CIA, oldpc, TRACE_LINENUM_P (cpu), NULL, 0, ""); |
| 276 | |
| 277 | You will then need to instrument your simulator code with calls to the |
| 278 | trace_generic() function with the appropriate trace index. Typically, this |
| 279 | will take a form similar to the above snippet. So to trace instructions, you |
| 280 | would use something like: |
| 281 | if (TRACE_INSN_P (cpu)) |
| 282 | trace_generic (sd, cpu, TRACE_INSN_IDX, "NOP;"); |
| 283 | |
| 284 | The exact output format is up to you. See the trace index enum in sim-trace.h |
| 285 | to see the different tracing info available. |
| 286 | |
| 287 | To utilize the tracing features at runtime, simply use the --trace-xxx flags. |
| 288 | run --trace-insn ./some-program |
| 289 | \f |
| 290 | Profiling |
| 291 | ========= |
| 292 | |
| 293 | Similar to the tracing section, this is merely an overview for non-CGEN based |
| 294 | ports. The full API may be found in sim-profile.h. Its API is also similar |
| 295 | to the tracing API. |
| 296 | |
| 297 | Note that unlike the tracing command line options, in addition to the profile |
| 298 | flags, you have to use the --verbose option to view the summary report after |
| 299 | execution. Tracing output is displayed on the fly, but the profile output is |
| 300 | only summarized. |
| 301 | |
| 302 | To profile core accesses (such as data reads/writes and insn fetches), add |
| 303 | calls to PROFILE_COUNT_CORE() to your read/write functions. So in your data |
| 304 | fetch function, you'd use something like: |
| 305 | PROFILE_COUNT_CORE (cpu, target_addr, size_in_bytes, map_read); |
| 306 | Then in your data write function: |
| 307 | PROFILE_COUNT_CORE (cpu, target_addr, size_in_bytes, map_write); |
| 308 | And in your insn fetcher: |
| 309 | PROFILE_COUNT_CORE (cpu, target_addr, size_in_bytes, map_exec); |
| 310 | |
| 311 | To use the PC profiling code, you simply have to tell the system where to find |
| 312 | your simulator's PC and its size. So in your sim_open() function: |
| 313 | STATE_WATCHPOINTS (sd)->pc = address_of_cpu0_pc; |
| 314 | STATE_WATCHPOINTS (sd)->sizeof_pc = number_of_bytes_for_pc_storage; |
| 315 | In a typical 32bit system, the sizeof_pc will be 4 bytes. |
| 316 | |
| 317 | To profile branches, in every location where a branch insn is executed, call |
| 318 | one of the related helpers: |
| 319 | PROFILE_BRANCH_TAKEN (cpu); |
| 320 | PROFILE_BRANCH_UNTAKEN (cpu); |
| 321 | If you have stall information, you can utilize the other helpers too. |
| 322 | \f |
| 323 | Environment Simulation |
| 324 | ====================== |
| 325 | |
| 326 | The simplest simulator doesn't include environment support -- it merely |
| 327 | simulates the Instruction Set Architecture (ISA). Once you're ready to move |
| 328 | on to the next level, call the common macro in your configure.ac: |
| 329 | SIM_AC_OPTION_ENVIRONMENT |
| 330 | |
| 331 | This will support for the user, virtual, and operating environments. See the |
| 332 | sim-config.h header for a more detailed description of them. The former are |
| 333 | pretty straight forward as things like exceptions (making system calls) are |
| 334 | handled in the simulator. Which is to say, an exception does not trigger an |
| 335 | exception handler in the simulator target -- that is what the operating env |
| 336 | is about. See the following userspace section for more information. |
| 337 | \f |
| 338 | Userspace System Calls |
| 339 | ====================== |
| 340 | |
| 341 | By default, the libgloss userspace is simulated. That means the system call |
| 342 | numbers and calling convention matches that of libgloss. Simulating other |
| 343 | userspaces (such as Linux) is pretty straightforward, but let's first focus |
| 344 | on the basics. The basic API is covered in include/gdb/callback.h. |
| 345 | |
| 346 | When an instruction is simulated that invokes the system call method (such as |
| 347 | forcing a hardware trap or exception), your simulator code should set up the |
| 348 | CB_SYSCALL data structure before calling the common cb_syscall() function. |
| 349 | For example: |
| 350 | static int |
| 351 | syscall_read_mem (host_callback *cb, struct cb_syscall *sc, |
| 352 | unsigned long taddr, char *buf, int bytes) |
| 353 | { |
| 354 | SIM_DESC sd = (SIM_DESC) sc->p1; |
| 355 | SIM_CPU *cpu = (SIM_CPU *) sc->p2; |
| 356 | return sim_core_read_buffer (sd, cpu, read_map, buf, taddr, bytes); |
| 357 | } |
| 358 | static int |
| 359 | syscall_write_mem (host_callback *cb, struct cb_syscall *sc, |
| 360 | unsigned long taddr, const char *buf, int bytes) |
| 361 | { |
| 362 | SIM_DESC sd = (SIM_DESC) sc->p1; |
| 363 | SIM_CPU *cpu = (SIM_CPU *) sc->p2; |
| 364 | return sim_core_write_buffer (sd, cpu, write_map, buf, taddr, bytes); |
| 365 | } |
| 366 | void target_sim_syscall (SIM_CPU *cpu) |
| 367 | { |
| 368 | SIM_DESC sd = CPU_STATE (cpu); |
| 369 | host_callback *cb = STATE_CALLBACK (sd); |
| 370 | CB_SYSCALL sc; |
| 371 | |
| 372 | CB_SYSCALL_INIT (&sc); |
| 373 | |
| 374 | sc.func = <fetch system call number>; |
| 375 | sc.arg1 = <fetch first system call argument>; |
| 376 | sc.arg2 = <fetch second system call argument>; |
| 377 | sc.arg3 = <fetch third system call argument>; |
| 378 | sc.arg4 = <fetch fourth system call argument>; |
| 379 | sc.p1 = (PTR) sd; |
| 380 | sc.p2 = (PTR) cpu; |
| 381 | sc.read_mem = syscall_read_mem; |
| 382 | sc.write_mem = syscall_write_mem; |
| 383 | |
| 384 | cb_syscall (cb, &sc); |
| 385 | |
| 386 | <store system call result from sc.result>; |
| 387 | <store system call error from sc.errcode>; |
| 388 | } |
| 389 | Some targets store the result and error code in different places, while others |
| 390 | only store the error code when the result is an error. |
| 391 | |
| 392 | Keep in mind that the CB_SYS_xxx defines are normalized values with no real |
| 393 | meaning with respect to the target. They provide a unique map on the host so |
| 394 | that it can parse things sanely. For libgloss, the common/nltvals.def file |
| 395 | creates the target's system call numbers to the CB_SYS_xxx values. |
| 396 | |
| 397 | To simulate other userspace targets, you really only need to update the maps |
| 398 | pointers that are part of the callback interface. So create CB_TARGET_DEFS_MAP |
| 399 | arrays for each set (system calls, errnos, open bits, etc...) and in a place |
| 400 | you find useful, do something like: |
| 401 | |
| 402 | ... |
| 403 | static CB_TARGET_DEFS_MAP cb_linux_syscall_map[] = { |
| 404 | # define TARGET_LINUX_SYS_open 5 |
| 405 | { CB_SYS_open, TARGET_LINUX_SYS_open }, |
| 406 | ... |
| 407 | { -1, -1 }, |
| 408 | }; |
| 409 | ... |
| 410 | host_callback *cb = STATE_CALLBACK (sd); |
| 411 | cb->syscall_map = cb_linux_syscall_map; |
| 412 | cb->errno_map = cb_linux_errno_map; |
| 413 | cb->open_map = cb_linux_open_map; |
| 414 | cb->signal_map = cb_linux_signal_map; |
| 415 | cb->stat_map = cb_linux_stat_map; |
| 416 | ... |
| 417 | |
| 418 | Each of these cb_linux_*_map's are manually declared by the arch target. |
| 419 | |
| 420 | The target_sim_syscall() example above will then work unchanged (ignoring the |
| 421 | system call convention) because all of the callback functions go through these |
| 422 | mapping arrays. |
| 423 | \f |
| 424 | Events |
| 425 | ====== |
| 426 | |
| 427 | Events are scheduled and executed on behalf of either a cpu or hardware devices. |
| 428 | The API is pretty much the same and can be found in common/sim-events.h and |
| 429 | common/hw-events.h. |
| 430 | |
| 431 | For simulator targets, you really just have to worry about the schedule and |
| 432 | deschedule functions. |
| 433 | \f |
| 434 | Device Trees |
| 435 | ============ |
| 436 | |
| 437 | The device tree model is based on the OpenBoot specification. Since this is |
| 438 | largely inherited from the psim code, consult the existing psim documentation |
| 439 | for some in-depth details. |
| 440 | http://sourceware.org/psim/manual/ |
| 441 | \f |
| 442 | Hardware Devices |
| 443 | ================ |
| 444 | |
| 445 | The simplest simulator doesn't include hardware device support. Once you're |
| 446 | ready to move on to the next level, call the common macro in your configure.ac: |
| 447 | SIM_AC_OPTION_HARDWARE(yes,,devone devtwo devthree) |
| 448 | |
| 449 | The basic hardware API is documented in common/hw-device.h. |
| 450 | |
| 451 | Each device has to have a matching file name with a "dv-" prefix. So there has |
| 452 | to be a dv-devone.c, dv-devtwo.c, and dv-devthree.c files. Further, each file |
| 453 | has to have a matching hw_descriptor structure. So the dv-devone.c file has to |
| 454 | have something like: |
| 455 | const struct hw_descriptor dv_devone_descriptor[] = { |
| 456 | {"devone", devone_finish,}, |
| 457 | {NULL, NULL}, |
| 458 | }; |
| 459 | |
| 460 | The "devone" string as well as the "devone_finish" function are not hard |
| 461 | requirements, just common conventions. The structure name is a hard |
| 462 | requirement. |
| 463 | |
| 464 | The devone_finish() callback function is used to instantiate this device by |
| 465 | parsing the corresponding properties in the device tree. |
| 466 | |
| 467 | Hardware devices typically attach address ranges to themselves. Then when |
| 468 | accesses to those addresses are made, the hardware will have its callback |
| 469 | invoked. The exact callback could be a normal I/O read/write access, as |
| 470 | well as a DMA access. This makes it easy to simulate memory mapped registers. |
| 471 | |
| 472 | Keep in mind that like a proper device driver, it may be instantiated many |
| 473 | times over. So any device state it needs to be maintained should be allocated |
| 474 | during the finish callback and attached to the hardware device via set_hw_data. |
| 475 | Any hardware functions can access this private data via the hw_data function. |
| 476 | \f |
| 477 | Ports (Interrupts / IRQs) |
| 478 | ========================= |
| 479 | |
| 480 | First, a note on terminology. A "port" is an aspect of a hardware device that |
| 481 | accepts or generates interrupts. So devices with input ports may be the target |
| 482 | of an interrupt (accept it), and/or they have output ports so that they may be |
| 483 | the source of an interrupt (generate it). |
| 484 | |
| 485 | Each port has a symbolic name and a unique number. These are used to identify |
| 486 | the port in different contexts. The output port name has no hard relationship |
| 487 | to the input port name (same for the unique number). The callback that accepts |
| 488 | the interrupt uses the name/id of its input port, while the generator function |
| 489 | uses the name/id of its output port. |
| 490 | |
| 491 | The device tree is used to connect the output port of a device to the input |
| 492 | port of another device. There are no limits on the number of inputs connected |
| 493 | to an output, or outputs to an input, or the devices attached to the ports. |
| 494 | In other words, the input port and output port could be the same device. |
| 495 | |
| 496 | The basics are: |
| 497 | - each hardware device declares an array of ports (hw_port_descriptor). |
| 498 | any mix of input and output ports is allowed. |
| 499 | - when setting up the device, attach the array (set_hw_ports). |
| 500 | - if the device accepts interrupts, it will have to attach a port callback |
| 501 | function (set_hw_port_event) |
| 502 | - connect ports with the device tree |
| 503 | - handle incoming interrupts with the callback |
| 504 | - generate outgoing interrupts with hw_port_event |