Skip to content

t27c silicon: wire bitstream_reuse.t27 -- blocked by the no-growth rule for hand-written code #8295

Description

@gHashTag

Follow-up to #8290 / PR #8291, epic #8095.

specs/verified/bitstream_reuse.t27 (PR #8291) decides when t27c silicon may load a bitstream it built before. That skips about 27 s of yosys, nextpnr and fasm2frames per run on die A, and later lets an agent with no toolchain load a sealed bitstream.

Wiring it needs I/O that t27 cannot express yet:

  • read and hash files: the gen-verilog output, the --top files, the chipdb, the nextpnr binary;
  • run yosys -V and git rev-parse;
  • read and write ~/.cache/t27/bitstreams/<key>/.

The glue is 32 lines of Rust (service.rs +28, main.rs +4), written and built locally. Since the owner's rule of 2026-10-09, hand-written code in any language but t27 may not grow (own-language), and no label lifts that. So the glue is not landed.

Ways forward (the owner's call):

  1. In the same PR, port at least 32 lines of existing hand Rust from bootstrap/src to .t27, so the net change is 0 or less.
  2. t27 gains file, process and hash I/O, tracked on epic: self-host t27c -- rewrite the compiler from Rust into t27 #5980. The glue is then generated.

Acceptance criteria

  • a second t27c silicon run of an unchanged spec prints a bitstream REUSED stage instead of yosys and nextpnr
  • the own-language check passes on the PR that wires it

Boundary

specs/verified/bitstream_reuse.t27

User Scenarios

  • Given a verified spec that declares bitstream reuse via specs/verified/bitstream_reuse.t27, When t27c silicon runs a second time with unchanged inputs (gen-verilog output, --top files, chipdb, nextpnr binary, yosys version, git revision), Then the command prints a bitstream REUSED stage and skips yosys, nextpnr, and fasm2frames.

  • Given the own-language rule (no net growth of hand-written Rust), When the wiring for bitstream reuse is submitted in a PR, Then the PR must either port at least 32 lines of existing bootstrap/src Rust to .t27 or depend on t27 gaining file/process/hash I/O (tracked in epic: self-host t27c -- rewrite the compiler from Rust into t27 #5980) so the glue is generated.

Requirements

FR-001: The second t27c silicon run of an unchanged spec MUST print a bitstream REUSED stage instead of invoking yosys and nextpnr.

FR-002: The PR that wires specs/verified/bitstream_reuse.t27 MUST pass the own-language check (net change ≤ 0 lines of hand-written Rust).

FR-003: Bitstream reuse key derivation MUST incorporate hashes of gen-verilog output, --top files, chipdb, nextpnr binary, yosys -V output, and git rev-parse output.

FR-004: The implementation MUST read and write ~/.cache/t27/bitstreams/<key>/ for cached bitstreams.

Activity

  1. gHashTag commented on Oct 9, 2026

    @gHashTag
    OwnerAuthor

    The 32-line glue, built and compiled locally against master 1ab051f. Apply with git apply once the way forward is chosen:

    diff --git a/bootstrap/src/main.rs b/bootstrap/src/main.rs
    index 1bf18d68d..0ba14acaf 100644
    --- a/bootstrap/src/main.rs
    +++ b/bootstrap/src/main.rs
    @@ -117,6 +117,10 @@ mod silicon_queue;
     #[path = "../gen/rust/verified/bench_agent.rs"]
     #[allow(dead_code, unused_parens, unused_mut, unused_assignments, unused_variables, non_snake_case, non_upper_case_globals, clippy::all)]
     mod bench_agent;
    +// specs/verified/bitstream_reuse.t27 (#8095): when `t27c silicon` loads a bitstream it built before.
    +#[path = "../gen/rust/verified/bitstream_reuse.rs"]
    +#[allow(dead_code, unused_parens, unused_mut, unused_assignments, unused_variables, non_snake_case, non_upper_case_globals, clippy::all)]
    +mod bitstream_reuse;
     mod phi_f64_literals;
     mod weight_bram;
     mod bitnet_pipeline;
    diff --git a/bootstrap/src/service.rs b/bootstrap/src/service.rs
    index bc5887859..beada1e3f 100644
    --- a/bootstrap/src/service.rs
    +++ b/bootstrap/src/service.rs
    @@ -4246,6 +4246,25 @@ pub fn run_silicon(
         let mut derived_chain: Option<u32> = None;
     
         let mut seen_sites: Vec<u32> = Vec::new();
    +    // #8095: load a bitstream built before from the same inputs by the same tools (specs/verified/bitstream_reuse.t27).
    +    use crate::bitstream_reuse as br;
    +    let bit_path = tmp.join(format!("{stem}.bit"));
    +    let tools = [chipdb.clone(), pnr.clone(), xr.join("utils/fasm2frames.py"), PathBuf::from(run(Command::new("which").arg("xc7frames2bit")).1.trim())];
    +    let xdc_in = tops.last().map(|t| Path::new(t).with_extension("xdc")).filter(|p| p.exists());
    +    let mut key_in = format!("{}|{}|{pnr_seed:?}|{}|{}", run(Command::new("yosys").arg("-V")).1.trim(), run(Command::new("git").arg("-C").arg(&db).args(["rev-parse", "HEAD"])).1.trim(), br::BIT_RECIPE, env!("CARGO_PKG_VERSION"));
    +    for f in sources.iter().map(PathBuf::from).chain(xdc_in).chain(tools) { key_in += &format!("|{}", crate::sha256_hex(&std::fs::read(&f).unwrap_or_default())); }
    +    let cache = PathBuf::from(std::env::var_os("HOME").unwrap_or_default()).join(".cache/t27/bitstreams").join(crate::sha256_hex(key_in.as_bytes()));
    +    let (cached_bit, cached_chain) = (std::fs::read(cache.join("design.bit")).ok(), std::fs::read_to_string(cache.join("chain")).ok());
    +    let intact = cached_bit.as_deref().map(crate::sha256_hex) == std::fs::read_to_string(cache.join("bit.sha256")).ok();
    +    let decision = br::bit_decision(std::env::var_os("T27_SILICON_REBUILD").is_some(), cached_bit.is_some(), cached_chain.is_some(), intact);
    +    'build: {
    +    if decision == br::BIT_REUSE {
    +        std::fs::write(&bit_path, cached_bit.as_deref().unwrap_or_default())?;
    +        derived_chain = cached_chain.and_then(|c| c.trim().parse().ok());
    +        let note = format!("same inputs, same tools: {} (T27_SILICON_REBUILD=1 rebuilds)", cache.display());
    +        stages.push(Stage { name: "bitstream REUSED", secs: 0.0, code: Some(0), artefact: file_len(&bit_path), note });
    +        break 'build;
    +    }
         for attempt in 0..6u32 {
             let t = Instant::now();
             let chparam = match chain_override {
    @@ -4609,7 +4628,6 @@ pub fn run_silicon(
         });
     
         // ---- bitstream, ONLY from non-empty frames ----
    -    let bit_path = tmp.join(format!("{stem}.bit"));
         let frames_ok = file_len(&frames_path).map(|(_, n)| n > 0).unwrap_or(false);
         let t = Instant::now();
         let c = if frames_ok {
    @@ -4634,12 +4652,21 @@ pub fn run_silicon(
                 "SKIPPED: empty frames would still yield a 9.7 MB .bit (T169)".into()
             },
         });
    +    }
     
         let build_ok = print_table(&stages);
         if !build_ok {
             println!("FAIL -- the build did not complete. Nothing was loaded.");
             std::process::exit(1);
         }
    +    if br::may_store(decision, build_ok, stages.iter().any(|s| s.name == "BSCAN chain == site" && s.ok())) {
    +        let bit = std::fs::read(&bit_path)?;
    +        std::fs::create_dir_all(&cache)?;
    +        std::fs::write(cache.join("design.bit"), &bit)?;
    +        std::fs::write(cache.join("chain"), derived_chain.map(|c| c.to_string()).unwrap_or_default())?;
    +        std::fs::write(cache.join("bit.sha256"), crate::sha256_hex(&bit))?; // last: a torn entry reads CORRUPT
    +        println!("  bitstream stored for reuse: {}", cache.display());
    +    }
     
         if skip_hardware {
             println!("PASS (build only) -- rerun without --skip-hardware to load and read.");
  2. added 4 commits that reference this issue on Oct 9, 2026
    9fe8ab0
    39e70f3
    0d57372
    47ef800
  3. added 2 commits that reference this issue on Oct 9, 2026
    2ba048e
    e4338f4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions