{"id":48244929,"url":"https://github.com/ioncodes/chipi","last_synced_at":"2026-04-04T20:30:46.586Z","repository":{"id":337687127,"uuid":"1154763391","full_name":"ioncodes/chipi","owner":"ioncodes","description":"A declarative instruction decoder and disassembler generator using a custom DSL","archived":false,"fork":false,"pushed_at":"2026-03-28T16:21:42.000Z","size":324,"stargazers_count":22,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-03-28T17:12:12.854Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ioncodes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-10T18:47:52.000Z","updated_at":"2026-03-28T17:08:34.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ioncodes/chipi","commit_stats":null,"previous_names":["ioncodes/chipi"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ioncodes/chipi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ioncodes%2Fchipi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ioncodes%2Fchipi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ioncodes%2Fchipi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ioncodes%2Fchipi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ioncodes","download_url":"https://codeload.github.com/ioncodes/chipi/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ioncodes%2Fchipi/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31413267,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T20:09:54.854Z","status":"ssl_error","status_checked_at":"2026-04-04T20:09:44.350Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-04T20:30:45.923Z","updated_at":"2026-04-04T20:30:46.580Z","avatar_url":"https://github.com/ioncodes.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# chipi\n\nA declarative instruction decoder generator. Define your CPU's instruction encoding in a portable `.chipi` DSL file, and chipi generates decoders, disassemblers, and emulator dispatch code.\n\n`.chipi` files are language-agnostic: they describe bit patterns, field extractions, and display formats with zero language-specific information. All language-specific configuration (type mappings, output paths, dispatch strategies) lives in a project-level `chipi.toml` or is passed as CLI arguments.\n\n## Backends\n\n| Backend | Lang   | Output                                   | Usage             |\n| ------- | ------ | ---------------------------------------- | ----------------- |\n| `rust`  | Rust   | Decoder enum + `decode()` + `Display`    | `build.rs` or CLI |\n| `cpp`   | C++    | Single-header decoder with `std::format` | CLI or CMake      |\n| `ida`   | Python | IDA Pro 9.x processor module             | CLI               |\n| `binja` | Python | Binary Ninja Architecture plugin         | CLI               |\n\n## Examples\n\n| Project                                                        | Description                                    |\n| -------------------------------------------------------------- | ---------------------------------------------- |\n| [chipi-gekko](https://github.com/ioncodes/chipi-gekko)         | GameCube CPU \u0026 DSP disassembler (Rust)         |\n| [chipi-gekko-cpp](https://github.com/ioncodes/chipi-gekko-cpp) | GameCube CPU disassembler (C++)                |\n| [gc-dsp-ida](https://github.com/ioncodes/gc-dsp-ida)           | GameCube DSP processor plugin for IDA Pro 9.x  |\n| [gc-dsp-binja](https://github.com/ioncodes/gc-dsp-binja)       | GameCube DSP processor plugin for Binary Ninja |\n| [chipi-spec](https://github.com/ioncodes/chipi-spec)           | Reusable `.chipi` specs                        |\n| [chipi-vscode](https://github.com/ioncodes/chipi-vscode)       | VS Code syntax highlighting for `.chipi` files |\n\nNote: The IDA and Binary Ninja generators backends are highly experimental and do *not* replace fully fledged processor modules.\n\n## Architecture\n\n```\n.chipi files --\u003e Parser --\u003e Decoder IR --\u003e Backend --\u003e generated source\n                                             ^\n               chipi.toml + CLI args --------+\n```\n\nThree crates:\n\n| Crate             | Purpose                                                  |\n| ----------------- | -------------------------------------------------------- |\n| **`chipi-core`**  | Parser, IR, validation, code generation backends         |\n| **`chipi-cli`**   | Standalone CLI (`chipi gen`, `chipi lut`, `chipi stubs`) |\n| **`chipi-build`** | `build.rs` helper for Rust projects                      |\n\n## CLI\n\n```bash\n# Install\ncargo install chipi-cli\n\n# Generate decoder/disassembler (Rust, C++, IDA, or Binary Ninja)\nchipi gen --config chipi.toml\nchipi gen --lang cpp --config chipi.toml\nchipi gen --lang ida --config chipi.toml\n\n# Override dispatch for a sub-decoder\nchipi gen --config chipi.toml --dispatch-for GcDspExt=jump_table\n\n# Dry run (print to stdout)\nchipi gen --dry-run src/dsp.chipi\n\n# Dump parsed IR for debugging\nchipi gen --dump-ir src/dsp.chipi\n\n# Generate emulator dispatch LUT from config\nchipi lut --config chipi.toml\n\n# Generate handler stubs\nchipi stubs --output src/handlers.rs src/cpu.chipi\n```\n\n### `chipi.toml`\n\nA single config file can define multiple targets across backends.\nPaths support `$VAR` environment variable expansion (e.g. `$OUT_DIR/lut.rs`).\nRelative paths are resolved from the TOML file's directory.\n\n```toml\n# Rust decoder\n[[gen]]\ninput = \"specs/dsp.chipi\"\nlang = \"rust\"\noutput = \"$OUT_DIR/dsp.rs\"\n\n[gen.dispatch_overrides]\nGcDspExt = \"jump_table\"\n\n[gen.type_map]\nreg5 = \"crate::dsp::DspReg\"\n\n# C++ decoder (single-header)\n[[gen]]\ninput = \"specs/gekko.chipi\"\nlang = \"cpp\"\noutput = \"generated/gekko.hpp\"\n\n[gen.lang_options]\nincludes = [\"types.hpp\"]\n\n[gen.type_map]\ngpr = \"Gpr\"\nfpr = \"Fpr\"\n\n# IDA Pro processor module\n[[gen]]\ninput = \"specs/dsp.chipi\"\nlang = \"ida\"\noutput = \"dsp_proc.py\"\n\n[gen.lang_options]\nprocessor_name = \"gcdsp\"\nprocessor_long_name = \"GameCube DSP\"\nprocessor_id = 0x8002\naddress_size = 16\nbytes_per_unit = 2\nregister_names = [\"ar0\", \"ar1\", \"ar2\", \"ar3\", \"CS\", \"DS\"]\nsegment_registers = [\"CS\", \"DS\"]\n\n[gen.lang_options.flow]\ncalls = [\"callcc\"]\nreturns = [\"retcc\"]\nstops = [\"halt\"]\n\n# Binary Ninja architecture plugin\n[[gen]]\ninput = \"specs/dsp.chipi\"\nlang = \"binja\"\noutput = \"dsp_arch.py\"\n\n[gen.lang_options]\narchitecture_name = \"gcdsp\"\naddress_size = 2\ndefault_int_size = 2\nendianness = \"BigEndian\"\nregister_names = [\"ar0\", \"ar1\", \"ar2\", \"ar3\"]\n\n# Emulator dispatch LUT (Rust only)\n[[lut]]\ninput = \"specs/gekko.chipi\"\noutput = \"$OUT_DIR/gekko_lut.rs\"\nhandler_mod = \"crate::cpu::interpreter\"\nctx_type = \"crate::Cpu\"\ninstr_type = \"crate::cpu::Instruction\"\nlut_mod = \"crate::cpu::lut\"\ninstr_type_output = \"$OUT_DIR/gekko_instr.rs\"\n\n[lut.groups]\nbranch = [\"bx\", \"bcx\", \"bclrx\", \"bcctrx\"]\nalu = [\"addi\", \"addis\", \"ori\", \"oris\"]\n```\n\n## Rust `build.rs` usage\n\nFor Rust projects, use `chipi-build` in `build.rs`:\n\n```toml\n[build-dependencies]\nchipi-build = \"0.8.0\" # or whatever is latest\n```\n\n### Config-driven\n\n```rust\n// build.rs: processes all [[gen]] and [[lut]] targets from chipi.toml\nfn main() {\n    chipi_build::run_config(\"chipi.toml\").expect(\"chipi codegen failed\");\n}\n```\n\n### Programmatic\n\n```rust\n// build.rs: decoder/disassembler generation\nfn main() {\n    chipi_build::generate(\"src/dsp/gcdsp.chipi\")\n        .type_map(\"reg5\", \"crate::dsp::DspReg\")\n        .dispatch_default(chipi_build::Dispatch::FnPtrLut)\n        .dispatch_for(\"GcDspExt\", chipi_build::Dispatch::JumpTable)\n        .output(\"src/dsp/generated/gcdsp.rs\")\n        .run()\n        .expect(\"chipi codegen failed\");\n}\n```\n\n```rust\n// build.rs: emulator dispatch LUT generation\nfn main() {\n    let out_dir = std::env::var(\"OUT_DIR\").unwrap();\n    chipi_build::lut(\"cpu.chipi\")\n        .handler_mod(\"crate::cpu::interpreter\")\n        .ctx_type(\"crate::Cpu\")\n        .instr_type(\"crate::cpu::Instruction\")\n        .lut_mod(\"crate::cpu::lut\")\n        .group(\"alu\", [\"addi\", \"addis\", \"ori\", \"oris\"])\n        .group(\"branch\", [\"bx\", \"bcx\"])\n        .output(format!(\"{out_dir}/cpu_lut.rs\"))\n        .instr_type_output(format!(\"{out_dir}/cpu_instr.rs\"))\n        .run()\n        .expect(\"chipi lut failed\");\n}\n```\n\n`chipi-build` automatically emits `cargo:rerun-if-changed` for all input files and their includes.\n\n### Using generated code\n\n```rust\nmod dsp {\n    include!(concat!(env!(\"OUT_DIR\"), \"/gcdsp.rs\"));\n}\n\nmatch dsp::GcDspInstruction::decode(\u0026data[offset..]) {\n    Some((instr, bytes)) =\u003e {\n        println!(\"{}\", instr);\n        offset += bytes;\n    }\n    None =\u003e println!(\"invalid instruction\"),\n}\n```\n\n## C++ backend\n\nThe C++ backend generates a single header with `enum class Opcode`, a tagged `Instruction` struct, `decode()`, and `format()` (using `std::format`).\n\nType aliases with `display(hex)` or `display(signed_hex)` automatically generate wrapper types (`Hex`, `SignedHex`) with `std::formatter` specializations. Custom types like registers are provided by the user via `type_map` and a user-written header with `std::formatter` specializations.\n\n```toml\n[gen.type_map]\ngpr = \"Gpr\"    # user provides Gpr type with std::formatter\u003cGpr\u003e\nfpr = \"Fpr\"\n\n[gen.lang_options]\nincludes = [\"types.hpp\"]   # user's type definitions\n```\n\n```cpp\n// types.hpp (provided by user)\nstruct Gpr {\n    uint8_t value;\n    Gpr() = default;\n    constexpr Gpr(uint8_t v) : value(v) {}\n    bool operator==(const Gpr\u0026) const = default;\n    bool operator==(int other) const { return value == other; }\n};\n\ntemplate \u003c\u003e struct std::formatter\u003cGpr\u003e : std::formatter\u003cstd::string\u003e {\n    auto format(Gpr g, auto\u0026 ctx) const {\n        return std::formatter\u003cstd::string\u003e::format(std::format(\"r{}\", g.value), ctx);\n    }\n};\n```\n\n## IDA backend\n\nGenerates a self-contained IDA Pro 9.x processor module (Python). Requires `lang_options` with processor metadata, register names, and optional flow analysis hints. See the `chipi.toml` example above.\n\n## Binary Ninja backend\n\nGenerates a Binary Ninja Architecture plugin (Python). Implements `get_instruction_info`, `get_instruction_text`, and a stub `get_instruction_low_level_il`.\n\n## DSL\n\n### Decoder block\n\n```chipi\ndecoder Ppc {\n    width = 32        # bits per instruction unit\n    bit_order = msb0  # msb0 or lsb0\n    endian = big      # big or little\n    max_units = 1     # optional safety guard\n}\n```\n\n### Instructions\n\n```chipi\naddi [0:5]=001110 rd:u8[6:10] ra:u8[11:15] simm:simm16[16:31]\n     | ra == 0: \"li {rd}, {simm}\"\n     | \"addi {rd}, {ra}, {simm}\"\n```\n\n- Fixed bits: `[range]=pattern` (use `?` for wildcards)\n- Fields: `name:type[range]`\n- Format lines: `| [guard:] \"template\"`\n\n### Custom types\n\n```chipi\ntype simm16 = i32 { sign_extend(16), display(signed_hex) }\ntype addr = u32 { shift_left(2), zero_extend(32) }\ntype gpr = u8\n```\n\n**Builtin types:** `bool`, `u1`-`u7`, `u8`, `u16`, `u32`, `i8`, `i16`, `i32`\n\n**Transforms:** `sign_extend(n)`, `zero_extend(n)`, `shift_left(n)`\n\n**Display hints:** `display(hex)`, `display(signed_hex)`\n\n### Format strings\n\n- Field references: `{field}`, `{field:#x}`\n- Ternary: `{flag ? yes : no}`\n- Arithmetic: `{a + b * 4}`, `{-field}`\n- Map calls: `{spr_name(spr)}`\n- Builtins: `{rotate_right(val, amt)}`, `{rotate_left(val, amt)}`\n- Sub-decoder fragments: `{ext.mnemonic}`\n- Guards: `| ra == 0: \"li {rd}, {simm}\"` (supports `==`, `!=`, `\u003c`, `\u003c=`, `\u003e`, `\u003e=`)\n- Escapes: `\\{`, `\\}`, `\\?`, `\\:`\n\n### Maps\n\n```chipi\nmap spr_name(spr) {\n    1 =\u003e \"xer\"\n    8 =\u003e \"lr\"\n    9 =\u003e \"ctr\"\n    _ =\u003e \"???\"\n}\n```\n\n### Includes\n\n```chipi\ninclude \"dsp_ext.chipi\"\n```\n\nResolved relative to the including file. Circular includes are detected and rejected.\n\n### Variable-length instructions\n\nBit positions beyond `width - 1` reference subsequent units:\n\n```chipi\ndecoder GcDsp {\n    width = 16\n    bit_order = msb0\n    endian = big\n    max_units = 2\n}\n\n# 2-unit instruction: bits [16:31] are in the second unit\nlri [0:10]=00000010000 rd:u5[11:15] imm:u16[16:31]\n    | \"lri r{rd}, #0x{imm:04x}\"\n```\n\n### Sub-decoders\n\nSub-decoders decode a bit-field within a parent instruction using a separate dispatch table:\n\n```chipi\nsubdecoder GcDspExt {\n    width = 8\n    bit_order = msb0\n}\n\next_dr  [0:5]=000001 r:u8[6:7]\n        | .mnemonic = \"'DR\"\n        | .operands = \" : $ar{r}\"\n\next_nop [0:5]=000000 [6:7]=??\n        | .mnemonic = \"\"\n        | .operands = \"\"\n```\n\nReference from a parent instruction:\n\n```chipi\naddr [0:3]=0100 [4]=0 ss:u8[5:6] d:u8[7] ext:GcDspExt[8:15]\n     | \"ADDR{ext.mnemonic} $ac{d}, ${ax2_name(ss)}{ext.operands}\"\n```\n\n### Formatting trait (Rust)\n\nThe generated trait allows per-instruction display overrides:\n\n```rust\nstruct MyFormat;\nimpl ppc::PpcFormat for MyFormat {\n    fn fmt_addi(rd: u8, ra: u8, simm: i32, f: \u0026mut std::fmt::Formatter) -\u003e std::fmt::Result {\n        write!(f, \"ADDI r{}, r{}, {}\", rd, ra, simm)\n    }\n}\n\nprintln!(\"{}\", instr.display::\u003cMyFormat\u003e());\n```\n\n## Dispatch strategies\n\n| Strategy     | Rust                               |\n| ------------ | ---------------------------------- |\n| `fn_ptr_lut` | `static [Option\u003cfn\u003e; N]` (default) |\n| `jump_table` | `#[inline(always)]` fn + `match`   |\n\n## Handler stubs\n\nStubs are generated via the CLI only. They are one-shot scaffolding meant to be edited by hand:\n\n```bash\nchipi stubs --output src/cpu/interpreter.rs cpu.chipi\n```\n\nThis produces:\n\n```rust\npub fn addi(_ctx: \u0026mut Ctx, _opcode: u32) { todo!(\"addi\") }\npub fn lwz(_ctx: \u0026mut Ctx, _opcode: u32) { todo!(\"lwz\") }\n// ... one stub per instruction\n```\n\n## Grouped handlers with const generics\n\n`.group()` (in config or builder) folds multiple instructions into one handler via `const OP: u32`:\n\n```rust\n// Generated LUT entry\ncrate::cpu::interpreter::alu::\u003c{ OP_ADDI }\u003e\n\n// Expected handler signature\npub fn alu\u003cconst OP: u32\u003e(ctx: \u0026mut crate::Cpu, instr: crate::cpu::Instruction) {\n    match OP {\n        OP_ADDI  =\u003e { /* ... */ },\n        OP_ADDIS =\u003e { /* ... */ },\n        _ =\u003e unreachable!(),\n    }\n}\n```\n\n## Instruction type generation\n\nWhen `instr_type` and `instr_type_output` are set (in config or builder), chipi generates a newtype with field accessors:\n\n```rust\npub struct Instruction(pub u32);\nimpl Instruction {\n    #[inline] pub fn rd(\u0026self) -\u003e u8 { ((self.0 \u003e\u003e 21) \u0026 0x1f) as u8 }\n    #[inline] pub fn ra(\u0026self) -\u003e u8 { ((self.0 \u003e\u003e 16) \u0026 0x1f) as u8 }\n    #[inline] pub fn simm(\u0026self) -\u003e i32 { (((((self.0 \u0026 0xffff) as i32) \u003c\u003c 16) \u003e\u003e 16)) as i32 }\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fioncodes%2Fchipi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fioncodes%2Fchipi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fioncodes%2Fchipi/lists"}