User's Tutorial to cereal

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

1. Introduction
2. Emulating Intel 8051
Setting Up Hardware
8051 Emulator Interface
Watches and Breakpoints
cereal Expressions Compendium
Evaluate/Modify
3. Connecting External Devices
4. cereal Built-In Modules
The 8051 module
The bit_constant Module
The bit_report Module
The bit_value Module
The byte_constant module
The byte_latch module
The byte_sink module
The byte_value Module
The memory Module
The uart Module
5. cereal Command-Line Utilities
cereal_disasm
cereal_text

List of Tables

4.1. 8051 Module Options
4.2. 8051 Module Spaces
4.3. bit_report Module Options
4.4. bit_report Module Spaces
4.5. bit_value Module Spaces
4.6. byte_latch Module Spaces
4.7. byte_value Module Spaces
4.8. memory Module Options
4.9. memory Module Spaces
4.10. uart Module Options
4.11. uart Module Spaces
5.1. cereal_text commands

Chapter 1. Introduction

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.

Chapter 2. Emulating Intel 8051

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.

Setting Up Hardware

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 Item->Insert…

cereal_khwconf Insert dialog

Select 8051 as Module type and name the module cpu. After you click OK, 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 Properties… in context menu of the module (or just double-click the module).

cereal_khwconf 8051 configuration

After setting up the options, save the configuration.

8051 Emulator Interface

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 View->cpu.

cereal 8051 UI

All that is left is to load your program. Select 8051->Load Program and load your program (in Intel HEX format). If you don't have one, a sample program 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 Run->Step (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.

Note

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.

Watches and Breakpoints

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 Data->New Watch Window you can open a watch window. Then you can add a watch expression by pressing Insert or selecting Insert… from the context menu.

Add 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 Port… button. Click it and in the dialog select module cpu, space sfr (spaces are groups of ports with similar properties) and port DPH.

Note

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 OK, the watch will be added to the list and updated whenever contents of the registers change.

Note

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 Data->New Breakpoint Window). 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 Run->Run (F9) to run through the whole program and get instant results.

Note

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.

cereal Expressions Compendium

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

Numeric constants can be expressed in decimal, octal (with leading 0) or hexadecimal (with leading 0x).

Character constants

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.
\aAlarm (BEL) character.
\bBackspace (BS) character.
\fForm-feed (FF) character.
\nNew-line (LF) character.
\tHorizontal tabulator (HT) character.
\rCarriage-return (CR) character.
\vVertical tabulator (VT) character.
\oooCharacter with given code (in octal, up to three digits).
\xx…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

Port references are in the form [module/space/port] or [c:module/space/port]. In most cases, there is the Port... button available for easy port reference creation. The meaning of the [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

    + aUnary plusUnchanged value of a
    - aUnary minusValue of a negated
    ! aLogical NOT1 if a is 0, 0 otherwise
  • Multiplicative operators

    a * bMultiplicationa multiplied by b
    a / bDivisiona divided by b, rounded toward zero
    a % bModulusRemainder of dividing a by b. Note that when a is negative, the remainder is negative too.
  • Additive operators

    a + bAdditiona added to b
    a - bSubtractionb subtracted from a
  • Bitwise shift operators

    a << bBitwise shift lefta shifted to the left by bbits
    a >> bBitwise shift righta shifted to the right by b bits. Note that results of shifting negative numbers right may vary between different computer architectures.
  • Relational operators

    a < bLess than1 if a is less than b, 0 otherwise
    a > bGreater than1 if a is greater than b, 0 otherwise
    a <= bLess than or equal1 if a is less than or equal to b, 0 otherwise
    a >= bGreater than or equal1 if a is greater than or equal to b, 0 otherwise
  • Equality operators

    a == bEqual1 if a is equal to b, 0 otherwise
    a != bNot equal1 if a is not equal to b, 0 otherwise
  • a & bBitwise ANDEach bit of result is 1 if the corresponding bits of both a and b are 1, 0 otherwise
  • a ^ bBitwise XOREach bit of result is 1 if the corresponding bits of a and b are not equal, 0 otherwise
  • a | bBitwise OREach bit of result is 1 if at least one of the corresponding bits of a and b is 1, 0 otherwise
  • a && bLogical ANDResult is 1 if both a and b are non-zero, 0 otherwise
  • a || bLogical ORResult is 1 if at one of a and b is non-zero, 0 otherwise
  • a ? b : cConditionb 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.

Evaluate/Modify

The last feature of the cereal UI is the Evaluate/Modify dialog box. Invoke it by selecting Data->Evaluate/Modify… (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/x] values and not the [@/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/x] form, which can be handled easily.



[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.

Chapter 3. Connecting External Devices

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.

Note

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 New.... 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 OK 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.

Note

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.

Note

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.

Chapter 4. cereal Built-In Modules

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.

The 8051 module

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

NameDescription
data_mem_sizeSize of internal data memory, one of 128, 192 and 256.
frequencyA non-zero integer, number of oscillator pulses per second (e.g. 11059200).
prog_mem_sizeSize of internal program memory, a power of two in range [64, 65536].
xmem_mem_sizeSize of XMEM (described above), a power of two less than or equal to 65536, or 0 to use “regular” external memory.
load_hexA hidden option which causes the module to load the Intel HEX file provided as a parameter.

Table 4.2. 8051 Module Spaces

NameWidthDescription
bit1Equivalent to 8051 bit addresses.
pin1Contains emulated 8051 pins.
mem8The 8051 internal data memory.
prog8The 8051 internal program memory.
sfr8Contains the SFR address space (0x80 … 0xFF). Because port addresses are 0-based, this is mapped to range 0x00 … 0x7F.
sfr_ext8A 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_insn8Contains the last executed instruction. Used in the KDE UI plug-in.
next_insn8Contains the next executed instruction, if not using external memory. Used in the KDE UI plug-in.
misc1616Contains the program counter and its value at previous instruction.

The bit_constant Module

This module has been replaced by byte_value.

The bit_report Module

This module has a single port that can be written by other modules and reports changes of its value.

Table 4.3. bit_report Module Options

NameDescription
nameA string the module prepends to its value change reports.

Table 4.4. bit_report Module Spaces

NameWidthDescription
bit1Contains the bit.

The bit_value Module

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.

Table 4.5. bit_value Module Spaces

NameWidthDescription
bit1Contains the bit.

The byte_constant module

This module has been replaced by byte_value.

The byte_latch module

This module emulates a 8-bit latch. If somebody cares, it was created using documentation of Czech chip MH74ALS373.

Table 4.6. byte_latch Module Spaces

NameWidthDescription
pin1Contains the pins of the latch.

The byte_sink module

This module has been replaced by byte_value.

The byte_value Module

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.

Table 4.7. byte_value Module Spaces

NameWidthDescription
byte8Contains the byte.

The memory Module

This module emulates a static memory of up to 64K × 8.

Table 4.8. memory Module Options

NameDescription
data_sizeA power of two less than or equal to 65536, capacity of the memory module in bytes. The module ignores unneeded address lines.

Table 4.9. memory Module Spaces

NameWidthDescription
pin1Contains the memory pins.
data8Contains the data kept in the memory.

The uart Module

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 4.10. uart Module Options

NameDescription
baud_rateAn integer, baud rate the UART is using.
data_bitsA non-zero integer less than or equal to 13, number of bits in one data unit.

Table 4.11. uart Module Spaces

NameWidthDescription
pin1Contains the UART pins.
data16Contains the received and transmitted data units.

Chapter 5. cereal Command-Line Utilities

Table of Contents

cereal_disasm
cereal_text

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.

cereal_disasm

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.

cereal_text

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

SynopsisDescription
help command or command --helpGives short help on usage of command.
mod_new type nameCreates a new module name of type type.
mod_delete nameDeletes module name
option module optionDisplays current value of option option of module module.
option module option valueSets option option of module module to value.
connect_1 type port_1 port_2Connects for type bit port port_1 to port_2.
mod_rename old newRenames module old to new
breakpoint expressionCreates a new breakpoint on expression
bp_listLists defined breakpoints, along with their ID numbers. If a breakpoint currently evaluates to nonzero, it is marked by a "+" character.
bp_del idDeletes breakpoint with ID id.
print expressionPrints current value of expression.
set dest = srcSets port referenced in dest so that dest == src.
step condition…Runs one step of emulation. If there is a list of conditions specified, runs until one of them is met. Valid conditions are error, warning, insn (end of instruction) and breakpoint.
setup_load fileLoads hardware setup from file
setup_save fileSaves hardware setup to file
state_load fileLoads emulation state from file
state_save fileSaves emulation state to file