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)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.