Analytical Combinator

by bruno77

A programmable circuit combinator controlled via a RISC-V inspired assembly language. Supports reading red and green input signals and writing up to four output signals per tick.

Content
2 hours ago
2.0
8
Circuit network
Owner:
bruno77
Source:
https://github.com/bruno7seven/analyt...
Homepage:
N/A
License:
MIT
Created:
a day ago
Latest Version:
0.8.11 (2 hours ago)
Factorio version:
2.0
Downloaded by:
8 users

Analytical Combinator

A Factorio mod adding a programmable circuit combinator
controlled via a RISC-V inspired assembly language.

Inspired and derived from joalon's Assembly Combinator, but heavily modified to include read signal capability.

All development was done by Anthropic's Claude AI.

Usage

Download it from the mod portal.

The analytical combinator is based on a Decider Combinator and supports both
input (red and green wires) and output circuit network connections.

Instruction set

Arithmetic

Instruction Syntax Description
ADDI rd, rs, imm rd = rs + imm — add immediate constant
ADD rd, rs, rt rd = rs + rt — add two registers
SUB rd, rs, rt rd = rs - rt — subtract register
MUL rd, rs, rt rd = rs * rt — multiply two registers
MULI rd, rs, imm rd = rs * imm — multiply by immediate
DIV rd, rs, rt rd = floor(rs / rt) — integer division; error on divide-by-zero
DIVI rd, rs, imm rd = floor(rs / imm) — divide by immediate; error on zero
REM rd, rs, rt rd = rs % rt — remainder; sign follows dividend (C-style)
REMI rd, rs, imm rd = rs % imm — remainder by immediate

Comparison

Instruction Syntax Description
SLT rd, rs, rt rd = (rs < rt) ? 1 : 0
SLTI rd, rs, imm rd = (rs < imm) ? 1 : 0

Bitwise

Instruction Syntax Description
AND rd, rs, rt rd = rs & rt — bitwise AND
OR rd, rs, rt rd = rs \| rt — bitwise OR
XOR rd, rs, rt rd = rs ^ rt — bitwise XOR
NOT rd, rs rd = ~rs — bitwise NOT (unary)

Shifts

Shift instructions follow the RISC-V naming convention. Left shifts are always
logical (zero-fill). Right shifts come in two flavours — logical (zero-fill from
the left, use when treating the value as unsigned) and arithmetic (sign-extend,
use when treating the value as a signed integer).

Instruction Syntax Description
SLL rd, rs, rt rd = rs << rt — shift left logical by register
SLLI rd, rs, imm rd = rs << imm — shift left logical by immediate
SRL rd, rs, rt rd = rs >> rt — shift right logical (zero-fill) by register
SRLI rd, rs, imm rd = rs >> imm — shift right logical (zero-fill) by immediate
SRA rd, rs, rt rd = rs >> rt — shift right arithmetic (sign-extend) by register
SRAI rd, rs, imm rd = rs >> imm — shift right arithmetic (sign-extend) by immediate

Control flow

Instruction Syntax Description
JAL rd, label Jump to label; save address of next instruction in rd (use x0 to discard)
JR rs Jump to address in rs — subroutine return. JR x0 restarts from line 1.
BEQ rs, rt, label Branch if rs == rt
BNE rs, rt, label Branch if rs != rt
BLT rs, rt, label Branch if rs < rt
BLE rs, rt, label Branch if rs <= rt
BGT rs, rt, label Branch if rs > rt
BGE rs, rt, label Branch if rs >= rt
BEQI rs, imm, label Branch if rs == imm (immediate)
BNEI rs, imm, label Branch if rs != imm
BLTI rs, imm, label Branch if rs < imm
BLEI rs, imm, label Branch if rs <= imm
BGTI rs, imm, label Branch if rs > imm
BGEI rs, imm, label Branch if rs >= imm

Circuit network output

Instruction Syntax Description
WSIG od, signal, rs Write signal to output channel od (o0–o3) with count from rs

Circuit network input

Instruction Syntax Description
RSIGR rd, signal Read named signal from the red input wire into rd (0 if absent)
RSIGG rd, signal Read named signal from the green input wire into rd (0 if absent)
CNTSR rd Set rd to the count of distinct signals on the red input wire
CNTSG rd Set rd to the count of distinct signals on the green input wire

Control

Instruction Syntax Description
WAIT imm or rs Stall for N game ticks (60 ticks = 1 second)
NOP No operation
HLT Halt execution

Registers

  • x0x31: general-purpose integer registers. x0 is always 0 (writes ignored).
  • o0o3: output signal registers, written by WSIG, emitted on the output network each tick.

Immediate value formats

All instructions that take an immediate (imm) argument accept integers in
decimal, hexadecimal (0x prefix), or negative decimal. Octal
(0-prefix) is technically accepted by the Lua parser but best avoided.

ADDI x10, x0, 255       # decimal
ADDI x10, x0, 0xFF      # hex — same value, preferred for bitmasks
ADDI x10, x0, -1        # negative decimal
SHLI x11, x10, 0x4      # shift amount as hex (unusual but valid)

Hex is especially useful with bitwise instructions:

ADDI  x10, x0,  0xFF00FF  # load a bitmask
AND   x11, x12, x10        # apply the mask
SRLI  x11, x11, 0x8        # extract middle byte

Example programs

Simple counter (output only)

main:
    ADDI x10, x0, 0             # Initialize counter to 0
loop:
    ADDI x10, x10, 1            # Increment counter
    WSIG o1, copper-plate, x10  # Output counter value
    WAIT 60                     # Wait 1 second (60 game ticks)
    SLTI x6, x10, 100           # Check if counter < 100
    BNE  x6, x0, loop           # Branch if not equal to zero
    JAL  x1, main               # Jump back to main

Threshold gate (read input, control output)

Reads an iron-ore count from the green wire. Once it exceeds 500,
outputs signal-A = 1 on the output connector and halts. The signal
appears on whichever wire(s) — red, green, or both — are physically
connected to the output side of the combinator.

poll:
    RSIGG x10, iron-ore         # Read iron-ore from green wire
    SLTI  x6, x10, 500          # x6 = 1 if count < 500
    BNE   x6, x0, poll          # Keep polling until threshold met
    ADDI  x11, x0, 1
    WSIG  o0, signal-A, x11     # Emit signal-A = 1
    HLT

Subroutine call with JAL / JR

JAL rd, label saves the address of the next instruction into rd, then jumps
to label. JR rd jumps to the address in rd, returning execution to the
instruction after the call site. Use a different link register for each call
frame. Recursive calls are not supported (no stack), but sequential calls to
shared subroutines work cleanly.

JR x0 is a special case: since x0 is always 0 and 0 is not a valid line
number, it is defined to restart the program from line 1.

main:
    RSIGG x10, iron-plate
    JAL   x1, clamp_255          # x1 = return address; jump to clamp_255
    WSIG  o0, iron-plate, x10   # resumes here after return
    RSIGG x10, copper-plate
    JAL   x1, clamp_255          # reuse the same subroutine and same link register
    WSIG  o1, copper-plate, x10
    WAIT  60
    JR    x0                     # restart from line 1 (same as JAL x0, main if main: is on line 1)

clamp_255:                       # expects value in x10, returns clamped value in x10
    SLTI  x6,  x10, 256         # x6 = 1 if value already in range
    BNE   x6,  x0,  clamp_ret  # skip clamp if already in range
    ADDI  x10, x0,  255         # clamp to 255
clamp_ret:
    JR    x1                    # return to caller

Bit masking — extract low byte

Factorio signals are 32-bit integers. Use AND to isolate the lower 8 bits,
useful if you are packing multiple small values into a single signal channel.

    RSIGR x10, signal-A         # Read packed value from red wire
    ADDI  x11, x0,  255         # Mask = 0xFF
    AND   x12, x10, x11         # x12 = low byte of signal-A
    SRLI  x13, x10, 8           # x13 = next byte up (logical: zero-fills upper bits)
    WSIG  o0, signal-A, x12     # Output low byte
    WSIG  o1, signal-B, x13     # Output second byte
    HLT

Signal presence detection with CNTSG

Fire signal-A when anything appears on the green wire (useful as a
"something arrived" trigger without caring what the signal is).

poll:
    CNTSG x10                   # x10 = number of distinct signals on green
    BEQ   x10, x0, poll         # Loop while nothing is present
    ADDI  x11, x0, 1
    WSIG  o0, signal-A, x11     # Trigger output
    WAIT  60                    # Hold for 1 second
    ADDI  x11, x0, 0
    WSIG  o0, signal-A, x11     # Clear output
    JAL   x0, poll

Red/green signal sum

Reads a signal from both wires each tick and outputs their sum. Useful
when two separate parts of a factory each report a count on different
wire colours and you want a combined total.

loop:
    RSIGR x10, iron-plate       # Read iron-plate from red wire
    RSIGG x11, iron-plate       # Read iron-plate from green wire
    ADD   x12, x10, x11         # x12 = red + green total
    WSIG  o0, iron-plate, x12   # Output the combined count
    WAIT  1
    JAL   x0, loop

Red/green sum with threshold gate

Same idea, but halts and fires a signal once the combined total exceeds 500.

loop:
    RSIGR x10, iron-plate       # Read iron-plate from red wire
    RSIGG x11, iron-plate       # Read iron-plate from green wire
    ADD   x12, x10, x11         # x12 = red + green total
    SLTI  x6,  x12, 500         # x6 = 1 if total < 500
    BNE   x6,  x0,  loop        # Keep polling until threshold met
    ADDI  x11, x0,  1
    WSIG  o0,  signal-A, x11    # Emit signal-A = 1
    HLT