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