Home | History | Annotate | Line # | Download | only in sim
      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 - C Language Assumptions
     11 - "dump" commands under gdb
     12 
     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. common/local.mk)
     22 
     23 In addition "common" contains portions of the system call support
     24 (e.g. callback.c, target-newlib-*.c).
     25 
     27 TAGS support
     28 ============
     29 
     30 Many files generate program symbols at compile time.
     31 Such symbols can't be found with grep nor do they normally appear in
     32 the TAGS file.  To get around this, source files can add the comment
     33 
     34 /* TAGS: foo1 foo2 */
     35 
     36 where foo1, foo2 are program symbols.  Symbols found in such comments
     37 are greppable and appear in the TAGS file.
     38 
     40 Generating "configure" files
     41 ============================
     42 
     43 "configure" can be generated by running `autoreconf'.
     44 
     46 C Language Assumptions
     47 ======================
     48 
     49 An ISO C11 compiler is required, as is an ISO C standard library.
     50 
     52 "dump" commands under gdb
     53 =========================
     54 
     55 gdbinit.in contains the following
     56 
     57 define dump
     58 set sim_debug_dump ()
     59 end
     60 
     61 Simulators that define the sim_debug_dump function can then have their
     62 internal state pretty printed from gdb.
     63 
     64 FIXME: This can obviously be made more elaborate.  As needed it will be.
     65 
     67 Rebuilding target-newlib-* files
     68 ================================
     69 
     70 Checkout a copy of the SIM and LIBGLOSS modules (Unless you've already
     71 got one to hand):
     72 
     73 	$  mkdir /tmp/$$
     74 	$  cd /tmp/$$
     75 	$  cvs checkout sim-no-testsuite libgloss-no-testsuite newlib-no-testsuite
     76 
     77 Configure things for an arbitrary simulator target (d10v is used here for
     78 convenience):
     79 
     80 	$  mkdir /tmp/$$/build
     81 	$  cd /tmp/$$/build
     82 	$  /tmp/$$/devo/configure --target=d10v-elf
     83 
     84 In the sim/ directory rebuild the headers:
     85 
     86 	$  cd sim/
     87 	$  make nltvals
     88 
     89 If the target uses the common syscall table (libgloss/syscall.h), then you're
     90 all set!  If the target has a custom syscall table, you need to declare it:
     91 
     92 	devo/sim/common/gennltvals.py
     93 
     94 		Add your new processor target (you'll need to grub
     95 		around to find where your syscall.h lives).
     96 
     97 	devo/sim/<processor>/*.[ch]
     98 
     99 		Include target-newlib-syscall.h instead of syscall.h.
    100 
    102 Tracing
    103 =======
    104 
    105 For ports based on CGEN, tracing instrumentation should largely be for free,
    106 so we will cover the basic non-CGEN setup here.  The assumption is that your
    107 target is using the common autoconf macros and so the build system already
    108 includes the sim-trace configure flag.
    109 
    110 The full tracing API is covered in sim-trace.h, so this section is an overview.
    111 
    112 Before calling any trace function, you should make a call to the trace_prefix()
    113 function.  This is usually done in the main sim_engine_run() loop before
    114 simulating the next instruction.  You should make this call before every
    115 simulated insn.  You can probably copy & paste this:
    116   if (TRACE_ANY_P (cpu))
    117     trace_prefix (sd, cpu, NULL_CIA, oldpc, TRACE_LINENUM_P (cpu), NULL, 0, "");
    118 
    119 You will then need to instrument your simulator code with calls to the
    120 trace_generic() function with the appropriate trace index.  Typically, this
    121 will take a form similar to the above snippet.  So to trace instructions, you
    122 would use something like:
    123   if (TRACE_INSN_P (cpu))
    124     trace_generic (sd, cpu, TRACE_INSN_IDX, "NOP;");
    125 
    126 The exact output format is up to you.  See the trace index enum in sim-trace.h
    127 to see the different tracing info available.
    128 
    129 To utilize the tracing features at runtime, simply use the --trace-xxx flags.
    130   run --trace-insn ./some-program
    131 
    133 Profiling
    134 =========
    135 
    136 Similar to the tracing section, this is merely an overview for non-CGEN based
    137 ports.  The full API may be found in sim-profile.h.  Its API is also similar
    138 to the tracing API.
    139 
    140 Note that unlike the tracing command line options, in addition to the profile
    141 flags, you have to use the --verbose option to view the summary report after
    142 execution.  Tracing output is displayed on the fly, but the profile output is
    143 only summarized.
    144 
    145 To profile core accesses (such as data reads/writes and insn fetches), add
    146 calls to PROFILE_COUNT_CORE() to your read/write functions.  So in your data
    147 fetch function, you'd use something like:
    148   PROFILE_COUNT_CORE (cpu, target_addr, size_in_bytes, map_read);
    149 Then in your data write function:
    150   PROFILE_COUNT_CORE (cpu, target_addr, size_in_bytes, map_write);
    151 And in your insn fetcher:
    152   PROFILE_COUNT_CORE (cpu, target_addr, size_in_bytes, map_exec);
    153 
    154 To use the PC profiling code, you simply have to tell the system where to find
    155 your simulator's PC.  So in your model initialization function:
    156   CPU_PC_FETCH (cpu) = function_that_fetches_the_pc;
    157 
    158 To profile branches, in every location where a branch insn is executed, call
    159 one of the related helpers:
    160   PROFILE_BRANCH_TAKEN (cpu);
    161   PROFILE_BRANCH_UNTAKEN (cpu);
    162 If you have stall information, you can utilize the other helpers too.
    163 
    165 Environment Simulation
    166 ======================
    167 
    168 The simplest simulator doesn't include environment support -- it merely
    169 simulates the Instruction Set Architecture (ISA).  Once you're ready to move
    170 on to the next level, it's time to start handling the --env option.  It's
    171 enabled by default for all ports already.
    172 
    173 This will support for the user, virtual, and operating environments.  See the
    174 sim-config.h header for a more detailed description of them.  The former are
    175 pretty straight forward as things like exceptions (making system calls) are
    176 handled in the simulator.  Which is to say, an exception does not trigger an
    177 exception handler in the simulator target -- that is what the operating env
    178 is about.  See the following userspace section for more information.
    179 
    181 Userspace System Calls
    182 ======================
    183 
    184 By default, the libgloss userspace is simulated.  That means the system call
    185 numbers and calling convention matches that of libgloss.  Simulating other
    186 userspaces (such as Linux) is pretty straightforward, but let's first focus
    187 on the basics.  The basic API is covered in include/sim/callback.h.
    188 
    189 When an instruction is simulated that invokes the system call method (such as
    190 forcing a hardware trap or exception), your simulator code should set up the
    191 CB_SYSCALL data structure before calling the common cb_syscall() function.
    192 For example:
    193 static int
    194 syscall_read_mem (host_callback *cb, struct cb_syscall *sc,
    195 		  unsigned long taddr, char *buf, int bytes)
    196 {
    197   SIM_DESC sd = (SIM_DESC) sc->p1;
    198   SIM_CPU *cpu = (SIM_CPU *) sc->p2;
    199   return sim_core_read_buffer (sd, cpu, read_map, buf, taddr, bytes);
    200 }
    201 static int
    202 syscall_write_mem (host_callback *cb, struct cb_syscall *sc,
    203 		  unsigned long taddr, const char *buf, int bytes)
    204 {
    205   SIM_DESC sd = (SIM_DESC) sc->p1;
    206   SIM_CPU *cpu = (SIM_CPU *) sc->p2;
    207   return sim_core_write_buffer (sd, cpu, write_map, buf, taddr, bytes);
    208 }
    209 void target_sim_syscall (SIM_CPU *cpu)
    210 {
    211   SIM_DESC sd = CPU_STATE (cpu);
    212   host_callback *cb = STATE_CALLBACK (sd);
    213   CB_SYSCALL sc;
    214 
    215   CB_SYSCALL_INIT (&sc);
    216 
    217   sc.func = <fetch system call number>;
    218   sc.arg1 = <fetch first system call argument>;
    219   sc.arg2 = <fetch second system call argument>;
    220   sc.arg3 = <fetch third system call argument>;
    221   sc.arg4 = <fetch fourth system call argument>;
    222   sc.p1 = (PTR) sd;
    223   sc.p2 = (PTR) cpu;
    224   sc.read_mem = syscall_read_mem;
    225   sc.write_mem = syscall_write_mem;
    226 
    227   cb_syscall (cb, &sc);
    228 
    229   <store system call result from sc.result>;
    230   <store system call error from sc.errcode>;
    231 }
    232 Some targets store the result and error code in different places, while others
    233 only store the error code when the result is an error.
    234 
    235 Keep in mind that the CB_SYS_xxx defines are normalized values with no real
    236 meaning with respect to the target.  They provide a unique map on the host so
    237 that it can parse things sanely.  For libgloss, the common/target-newlib-syscall
    238 file contains the target's system call numbers to the CB_SYS_xxx values.
    239 
    240 To simulate other userspace targets, you really only need to update the maps
    241 pointers that are part of the callback interface.  So create CB_TARGET_DEFS_MAP
    242 arrays for each set (system calls, errnos, open bits, etc...) and in a place
    243 you find useful, do something like:
    244 
    245 ...
    246 static CB_TARGET_DEFS_MAP cb_linux_syscall_map[] = {
    247 # define TARGET_LINUX_SYS_open 5
    248   { CB_SYS_open, TARGET_LINUX_SYS_open },
    249   ...
    250   { -1, -1 },
    251 };
    252 ...
    253   host_callback *cb = STATE_CALLBACK (sd);
    254   cb->syscall_map = cb_linux_syscall_map;
    255   cb->errno_map = cb_linux_errno_map;
    256   cb->open_map = cb_linux_open_map;
    257   cb->signal_map = cb_linux_signal_map;
    258   cb->stat_map = cb_linux_stat_map;
    259 ...
    260 
    261 Each of these cb_linux_*_map's are manually declared by the arch target.
    262 
    263 The target_sim_syscall() example above will then work unchanged (ignoring the
    264 system call convention) because all of the callback functions go through these
    265 mapping arrays.
    266 
    268 Events
    269 ======
    270 
    271 Events are scheduled and executed on behalf of either a cpu or hardware devices.
    272 The API is pretty much the same and can be found in common/sim-events.h and
    273 common/hw-events.h.
    274 
    275 For simulator targets, you really just have to worry about the schedule and
    276 deschedule functions.
    277 
    279 Device Trees
    280 ============
    281 
    282 The device tree model is based on the OpenBoot specification.  Since this is
    283 largely inherited from the psim code, consult the existing psim documentation
    284 for some in-depth details.
    285 	http://sourceware.org/psim/manual/
    286 
    288 Hardware Devices
    289 ================
    290 
    291 The simplest simulator doesn't include hardware device support.  Once you're
    292 ready to move on to the next level, declare in your Makefile.in:
    293 SIM_EXTRA_HW_DEVICES = devone devtwo devthree
    294 
    295 The basic hardware API is documented in common/hw-device.h.
    296 
    297 Each device has to have a matching file name with a "dv-" prefix.  So there has
    298 to be a dv-devone.c, dv-devtwo.c, and dv-devthree.c files.  Further, each file
    299 has to have a matching hw_descriptor structure.  So the dv-devone.c file has to
    300 have something like:
    301   const struct hw_descriptor dv_devone_descriptor[] = {
    302     {"devone", devone_finish,},
    303     {NULL, NULL},
    304   };
    305 
    306 The "devone" string as well as the "devone_finish" function are not hard
    307 requirements, just common conventions.  The structure name is a hard
    308 requirement.
    309 
    310 The devone_finish() callback function is used to instantiate this device by
    311 parsing the corresponding properties in the device tree.
    312 
    313 Hardware devices typically attach address ranges to themselves.  Then when
    314 accesses to those addresses are made, the hardware will have its callback
    315 invoked.  The exact callback could be a normal I/O read/write access, as
    316 well as a DMA access.  This makes it easy to simulate memory mapped registers.
    317 
    318 Keep in mind that like a proper device driver, it may be instantiated many
    319 times over.  So any device state it needs to be maintained should be allocated
    320 during the finish callback and attached to the hardware device via set_hw_data.
    321 Any hardware functions can access this private data via the hw_data function.
    322 
    324 Ports (Interrupts / IRQs)
    325 =========================
    326 
    327 First, a note on terminology.  A "port" is an aspect of a hardware device that
    328 accepts or generates interrupts.  So devices with input ports may be the target
    329 of an interrupt (accept it), and/or they have output ports so that they may be
    330 the source of an interrupt (generate it).
    331 
    332 Each port has a symbolic name and a unique number.  These are used to identify
    333 the port in different contexts.  The output port name has no hard relationship
    334 to the input port name (same for the unique number).  The callback that accepts
    335 the interrupt uses the name/id of its input port, while the generator function
    336 uses the name/id of its output port.
    337 
    338 The device tree is used to connect the output port of a device to the input
    339 port of another device.  There are no limits on the number of inputs connected
    340 to an output, or outputs to an input, or the devices attached to the ports.
    341 In other words, the input port and output port could be the same device.
    342 
    343 The basics are:
    344  - each hardware device declares an array of ports (hw_port_descriptor).
    345    any mix of input and output ports is allowed.
    346  - when setting up the device, attach the array (set_hw_ports).
    347  - if the device accepts interrupts, it will have to attach a port callback
    348    function (set_hw_port_event)
    349  - connect ports with the device tree
    350  - handle incoming interrupts with the callback
    351  - generate outgoing interrupts with hw_port_event
    352