Copyright © 2002, 2004 Miloslav Trmač
You can redistribute and/or modify this program under the terms of the GNU General Public License version 2 as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place Suite 330, Boston, MA 02111-1307, USA.
Table of Contents
List of Tables
The cereal emulator is an emulation framework featuring an Intel 8051 emulator and powerful abilities to emulate connected peripherals, be it the common ones (e.g. external memory) or any custom peripherals your application uses.
This document will guide you through operating cereal. You'll learn how to emulate 8051 programs and how to connect peripherals. Then you'll learn about all cereal built-in modules and finally, in the last chapter you'll find description of the command-line utilities provided with cereal.
Table of Contents
While cereal is based on a generic framework able to support any CPU[1], currently only an Intel 8051 core has been implemented. In this chapter you'll learn how to use generic cereal features and the 8051 interface.
Because cereal can support virtually infinite number of hardware configurations, you need to set up the hardware configuration you want to use. To do this, run cereal_khwconf, the cereal hardware configuration utility.
Main window of cereal_khwconf
Hardware is represented in cereal as a list of modules with optional connections. All we want to emulate is a single 8051, so we need to add an 8051 module. Select ->
cereal_khwconf Insert dialog
Select 8051 as Module type and name
the module cpu
. After you click
, the cpu
module
appears in the list.
There are many 8051-compatible CPUs differing for example in built-in data memory size and built-in program size. If your application is timing-dependent, you may also want to configure the frequency of CPU clock (although this is useful only if your are also emulating the hardware the CPU is communicating with, such as an UART of the communication partner). To configure the CPU, select in context menu of the module (or just double-click the module).
cereal_khwconf 8051 configuration
After setting up the options, save the configuration.
Now you can finally run cereal itself, cereal_kde. Open the configuration file you have just created and cereal will start and automatically load the 8051 UI (user interface) extension. This extension provides an 8051-specific window, which you can open by selecting -> .
cereal 8051 UI
All that is left is to load your program. Select
addregs.hex
(which adds values of
R0, R1, …
R7, puts the result to DPTR and
loops infinitely) is included with this tutorial.
Now you can start emulating your program, using F8). In the left part, you'll see a disassembling appear. The list contains recently executed instructions, and the separate box below shows next instruction to be executed.
-> (The right part contains most 8051 SFRs (special function registers), all of which you can modify by typing in the desired value and pressing Enter or selecting a different register. When you select an 8-bit SFR, the Bits box below updates to contains bits of this register, so you can also examine and modify the registers bitwise. This is particularly useful when examining PSW (program status word) or any of the MOD and CON mode and control registers.
When modifying a register, you can insert any valid
cereal expression, such as 1 + 1
. You'll
learn about cereal expressions in the next two sections.
You may notice that you can't modify the lowest bit of PSW. That is because this bit contains parity of the accumulator ACC, and cereal enforces the right value of the parity bit.
If you used the provided addregs.hex
,
you may have had a small difficulty when checking the result. This
stems from the fact that the 16b DPTR register is
actually composed of two separate 8b registers, DPH
and DPL. Therefore you had to calculate the total
value by hand.
This problem can be solved using a watch. By selecting Insert or selecting from the context menu.
-> you can open a watch window. Then you can add a watch expression by pressingAdd Watch window
We want to see value of 256 * DPH +
DPL. Because cereal can emulate much more than a
simple CPU (in fact, you can easily emulate three
CPUs running in parallel), we must tell cereal
which module should it read the values from. In our case, the module
is named cpu
(this is the name given when
configuring hardware). Additionally, there is more to the
CPU than the registers—you may also want to
watch a byte of internal memory, or state of a CPU
pin.
This all is contained in the concept of a
port, which uniquely identifies a module and a
variable the module exports. That's why there is the
cpu
, space
sfr
(spaces are groups
of ports with similar properties) and port
DPH
.
You can either select the port name from the drop-down
list, or type it in, or type the numerical index (in our case,
DPH has 8051 address 0x83, so the offset is 0x03, as
described in the sfr space comment). Using the number is obviously
bad idea here, but not all ports have names—for example
mem
, the internal memory space, contains
unnamed ports for the whole memory space, which can be specified only
using the numerical index.
After selecting the port, click OK and the a port
reference ([cpu/sfr/DPH]
) will appear
in the Expression box. Append * 256
+
and insert port reference to the DPL
register. After clicking , the watch will be
added to the list and updated whenever contents of the registers
change.
If you don't like the Port dialog box, you can just type the port reference in, any mistakes you might make will be reported in the Current Value field.
Another annoying thing when using the sample program is that in order to see the value, you need to step through almost 70 instructions before you see the result. This certainly disqualifies cereal from replacing your calculator application ;-). And because 8051 lacks any HALT or STOP instruction, you can't just let the program run, because simple inspection shows that the program is ended by an infinite loop at address 0x0023. The solution is to create a breakpoint which triggers when the program counter reaches 0x0023.
Breakpoints are conditions that are periodically evaluated
and stop emulation when they trigger. In
cereal, breakpoints are ordinary expressions which trigger whenever
they evaluate to a non-zero value. Breakpoints are defined in the
same way as watches, except that you use a Breakpoints window (opened
by -> ). Enter the expression
[cpu/misc16/PC] == 0x0023
(of course, you can
use the Port dialog to insert reference to PC). Now you can just use
-> (F9) to
run through the whole program and get instant results.
When a breakpoint triggers, it is automatically deactivated until it evaluates to a zero value at least once. This allows you to easily break program execution on a leading edge of a signal (such as the ALE (Address Latch Enable) output from the CPU). Breaking program execution on falling edge of a signal can be accomplished by negating the breakpoint condition.
You have seen how to use expressions for watches, breakpoints and modification of 8051 registers. This sections aims to be a complete reference of what you can and cannot do using cereal expressions.
An expression is build from operands and operators, which may optionally be separated by white space. All expressions are evaluated in the widest signed integral type your compiler supports (which is at least 64 bits wide). The expressions are based on C programming language, so if you know C, most of the description should sound familiar.
Valid operands include:
Numeric constants can be expressed in decimal,
octal (with leading 0
) or hexadecimal (with
leading 0x
).
Character constants have value defined by the
character set used by your OS. A character constant is formed by
enclosing a single character in apostrophes, like this:
'A'
. Instead of a single character, you can
also use a C-like escape sequence:
\' | The apostrophe character itself (you can't use ''' to represent it). |
\" ,
\? | The
" and
? characters. These
escape sequences are provided only for
compatibility with C. |
\a | Alarm (BEL) character. |
\b | Backspace (BS) character. |
\f | Form-feed (FF) character. |
\n | New-line (LF) character. |
\t | Horizontal tabulator (HT) character. |
\r | Carriage-return (CR) character. |
\v | Vertical tabulator (VT) character. |
\ | Character with given code (in octal, up to three digits). |
\x | Character with given code (in hexadecimal, any number of digits) |
\\ | The \ character
itself (single \
character is recognized as a start of an escape
sequence). |
Port references are in the form
[
or
module
/space
/port
][c:
.
In most cases, there is the button
available for easy port reference creation. The meaning of the
module
/space
/port
][c:
… form will be explained in the
following section.
Valid operators, listed in order of
decreasing precedence (can be overridden by using parentheses
(
and )
):
Unary operators
+
a | Unary plus | Unchanged value of
a |
-
a | Unary minus | Value of a
negated |
!
a | Logical NOT | 1 if a is 0, 0
otherwise |
Multiplicative operators
a
*
b | Multiplication | a multiplied by
b |
a
/
b | Division | a divided by
b , rounded toward
zero |
a
%
b | Modulus | Remainder of dividing
a by
b . Note that when
a is negative, the
remainder is negative too. |
Additive operators
a
+
b | Addition | a added to
b |
a
-
b | Subtraction | b subtracted from
a |
Bitwise shift operators
a
<<
b | Bitwise shift left | a shifted to the left by
b bits |
a
>>
b | Bitwise shift right | a shifted to the
right by b bits. Note
that results of shifting negative numbers right
may vary between different computer
architectures. |
Relational operators
a
<
b | Less than | 1 if a is less
than b , 0
otherwise |
a
>
b | Greater than | 1 if a is
greater than b , 0
otherwise |
a
<=
b | Less than or equal | 1 if a is less
than or equal to b , 0
otherwise |
a
>=
b | Greater than or equal | 1 if a is
greater than or equal to
b , 0 otherwise |
Equality operators
a
==
b | Equal | 1 if a is equal
to b , 0
otherwise |
a
!=
b | Not equal | 1 if a is not
equal to b , 0
otherwise |
a
&
b | Bitwise AND | Each bit of result is 1 if the
corresponding bits of both
a and
b are 1, 0
otherwise |
a
^
b | Bitwise XOR | Each bit of result is 1 if the
corresponding bits of a
and b are not
equal, 0 otherwise |
a
|
b | Bitwise OR | Each bit of result is 1 if at least one of
the corresponding bits of
a and
b is 1, 0
otherwise |
a
&&
b | Logical AND | Result is 1 if both
a and
b are non-zero, 0
otherwise |
a
||
b | Logical OR | Result is 1 if at one of
a and
b is non-zero, 0
otherwise |
a
?
b
:
c | Condition | b if
a is non-zero,
c otherwise |
Note that unlike in C, all expressions are completely
evaluated and the &&
,
||
and ? :
operators
have no shortcut semantics. Another difference from C is that the
~
(bitwise negation) operator is not included.
The reason is that cereal expressions have no associated type and
the result would be bitwise negation in the integral type expressions
are evaluated with, which is probably not what you want. Instead of
~
, use explicit bitwise XOR: to negate an 8-bit
value x
, use
.x
^ 0xFF
The last feature of the cereal UI is the Evaluate/Modify dialog box. Invoke it by selecting -> (F4).
Evaluate/Modify window
The Evaluate/Modify dialog box can be used to compute value of an expression you don't need as a watch (this is the real replacement of your calculator application). Just enter the expression and the value will be computed as you type.
The dialog box can be also used for modification of memory (or generally port) values that are not available in the 8051 interface by referencing the port in the Expression box and writing the new value to the New Value box.
The string you enter to the New Value
box can of course be any valid cereal expression. If you feel
bored, set Expression to
[cpu/sfr/ACC]
, New Value
to [cpu/sfr/ACC] + 1
and hold the
Enter key for a while.
When modifying a port value, you can use any valid
cereal expression in the Expression box just as
in the New Value box. Thus almost the same
effect as in the previous paragraph can be achieved by setting
Expression to [cpu/sfr/ACC] -
1
and New Value to
[cpu/sfr/ACC]
. cereal contains a minimal
equation solver, which allows you to modify values of simple
expressions without computing the solution yourself.
The equation solver is quite a trivial piece, so about the
best you can expect it to solve is a linear equation, it can't solve
[cpu/sfr/ACC] * [cpu/sfr/ACC] = 4
. The general
rule is that the expression to be modified can only contain a single
port reference, together with the fact that cereal refuses to modify
the port value if there are multiple possible solutions.
Of course, changing the register values in the 8051 window is done using the same mechanism and modifying the value in the ACC edit box is equivalent to doing it in the Evaluate/Modify dialog box.
If you know the 8051 architecture a bit, you'll recall
that the R0, …, R7 registers are not registers with fixed
addresses at all. R0, …, R7 stand for internal memory
locations either 0, … 7 or 8, …, 15 or 16, …, 23
or finally 24, … 31 according to RS1 and
RS0 bits of PSW. This is still
possible to implement using cereal expressions. For example, the
expression for value of R0 is ([c:@/sfr/PSW] & 0x10) == 0 ?
(([c:@/sfr/PSW] & 0x08) == 0 ? [@/mem/0] : [@/mem/8]) :
(([c:@/sfr/PSW] & 0x08) == 0 ? [@/mem/16] : [@/mem/24])
,
where @
represents the module name. This
expression is much more complicated than what the rule above allows
for expressions that the solver can handle (five different port
references, one of them repeated three times). But we know that we
want to modify one of the
[@/mem/
values and
not the x
][@/sfr/PSW]
value. Moreover, depending
on the value of [@/sfr/PSW]
, one simple
expression (which can be handled by the solver) is selected. In this
expression, we have helped the solver by inserting the
c:
(meaning constant)
prefix to the PSW port references. This tells the
solver that these ports are not to be modified. The solver takes
these port references as if it were constants and with a bit of
constant expression evaluation it can reduce the expression to the
[@/mem/
form,
which can be handled easily.x
]
[1] The code currently does not support data paths wider than 16 bits, but it is simple to extend it up to number of bits of widest integral data type your machine supports.
The main strength of cereal (and, indeed, the very reason of its existence) is the ability to emulate sets of connected devices as a group and support of comparatively easy adding of new devices. In this chapter you'll see this ability demonstrated by connecting an external 64K × 8 memory chip to the 8051 CPU. To be able to use 8051 MOVX instructions, we need to connect port P2 to higher 8 memory address pins, port P0 to memory data pins. Lower 8 bits of memory address are also provided at port P0, and are supposed to be latched by the ALE signal. The whole situation is depicted in the following schema:
In cereal, this is represented as a set of three modules, representing the CPU, the latch and the memory chip, respectively. Unsurprisingly, all of them are available as cereal built-in modules. Each of these modules exports the “pins of the emulated chip”. We need to connect the modules: for example, we want to assure that when the 8051 module changes value on the /RD pin, the memory module receives this as a change of its /OE pin. Similarly, we want that when the memory module reads current status of its /OE pin, it actually reads the value of the /RD pin of the 8051 module.
The two mentioned relationships are called
connections in cereal and are configured
using the cereal hardware configuration utility. Run
cereal_khwconf and insert three modules:
cpu
of type 8051, latch
of type byte_latch and ram
of type memory.
Don't forget to set memory data_size to
65536!
We'll start with the above mentioned examples: we want to
create an write connection from
[cpu/pin/~RD]
to
ram/pin/~OE
and a read in
the reverse direction. This situation (read/write in opposite
directions) naturally occurs quite often, so you can create only one
of the connections and if possible, cereal will create the reverse
one automatically.
The other two port types, display and modify are rarely used. They are the way the user interface reads and changes the port values. The difference is sometimes important, for example just reading a status register may clear an interrupt line. For example of creating display or modify connections, see description the sfr_ext space of the 8051 module in the cereal Built-In Modules chapter.
To add the connection, bring up the Properties dialog for
cpu
, choose the
Connections tab and click on
. By opening the Properties dialog for
the cpu
module, you have implicitly defined the
source module. Now select write
in
Type, 1
in
Width (single-bit connection),
pin
and ~RD
in
Source, ram
in
Destination module
and finally
pin
and ~OE
in
Destination. Note that the Add also
reverse connection checkbox is automatically checked. By
clicking you create the connection.
Add Connection window
Similarly you can continue adding other connections.
Admittedly it is a bit lengthy, but you only need to do it
once—not at all in this case, because the result,
8051_mem.xml
is available with this
tutorial.
When trying to connect this yourself, you could have
noticed that the precise configuration cannot be created, because the
P0.x
ports cannot be connected for writing
twice (to the latch and to the memory). The solution would be to add
8 "bit_tee" modules, but this has been sidestepped by using the
coincidence that the ram module always reads the values from the CPU
itself and therefore doesn't need the write connection. This is
obviously a bad kludge, but it works and I didn't have to program the
bit_tee module, insert it 8 times and create 8 more
connections.
To test this module, you can use enclosed 8051_mem.hex
, which negates all bytes of the external memory.
If you use /PSEN instead of /RD in the above example, the CPU will be able to fetch instructions from the external memory (make sure to note the function of the EA pin). In that case, the next instruction disassembling in the 8051 window is not available, because there is no way for the 8051 UI to get the value from the external memory other than to emulate the access just for the purpose of the disassembling, which of course doesn't happen in the real world.
Table of Contents
With cereal comes the all-important 8051 module and a few quite simple modules which should provide you with a starting point for creating your hardware configuration as well as your own modules.
You can write additional modules, which can be either
built-in (if you recompile cereal), or dynamically loaded, in which
case you need to point environment variable
CEREAL_MODULE_DIR
to a directory containing the
modules.
Need I say more? Emulates most of Intel 8051-compatible CPU. Unsupported features include the PD (Power Down) and IDL (Idle) bits of PCON. Also, the module has its own, built-in clock (i.e. you can't emulate clever tricks with stopping the CPU clock), although this is not terribly hard to fix (it will just slow down the emulation even more).
The module has only one unexpected feature: XMEM is a built-in “external” memory, a built-in memory accessed using the MOVX instruction. This occurs quite often in practice, e.g. Atmel 89S8252 has a built-in EEPROM accessed using MOVX. This cannot be always emulated by emulating a “true” external memory (as demonstrated in the previous chapter), because using the “true” external memory uses 19 pins of the CPU, while accessing the built-in EEPROM does not, and at least 18 of them can be used for other purposes. Therefore applications that use this EEPROM and also use some of the pins cannot be emulated without having XMEM.
Table 4.1. 8051 Module Options
Name | Description |
---|---|
data_mem_size | Size of internal data memory, one of 128, 192 and 256. |
frequency | A non-zero integer, number of oscillator pulses per second (e.g. 11059200). |
prog_mem_size | Size of internal program memory, a power of two in range [64, 65536]. |
xmem_mem_size | Size of XMEM (described above), a power of two less than or equal to 65536, or 0 to use “regular” external memory. |
load_hex | A hidden option which causes the module to load the Intel HEX file provided as a parameter. |
Table 4.2. 8051 Module Spaces
Name | Width | Description |
---|---|---|
bit | 1 | Equivalent to 8051 bit addresses. |
pin | 1 | Contains emulated 8051 pins. |
mem | 8 | The 8051 internal data memory. |
prog | 8 | The 8051 internal program memory. |
sfr | 8 | Contains the SFR address space (0x80 … 0xFF). Because port addresses are 0-based, this is mapped to range 0x00 … 0x7F. |
sfr_ext | 8 | A mirror of the sfr space for the purpose of extending the 8051 emulator. To emulate an additional SFR, create a separate module for it implementing all four port types (read, write, display modify) that make sense for the SFR, and connect it to the appropriate sfr_ext port. Then any connection your SFR in the sfr space will be forwarded to the module you connected to the sfr_ext space. |
prev_insn | 8 | Contains the last executed instruction. Used in the KDE UI plug-in. |
next_insn | 8 | Contains the next executed instruction, if not using external memory. Used in the KDE UI plug-in. |
misc16 | 16 | Contains the program counter and its value at previous instruction. |
This module has a single port that can be written by other modules and reports changes of its value.
This module has a single port that can be read or written by other modules or the user. It is backward-compatible with the bit_constant module from cereal versions 0.93.3 and earlier.
This module emulates a 8-bit latch. If somebody cares, it was created using documentation of Czech chip MH74ALS373.
This module has a single port that can be read or written by other modules or the user. It is backward-compatible with the byte_constant and byte_sink modules from cereal versions 0.93.3 and earlier.
This module emulates a static memory of up to 64K × 8.
Emulates a very simple UART. Connect RXD and the module will write any received data to RX, write data to TX and the module will transmit it on TXD. After completion it will set RX_done or TX_done, respectively, to 1 (stays 1 until cleared by writing a 0).
Table of Contents
In the course of writing cereal, two command-line utilities were created. They are used in the automatic testsuite created during cereal development, but they may be useful also to you.
The cereal_disasm utility is a simple 8051 disassembler. When invoked with arguments, it treats them as file names of Intel HEX files, loads them all in the order given on the command line. When given no arguments, it reads one Intel HEX file from standard input instead. Then it outputs the disassembling to standard output.
The cereal_text utility is a text interface to the cereal emulation framework, which is able to do most of what is available using the GUI. It can be used for automatic regression testing not only of cereal itself, but also of your applications.
The user interface is admittedly crude. It expects commands on the standard input and writes results to the standard output. An empty line means to repeat the previous command.
Table 5.1. cereal_text commands
Synopsis | Description |
---|---|
help
or
| Gives short help on usage of
command . |
mod_new
| Creates a new module name of type type . |
mod_delete
| Deletes module name |
option
| Displays current value of option
option of module
module . |
option
value | Sets option option of
module module to
value . |
connect_1
| Connects for type bit port port_1 to port_2 . |
mod_rename
| Renames module old to new |
breakpoint | Creates a new breakpoint on expression |
bp_list | Lists defined breakpoints, along with their ID numbers. If a breakpoint currently evaluates to nonzero, it is marked by a "+" character. |
bp_del
| Deletes breakpoint with ID id . |
print
| Prints current value of
expression . |
set | Sets port referenced in
dest so that
dest ==
src . |
step | Runs one step of emulation. If there is a list
of condition s specified, runs
until one of them is met. Valid conditions are error,
warning, insn (end of instruction) and
breakpoint. |
setup_load
| Loads hardware setup from
file |
setup_save
| Saves hardware setup to
file |
state_load
| Loads emulation state from
file |
state_save
| Saves emulation state to
file |