OpenTitan Big Number Accelerator (OTBN) Technical Specification

Note on the status of this document

This specification is work in progress and will see significant changes before it can be considered final. We invite input of all kind through the standard means of the OpenTitan project; a good starting point is filing an issue in our GitHub issue tracker.

Overview

This document specifies functionality of the OpenTitan Big Number Accelerator, or OTBN. OTBN is a coprocessor for asymmetric cryptographic operations like RSA or Elliptic Curve Cryptography (ECC).

This module conforms to the Comportable guideline for peripheral functionality. See that document for integration overview within the broader top level system.

Features

  • Processor optimized for wide integer arithmetic
  • 32b wide control path with 32 32b wide registers
  • 256b wide data path with 32 256b wide registers
  • Full control-flow support with conditional branch and unconditional jump instructions, hardware loops, and hardware-managed call/return stacks.
  • Reduced, security-focused instruction set architecture for easier verification and the prevention of data leaks.
  • Built-in access to random numbers. Note: The (quality) properties of the provided random numbers it not specified currently; this gap in the specification will be addressed in a future revision.

Description

OTBN is a processor, specialized for the execution of security-sensitive asymmetric (public-key) cryptography code, such as RSA or ECC. Such algorithms are dominated by wide integer arithmetic, which are supported by OTBN's 256b wide data path, registers, and instructions which operate these wide data words. On the other hand, the control flow is clearly separated from the data, and reduced to a minimum to avoid data leakage.

The data OTBN processes is security-sensitive, and the processor design centers around that. The design is kept as simple as possible to reduce the attack surface and aid verification and testing. For example, no interrupts or exceptions are included in the design, and all instructions are designed to be executable within a single cycle.

OTBN is designed as a self-contained co-processor with its own instruction and data memory, which is accessible as a bus device.

Compatibility

OTBN is not designed to be compatible with other cryptographic accelerators. Its design is inspired by dcrypto, the cryptographic accelerator used in Google’s Titan chips, which itself is inspired by the Fiat Crypto Machine.

Instruction Set

OTBN is a processor with a custom instruction set, which is described in this section. The instruction set is split into two groups:

  • The base instruction subset operates on the 32b General Purpose Registers (GPRs). Its instructions are used for the control flow of a OTBN application. The base instructions are inspired by RISC-V’s RV32I instruction set, but not compatible with it.
  • The big number instruction subset operates on 256b Wide Data Registers (WDRs). Its instructions are used for data processing. Also included are compare and select instructions to perform data-dependent control flow in a safe and constant time fashion.

The subsequent sections describe first the processor state, followed by a description of the base and big number instruction subsets.

Processor State

General Purpose Registers (GPRs)

OTBN has 32 General Purpose Registers (GPRs). Each GPR is 32b wide. General Purpose Registers in OTBN are mainly used for control flow. The GPRs are defined in line with RV32I.

Note: GPRs and Wide Data Registers (WDRs) are separate register files. They are only accessible through their respective instruction subset: GPRs are accessible from the base instruction subset, and WDRs are accessible from the big number instruction subset (BN instructions).

x0 Zero. Always reads 0. Writes are ignored.
x1 Return address. Access to the call stack. Reading x1 pops an address from the call stack. Writing x1 pushes a return address to the call stack. Reading from an empty call stack results in an alert.
x2 General Purpose Register 2.
...
x31 General Purpose Register 31.

Note: Currently, OTBN has no “standard calling convention,” and GPRs except for x0 and x1 can be used for any purpose. If, at one point, a calling convention is needed, it is expected to be aligned with the RISC-V standard calling conventions, and the roles assigned to registers in that convention. Even without a agreed-on calling convention, software authors are encouraged to follow the RISC-V calling convention where it makes sense. For example, good choices for temporary registers are x6, x7, x28, x29, x30, and x31.

Control and Status Registers (CSRs)

Control and Status Registers (CSRs) are 32b wide registers used for “special” purposes, as detailed in their description; they are not related to the GPRs. CSRs can be accessed through dedicated instructions, CSRRS and CSRRW.

Number Privilege Description
0x7C0 RW FLAGS. Wide arithmetic flags. This CSR provides access to the flags used in wide integer arithmetic.
BitDescription
0Carry of Flag Group 0
1LSb of Flag Group 0
2MSb of Flag Group 0
3Zero of Flag Group 0
4Carry of Flag Group 1
5LSb of Flag Group 1
6MSb of Flag Group 1
7Zero of Flag Group 1
0x7D0 RW MOD0. Bits [31:0] of the modulus operand, used in the BN.ADDM/BN.SUBM instructions. This CSR is mapped to the MOD WSR.
0x7D1 RW MOD1. Bits [63:32] of the modulus operand, used in the BN.ADDM/BN.SUBM instructions. This CSR is mapped to the MOD WSR.
0x7D2 RW MOD2. Bits [95:64] of the modulus operand, used in the BN.ADDM/BN.SUBM instructions. This CSR is mapped to the MOD WSR.
0x7D3 RW MOD3. Bits [127:96] of the modulus operand, used in the BN.ADDM/BN.SUBM instructions. This CSR is mapped to the MOD WSR.
0x7D4 RW MOD4. Bits [159:128] of the modulus operand, used in the BN.ADDM/BN.SUBM instructions. This CSR is mapped to the MOD WSR.
0x7D5 RW MOD5. Bits [191:160] of the modulus operand, used in the BN.ADDM/BN.SUBM instructions. This CSR is mapped to the MOD WSR.
0x7D6 RW MOD6. Bits [223:192] of the modulus operand, used in the BN.ADDM/BN.SUBM instructions. This CSR is mapped to the MOD WSR.
0x7D7 RW MOD7. Bits [255:224] of the modulus operand, used in the BN.ADDM/BN.SUBM instructions. This CSR is mapped to the MOD WSR.
0xFC0 R RND. A random number.

Wide Data Registers (WDRs)

In addition to the 32b wide GPRs, OTBN has a second “wide” register file, which is used by the big number instruction subset. This register file consists of NWDR = 32 Wide Data Registers (WDRs). Each WDR is WLEN = 256b wide.

Wide Data Registers (WDRs) and the 32b General Purpose Registers (GPRs) are separate register files. They are only accessible through their respective instruction subset: GPRs are accessible from the base instruction subset, and WDRs are accessible from the big number instruction subset (BN instructions).

Register
w0
w1
w31

Wide Special Purpose Registers (WSRs)

In addition to the Wide Data Registers, BN instructions can also access WLEN-sized special purpose registers, short WSRs.

Number Privilege Description
0x1 RW MOD Modulus. To be used in the BN.ADDM and BN.SUBM instructions. This WSR is mapped to the MOD0 to MOD7 CSRs.
0x2 R RND A random number.

Flags

In addition to the wide register file, OTBN maintains global state in two groups of flags for the use by wide integer operations. Flag groups are named Flag Group 0 (FG0), and Flag Group 1 (FG1). Each group consists of four flags. Each flag is a single bit.

  • C (Carry flag). Set to 1 an overflow occurred in the last arithmetic instruction.

  • L (LSb flag). The least significant bit of the result of the last arithmetic or shift instruction.

  • M (MSb flag) The most significant bit of the result of the last arithmetic or shift instruction.

  • Z (Zero Flag) Set to 1 if the result of the last operation was zero; otherwise 0.

Loop Stack

The LOOP instruction allows for nested loops; the active loops are stored on the loop stack. Each loop stack entry is a tuple of loop count, start address, and end address. The number of entries in the loop stack is implementation-dependent.

Call Stack

A stack (LIFO) of function call return addresses (also known as “return address stack”). The number of entries in this stack is implementation-dependent.

The call stack is accessed through the x1 GPR (return address). Writing to x1 pushes to the call stack, reading from it pops an item.

Accumulator

A WLEN bit wide accumulator used by the BN.MULQACC instruction.

Instruction Format

All instructions are a fixed 32b in length and must be aligned on a four byte-boundary in memory.

Note

The instruction encoding has not been finalized yet. See issue #2391 for the current status.

Base Instruction Subset

The base instruction set of OTBN is a limited 32b instruction set. It is used together with the 32b wide General Purpose Register file. The primary use of the base instruction set is the control flow in applications.

The base instruction set is an extended subset of RISC-V's RV32I_Zcsr. Refer to the RISC-V Unprivileged Specification for a detailed instruction specification. Not all RV32 instructions are implemented. The implemented subset is shown below. Many instructions in the base instruction set have an equivalent in the big number instruction subset, enabling processor logic to be shared between the instruction subsets.

ADD

Add.

ADD <grd>, <grs1>, <grs2>

This instruction is defined in the RV32I instruction set.

ADDI

Add Immediate.

ADDI <grd>, <grs1>, <imm>

This instruction is defined in the RV32I instruction set.

LUI

Load Upper Immediate.

LUI <grd>, <imm>

This instruction is defined in the RV32I instruction set.

SUB

Subtract.

SUB <grd>, <grs1>, <grs2>

This instruction is defined in the RV32I instruction set.

AND

Bitwise AND.

AND <grd>, <grs1>, <grs2>

This instruction is defined in the RV32I instruction set.

ANDI

Bitwise AND with Immediate.

ANDI <grd>, <grs1>, <imm>

This instruction is defined in the RV32I instruction set.

OR

Bitwise OR.

OR <grd>, <grs1>, <grs2>

This instruction is defined in the RV32I instruction set.

ORI

Bitwise OR with Immediate.

ORI <grd>, <grs1>, <imm>

This instruction is defined in the RV32I instruction set.

XOR

Bitwise XOR.

XOR <grd>, <grs1>, <grs2>

This instruction is defined in the RV32I instruction set.

XORI

Bitwise XOR with Immediate.

XORI <grd>, <grs1>, <grs2>

This instruction is defined in the RV32I instruction set.

LW

Load Word.

LW <grd>, <offset>(<grs1>)

This instruction is defined in the RV32I instruction set.

SW

Store Word.

SW <grs2>, <offset>(<grs1>)

This instruction is defined in the RV32I instruction set.

BEQ

Branch Equal.

BEQ <grs1>, <grs2>, <offset>

This instruction is defined in the RV32I instruction set.

BNE

Branch Not Equal.

BNE <grs1>, <grs2>, <offset>

This instruction is defined in the RV32I instruction set.

LOOP

Note

The LOOP and LOOPI instructions are under-specified, and improvements to them are being discussed. See https://github.com/lowRISC/opentitan/issues/2496 for up-to-date information.

Loop (indirect). Repeat a sequence of code multiple times. The number of iterations is a GPR value. The length of the loop is given as immediate.

LOOP <grs>, <bodysize>

Alternative assembly notation: The size of the loop body is given by the number of instructions in the parentheses.

LOOP <grs> (
  # loop body
)
Assembly symbolDescription

<grs>

Name of the GPR containing the number of iterations

<bodysize>

Number of instructions in the loop body

LOOPI

Note

The LOOP and LOOPI instructions are under-specified, and improvements to them are being discussed. See https://github.com/lowRISC/opentitan/issues/2496 for up-to-date information.

Loop Immediate. Repeat a sequence of code multiple times. The number of iterations is given as an immediate, as is the length of the loop. The number of iterations must be larger than zero.

LOOPI <iterations>, <bodysize>

Alternative assembly notation. The size of the loop body is given by the number of instructions in the parentheses.

LOOPI <iterations> (
  # loop body
)
Assembly symbolDescription

<iterations>

Number of iterations

<bodysize>

Number of instructions in the loop body

JAL

Jump And Link.

JAL <grd>, <offset>

This instruction is defined in the RV32I instruction set.

Unlike in RV32I, the x1 (return address) GPR is hard-wired to the call stack. To call a subroutine use jal x1, <offset>.

JALR

Jump And Link Register.

JALR <grd>, <grs1>, <offset>

This instruction is defined in the RV32I instruction set.

Unlike in RV32I, the x1 (return address) GPR is hard-wired to the call stack. To return from a subroutine, use jalr x0, x1, 0.

CSRRS

Atomic Read and Set bits in CSR.

CSRRS <grd>, <csr>, <grs>

This instruction is defined in the RV32I instruction set.

CSRRW

Atomic Read/Write CSR.

CSRRW <grd>, <csr>, <grs>

This instruction is defined in the RV32I instruction set.

ECALL

Environment Call. Triggers the done interrupt to indicate the completion of the operation.

ECALL 

This instruction is defined in the RV32I instruction set.

Big Number Instruction Subset

All Big Number (BN) instructions operate on the wide register file WREG.

BN.ADD

Add. Adds two WDR values, writes the result to the destination WDR and updates flags. The content of the second source WDR can be shifted by an immediate before it is consumed by the operation.

BN.ADD <wrd>, <wrs1>, <wrs2> [, <shift_type> <shift_bytes>B] [, FG<flag_group>]

Assembly symbolDescription

<wrd>

Name of the destination WDR

<wrs1>

Name of the first source WDR

<wrs2>

Name of the second source WDR

<shift_type>

The direction of an optional shift applied to <wrs2>.

Syntax table:

Syntax Value of immediate
<< 0
>> 1

<shift_bytes>

Number of bytes by which to shift <wrs2>. Defaults to 0.

Valid range: 0..31

<flag_group>

Flag group to use. Defaults to 0.

Valid range: 0..1

Decode

d = UInt(wrd)
a = UInt(wrs1)
b = UInt(wrs2)

fg = DecodeFlagGroup(flag_group)
sb = UInt(shift_bytes)
st = DecodeShiftType(shift_type)

Operation

b_shifted = ShiftReg(b, st, sb)
(result, flags_out) = AddWithCarry(a, b_shifted, "0")

WDR[d] = result
FLAGS[flag_group] = flags_out

BN.ADDC

Add with Carry. Adds two WDR values and the Carry flag value, writes the result to the destination WDR, and updates the flags. The content of the second source WDR can be shifted by an immediate before it is consumed by the operation.

BN.ADDC <wrd>, <wrs1>, <wrs2> [, <shift_type> <shift_bytes>B] [, FG<flag_group>]

Assembly symbolDescription

<wrd>

Name of the destination WDR

<wrs1>

Name of the first source WDR

<wrs2>

Name of the second source WDR

<shift_type>

The direction of an optional shift applied to <wrs2>.

Syntax table:

Syntax Value of immediate
<< 0
>> 1

<shift_bytes>

Number of bytes by which to shift <wrs2>. Defaults to 0.

Valid range: 0..31

<flag_group>

Flag group to use. Defaults to 0.

Valid range: 0..1

Decode

d = UInt(wrd)
a = UInt(wrs1)
b = UInt(wrs2)

fg = DecodeFlagGroup(flag_group)
sb = UInt(shift_bytes)
st = DecodeShiftType(shift_type)

Operation

b_shifted = ShiftReg(b, st, sb)
(result, flags_out) = AddWithCarry(a, b_shifted, FLAGS[flag_group].C)

WDR[d] = result
FLAGS[flag_group] = flags_out

BN.ADDI

Add Immediate. Adds a zero-extended immediate to the value of a WDR, writes the result to the destination WDR, and updates the flags.

BN.ADDI <wrd>, <wrs>, <imm> [, FG<flag_group>]

Assembly symbolDescription

<wrd>

Name of the destination WDR

<wrs>

Name of the source WDR

<imm>

Immediate value

<flag_group>

Flag group to use. Defaults to 0.

Valid range: 0..1

Decode

d = UInt(wrd)
a = UInt(wrs1)

fg = DecodeFlagGroup(flag_group)
i = ZeroExtend(imm, WLEN)

Operation

(result, flags_out) = AddWithCarry(a, i, "0")

WDR[d] = result
FLAGS[flag_group] = flags_out

BN.ADDM

Pseudo-Modulo Add. Adds two WDR values, subtracts the value of the MOD WSR once if the result is equal or larger than MOD, and writes the result to the destination WDR. This operation is a modulo addition if the sum of the two input registers is smaller than twice the value of the MOD WSR. Flags are not used or saved.

BN.ADDM <wrd>, <wrs1>, <wrs2>

Decode

d = UInt(wrd)
a = UInt(wrs1)
b = UInt(wrs2)

Operation

(result, ) = AddWithCarry(a, b, "0")

if result >= MOD:
  result = result - MOD

WDR[d] = result

BN.MULQACC

Quarter-word Multiply and Accumulate. Multiplies two WLEN/4 WDR values and adds the result to an accumulator after shifting it. Optionally shifts some/all of the resulting accumulator value out to a destination WDR.

BN.MULQACC [<wb_variant>][<zero_acc>] [<wrd><wrd_hwsel>,]
<wrs1>.<wrs1_qwsel>, <wrs2>.<wrs2_qwsel>, <acc_shift_imm>

Assembly symbolDescription

<wb_variant>

Result writeback instruction variant. If no writeback variant is chosen, no destination register is written, and the multiplication result is only stored in the accumulator.

Valid values:

  • .SO: Shift out the lower half-word of the value stored in the accumulator to a WLEN/2-sized half-word of the destination WDR. The destination half-word is selected by the “wrd_hwsel” field.
  • .WO: Write the value stored in the accumulator to the destination WDR.

Syntax table:

Syntax Value of immediate
.SO 0
.WO 1

<zero_acc>

Zero the accumulator before accumulating the multiply result.

To specify, use the literal syntax .Z

<wrd>

Name of the destination WDR. Only required for .SO/.WO instruction variant.

<wrd_hwsel>

Half-word select for <wrd>. Only required for the .SO instruction variant. A value of L means the less significant half-word; U means the more significant half-word.

Syntax table:

Syntax Value of immediate
L 0
U 1

<wrs1>

Name of the first source WDR

<wrs1_qwsel>

Quarter-word select for <wrs1>.

Valid values:

  • 0: Select wrs1[WLEN/4-1:0] (least significant quarter-word)
  • 1: Select wrs1[WLEN/2:WLEN/4]
  • 2: Select wrs1[WLEN/4*3-1:WLEN/2]
  • 3: Select wrs1[WLEN-1:WLEN/4*3] (most significant quarter-word)

Valid range: 0..3

<wrs2>

Name of the second source WDR

<wrs2_qwsel>

Quarter-word select for <wrs2>.

Valid values:

  • 0: Select wrs1[WLEN/4-1:0] (least significant quarter-word)
  • 1: Select wrs1[WLEN/2:WLEN/4]
  • 2: Select wrs1[WLEN/4*3-1:WLEN/2]
  • 3: Select wrs1[WLEN-1:WLEN/4*3] (most significant quarter-word)

Valid range: 0..3

<acc_shift_imm>

How many quarter-words (WLEN/4 bits) to shift the WLEN/2-bit multiply result before accumulating.

Valid range: 0..3

Decode

writeback_variant = DecodeMulqaccVariant(wb_variant)
zero_accumulator = DecodeMulqaccZeroacc(zero_acc)

d = UInt(wrd)
a = UInt(wrs1)
b = UInt(wrs2)

d_hwsel = DecodeHalfWordSelect(wrd_hwsel)
a_qwsel = DecodeQuarterWordSelect(wrs1_qwsel)
b_qwsel = DecodeQuarterWordSelect(wrs2_qwsel)

Operation

a_qw = GetQuarterWord(a, a_qwsel)
b_qw = GetQuarterWord(b, b_qwsel)

mul_res = a_qw * b_qw

if zero_accumulator:
  ACC = 0

ACC = ACC + (mul_res << (acc_shift_imm * WLEN / 4))

if writeback_variant == 'shiftout':
  if d_hwsel == 'L':
    WDR[d][WLEN/2-1:0] = ACC[WLEN/2-1:0]
  elif d_hwsel == 'U':
    WDR[d][WLEN-1:WLEN/2] = ACC[WLEN/2-1:0]
  ACC = ACC >> (WLEN/2)

elif writeback_variant == 'writeout':
  WDR[d] = ACC

BN.SUB

Subtraction. Subtracts the second WDR value from the first one, writes the result to the destination WDR and updates flags. The content of the second source WDR can be shifted by an immediate before it is consumed by the operation.

BN.SUB <wrd>, <wrs1>, <wrs2> [, <shift_type> <shift_bytes>B] [, FG<flag_group>]

Assembly symbolDescription

<wrd>

Name of the destination WDR

<wrs1>

Name of the first source WDR

<wrs2>

Name of the second source WDR

<shift_type>

The direction of an optional shift applied to <wrs2>.

Syntax table:

Syntax Value of immediate
<< 0
>> 1

<shift_bytes>

Number of bytes by which to shift <wrs2>. Defaults to 0.

Valid range: 0..31

<flag_group>

Flag group to use. Defaults to 0.

Valid range: 0..1

Decode

d = UInt(wrd)
a = UInt(wrs1)
b = UInt(wrs2)

fg = DecodeFlagGroup(flag_group)
sb = UInt(shift_bytes)
st = DecodeShiftType(shift_type)

Operation

b_shifted = ShiftReg(b, st, sb)
(result, flags_out) = AddWithCarry(a, -b_shifted, "0")

WDR[d] = result
FLAGS[flag_group] = flags_out

BN.SUBB

Subtract with borrow. Subtracts the second WDR value and the Carry from the first one, writes the result to the destination WDR, and updates the flags. The content of the second source WDR can be shifted by an immediate before it is consumed by the operation.

BN.SUBB <wrd>, <wrs1>, <wrs2> [, <shift_type> <shift_bytes>B] [, FG<flag_group>]

Assembly symbolDescription

<wrd>

Name of the destination WDR

<wrs1>

Name of the first source WDR

<wrs2>

Name of the second source WDR

<shift_type>

The direction of an optional shift applied to <wrs2>.

Syntax table:

Syntax Value of immediate
<< 0
>> 1

<shift_bytes>

Number of bytes by which to shift <wrs2>. Defaults to 0.

Valid range: 0..31

<flag_group>

Flag group to use. Defaults to 0.

Valid range: 0..1

Decode

d = UInt(wrd)
a = UInt(wrs1)
b = UInt(wrs2)

fg = DecodeFlagGroup(flag_group)
sb = UInt(shift_bytes)
st = DecodeShiftType(shift_type)

Operation

b_shifted = ShiftReg(b, st, sb)
(result, flags_out) = AddWithCarry(a, -b_shifted, ~FLAGS[flag_group].C)

WDR[d] = result
FLAGS[flag_group] = flags_out

BN.SUBI

Subtract Immediate. Subtracts a zero-extended immediate from the value of a WDR, writes the result to the destination WDR, and updates the flags.

BN.SUBI <wrd>, <wrs>, <imm> [, FG<flag_group>]
Assembly symbolDescription

<wrd>

Name of the destination WDR

<wrs>

Name of the source WDR

<imm>

Immediate value

<flag_group>

Flag group to use. Defaults to 0.

Valid range: 0..1

Decode

d = UInt(wrd)
a = UInt(wrs1)

fg = DecodeFlagGroup(flag_group)
i = ZeroExtend(imm, WLEN)

Operation

(result, flags_out) = AddWithCarry(a, -i, "0")

WDR[d] = result
FLAGS[flag_group] = flags_out

BN.SUBM

Pseudo-modulo subtraction. Subtracts the second WDR value from the first WDR value, performs a modulo operation with the MOD WSR, and writes the result to the destination WDR. This operation is equivalent to a modulo subtraction as long as wrs1 - wrs2 >= -MOD holds. This constraint is not checked in hardware. Flags are not used or saved.

BN.SUBM <wrd>, <wrs1>, <wrs2>

Decode

d = UInt(wrd)
a = UInt(wrs1)
b = UInt(wrs2)

Operation

(result, ) = AddWithCarry(a, -b, "0")

if result >= MOD:
  result = result - MOD

WDR[d] = result

BN.AND

Bitwise AND. Performs a bitwise and operation. Takes the values stored in registers referenced by wrs1 and wrs2 and stores the result in the register referenced by wrd. The content of the second source register can be shifted by an immediate before it is consumed by the operation.

BN.AND <wrd>, <wrs1>, <wrs2> [, <shift_type> <shift_bytes>B]

Assembly symbolDescription

<wrd>

Name of the destination WDR

<wrs1>

Name of the first source WDR

<wrs2>

Name of the second source WDR

<shift_type>

The direction of an optional shift applied to <wrs2>.

Syntax table:

Syntax Value of immediate
<< 0
>> 1

<shift_bytes>

Number of bytes by which to shift <wrs2>. Defaults to 0.

Valid range: 0..31

Decode

d = UInt(wrd)
a = UInt(wrs1)
b = UInt(wrs2)

sb = UInt(shift_bytes)
st = DecodeShiftType(shift_type)

Operation

b_shifted = ShiftReg(b, st, sb)
result = a & b_shifted

WDR[d] = result

BN.OR

Bitwise OR. Performs a bitwise or operation. Takes the values stored in WDRs referenced by wrs1 and wrs2 and stores the result in the WDR referenced by wrd. The content of the second source WDR can be shifted by an immediate before it is consumed by the operation.

BN.OR <wrd>, <wrs1>, <wrs2> [, <shift_type> <shift_bytes>B]

Assembly symbolDescription

<wrd>

Name of the destination WDR

<wrs1>

Name of the first source WDR

<wrs2>

Name of the second source WDR

<shift_type>

The direction of an optional shift applied to <wrs2>.

Syntax table:

Syntax Value of immediate
<< 0
>> 1

<shift_bytes>

Number of bytes by which to shift <wrs2>. Defaults to 0.

Valid range: 0..31

Decode

d = UInt(wrd)
a = UInt(wrs1)
b = UInt(wrs2)

sb = UInt(shift_bytes)
st = DecodeShiftType(shift_type)

Operation

b_shifted = ShiftReg(b, st, sb)
result = a | b_shifted

WDR[d] = result

BN.NOT

Bitwise NOT. Negates the value in <wrs>, storing the result into <wrd>. The source value can be shifted by an immediate before it is consumed by the operation.

BN.NOT <wrd>, <wrs> [, <shift_type> <shift_bytes>B]

Assembly symbolDescription

<wrd>

Name of the destination WDR

<wrs>

Name of the source WDR

<shift_type>

The direction of an optional shift applied to <wrs2>.

Syntax table:

Syntax Value of immediate
<< 0
>> 1

<shift_bytes>

Number of bytes by which to shift <wrs2>. Defaults to 0.

Valid range: 0..31

Decode

d = UInt(wrd)
a = UInt(wrs1)

sb = UInt(shift_bytes)
st = DecodeShiftType(shift_type)

Operation

a_shifted = ShiftReg(a, st, sb)
result = ~a_shifted

WDR[d] = result

BN.XOR

Bitwise XOR. Performs a bitwise xor operation. Takes the values stored in WDRs referenced by wrs1 and wrs2 and stores the result in the WDR referenced by wrd. The content of the second source WDR can be shifted by an immediate before it is consumed by the operation.

BN.XOR <wrd>, <wrs1>, <wrs2> [, <shift_type> <shift_bytes>B]

Assembly symbolDescription

<wrd>

Name of the destination WDR

<wrs1>

Name of the first source WDR

<wrs2>

Name of the second source WDR

<shift_type>

The direction of an optional shift applied to <wrs2>.

Syntax table:

Syntax Value of immediate
<< 0
>> 1

<shift_bytes>

Number of bytes by which to shift <wrs2>. Defaults to 0.

Valid range: 0..31

Decode

d = UInt(wrd)
a = UInt(wrs1)
b = UInt(wrs2)

sb = UInt(shift_bytes)
st = DecodeShiftType(shift_type)

Operation

b_shifted = ShiftReg(b, st, sb)
result = a ^ b_shifted

WDR[d] = result

BN.RSHI

Concatenate and right shift immediate. The concatenation of the content from the WDRs referenced by wrs1 and wrs2 (wrs1 forms the upper part) is right shifted by an immediate value and truncated to WLEN bit. The result is stored in the WDR referenced by wrd.

BN.RSHI <wrd>, <wrs1>, <wrs2> >> <imm>

Assembly symbolDescription

<wrd>

Name of the destination WDR

<wrs1>

Name of the first source WDR

<wrs2>

Name of the second source WDR

<imm>

Number of bits to shift the second source register by. Valid range: 0..(WLEN-1).

Decode

d = UInt(wrd)
a = UInt(wrs1)
b = UInt(wrs2)
imm = Uint(imm)

Operation

WDR[d] = ((rs1 | rs2) >> im)[WLEN-1:0]

BN.SEL

Flag Select. Returns in the destination WDR the value of the first source WDR if the flag in the chosen flag group is set, otherwise returns the value of the second source WDR.

BN.SEL <wrd>, <wrs1>, <wrs2>, [FG<flag_group>.]<flag>

Assembly symbolDescription

<wrd>

Name of the destination WDR

<wrs1>

Name of the first source WDR

<wrs2>

Name of the second source WDR

<flag_group>

Flag group to use. Defaults to 0.

Valid range: 0..1

<flag>

Flag to check. Valid values:

  • C: Carry flag
  • M: MSB flag
  • L: LSB flag
  • Z: Zero flag

Syntax table:

Syntax Value of immediate
C 0
M 1
L 2
Z 3

Decode

d = UInt(wrd)
a = UInt(wrs1)
b = UInt(wrs2)
fg = DecodeFlagGroup(flag_group)
flag = DecodeFlag(flag)

Operation

flag_is_set = FLAGS[fg].get(flag)

WDR[d] = wrs1 if flag_is_set else wrs2

BN.CMP

Compare. Subtracts the second WDR value from the first one and updates flags. This instruction is identical to BN.SUB, except that no result register is written.

BN.CMP <wrs1>, <wrs2> [, <shift_type> <shift_bytes>B] [, FG<flag_group>]

Assembly symbolDescription

<wrs1>

Name of the first source WDR

<wrs2>

Name of the second source WDR

<shift_type>

The direction of an optional shift applied to <wrs2>.

Syntax table:

Syntax Value of immediate
<< 0
>> 1

<shift_bytes>

Number of bytes by which to shift <wrs2>. Defaults to 0.

Valid range: 0..31

<flag_group>

Flag group to use. Defaults to 0.

Valid range: 0..1

Decode

a = UInt(wrs1)
b = UInt(wrs2)

fg = DecodeFlagGroup(flag_group)
sb = UInt(shift_bytes)
st = DecodeShiftType(shift_type)

Operation

b_shifted = ShiftReg(b, st, sb)
(, flags_out) = AddWithCarry(a, -b_shifted, "0")

FLAGS[flag_group] = flags_out

BN.CMPB

Compare with Borrow. Subtracts the second WDR value from the first one and updates flags. This instruction is identical to BN.SUBB, except that no result register is written.

BN.CMPB <wrs1>, <wrs2> [, <shift_type> <shift_bytes>B] [, FG<flag_group>]

Assembly symbolDescription

<wrs1>

Name of the first source WDR

<wrs2>

Name of the second source WDR

<shift_type>

The direction of an optional shift applied to <wrs2>.

Syntax table:

Syntax Value of immediate
<< 0
>> 1

<shift_bytes>

Number of bytes by which to shift <wrs2>. Defaults to 0.

Valid range: 0..31

<flag_group>

Flag group to use. Defaults to 0.

Valid range: 0..1

Decode

a = UInt(wrs1)
b = UInt(wrs2)

fg = DecodeFlagGroup(flag_group)
sb = UInt(shift_bytes)
st = DecodeShiftType(shift_type)

Operation

(, flags_out) = AddWithCarry(a, -b, ~FLAGS[flag_group].C)

FLAGS[flag_group] = flags_out

BN.LID

Load Word (indirect source, indirect destination). Calculates a byte memory address by adding the offset to the value in the GPR grs1. The value from this memory address is then copied into the WDR pointed to by the value in GPR grd.

After the operation, either the value in the GPR grs1, or the value in grd can be optionally incremented.

  • If grs1_inc is set, the value in grs1 is incremented by the value WLEN/8 (one word).
  • If grd_inc is set, the value in grd is incremented by the value 1.

TODO: how to handle overflows?

BN.LID <grd>[<grd_inc>], <offset>(<grs1>[<grs1_inc>])

Assembly symbolDescription

<grd>

Name of the GPR referencing the destination WDR

<grs1>

Name of the GPR containing the memory byte address. The value contained in the referenced GPR must be WLEN-aligned.

<offset>

Offset value. Must be WLEN-aligned.

<grs1_inc>

Increment the value in <grs1> by WLEN/8 (one word). Cannot be specified together with grd_inc.

To specify, use the literal syntax +

<grd_inc>

Increment the value in <grd> by one. Cannot be specified together with grs1_inc.

To specify, use the literal syntax +

Decode

rd = UInt(grd)
rs1 = UInt(grs1)
offset = UInt(offset)

Operation

memaddr = GPR[rs1] + (offset / WLEN / 8)
wdr_dest = GPR[rd]

WDR[wdr_dest] = LoadWlenWordFromMemory(memaddr)

if grs1_inc and grd_inc:
    raise Unsupported() \# prevented in encoding
if grs1_inc:
    GPR[rs1] = GPR[rs1] + (WLEN / 8)
if grd_inc:
    GPR[rd] = GPR[rd] + 1

BN.SID

Store Word (indirect source, indirect destination). Calculates a byte memory address by adding the offset to the value in the GPR grs1. The value from the WDR pointed to by grs2 is then copied into the memory.

After the operation, either the value in the GPR grs1, or the value in grs2 can be optionally incremented.

  • If grs1_inc is set, the value in grs1 is incremented by the value WLEN/8 (one word).
  • If grs2_inc is set, the value in grs2 is incremented by the value 1.
BN.SID <grs1>[<grs1_inc>], <offset>(<grs2>[<grs2_inc>])

Assembly symbolDescription

<grs1>

Name of the GPR containing the memory byte address. The value contained in the referenced GPR must be WLEN-aligned.

<grs2>

Name of the GPR referencing the source WDR.

<offset>

Offset value. Must be WLEN-aligned.

<grs1_inc>

Increment the value in <grs1> by WLEN/8 (one word). Cannot be specified together with grs2_inc.

To specify, use the literal syntax +

<grs2_inc>

Increment the value in <grs2> by one. Cannot be specified together with grs1_inc.

To specify, use the literal syntax +

Decode

rs1 = UInt(grs1)
rs2 = UInt(grs2)
offset = UInt(offset)

Operation

memaddr = GPR[rs1] + offset / WLEN / 8
wdr_src = GPR[rs2]

StoreWlenWordToMemory(memaddr, WDR[wdr_src])

if grs1_inc and grs2_inc:
    raise Unsupported() # prevented in encoding
if grs1_inc:
    GPR[rs1] = GPR[rs1] + (WLEN / 8)
if grs2_inc:
    GPR[rs2] = GPR[rs2] + 1

BN.MOV

Copy content between WDRs (direct addressing).

BN.MOV <wrd>, <wrs>

Decode

s = UInt(wrs)
d = UInt(wrd)

Operation

WDR[d] = WDR[s]

BN.MOVR

Copy content between WDRs (register-indirect addressing). Copy WDR contents between registers with indirect addressing. Optionally, either the source or the destination register address can be incremented by 1.

BN.MOVR <grd>[<grd_inc>], <grs>[<grs_inc>]

Assembly symbolDescription

<grd>

Name of the GPR containing the destination WDR.

<grs>

Name of the GPR referencing the source WDR.

<grd_inc>

Increment the value in <grd> by one. Cannot be specified together with grs_inc.

To specify, use the literal syntax +

<grs_inc>

Increment the value in <grs> by one. Cannot be specified together with grd_inc.

To specify, use the literal syntax +

Decode

s = UInt(grs)
d = UInt(grd)

Operation

WDR[GPR[d]] = WDR[GPR[s]]

if grs_inc:
  GPR[s] = GPR[s] + 1
if grd_inc:
  GPR[d] = GPR[d] + 1

BN.WSRRS

Atomic Read and Set Bits in WSR.

BN.WSRRS <wrd>, <wsr>, <wrs>

BN.WSRRW

Atomic Read/Write WSR.

BN.WSRRW <wrd>, <wsr>, <wrs>

Pseudo-Code Functions for BN Instructions

The instruction description uses Python-based pseudocode. Commonly used functions are defined once below.

Note

This “pseudo-code” is intended to be Python 3, and contains known inconsistencies at the moment. It will be further refined as we make progress in the implementation of a simulator using this syntax.

class Flag(Enum):
  C: Bits[1]
  M: Bits[1]
  L: Bits[1]
  Z: Bits[1]

class FlagGroup:
  C: Bits[1]
  M: Bits[1]
  L: Bits[1]
  Z: Bits[1]

  def set(self, flag: Flag, value: Bits[1]):
    assert flag in Flag

    if flag == Flag.C:
      self.C = value
    elif flag == Flag.M:
      self.M = value
    elif flag == Flag.L:
      self.L = value
    elif flag == Flag.Z:
      self.Z = value

  def get(self, flag: Flag):
    assert flag in Flag

    if flag == Flag.C:
      return self.C
    elif flag == Flag.M:
      return self.M
    elif flag == Flag.L:
      return self.L
    elif flag == Flag.Z:
      return self.Z


class ShiftType(Enum):
  LSL = 0 # logical shift left
  LSR = 1 # logical shift right

class HalfWord(Enum):
  LOWER = 0 # lower or less significant half-word
  UPPER = 1 # upper or more significant half-word

def DecodeShiftType(st: Bits(1)) -> ShiftType:
  if st == 0:
    return ShiftType.LSL
  elif st == 1:
    return ShiftType.LSR
  else:
    raise UndefinedException()

def DecodeFlagGroup(flag_group: Bits(1)) -> UInt:
  if flag_group > 1:
    raise UndefinedException()
  return UInt(flag_group)

def DecodeFlag(flag: Bits(1)) -> Flag:
  if flag == 0:
    return ShiftType.C
  elif flag == 1:
    return ShiftType.M
  elif flag == 2:
    return ShiftType.L
  elif flag == 3:
    return ShiftType.Z
  else:
    raise UndefinedException()


def ShiftReg(reg, shift_type, shift_bytes) -> Bits(N):
  if ShiftType == ShiftType.LSL:
    return GPR[reg] << shift_bytes << 3
  elif ShiftType == ShiftType.LSR:
    return GPR[reg] >> shift_bytes >> 3

def AddWithCarry(a: Bits(WLEN), b: Bits(WLEN), carry_in: Bits(1)) -> (Bits(WLEN), FlagGroup):
  result: Bits[WLEN+1] = a + b + carry_in

  flags_out = FlagGroup()
  flags_out.C = result[WLEN]
  flags_out.L = result[0]
  flags_out.M = result[WLEN-1]
  flags_out.Z = (result == 0)

  return (result[WLEN-1:0], flags_out)

def DecodeHalfWordSelect(hwsel: Bits(1)) -> HalfWord:
  if hwsel == 0:
    return HalfWord.LOWER
  elif hwsel == 1:
    return HalfWord.UPPER
  else:
    raise UndefinedException()

def GetHalfWord(reg: integer, hwsel: HalfWord) -> Bits(WLEN/2):
  if hwsel == HalfWord.LOWER:
    return GPR[reg][WLEN/2-1:0]
  elif hwsel == HalfWord.UPPER:
    return GPR[reg][WLEN-1:WLEN/2]

def LoadWlenWordFromMemory(byteaddr: integer) -> Bits(WLEN):
  wordaddr = byteaddr >> 5
  return DMEM[wordaddr]

def StoreWlenWordToMemory(byteaddr: integer, storedata: Bits(WLEN)):
  wordaddr = byteaddr >> 5
  DMEM[wordaddr] = storedata

Theory of Operations

Block Diagram

OTBN architecture block diagram

Hardware Interfaces

Referring to the Comportable guideline for peripheral device functionality, the module otbn has the following hardware interfaces defined.

Primary Clock: clk_i

Other Clocks: none

Bus Device Interface: tlul

Bus Host Interface: none

Peripheral Pins for Chip IO: none

Interrupts:

Interrupt NameDescription
doneOTBN has completed the operation
errAn error occurred. Read the ERR_CODE register for error details.

Security Alerts:

Alert NameDescription
imem_uncorrectableUncorrectable error in the instruction memory detected.
dmem_uncorrectableUncorrectable error in the data memory detected.
reg_uncorrectableUncorrectable error in one of the register files detected.

Design Details

Note

To be filled in as we create the implementation.

Programmers Guide

Note

This section will be written as we move on in the design and implementation process.

Operation

Note

The exact sequence of operations is not yet finalized.

Rough expected process:

Error conditions

Note

To be filled in as we create the implementation.

Register Table

otbn.INTR_STATE @ + 0x0
Interrupt State Register
Reset default = 0x0, mask 0x3
31302928272625242322212019181716
 
1514131211109876543210
  err done
BitsTypeResetNameDescription
0rw1c0x0doneOTBN has completed the operation
1rw1c0x0errAn error occurred. Read the ERR_CODE register for error details.


otbn.INTR_ENABLE @ + 0x4
Interrupt Enable Register
Reset default = 0x0, mask 0x3
31302928272625242322212019181716
 
1514131211109876543210
  err done
BitsTypeResetNameDescription
0rw0x0doneEnable interrupt when INTR_STATE.done is set
1rw0x0errEnable interrupt when INTR_STATE.err is set


otbn.INTR_TEST @ + 0x8
Interrupt Test Register
Reset default = 0x0, mask 0x3
31302928272625242322212019181716
 
1514131211109876543210
  err done
BitsTypeResetNameDescription
0wo0x0doneWrite 1 to force INTR_STATE.done to 1
1wo0x0errWrite 1 to force INTR_STATE.err to 1


otbn.CMD @ + 0xc
command register
Reset default = 0x0, mask 0x3
31302928272625242322212019181716
 
1514131211109876543210
  dummy start
BitsTypeResetNameDescription
0r0w1c0x0startStart the operation The completion is signalled by the done interrupt.
1r0w1c0x0dummyReggen doesn't generate sub-fields with only a single field specified; instead, the whole register is taken as a field, leading to signals like `hw2reg.status.d` instead of `hw2reg.status.start.d`. Since we expect to add more commands later, we force the generation of fields with this dummy field for now.


otbn.STATUS @ + 0x10
Status
Reset default = 0x0, mask 0x3
31302928272625242322212019181716
 
1514131211109876543210
  dummy busy
BitsTypeResetNameDescription
0ro0x0busyOTBN is performing an operation.
1ro0x0dummySee CMD.dummy for details.


otbn.ERR_CODE @ + 0x14
Error Code
Reset default = 0x0, mask 0xffffffff
31302928272625242322212019181716
err_code...
1514131211109876543210
...err_code
BitsTypeResetNameDescription
31:0ro0x0err_codeThe error cause if an error occurred. TODO: Define error codes. Software should read this register before clearing the err interrupt to avoid race conditions.


otbn.START_ADDR @ + 0x18
Start byte address in the instruction memory
Reset default = 0x0, mask 0xffffffff
31302928272625242322212019181716
start_addr...
1514131211109876543210
...start_addr
BitsTypeResetNameDescription
31:0wo0x0start_addrByte address in the instruction memory OTBN starts to execute from when instructed to do so with the CMD.start .


otbn.IMEM @ + 0x100000
1024 item rw window
Byte writes are not supported
310
+0x100000 
+0x100004 
 ...
+0x100ff8 
+0x100ffc 
Instruction Memory. Not accessible during the operation of the engine. TODO: Discuss and document behavior in that case. Alert? Ignore?


otbn.DMEM @ + 0x200000
1024 item rw window
Byte writes are not supported
310
+0x200000 
+0x200004 
 ...
+0x200ff8 
+0x200ffc 
Data Memory. Not accessible during the operation of the engine. TODO: Discuss and document behavior in that case. Alert? Ignore?


Algorithic Example: Replacing BN.MULH with BN.MULQACC

This specification gives the implementers the option to provide either a quarter-word multiply-accumulate instruction, BN.MULQADD, or a half-word multiply instruction, BN.MULH. Four BN.MULQACC can be used to replace one BN.MULH instruction, which is able to operate on twice the data size.

BN.MULH r1, r0.l, r0.u becomes

BN.MULQACC.Z      r0.0, r0.2, 0
BN.MULQACC        r0.0, r0.3, 64
BN.MULQACC        r0.1, r0.2, 64
BN.MULQACC.WO r1, r0.1, r0.3, 128

Algorithmic Example: Multiplying two WLEN numbers with BN.MULQACC

The big number instruction subset of OTBN generally operates on WLEN bit numbers. However, the multiplication instructions only operate on half or quarter-words of WLEN bit. This section outlines a technique to multiply two WLEN-bit numbers with the use of the quarter-word multiply-accumulate instruction BN.MULQACC.

The shift out functionality can be used to perform larger multiplications without extra adds. The table below shows how two registers wr0 and wr1 can be multiplied together to give a result in wr2 and wr3. The cells on the right show how the result is built up a0:a3 = wr0.0:wr0.3 and b0:b3 = wr1.0:wr1.3. The sum of a column represents WLEN/4 bits of a destination register, where c0:c3 = wr2.0:wr2.3 and d0:d3 = wr3.0:wr3.3. Each cell with a multiply in takes up two WLEN/4-bit columns to represent the WLEN/2-bit multiply result. The current accumulator in each instruction is represented by highlighted cells where the accumulator value will be the sum of the highlighted cell and all cells above it.

The outlined technique can be extended to arbitrary bit widths but requires unrolled code with all operands in registers.

d3 d2 d1 d0 c3 c2 c1 c0
BN.MULQACC.Z wr0.0, wr1.0, 0 a0 * b0
BN.MULQACC wr0.1, w1.0, 64 a1 * b0
BN.MULQACC.SO wr2.l, wr0.0, wr1.1, 64 a0 * b1
BN.MULQACC wr0.2, wr1.0, 0 a2 * b0
BN.MULQACC wr0.1, wr1.1, 0 a1 * b1
BN.MULQACC wr0.0, wr1.2, 0 a0 * b2
BN.MULQACC wr0.3, wr1.0, 64 a3 * b0
BN.MULQACC wr0.2, wr1.1, 64 a2 * b1
BN.MULQACC wr0.1, wr1.2, 64 a1 * b2
BN.MULQACC.SO wr2.u, wr0.0, wr1.3, 64 a0 * b3
BN.MULQACC wr0.3, wr1.1, 0 a3 * b1
BN.MULQACC wr0.2, wr1.2, 0 a2 * b2
BN.MULQACC wr0.1, wr1.3, 0 a1 * b3
BN.MULQACC wr0.3, wr1.2, 64 a3 * b2
BN.MULQACC.SO wr3.l, wr0.2, wr1.3, 64 a2 * b3
BN.MULQACC.SO wr3.u, wr0.3, wr1.3, 0 a3 * b3