Close

Basic assembly programming idioms

A project log for YGREC8

A byte-wide stripped-down version of the YGREC16 architecture

Yann Guidon / YGDESYann Guidon / YGDES 01/02/2018 at 02:380 Comments

After the previous logs 7. Programming the YGREC8 and 15. Assembly language and syntax, this log shows a few basic programming idioms and explains how to use the YGREC8 architecture.

Conditional execution

Most instructions can be predicated (be executed if a certain condition is met) except IN and OUT (they really stand out, I know). A condition can be given if the SRC operand is a register or a short immediate (imm3), giving a range of -4 to +3. Due to this very narrow range, the ADD opcode will add 1 to imm3 if it ranges from 0 to 3. This extends the range from -4 to +4, thus skipping the value 0 because ADD R1 0 does nothing.

There are three main cases :

  1. The instruction to conditionally execute is only one opcode and uses only registers, or a imm3 value : the condition is appended to the instruction.
    ADD R1 1 IFC ; increments R1 if carry bit set
    That's the best, and simplest case.
  2. There is a sequence of two or three instructions to conditionally execute : a conditional short jump will do the trick
    ADD PC 4 IFC ; skip 3 instructions if carry bit set
      MOV R1 R2
      MOV R2 R3  ; exchange R2 and R2
      MOV R3 R1
    ; resume here : 
  3. When 4 or more instructions must be jumped over, a long jump is required but this type of instruction can't have a condition. There are two methods :
    - Preload the target address in a register, then conditionally MOV it to PC
    MOV R1 42h
    MOV PC R1 IFC 
    
    - Invert the condition to perform a short jump, over a long jump to the target
    ADD PC 1 IFNC
    MOV PC 42h

    The first method requires an additional register so the second is often preferred.

All these methods apply for CALL as well as loops. In particular, the first method of 3 might be used to hold the starting address of a loop and save one instruction cycle.

Call and return

The instruction CALL acts like a MOV but the core swaps NPC and RESULT so the result value is written to PC and the NPC is written to the selected register.

There is no return instruction, this is simply a MOV to PC.

There is no dedicated stack or "link register". Conventions can be drafted but 7 registers are equally possible and they can be optimised easily. CALL to PC however makes no sense and amounts to a MOV (or jump) {this could be used as a special function code or something later}.

In all cases, it's important to be sure of the calling convention for each function.

Access to memory is pretty limited (with only 2 ports) so the stack pointer must often be saved and restored if one pointer is not enough to access the required data...

Example

The following listing uses both looping and function call/return to compute the sum of all the integers up to R1 :

; max register value is 256 so it overflows over sqrt(2×256)
MOV R1 22
CALL R2 SUM
; display result here,
; expect (22×22)/2 = 242
INV

SUM:
  MOV R3 0 ; clear the accumulator

SUM_LOOP:
  ADD R3 R1 ; accumulate
  ADD R1 -1 ; decrement the counter
  ADD PC -2 IFNC ; loop back to SUM_LOOP if carry not set
     ; (could have been IFNS if R1 argument
     ;   is guaranteed > 0, saving one iteration)

  ; result of accumulation in R3
MOV PC R2 ; return

 So the YGREC8 core is not very powerful but just enough to do some useful work in an unusual yet not too awkward way.

Stack

As previously seen, there is no stack-specific mechanism or opcode. The stack can be mapped to A1/D1 or A2/D2. Or both. They can grow up or down. No stack overflow is provided.

Push and pop are software-defined and amount to incrementing or decrementing the relevant A register.

Discussions