Skip to main content

CPU flags

The CPU flags are boolean states that can be either 1 (true) or 0 (false). Instructions can set a flag (make it 1) or clear a flag (make it 0).

The flags are represented as bits of a byte called the flags register:

Bit #FlagFlag typeNotes
0Zero (ZF)condition flag
1Negative (NF)condition flagfor signed values
2Overflow (OF)condition flagfor signed values
3Carry (CF)condition flagfor unsigned values
4With (WF)control flagset by with instructions
5Skip (SF)control flagset by if instructions

The NF, OF, ZF, and CF flags are called condition flags. They are updated by various instructions to report arithmetic outcomes, which then typically serve as conditions to be checked by the if instruction.

The WF and SF flags are called control flags because they modify the behavior of the next instruction. WF is normally set by the with instruction. SF is normally set by the if instruction. Except for special cases noted below, both are cleared when the next instruction executes.

The flags byte can also be accessed directly using the push flags and pop flags instructions (but note that pop flags excludes WF and SF). When a CPU event is triggered, the flags byte is stored in io::cpu_event_flags, and can be restored by a with context pop ip sequence.

Edge cases

  • Conventional registers don't set the condition flags: the add instruction can accept sp and ip as operands, for example add sp, 123 or add ip, 123. These forms will NOT modify ZF, NF, OF, or CF.
  • For a shift instruction whose shift count is zero, no operation is performed, and therefore no flags are modified.

Zero (ZF)

  • Updated by: add, and, compare, convert, divide, multiply, negate, or, shift, subtract, xor
  • Tested using: if zero

Instructions set ZF if the result of the operation is zero, and ZF=0 otherwise.

Negative (NF)

  • Updated by: add, and, compare, convert, divide, multiply, negate, or, shift, subtract, xor
  • Tested using: if negative
  • Meaningful for signed numbers

Instructions put the most significant bit of their result into NF. For signed arithmetic using two's complement, NF=1 indicates a negative number.

Overflow (OF)

  • Updated by: add, and, compare, convert, divide, multiply, negate, or, subtract, xor
  • Tested using: if overflow or fail if overflow
  • Meaningful for signed numbers

For add, subtract, compare, multiply, and negate, OF=1 indicates that the result exceeded the register's signed range. The divide instruction sets OF when the quotient cannot be represented (including division by zero). The convert instruction sets OF whenever a narrowing conversion exceeds the target register's signed range.

The other instructions listed above will simply clear OF to avoid confusion.

Note that shift does not affect this flag at all.

Carry (CF)

  • Updated by: add, and, compare, convert, divide, multiply, negate, or, shift, subtract, xor
  • Tested using: if less unsigned or fail if carry
  • Meaningful for unsigned numbers

For add and multiply, CF=1 indicates that the result exceeded the register's unsigned range. The convert instruction sets CF whenever a narrowing conversion exceeds the target register's unsigned range. The shift instruction puts the last shifted bit into the carry flag.

The subtract and compare instructions use the subtract-with-carry model (familiar from ARM and 6502 processors), where CF=0 indicates an underflow or "less than" comparison.

The other instructions listed above will simply clear CF to avoid confusion.

With (WF)

  • Set by: with carry or with context or with euclid
  • Cleared by every other instruction (except when restored by with context pop ip)

This flag enables certain instructions to perform specialized behaviors that are useful but rarely needed. The with instruction sets this flag. The three forms are actually the same opcode, just renamed by the assembler for clarity.

with carry sets WF=1 to cause an immediately following add, subtract, or compare instruction to include the carry flag in its calculation.

with context sets WF=1 to cause an immediately following pop ip to restore the stack pointer (SP) and CPU flags as well.

with euclid sets WF=1 to cause an immediately following divide instruction to perform Euclidean integer division instead of truncated division.

Skip (SF)

  • Updated by: if
  • Cleared by every other instruction except with (or when restored by with context pop ip)

The if instruction tests a condition and only executes the next instruction if that condition is satisfied. In other words, if the condition is unsatisfied, then the next Chombit instruction will be skipped over.

This skipping is implemented by the SF flag. When SF=1, the current instruction is skipped; no action is performed other than clearing the SF flag and advancing IP. Skipped instructions only require 1 CPU cycle.

xor b:1, b:1 # zf=1 because the result is zero
if not zero # condition is unsatisfied, so sf=1
add b:1, 1 # this is skipped over, revert to sf=0
add b:1, 2 # execution continues here