Skip to main content

Crate chipi

Crate chipi 

Source
Expand description

§chipi

Generate instruction decoders and disassemblers from .chipi files.

Write your CPU instruction encoding in a simple DSL, and chipi generates the Rust decoder and formatting code for you.

§Usage

Add to Cargo.toml:

[build-dependencies]
chipi = "0.1.1"

Create build.rs:

use std::env;
use std::path::PathBuf;

fn main() {
    let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
    chipi::generate("cpu.chipi", out_dir.join("cpu.rs").to_str().unwrap())
        .expect("failed to generate decoder");
    println!("cargo:rerun-if-changed=cpu.chipi");
}

Use the generated decoder:

mod cpu {
    include!(concat!(env!("OUT_DIR"), "/cpu.rs"));
}

match cpu::CpuInstruction::decode(raw) {
    Some(instr) => println!("{}", instr),
    None => println!("invalid instruction"),
}

§Example .chipi file

decoder Cpu {
    width = 32
    bit_order = msb0
}

type simm16 = i32 { sign_extend(16) }
type simm24 = i32 { sign_extend(24), shift_left(2) }

bx   [0:5]=010010 li:simm24[6:29] aa:bool[30] lk:bool[31]
     | "b{lk ? l}{aa ? a} {li:#x}"

addi [0:5]=001110 rd:u8[6:10] ra:u8[11:15] simm:simm16[16:31]
     | ra == 0: "li {rd}, {simm}"
     | "addi {rd}, {ra}, {simm}"

§Syntax

§Decoder block

decoder Name {
    width = 32        # 8, 16, or 32 bits
    bit_order = msb0  # msb0 or lsb0
    max_units = 4     # optional: safety guard (validates bit ranges)
}
§Variable-Length Instructions

chipi automatically generates variable-length decoders when you use bit positions beyond width-1. Simply reference subsequent units in your bit ranges:

decoder Dsp {
    width = 16
    bit_order = msb0
    max_units = 2     # Optional safety check: ensures bits don't exceed 32 (width * max_units)
}

nop    [0:15]=0000000000000000        # 1 unit (16 bits)
lri    [0:10]=00000010000 rd:u5[11:15] imm:u16[16:31]  # 2 units (32 bits)

Generated decode signature for width = u16:

  • Single-unit: pub fn decode(opcode: u16) -> Option<Self>
  • Variable-length: pub fn decode(units: &[u16]) -> Option<(Self, usize)>

The variable-length decoder returns both the instruction and the number of units consumed.

§Instructions

Each instruction is one line with a name, fixed bit patterns, and fields:

add [0:5]=011111 rd:u8[6:10] ra:u8[11:15]

Fixed bits use [range]=pattern. Fields use name:type[range].

§Overlapping Patterns

chipi supports overlapping instruction patterns where one pattern is a subset of another. More specific patterns (with more fixed bits) are checked first:

# Generic instruction - matches 0x1X (any value in bits 4-7)
load  [0:3]=0001 reg:u4[4:7]
      | "load r{reg}"

# Specific instruction - matches only 0x1F
load_max [0:3]=0001 [4:7]=1111
         | "load rmax"

The decoder will check load_max first (all bits fixed), then fall back to load (bits 4-7 are wildcards). This works across all units in variable-length decoders.

§Types

Builtin types:

  • bool (converts bit to true/false)
  • u1 to u7 (maps to u8)
  • u8, u16, u32
  • i8, i16, i32

Custom types:

type simm = i32 { sign_extend(16) }
type reg = u8 as Register

Available transformations:

  • sign_extend(n) - sign extend from n bits
  • zero_extend(n) - zero extend from n bits
  • shift_left(n) - shift left by n bits

Display format hints (controls how the field is printed in format strings):

  • display(signed_hex) - signed hex: 0x1A, -0x1A, 0
  • display(hex) - unsigned hex: 0x1A, 0

§Imports

Import Rust types to wrap extracted values:

import crate::cpu::Register
import std::num::Wrapping

§Format lines

Format lines follow an instruction and define its disassembly output:

bx [0:5]=010010 li:simm24[6:29] aa:bool[30] lk:bool[31]
   | "b{lk ? l}{aa ? a} {li:#x}"

Features:

  • {field} - insert field value, with optional format spec: {field:#x}
  • {field ? text} - emit text if nonzero, {field ? yes : no} for else
  • {a + b * 4} - inline arithmetic (+, -, *, /, %)
  • {-field} - unary negation
  • {map_name(arg)} - call a map lookup
  • {rotate_right(val, amt)} - builtin functions
  • Guards: | ra == 0: "li {rd}, {simm}" - conditional format selection
  • Guard arithmetic: | sh == 32 - mb : "srwi ..." - arithmetic in guard operands

§Maps

Lookup tables for use in format strings:

map spr_name(spr) {
    1 => "xer"
    8 => "lr"
    9 => "ctr"
    _ => "???"
}

§Formatting trait

chipi generates a {Name}Format trait with one method per instruction. Default implementations come from format lines. Override selectively:

struct MyFormat;
impl cpu::CpuFormat for MyFormat {
    fn fmt_bx(li: i32, aa: bool, lk: bool,
              f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "BRANCH {:#x}", li)
    }
}

println!("{}", instr.display::<MyFormat>());

§API

// Parse and generate from file
chipi::generate("cpu.chipi", "out.rs")?;

// Generate from string
let code = chipi::generate_from_str(source, "cpu.chipi")?;

// Step by step
let def = chipi::parse("cpu.chipi")?;
chipi::emit(&def, "out.rs")?;

Modules§

codegen
Rust code generation from validated definitions and decision trees.
error
Error types and reporting for parsing and validation.
format_parser
Character-level parser for format string internals.
parser
DSL parsing for instruction definitions.
tree
Decision tree construction for optimal instruction dispatch.
types
Core type definitions for the intermediate representation.
validate
Semantic validation for instruction definitions.

Functions§

emit
Validate a parsed definition and write generated Rust code to a file.
generate
Full pipeline: parse a .chipi file and generate a Rust decoder.
generate_from_str
Parse, validate, and generate code from source text. Returns the generated Rust code as a String.
parse
Parse a .chipi file from a file path and return the decoder definition.
parse_str
Parse source text directly without reading from a file.