Logical Operations and Decision-Making Instructions in RISC-V

Beyond arithmetic, processors need to manipulate individual bits and alter their execution path based on conditions. This article covers the core logical operations used for bit manipulation and explains how conditional branching and looping are built from a small set of comparison-based instructions.

Logical OperationsConditional BranchingBitwise Instructions

~3 min read · Updated Sep 6, 2026

Logical Operations: Manipulating Bits Directly

Not every operation a processor performs deals with numbers as whole quantities. Sometimes software needs to manipulate specific bits within a value, which is done using Logical Operations.

  • AND produces a 1 in each bit position only where both operands have a 1, commonly used to clear specific bits, an operation called Masking.
  • OR produces a 1 in each bit position where at least one operand has a 1, commonly used to set specific bits.
  • Shift Left (sll) and Shift Right (srl) move all bits of a value by a fixed number of positions, which is also an efficient way to multiply or divide by powers of two.

An example of a logical AND instruction in RISC-V assembly:

and a, b, c

This instruction performs a bitwise AND between the values in registers b and c, storing the result in a.

Making Decisions: How Branching Works at the Hardware Level

A program rarely executes in one straight, unconditional line. Loops, if-statements, and function calls all require the processor to conditionally jump to a different point in the instruction sequence, a capability provided by Conditional Branch instructions.

The two fundamental branch instructions in RISC-V compare two register values directly:

beq a, b, Label
bne a, b, Label

beq (branch if equal) jumps to the specified label only if the two register values are equal, while bne (branch if not equal) jumps only if they differ. If the condition is false, execution simply continues with the next sequential instruction.

Building Loops and If-Statements from Branches

High-level constructs like if, while, and for do not exist at the hardware level. A compiler translates them into a sequence of comparisons and conditional branches.

Example: a simple high-level if-statement and its conceptual translation:

High-level:
if (i == j) f = g + h;

Translated concept:
bne i, j, Exit
add f, g, h
Exit:

Here, if i and j are not equal, execution jumps directly past the addition to the label Exit, skipping it. If they are equal, the branch is not taken and the addition executes normally.

Unconditional Jumps

Not all changes in execution flow depend on a condition. An Unconditional Jump instruction always transfers control to a specified location, regardless of any comparison, and is used to implement constructs such as the end of a loop body jumping back to its start.

Why This Small Set of Instructions Is Enough

Every complex control structure found in high-level programming languages — nested loops, switch statements, early returns — can be constructed by combining just these few primitives: comparison, conditional branching, and unconditional jumping. This is a direct demonstration of the "make the common case simple and let software build complexity on top" philosophy underlying instruction set design.

Written & researched by Dr. Shahin Siami

Related Articles

How Hardware Performs Division: Quotients, Remainders, and Edge Cases

Division is the most hardware-intensive of the basic arithmetic operations, involving repeated subtraction and comparison rather than a single-pass circuit. This article explains the conceptual long-division algorithm hardware follows, how quotient and remainder are produced together, and the special edge cases like division by zero that hardware must explicitly handle.

Continue

How Hardware Multiplies Numbers: From Simple Logic to Real Circuits

Multiplication is far more hardware-intensive than addition, since it fundamentally involves repeated addition and shifting. This article walks through the conceptual algorithm hardware uses to multiply binary numbers, explains why the result needs twice the bit width of the inputs, and covers how signed multiplication differs from the unsigned case.

Continue

How Hardware Performs Addition and Subtraction, and Detects Overflow

Arithmetic looks trivial in software but requires careful circuit design and explicit overflow handling in hardware. This article explains how a processor's adder circuit performs both addition and subtraction using the same hardware, and how overflow conditions are detected and handled for signed and unsigned numbers.

Continue

Arrays Versus Pointers at the Hardware Level

In C, arrays and pointers often look interchangeable, and many programmers treat them as if they were the same thing. At the hardware level, however, they compile down to noticeably different instruction sequences with different performance characteristics. This article compares the two approaches using RISC-V assembly to show exactly why pointer-based code is often faster.

Continue

From Source Code to a Running Process: Translation and a Full Sort Example

Turning a C program into something the operating system can actually run involves several distinct translation stages, each producing a different intermediate file. This article walks through that full pipeline from compiler to loader, then applies the concepts from this chapter to a complete, realistic example: translating a C sorting routine into RISC-V assembly step by step.

Continue

Wide Address Handling and Synchronization in RISC-V

A 32-bit instruction cannot fit a large constant or a far-away memory address directly inside it, and multiple processors sharing memory cannot safely update the same data without coordination. This article explains how RISC-V builds large immediate values and addresses out of smaller pieces, and how atomic instructions allow parallel programs to synchronize safely.

Continue