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.2.0"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].
§Wildcard Bits
Use ? in bit patterns for bits that can be any value:
# Match when bits [15:8] are 0x8c, bits [7:0] can be anything
clr15 [15:0]=10001100????????
| "CLR15"
# Mix wildcards with specific bits
nop [7:4]=0000 [3:0]=????
| "nop"Wildcard bits are excluded from the matching mask, so instructions match regardless of the values in those positions. This is useful for reserved or architecturally undefined bits.
§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)u1tou7(maps to u8)u8,u16,u32i8,i16,i32
Custom types:
type simm = i32 { sign_extend(16) }
type reg = u8 as RegisterAvailable transformations:
sign_extend(n)- sign extend from n bitszero_extend(n)- zero extend from n bitsshift_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,0display(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}- emittextif 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
.chipifile 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
.chipifile from a file path and return the decoder definition. - parse_
str - Parse source text directly without reading from a file.