1999-04-16 09:35:26 +08:00
|
|
|
|
This is a loose collection of notes for people hacking on simulators.
|
|
|
|
|
If this document gets big enough it can be prettied up then.
|
|
|
|
|
|
|
|
|
|
Contents
|
|
|
|
|
|
|
|
|
|
- The "common" directory
|
|
|
|
|
- Common Makefile Support
|
|
|
|
|
- TAGS support
|
|
|
|
|
- Generating "configure" files
|
|
|
|
|
- C Language Assumptions
|
|
|
|
|
- "dump" commands under gdb
|
|
|
|
|
|
|
|
|
|
The "common" directory
|
|
|
|
|
======================
|
|
|
|
|
|
|
|
|
|
The common directory contains:
|
|
|
|
|
|
|
|
|
|
- common documentation files (e.g. run.1, and maybe in time .texi files)
|
|
|
|
|
- common source files (e.g. run.c)
|
2023-01-02 11:58:35 +08:00
|
|
|
|
- common Makefile fragment and configury (e.g. common/local.mk)
|
1999-04-16 09:35:26 +08:00
|
|
|
|
|
|
|
|
|
In addition "common" contains portions of the system call support
|
2021-11-01 13:31:44 +08:00
|
|
|
|
(e.g. callback.c, target-newlib-*.c).
|
1999-04-16 09:35:26 +08:00
|
|
|
|
|
|
|
|
|
TAGS support
|
|
|
|
|
============
|
|
|
|
|
|
|
|
|
|
Many files generate program symbols at compile time.
|
|
|
|
|
Such symbols can't be found with grep nor do they normally appear in
|
|
|
|
|
the TAGS file. To get around this, source files can add the comment
|
|
|
|
|
|
|
|
|
|
/* TAGS: foo1 foo2 */
|
|
|
|
|
|
|
|
|
|
where foo1, foo2 are program symbols. Symbols found in such comments
|
|
|
|
|
are greppable and appear in the TAGS file.
|
|
|
|
|
|
|
|
|
|
Generating "configure" files
|
|
|
|
|
============================
|
|
|
|
|
|
2023-01-02 06:35:16 +08:00
|
|
|
|
"configure" can be generated by running `autoreconf'.
|
1999-04-16 09:35:26 +08:00
|
|
|
|
|
|
|
|
|
C Language Assumptions
|
|
|
|
|
======================
|
|
|
|
|
|
2021-01-08 15:24:51 +08:00
|
|
|
|
An ISO C11 compiler is required, as is an ISO C standard library.
|
1999-04-16 09:35:26 +08:00
|
|
|
|
|
|
|
|
|
"dump" commands under gdb
|
|
|
|
|
=========================
|
|
|
|
|
|
|
|
|
|
gdbinit.in contains the following
|
|
|
|
|
|
|
|
|
|
define dump
|
|
|
|
|
set sim_debug_dump ()
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
Simulators that define the sim_debug_dump function can then have their
|
|
|
|
|
internal state pretty printed from gdb.
|
|
|
|
|
|
|
|
|
|
FIXME: This can obviously be made more elaborate. As needed it will be.
|
|
|
|
|
|
2021-11-01 13:31:44 +08:00
|
|
|
|
Rebuilding target-newlib-* files
|
|
|
|
|
================================
|
1999-04-16 09:35:26 +08:00
|
|
|
|
|
|
|
|
|
Checkout a copy of the SIM and LIBGLOSS modules (Unless you've already
|
|
|
|
|
got one to hand):
|
|
|
|
|
|
|
|
|
|
$ mkdir /tmp/$$
|
|
|
|
|
$ cd /tmp/$$
|
|
|
|
|
$ cvs checkout sim-no-testsuite libgloss-no-testsuite newlib-no-testsuite
|
|
|
|
|
|
2021-04-26 06:11:35 +08:00
|
|
|
|
Configure things for an arbitrary simulator target (d10v is used here for
|
1999-04-16 09:35:26 +08:00
|
|
|
|
convenience):
|
|
|
|
|
|
|
|
|
|
$ mkdir /tmp/$$/build
|
|
|
|
|
$ cd /tmp/$$/build
|
|
|
|
|
$ /tmp/$$/devo/configure --target=d10v-elf
|
|
|
|
|
|
2021-01-17 18:32:12 +08:00
|
|
|
|
In the sim/ directory rebuild the headers:
|
1999-04-16 09:35:26 +08:00
|
|
|
|
|
2021-01-17 18:32:12 +08:00
|
|
|
|
$ cd sim/
|
|
|
|
|
$ make nltvals
|
1999-04-16 09:35:26 +08:00
|
|
|
|
|
2021-04-26 06:11:35 +08:00
|
|
|
|
If the target uses the common syscall table (libgloss/syscall.h), then you're
|
|
|
|
|
all set! If the target has a custom syscall table, you need to declare it:
|
1999-04-16 09:35:26 +08:00
|
|
|
|
|
2021-04-26 06:11:35 +08:00
|
|
|
|
devo/sim/common/gennltvals.py
|
1999-04-16 09:35:26 +08:00
|
|
|
|
|
|
|
|
|
Add your new processor target (you'll need to grub
|
|
|
|
|
around to find where your syscall.h lives).
|
|
|
|
|
|
|
|
|
|
devo/sim/<processor>/*.[ch]
|
|
|
|
|
|
2021-11-01 13:31:44 +08:00
|
|
|
|
Include target-newlib-syscall.h instead of syscall.h.
|
2010-04-13 05:44:46 +08:00
|
|
|
|
|
|
|
|
|
Tracing
|
|
|
|
|
=======
|
|
|
|
|
|
|
|
|
|
For ports based on CGEN, tracing instrumentation should largely be for free,
|
|
|
|
|
so we will cover the basic non-CGEN setup here. The assumption is that your
|
|
|
|
|
target is using the common autoconf macros and so the build system already
|
|
|
|
|
includes the sim-trace configure flag.
|
|
|
|
|
|
|
|
|
|
The full tracing API is covered in sim-trace.h, so this section is an overview.
|
|
|
|
|
|
|
|
|
|
Before calling any trace function, you should make a call to the trace_prefix()
|
|
|
|
|
function. This is usually done in the main sim_engine_run() loop before
|
|
|
|
|
simulating the next instruction. You should make this call before every
|
|
|
|
|
simulated insn. You can probably copy & paste this:
|
|
|
|
|
if (TRACE_ANY_P (cpu))
|
|
|
|
|
trace_prefix (sd, cpu, NULL_CIA, oldpc, TRACE_LINENUM_P (cpu), NULL, 0, "");
|
|
|
|
|
|
|
|
|
|
You will then need to instrument your simulator code with calls to the
|
|
|
|
|
trace_generic() function with the appropriate trace index. Typically, this
|
|
|
|
|
will take a form similar to the above snippet. So to trace instructions, you
|
|
|
|
|
would use something like:
|
|
|
|
|
if (TRACE_INSN_P (cpu))
|
|
|
|
|
trace_generic (sd, cpu, TRACE_INSN_IDX, "NOP;");
|
|
|
|
|
|
|
|
|
|
The exact output format is up to you. See the trace index enum in sim-trace.h
|
|
|
|
|
to see the different tracing info available.
|
|
|
|
|
|
|
|
|
|
To utilize the tracing features at runtime, simply use the --trace-xxx flags.
|
|
|
|
|
run --trace-insn ./some-program
|
|
|
|
|
|
|
|
|
|
Profiling
|
|
|
|
|
=========
|
|
|
|
|
|
|
|
|
|
Similar to the tracing section, this is merely an overview for non-CGEN based
|
|
|
|
|
ports. The full API may be found in sim-profile.h. Its API is also similar
|
|
|
|
|
to the tracing API.
|
|
|
|
|
|
|
|
|
|
Note that unlike the tracing command line options, in addition to the profile
|
|
|
|
|
flags, you have to use the --verbose option to view the summary report after
|
|
|
|
|
execution. Tracing output is displayed on the fly, but the profile output is
|
|
|
|
|
only summarized.
|
|
|
|
|
|
|
|
|
|
To profile core accesses (such as data reads/writes and insn fetches), add
|
|
|
|
|
calls to PROFILE_COUNT_CORE() to your read/write functions. So in your data
|
|
|
|
|
fetch function, you'd use something like:
|
|
|
|
|
PROFILE_COUNT_CORE (cpu, target_addr, size_in_bytes, map_read);
|
|
|
|
|
Then in your data write function:
|
|
|
|
|
PROFILE_COUNT_CORE (cpu, target_addr, size_in_bytes, map_write);
|
|
|
|
|
And in your insn fetcher:
|
|
|
|
|
PROFILE_COUNT_CORE (cpu, target_addr, size_in_bytes, map_exec);
|
|
|
|
|
|
|
|
|
|
To use the PC profiling code, you simply have to tell the system where to find
|
2015-03-23 12:44:54 +08:00
|
|
|
|
your simulator's PC. So in your model initialization function:
|
|
|
|
|
CPU_PC_FETCH (cpu) = function_that_fetches_the_pc;
|
2010-04-13 05:44:46 +08:00
|
|
|
|
|
|
|
|
|
To profile branches, in every location where a branch insn is executed, call
|
|
|
|
|
one of the related helpers:
|
|
|
|
|
PROFILE_BRANCH_TAKEN (cpu);
|
|
|
|
|
PROFILE_BRANCH_UNTAKEN (cpu);
|
|
|
|
|
If you have stall information, you can utilize the other helpers too.
|
|
|
|
|
|
|
|
|
|
Environment Simulation
|
|
|
|
|
======================
|
|
|
|
|
|
|
|
|
|
The simplest simulator doesn't include environment support -- it merely
|
|
|
|
|
simulates the Instruction Set Architecture (ISA). Once you're ready to move
|
2021-05-12 12:46:19 +08:00
|
|
|
|
on to the next level, it's time to start handling the --env option. It's
|
|
|
|
|
enabled by default for all ports already.
|
2010-04-13 05:44:46 +08:00
|
|
|
|
|
|
|
|
|
This will support for the user, virtual, and operating environments. See the
|
|
|
|
|
sim-config.h header for a more detailed description of them. The former are
|
|
|
|
|
pretty straight forward as things like exceptions (making system calls) are
|
|
|
|
|
handled in the simulator. Which is to say, an exception does not trigger an
|
|
|
|
|
exception handler in the simulator target -- that is what the operating env
|
|
|
|
|
is about. See the following userspace section for more information.
|
|
|
|
|
|
|
|
|
|
Userspace System Calls
|
|
|
|
|
======================
|
|
|
|
|
|
|
|
|
|
By default, the libgloss userspace is simulated. That means the system call
|
|
|
|
|
numbers and calling convention matches that of libgloss. Simulating other
|
|
|
|
|
userspaces (such as Linux) is pretty straightforward, but let's first focus
|
2021-05-12 12:35:54 +08:00
|
|
|
|
on the basics. The basic API is covered in include/sim/callback.h.
|
2010-04-13 05:44:46 +08:00
|
|
|
|
|
|
|
|
|
When an instruction is simulated that invokes the system call method (such as
|
|
|
|
|
forcing a hardware trap or exception), your simulator code should set up the
|
|
|
|
|
CB_SYSCALL data structure before calling the common cb_syscall() function.
|
|
|
|
|
For example:
|
|
|
|
|
static int
|
|
|
|
|
syscall_read_mem (host_callback *cb, struct cb_syscall *sc,
|
|
|
|
|
unsigned long taddr, char *buf, int bytes)
|
|
|
|
|
{
|
|
|
|
|
SIM_DESC sd = (SIM_DESC) sc->p1;
|
|
|
|
|
SIM_CPU *cpu = (SIM_CPU *) sc->p2;
|
|
|
|
|
return sim_core_read_buffer (sd, cpu, read_map, buf, taddr, bytes);
|
|
|
|
|
}
|
|
|
|
|
static int
|
|
|
|
|
syscall_write_mem (host_callback *cb, struct cb_syscall *sc,
|
|
|
|
|
unsigned long taddr, const char *buf, int bytes)
|
|
|
|
|
{
|
|
|
|
|
SIM_DESC sd = (SIM_DESC) sc->p1;
|
|
|
|
|
SIM_CPU *cpu = (SIM_CPU *) sc->p2;
|
|
|
|
|
return sim_core_write_buffer (sd, cpu, write_map, buf, taddr, bytes);
|
|
|
|
|
}
|
|
|
|
|
void target_sim_syscall (SIM_CPU *cpu)
|
|
|
|
|
{
|
|
|
|
|
SIM_DESC sd = CPU_STATE (cpu);
|
|
|
|
|
host_callback *cb = STATE_CALLBACK (sd);
|
|
|
|
|
CB_SYSCALL sc;
|
|
|
|
|
|
|
|
|
|
CB_SYSCALL_INIT (&sc);
|
|
|
|
|
|
|
|
|
|
sc.func = <fetch system call number>;
|
|
|
|
|
sc.arg1 = <fetch first system call argument>;
|
|
|
|
|
sc.arg2 = <fetch second system call argument>;
|
|
|
|
|
sc.arg3 = <fetch third system call argument>;
|
|
|
|
|
sc.arg4 = <fetch fourth system call argument>;
|
|
|
|
|
sc.p1 = (PTR) sd;
|
|
|
|
|
sc.p2 = (PTR) cpu;
|
|
|
|
|
sc.read_mem = syscall_read_mem;
|
|
|
|
|
sc.write_mem = syscall_write_mem;
|
|
|
|
|
|
|
|
|
|
cb_syscall (cb, &sc);
|
|
|
|
|
|
|
|
|
|
<store system call result from sc.result>;
|
|
|
|
|
<store system call error from sc.errcode>;
|
|
|
|
|
}
|
|
|
|
|
Some targets store the result and error code in different places, while others
|
|
|
|
|
only store the error code when the result is an error.
|
|
|
|
|
|
|
|
|
|
Keep in mind that the CB_SYS_xxx defines are normalized values with no real
|
|
|
|
|
meaning with respect to the target. They provide a unique map on the host so
|
2021-11-01 13:31:44 +08:00
|
|
|
|
that it can parse things sanely. For libgloss, the common/target-newlib-syscall
|
|
|
|
|
file contains the target's system call numbers to the CB_SYS_xxx values.
|
2010-04-13 05:44:46 +08:00
|
|
|
|
|
|
|
|
|
To simulate other userspace targets, you really only need to update the maps
|
|
|
|
|
pointers that are part of the callback interface. So create CB_TARGET_DEFS_MAP
|
|
|
|
|
arrays for each set (system calls, errnos, open bits, etc...) and in a place
|
|
|
|
|
you find useful, do something like:
|
|
|
|
|
|
|
|
|
|
...
|
|
|
|
|
static CB_TARGET_DEFS_MAP cb_linux_syscall_map[] = {
|
|
|
|
|
# define TARGET_LINUX_SYS_open 5
|
|
|
|
|
{ CB_SYS_open, TARGET_LINUX_SYS_open },
|
|
|
|
|
...
|
|
|
|
|
{ -1, -1 },
|
|
|
|
|
};
|
|
|
|
|
...
|
|
|
|
|
host_callback *cb = STATE_CALLBACK (sd);
|
|
|
|
|
cb->syscall_map = cb_linux_syscall_map;
|
|
|
|
|
cb->errno_map = cb_linux_errno_map;
|
|
|
|
|
cb->open_map = cb_linux_open_map;
|
|
|
|
|
cb->signal_map = cb_linux_signal_map;
|
|
|
|
|
cb->stat_map = cb_linux_stat_map;
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
Each of these cb_linux_*_map's are manually declared by the arch target.
|
|
|
|
|
|
|
|
|
|
The target_sim_syscall() example above will then work unchanged (ignoring the
|
|
|
|
|
system call convention) because all of the callback functions go through these
|
|
|
|
|
mapping arrays.
|
|
|
|
|
|
|
|
|
|
Events
|
|
|
|
|
======
|
|
|
|
|
|
|
|
|
|
Events are scheduled and executed on behalf of either a cpu or hardware devices.
|
|
|
|
|
The API is pretty much the same and can be found in common/sim-events.h and
|
|
|
|
|
common/hw-events.h.
|
|
|
|
|
|
|
|
|
|
For simulator targets, you really just have to worry about the schedule and
|
|
|
|
|
deschedule functions.
|
|
|
|
|
|
|
|
|
|
Device Trees
|
|
|
|
|
============
|
|
|
|
|
|
|
|
|
|
The device tree model is based on the OpenBoot specification. Since this is
|
|
|
|
|
largely inherited from the psim code, consult the existing psim documentation
|
|
|
|
|
for some in-depth details.
|
|
|
|
|
http://sourceware.org/psim/manual/
|
|
|
|
|
|
|
|
|
|
Hardware Devices
|
|
|
|
|
================
|
|
|
|
|
|
|
|
|
|
The simplest simulator doesn't include hardware device support. Once you're
|
2021-06-20 07:36:39 +08:00
|
|
|
|
ready to move on to the next level, declare in your Makefile.in:
|
|
|
|
|
SIM_EXTRA_HW_DEVICES = devone devtwo devthree
|
2010-04-13 05:44:46 +08:00
|
|
|
|
|
|
|
|
|
The basic hardware API is documented in common/hw-device.h.
|
|
|
|
|
|
|
|
|
|
Each device has to have a matching file name with a "dv-" prefix. So there has
|
|
|
|
|
to be a dv-devone.c, dv-devtwo.c, and dv-devthree.c files. Further, each file
|
|
|
|
|
has to have a matching hw_descriptor structure. So the dv-devone.c file has to
|
|
|
|
|
have something like:
|
|
|
|
|
const struct hw_descriptor dv_devone_descriptor[] = {
|
|
|
|
|
{"devone", devone_finish,},
|
|
|
|
|
{NULL, NULL},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
The "devone" string as well as the "devone_finish" function are not hard
|
|
|
|
|
requirements, just common conventions. The structure name is a hard
|
|
|
|
|
requirement.
|
|
|
|
|
|
|
|
|
|
The devone_finish() callback function is used to instantiate this device by
|
|
|
|
|
parsing the corresponding properties in the device tree.
|
|
|
|
|
|
|
|
|
|
Hardware devices typically attach address ranges to themselves. Then when
|
|
|
|
|
accesses to those addresses are made, the hardware will have its callback
|
|
|
|
|
invoked. The exact callback could be a normal I/O read/write access, as
|
|
|
|
|
well as a DMA access. This makes it easy to simulate memory mapped registers.
|
|
|
|
|
|
|
|
|
|
Keep in mind that like a proper device driver, it may be instantiated many
|
|
|
|
|
times over. So any device state it needs to be maintained should be allocated
|
|
|
|
|
during the finish callback and attached to the hardware device via set_hw_data.
|
|
|
|
|
Any hardware functions can access this private data via the hw_data function.
|
|
|
|
|
|
|
|
|
|
Ports (Interrupts / IRQs)
|
|
|
|
|
=========================
|
1999-04-16 09:35:26 +08:00
|
|
|
|
|
2010-04-13 05:44:46 +08:00
|
|
|
|
First, a note on terminology. A "port" is an aspect of a hardware device that
|
|
|
|
|
accepts or generates interrupts. So devices with input ports may be the target
|
|
|
|
|
of an interrupt (accept it), and/or they have output ports so that they may be
|
|
|
|
|
the source of an interrupt (generate it).
|
|
|
|
|
|
|
|
|
|
Each port has a symbolic name and a unique number. These are used to identify
|
|
|
|
|
the port in different contexts. The output port name has no hard relationship
|
|
|
|
|
to the input port name (same for the unique number). The callback that accepts
|
|
|
|
|
the interrupt uses the name/id of its input port, while the generator function
|
|
|
|
|
uses the name/id of its output port.
|
|
|
|
|
|
|
|
|
|
The device tree is used to connect the output port of a device to the input
|
|
|
|
|
port of another device. There are no limits on the number of inputs connected
|
|
|
|
|
to an output, or outputs to an input, or the devices attached to the ports.
|
|
|
|
|
In other words, the input port and output port could be the same device.
|
|
|
|
|
|
|
|
|
|
The basics are:
|
|
|
|
|
- each hardware device declares an array of ports (hw_port_descriptor).
|
|
|
|
|
any mix of input and output ports is allowed.
|
|
|
|
|
- when setting up the device, attach the array (set_hw_ports).
|
|
|
|
|
- if the device accepts interrupts, it will have to attach a port callback
|
|
|
|
|
function (set_hw_port_event)
|
|
|
|
|
- connect ports with the device tree
|
|
|
|
|
- handle incoming interrupts with the callback
|
|
|
|
|
- generate outgoing interrupts with hw_port_event
|