1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
//! HDL -> redstone: the `hdl` feature's bridge surface.
//!
//! Thin wrapper over `nucleation-hdl` (crates/nucleation-hdl), the Rust port
//! of the verified `redstone-eda/hdl` Python pipeline: a combinational BLIF
//! (yosys `synth -lut 4; write_blif` output, or hand-written) compiles to a
//! placed, probed, lever-driven dual-rail PLA build.
//!
//! Same one-way rule as mc-tick and routing: `nucleation-hdl` never sees
//! nucleation; this module converts its cell map into a [`crate::UniversalSchematic`].
//! Structured results cross as JSON strings (PORTING.md rule 9).
//!
//! Bindings are regenerated (`tools/gen-bindings.sh`); the module compiles
//! under `--features bridge,hdl` (bake additionally wants `mc-tick`).
/// Author the compiled cells into a fresh schematic.
fn to_schematic(
build: &nucleation_hdl::Build,
name: &str,
) -> Result<crate::UniversalSchematic, String> {
let mut schem = crate::UniversalSchematic::new(name.to_string());
for (&(x, y, z), block) in &build.cells {
schem.set_block_from_string(x, y, z, block)?;
}
Ok(schem)
}
/// Settle the build in the tick engine (levers at rest) and write every
/// settled state back — the compiled circuit saved "at rest".
#[cfg(feature = "mc-tick")]
fn bake_build(build: &mut nucleation_hdl::Build) -> Result<usize, String> {
let sim = nucleation_hdl::verify::simulate(build, 4000)?;
Ok(nucleation_hdl::verify::bake(build, &sim))
}
#[cfg(not(feature = "mc-tick"))]
fn bake_build(_build: &mut nucleation_hdl::Build) -> Result<usize, String> {
Err("bake=true needs a simulator: rebuild with the `mc-tick` feature".to_string())
}
/// The compiled design's typed-cell contract, parsed into the shared
/// [`crate::io_contract::CellContract`] type. The compiler derives it
/// (vector-port grouping, lever/probe port mapping, estimated delay table);
/// this parse is also the schema guarantee — the hand-rolled JSON in
/// `nucleation-hdl` must deserialize as a real `CellContract` or compilation
/// of the contract fails loudly here.
pub(crate) fn cell_contract(
compiled: &nucleation_hdl::Compiled,
) -> Result<crate::io_contract::CellContract, String> {
crate::io_contract::CellContract::from_json(&compiled.cell_contract_json())
.map_err(|e| format!("compiler emitted an invalid CellContract: {e}"))
}
#[diplomat::bridge]
pub mod ffi {
use super::super::schematic::ffi::Schematic;
use super::super::shared::ffi::NucleationError;
use diplomat_runtime::DiplomatWrite;
use std::fmt::Write;
/// Namespacing opaque for the HDL compiler entry points (static methods,
/// like `Routing`).
#[diplomat::opaque]
pub struct Hdl;
impl Hdl {
/// Compile combinational BLIF text into a redstone PLA schematic.
///
/// `blif` is yosys `write_blif` output (`.latch`/`.subckt` are
/// rejected — combinational only). One floor lever per `.inputs` net
/// drives the build; every signal has a dust probe. `bake=true`
/// settles the build in the tick engine first and saves it at rest
/// (needs the `mc-tick` feature, else errors).
///
/// Probe/lever coordinates and stats come from `compile_blif_report`.
pub fn compile_blif(
blif: &DiplomatStr,
name: &DiplomatStr,
bake: bool,
) -> Result<Box<Schematic>, NucleationError> {
let (blif, name) = decode(blif, name)?;
let compiled = nucleation_hdl::compile_blif(blif, name).map_err(|e| {
crate::bridge::set_last_error_detail(e.to_string());
NucleationError::InvalidArgument
})?;
let mut build = compiled.build;
if bake {
super::bake_build(&mut build).map_err(|e| {
crate::bridge::set_last_error_detail(e);
NucleationError::Simulation
})?;
}
let schem = super::to_schematic(&build, name).map_err(|e| {
crate::bridge::set_last_error_detail(e);
NucleationError::InvalidArgument
})?;
Ok(Box::new(Schematic(schem)))
}
/// Compile `blif` and write the JSON report: stats (`prims`,
/// `levels`, `peephole_removed`, `blocks`, `bounds`), `inputs` (=
/// lever drive order), `outputs` (each `{name, probe}` or `{name,
/// const}`), `levers` (`{signal, pos}`), and `probes`
/// (signal -> `[x, y, z]` dust cell, in the schematic's own
/// coordinates).
pub fn compile_blif_report(
blif: &DiplomatStr,
name: &DiplomatStr,
out: &mut DiplomatWrite,
) -> Result<(), NucleationError> {
let (blif, name) = decode(blif, name)?;
let compiled = nucleation_hdl::compile_blif(blif, name).map_err(|e| {
crate::bridge::set_last_error_detail(e.to_string());
NucleationError::InvalidArgument
})?;
let _ = write!(out, "{}", compiled.report_json());
Ok(())
}
/// Compile `blif` and write its typed-cell contract as JSON — the
/// `CellContract` file format (name, `io` with typed ports/buses,
/// `physical` sidecar). Vector ports (`a[0..3]` or `a0..a3`) group
/// into word buses (LSB = index 0); single bits are boolean. Input
/// port positions are the drive levers, output positions the dust
/// probes, in the same schematic coordinates as `compile_blif`.
///
/// The `physical.delays_rt` table is ESTIMATED from levelization
/// depth (2 redstone ticks per level), not measured; `paste_safe`
/// is false until proven. Pair with `compile_blif` for the
/// schematic: schematic + this contract = an executable typed cell.
pub fn compile_blif_contract(
blif: &DiplomatStr,
name: &DiplomatStr,
out: &mut DiplomatWrite,
) -> Result<(), NucleationError> {
let (blif, name) = decode(blif, name)?;
let compiled = nucleation_hdl::compile_blif(blif, name).map_err(|e| {
crate::bridge::set_last_error_detail(e.to_string());
NucleationError::InvalidArgument
})?;
let contract = super::cell_contract(&compiled).map_err(|e| {
crate::bridge::set_last_error_detail(e);
NucleationError::InvalidArgument
})?;
let json = contract.to_json().map_err(|e| {
crate::bridge::set_last_error_detail(e);
NucleationError::InvalidArgument
})?;
let _ = write!(out, "{json}");
Ok(())
}
}
fn decode<'a>(
blif: &'a DiplomatStr,
name: &'a DiplomatStr,
) -> Result<(&'a str, &'a str), NucleationError> {
let blif = core::str::from_utf8(blif).map_err(|_| NucleationError::InvalidArgument)?;
let name = core::str::from_utf8(name).map_err(|_| NucleationError::InvalidArgument)?;
Ok((blif, name))
}
}
#[cfg(test)]
mod tests {
/// The bridge path end-to-end: a 2-input BLIF compiles to a schematic
/// whose cells match the compiler's build, and the report carries the
/// lever/probe metadata a driver needs.
#[test]
fn a_blif_compiles_to_a_schematic_with_report() {
let blif = ".model xor2\n.inputs a b\n.outputs y\n.names a b y\n01 1\n10 1\n.end\n";
let compiled = nucleation_hdl::compile_blif(blif, "xor2").unwrap();
let schem = super::to_schematic(&compiled.build, "xor2").unwrap();
let placed = schem
.iter_blocks()
.filter(|(_, bs)| bs.name.as_str() != "minecraft:air")
.count();
assert_eq!(placed, compiled.build.cells.len());
let report = compiled.report_json();
assert!(report.contains("\"levers\""), "{report}");
assert!(report.contains("\"probe\""), "{report}");
}
const CMP4: &str = include_str!("../../crates/nucleation-hdl/tests/data/cmp4.blif");
/// The schema guarantee: the compiler's hand-rolled contract JSON
/// deserializes as a real `CellContract`, typed and canonical.
#[test]
fn compiled_contract_parses_as_a_typed_cell_contract() {
use crate::io_contract::{CellContract, IoType};
let compiled = nucleation_hdl::compile_blif(CMP4, "cmp4").unwrap();
let contract = super::cell_contract(&compiled).unwrap();
assert_eq!(contract.name, "cmp4");
let a = contract.io.get_input("a").expect("grouped bus port a");
assert_eq!(a.io_type, IoType::UnsignedInt { bits: 4 });
assert_eq!(a.positions.len(), 4, "one lever per bit");
let eq = contract.io.outputs.get("eq").expect("boolean output eq");
assert_eq!(eq.io_type, IoType::Boolean);
assert!(!contract.physical.paste_safe, "unknown -> false");
assert!(
contract.physical.delay_rt("a", "lt").unwrap_or(0) >= 2,
"estimated arrival from levelization depth"
);
assert!(
!contract.physical.keepouts.is_empty(),
"build bounds guard the cell body"
);
// canonical serde round trip
let back = CellContract::from_json(&contract.to_json().unwrap()).unwrap();
assert_eq!(back, contract);
}
const COUNTER4: &str = include_str!("../../crates/nucleation-hdl/tests/data/counter4.blif");
/// A SEQUENTIAL design's contract also parses as a typed CellContract:
/// the clock is a real Boolean input port (position = the spine lever)
/// and the `sequential` sidecar rides along without breaking the shared
/// schema (serde ignores unknown keys).
#[test]
fn sequential_contract_parses_with_a_clock_port() {
use crate::io_contract::IoType;
let compiled = nucleation_hdl::compile_blif(COUNTER4, "counter4").unwrap();
assert_eq!(compiled.latches.len(), 4);
let contract = super::cell_contract(&compiled).unwrap();
let clk = contract.io.get_input("clk").expect("clock input port");
assert_eq!(clk.io_type, IoType::Boolean);
assert_eq!(
clk.positions[0],
compiled.clock.as_ref().unwrap().lever,
"clock port position is the spine lever"
);
let q = contract.io.outputs.get("q").expect("state word output");
assert_eq!(q.io_type, IoType::UnsignedInt { bits: 4 });
assert!(compiled.cell_contract_json().contains("\"sequential\":"));
}
/// The typed-cell promise end-to-end: compile cmp4, wrap schematic +
/// contract in a `BackendCircuitExecutor` over the mc-tick oracle, and
/// verify 64 sampled cases purely through port NAMES and word VALUES —
/// no raw coordinates anywhere.
#[cfg(all(feature = "simulation", feature = "mc-tick"))]
#[test]
fn cmp4_executes_as_a_typed_cell_by_port_name() {
use crate::io_contract::Value;
use crate::simulation::typed_executor::BackendCircuitExecutor;
let compiled = nucleation_hdl::compile_blif(CMP4, "cmp4").unwrap();
let contract = super::cell_contract(&compiled).unwrap();
// Cells deploy BAKED (settled states saved); the mc-tick backend
// loads with InWorld settle and trusts them. An unbaked build sits
// inert until the first lever flip.
let mut build = compiled.build;
super::bake_build(&mut build).unwrap();
let schem = super::to_schematic(&build, "cmp4").unwrap();
let extra = nucleation_hdl::verify::extra_states();
let extra: Vec<&str> = extra.iter().map(String::as_str).collect();
let mut cell = BackendCircuitExecutor::for_cell(schem, &contract, &extra).unwrap();
cell.settle(4000);
// 8x8 sample of the 16x16 space; each operand set exercises every bit.
const A: [u32; 8] = [0, 3, 5, 6, 9, 10, 12, 15];
const B: [u32; 8] = [0, 2, 7, 8, 11, 13, 14, 15];
for &a in &A {
for &b in &B {
cell.set_input("a", &Value::U32(a)).unwrap();
cell.set_input("b", &Value::U32(b)).unwrap();
cell.settle(400);
assert_eq!(
cell.read_output("eq").unwrap(),
Value::Bool(a == b),
"eq({a},{b})"
);
assert_eq!(
cell.read_output("lt").unwrap(),
Value::Bool(a < b),
"lt({a},{b})"
);
assert_eq!(
cell.read_output("gt").unwrap(),
Value::Bool(a > b),
"gt({a},{b})"
);
}
}
}
}