==============================================================================
Introduction to CP-1600 Programming on the Intellivision
By Joe Zbiciak
$Id: intro_to_cp1600.txt,v 1.9 2002/01/27 23:31:53 im14u2c Exp $
==============================================================================
----------
Foreword
----------
Hello! Welcome to the wonderful world of the CP-1600 assembly
language.
This document aims to be a gentle introduction to the CP-1600
processor and its assembly language. As a result, this documentation
is somewhat simplistic and not necessarily a complete explanation
of the CP-1600. Rather, it's meant to help bridge the gap of
understanding from programming languages that you may be familiar
with to the environment that the CP-1600 offers. (Note: The actual
chip used in the Intellivision is the CP-1610. "CP-1600" is the
"generic" or "canonical" name for the processor -- "CP-1610" is a
specific instance.)
It does not cover the specifics of Intellivision programming, as that
is covered in other documentation that comes with the Intellivision
Development Kit. William Moeller's "De Re Intellivision" is a good
source for information as well.
This document assumes that the reader is familiar with the BASIC
language, as provided on the machines of yore. (My preferred language
is C, but BASIC seems to be more widely understood amongst my target
audience.)
Since BASIC (and most high-level languages) do not provide access to
"status bits", I begin this introduction with a quick coverage of 2s-
complement arithmetic and its relationship to the CP-1600's status
flags. Also, since many BASIC users are not familiar with bitwise
boolean operators, I also include a boolean arithmetic tutorial.
Because I'm human, this document may have some minor glitches here
and there. Also, there's a good chance that I'll add more material
and/or clarify some of the sections as I receive feedback. I'm
always open to feedback. My email address is 'im14u2c AT primenet DOT com'.
I plan to keep an up-do-date copy available at my Intellivision page:
http://www.primenet.com/~im14u2c/intv/
I hope you find this document useful as you explore the world of
CP-1600 programming.
At this point, I'd like to offer special thanks to Shane Fell,
William Moeller, and Doug "Foom the Avenger" Parsons for reviewing
this document and generally providing me with sufficient motivation
to write it in the first place. Thanks!
Enjoy,
--Joe Zbiciak
==============================================================================
Table of Contents
==============================================================================
Section 1. General Computer Arithmetic Overview
1.1 Binary, Decimal and Hexadecimal Numbers
1.2 2s Complement Representation
1.3 Status Flags
1.4 Boolean Logic
1.5 Shift and Rotate Operators
Section 2. Architecture Overview
2.1 System Overview
2.2 Registers
2.3 Addressing Modes
2.4 Double Byte Data
Section 3. Jumps and Branches
3.1 Unconditional Jumps
3.2 Jumps to Subroutines
3.3 Conditional Branches
Section 4. Instruction Set Reference
4.1 Legend and Notes
4.2 Arithmetic/Boolean Instructions
4.3 Shift/Rotate Instructions
4.4 Jump Instructions
4.5 Conditional Branch Instructions
4.6 Status Bit / CPU Control Instructions
Section 5. Examples
Section 6. Glossary of Terms
==============================================================================
Section 1: General Computer Arithmetic Overview
==============================================================================
-----------------------------------------------
1.1: Binary, Decimal and Hexadecimal Numbers
-----------------------------------------------
Numbers in the computer are stored as sequences of 0s and 1s, known as
binary numbers. On the CP-1600, these numbers are typically integers
(aka. whole numbers). The binary numbering system for integers is
very similar to the decimal system we all know and love.
In the decimal system, we assign values to digits based on their
position as counted from the right. For instance, the rightmost digit
is in the "ones" place, the next is in the "tens" place, and so on.
The diagram below illustrates the weight given to each digit in an
'n' digit decimal number: (each box is a digit)
n-1 n-2 2 1 0
10 10 10 10 10
+-----+-----+- -+-----+-----+-----+
| | | ... | | | |
+-----+-----+- -+-----+-----+-----+
(100) (10) (1)
n
Here, 10 means "10 raised to the nth power". Notice how
systematically weights are assigned to digits. In the 'Nth'
N-1
position from the right, the weight is 10 . We say that
decimal is a "base-10" numbering system for this reason.
The value of a decimal number is equal to the value of each
digit times the weight corresponding to that digit's position.
For instance, the value of the decimal number "6942" is
3 2 1 0
(6 * 10 ) + (9 * 10 ) + (4 * 10 ) + (2 * 10 )
= (6 * 1000) + (9 * 100) + (4 * 10 ) + (2 * 1 )
= 6000 + 900 + 40 + 2
= 6942
The binary numbering system works similarly, using powers of
2 instead of powers of 10. It is a "base-2" numbering system.
Correspondingly, each digit may have one of two values, 0 or 1,
instead of 10 different values. So, our diagram changes only
slightly: (I've added a few more digits)
n-1 n-2 4 3 2 1 0
2 2 2 2 2 2 2
+----+----+- -+----+----+----+----+----+
| | | ... | | | | | |
+----+----+- -+----+----+----+----+----+
16 8 4 2 1
The value of a binary number, then, can be figured out in much
the same manner as for decimal. For instance, suppose we have
the binary number "11010".
4 3 2 1 0
(1 * 2 ) + (1 * 2 ) + (0 * 2 ) + (1 * 2 ) + (0 * 2 )
= (1 * 16) + (1 * 8) + (0 * 4) + (1 * 2) + (0 * 1)
= 16 + 8 + 2
= 26
So, the number "11010" in binary equals "26" in decimal.
There is an additional numbering system that is often encountered
when coding in assembly language, known as "hexadecimal", or "hex"
for short. Hexadecimal is base-16, in contrast to the base-2 and
base-10 of binary and decimal. That means that a hex digit can
take on one of 16 values. In most places, the letters "A" through
"F" are used to denote digit values "10" through 15".
As you might guess, the value of a hexadecimal number can be
calculated in the same way as for binary and decimal numbers.
For example, consider the hex number "5AC4":
3 2 1 0
(5 * 16 ) + (10 * 16 ) + (12 * 16 ) + (4 * 16 )
= (5 * 4096) + (10 * 256) + (12 * 16 ) + (4 * 1)
= 20480 + 2560 + 192 + 4
= 23236
So, the number "5AC4" in binary equals "23236" in decimal.
Hexadecimal numbering is convenient because each hex digit maps to
exactly four binary digits. It serves as a convenient short-hand
for binary numbers. The following table illustrates how hexadecimal
digits map to decimal and binary values.
Hex Binary Decimal
-----------------------------------------
0 0000 0
1 0001 1
2 0010 2
3 0011 3
4 0100 4
5 0101 5
6 0110 6
7 0111 7
8 1000 8
9 1001 9
A 1010 10
B 1011 11
C 1100 12
D 1101 13
E 1110 14
F 1111 15
Converting between hex and binary is much, much easier than converting
from either hex or binary to decimal or vice-versa. Hex provides a
handy short-hand for expressing arbitrary binary patterns as a result.
Consider, for instance, FFFF vs. 65535 vs. 1111111111111111.
A short word on notation: Hexadecimal and Binary constants are
usually specified via a couple different notations. Hex, being
the most popular, has the most notations, with three that are still
commonly used. In contrast, binary has only two popular notations.
Hex: 0ABCDh $ABCD 0xABCD
Bin: 01010b 0b1010
------------------------------------
1.2: 2s Complement Representation
------------------------------------
Most computers developed within the past 25 years or so use a
numeric representation known as "Two's Complement", or 2s Complement
for short. This includes our beloved CP-1600 CPU.
Notice that the description of binary representations above did not
cover negative numbers? This is purposeful.
Humans generally keep track of the sign of a number by either
assuming the number is positive, or by writing a minus sign in front
of the number to say it is negative. For most purposes, this works
just fine. Indeed, some computers use a similar representation
for their numbers, by prepending a "sign bit" which serves the same
purpose as the minus sign we all know and love, but without making
any other changes to the representation.
Unfortunately, simply carrying around a sign bit and performing
arithmetic differently based on that bit is expensive and slow in
hardware. For instance, when you go to add two numbers in this
form, the hardware needs to pick between addition and subtraction
based on the numbers being added. Ick.
2s complement notation takes a different approach that takes
advantage of how addition is actually implemented in hardware.
When we add two numbers, we work from right-to-left adding
digits. If the answer we get in one digit position is too big,
we "carry" the extra portion to the next digit position.
For instance, suppose we're adding the (decimal) numbers 456 and
172 (see the figure below). First, we'd add "6 + 2", which gives
us 8. Fine. Next, we'd add "5 + 7", which gives us 12 -- oops!
What happens here is we write the '2' and "carry the '1'". Then we
add "4 + 1" plus the "1" we carried, giving us 6. The whole process
is shown below.
(1)
4 5 6
+ 1 7 2
--------------
6 2 8
Binary arithmetic works in the same manner, only the digits are
limited to 0 and 1. Carries are also handled in the same manner.
For example:
(1)
0 1 1 0
+ 0 1 0 1
-------------------
1 0 1 1
There is one twist though: The computer is a finite device, and
so our binary numbers can only get so big.
So what does that mean? Well, what happens is that overflow occurs
when very large numbers are added together. In fact, large positive
numbers end up acting like small negative numbers. We can (and do)
use that to our advantage.
Up until now, our description of binary arithmetic has focused on
unsigned quantities. To have signed quantities, we need to have
a means for expressing negative numbers. What we would like in
our numbering system are these attributes:
a) There should be as much similarity between signed and unsigned
numbers as possible, and hopefully should be able to use the
same hardware.
b) The negative of some number 'x', when added to 'x', must
equal 0. In other words, 'x - x = x + (-x) = 0'.
c) There should be a well defined set of positive and negative
numbers.
d) Each binary pattern should map to exactly one value.
The 2s complement numbering system satisfies each of these criteria.
Let me explain the system by using the criteria as a starting point.
The first condition is easily met, as hinted to by the statement
we made above: "large positive numbers end up acting like small
negative numbers." So, the first thing we do is state that only
"normal binary arithmetic" will be used in our system, regardless
of whether the numbers are signed or unsigned.
The next condition, (b), is a bit trickier. The requirement states
that a number, when added to its negative, must equal zero. Here is
where we make use of the fact that our machine is of finite width:
As long as we ensure that our final result in the machine word is
zero, we've satisfied the requirement. Our device's machine word is
16-bits wide, so effectively all results wrap around if they get
bigger than "all 1s" (eg. 0xFFFF). What this means is that results
bigger than 0xFFFF end up looking smaller than 0xFFFF.
Consider, for instance, "x + 0x10000". On our 16-bit machine,
adding 0x10000 makes the result wrap around, and in fact, with
this particular value, it wraps around back to where it started.
So, in other words, "x + 0x10000 = x" on a 16-bit machine. We
can use that bit of trivia to devise a means for negating numbers:
1. Let x = -y.
2. x + 0x10000 = x Given.
3. -y + 0x10000 = -y Substitution.
4. 0x10000 - y = -y Commutative property.
How convenient. On a 16-bit machine, negating a number can be
accomplished by subtracting it from 0x10000 and keeping the lower
16 bits. You're probably wondering, "So what does this look like
bitwise, and does it work?" Let's see what happens with some small
numbers: (I've divided the bits into groups of four for readability
only -- the numbers are all 16- or 17-bit numbers.)
# Bit Pattern 0x10000 - Number 16-bit Result
---------------------------------------------------------------------
0 0000 0000 0000 0000 1 0000 0000 0000 0000 0000 0000 0000 0000
1 0000 0000 0000 0001 0 1111 1111 1111 1111 1111 1111 1111 1111
2 0000 0000 0000 0010 0 1111 1111 1111 1110 1111 1111 1111 1110
3 0000 0000 0000 0011 0 1111 1111 1111 1101 1111 1111 1111 1101
4 0000 0000 0000 0100 0 1111 1111 1111 1100 1111 1111 1111 1100
5 0000 0000 0000 0101 0 1111 1111 1111 1011 1111 1111 1111 1011
6 0000 0000 0000 0110 0 1111 1111 1111 1010 1111 1111 1111 1010
7 0000 0000 0000 0111 0 1111 1111 1111 1001 1111 1111 1111 1001
8 0000 0000 0000 1000 0 1111 1111 1111 1000 1111 1111 1111 1000
The first thing you might notice is that the negative of '0' is '0'
after we truncate back to 16 bits -- a good sign. The next thing you
might notice is that as the numbers at the left count upwards, their
negatives at the right look like large numbers counting backwards --
another good sign. So, now let's prove to ourself that, at least for
some of these numbers, "x + (-x)" does equal zero once the results
are truncated to 16 bits.
Example 1: 1 + (-1)
0000 0000 0000 0001 1
+ 1111 1111 1111 1111 -1
------------------------- -----
1 0000 0000 0000 0000 0
|<---- 16 bits ---->|
Example 2: 7 + (-7)
0000 0000 0000 0111 7
+ 1111 1111 1111 1001 -7
------------------------- -----
1 0000 0000 0000 0000 0
|<---- 16 bits ---->|
Notice, in both examples, the numbers add to 0x10000 (which makes
sense, considering how we constructed the numbering system) and
that the 16-bit result ends up being zero? (Also, notice that
the 17th bit is always 1 -- this will be important to remember
in the next section, when we discuss status bits.)
The next requirement says that "There should be a well defined
set of positive and negative numbers." Looking at the table above,
we immediately notice that small positive numbers always start
with zeros to the left, and small negative numbers always start
with ones to the left. So, one way we can divide the numbering
is to say "if the left-most bit is a 0, the number is positive,
and if the left-most bit is a 1, the number is negative." This
conveniently divides the number space in half, and allows us to
quickly and easily determine if a number is negative. As a
result, we refer to the leftmost bit as "the sign bit."
The final requirement, "every binary pattern should map to exactly
one number" pretty much falls into place as a result of how
we've structured things. First, for all of the numbers larger
than zero, we know there exists one and only one negative. This
takes care of the numbers 0x0001 through 0x7FFF and their
negative counterparts, 0xFFFF through 0x8001. Also, we know that
zero is 0x0000 is unique. That leaves only one oddball case --
the number 0x8000. This number is the largest magnitude negative
value, and it has the unique property that there is no positive
counterpart to it in the numbering system. It is also a unique
number-to-binary-pattern mapping, and so it too fills this
requirement.
That, in a nutshell is our numbering system for signed numbers.
At this point, you're probably thinking "So why is it named 2s
Complement, then?" The reason stems from how 2s complement is
implemented in hardware. The process of inverting all of the bits in
a number is referred to as taking the "1s complement" of the number --
all of the 1s are changed to 0s and all of the 0s are changed to 1s.
This is mathematically equivalent to subtracting the value from
a number consisting of all 1's, eg. 0xFFFF, as a 16-bit binary.
Notice that if we add 1 to the 1s complement, we get the negation
we so carefully constructed above. The 0xFFFF becomes 0x10000.
Since we've added 1 to the 1s complement, we refer to this as the
2s complement of the number.
The hardware uses this property directly. Since inverting bits
is very inexpensive in hardware, the hardware actually performs
the 1s complement and adds 1, as described, when it needs the 2s
complement of a value. (The CP1600 instruction "COM" performs a 1s
complement alone.) This makes it easy to implement subtraction
as addition, then: "a - b" becomes "a + (-b)", which becomes
"a + COM(b) + 1", which is what the hardware actually performs.
The 2s complement representation effectively shifts the numerical
range of interest from the unsigned range 0x0000 to 0xFFFF (0 to
65535) down to the signed range 0x8000 to 0x7FFF (-32768 to 32767).
It does not change how mathematics are performed in the hardware
at all.
What's nice about 2s complement arithmetic is that we are free to
consider a number as a signed or unsigned quantity at any time --
general arithmetic (addition and subtraction mainly) on that number
works identically regardless. Certain instructions (conditional
branches, etc.) are specific to signed or unsigned numbers, though.
The difference here lies not in the number itself, but in how
instructions interpret the status flags that are set when the number
is manipulated.
--------------------
1.3: Status Flags
--------------------
Most traditional 8- and 16-bit CPUs offer a number of status flags
which are used for controlling the program. Some or all of the status
flags are updated when arithmetic and logical instructions execute.
The CP-1600 offers four flags, a shown in the following table:
Name Description
----------------------
S Sign
Z Zero
O Overflow
C Carry
These bits are used primarily by the conditional branch instructions,
which are covered in Section 3.3. This section focuses primarily on
the mathematical conditions which set and clear these bits, leaving
the actual discussion of their use to Section 3.3. The status flags
are also used by the shift and rotate instructions, as described in
Section 1.5.
The Zero status bit is the simplest to explain of the four status
bits. Whenever a result of an arithmetic operation is zero,
the zero bit gets set to 1 -- otherwise the zero bit is cleared.
The most common use of the Zero status bit is to test whether two
numbers are equal. Since the difference between two equal numbers
is zero, a subtract which subtracts them will set the zero bit if
the numbers are equal.
Example:
0000 0000 0000 0110
+ 1111 1111 1111 1010
-------------------------
0000 0000 0000 0000 ===> Sets Z = 1
|<---- 16 bits ---->|
0110 0000 0000 0010
+ 0100 1111 1111 1010
-------------------------
1010 1111 1111 1100 ===> Sets Z = 0
|<---- 16 bits ---->|
The Sign status bit contains a copy of the sign bit of the result.
Recall, in Section 1.2 above, we said that the sign bit is
the left-most bit (bit 15, in our case) in the word. In some
applications, it's necessary to know whether a value is positive
or negative. The sign bit is perfect for these cases.
Example:
0000 0000 0000 0110
+ 1111 1111 1111 1010
-------------------------
0000 0000 0000 0000 ===> Sets S = 0
|<---- 16 bits ---->|
0110 0000 0000 0010
+ 0100 1111 1111 1010
-------------------------
1010 1111 1111 1100 ===> Sets S = 1
|<---- 16 bits ---->|
The Overflow status bit is a little more subtle. The Overflow
status bit reports when the addition of two signed, positive
numbers has produced a negative-looking result, or the addition of two
signed, negative numbers has produced a positive-looking result.
(Since subtraction is implemented as addition with the second
operand negated, a similar description applies to subtraction.)
This bit is can be computed simply by looking at the sign bits of
the input and the output. If the sign bits of the input are
equal to each other, but not equal to the result, then an overflow
has occurred.
Examples:
0000 0000 0000 0110
+ 1111 1111 1111 1010
-------------------------
0000 0000 0000 0000 ===> Sets O = 0
|<---- 16 bits ---->|
0110 0000 0000 0010
+ 0100 1111 1111 1010
-------------------------
1010 1111 1111 1100 ===> Sets O = 1
|<---- 16 bits ---->|
The Carry bit is a bit more straight forward. It contains the
17th bit of the result after an addition or subtraction. This can
be used to detect when the addition of two large unsigned numbers
generated a result larger than 16 bits. In the case of subtraction,
the 2s complement representation causes the carry bit to act as a
"not borrow" bit. The reason for this is that negative numbers
are defined as positive values subtracted from 0x10000 (see section
1.2 above). In cases which would generate a borrow, the borrow is
made from the 1 in the 17th bit itself -- otherwise, the 17th bit
remains a 1. An interesting consequence of this is that "0 - 0"
does generate a carry.
Examples:
0000 0000 0000 0110
+ 1111 1111 1111 1010
-------------------------
1 0000 0000 0000 0000 ===> Sets C = 1
|<---- 16 bits ---->|
0110 0000 0000 0010
+ 0100 1111 1111 1010
-------------------------
0 1010 1111 1111 1100 ===> Sets C = 0
|<---- 16 bits ---->|
0000 0000 0000 0000
- 0000 0000 0000 0000
------------------------- ... becomes
0000 0000 0000 0000
+ 1111 1111 1111 1111
+ 1
-------------------------
1 0000 0000 0000 0000 ===> Sets C = 1
|<---- 16 bits ---->|
Notice that the Sign and Overflow bits are useful when you consider
the numbers to be signed. Both of these bits imply a 2s Complement
representation of signed values. In contrast, the Carry and Zero
bits are independent of whether the number is signed or unsigned,
and are most useful when the number is unsigned. How a number is
treated, therefore, depends on which status bits you pay attention to
in your code. The description of conditional branches and the compare
instruction in Section 3.3 covers this in somewhat greater detail.
---------------------
1.4: Boolean Logic
---------------------
In addition to traditional arithmetic, the CP-1600 also offers
the bitwise boolean operators AND, XOR and COM. These operators allow
manipulating bits within a word. This is sometimes referred to
as "bit twiddling."
Bit twiddling is valuable in cases where the values stored
in registers aren't necessarily meant to represent numbers.
This can be the case if you're manipulating a bitmap for a graphic,
decoding hand-controller data, or performing other such operations.
The boolean operations AND, XOR, and COM work in conjunction with
the shift/rotate operators (Section 1.5) to provide a complete set
of bit-manipulation primitives.
The AND and XOR operators each accept two inputs. Calculation
is performed on corresponding bits between each input, producing
results in the corresponding bit of the output.
In the case of AND, each bit in the output is set to 1 if both
of the corresponding bits in the inputs are also set to 1 -- eg.
the output is 1 if the first AND second inputs are 1. In the case
of XOR, which stands of eXclusive OR, each bit in the output is set
to 1 if exactly one of the corresponding bits is set 1 in the input
-- eg. the output is 1 if either the first OR the second input is 1,
but not both. The remaining bits in both cases are set to 0.
There is a third commonly-available boolean operator, OR, which
performes an inclusive-OR. It sets the output bit to 1 if either
or both of the input bits is set to 1. The CP-1600 does not provide
an OR instruction. See examples 3 and 4 at the end of this section
below for ways to perform this operation using combinations of AND,
XOR and COM.
The following truth table explains the relationship between two
input bit, and the result that AND, XOR and OR produce:
A B A AND B A XOR B A OR B
-------------------------------------------------------
0 0 0 0 0
0 1 0 1 1
1 0 0 1 1
1 1 1 0 1
(Note: I've included OR for completeness only. The CP-1600 does not
provide an OR instruction, although as mentioned above, you can
synthesize one using other instructions.)
The following example illustrates AND and XOR applied to full 16
bit registers. Remember, the truth table above is applied to each
corresponding pair of bits from the inputs:
+-------+-------+-------+-------+
R0 |0 0 0 0|1 1 1 1|0 1 0 1|1 0 1 0| $0F5A
+-------+-------+-------+-------+
+-------+-------+-------+-------+
R1 |1 1 1 1|0 1 1 0|1 0 1 0|0 0 0 0| $F6A0
+-------+-------+-------+-------+
+-------+-------+-------+-------+
R0 AND R1 |0 0 0 0|0 1 1 0|0 0 0 0|0 0 0 0| $0600
+-------+-------+-------+-------+
+-------+-------+-------+-------+
R0 XOR R1 |1 1 1 1|1 0 0 1|1 1 1 1|1 0 1 0| $F9FA
+-------+-------+-------+-------+
The CP-1600 also offers the unary operator "COM", which inverts
bits in the input. "COM" stands for "1s COMplement", which is
simply the process of changing the 1s to 0s and 0s to 1s. (2s
complement, described in Section 1.2 adds an additional '1' to
the result.) COM's truth table is alot simpler than AND's or
XOR's:
A COM A
-----------------
0 1
1 0
The following example illustrates how COM works:
+-------+-------+-------+-------+
R0 |0 0 0 0|1 1 1 1|0 1 0 1|1 1 0 0| $0F5C
+-------+-------+-------+-------+
+-------+-------+-------+-------+
COMR R0 |1 1 1 1|0 0 0 0|1 0 1 0|0 0 1 1| $F0A3
+-------+-------+-------+-------+
The boolean operators, when used together, allow a number of useful
actions to be performed. For instance, the AND operator makes it
easy to clear bits in a word using a "mask". ("Clear" means to "set
to zero".) Once a set of bits are cleared, the XOR operator can
then be used to set some combination of bits in the cleared area.
("Set" alone means "set to 1".)
Here are some quick examples:
Example 1: Clear the upper byte of a word
BASIC CP-1600 Comment
---------------------------------------------------------------------
R0 = R0 AND 255 ANDI #$00FF, R0 Clear lower byte of R0
Example 2: Copy R2's upper byte into R1's upper byte, leaving
R1's lower byte undisturbed.
BASIC CP-1600 Comment
---------------------------------------------------------------------
R2 = R2 AND 65280 ANDI #$FF00, R2 Clear lower byte of R2
R1 = R1 AND 255 ANDI #$00FF, R1 Clear upper byte of R1
R1 = R1 XOR R2 XORR R2, R1 Merge the values together
Example 3: Set bits in R0 that are currently set to 1 in either
R0 or R1. This effectively performs "R0 = R0 OR R1",
even though the CP-1600 doesn't have an OR instruction.
(Uses R2 as a temp.)
BASIC CP-1600 Comment
---------------------------------------------------------------------
R2 = R1 MOVR R1, R2 Copy R1 to R2
R2 = R2 XOR R0 XORR R0, R2 Find differing bits
between R0 and R1
R2 = R2 AND R1 ANDR R1, R2 Find bits that are set in
R1 but not in R0.
R0 = R0 XOR R2 XORR R2, R0 Set bits in R0 that were
set in R1 but not R0.
Example 4: Set bits in R0 that are currently set to 1 in either
R0 or R1. This effectively performs "R0 = R0 OR R1",
even though the CP-1600 doesn't have an OR instruction.
(Does not use a temporary.) This version makes use
of "De Morgan's Theorem", namely the boolean property
that "A OR B = COM ((COM A) AND (COM B))".
BASIC CP-1600 Comment
---------------------------------------------------------------------
R0 = R0 XOR 65535 COMR R0 Invert bits in R0
R1 = R1 XOR 65535 COMR R1 Invert bits in R1
R0 = R0 AND R1 ANDR R1, R0 Clear bits in R0 that
are clear in R0 or R1
R0 = R0 XOR 65535 COMR R0 Result: R0 = R0 OR R1
----------------------------------
1.5: Shift and Rotate Operators
----------------------------------
In addition to the bitwise boolean operators which operate on bits
"in place", the CP-1600 offers a rich variety of shift and rotate
instructions which move bits within a word. (Note: Since the CP-1600
has some rather unique behavior with regards to its rotate and shift
instructions, this section is a little more CP-1600 specific than
Sections 1.1 through 1.4.)
Shift instructions move bits to the left or right within a word.
Arithmetically, this corresponds to multiplying or dividing the number
by a power of 2. The bits, though, merely move to the left or right.
On the CP-1600, shift instructions can shift words by one or two
places in a single instruction.
There are three basic forms of shift: Shift Logical Left (SLL), Shift
Logical Right (SLR), and Shift Arithmetic Right (SAR). In addition,
some Shift instructions will store shifted bits in the Carry or
Overflow bits, to be used in conjunction with other instructions
such as conditional branches or rotates. More on this in a moment.
The "Logical" in "Shift Logical Left" and "Shift Logical Right"
refers to the fact that the sign bit of the number is ignored --
the number is considered to be a sequence of bits that is either an
unsigned quantity or a non-numeric quantity such as a graphic. When a
number is shifted in this manner, the sign bit is not preserved.
Logical shifts always bring in '0's in the bit positions being
"shifted in".
The "Arithmetic" in "Shift Arithmetic Right" refers to the fact
that the sign-bit is replicated as the number is shifted, thereby
preserving the sign of the number. This is referred to as "sign
extension", and is useful when dividing signed numbers by powers of 2.
To illustrate, let's shift the bit pattern 1010010110100101 by
one bit with each of the three forms of shift.
+-------+-------+-------+-------+
R0 |1 0 1 0|0 1 0 1|1 0 1 0|0 1 0 1| $A5A5
+-------+-------+-------+-------+
/ / / / / / / / / / / / / / /
+-------+-------+-------+-------+
SLL R0, 1 |0 1 0 0|1 0 1 1|0 1 0 0|1 0 1 0| $4B4A
+-------+-------+-------+-------+
+-------+-------+-------+-------+
R0 |1 0 1 0|0 1 0 1|1 0 1 0|0 1 0 1| $A5A5
+-------+-------+-------+-------+
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \
+-------+-------+-------+-------+
SLR R0, 1 |0 1 0 1|0 0 1 0|1 1 0 1|0 0 1 0| $52D2
+-------+-------+-------+-------+
+-------+-------+-------+-------+
R0 |1 0 1 0|0 1 0 1|1 0 1 0|0 1 0 1| $A5A5
+-------+-------+-------+-------+
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \
+-------+-------+-------+-------+
SAR R0, 1 |1 1 0 1|0 0 1 0|1 1 0 1|0 0 1 0| $D2D2
+-------+-------+-------+-------+
As you can see, the Arithmetic and Logical shift rights differ only
in how they handle the sign bit.
In addition to the basic forms of the Shift instructions, the CP-1600
also offers "Shift through Carry" instructions, which will write the
shifted-away bits to the Carry bit and Overflow bits. When shifting
by one bit, the shifted away bit is written to the Carry bit.
When shifting by two bits, the first shifted-away bit is written to
the Carry bit, and the second is written to the Overflow bit.
Rotate instructions behave similarly to Shift instructions.
The main difference between a Rotate and a Shift is that the Rotate
instructions bring in non-zero bits from the status words as they
shift the input, thus effectively allowing you to "rotate" a bit
pattern through a register and a status bit.
Consider the "Rotate Left through Carry" (RLC) instruction, when
rotating by 1 bit. It shifts the number left by 1, setting Bit 0
to the old value of the Carry bit, and then setting the carry to
the old value of Bit 15. The diagram below illustrates:
+-----------------------------------------------+
| |
| +---+ +-------------------------------+ |
+--| C |<---| < - - - - |<--+
+---+ +-------------------------------+
15 0
RLC Rx, 1
When rotating by two bits, things become more interesting. The
Carry and Overflow bits are placed in Bits 1 and 0, respectively,
and old Bits 15 and 14 are placed in the Carry and Overflow:
+--------------------------------------------------------+
| |
| +---+ +---+ +-------------------------------+ |
+--| C |<---| O |<---| < - - - - |<--+
+---+ +---+ +-------------------------------+
15 0
RLC Rx, 2
The rotate-right instructions work similarly, only the direction is
reversed:
+-----------------------------------------------+
| |
| +-------------------------------+ +---+ |
+-->| - - - - > |--->| C |--+
+-------------------------------+ +---+
15 0
RRC Rx, 1
+--------------------------------------------------------+
| |
| +-------------------------------+ +---+ +---+ |
+-->| - - - - > |--->| O |--->| C |--+
+-------------------------------+ +---+ +---+
15 0
RRC Rx, 2
Rotate instructions can be mixed and matched with any other
instruction which sets or reads status bits. For instance, Shift
and Rotate instructions may be used together to shift bit patterns
that are longer than 16 bits to the left or right.
In addition to the shifts and rotates, the CP-1600 offers a Byte
Swap instruction, named appropriately "SWAP". It merely exchanges
the two 8-bit bytes in a 16-bit word. This is useful when handling
8-bit quantities on a 16-bit machine.
==============================================================================
Section 2: Architecture Overview.
==============================================================================
-----------------------
2.1: System Overview
-----------------------
The CP-1600 provides what's known as a Von Neumann style computer
architecture. What this means is that we have a Central Processing
Unit which is connected by a single bus to a bunch of memories
and peripherals. In our case, the Central Processing Unit is the
CP-1600 itself.
Conceptually, the diagram looks like so:
+-----------+ Addresses
| |===========>--------+-----------+--------------+- ...
| CP-1600 | | | |
| |<==========><---+-----------+------------+------- ...
+-----------+ Data | | | | | |
| | | | | |
v v v v v v
+-------+ +-------+ +------------+
| RAM | | ROM | | Peripheral |
+-------+ +-------+ +------------+
In this setup, all of the devices that are outside the CPU appear
in a single, unified Address Space. On the CP-1600, addresses are
16-bits wide, and so the address space is 64K words large.
Programs and data are stored in memory, which the CPU accesses via
this bus. The CPU makes no distinction between whether a memory holds
program code or data, so both can be stored in the same memory (but
generally at different locations). Also, memories and peripherals
are treated identically, so accesses to "memory" may go to either
RAMs, ROMs, or various peripherals.
As I've mentioned before, CP-1600 is a 16-bit CPU. The address and
data bus it provides to external memories and peripherals is therefore
16-bits wide as well. At the time the CP-1600 was introduced,
peripherals and memories were often narrower than 16 bits. With these
devices, each location of the device still maps to a single location
in the CP-1600 memory map; however, reads from the device only
return meaningful data on the lower bits of the result, and writes
to the device ignore the upper bits.
For example, suppose we have an 256-entry, 8-bit RAM in our address
space, and it is mapped into memory at locations $0100 to $01FF.
If we read from the device, we will get an 8-bit value back in the
lower 8 bits of the word, and zeros will be placed in the upper 8
bits of the word, like so: 00000000xxxxxxxx (x's represent the
data that we read from the RAM). If we write to the device, the
lower 8 bits of the data we write will get stored, and the upper 8
bits will get ignored.
As you can imagine, this causes some interesting programming
challenges. Section 2.4, which discusses Double Byte Data, explains
one way in which the CP-1600 copes with this.
-----------------
2.2: Registers
-----------------
Memory accesses are slow, because they have to go "off chip"
for data. As a result, most operations in the CPU operate on
registers. Registers are special memory locations inside the CPU
which are connected directly to the CPU's arithmetic and logic units.
Most of these registers are so-called "General Purpose" registers,
although also many have additional special uses assigned to them.
The CP-1600 has 8 16-bit general purpose registers, named R0 through
R7. These registers may be used for general purpose arithmetic.
Additionally, there is the status word, SWD, which contains the
status bits. The following table describes them all.
Register Special Purpose
---------------------------------------------------------------------
R0 None
R1 Data Counter
R2 Data Counter
R3 Data Counter
R4 Auto-incr Data Counter, or JSR Return Address
R5 Auto-incr Data Counter, or JSR Return Address
R6 Stack Data Counter, or JSR Return Address
R7 Program Counter
SWD Status word: Holds Sign, Zero, Carry, Over bits
Memory accesses on the CP-1600 are performed via Data Counters*.
These provide access to memory in a fashion similar to the PEEK
and POKE instructions in BASIC. In addition to providing access to
memory, R4 and R5 auto-increment and R6 behaves as a stack pointer
when used as Data Counters. In general, Data Counters perform memory
accesses for instruction arguments, using "Indirect Addressing",
which is described in detail in Section 2.3.
Registers R4 through R6 also may be used to hold the return-address
for a JSR instruction. JSR acts very similar to BASIC's GOSUB
instruction. The return address may then be saved in memory by your
program, thereby allowing recursion. Alternately, this value may
double as an extra argument to a function, which is unlike anything
that BASIC would do, but is extrememly efficient and convenient.
All of these topics will be covered in detail in Section 3.2.
* I know "Data Counter" is a strange name, but it is the name given
in the General Instruments documentation. It is for the sake of
consistency that I use it.
------------------------
2.3: Addressing Modes
------------------------
The CP-1600 offers a wide variety of addressing modes for its
instruction set. Some of these modes operate entirely on registers
inside the CPU. Others access memory, allowing access to RAMs, ROMs,
and peripheral devices. Before launching into a complete description
of the modes, let's first look at the common instruction forms.
Single-operand instructions, such as "INCR", "DECR" and so on work
with a single operand that doubles as both "source" and "destination."
Dual-operand instructions, such as "ADDR" and "SUBR" operate on
two operands, where the first is a "source" and the second is both
"source" and "destination."
For example, consider the single operand instruction "INCR Rx".
("INCR" stands for "INCrement Register".) The BASIC equivalent
for this would be "Rx = Rx + 1". Here, Rx acts both as a source
(input) and destination (output) for the instruction. Now, consider
the two-operand instruction "ADDR Rx, Ry". ("ADDR" stands for "ADD
Registers".) In this case, the BASIC equivalent would be "Ry = Ry
+ Rx". Now, Rx is simply a source operand, and Ry is both a source,
and a destination.
The simplest addressing mode is "Implied" mode, in which the
instruction operates on one or more operands that are not directly
specified. Instructions which directly set/clear flags fall into
this category.
Example:
CLRC ; C = 0 (Clear the carry bit.)
The next simplest addressing mode is "Register" mode, in which
the instruction reads and writes all of its results to registers.
Register mode instructions generally have an "R" as the last letter
of their mnemonic, as in "ADDR", "COMR", etc. "Register" instructions
do not access memory.
Examples:
INCR R0 ; R0 = R0 + 1
ADDR R1, R2 ; R2 = R2 + R1
SUBR R3, R4 ; R4 = R4 - R3
"Immediate" mode instructions accept a constant as one of the two
operands. These instructions are always dual-operand instructions,
and the first operand is always the constant, with exception to
"MVOI," which writes to its immediate operand. (The usefulness of
"MVOI" is questionable.) Immediate mode instructions generally have
an "I" as the last letter of the mnemonic, as in "ADDI".
Examples:
MVII #$0042, R3 ; R3 = $0042
XORI #$FF00, R6 ; R6 = R6 XOR $FF00
"Direct" mode instructions specify a fixed memory address from
which to read one of the operands. As with Immediate mode, the
first operand is always the Direct operand, except for MVO, which
writes a value to the requested address. Direct mode instructions
generally do not have any special mention in the mnemonic.
Examples:
MVO R4, $01F1 ; POKE $01F1, R4
MVI $01F0, R3 ; R3 = PEEK($01F0)
ADD $02F0, R5 ; R5 = R5 + PEEK($02F0)
"Indirect" mode instructions access memory through a Data Counter
register for one of the operands. The first operand is generally
the data counter, with exception to "MVO@," in which it is the second
operand. Except in the case of "MVO@," Indirect mode instructions
read the value at the memory location pointed to by the Data Counter
before performing the instruction. With "MVO@", the value is
written to the desired location. Indirect mode instructions are
generally noted with an "@" at the end of the mnemonic.
When an Auto-Incrementing Data Counter is used with Indirect mode,
the Data Counter is incremented after the access. This allows
loops to step through arrays very efficiently, since it is not
necessary to manually update the Data Counters.
The Stack Data Counter is a special case. When writing, it is
incremented after the access, just as the Auto-Incrementing Data
Counters are. When reading, however, it is decremented BEFORE
the access, thus providing a simplistic stack. Indirect addressing
via the Stack Data Counter is referred to as "Stack Addressing."
Examples:
MVO@ R4, R3 ; POKE R3, R4
MVO@ R3, R4 ; POKE R4, R3 : R4 = R4 + 1 (Auto-incr)
MVO@ R3, R6 ; POKE R6, R3 : R6 = R6 + 1 (Stack)
MVI@ R3, R4 ; R4 = PEEK(R3)
MVI@ R4, R3 ; R3 = PEEK(R4) : R4 = R4 + 1 (Auto-incr)
MVI@ R6, R3 ; R6 = R6 - 1 : R3 = PEEK(R6) (Stack)
XOR@ R3, R2 ; R2 = R2 XOR PEEK(R3)
------------------------
2.4: Double Byte Data
------------------------
As I'm fond of mentioning, the CP-1600 is a 16-bit wide machine.
However, at the time of its inception, 16-bit wide memory systems
were expensive, and so the CP-1600 provides mechanisms for dealing
with "narrow" memories. (Any memory whose width is less than 16
bits is referred to as a "narrow memory" in this document.)
On the Intellivision, for instance, most programs are stored in
10-bit wide ROMs. This causes some problems, because Immediate
mode stores its constant in the word immediately following the
instruction word. As a result, a 10-bit wide ROM would restrict
constants to 10 bits wide. It also causes problems for Indirect
accesses to narrow memory, since quantities wider than the memory
cannot be stored or read from these memories by default.
Fortunately, the CP-1600 provides an answer with the "Set Double
Byte Data" (SDBD) instruction, which allows reading data in "Double
Byte Data" format. Double Byte Data format stores 16-bit quantities
as you'd expect -- as two 8-bit bytes. In memory, the low byte is
stored first, and the high byte is stored second. (This is referred
to as "Little Endian" data ordering, because the "little" end
is stored first. And yes, the terms "Little Endian" and "Big Endian"
are borrowed from Gulliver's Travels and are in common use in the
computing industry today.)
SDBD acts as a modifier instruction which tells the CP-1600 that the
next instruction accesses "Double Byte Data". The SDBD instruction
only modifies the instruction that immediately follows it. It only
modifies reads, and it works only with Indirect and Immediate
addressing modes.
When using SDBD with Immediate mode, the Immediate constant is stored
in the two bytes immediately after the instruction rather than in
one word. This lengthens the instruction's encoding from two words
to three words. In Indirect mode, two accesses are performed through
the same Data Counter. If an Auto-incrementing Data Counter is used,
the counter is incremented twice. If a non-auto-incrementing Data
Counter is used, though, the same memory location is accessed twice.
Note that SDBD is not allowed with Stack Addressing or with MVO.
Examples:
; This shows how to handle large constants that don't fit into
; 10-bit words.
SDBD ; Set Double-Byte-Data
ADDI #$5A3C, R0 ; R0 = R0 + $5A3C
; This reads both hand-controllers (at locations $1FE, $1FF)
MVII #$01FE, R4 ; R4 = $1FE
SDBD ; Set Double-Byte-Data
MVI@ R4, R0 ; R0 = PEEK($1FE) + PEEK($1FF)*256 : R4 = R4 + 2
The SDBD instruction provides a fine way to read 16-bit values stored
in narrow memory. It does not, however, provide a way to write 16-bit
values, since SDBD may not be used with MVO. Instead, one may use
the SWAP instruction in conjunction with two MVOs to store a 16-bit
value to narrow memory. The first MVO can be used to store the
lower half of the value. One then SWAPs the data, and uses a second
MVO to store the upper half. This works especially well with indirect
addressing modes, although there is no restriction on addressing mode
as there is with SDBD.
Example:
; This shows how to store a 16-bit value in 8-bit RAM. In
; this case, the value in R0 is stored starting at the location
; pointed to by R4.
MVII #$1EE, R4 ; R4 = $1EE
MVO@ R0, R4 ; POKE R4, R0 : R4 = R4 + 1
SWAP R0, 1 ; Swap the bytes in R0
MVO@ R0, R4 ; POKE R4, R0 : R4 = R4 + 1
; Now read the value back in using SDBD and MVI:
SUBI #2, R4 ; R4 = R4 - 2 ' rewind the pointer
SDBD ; Set Double-Byte-Data
MVI@ R4, R0 ; R0 = PEEK($1EE) + PEEK($1EF)*256 : R4 = R4 + 2
Advanced tip: The MVO and SWAP instructions on the CP-1600 are marked
as Non-Interruptible, which means a sequence of MVOs and SWAPs cannot
be interrupted until it completes. This allows one to update 16-bit
values in 8-bit memory "atomically", such that interrupt handlers cannot
see the partially updated value. This is especially useful for setting
values that will be used by an interrupt handler, or for changing the
interrupt vector address.
Example:
; Change the interrupt vector address to point to MYISR.
; The interrupt vector is stored as double-byte-data at $100 and $101.
; The MVO/SWAP/MVO is non-interruptible, thus making this "safe."
MVII #MYISR, R0 ; Point R0 to my interrupt handler
MVO R0, $100 ; Store out lower byte of address
SWAP R0, 1 ; Exchange bytes
MVO R0, $101 ; Store out upper byte of address
==============================================================================
Section 3: Jumps and Branches
==============================================================================
---------------------------
3.1: Unconditional Jumps
---------------------------
Unconditional Jumps provide the equivalent of BASIC's GOTO statement.
They allow a program to be laid out in a non-linear fashion, with
control passing from one point to another.
The CP-1600 provides four forms of unconditional jump: Absolute
Jumps, Absolute Jumps to Subroutines, Relative Branches, and
Arithmetic Jumps. We will cover all of these except Jumps to
Subroutines (JSRs) here. JSRs are covered in Section 3.2.
Absolute Jumps are the simplest to understand. These are provided
by the instructions J, JE, and JD. They essentially tell the CP-1600
"Go here, immediately." The Jump instructions accept a label which
can be anywhere in the address space. They are encoded in such a
way that they never require an SDBD prefix. JE and JD optionally
enable or disable interrupts during the branch. Because the address
being jumped to is hard-coded in the instruction, a given Jump
instruction will always branch to the same location, regardless of
where the Jump instruction is placed in memory.
Examples:
J foo ; GOTO foo
JE bar ; Interrupts = ON : GOTO bar
JD baz ; Interrupts = OFF : GOTO baz
Relative Branches are very similar to Absolute Branches, in that
they instruct the CPU to go to a particular label. The main
difference is that Relative Branches specify an offset from the
current location to branch to. This allows relative branches
to be "relocated" in memory, so that a piece of code might be
moved around if needed. It also means that relative branches
stored in a narrow ROM have limited range. (SDBD is not supported
with relative branch offsets.)
Example:
B foo ; GOTO foo
Arithmetic Jumps provide a means for branching to a computed location.
This is similar to the "ON x GOTO" in BASIC. More commonly, however,
Arithmetic Jumps provide a means for returning from a function.
These jumps differ from other types of jumps, because they are
performed using the regular arithmetic instructions, instead of
specific Jump or Branch instructions. Most typically, a MOVR or
MVI@ is used to set R7, the Program Counter, to a specific location.
Sometimes, INCR is used to branch over a single-word instruction.
Examples:
INCR R7 ; Skip next instruction
ADDR R0,R1 ; Skipped by INCR
...
MVI@ R6,R7 ; Return to address stored on stack
----------------------------
3.2: Jumps to Subroutines
----------------------------
In order to support structured programming, the CP-1600 provide
instructions for jumping to subroutines. The Jump to Subroutine
instructions, JSR, JSRE, JSRD, behave similarly to their absolute
Jump counterparts, J, JE, JD. But in addition to jumping, these
instructions also store a return address in a user-specified
register, either R4, R5, or R6.
Because the return address is stored in a register, returns from
short subroutines can be made fairly quickly, by merely moving
the return address back into the program counter. For instance,
in the following, the subroutine "foo" does some trivial operation,
and merely moves the return address back to R7 to return.
following.
Example:
MVII #2, R0 ; R0 = 2
MVII #2, R1 ; R1 = 2
JSR R5, foo ; Call subroutine foo: Put 2 + 2 together.
... ; R1 = 2 + 2 = 4
foo: ADDR R0, R1 ; Add R0 to R1
MOVR R5, R7 ; Return to caller
Note that to support recursion, it becomes necessary to store the
return address on the stack. Consider, for instance, a recursive
function that adds numbers 1 .. N together (not the greatest use
of recursion, but definitely simple to understand). The BASIC code
might look like so:
10 SUM = SUM + N
20 N = N - 1
30 IF N <= 0 GOTO 50
40 GOSUB 10
50 RETURN
The assembly code for this might go like this, then: (See Section
3.3 for more information on the conditional branch.)
; SUM is in R0
; N is in R1
foo: MVO@ R5, R6 ; Remember return address on stack
ADDR R1, R0 ; R0 = R0 + R1
DECR R1 ; R1 = R1 - 1
BLE done ; IF R1 <= 0 GOTO DONE
JSR R5, foo ; GOSUB foo
done: MVI@ R6, R7 ; Pop return address from stack and return
Another consequence of placing the return address in a register is
that it can act as an extra function argument. This encourages a
paradigm where complex blocks of parameters are passed to a function
as a data-block immediately after the function call. There is nothing
in BASIC which corresponds to this directly, except possibly the
"RESTORE line_no" statement. Imagine, for instance, a function
which initializes a chunk of memory to a desired pattern. Suppose
the BASIC code looks like so:
5000 REM Init Memory
5010 REM The first data value we read will contain the address
5020 REM to poke data into. The second contains the length of
5030 REM the data. The rest is the data itself.
5040 READ addr
5050 READ len
5060 FOR I = 1 to len
5070 READ data
5080 POKE addr, data : addr = addr + 1
5090 NEXT I
5100 RETURN
It might be invoked like so:
100 RESTORE 120
110 GOSUB 5000
120 DATA 1024, 5, 10, 20, 30, 40, 50
130 REM Code resumes here
The corresponding CP-1600 assembly might look like so: (Again,
refer to Section 3.3 for information on the conditional branches.)
init_mem: SDBD
MVI@ R5, R4 ; Read address
MVI@ R5, R1 ; Read length
loop: MVI@ R5, R0 ; Read a piece of data
MVO@ R0, R4 ; Write it out
DECR R1 ; Decrement loop count
BPL loop ; Iterate until we're done
MOVR R5, R7 ; Return to the caller
The function would then be invoked with a JSR, followed by a "DCW"
(Define Control Word) directive that has the data for the subroutine.
(DCW is an assembler directive which tells the assembler to place data
values in memory directly. It's roughly similar to BASIC's "DATA"
instruction, only a little less mystical and a lot less subtle.)
Example:
JSR R5, init_mem
data: WORD 1024 ; "WORD" gives us Double Byte Data
DECLE 5, 10, 20, 30, 40, 50
rtrn: ; Code resumes here
Note that at the start of our init_mem function, R5 will be set to
point at its data block (labeled "data" above for clarity), and at the
end of init_mem, it'll be set to point to the next actual instruction
(labeled "rtrn" above, again for clarity).
As you can see, the CP-1600's JSR instruction family provides an
interesting set of programming opportunities.
----------------------------
3.3: Conditional Branches
----------------------------
Conditional branches are one of the Swiss Army Knives of assembly
language programming. In their simplest uses, paired with the "CMP"
(Compare) instruction, they provide "IF .. THEN GOTO" capability
in the language. This makes varied program behavior possible.
When combined with other instructions, much more subtle and powerful
control flow can be devised.
(Note: The CP-1600 also provides a special flavor of conditional
branch that allows branching on one of 16 external conditions.
The external condition inputs are not wired in the Intellivision,
and so this is not discussed here.)
All of the conditional branch instructions work by testing some
combination of status bits, and deciding whether or not to jump based
on that test. The CP-1600 provides 16 different conditional branch
opcodes, although two of them map to "Branch Always" and "Branch
Never". (These two are given the mnemonics B and NOPP, respectively.)
The complete set of conditional branches are listed in Section 4.5.
The most easily explained use of conditional branches is in
relationship to the "CMP" (Compare) instruction. "CMP" performs a
subtraction between two numbers, and sets the status bits accordingly.
The result of the subtract is discarded, however. The reason a
subtract is performed is that it allows us to learn many things
about the relative values between two numbers. The following table
illustrates the relationship between comparisons, subtraction, and
status bits that get set. (In this table, x and y are considered
to be signed. Note that for the relative compares, there are two
cases since the subtract might overflow if one input is positive
and the other is negative and the two are far apart.)
If this Then so And these
is true... is this... status bits are set.
-----------------------------------------------------------------
x = y x - y = 0 S = 0, Z = 1, O = 0
x < y x - y < 0 (no overflow) S = 1, Z = 0, O = 0
x - y > 0 (overflow) S = 0, Z = 0, O = 1
x > y x - y > 0 (no overflow) S = 0, Z = 0, O = 0
x - y < 0 (overflow) S = 1, Z = 0, O = 1
The CP-1600 provides several conditional branches which are geared
around these conditions, as well as branches which test for overflow.
Note that the compare/branch effectively form a signed comparison.
Mnemonic Branch if... Test
--------------------------------------------------------------
BEQ Equal Z = 1
BNEQ Equal Z = 1
BLT Less Than S <> O
BLE Lesser or Equal S <> O OR Z = 1
BGT Greater Than (S = O) AND Z = 0
BGE Greater or Equal S = O
BOV Overflow occurred O = 1
Alternate
Mnemonics Branch if... Test
--------------------------------------------------------------
BNGE Not Greater or Equal S <> O
BNGT Not Greater Than S <> O OR Z = 1
BNLE Not Lesser or Equal (S = O) AND Z = 0
BNLT Not Less Than S = O
BNOV No Overflow occurred O = 0
As you can see, these mnemonics are fairly straightforward,
particularly when comparing signed numbers. Now for some examples
relating BASIC IF statements to some actual assembly code. Note the
ordering of operands in the compare -- it's backwards to what you
may be used to.
Examples Using Signed Numbers:
BASIC code CP-1600 Assembly Code
-----------------------------------------------------------------
IF R0 > R1 THEN GOTO foo CMPR R1, R0
BGT foo
IF R2 <= R3 THEN GOTO foo ELSE GOTO bar CMPR R3, R2
BLE foo
B bar
FOR R0 = 5 TO 10 MVII #5, R0
POKE R4, R0 loop: MVO@ R0, R4
R4 = R4 + 1 INCR R0
NEXT R0 CMPR #10, R0
BLE loop
To perform unsigned comparsions, the situation is a little simpler,
since we don't care about overflows. Since the numbers are unsigned,
though, the sign bit doesn't contain the information we're interested
in. Instead we can look directly at the carry and zero bits.
If this Then so And these
is true... is this... status bits are set.
-----------------------------------------------------------------
x < y x - y < 0 C = 0, Z = 0, O = ?
x > y x - y > 0 C = 1, Z = 0, O = ?
x = y x - y = 0 C = 1, Z = 1, O = ?
We can then use the branches that look at the carry bit and zero
bit to handle greater-than, less-than, and equals on unsigned numbers:
Mnemonic Branch if... Test
--------------------------------------------------------------
BC Carry is set C = 1
BNC Carry is not set C = 0
BEQ Zero is set Z = 1
Notice that "BC" is effectively "Branch if Greater or Equal", and
"BNC" is "Branch if Less Than." We can use "BEQ" before a "BC"
to sort out the "Equals" cases from the "Greater" cases.
Although compares with branches are probably the most easily explained
use of conditional branches, another common use is to test the result
of other arithmetic, as hinted to by the "BPL", "BMI", "BOV", and
"BNOV" instructions we've already covered.
Most of the CP-1600 instructions set some or all of the status bits.
Logical instructions (AND, XOR, COMR) and increment/decrement
instructions (INCR, DECR) tend to set the Sign and Zero bits. Other
arithmetic instructions (ADD, SUB, CMP, etc.) as well as the shift
and rotate instructions set all four status bits.
Probably the most common use of conditional branches is to control
loops. Typically, loops are rewritten so that their loop counters
starts out positive and works towards zero. Then, a "DECR" and "BPL"
or "BNEQ" combination actually form the loop.
In BASIC, this transformation might look like so:
Original:
10 FOR I = 1 TO 99
20 NEXT I
Both versions:
10 I = 99 10 I = 99
20 I = I - 1 20 I = I - 1
30 IF I >= 0 THEN 20 30 IF I <> 0 THEN 20
In assembly code, the code is about as simple:
MVII #99, R0 MVII #99, R0
xx: DECR R0 xx: DECR R0
BPL xx BNEQ xx
The difference between the two for most loop trip counts is mostly
academic. For trip counts that are too large to fit into a signed
variable, the version at the right is required, though.
Another use of conditional branches is to branch based on the bits
stored in a word. This can be done by using the shift instructions
in conjunction with conditional branches. One use of this is to
provide a compact, efficient encoding for multi-way "case" statements.
(BASIC lacks 'case' statements, so in BASIC, we use multiple IF
statements instead.)
Example:
BASIC Code
--------------------------------
IF (R0 AND 128) <> 0 GOTO opt7
IF (R0 AND 64) <> 0 GOTO opt6
IF (R0 AND 32) <> 0 GOTO opt5
IF (R0 AND 16) <> 0 GOTO opt4
IF (R0 AND 8) <> 0 GOTO opt3
IF (R0 AND 4) <> 0 GOTO opt2
IF (R0 AND 2) <> 0 GOTO opt1
IF (R0 AND 1) <> 0 GOTO opt0
CP-1600 Code
----------------------------------------------------------
MOVR R0, R1 ; Do all of our work on a copy of R0
SWAP R1 ; Put lower byte in upper half
SLLC R1, 2 ; Set C, O to bits 7, 6 of R0
BC opt7
BOV opt6
SLLC R1, 2 ; Set C, O to bits 5, 4 of R0
BC opt5
BOV opt4
SLLC R1, 2 ; Set C, O to bits 3, 2 of R0
BC opt3
BOV opt2
SLLC R1, 2 ; Set C, O to bits 1, 0 of R0
BC opt1
BOV opt0
==============================================================================
Section 4: Instruction Set Reference
==============================================================================
------------------------
4.1: Legend and Notes
------------------------
The tables below describe the core instruction set of in the CP-1600.
Each table attempts to relate CP-1600 instructions to their
BASIC equivalent where it can. Note that in the case of Indirect
instructions, the update that is performed on Auto-incrementing
or Stack Data Counters is not shown in the table, although a short
explanation is given below for reference purposes. See Section 2.3
and 2.4 for a more detailed explanation.
Table Legend
------------
Rx, Ry Registers -- one of R0, R1, R2, R3, R4, R5, R6, R7
#x Constants.
x, y Addresses.
[, 2] Optional ", 2" argument for shifts and rotates.
S Sign bit
Z Zero bit
O Overflow bit
C Carry bit
I Interrupt bit
D Double-byte Data bit
2 Overflow bit if second argument is 2.
Auto-Incrementing Data Counters
-------------------------------
R4 and R5 are "incrementing data counters". Their values are incremented
after every memory access (eg. PEEK or POKE) via these registers. For
instance:
MVI@ R4, R5 ; R5 = PEEK(R4) : R4 = R4 + 1
MVO@ R4, R5 ; POKE R5, R4 : R5 = R5 + 1
Stack Data Counter
------------------
R6 is the "stack pointer". It is incremented after every write,
and decremented before every read, thus providing an upward-growing Stack.
MVO@ R5, R6 ; POKE R6, R5 : R6 = R6 + 1
MVI@ R6, R5 ; R6 = R6 - 1 : R5 = PEEK(R6)
---------------------------------------
4.2: Arithmetic/Boolean Instructions
---------------------------------------
CP-1600 Description Flags Type
------------------------------------------------------------------------
INCR Rx Rx = Rx + 1 S Z Register
DECR Rx Rx = Rx - 1 S Z Register
COMR Rx Rx = - Rx - 1 S Z Register
NEGR Rx Rx = - Rx S Z O C Register
ADCR Rx Rx = Rx + C S Z O C Register
MOVR Rx, Ry Ry = Rx S Z Register
ADDR Rx, Ry Ry = Ry + Rx S Z O C Register
SUBR Rx, Ry Ry = Ry - Rx S Z O C Register
CMPR Rx, Ry Ry - Rx S Z O C Register
ANDR Rx, Ry Ry = Ry AND Rx S Z Register
XORR Rx, Ry Ry = Ry XOR Rx S Z Register
MVO Rx, y POKE y, Rx Direct
MVI x, Ry Ry = PEEK(x) Direct
ADD x, Ry Ry = Ry + PEEK(x) S Z O C Direct
SUB x, Ry Ry = Ry - PEEK(x) S Z O C Direct
CMP x, Ry Ry - PEEK(x) S Z O C Direct
AND x, Ry Ry = Ry AND PEEK(x) S Z Direct
XOR x, Ry Ry = Ry XOR PEEK(x) S Z Direct
MVO@ Rx, Ry POKE Ry, Rx Indirect
MVI@ Rx, Ry Ry = PEEK(Rx) Indirect
ADD@ Rx, Ry Ry = Ry + PEEK(Rx) S Z O C Indirect
SUB@ Rx, Ry Ry = Ry - PEEK(Rx) S Z O C Indirect
CMP@ Rx, Ry Ry - PEEK(Rx) S Z O C Indirect
AND@ Rx, Ry Ry = Ry AND PEEK(Rx) S Z Indirect
XOR@ Rx, Ry Ry = Ry XOR PEEK(Rx) S Z Indirect
MVII #x, Ry Ry = #x Immediate
ADDI #x, Ry Ry = Ry + #x S Z O C Immediate
SUBI #x, Ry Ry = Ry - #x S Z O C Immediate
CMPI #x, Ry Ry - #x S Z O C Immediate
ANDI #x, Ry Ry = Ry AND #x S Z Immediate
XORI #x, Ry Ry = Ry XOR #x S Z Immediate
---------------------------------
4.3: Shift/Rotate Instructions
---------------------------------
See Section 1.5 for more info on shift/rotate instructions.
CP-1600 Description Flags Type
------------------------------------------------------------------------
SLL Rx[, 2] Shift Logical Left S Z Register
SLR Rx[, 2] Shift Logical Right S Z Register
SAR Rx[, 2] Shift Arithmetic Right S Z Register
SWAP Rx[, 2] Swap bytes S Z Register
SLLC Rx[, 2] SLL into Carry S Z 2 C Register
SARC Rx[, 2] SAR into Carry S Z 2 C Register
RLC Rx[, 2] Rotate Left thru Carry S Z 2 C Register
RRC Rx[, 2] Rotate Right thru Carry S Z 2 C Register
-------------------------
4.4: Jump Instructions
-------------------------
CP-1600 Description Flags Type
------------------------------------------------------------------------
J label Jump to Label Jump
JE label Jump, Enable Interrupts I Jump
JD label Jump, Disable Interrupts I Jump
JSR Rx, label Jump to Subroutine Jump/Reg
JSRE Rx, label JSR, Enable Interrupts I Jump/Reg
JSRD Rx, label JSR, Disable Interrupts I Jump/Reg
---------------------------------------
4.5: Conditional Branch Instructions
---------------------------------------
CP-1600 Description
------------------------------------------------------------------------
B label IF 1 = 1 GOTO label
BC label IF C = 1 GOTO label
BOV label IF O = 1 GOTO label
BPL label IF S = 0 GOTO label
BZE label IF Z = 1 GOTO label
BEQ label IF Z = 1 GOTO label
BLT label IF S <> O GOTO label
BNGE label IF S <> O GOTO label
BLE label IF Z = 1 OR S <> O GOTO label
BNGT label IF Z = 1 OR S <> O GOTO label
BUSC label IF S <> C GOTO label
NOPP IF 1 = 0 GOTO label
BNC label IF C = 0 GOTO label
BNOV label IF O = 0 GOTO label
BMI label IF S = 1 GOTO label
BNZE label IF Z = 0 GOTO label
BNEQ label IF Z = 0 GOTO label
BGE label IF S = O GOTO label
BNLT label IF S = O GOTO label
BGT label IF Z = 0 AND S = O GOTO label
BNLE label IF Z = 0 AND S = O GOTO label
BESC label IF S = C GOTO label
---------------------------------------------
4.6: Status Bit / CPU Control Instructions
---------------------------------------------
CP-1600 Description Flags Type
------------------------------------------------------------------------
SETC C = 1 C Implied
CLRC C = 0 C Implied
SDBD Set Double Byte Data D Implied
EIS Enable Interrupts I Implied
DIS Disable Interrupts I Implied
TCI Terminate Interrupt Implied
SIN Software Interrupt Implied
GSWD Rx Rx = SZOC0000SZOC0000 Register
RSWD Rx Set S,Z,O,C from Rx S Z O C Register
==============================================================================
Section 5: Examples
==============================================================================
I need to put examples here. If anyone is willing to write examples
to go here, please contact Joe Zbiciak at .
Thank you!
==============================================================================
Section 6: Glossary of Terms
==============================================================================
1s complement See "one's complement."
2s complement See "two's complement."
AND Boolean operation involving two input bits, producing
a single output. The output bit is 1 if both input
bits are 1. Otherwise the output is 0. When applied
to two registers, AND is performed to pairs of
corresponding bits from each input, and the result
is written to the corresponding bit in the output.
AND is covered in Section 1.4. Compare to OR and XOR.
arithmetic A shift instruction which treats the number being
shift shifted as a signed quantity. In the case of a right-
shift, the sign bit is duplicated in the number.
Shifts are covered in Section 1.5. See also "shift"
and "logical shift".
base-2 See binary.
base-10 See decimal.
base-16 See hexadecimal.
BASIC Beginner's All-purpose Symbolic Instruction Code.
BASIC is a simple programming language that was
extremely popular in the early days of home computers,
and lives on at the heart of Microsoft-based products
in the form of Visual BASIC.
bidecle Two 10-bit numbers together. Although bidecles are
20 bits long, they are most often used to store
16 bit numbers as two bytes. See also "decle".
binary Numbering system in which the only available
digits are 0 and 1. Also referred to as "base 2".
The binary numbering system is used by computers,
since the two states "0" and "1" correspond directly
to the circuit states "off" and "on", making it easy
to directly relate the state of digital circuits
to numbers. Section 1.1 discusses binary numbers.
bit BInary digiT. A bit is a single 0 or 1 in a number.
Inside a computer, numeric values are made up of
one or more bits.
branch An interruption in the linear flow of a program.
Under normal circumstances, the instructions
in a program are executed in a linear order.
Branches cause the flow of the program to be
continued elsewhere. See also "conditional branch".
When unconditional, this is sometimes referred to as a
"jump". Jumps and Branches are covered in Section 3.
byte A collection of bits. On most computers, a byte
is 8 bits. See also "word".
carry In arithmetic, the portion of an addition result that
is too big to fit in a single digit. (For instance,
when adding '9 + 8', the '1' in '17' is the carry.)
Carry Bit The Carry Bit is a bit in the machine's Status Word.
When adding two numbers in a computer, the final
"carry" at the left end of an addition is remembered
in the Carry Bit. Also, certain other operations
(such as shifts) use the Carry Bit for storage.
The Carry Bit is part of the computer's Status
Word, and is covered in sections 1.3, 1.5 and 3.3.
See also "Sign Bit", "Overflow Bit", "Zero Bit",
"Status Word", "shift" and "rotate".
clear Set a number's value to zero.
conditional A conditional branch is a program branch occurs only
branch if a particular condition is met. This allows the
program's behavior to vary. The BASIC construct
"IF ... THEN ..." is a form of conditional branch.
Conditional Branches are covered in Section 3.3.
CP-1600 The CP-1600 is a 16-bit processor family that was
produced by General Instruments in the late 70s and
early 80s. The CP-1610, which is a member of this
family, was the processor used in the Intellivision
video game system.
CPU Central Processing Unit, also referred to simply as
a "processor". CPUs are the brains of computers.
They perform the computational tasks of the system.
The CP-1600 is a CPU.
decimal Numbering system which uses 10 digits numbered 0, 1,
2, 3, 4, 5, 6, 7, 8, 9. Also referred to as base-10.
Decimal is the most common numbering system in
use amongst humans, but is not very natural for
computers. Section 1.1 discusses how the decimal
numbering system is related to the more computer-
oriented binary numbering system.
decle A 10-bit number. Rhymes with "heckle". Decles are
common on the Intellivision since most Intellivision
game ROMs are 10 bits wide.
EXEC ROM The EXEC ROM is the built-in set of program routines
inside the Intellivision. The EXEC contains routines
for sound effects, manipulating graphics, reading
hand controllers, and so on. See also "Read Only
Memory".
integer A number having no fractional portion. Similar to
a "whole number", only integers are allowed to be
negative, whereas whole numbers start at 0.
Intellivision The world's first 16-bit video game system. :-)
'Nuff said.
GRAM Graphics RAM. The GRAM in the Intellivision is a RAM
that is dedicated to holding programmable graphics
information for the STIC. See also "GROM", "STIC"
and "RAM".
GROM Graphics ROM. The GROM in the Intellivision is a
ROM that is dedicated to holding fixed graphics
information for the STIC. (For instance, the
Intellivision text font is stored in the GROM.)
See also "GRAM", "STIC" and "ROM".
hex Abbrev: hexadecimal.
hexadecimal Numbering system which uses 16 digits numbered
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F.
Also referred to as base-16, and usually abbreviated
as "hex". Hexadecimal is often used by humans
working with computers, because it represents a
useful shorthand for describing binary numbers.
There is a one-to-one correspondence between
each hexadecimal digit and a 4-bit binary pattern.
Section 1.1 discusses hexadecimal numbering. See also
"binary" and "decimal".
jump A branch in a program. See "branch".
jump to A branch in a program which remembers the address
subroutine of the instruction after the "jump" instruction.
These are useful for calling routines in a program
or in the EXEC ROM. Jumps to Subroutines are
covered in Section 3.2. See also "EXEC ROM".
logical shift A shift instruction which treats the number being
shifted as an unsigned or non-numeric quantity.
In the case of right shifts, the Sign Bit receives
no special treatment. Shifts are covered in
Section 1.5. See also "shift" and "arithmetic
shift".
memory In general, memory refers to anyplace values may
be stored. In a computer, the available forms of
memory include Read-Only Memory (ROM), Random
Access Memory (RAM), and CPU Registers.
octal Numbering system which uses 8 digits numbered 0, 1,
2, 3, 4, 5, 6, 7. Octal is constructed similarly
to hexadecimal, providing a 1-to-1 mapping between
3-bit values and digits. Octal was much more popular
30 years ago than today. See also "binary", "decimal",
and "hexadecimal".
one's The one's complement for a number is the value
complement given when all of the 1 bits are changed to a 0
and all of the 0 bits are changed to a 1. See also
"two's complement".
OR Boolean operation involving two input bits, producing
a single output. The output bit is 1 if at least
one input bit is 1. Otherwise the output is 0.
When applied to two registers, OR is performed to
pairs of corresponding bits from each input, and
the result is written to the corresponding bit in
the output. Boolean logic is covered in Section 1.4.
Compare to AND and XOR.
Overflow Bit The Overflow Bit is a bit in the Status Word which
records whether a computation involving two signed
numbers returned a result of the expected sign.
For example, the addition of two positive numbers
is expected to return a positive result. If a
computation did not return a result with the
expected sign, the Overflow Bit is set; otherwise
the Overflow Bit is cleared. Also, certain shift and
rotate operations use the Overflow Bit for storage.
The Overflow Bit is covered in Section 1.3, and shifts
and rotates are covered in Section 1.5. See also
"Carry Bit", "Sign Bit", "Zero Bit", "Status Word",
"shift" and "rotate".
peripheral In this context of this document, a peripheral is a
device other than RAM or ROM that can be accessed
directly by the CPU. Peripherals include things
like the Sound Chip, hand-controllers, the STIC
(display) chip, etc. See also "STIC".
pop Retrieves the top element (eg. the most recently
pushed item) from a stack. On the CP-1600, this is
performed by decrementing R6, and then reading from
the location pointed to by R6 after the decrement.
If more elements are popped than are pushed, then the
stack "underflows". Also, pop is a synonym for pull.
See also "push", "stack", and "stack underflow".
pull Synonym for pop.
push Places an element onto the stack. The most recently
pushed item will be the next item popped. On the
CP-1600, items are pushed on the stack by storing
them to the location pointed to by R6, and then
incrementing R6's value. If more elements are pushed
on the stack than the stack has room to hold, then the
stack "overflows". By default, the Intellivision's
memory map leaves room for 39 words of stack space,
from $02F1 - $0318. See also "pop", "stack", and
"stack overflow."
RAM See "Random Access Memory".
Random Access Memory which can be read or written arbitrarily.
Memory This is where most variable data is stored. See
also "Read Only Memory."
Read Only Memory which can only be read. Most game-program
Memory data is stored in ROM. See also "Random Access
Memory."
register A special memory location inside the CPU which is
used for holding numbers and computation results.
Registers are special because they are inside the
CPU and can be accessed quickly. Section 2.2 covers
the registers that are in the CP-1600. See also
"Data Counter", "Status Word", and "Memory".
ROM See "Read Only Memory".
rotate An operation on a binary number, in which all of the
bits in the number are moved to the left or right,
and a new bit is brought in on the end being moved
away from. The Carry Bit (and the Overflow Bit,
in the case of 2-bit rotates) are used both for the
bits being rotated in, as well as for storage of
the bits being rotated out. Section 1.5 discusses
rotates in detail. See also "shift", "Carry Bit"
and "Overflow Bit".
set Change a number's value to a new value. The new
value is implied to be '1' when used in reference to
a bit without stating the new value. eg. "set the
carry bit" means the same as "set the carry bit to 1".
See also "clear".
shift An operation on a binary number, in which all of the
bits in the number are moved to the left or right.
Numerically, shifts correspond to multiplying or
ividing a number by 2. There are three basic kinds
of shifts: left shifts, arithmetic right shifts,
and logical right shifts. (Left shifts are neither
specifically logical or arithmetic -- they could be
considered as either.) Some shifts use the Carry
Bit (and the Overflow Bit, in the case of 2-bit
shifts) to store the bits that were "shifted away."
Section 1.5 discusses shifts in detail. See also
"arithmetic shift", "logical shift", "rotate",
"Carry Bit" and "Overflow Bit".
Sign Bit When referring to a number, the sign bit is the
left-most bit within the number, and is used to denote
the sign of the number. When referring to the bit
in the Status Word, the Sign Bit is the status bit
which refers to the sign of a computation result.
In both cases, 0 == positive, 1 == negative.
Sections 1.2 and 1.3 cover numerical representation
and status bits. See also "Carry Bit", "Zero Bit",
"Overflow Bit" and "Status Word".
stack A data structure in memory in which the last item
placed ("pushed") on the stack is the first item later
retreived from the stack ("popped" or "pulled").
This is referred to as a LIFO arrangement -- Last
In, First Out. Hardware stacks are implemented
in a special memory which can only be accessed in
stack order. Software stacks are implemented as an
array, with a pointer which moves within that array
as the stack grows and shrinks. The CP-1600 uses a
software stack and R6 serves as the stack pointer.
Section 2.3 covers addressing modes. See also
"stack overflow", "stack underflow", "push",
"pull" and "pop".
stack overflow Stack overflow occurs when too many items are pushed
onto a stack. Software stacks are stored as simple
arrays in memory. As elements are pushed on the
stack, the pointer moves through memory. (On the
CP-1600, it starts at low numbered addresses and
moves towards higher numbered addresses as items are
pushed. Therefore, we say the stack "grows upwards".)
If too many elements are pushed onto the stack, the
stack pointer may end up pointing into other data
structures, or into invalid memory areas, resulting
in incorrect program behavior. See also "stack",
"stack underflow", "push", and "pop".
stack Stack underflow occurs when too many items are popped
underflow from a stack. Stacks are "Last-In, First-Out" data
structures, meaning that the last item placed on a
stack is the first item retrieved. Popping from
a stack returns the elements that were pushed on
the stack in the opposite order from which they
were pushed. If more pops are issued on a stack
than pushes, then the stack "underflows" since there
is no corresponding "previously pushed value" to
return, and it instead returns a meaningless value.
In the case of software stacks, such as what the
CP-1600 provides, the stack pointer may end up
pointing to other data structures or invalid memory.
(In the default configuration provided by the EXEC,
the stack is placed just after the display memory,
so a stack underflow might result in the stack
pointer pointing to the display.) See also "stack",
"stack overflow", "push", and "pop".
Status Word A special register in the CPU which records
information about the results of computations.
On the CP-1600, the Status Word contains four bits:
the Sign Bit, Zero Bit, Overflow Bit, and Carry Bit.
STIC Standard Television Interface Chip. The STIC chip
is the device in the Intellivision which produces
the display. It is responsible for reading the
display information from the GRAM, GROM, and system
RAM, and producing the game display. See also "GRAM"
and "GROM".
two's The two's complement of a number is mathematically
complement equivalent to its negative on a machine with a
finite word width. The two's complement of a
number is generated by taking the one's complement
of the number and adding 1. This is mathematically
equivalent to subtracting the number from a value that
is one larger than the largest unsigned value that can
be represented in a machine word. Two's complement
arithmetic is discussed in Section 1.2. See also
"one's complement" and "word".
word A collection of bits usually equal to the computation
width of the machine. In the case of the CP-1600,
words are 16 bits (2 bytes) wide. See also "byte"
and "decle".
XOR (Also, Exclusive OR.) Boolean operation involving two
input bits, producing a single output. The output
bit is 1 if exactly one input bit is 1. Otherwise
the output is 0. When applied to two registers,
XOR is performed to pairs of corresponding bits
from each input, and the result is written to the
corresponding bit in the output. Boolean logic is
covered in Section 1.4. Compare to AND and OR.
Zero Bit The Zero Bit records whether a calculation result
was zero or not. If a result was zero, the Zero
Bit is set. If a result was non-zero, the Zero
Bit is cleared. Section 1.3 covers the Zero Bit as
well as other status bits. See also "Carry Bit",
"Zero Bit", "Overflow Bit" and "Status Word".