|  | # OpenTitan Big Number Accelerator (OTBN) Instruction Set Architecture | 
|  |  | 
|  | This document describes the instruction set for OTBN. | 
|  | For more details about the processor itself, see the [OTBN Technical Specification](../README.md). | 
|  | In particular, this document assumes knowledge of the *Processor State* section from that guide. | 
|  |  | 
|  | The instruction set is split into *base* and *big number* subsets. | 
|  | The base subset (described first) is similar to RISC-V's RV32I instruction set. | 
|  | It also includes a hardware call stack and hardware loop instructions. | 
|  | The big number subset is designed to operate on 256b WDRs. | 
|  | It doesn't include any control flow instructions, and just supports load/store, logical and arithmetic operations. | 
|  |  | 
|  | In the instruction documentation that follows, each instruction has a syntax example. | 
|  | For example, the `SW` instruction has syntax: | 
|  | ``` | 
|  | SW <grs2>, <offset>(<grs1>) | 
|  | ``` | 
|  | This means that it takes three operands, called `grs2`, `offset` and `grs1`. | 
|  | These operands are further documented in a table. | 
|  | Immediate operands like `offset` show their valid range of values. | 
|  |  | 
|  | Below the table of operands is an encoding table. | 
|  | This shows how the 32 bits of the instruction word are filled in. | 
|  | Ranges of bits that map to an operand are named (in capitals) and those names are used in the operand table. | 
|  | For example, the `SW` instruction's `offset` operand is split across two ranges of bits (31:25 and 11:7) called `OFF_1` and `OFF_0`, respectively. | 
|  |  | 
|  | # Pseudo-code for operation descriptions | 
|  |  | 
|  | Each instruction has an Operation section. | 
|  | This is written in a Python-like pseudo-code, generated from the instruction set simulator (which can be found at `hw/ip/otbn/dv/otbnsim`). | 
|  | The code is generated from Python, but there are some extra changes made to aid readability. | 
|  |  | 
|  | All instruction operands are considered to be in scope and have integer values. | 
|  | These values come from the encoded bits in the instruction and the operand table for the instruction describes exactly how they are decoded. | 
|  | Some operands are encoded PC-relative. | 
|  | Such an operand has its absolute value (an address) when it appears in the Operation section. | 
|  |  | 
|  | Some state updates are represented as an assignment, but take effect at the end of the instruction. | 
|  | This includes register updates or jumps and branches (updating the PC). | 
|  | To denote this, we use the ⇐ symbol, reminiscent of Verilog's non-blocking assignment. | 
|  |  | 
|  | The program counter (PC) is represented as a variable called `PC`. | 
|  |  | 
|  | Machine registers are accessed with an array syntax. | 
|  | These arrays are: | 
|  |  | 
|  | - `GPRs`: General purpose registers | 
|  | - `WDRs`: Wide data registers | 
|  | - `CSRs`: Control and status registers | 
|  | - `WSRs`: Wide special purpose registers | 
|  |  | 
|  | Accesses to these arrays are as unsigned integers. | 
|  | The instruction descriptions are written to ensure that any value written to a register is representable. | 
|  | For example, a write to `GPRs[2]` will always have a non-negative value less than `1 << 32`. | 
|  |  | 
|  | Memory accesses are represented as function calls. | 
|  | This is because the memory can be accessed on either the narrow or the wide side, which isn't easy to represent with an array syntax. | 
|  | Memory loads are represented as `DMEM.load_u32(addr)`, `DMEM.load_u256(addr)`. | 
|  | Memory stores are represented as `DMEM.store_u32(addr, value)` and `DMEM.store_u256(addr, value)`. | 
|  | In all cases, memory values are interpreted as unsigned integers and, as for register accesses, the instruction descriptions are written to ensure that any value stored to memory is representable. | 
|  |  | 
|  | Some instructions can stall for one or more cycles (those instructions that access memory, CSRs or WSRs). | 
|  | To represent this precisely in the pseudo-code, and the simulator reference model, such instructions execute a `yield` statement to stall the processor for a cycle. | 
|  |  | 
|  | There are a few other helper functions, defined here to avoid having to inline their bodies into each instruction. | 
|  | ```python3 | 
|  | def from_2s_complement(n: int) -> int: | 
|  | '''Interpret the bits of unsigned integer n as a 32-bit signed integer''' | 
|  | assert 0 <= n < (1 << 32) | 
|  | return n if n < (1 << 31) else n - (1 << 32) | 
|  |  | 
|  |  | 
|  | def to_2s_complement(n: int) -> int: | 
|  | '''Interpret the bits of signed integer n as a 32-bit unsigned integer''' | 
|  | assert -(1 << 31) <= n < (1 << 31) | 
|  | return (1 << 32) + n if n < 0 else n | 
|  |  | 
|  | def logical_byte_shift(value: int, shift_type: int, shift_bytes: int) -> int: | 
|  | '''Logical shift value by shift_bytes to the left or right. | 
|  |  | 
|  | value should be an unsigned 256-bit value. shift_type should be 0 (shift | 
|  | left) or 1 (shift right), matching the encoding of the big number | 
|  | instructions. shift_bytes should be a non-negative number of bytes to shift | 
|  | by. | 
|  |  | 
|  | Returns an unsigned 256-bit value, truncating on an overflowing left shift. | 
|  |  | 
|  | ''' | 
|  | mask256 = (1 << 256) - 1 | 
|  | assert 0 <= value <= mask256 | 
|  | assert 0 <= shift_type <= 1 | 
|  | assert 0 <= shift_bytes | 
|  |  | 
|  | shift_bits = 8 * shift_bytes | 
|  | shifted = value << shift_bits if shift_type == 0 else value >> shift_bits | 
|  | return shifted & mask256 | 
|  |  | 
|  | def extract_quarter_word(value: int, qwsel: int) -> int: | 
|  | '''Extract a 64-bit quarter word from a 256-bit value.''' | 
|  | assert 0 <= value < (1 << 256) | 
|  | assert 0 <= qwsel <= 3 | 
|  | return (value >> (qwsel * 64)) & ((1 << 64) - 1) | 
|  | ``` | 
|  |  | 
|  | # Errors | 
|  |  | 
|  | OTBN can detect various errors when it is operating. | 
|  | For details about OTBN's approach to error handling, see the [Errors section](../README.md#design-details-errors) of the Technical Specification. | 
|  | The instruction descriptions below describe any software errors that executing the instruction can cause. | 
|  | These errors are listed explicitly and also appear in the pseudo-code description, where the code sets a bit in the `ERR_BITS` register with a call to `state.stop_at_end_of_cycle()`. | 
|  |  | 
|  | Other errors are possible at runtime. | 
|  | Specifically, any instruction that reads from a GPR or WDR might detect a register integrity error. | 
|  | In this case, OTBN will set the `REG_INTG_VIOLATION` bit. | 
|  | Similarly, an instruction that loads from memory might detect a DMEM integrity error. | 
|  | In this case, OTBN will set the `DMEM_INTG_VIOLATION` bit. | 
|  |  | 
|  | TODO: | 
|  | Specify interactions between these fatal errors and any other errors. | 
|  | In particular, how do they interact with instructions that could cause other errors as well? | 
|  |  | 
|  | <!-- Documentation for the instructions in the ISA. Generated from ../data/insns.yml. --> | 
|  | # Base Instruction Subset | 
|  |  | 
|  | {{#otbn-isa base }} | 
|  |  | 
|  | # Big Number Instruction Subset | 
|  |  | 
|  | {{#otbn-isa bignum }} |