Professional Documents
Culture Documents
TEACHING HARDWARE-
SOFTWARE
INTEGRATION WITH
SIMICS
VERSION 0.9.5
JAKOB ENGBLOM
WWW.VIRTUTECH.COM
T EACHING H ARDWARE -S OFTWARE I NTEGRATION WITH S IMICS
INTRODUCTION
This application note documents a teaching setup for Virtutech Simics
intended to teach hardware-software integration at a system level. The
setup consists of an MPC8641D-based virtual platform, running Linux,
into which students add a custom hardware unit, a Linux driver for the
hardware unit, and user-level software that makes use of the custom
hardware unit.
PROJECT IDEAS
The following is a set of suggested projects that can be done using this
setup, teaching various aspects of hardware-software integration.
produced (as part of the evaluation of the design, students might try
different delays in order to determine how fast the hardware must be to be
worthwhile).
It is quite possible that the overhead of a device driver cancels a large part
of the benefit of acceleration hardware. Exploring that aspect is part of the
mission here.
AVAILABILITY
The software setup will be made available from Virtutech as a
downloadable archive. It should be noted that it is fairly extensive, since it
includes source code for all relevant components. The complete file
archive is on the order of 1 GB.
Host Machine
The setup requires a Linux host, due to the need to cross-compile the
Linux kernel and related software, it is very hard to port to Windows in a
reasonable way. It has been tested on a Kubuntu 7.04 setup.
Host Compiler
The host machine needs to have the tools available to build Simics
modules, which means that it needs to have a host C compiler installed.
Please refer to the Simics installation guide for more information.
Simics Installation
Virtutech Simics is supposed to be installed in the default way, which
would put it at /opt/virtutech/simics-4.0/. This does not matter once
workspace setup for the Simics workspace has been performed. All work
is done in the user workspace.
Please refer to the Simics installation guide for more information on how
to install packages.
OVERVIEW
The complete setup is fairly complex, as shown in Figure 1, but the course
focus is just on three parts, all the other parts are really just scaffolding
necessary to provide a realistic system environment:
Virtual MPC8641D Board
MPC8641D SoC
Simics
scripting Program using Other
Busybox
simple device program
Device
Simple device Simicsfs
Virtual serial drivers
driver driver
console
UART Linux 2.6.23 kernel
CPU cores 0 and 1
Ethernet
Virtual
network
MemCtrl Timers MPIC
RAM Simple_device Hfs device
PCI PCIe
Simics
Host file
Host operating system system
Host computer
Host disk
The areas of primary interest for teaching are shown with bold outlines in
Figure 1. In all likelihood, the virtual network will not be used, but it is
there if needed. The simicsfs file system provides an easy way to get
software and data into the target machine, without having to rebuild the
initial ramdisk file system (which is easy with the provided scripts, but
fairly time-consuming anyway).
All compilation of software for the target will be done outside of the
Simics target machine, using cross-compilation tools. This is how most
embedded software is developed in practice today.
DIRECTORY LAYOUT
The setup contains a complete embedded system, and as such it is fairly
complex. The file set is supposed to be put in some main directory, which
we call base in the following discussion.
The paths might of course be different on your machine. The path to the
cross-compiler is automatically picked up from the environment variable
by all parts that need to use it.
RUNNING SIMICS
To give a feel for how things work with this setup, we should start by
running Simics with some scripts.
base$ cd simics-workspace
Once the Simics GUI has started, you should then select “New Session from
Script…” and select a start script. We suggest you start with the script called
“mpc8641-simple/mpc8641-hsi-course-setup.simics”, as shown in Figure 6.
Once this script has started, you will see a new window on the screen,
which is the serial console of the target machine. Nothing has appeared yet
there, since the simulation is not yet started. Figure 7 shows the state at
this point, and also identifies some key windows.
Press the start button in the GUI to start running the target. Note that you
can pause the simulation at any point in time and resume it.
If you wait a while, the target system will boot into U-Boot, load the
kernel and ramdisk into memory, boot the Linux kernel, mount the
ramdisk, and get to a login prompt.
Simics has scripted the bootup and login process, and the boot will finish
with you logged into the Linux running on the virtual MPC8641D target.
~ # cat /proc/interrupts
~ # ls /bin
~ # cat /proc/cpuinfo
~ # cat /proc/devices
~ # ls –l /dev
~ # ping 10.10.0.1
~ # df
Reverse execution
Another interesting Simics feature is reverse execution. To try this, do the
following:
Note that Simics does not save the changes you make to the target system
disk, so there is no risk that you can wreck future runs by making mistakes
inside a simulation run. The reason for this is both that you need to
explicitly ask Simics to save any changes to target system disks in order
keep them (and they would only be saved as disk differences from the
original state), and that the kind of initial ramdisk disk used in this setup
cannot be saved. It is loaded into memory and uncompressed during the
boot, and its state in ram is not possible to compress back down to a file
system for future reference.
If you want to change where the starting point for the simicsfs file system
on the host file system is, you need to use the Simics command-line
window and the command “hfs.root”.
Once you have copied the files you need into the target system, you should
unmount the host file system using “umount /host”. The reason is that this
preserves the deterministic and repeatable semantics of Simics – mounting
the host introduces a non-controllable factor into the simulation.
The script is shown in Figure 11. Note how it works by calling other
scripts, running the simulation for a while (using continue, the scripts
called will call stop to stop the simulation).
#
# Start from the checkpoint, and then load the driver,
# and then run the test program that exercises the driver
#
run-command-file "%script%/mpc8641d-hsi-course-booted.simics"
#
# Load the driver
# -- Provide argument here to give it an address
#
$insmod_arguments = "phys_addr=0xf80f0000"
run-command-file "%script%/hsi-course-load-driver.simics"
continue
#
# The load driver script finishes once the driver is loaded
#
run-command-file "%script%/hsi-course-load-test-program.simics"
continue
In this way, testing a new set of device model, device driver, and test
program is really very simple. There is no manual work involved other
than running the script.
Note that all of the steps above can be run from the Simics command-line
interactively.
DMLmodel DML
source files templates
& library
User program
Target firmware
C files Simics
header files
IO,
Devices Links,
Devices Devices Devices
Devices
Devices Memories Processors
Inter-
connects
C Compiler
Simics
module DLL
Since you will be using DML in this course, you should look into the
documentation provided about DML.
Once the device model has been built, it can be loaded into Simics using
the script “hsi-course-add-simple-device.simics”. This script creates a new
device object in Simics and sets the crucial parameters for its integration
into Simics: the name of the processor it is attached to for simulation
purposes, and the target for its interrupts. Figure 15 shows the Simics
command-line code needed to create a device object and map into
memory. The line starting with “@” is inline Python code, which is often
used in Simics scripts to do complicated things.
load-module simple_device
@SIM_create_object("simple_device","sd0",[["queue",conf.cpu0],["irq_dev",[
conf.pic,'internal_interrupts']],["irq_level",23], ["time_to_result", 1e-
3]])
ccsr_space.add-map sd0:regs 0xf0000 0x100
Note that you can map into any other free location, there is nothing
mandatory about this. But it works. To see all mappings in Simics, use the
“Memory Mappings Browser” found on the “Tools” menu. Figure 16 shows
how this looks after the device has been mapped. Our simple device object
is called “sd0”, keeping with Simics style of using fairly short object
names, and the “:regs” bit indicates that the register bank in the device that
is being mapped here is called “regs”. Look inside the source code in
simple_device.dml and you will find the declaration of this register bank.
To further look into the device, you can either look at the source code or
use the Simics “Device Registers” window you find on the “Debug” menu.
Actually, look at both and see how they relate. There is a also an “Object
Browser” available that presents the Simics implementation view of the
objects in the simulation, as shown in Figure 21.
Note that mapping has a defined length, and this is set in the script creating
the device, in the “map” command that maps it into ccsr_space as shown in
Figure 15.
You should not need to change this part of the device model or its setup.
Just use the facilities provided in the simple device code to raise and lower
interrupts. How to do this should be clear from the source code.
The target software is expected to check the operation completed (OC) bit in
the status register before reading the results. This bit is set with a delay
from the last write to input (note that input accepts data at any rate). Only
after this delay has passed will the device model set the OC bit and send an
interrupt signaling completion.
Note that the result in this simple model is computed immediately, but that
it is not considered valid. You could also model this by not setting the
value of the accumulator register until the final result is due, but that would
require some kind of internal register to hold the result until then. This
type of simplified modeling works well most of the time, especially when
we know that the target software is well-behaved.
The delay is implemented by using Simics events, posted into the future so
that the device model gets a callback to “event()” method inside the “event
operation_complete” object in the DML file.
The version register is used by the device driver to check that the device is
really what it expects and to report the version number of the device
hardware to the software.
The output is viewed both in the Simics object browser (as shown in
Figure 18), and from the command-line using sd0.info/sd0.status
commands. Note that there is no static checking of the command code, so
if you use a bad name for an attribute, it will not show up as an error until
Simics runtime when you try to use the commands.
A good starting point to understand Linux device drivers is the book called
“Linux Device Drivers, 3rd edition”. It can be bought in paper form, or
downloaded from http://lwn.net/Kernel/LDD3/ . It is highly recommended
to have some such reference handy when working with the device driver.
Verbose build
If you want to check on the detailed invocation of the cross-compiler on
the device driver code, you use the command “make V=1” instead. V=1 tells
the Linux kernel make system to be verbose and tell you all it does and the
complete command-line invocation of compilers.
Mounting host, copying the most recent driver in, and unmounting the
host.
Doing an insmod to make the kernel load the driver, and providing and
argument to insmod to tell the driver where to find the hardware.
Creating the device node /dev/simd so that programs can access the
device, using mknod.
Linux uses device numbers to call the right driver for the right node in
/dev/. For this setup, we use the major number 240 and minor 100, since it
will only be used in this system, and thus it is fine to use one of the
numbers set aside for local use. There is no need to complicate things with
automatic assignment of dynamic device numbers at this point in time.
The driver code is extensively commented to help you find your way
around.
The example code for the Linux Device Drivers book is a good source of
snippets of code to extend the driver.
Note that as noted in the README file, the device driver has plenty of room
for added functionality.
The target programs are built to have an extension .elf, to make it easy to
see that they are not host programs (since they are cross-compiled).
Note that once a program has been copied into the target disk, it can be
rerun any number of times. There is no need to recopy it unless you have
change the code in some way.
Note that you can run scripts like “hsi-course-load-rule30.simics” from the
Simics GUI using the command “Append from Script…” on the File menu,
after the machine has been booted or a checkpoint opened.
The present kernel configuration has been configured for use on the
particular simulation setup we have in place only, with certain device
drivers that generated warnings about accesses to non-existing memory
and similar deactivated.
All device drivers and features being used are configured as statically
loaded modules, i.e., part of the compiled kernel binary. This is typical for
how embedded systems are usually configured, simply because you know
the hardware you are running on and the applications it is supposed to run.
Desktop and server setups tend to use loadable modules far more.
Note that since we are using dynamic module loading for our device
driver, all the support for loading modules dynamically is configured to be
present.
To reconfigure the kernel, issue “make menuconfig”, and you should get the
interactive setup shown in Figure 21.
To use the new kernel in the Simics simulation setup, you need to copy
two files to the Simics workspace file tree:
To get more information for symbolic debug, you need to explicitly enable
debugging information in kernel setup. This is done under “Kernel Hacking”
in the setup system, as shown in Figure 22.
Note that if you build the kernel with debug information and ask Simics to
load that debug information, the Simics process will get quite large due to
the need to represent the line-number information in Simics.
The kernel is a fairly large piece of software, and just the compiled debug
information is on the order of 35MB. Loading that into Simics will require
a few hundred MB of host memory to represent, so make sure that you
host has plenty of memory. In particular, running Simics inside a VmWare
virtual machine might make this operation swap like crazy. Therefore, it is
no recommended to use a full debug-information kernel unless absolutely
necessary. Note that the default build does have symbol information that
tells you which function you are in, which is often sufficient.
Figure 24. Register fields for control register of simple device in DML
register control @ 0x04 "Device control register" {
field OP [1:0] "Operation select" {
// read-write semantics
method write(value) {
$this = value;
inline $reset();
log "info", 2 : "Operation selected: %d", $this;
}
}
field IE [2] "Interrupt enable" {
method write(value) {
$this = value;
if(value==1) {
log "info", 2 : "Enabling interrupts";
} else {
// Lower our flag about having raised an IRQ
// and lower IRQ if it was raised
log "info", 2 : "Disabling interrupts";
inline $clear_interrupt_if_raised();
}
}
}
}
Writes to the control register will usually have side-effects, while reading
should not so that software can check the device settings without changing
anything.
that a flag is cleared if a bit value of one is written to it. This makes it easy
to clear a range of flags with a single write, and also to leave certain flags
alone by just writing them as zero bits.
Polling
The simplest way to drive most hardware is to use polling, where the
software keeps reading some status register bit to tell if the hardware has
completed its operation. This means that you should always have
“operation complete” indicators in your hardware status registers.
The input registers are designed in such a way that there is no need to have
a flag for “input accepted”. Essentially, the hardware device will not
complete the write operation from the processor until it has put the data it
received in the right place internally.
Interrupts
If a device can raise interrupts, it needs a few facilities in place:
The reason you want to have explicit status bits for interrupt status is that
several devices could share a single interrupt line to the processor, and the
device drivers would then need to look at the device status registers and
tell which device or devices raised the interrupt.
In our simple device example, the interrupt status bit is the same as the
general operation complete bit.
Starting operations
Starting a hardware operation can either be explicit or implicit.
Our simple device has an implicit design, where writing to the single input
registers begins an operation, and the point of operation complete is
pushed out each time. This is a bit simplistic, since it basically assumes
that the hardware has an infinitely long pipeline that can accept any
amount of input.
More reading
The number of sources on this topic is fairly limited, but here are a few
links:
Device-internal registers
DML supports the generation of registers that are internal to a device and
that have software-accessible interface. This is useful to model internal
state registers, for example, in a way that is consistent with other registers.
The syntax is simple: apply the template “unmapped” to the register, as
shown in Figure 26. Such registers are checkpointed like other registers,
and are visible in the Simics register viewer. It is the best way to code
internal registers in a device.
The simplest solution is shown in Figure 27. You essentially allocate a set
of registers in an array of registers, and have the software write this. The
device model can then access the written data using array notation. Note
that this solution does not really create a memory buffer that can be
accessed using accesses of any size, but really just an array of registers. To
read out the contents, you have to work on units the size of the registers.
Note that you can make 32-bit registers byte-writable from software by
giving the parameter “partial=true” to the bank, as shown at the top in
Figure 27. This generates a bit more code for your device model, and
usually real hardware does assume aligned full-size writes to registers, so
it is off by default.
Items declared as data are not checkpointed, so their use should really be
restricted to temporary information that is created and used during a single
execution step (implementing a memory operation or event callback), or to
data that is trivial to reconstruct from other information in the device.
For example, for ease of computation, you might want to expand a terse
bit-based input register into an array of bytes in your device model to be
used from several separate functions during a single simulation step. This
is just an internal optimization, and the true state of the device is still
represented by the value of the register. An outline code is shown in Figure
28, note that both the write and set methods have to be overridden for this
to work correctly. This example is also checkpoint-safe.
bank e {
register input size 1 @ 0x00 {
method write(value) {
// Expand data to the buffer
$this = value;
inline $do_expand();
}
method set(value) {
// expand data on attribute set, to support checkpoint
$this = value;
inline $do_expand();
}
// get and read do not need changing, they are just reporting
// the byte value in the register
}
method do_expand() {
// iterate across bits in $input
// set the bytes in expanded_buffer
// ... implementation abbreviated ...
}
}
Note that if you can, you should try to use a local variable inside a single
method or function instead, as shown above in the second part of Figure
27. This avoids any checkpointing issues and makes it clear that data is
temporary.
template wired_pin_field {
// Set pin_attribute to tell us which attribute to read
parameter pin_attribute default undefined;
// Make this not stored in the device data structure
parameter allocate = false;
method write(value) {
log "spec_violation" : "Writing hard-wired bit %s", $qname;
}
method read() -> (result) {
result = $pin_attribute.value;
}
// read current value for checkpointing and device viewer
method get -> (v) {
inline $read -> (v);
}
method set(v) {
// Should not get called, but ignore values written
}
}
bank wired_config {
register values size 4 @ 0x00 {
// To not checkpoint this value, as it is redundant
parameter configuration = "pseudo";
// Then the fields
field p1 [0] is (wired_pin_field) {
parameter pin_attribute = $pin_1;
}
field p2 [1] is (wired_pin_field) {
parameter pin_attribute = $pin_2;
}
field p3 [2] is (wired_pin_field) {
parameter pin_attribute = $pin_3;
}
}
}
DEBUGGING TIPS
A virtual platform offers a very good base for debugging the hardware-
software integration as it is far easier to see what is going on compared to
a physical hardware setup.
This section will give you broad ideas for how Simics can be used in
debugging problems. For the details, please see the Simics documentation,
in particular:
Device Activity
To peek at the activity towards any device, you have several tools at your
disposal, at the Simics command-line. We assume the object for our
simple device in Simics is called “sd0”.
In the target Linux, you can do some checking of what the kernel has
logged in terms of events:
cat /proc/interrupts: lists all interrupts that the kernel has handled
cat /proc/devices: lists all active devices
Device Logging
It is a very good idea to pepper your device code with log statements as
you initially figure out how to do things. They can be removed later, or left
in at a high level of logging (they do not hurt performance if not
activated). See Modeling Your System in Simics, Section 3.7.
The standard log levels used in Virtutech devices are the following:
Figure 31 shows an example session using read and write. Note that the
reads and writes seem to come from cpu0 for the device, and by design
there is no way to differentiate them from regular processor reads and
writes. If you want to see or change values without looking like a
processor, use the attribute mechanism (or the device viewer shown in
Figure 17).
Note that write and read have side-effects, and that is what you want for
device testing. There are also MEMORYSPACE.get and set commands that
generate “inquiry” accesses which do not have side-effects. These are not
that useful for device model testing.
simics> output-radix 16 2
simics> ccsr_space.read 0xf0000
[sd0 trace-io] Read from cpu0: 0x0f_00_00 4 0xab_cd_00_01 (BE)
0xab_cd_00_01
simics> ccsr_space.write 0xf0000 0xdeadbeef
[sd0 spec-viol] Write to read-only field regs.version.V_MINOR (value written = 0xef, contents = 0x1).
[sd0 spec-viol] Write to read-only field regs.version.V_MAJOR (value written = 0xbe, contents = 0x0).
[sd0 spec-viol] Write to read-only field regs.version.COOKIE (value written = 0xdead, contents = 0xabcd).
[sd0 trace-io] Write from cpu0: 0x0f_00_00 4 0xde_ad_be_ef (BE)
simics> phys_mem.write 0xf80f_0000 0xdeadbeef
[sd0 spec-viol] Write to read-only field regs.version.V_MINOR (value written = 0xef, contents = 0x1).
[sd0 spec-viol] Write to read-only field regs.version.V_MAJOR (value written = 0xbe, contents = 0x0).
[sd0 spec-viol] Write to read-only field regs.version.COOKIE (value written = 0xdead, contents = 0xabcd).
[sd0 trace-io] Write from cpu0: 0x0f_00_00 4 0xde_ad_be_ef (BE)
simics> simics> sd0->regs_version
0xab_cd_00_01
If you want
Driver Logging
Using printk() in your driver code offers a way to have it tell you what it is
doing. Note that it is not good to put this on the common calls like write()
as the target console will mix the printk() and application-level printouts
on the same line. When the driver detects errors, however, printk() can
definitely be a very useful way to signal driver state.
Use the kernel various defined KERN_x printk levels. To remove debug code
when not used, use local DEBUG_SOMETHING defines in the device driver. There
is currently a few of these defined in simple.h and used in the various .c
files.
Simics Debugger
Simics has a built-in debugger that can handle both kernel-level and user-
level code. We will get back with a description on how to use these.
PERFORMANCE MEASUREMENT
More to come.
SUMMARY
This application note has presented a complete teaching setup based on
Virtutech Simics, the Freescale MPC8641D virtual platform, and the
Linux operating system. This setup can be used to teach a variety of topics
related to how hardware and software interact. It provides students and
teachers a complete software stack for a realistic target, with the flexibility
to change any part, including the hardware.
CONTACT INFORMATION
North and South America Europe, Middle-East, Africa
sales_americas@virtutech.com sales_emea@virtutech.com
Asia-Pacific Japan
sales_apac@virtutech.com sales_japan@virtutech.com
http://www.virtutech.com
THIS PUBLICATION IS PROVIDED ”AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
THIS PUBLICATION COULD INCLUDE TECHNICAL INACCURACIES OR TYPOGRAPHICAL ERRORS. CHANGES
ARE PERIODICALLY ADDED TO THE INFORMATION HEREIN; THESE CHANGES WILL BE INCORPORATED IN
NEW EDITIONS OF THE PUBLICATION. VIRTUTECH MAY MAKE IMPROVEMENTS AND/OR CHANGES IN THE
PRODUCT(S) AND/OR THE PROGRAM(S) DESCRIBED IN THIS PUBLICATION AT ANY TIME.
Virtutech, Inc., 2001 Gateway Place, Suite 201E, San Jose, CA 95110, USA