Ducky instruction set¶
Design principles¶
- basic data unit is a word - 4 bytes, 32 bits. Other units are short - 2 bytes, 16 bits - and byte - 1 byte, 8 bits. Instructions often have variants for different data units, distinguished by a suffix (w for words, s for shorts, and b for single bytes)
- load and store operations are performed by dedicated instructions
- memory-register transfers work with addresses that are aligned to the size of their operands (1 byte alignment - so no alignment at all - for byte operands)
- unless said otherwise, destination operand is the first one
- when content of a register is changed by instruction, several flags can be modified subsequently. E.g. when new value of register is zero,
z
flag is set.
Notes on documentation¶
rN
refers to generaly any register, fromr0
up tor29
- special registers are refered to by their common names (e.g.SP
).rA
,rB
refer to the first and the second instruction operand respectively and stand for any register.<value>
means immediate, absolute value. This covers both integers, specified as base 10 or base 16 integers, both positive and negative, and labels and addresses, specified as&label
- when instruction accepts more than one operand type, it is documented using
|
character, e.g.(rA|<value>)
means either register or immediate value- immediate values are encoded in the instructions, therefore such value cannot have full 32-bit width. Each instruction should indicate the maximal width of immediate value that can be safely encoded, should you require grater values, please see
li
andliu
instructions
Stack frames¶
Several instructions transfer control to other parts of program, with possibility of returning back to the previous spot. It is done by creating a stack frame. When stack frame is created, CPU performs these steps:
IP
is pushed onto the stackFP
is pushed onto the stackFP
is loaded with value ofSP
Destroying stack frame - reverting the steps above - effectively transfers control back to the point where the subroutine was called from.
Arithmetic¶
All arithmetic instructions take at least one operand, a register. In case of binary operations, the second operand can be a register, or an immediate value (15 bits wide, sign-extended to 32 bits). The result is always stored in the first operand.
add rA, (rB|<value>)
dec rA
inc rA
mul rA, (rB|<value>)
sub rA, (rB|<value>)
Bitwise operations¶
All bitwise operations - with exception of not
- take two operands, a register, and either another register or an immediate value (15 bits wide, sign-extended to 32 bits). The result is always stored in the first operand.
and rA, (rB|<value>)
not rA
or rA, (rB|<value>)
shiftl rA, (rB|<value>)
shiftr rA, (rB|<value>)
shiftrs rA, (rB|<value>)
xor rA, (rB|<values>)
Branching instructions¶
Branching instructions come in form <inst> (rA|<address>)
. If certain conditions are met, branching instruction will perform jump by adding value of the operand to the current value of PC
(which, when instruction is being executed, points to the next instruction already). If the operand is an immediate address, it is encoded in the instruction as an immediate value (16 bit wide, sign-extended to 32 bits). This limits range of addresses that can be reached using this form of branching instructions.
Branching instructions do not create new stack frame.
Unconditional branching¶
j (rA|<value>)
Conditional branching¶
Instruction | Jump when ... |
be |
e = 1 |
bne |
e = 0 |
bs |
s = 1 |
bns |
s = 0 |
bz |
z = 1 |
bnz |
z = 0 |
bo |
o = 1 |
bno |
o = 0 |
bg |
e = 0 and s = 0 |
bge |
e = 1 or s = 0 |
bl |
e = 0 and s = 1 |
ble |
e = 1 or s = 1 |
Conditional setting¶
All conditional setting instructions come in form <inst> rA
. Depending on relevant flags, rA
is set to 1
if condition is evaluated to be true, or to 0
otherwise.
For flags relevant for each instruction, see branching instruction with the same suffix (e.g. setle
evaluates the same flags with the same result as ble
).
Instruction |
sete |
setne |
setz |
setnz |
seto |
setno |
sets |
setns |
setg |
setge |
setl |
setle |
Comparing¶
Two instructions are available for comparing of values. Compare their operands and sets corresponding flags. The second operand can be either a register or an immediate value (15 bits wide).
cmp rA, (rB|<value>)
- immediate value is sign-extended to 32 bits.
cmpu rA, (rB|<value>)
- treat operands as unsigned values, immediate value is zero-extended to 32 bits.
Interrupts¶
Delivery¶
If flag hwint_allowed
is unset, no hardware IRQ can be accepted by CPU and stays queued. All queued IRQs will be delivered as soon as flag is set.
cli
- clear hwint
flag
sti
- set hwint
flag
In need of waiting for external events it is possible to suspend CPU until the next IRQ is delivered.
idle
- wait until next IRQ
Invocation¶
Any interrupt service routine can be invoked by means of special instruction. When invoked several events take place:
SP
is saved in temporary spaceIP
andSP
are set to values that are stored inEVT
in the corresponding entry- important registers are pushed onto new stack (in this order): old
SP
,flags
- new stack frame is created
- privileged mode is enabled
- delivery of hardware interrupts is disabled
When routine ends (via retint
), these steps are undone, and content of saved registers is restored.
int (rA|<index>)
retint
- return from interrupt routine
Inter-processor interrupts (IPI
) can be delivered to other processors, via dedicated instruction, similar to int
but specifying CPUID of target core in the first operand.
ipi rA, (rB|<index>)
Routines¶
When routine is called, new stack frame is created, and CPU continues with instructions pointed to by the first operand. For its meaning (and limitations) see Branching instructions.
call (rA|<address>)
ret
Miscellaneous¶
nop
- do absolutely nothing
hlt (rA|<value>)
- Halt CPU and set its exit code to specified value.
rst
- reset CPU state. All flags cleared, privileged = 1
, hwint_allowed = 0
, all registers set to 0
mov rA, rB
- copy value of rB
into rA
swp rA, rB
- swap content of two registers
sis <value>
- switch instruction set to a different one
Memory access¶
Address operand - {address}
- can be specified in different ways:
rA
- address is stored in registerrA[<offset>]
- address is computed by addition ofrA
andoffset
.offset
can be both positive and negative.fp
andsp
can be also used asrA
.<offset>
is an immediate value, 15 bits wide, sign-extended to 32 bits.
Read¶
lw rA, {address}
- load word from memory
ls rA, {address}
- load short from memory
lb rA, {address}
- load byte from memory
Write¶
stw {address}, rA
sts {address}, rA
- store lower 2 bytes of rA
stb {addres}, rA
- store lower byte of rA
Constants¶
Instructions for filling registers with values known in compile time.
li rA, <constant>
- load constant
into register. constant
is encoded into instruction as an immediate value (20 bits wide, sign-extended to 32 bits)
liu rA, <constant>
- load constant
into the upper half of register. constant
is encoded into instruction as an immediate value (20 bits wide immediate, only lower 16 bits are used)
la rA, <constant>
- load constant
into the register. constant
is an immediate value (20 bits wide, sign-extended to 32 bits), and is treated as an offset from the current value of PC
- register is loaded with the result of PC + constant
.
Compare-and-swap¶
cas rA, rB, rC
- read word from address in register rA
. Compare it with value in register rB
- if both are equal, take content of rC
and store it in memory on address rA
, else store memory value in rB
.