Although its name suggests that the Watch Memory window can display only values in the memory, the capabilities of this tool are richer.
You can define watch expressions and define their display format. For example, if you want to display for consecutive bytes in the memory pointed by the HL register in bitvector format (32 bits), you can do this with this command:
+ [HL :DW] :%32
Later, you need to display the 16-bit value in the memory address pointed by HL plus BC. You can use this command:
+ [HL + BC :W] :W
Watch expressions are arithmetic expression using C-like expression syntax. The engine behind the Watch Memory window continuously evalutes these expressions, formats them and displays their values.
When working with expressions, the engine uses for integral types, and automatically converts them:
- Boolean: Simple
FALSE. Any non-zero value is converted to
TRUE, zero is
- Byte: 8-bit unsigned value
- Word: 16-bit unsigned value
- Double word: 32-bit unsigned value
When working with values, the expression engine automatically converts operation result to keep all valuable bits. For example, when you multiply two bytes, the result will be a word. Multiplying a byte and a word results a double word. Nonetheless, when you multiply two double words, the result will be a double word, so only the last 32 bits are kept.
Comparisons and other logical operations result in booleans.
You do not need to deal with types, the evaluation engine does it for you. Sometimes you need signed types. With the help of the
:-DWformat specifiers, as you will learn later, you can display values in signed form.
You have several value sources that you can use in expressions:
- Literal values (decimal, hexadecimal, binary, character)
- Compilation symbols
- Z80 register values
- Z80 CPU flag values
- ZX Spectrum memory contents
In the future you may use ULA-specific values, or memory values of non-paged memory banks.
The expression syntax provides these types of literals:
Decimal numbers. You can use up to 5 digits (0..9) to declare a decimal number. Examples: 16, 32768, 2354.
Hexadecimal numbers. You can use up to 4 hexadecimal digits (0..9, a..f or A..F) to declare a hexadecimal literal. The engine recognizes one of the
$prefix, or one of the
Hsuffixes. If you use the
Hsuffixes, the hexadecimal number should start with a decimal digit
Here are a few samples:
#12AC 0x12ac $12Ac 12ACh 12acH 0AC34H
- Binary numbers. Literal starting with the one of the
0bprefix are taken into account as binary literals. You can follow the prefix with up to 16
1digits. To make them more readable, you can separate adjacent digits with the underscore (
_) character. These are all valid binary literals:
%01011111 0b01011111 0b_0101_1111
You can use negative number with the minus sign in front of them. Actually, the sign is not the part of the numeric literal, it is an operator.
- Characters. You can put a character between single quotes (for example:
You can use escape sequences to define non-visible or control characters:
To declare a character by its binary code, you can use the
\xHH sequences (
H is a hexadecimal digit). For example, these
escape sequence pairs are equivalent:
You can use symbols to refer to labels and other constants used in Z80 programs. Identifiers must start with
a letter (a..z or A..Z) or the underscore character (
_). The subsequent characters
letters, digits, or underscores. Here are a few samples:
MyCycle ERR_NO Cycle_4_Wait
There are strings that can be both identifiers or hexadecimal literals with the
FADH. The engine considers such strings as symbols. To use hexadecimal literal, use a
0FADHis a hexadecimal literal, while
FADHis an identifier.
The engine usus the symbols within the Z80 program you compile, inject, run or debug.
The SpecteNetIde Z80 Assembler handles symbols with string values. These symbols retrieve 0 (word) value.
The expression engine recognizes the standard 8-bit and 16-bit register names, as specified in the official Zilog Z80 documentation:
- 8-bit registers:
- 16-bit registers:
- For the 8-bit halves of the
IYindex registers, the engine uses these names:
YH. Alternatively, the compiler accepts these names, too:
IYH. As a kind of exception to general naming conventions, these mixed-case names are also accepted:
- The engine recognizes the
WZ(internal Z80 register, not accessible programmatically) register, too.
The expression engine can access the Z80 CPU flags individually, and it also has tokens for the inverted flag values:
|` P||True, if S flag (Bit 7) is set|
|` M||True, if S flag (Bit 7) is reset|
|` Z||True, if Z flag (Bit 6) is set|
|` NZ||True, if Z flag (Bit 6) is reset|
|` 5||True, if R5 flag (Bit 5) is set|
|` N5||True, if R5 flag (Bit 5) is reset|
|` H||True, if H flag (Bit 4) is set|
|` NH||True, if H flag (Bit 4) is reset|
|` 3||True, if R3 flag (Bit 3) is set|
|` N3||True, if R3 flag (Bit 3) is reset|
|` PE||True, if PV flag (Bit 2) is set|
|` PO||True, if PV flag (Bit 2) is reset|
|` N||True, if N flag (Bit 1) is set|
|` NN||True, if N flag (Bit 1) is reset|
|` C||True, if C flag (Bit 0) is set|
|` NC||True, if C flag (Bit 0) is reset|
ZX Spectrum Memory Contents
You can query the entire 64K memory available by the Z80 CPU. With a memory indirection expression, you can query byte, word, and double word values, respectively:
You wrap an expression and an optional access specifier between square brackets. The expression specifies the memory address, the access specifier sets the number of bytes to query from the memory:
:B— A single byte stored in the specified memory address.
:W— Two consequtive bytes stored in the specified address and the next one.
:DW— Four consequtive bytes stored in the specified memory address and the next three.
If you omit the access specifier, the default is
When you query multiple bytes from the memory, the first byte is the LSB (least significant byte), the last is the MSB (most significant byte). Lets assume, you store these four bytes at the address #4000:
#12, #4A, #C3, #78
[#4000:B]) expressions retrieve
[#4000:W]expression results in
[#4000:DW]expression results in
You can use about a dozen operators, including unary, binary and ternary ones. In this section you will learn about them. I will introduce them in descending order of their precendence.
The engine supports using only one ternary operator, the conditional operator:
This operation results in -1:
2 > 3 ? 2 : -1
When conditional-expression evaluates to true, the operation results in true-value; otherwise in false-value.
Conditional expressions are evaluated from right to left, in contrast to binary operators, which use left-to-right evaluation.
Binary Bitwise Operators
||5||Less than or equal|
||5||Greater than or equal|
The bits of the left operand are shifted by the number of bits given by the right operand.
Basic Arithmetic Operators
||9||Unary bitwise NOT|
||9||Unary logical NOT|
Do not forget, you can change the default precendence with
), for example:
(4 + 2) * 3.
With format specifiers, you can declare how would you like to display the results of watch expressions. These are optional, if you do not use them, the engine will select the format according to the type of the expressions’s value:
- Boolean values —
- Byte values —
- Word values —
- Double word values —
This table lists the format specifiers supported by the watch engine:
||Flag format. When the value is zero, the output is
||Byte format. The engine displays both the hexadecimal value in two hexadecimal digits, plus the decimal value. For example:
||Signed byte format. Similar to
||Character format. The value is converted to a byte, and the ASCII character value of the byte is displayed. When the character code is between 32 and 126, the character is displayed, otherwise a hexadecimal character escape is used. For example,
||Word format. The engine displays both the hexadecimal value in four hexadecimal digits, plus the decimal value. For example:
||Signed word format. Similar to
||Double word format. The engine displays both the hexadecimal value in eight hexadecimal digits, plus the decimal value. For example:
||Signed double word format. Similar to
||4 hexadecimal digits. The result is converted to a word and displayed in hexadecimal format, bytes in LSB/MSB order. For example,
||8 hexadecimal digits. The result is converted to a double word and displayed in hexadecimal format, bytes in LSB/MSB order. For example,
||8 binary digits. The result is converted to a byte and displayed in binary format. For example,
||16 binary digits. The result is converted to a word and displayed in binary format, bytes in MSB/LSB order. For example,
||32 binary digits. The result is converted to a double word and displayed in binary format, bytes in MSB/LSB order. For example,
In the future, the list of format specifiers may extend.
Please note, while
:H8 use LSB/MSB byte order,
:%32 apply MSB/LSB order. You may use the
:H8 formats to display memory
contents in the order of bytes as they are stored in the memory. Lets assume, you store these
four bytes at the address #4000:
#12, #4A, #C3, #78
This is how watch expressions display the content:
#12 #00(Do not forget, the expression reads only a single byte from the memory!)
#4A12 (18962)(This expression reads two bytes from the memory)
#12 #4A(This expression reads two bytes from the memory)
#78C34A12 (2026064402)(This expression reads four bytes from the memory)
#12 #4A #C3 #78(This expression reads four bytes from the memory)