This is the second post in this series, and will cover stack manipulation instructions. You can find a table of all the stack instructions at the end of this post.
You can find the first post here.
Prerequisites
There are a couple of prerequisites to fully understanding this post. It will assume you know what a stack is (but I've included a refresher) and that you have some knowledge of assembly language. But the content could still be interesting even without that knowledge. I'll leave it to you to decide.
Notation
The table at the end of this post uses a stack effect notation typically used by Forth. These effect notations are enclosed in parentheses, and consist of a before state and an after state, separated by two dashes. For instance, the swap instruction exchanges the top two items on the stack. This would be noted as (a b -- b a), showing that before the instruction is executed, b is at the top of the stack, and a is one below the top of the stack. After the execution a is on the top of the stack and b is one below the top.
Stacks
A stack is a last-in, first-out data structure. The location of the last item added to it is called the top of the stack, and the number of items on the stack is the depth. Most stack instructions only operate on the top of the stack, but there are some exceptions, such as the roll instructions.
The emulator operates two 256-byte stacks: the data stack and the return address stack. Neither is directly accessible, but the data stack can be manipulated using the instructions covered in this post. Eventually, there will also be instructions to move or copy items from one of the stacks to the other, but this is as yet unimplemented. The main use for this functionality would be in implementing interpreted languages like Forth.
Push-me Pull-you
To push a value to a stack means to place a new value on top of the current stack. To pull, or pop, a value means to remove the value at the top of the stack. The RX-9000 instruction set contains a number of push/pull pairs, such as pushi/pop, pushx/pullx, etc.
While pulling a value that is larger than the depth of the stack is a logical error, the behaviour of such a pull is undefined. In the current implementation the stack pointer will end up rolling over, changing the depth of the stack and leaving the contents of the stack undefined.
Value size
In order to support both single and double byte values, the instruction mnemonic for a single byte value, like pushi, is followed by a w for two byte values. So to push an immediate one byte value you would use pushi, and for a two byte value pushiw.
The exceptions to this are when the size is implicit in the instruction, such as setting or retrieving a value in a register.
Register/Stack transfers
In order to set the DP or X registers, the desired value needs to be on the top of the stack. Then the pulldp or pullx instructions, respectively, take that value off the stack and store it in the respective register. pulldp pulls a single byte from the stack, whereas pullx pulls two bytes.
Alternatively, to place the value of either DP or X on the stack, you would use pushdp or pushx respectively.
Register indexed transfers (aka memory/stack transfers)
This is perhaps the least intuitive part of the instruction set. I'll try to make it as clear as possible, but if you have any questions don't hesitate to ask them in the comments.
The DP or X registers are required to move items between the stack and memory. In RX-9000 assembly this is noted with the register in square brackets, such as pull [DP] to pop the top byte from the stack and place its value at the memory location pointed to by DP. push [DP] takes the byte pointed to by DP and pushes it onto the top of the stack.
The width of the transfer can be expanded to two bytes by adding w to either the push or pull instruction. The width of the register itself is irrelevant. If both DP and X contain the same value, push [DP] is equivalent to push [X].
In addition, the register used can be either decremented or incremented, either before or after the operation takes place. This change can either be by one byte or two. For instance, to put the value at DP on top of the stack, and then increment DP by one byte, you would write push [DP+]. In order to increment by two bytes, you would write push [DP++]. This allows both DP and X to be used as stack pointers as well, and allows for easy array access through these registers.
What next?
I plan to cover arithmetic and logic unit (ALU) instructions in the next post. These instructions operate only on items at the top of the stack and include addition, subtraction, logical AND, among others.
Instruction table
Each of these instructions is listed with its single byte variant, but any of these instructions can have a w added to them to cause them to operate on two byte values instead. The exceptions to this are the pulldp/pushdp/pullx/pushx instructions, whose bit counts are implicit.
Added to the tables of instructions involving the registers are the condition register flags that can be changed by the operation. The condition code (CC) register will be explained further in the post on ALU operations.
Stack manipulation
Register manipulation
Register indexed
These instructions support pre/post-increment/decrements.
Comments