yasep/docs/registers.html version 2009-08-08
Back to the main page
version française :


The YASEP registers

The YASEP architecture comprises 16 registers, named R0 R1 R2 R3 R4 NPC A0 D0 A1 D1 A2 D2 A3 D3 A4 D4. Each of these registers can be of one of 4 (and a half) types of registers. This reduces the number of needed opcodes because each opcode can perform several different actions.


The "normal" registers

R0, R1, R2, R3 and R4 are normal registers, just like in other RISC architectures. One can write and read from them, without any implicit side effect.

; Example 1
 MOV 1234h R2   ; Set register R2 with the value 1234h
; Example 2
 ADD 4 R1       ; R1 <- R1 + 4
; Example 3
 ADD 32 R1 R3   ; R3 <- R1 + 32

These register typically hold temporary results of computations, loop counters, function call parameters... The extended form can also increment or decrement them.


The "Adress" registers

A0, A1, A2, A3 and A4 are the "address registers". They contain the address where data will be read or written. The extended instruction form can also increment or decrement them.


The "Data" registers

D0, D1, D2, D3 and D4 are the "Data registers" and they are closely related to the Address registers : A0 is bound to D0, A1 to D1 etc.

Each data register contains the value of the memory pointed by the associated Address register, the property Dx=memory[Ax] is always preserved by the CPU.

When a Data register is written, this will also write the same value to the memory, at the address contained in the associated Address register.

; Example 1
 MOV 1234h A1    ; Point A1 to the address 1234h ==> D1 contains the value at this address
 ADD D1 R0       ; Load the contents of [1234] and add it to register R0.
; Example 2
 MOV 1234h A3    ; Point A3 to the address 1234h ==> D3 contains the value at this address
 ADD D3 R2       ; Load the contents of [1234] and add it to register R2.
; Example 3
 ADD 1234h R1 A4 ; point A4 to the address R1+1234h ==> D4 contains the value at this address
 ADD R0 D4       ; load the contents of memory[R1+1234h],
                  ; add it to R0 and put the result back at the same address

When used with auto-decrement or auto-increment features, these register can implement stacks. By convention, A4 is the stack pointer and D4 is the stack top. However, nothing keeps one from creating 2 or 3 stacks, or even moving the standard stack to other registers.

Warning : ALL the memory accesses are aligned on a natural word boundary. YASEP16 ignores the LSB, and YASEP32 ignores 2 LSB. Unaligned words must be reconstructed with instruction sequences. However, bytes and halfwords are directly supported by specific instructions that take these LSBs into account :

; Example 1
 MOV 1231h A0    ; point A0 to the address 1231h (aligned on a byte boundary)
 LSB D0 R2       ; align and sign-extend the byte at [1231], write the result to R2.
; Example 1 bis (YASEP32 only)
 MOV 1232h A1    ; point A1 to the address 1232h (aligned on a halfword boundary)
 LSH D1 R1       ; align and sign-extend the halfword at [1232], write the result to R1.

; Example 2
 MOV 1231h A2    ; point A2 to the address 1231h (aligned on a byte boundary)
 SB R2 D2        ; take the lower byte of R2, align and insert the result into D2.
; Example 2 bis (YASEP32 only)
 MOV 1232h A3    ; point A3 to the address 1232h (aligned on a halfword boundary)
 SH R0 D3        ; take the lower half-word of R0, align and insert the result into D3


The "Next PC" register

The last register is even more unusual : NPC is the pointer to the next instruction. It is automatically incremented after each new instruction, and can be read and written by a program.

Remark that YASEP's instructions are encoded on 2 or 4 bytes but all addresses have a byte granularity and the instructions are always aligned on even addresses, so the LSB of the address is always implicitly equal to 0. When an address is written to the NPC, the LSB is ignored so odd addresses have the same effect as even addresses.

The "Carry flag"

The least significant bit of NPC is not used for pointing to the instructions. This last bit stores the carry or borrow bit of the last executed ADD or SUB instruction. The carry flag is set when an addition overflows :

; R1 = 5678h with YASEP16
 ADD CDEFh R1 R2  ; R2 <- 5678h + CDEFh = 12467h > FFFFh so the carry is set.
 ADD 1234h R1 R2  ; R2 <- 5678h + 1234h = 68ACh <= FFFFh so the carry is cleared.

This bit can then be tested by a conditional instruction :

 ADD 1234h R1         ; R1 <- R1 + 1234h
 ADD 56h R2 LSB0 NPC  ; IF the carry bit (the LSB of NPC) is 0, then add 56h to R2

The SUB instruction is based on the addition and the borrow is signaled with the carry bit of ADD. However, with SUB, the value is negated so the carry bit is set when the substraction did not overflow :

; R1 = 4h
SUB 3 R1 ; R1 = 3 - 4 = -1 ==> borrow=0
SUB 4 R1 ; R1 = 4 - 4 = 0  ==> borrow=1
SUB 5 R1 ; R1 = 5 - 4 = 1  ==> borrow=1

Only the instructions that are flagged as "CHANGE_CARRY" can change the carry bit. The only other instructions are CMPU and CMPS : they are similar to SUB but the destination is not written. All the other operations will preserve this bit, even when NPC is written. The carry bit can be tested many cycles after ADD/SUB/CMPU/CMPS are executed, even after function calls or returns. So the only way to clear or set the carry flag is to cleverly use the CMPU/CMPS instructions with operands that will affect the carry flag in a deterministic way :

; clear the carry flag :
 CMPU R1, R1    ; R1 equals R1 so the carry can't be set.
; set the flag :
 CMPU 0, NPC   ; NPC is (almost) always >0 so the carry is set.