Programming

From Just a bit RISC
Jump to: navigation, search

Programming Tips

Use of the Stack

In order for the programmer to use the stack, and the associated instructions, the stack pointer must be initialised manually by the programmer. The { ADD} instruction in register-immediate mode can be used to load an immediate value into a register and so, therefore, initialise the stack pointer. For example, to use the memory location 0x0800 (or 2048 in decimal) as the default value for the stack pointer the instruction “{ ADD sp, r0, 2048}” would be used.

It is important for the programmer to remember that the stack is implemented as a fully descending stack and this must be taken into account when initialising and using the stack.

Once the stack pointer register is initialised instructions that use the stack can be used, including { PUSH}, { POP} and subroutine calls. The { PUSH} and { POP} instructions alter the stack pointer appropriately so that the programmer does not have to manually increment or decrement the stack pointer.

The { PUSH} and { POP} instructions can be used for the storage of local variables, however, within each section of the program the number of each instruction must be equal so that the stack is left balanced. The instructions are also used for the creation and destruction of stack frames as described in the next section.

Calling Subroutines

PUSH	op1
PUSH	op2
ADD	sp, sp, -1
CALL	subroutine
POP	result
ADD	sp, sp, 2

In order to call a subroutine, three stages must be performed; stack frame creation, subroutine call and stack frame destruction.

To create a stack frame the operands to the subroutine must be placed on the stack, this is achieved by use of the { PUSH} instruction. One instruction is used to place each operand on the stack, for example, Listing {SubCall} shows a two-operand subroutine call. Space for the result must also be left within the stack frame, this is achieved by using the { ADD} instruction to add a negative immediate to the stack pointer. For example, { ADD sp, sp, -1}, decrements sp by one to leave space for one 16-bit result. This saves a memory access associated with a { PUSH} instruction.

The subroutine call is performed using the { CALL} instruction. When using labels the assembler will calculate the appropriate destination but the call target can also be stored within a register to allow calling different subroutines conditionally. When using subroutines all general purpose registers are caller-saved. This means that the contents of any registers that need to be re-used after the subroutine call must be placed on the stack. The stack pointer however is guaranteed to remain the same after a subroutine call.

To destroy the stack frame the result must first be removed from the stack. The { POP} instruction will remove the topmost element from the stack which will contain the result of the subroutine call. If a subroutine returns multiple values then multiple { POP} instructions can be used. { ADD sp, sp, 2} is used to increment the stack pointer and so remove the operands from the stack.

Writing Subroutines

.subroutine
	LOAD	r1, 3(sp)	
	LOAD	r2, 2(sp)
	;processing
	STORE	r3, 1(sp)
	RETURN

A subroutine is able to access the operands passed to it through use of the { LOAD} instruction. The indexed addressing mode is used to specify which operand should be retrieved. There is an offset of “<math>1+\mathrm{number~of~results}</math>” to access the first operand, for example { LOAD r2, 2(sp)} will access the first operand for a subroutine with a single result. Further operands are access by increasing the offset.

The results are placed on the stack using the { STORE} instruction. There is an offset of one to the index used to access the results due to the use of the stack to store the program counter. For example { STORE r2, 1(sp)} would be used to save the first result of a subroutine.

Within a subroutine the stack must be left balanced, i.e. any { PUSH} instructions must be matched with a { POP} instruction. If the stack is unbalanced on the return of a subroutine the program counter will not be correctly restored.

Once the results of a subroutine have been saved and the stack has been balanced the { RETURN} instruction may be called. This instruction will return the flow of execution to the instruction after the { CALL} instruction which started the subroutine.

General Tips

The dummy register, r0, can be used as a source of a zero value where an immediate is unnecessary. However, the zero register can also be used as the destination register, when this is the case the the value of the r0 will not be changed but the flags will be set appropriately.

The instruction set provides multiple ways to perform the same operation but sometimes one method may be more efficient in terms of speed. For example, when writing a loop to iterate 16 times the loop counter could be initialised to ‘16’. The immediate value ‘16’ would not fit into a 5-bit signed immediate and so a two word instruction would have to be used. Instead, the value ‘-16’ could be used and the loop counter incremented rather than decremented. This would then use a one-word instruction and save a memory access.

Interrupts

The stack pointer must be initialised before interrupts are enabled as the stack is used to save context.

The address of the ISR (interrupt service routine) is hard-wired to 0x0001 to allow timely response to interrupts. The { JUMPR} instruction allows a jump of 1023, or -1024, words and so can be used to move past the interrupt service routine on start-up.

The interrupts are controlled by the nIRQ signal. When the signal is low an interrupt will be triggered at the end of the current instruction. The instruction set does not provide interruptible instructions. While the signal is low an interrupt will continue to be triggered until the source of the interrupt is cleared and the nIRQ signal returns to a high level.

When an interrupt is triggered the flags and program counter will be stored on the stack. The program counter will then be loaded with a value of ‘1’ and the next instruction will be read from this location.

The { RETI} instruction is used to return from an interrupt service routine and will restore the flags and program counter to their state before the interrupt occurred.

To enable or disable interrupts the { STI} instruction is used. The instruction has one argument, a value of ‘1’ will enable interrupts and an argument of ‘0’ will disable interrupts. While in a ISR interrupts will be disabled and so { STI 1} must be used within the interrupt service routine to allow nested interrupts.

Example Programs