From 4f081dc3b589e478ed2f28be81f52772ff5c0f0d Mon Sep 17 00:00:00 2001 From: rollrat Date: Mon, 25 May 2026 00:41:57 +0900 Subject: [PATCH 1/5] Place sequential prefixes with local placer --- src/sequential/core.rs | 81 ++++- .../local_placer/component_tests.rs | 12 +- .../place_and_route/local_placer/mod.rs | 40 ++- .../local_placer/sequential.rs | 315 ++++++------------ .../place_and_route/local_placer/tests.rs | 23 ++ test/d-latch.nbt | Bin 552 -> 551 bytes 6 files changed, 243 insertions(+), 228 deletions(-) diff --git a/src/sequential/core.rs b/src/sequential/core.rs index 048a63a..10a2480 100644 --- a/src/sequential/core.rs +++ b/src/sequential/core.rs @@ -1,6 +1,7 @@ -use std::collections::HashSet; +use std::collections::{HashSet, VecDeque}; -use crate::graph::{Graph, GraphNodeId, GraphNodeKind}; +use crate::graph::logic::LogicGraph; +use crate::graph::{Graph, GraphNode, GraphNodeId, GraphNodeKind}; use crate::logic::LogicType; #[derive(Debug, Clone, PartialEq, Eq)] @@ -74,6 +75,68 @@ pub fn recognize_rs_latch_core(graph: &Graph) -> Option { }) } +// Extract the acyclic logic that drives the set/reset inputs of an RS latch core. +// Feedback SCC nodes are excluded so the returned graph can be handled by the +// existing combinational placer. +pub fn rs_latch_prefix_graph(graph: &Graph, core: &RsLatchCore) -> Option { + let feedback_scc = core.feedback_scc.iter().copied().collect::>(); + let output_roots = [("s", core.set_input), ("r", core.reset_input)]; + let mut prefix_nodes = HashSet::new(); + let mut queue = output_roots + .iter() + .map(|(_, node_id)| *node_id) + .collect::>(); + + while let Some(node_id) = queue.pop_front() { + if feedback_scc.contains(&node_id) { + return None; + } + if !prefix_nodes.insert(node_id) { + continue; + } + + let node = graph.find_node_by_id(node_id)?; + for input in &node.inputs { + queue.push_back(*input); + } + } + + let mut nodes = graph + .nodes + .iter() + .filter(|node| prefix_nodes.contains(&node.id)) + .cloned() + .map(|mut node| { + node.inputs.retain(|input| prefix_nodes.contains(input)); + node.outputs.clear(); + node + }) + .collect::>(); + + let mut next_node_id = graph.nodes.iter().map(|node| node.id).max().unwrap_or(0) + 1; + for (output_name, source_node_id) in output_roots { + nodes.push(GraphNode { + id: next_node_id, + kind: GraphNodeKind::Output(output_name.to_owned()), + inputs: vec![source_node_id], + ..Default::default() + }); + next_node_id += 1; + } + nodes.sort_by_key(|node| node.id); + + let mut graph = Graph { + nodes, + ..Default::default() + }; + graph.build_outputs(); + graph.build_producers(); + graph.build_consumers(); + graph.verify().ok()?; + + Some(LogicGraph { graph }) +} + fn find_output(graph: &Graph, output_name: &str) -> Option { graph.nodes.iter().find_map(|node| { matches!(&node.kind, GraphNodeKind::Output(name) if name == output_name).then_some(node.id) @@ -139,4 +202,18 @@ mod tests { assert_eq!(core.nq_output, 10); assert_eq!(core.feedback_scc, vec![5, 6, 7, 8]); } + + #[test] + fn rs_latch_prefix_graph_extracts_d_latch_input_gating() -> eyre::Result<()> { + let primitive = SequentialPrimitive::d_latch(); + let core = recognize_rs_latch_core(&primitive.inner_graph).unwrap(); + let prefix = rs_latch_prefix_graph(&primitive.inner_graph, &core).unwrap(); + let table = prefix.truth_table()?; + + assert_eq!(table.input_names, vec!["d".to_owned(), "en".to_owned()]); + assert_eq!(table.output_tables["s"], vec![false, false, false, true]); + assert_eq!(table.output_tables["r"], vec![false, false, true, false]); + assert!(!prefix.has_cycle()); + Ok(()) + } } diff --git a/src/transform/place_and_route/local_placer/component_tests.rs b/src/transform/place_and_route/local_placer/component_tests.rs index 1e469d0..15a6bd2 100644 --- a/src/transform/place_and_route/local_placer/component_tests.rs +++ b/src/transform/place_and_route/local_placer/component_tests.rs @@ -7,9 +7,9 @@ use crate::sequential::{SequentialPrimitive, SequentialType}; use crate::transform::place_and_route::estimate::world_compact_cost; use crate::transform::place_and_route::local_placer::{ generate_d_latch_gate_routes, generate_rs_latch_not_pairs, route_rs_latch_branches, - select_rs_latch_not_pairs, InputPlacementStrategy, LocalPlacer, LocalPlacerConfig, - LocalPlacerDebug, NotRouteStrategy, PlacementSamplingPolicy, SamplingPolicy, - TorchPlacementStrategy, D_LATCH_INPUT_GATING_NODES, + rs_latch_input_node_ids, select_rs_latch_not_pairs, InputPlacementStrategy, LocalPlacer, + LocalPlacerConfig, LocalPlacerDebug, NotRouteStrategy, PlacementSamplingPolicy, SamplingPolicy, + TorchPlacementStrategy, }; use crate::transform::place_and_route::utils::{ contains_truth_table_with_world3ds, equivalent_logic_with_world3d, @@ -391,7 +391,7 @@ fn test_generate_component_d_latch() -> eyre::Result<()> { eprintln!("candidate {checked} failed: {error}; q={q:?} nq={nq:?}"); let q_support = q.walk(world[q].direction).unwrap(); let nq_support = nq.walk(world[nq].direction).unwrap(); - let nodes = D_LATCH_INPUT_GATING_NODES; + let nodes = rs_latch_input_node_ids(node.id); let _ = trace_d_latch_behavior( world, d, @@ -399,10 +399,6 @@ fn test_generate_component_d_latch() -> eyre::Result<()> { &[ ("d", d), ("en", en), - ("not_d", state[&nodes.not_d]), - ("not_en", state[&nodes.not_en]), - ("set_or", state[&nodes.set_or]), - ("reset_or", state[&nodes.reset_or]), ("set", state[&nodes.set]), ("reset", state[&nodes.reset]), ("q_support", q_support), diff --git a/src/transform/place_and_route/local_placer/mod.rs b/src/transform/place_and_route/local_placer/mod.rs index 8ecad4f..8f16bad 100644 --- a/src/transform/place_and_route/local_placer/mod.rs +++ b/src/transform/place_and_route/local_placer/mod.rs @@ -44,7 +44,7 @@ use sequential::{ #[cfg(test)] use sequential::{ generate_rs_latch_not_pairs, place_sequential_macro, route_rs_latch_branches, - route_sequential_inputs, select_rs_latch_not_pairs, D_LATCH_INPUT_GATING_NODES, + route_sequential_inputs, rs_latch_input_node_ids, select_rs_latch_not_pairs, }; pub struct LocalPlacer { @@ -156,12 +156,20 @@ impl LocalPlacer { &self, dim: DimSize, finish_step: Option, - mut debug: Option<&mut LocalPlacerDebug>, + debug: Option<&mut LocalPlacerDebug>, ) -> PlacerQueue { - tracing::info!("generate starts"); - let mut queue = PlacerQueue::new(); queue.push((World3D::new(dim), Default::default())); + self.generate_queue_from(queue, finish_step, debug) + } + + fn generate_queue_from( + &self, + mut queue: PlacerQueue, + finish_step: Option, + mut debug: Option<&mut LocalPlacerDebug>, + ) -> PlacerQueue { + tracing::info!("generate starts"); let mut step = 0; while step < self.visit_orders.len() && Some(step) != finish_step { @@ -252,16 +260,24 @@ impl LocalPlacer { ) -> PlacementGeneration { let mut route_debug = None; let items = match node.kind { - GraphNodeKind::Input(_) => input_node_kind() - .into_iter() - .flat_map(|kind| generate_inputs(&self.config, &world, kind)) - .map(|(world, position)| { + GraphNodeKind::Input(_) => { + if let Some(position) = state.node_position(node.id) { let mut state = state.clone(); - state.set_node_position(node.id, position); state.set_signal_footprint(node.id, [position]); - (world, state) - }) - .collect(), + vec![(world, state)] + } else { + input_node_kind() + .into_iter() + .flat_map(|kind| generate_inputs(&self.config, &world, kind)) + .map(|(world, position)| { + let mut state = state.clone(); + state.set_node_position(node.id, position); + state.set_signal_footprint(node.id, [position]); + (world, state) + }) + .collect() + } + } GraphNodeKind::Output(_) => vec![(world.clone(), state.clone())], GraphNodeKind::Logic(logic) => match logic.logic_type { LogicType::Not => not_node_kind() diff --git a/src/transform/place_and_route/local_placer/sequential.rs b/src/transform/place_and_route/local_placer/sequential.rs index 8f7ab64..c3ed723 100644 --- a/src/transform/place_and_route/local_placer/sequential.rs +++ b/src/transform/place_and_route/local_placer/sequential.rs @@ -1,4 +1,5 @@ use super::*; +use crate::sequential::core::rs_latch_prefix_graph; use crate::world::World; pub(super) fn supports_sequential_primitive(sequential: &SequentialPrimitive) -> bool { @@ -19,26 +20,22 @@ pub(super) struct RsLatchGatePlacement { } #[derive(Debug, Clone, Copy)] -pub(super) struct DLatchInputGatingNodes { - pub(super) not_d: GraphNodeId, - pub(super) not_en: GraphNodeId, - pub(super) set_or: GraphNodeId, - pub(super) reset_or: GraphNodeId, +pub(super) struct RsLatchInputNodeIds { pub(super) set: GraphNodeId, pub(super) reset: GraphNodeId, } -// D latch is placed as an acyclic input-gating prefix feeding the cyclic RS latch core. -// These synthetic IDs let the prefix store intermediate endpoints in PlacementState without -// colliding with graph node IDs owned by the caller. -pub(super) const D_LATCH_INPUT_GATING_NODES: DLatchInputGatingNodes = DLatchInputGatingNodes { - not_d: usize::MAX - 2, - not_en: usize::MAX - 3, - set_or: usize::MAX - 4, - reset_or: usize::MAX - 5, - set: usize::MAX - 1, - reset: usize::MAX, -}; +const SEQUENTIAL_SYNTHETIC_NODE_ID_STRIDE: usize = 4; + +// Prefix placement runs in its own graph, then publishes only the set/reset +// endpoints back into the outer placement state for the RS latch core router. +pub(super) fn rs_latch_input_node_ids(node_id: GraphNodeId) -> RsLatchInputNodeIds { + let base = usize::MAX - node_id * SEQUENTIAL_SYNTHETIC_NODE_ID_STRIDE; + RsLatchInputNodeIds { + set: base - 1, + reset: base - 2, + } +} pub(super) fn generate_d_latch_gate_routes( config: &LocalPlacerConfig, @@ -53,6 +50,35 @@ pub(super) fn generate_d_latch_gate_routes( let Some(enable_input) = sequential_input(node, sequential, state, "en") else { return Vec::new(); }; + let Some(data_position) = state.node_position(data_input.node_id) else { + return Vec::new(); + }; + let Some(enable_position) = state.node_position(enable_input.node_id) else { + return Vec::new(); + }; + let Some(core) = sequential.rs_latch_core() else { + return Vec::new(); + }; + let Some(prefix_graph) = rs_latch_prefix_graph(&sequential.inner_graph, &core) else { + return Vec::new(); + }; + let Ok(prefix_graph) = prefix_graph.prepare_place() else { + return Vec::new(); + }; + let Some(prefix_data_input) = input_node_id_by_name(&prefix_graph, "d") else { + return Vec::new(); + }; + let Some(prefix_enable_input) = input_node_id_by_name(&prefix_graph, "en") else { + return Vec::new(); + }; + let Some(prefix_set_source) = output_source_node_id(&prefix_graph, "s") else { + return Vec::new(); + }; + let Some(prefix_reset_source) = output_source_node_id(&prefix_graph, "r") else { + return Vec::new(); + }; + + let rs_input_nodes = rs_latch_input_node_ids(node.id); let rs_sequential = SequentialPrimitive::new( SequentialType::RsLatch, vec!["s".to_owned(), "r".to_owned()], @@ -61,15 +87,28 @@ pub(super) fn generate_d_latch_gate_routes( let rs_node = GraphNode { id: node.id, kind: GraphNodeKind::Sequential(rs_sequential.clone()), - inputs: vec![ - D_LATCH_INPUT_GATING_NODES.set, - D_LATCH_INPUT_GATING_NODES.reset, - ], + inputs: vec![rs_input_nodes.set, rs_input_nodes.reset], ..Default::default() }; - let queue = place_d_latch_input_gating(config, world, state, data_input, enable_input); - let queue = retain_valid_d_latch_input_gating(config, queue, data_input, enable_input); + let queue = place_rs_latch_prefix( + config, + world, + prefix_graph, + &[ + (prefix_data_input, data_input.node_id), + (prefix_enable_input, enable_input.node_id), + ], + state, + ); + let queue = retain_valid_d_latch_prefix( + config, + queue, + data_position, + enable_position, + prefix_set_source, + prefix_reset_source, + ); let mut routed = Vec::new(); let limit = take_sampling_limit(config.step_sampling_policy); @@ -81,7 +120,16 @@ pub(super) fn generate_d_latch_gate_routes( max_route_step: 8, ..*config }; - for (world, state) in queue { + for (world, prefix_state) in queue { + let Some(set_position) = prefix_state.node_position(prefix_set_source) else { + continue; + }; + let Some(reset_position) = prefix_state.node_position(prefix_reset_source) else { + continue; + }; + let mut state = state.clone(); + state.set_node_position(rs_input_nodes.set, set_position); + state.set_node_position(rs_input_nodes.reset, reset_position); for (world, state) in generate_rs_latch_gate_routes(&rs_config, &rs_node, &rs_sequential, &world, &state) { @@ -117,62 +165,56 @@ pub(super) fn generate_d_latch_gate_routes( sample_d_latch_step(config, 6, routed) } -fn place_d_latch_input_gating( +fn place_rs_latch_prefix( config: &LocalPlacerConfig, world: &World3D, - state: &PlacementState, - data_input: SequentialInput, - enable_input: SequentialInput, + prefix_graph: LogicGraph, + input_mappings: &[(GraphNodeId, GraphNodeId)], + outer_state: &PlacementState, ) -> PlacerQueue { - let nodes = D_LATCH_INPUT_GATING_NODES; - let queue = vec![(world.clone(), state.clone())]; - let queue = generate_not_step(config, 0, queue, data_input.node_id, nodes.not_d); - let queue = generate_not_step(config, 1, queue, enable_input.node_id, nodes.not_en); - - // set = !(!d | !en) = d & en - // reset = !( d | !en) = !d & en - let queue = generate_two_or_step( - config, - 2, - queue, - (nodes.not_d, nodes.not_en, nodes.set_or), - (data_input.node_id, nodes.not_en, nodes.reset_or), - ); - let queue = generate_not_step(config, 3, queue, nodes.set_or, nodes.set); - generate_not_step(config, 4, queue, nodes.reset_or, nodes.reset) + let mut prefix_state = PlacementState::default(); + for &(prefix_input, outer_input) in input_mappings { + let Some(position) = outer_state.node_position(outer_input) else { + return Vec::new(); + }; + prefix_state.set_node_position(prefix_input, position); + } + + let Ok(prefix_placer) = LocalPlacer::new(prefix_graph, *config) else { + return Vec::new(); + }; + prefix_placer.generate_queue_from(vec![(world.clone(), prefix_state)], None, None) } -fn retain_valid_d_latch_input_gating( +fn retain_valid_d_latch_prefix( config: &LocalPlacerConfig, queue: PlacerQueue, - data_input: SequentialInput, - enable_input: SequentialInput, + data: Position, + enable: Position, + set_node_id: GraphNodeId, + reset_node_id: GraphNodeId, ) -> PlacerQueue { let queue = queue .into_iter() .filter(|(world, state)| { - d_latch_input_gating_behaves(world, data_input.node_id, enable_input.node_id, state) + d_latch_prefix_behaves(world, data, enable, set_node_id, reset_node_id, state) }) .collect_vec(); SamplingPolicy::Random(64).sample_with_seed(queue, config.sampling_seed(7, 6)) } -fn d_latch_input_gating_behaves( +fn d_latch_prefix_behaves( world: &World3D, - data_node_id: GraphNodeId, - enable_node_id: GraphNodeId, + data: Position, + enable: Position, + set_node_id: GraphNodeId, + reset_node_id: GraphNodeId, state: &PlacementState, ) -> bool { - let Some(data) = state.node_position(data_node_id) else { - return false; - }; - let Some(enable) = state.node_position(enable_node_id) else { + let Some(set) = state.node_position(set_node_id) else { return false; }; - let Some(set) = state.node_position(D_LATCH_INPUT_GATING_NODES.set) else { - return false; - }; - let Some(reset) = state.node_position(D_LATCH_INPUT_GATING_NODES.reset) else { + let Some(reset) = state.node_position(reset_node_id) else { return false; }; @@ -332,128 +374,17 @@ fn sequential_input( }) } -fn generate_not_step( - config: &LocalPlacerConfig, - step: usize, - queue: PlacerQueue, - input_node_id: GraphNodeId, - output_node_id: GraphNodeId, -) -> PlacerQueue { - let candidates: PlacerQueue = queue - .into_iter() - .flat_map(|(world, state)| { - let source = state[&input_node_id]; - let route_config = if world[source].kind.is_switch() { - Cow::Owned(LocalPlacerConfig { - not_route_strategy: NotRouteStrategy::DirectOnly, - ..*config - }) - } else { - Cow::Borrowed(config) - }; - not_node_kind() - .into_iter() - .flat_map(move |kind| { - let state = state.clone(); - generate_place_and_routes(&route_config, &world, source, kind) - .into_iter() - .map(move |(world, position)| { - let mut state = state.clone(); - state.set_node_position(output_node_id, position); - (world, state) - }) - }) - .collect_vec() - }) - .collect(); - sample_d_latch_step(config, step, candidates) -} - -fn generate_two_or_step( - config: &LocalPlacerConfig, - step: usize, - queue: PlacerQueue, - first: (GraphNodeId, GraphNodeId, GraphNodeId), - second: (GraphNodeId, GraphNodeId, GraphNodeId), -) -> PlacerQueue { - let candidates = queue - .into_iter() - .flat_map(|(base_world, state)| { - let first_routes = - generate_or_routes(config, &base_world, state[&first.0], state[&first.1]).routes; - let second_routes = - generate_or_routes(config, &base_world, state[&second.0], state[&second.1]).routes; - first_routes - .into_iter() - .cartesian_product(second_routes) - .filter_map( - |((first_world, first_positions), (second_world, second_positions))| { - let world = - merge_independent_route_worlds(&base_world, first_world, second_world)?; - let first_position = first_positions.last().copied()?; - let second_position = second_positions.last().copied()?; - Some({ - let mut state = state.clone(); - state.set_node_position(first.2, first_position); - state.set_node_position(second.2, second_position); - (world, state) - }) - }, - ) - .collect_vec() - }) - .collect(); - sample_d_latch_step(config, step, candidates) -} - -fn merge_independent_route_worlds( - base: &World3D, - first: World3D, - second: World3D, -) -> Option { - let mut merged = first.clone(); - let mut first_redstone = HashSet::new(); - let mut second_redstone = HashSet::new(); - for (x, y, z) in iproduct!(0..base.size.0, 0..base.size.1, 0..base.size.2) { - let position = Position(x, y, z); - if first[position] != base[position] && first[position].kind.is_redstone() { - first_redstone.insert(position); - } - if second[position] != base[position] && second[position].kind.is_redstone() { - second_redstone.insert(position); - } - if second[position] == base[position] { - continue; - } - if first[position] != base[position] { - return None; - } - merged[position] = second[position]; - if merged[position].kind.is_redstone() { - merged.update_redstone_states(position); - } - } - if redstone_networks_touch(&merged, &first_redstone, &second_redstone) { - return None; - } - Some(merged) +fn input_node_id_by_name(graph: &LogicGraph, input_name: &str) -> Option { + graph.nodes.iter().find_map(|node| { + matches!(&node.kind, GraphNodeKind::Input(name) if name == input_name).then_some(node.id) + }) } -fn redstone_networks_touch( - world: &World3D, - first: &HashSet, - second: &HashSet, -) -> bool { - first.iter().any(|&position| { - PlacedNode::new(position, world[position]) - .propagation_bound(Some(world)) - .into_iter() - .any(|bound| second.contains(&bound.position())) - }) || second.iter().any(|&position| { - PlacedNode::new(position, world[position]) - .propagation_bound(Some(world)) - .into_iter() - .any(|bound| first.contains(&bound.position())) +fn output_source_node_id(graph: &LogicGraph, output_name: &str) -> Option { + graph.nodes.iter().find_map(|node| { + (matches!(&node.kind, GraphNodeKind::Output(name) if name == output_name) + && node.inputs.len() == 1) + .then(|| node.inputs[0]) }) } @@ -550,15 +481,11 @@ pub(super) fn select_rs_latch_not_pairs( let input_space_penalty = (reset_distance.abs_diff(set_distance) * 10) + usize::from(reset_distance < 2) * 100 + usize::from(set_distance < 2) * 100; - let internal_interference_penalty = - d_latch_internal_interference_penalty(state, placed) * 2_000; - reset_distance + set_distance + placed.q_torch.manhattan_distance(&placed.nq_torch) + input_block_penalty + input_space_penalty - + internal_interference_penalty }); match config.step_sampling_policy { @@ -569,30 +496,6 @@ pub(super) fn select_rs_latch_not_pairs( } } -fn d_latch_internal_interference_penalty( - state: &PlacementState, - placed: &RsLatchGatePlacement, -) -> usize { - let nodes = D_LATCH_INPUT_GATING_NODES; - let protected_nodes = [nodes.not_d, nodes.not_en, nodes.set_or, nodes.reset_or]; - let latch_positions = [ - placed.q_torch, - placed.q_cobble, - placed.nq_torch, - placed.nq_cobble, - ]; - - protected_nodes - .into_iter() - .filter_map(|node_id| state.node_position(node_id)) - .flat_map(|protected| { - latch_positions - .into_iter() - .map(move |latch| 4usize.saturating_sub(protected.manhattan_distance(&latch))) - }) - .sum() -} - fn lies_between(start: Position, end: Position, position: Position) -> bool { start.manhattan_distance(&position) + position.manhattan_distance(&end) == start.manhattan_distance(&end) diff --git a/src/transform/place_and_route/local_placer/tests.rs b/src/transform/place_and_route/local_placer/tests.rs index 60ec880..e2d6279 100644 --- a/src/transform/place_and_route/local_placer/tests.rs +++ b/src/transform/place_and_route/local_placer/tests.rs @@ -100,6 +100,29 @@ fn local_placer_accepts_unbuffered_full_adder_after_buffer_insertion() -> eyre:: Ok(()) } +#[test] +fn local_placer_seeded_queue_reuses_preplaced_input_nodes() -> eyre::Result<()> { + let graph = LogicGraph::from_stmt("a", "out")?; + let placer = LocalPlacer::new(graph.clone(), config(1))?; + let input_id = graph + .nodes + .iter() + .find(|node| matches!(&node.kind, GraphNodeKind::Input(name) if name == "a")) + .unwrap() + .id; + let input_position = Position(2, 2, 1); + let mut world = empty_world(); + world[input_position] = switch(Direction::East); + let state = [(input_id, input_position)].into_iter().collect(); + + let generated = placer.generate_queue_from(vec![(world, state)], None, None); + + assert_eq!(generated.len(), 1); + assert_eq!(generated[0].1.node_position(input_id), Some(input_position)); + assert!(generated[0].0[input_position].kind.is_switch()); + Ok(()) +} + #[test] fn placement_cost_penalizes_spread_future_join_inputs() -> eyre::Result<()> { let graph = LogicGraph::from_stmt("a|b", "c")?.prepare_place()?; diff --git a/test/d-latch.nbt b/test/d-latch.nbt index dc83f8e5726afbbc68650afe63b56d84098143ec..fabdce9aa9bbe21d0587f774a000d43e86fbc373 100644 GIT binary patch literal 551 zcmV+?0@(c@iwFP!00002|FxD&Zrd;rhDYKmDG>AU6mUdz=VSEQ4fX zAg2!%OY(ywl86%n1Wf7UKR;(i1Q!4rxNTmM0YE<%ekebFXpnbpw|unylPdQK#Gcsj_sV0?qMhy%($O>{ zyoYLAx9A-9*Szlv_;vk+@aw7B;L_A<_usm1gSU64@mE0@-~T?mH@Mm(yT*8t#f|B< zXk3F#(-2ziO_~Keyepd)Uw50?}g~KihOBRQ-8SZSe)onVMYJ%Vu*w zEcT219D(oZwpBhieJEUkSl6vR68%eA^-FGDXO<5$A>LDW5y7&X2U@(GyLIY#lASw1?}CyI3elj1QM zJci?8G?aLd<5_4}WN27$8eYJtgw6q@1>d8543Ej-F~XWx0#CpgL1&erVT3^*Gt;@? zF)AZgD(8~Rr{FOu4XcdW)zn{Pj86EjIZVl8B;Sv|rTdr`_E(WnS26Vk88fS^;xQ(n zPnD5R#pSb(y)YSmnI(_rG1C5$Vg-y!=o2s~^i?UAnTCu(p+8HpkkiM+zDpXGoEJsx zdxR-tzff4KYU+3C^AwjkGiN1coTpK&I`#rH?ky=_8l{N z3krL%oG@!B_f1F6(Rf|_RDt=^+!1`eYj?C6nyvd~UB9EN>!I~mN(kS-oW6JTKtm1n z-=W{r;99a36x(Lg?*0Mp`oZ1$N&cC@9Gb4He8B!d1Kj{_o6ZtUq(4ri{_`>JIOWRq z!{+u<2;1L}A#~Tei18c8*bcPd!sm=&_0!Lz#=7xleMA1?K;hv{oF|&&WXC Date: Mon, 25 May 2026 00:49:41 +0900 Subject: [PATCH 2/5] Encapsulate sequential prefix planning --- .../local_placer/sequential.rs | 205 ++++++++++-------- 1 file changed, 111 insertions(+), 94 deletions(-) diff --git a/src/transform/place_and_route/local_placer/sequential.rs b/src/transform/place_and_route/local_placer/sequential.rs index c3ed723..fa6fca9 100644 --- a/src/transform/place_and_route/local_placer/sequential.rs +++ b/src/transform/place_and_route/local_placer/sequential.rs @@ -37,6 +37,87 @@ pub(super) fn rs_latch_input_node_ids(node_id: GraphNodeId) -> RsLatchInputNodeI } } +struct RsLatchPrefixPlan { + graph: LogicGraph, + input_mappings: Vec<(GraphNodeId, GraphNodeId)>, + input_node_ids_by_port: HashMap, + input_positions_by_port: HashMap, + set_source: GraphNodeId, + reset_source: GraphNodeId, + rs_input_nodes: RsLatchInputNodeIds, +} + +impl RsLatchPrefixPlan { + fn build( + node: &GraphNode, + sequential: &SequentialPrimitive, + state: &PlacementState, + ) -> Option { + let core = sequential.rs_latch_core()?; + let graph = rs_latch_prefix_graph(&sequential.inner_graph, &core) + .and_then(|graph| graph.prepare_place().ok())?; + let set_source = output_source_node_id(&graph, "s")?; + let reset_source = output_source_node_id(&graph, "r")?; + + let mut input_mappings = Vec::new(); + let mut input_node_ids_by_port = HashMap::new(); + let mut input_positions_by_port = HashMap::new(); + for prefix_input in graph.nodes.iter().filter_map(|node| match &node.kind { + GraphNodeKind::Input(name) => Some((node.id, name.as_str())), + _ => None, + }) { + let outer_input = outer_input_node_id(node, sequential, prefix_input.1)?; + let position = state.node_position(outer_input)?; + input_mappings.push((prefix_input.0, outer_input)); + input_node_ids_by_port.insert(prefix_input.1.to_owned(), outer_input); + input_positions_by_port.insert(prefix_input.1.to_owned(), position); + } + + Some(Self { + graph, + input_mappings, + input_node_ids_by_port, + input_positions_by_port, + set_source, + reset_source, + rs_input_nodes: rs_latch_input_node_ids(node.id), + }) + } + + fn rs_node_inputs(&self) -> Vec { + vec![self.rs_input_nodes.set, self.rs_input_nodes.reset] + } + + fn input_node_id(&self, port: &str) -> Option { + self.input_node_ids_by_port.get(port).copied() + } + + fn input_position(&self, port: &str) -> Option { + self.input_positions_by_port.get(port).copied() + } + + fn d_latch_input_node_ids(&self) -> Option<(GraphNodeId, GraphNodeId)> { + Some((self.input_node_id("d")?, self.input_node_id("en")?)) + } + + fn source_positions(&self, state: &PlacementState) -> Option<(Position, Position)> { + Some(( + state.node_position(self.set_source)?, + state.node_position(self.reset_source)?, + )) + } + + fn publish_rs_inputs( + &self, + state: &mut PlacementState, + set_position: Position, + reset_position: Position, + ) { + state.set_node_position(self.rs_input_nodes.set, set_position); + state.set_node_position(self.rs_input_nodes.reset, reset_position); + } +} + pub(super) fn generate_d_latch_gate_routes( config: &LocalPlacerConfig, node: &GraphNode, @@ -44,41 +125,12 @@ pub(super) fn generate_d_latch_gate_routes( world: &World3D, state: &PlacementState, ) -> PlacerQueue { - let Some(data_input) = sequential_input(node, sequential, state, "d") else { - return Vec::new(); - }; - let Some(enable_input) = sequential_input(node, sequential, state, "en") else { - return Vec::new(); - }; - let Some(data_position) = state.node_position(data_input.node_id) else { - return Vec::new(); - }; - let Some(enable_position) = state.node_position(enable_input.node_id) else { - return Vec::new(); - }; - let Some(core) = sequential.rs_latch_core() else { - return Vec::new(); - }; - let Some(prefix_graph) = rs_latch_prefix_graph(&sequential.inner_graph, &core) else { - return Vec::new(); - }; - let Ok(prefix_graph) = prefix_graph.prepare_place() else { - return Vec::new(); - }; - let Some(prefix_data_input) = input_node_id_by_name(&prefix_graph, "d") else { + let Some(prefix_plan) = RsLatchPrefixPlan::build(node, sequential, state) else { return Vec::new(); }; - let Some(prefix_enable_input) = input_node_id_by_name(&prefix_graph, "en") else { + let Some((data_input, enable_input)) = prefix_plan.d_latch_input_node_ids() else { return Vec::new(); }; - let Some(prefix_set_source) = output_source_node_id(&prefix_graph, "s") else { - return Vec::new(); - }; - let Some(prefix_reset_source) = output_source_node_id(&prefix_graph, "r") else { - return Vec::new(); - }; - - let rs_input_nodes = rs_latch_input_node_ids(node.id); let rs_sequential = SequentialPrimitive::new( SequentialType::RsLatch, vec!["s".to_owned(), "r".to_owned()], @@ -87,28 +139,12 @@ pub(super) fn generate_d_latch_gate_routes( let rs_node = GraphNode { id: node.id, kind: GraphNodeKind::Sequential(rs_sequential.clone()), - inputs: vec![rs_input_nodes.set, rs_input_nodes.reset], + inputs: prefix_plan.rs_node_inputs(), ..Default::default() }; - let queue = place_rs_latch_prefix( - config, - world, - prefix_graph, - &[ - (prefix_data_input, data_input.node_id), - (prefix_enable_input, enable_input.node_id), - ], - state, - ); - let queue = retain_valid_d_latch_prefix( - config, - queue, - data_position, - enable_position, - prefix_set_source, - prefix_reset_source, - ); + let queue = place_rs_latch_prefix(config, world, &prefix_plan, state); + let queue = retain_valid_d_latch_prefix(config, queue, &prefix_plan); let mut routed = Vec::new(); let limit = take_sampling_limit(config.step_sampling_policy); @@ -121,15 +157,12 @@ pub(super) fn generate_d_latch_gate_routes( ..*config }; for (world, prefix_state) in queue { - let Some(set_position) = prefix_state.node_position(prefix_set_source) else { - continue; - }; - let Some(reset_position) = prefix_state.node_position(prefix_reset_source) else { + let Some((set_position, reset_position)) = prefix_plan.source_positions(&prefix_state) + else { continue; }; let mut state = state.clone(); - state.set_node_position(rs_input_nodes.set, set_position); - state.set_node_position(rs_input_nodes.reset, reset_position); + prefix_plan.publish_rs_inputs(&mut state, set_position, reset_position); for (world, state) in generate_rs_latch_gate_routes(&rs_config, &rs_node, &rs_sequential, &world, &state) { @@ -140,14 +173,7 @@ pub(super) fn generate_d_latch_gate_routes( continue; }; if std::panic::catch_unwind(|| { - d_latch_candidate_behaves( - &world, - data_input.node_id, - enable_input.node_id, - &state, - q, - nq, - ) + d_latch_candidate_behaves(&world, data_input, enable_input, &state, q, nq) }) .unwrap_or(false) { @@ -168,19 +194,18 @@ pub(super) fn generate_d_latch_gate_routes( fn place_rs_latch_prefix( config: &LocalPlacerConfig, world: &World3D, - prefix_graph: LogicGraph, - input_mappings: &[(GraphNodeId, GraphNodeId)], + prefix_plan: &RsLatchPrefixPlan, outer_state: &PlacementState, ) -> PlacerQueue { let mut prefix_state = PlacementState::default(); - for &(prefix_input, outer_input) in input_mappings { + for &(prefix_input, outer_input) in &prefix_plan.input_mappings { let Some(position) = outer_state.node_position(outer_input) else { return Vec::new(); }; prefix_state.set_node_position(prefix_input, position); } - let Ok(prefix_placer) = LocalPlacer::new(prefix_graph, *config) else { + let Ok(prefix_placer) = LocalPlacer::new(prefix_plan.graph.clone(), *config) else { return Vec::new(); }; prefix_placer.generate_queue_from(vec![(world.clone(), prefix_state)], None, None) @@ -189,15 +214,25 @@ fn place_rs_latch_prefix( fn retain_valid_d_latch_prefix( config: &LocalPlacerConfig, queue: PlacerQueue, - data: Position, - enable: Position, - set_node_id: GraphNodeId, - reset_node_id: GraphNodeId, + prefix_plan: &RsLatchPrefixPlan, ) -> PlacerQueue { + let Some(data) = prefix_plan.input_position("d") else { + return Vec::new(); + }; + let Some(enable) = prefix_plan.input_position("en") else { + return Vec::new(); + }; let queue = queue .into_iter() .filter(|(world, state)| { - d_latch_prefix_behaves(world, data, enable, set_node_id, reset_node_id, state) + d_latch_prefix_behaves( + world, + data, + enable, + prefix_plan.set_source, + prefix_plan.reset_source, + state, + ) }) .collect_vec(); SamplingPolicy::Random(64).sample_with_seed(queue, config.sampling_seed(7, 6)) @@ -351,33 +386,15 @@ fn pad_world_top_for_simulation(world: &World3D, extra_z: usize) -> World3D { padded } -#[derive(Clone, Copy)] -struct SequentialInput { - node_id: GraphNodeId, -} - -fn sequential_input( +fn outer_input_node_id( node: &GraphNode, sequential: &SequentialPrimitive, - state: &PlacementState, port: &str, -) -> Option { +) -> Option { node.inputs .iter() .zip(&sequential.input_ports) - .find_map(|(input_node, input_port)| { - (input_port == port && state.node_position(*input_node).is_some()).then_some( - SequentialInput { - node_id: *input_node, - }, - ) - }) -} - -fn input_node_id_by_name(graph: &LogicGraph, input_name: &str) -> Option { - graph.nodes.iter().find_map(|node| { - matches!(&node.kind, GraphNodeKind::Input(name) if name == input_name).then_some(node.id) - }) + .find_map(|(input_node, input_port)| (input_port == port).then_some(*input_node)) } fn output_source_node_id(graph: &LogicGraph, output_name: &str) -> Option { From 1bdecfabae6a9d7e83284f50c6a9db32d1fc07c2 Mon Sep 17 00:00:00 2001 From: rollrat Date: Mon, 25 May 2026 08:10:08 +0900 Subject: [PATCH 3/5] Refactor sequential local placer modules --- .../place_and_route/local_placer/mod.rs | 1 - .../local_placer/sequential.rs | 885 ------------------ .../local_placer/sequential/d_latch.rs | 286 ++++++ .../local_placer/sequential/macro_routes.rs | 120 +++ .../local_placer/sequential/mod.rs | 49 + .../local_placer/sequential/prefix.rs | 134 +++ .../local_placer/sequential/rs_latch.rs | 364 +++++++ 7 files changed, 953 insertions(+), 886 deletions(-) delete mode 100644 src/transform/place_and_route/local_placer/sequential.rs create mode 100644 src/transform/place_and_route/local_placer/sequential/d_latch.rs create mode 100644 src/transform/place_and_route/local_placer/sequential/macro_routes.rs create mode 100644 src/transform/place_and_route/local_placer/sequential/mod.rs create mode 100644 src/transform/place_and_route/local_placer/sequential/prefix.rs create mode 100644 src/transform/place_and_route/local_placer/sequential/rs_latch.rs diff --git a/src/transform/place_and_route/local_placer/mod.rs b/src/transform/place_and_route/local_placer/mod.rs index 8f16bad..a0121fb 100644 --- a/src/transform/place_and_route/local_placer/mod.rs +++ b/src/transform/place_and_route/local_placer/mod.rs @@ -18,7 +18,6 @@ use crate::transform::place_and_route::estimate::{bounding_box_of_positions, wor use crate::transform::place_and_route::place_bound::PropagateType; use crate::world::block::{Block, BlockKind, Direction}; use crate::world::position::{DimSize, Position}; -use crate::world::simulator::Simulator; use crate::world::World3D; mod config; diff --git a/src/transform/place_and_route/local_placer/sequential.rs b/src/transform/place_and_route/local_placer/sequential.rs deleted file mode 100644 index fa6fca9..0000000 --- a/src/transform/place_and_route/local_placer/sequential.rs +++ /dev/null @@ -1,885 +0,0 @@ -use super::*; -use crate::sequential::core::rs_latch_prefix_graph; -use crate::world::World; - -pub(super) fn supports_sequential_primitive(sequential: &SequentialPrimitive) -> bool { - matches!( - sequential.sequential_type, - SequentialType::RsLatch | SequentialType::DLatch - ) && (sequential.rs_latch_core().is_some() - || !SequentialMacro::candidates(sequential).is_empty()) -} - -#[derive(Debug, Clone)] -pub(super) struct RsLatchGatePlacement { - pub(super) world: World3D, - pub(super) q_torch: Position, - pub(super) q_cobble: Position, - pub(super) nq_torch: Position, - pub(super) nq_cobble: Position, -} - -#[derive(Debug, Clone, Copy)] -pub(super) struct RsLatchInputNodeIds { - pub(super) set: GraphNodeId, - pub(super) reset: GraphNodeId, -} - -const SEQUENTIAL_SYNTHETIC_NODE_ID_STRIDE: usize = 4; - -// Prefix placement runs in its own graph, then publishes only the set/reset -// endpoints back into the outer placement state for the RS latch core router. -pub(super) fn rs_latch_input_node_ids(node_id: GraphNodeId) -> RsLatchInputNodeIds { - let base = usize::MAX - node_id * SEQUENTIAL_SYNTHETIC_NODE_ID_STRIDE; - RsLatchInputNodeIds { - set: base - 1, - reset: base - 2, - } -} - -struct RsLatchPrefixPlan { - graph: LogicGraph, - input_mappings: Vec<(GraphNodeId, GraphNodeId)>, - input_node_ids_by_port: HashMap, - input_positions_by_port: HashMap, - set_source: GraphNodeId, - reset_source: GraphNodeId, - rs_input_nodes: RsLatchInputNodeIds, -} - -impl RsLatchPrefixPlan { - fn build( - node: &GraphNode, - sequential: &SequentialPrimitive, - state: &PlacementState, - ) -> Option { - let core = sequential.rs_latch_core()?; - let graph = rs_latch_prefix_graph(&sequential.inner_graph, &core) - .and_then(|graph| graph.prepare_place().ok())?; - let set_source = output_source_node_id(&graph, "s")?; - let reset_source = output_source_node_id(&graph, "r")?; - - let mut input_mappings = Vec::new(); - let mut input_node_ids_by_port = HashMap::new(); - let mut input_positions_by_port = HashMap::new(); - for prefix_input in graph.nodes.iter().filter_map(|node| match &node.kind { - GraphNodeKind::Input(name) => Some((node.id, name.as_str())), - _ => None, - }) { - let outer_input = outer_input_node_id(node, sequential, prefix_input.1)?; - let position = state.node_position(outer_input)?; - input_mappings.push((prefix_input.0, outer_input)); - input_node_ids_by_port.insert(prefix_input.1.to_owned(), outer_input); - input_positions_by_port.insert(prefix_input.1.to_owned(), position); - } - - Some(Self { - graph, - input_mappings, - input_node_ids_by_port, - input_positions_by_port, - set_source, - reset_source, - rs_input_nodes: rs_latch_input_node_ids(node.id), - }) - } - - fn rs_node_inputs(&self) -> Vec { - vec![self.rs_input_nodes.set, self.rs_input_nodes.reset] - } - - fn input_node_id(&self, port: &str) -> Option { - self.input_node_ids_by_port.get(port).copied() - } - - fn input_position(&self, port: &str) -> Option { - self.input_positions_by_port.get(port).copied() - } - - fn d_latch_input_node_ids(&self) -> Option<(GraphNodeId, GraphNodeId)> { - Some((self.input_node_id("d")?, self.input_node_id("en")?)) - } - - fn source_positions(&self, state: &PlacementState) -> Option<(Position, Position)> { - Some(( - state.node_position(self.set_source)?, - state.node_position(self.reset_source)?, - )) - } - - fn publish_rs_inputs( - &self, - state: &mut PlacementState, - set_position: Position, - reset_position: Position, - ) { - state.set_node_position(self.rs_input_nodes.set, set_position); - state.set_node_position(self.rs_input_nodes.reset, reset_position); - } -} - -pub(super) fn generate_d_latch_gate_routes( - config: &LocalPlacerConfig, - node: &GraphNode, - sequential: &SequentialPrimitive, - world: &World3D, - state: &PlacementState, -) -> PlacerQueue { - let Some(prefix_plan) = RsLatchPrefixPlan::build(node, sequential, state) else { - return Vec::new(); - }; - let Some((data_input, enable_input)) = prefix_plan.d_latch_input_node_ids() else { - return Vec::new(); - }; - let rs_sequential = SequentialPrimitive::new( - SequentialType::RsLatch, - vec!["s".to_owned(), "r".to_owned()], - sequential.output_ports.clone(), - ); - let rs_node = GraphNode { - id: node.id, - kind: GraphNodeKind::Sequential(rs_sequential.clone()), - inputs: prefix_plan.rs_node_inputs(), - ..Default::default() - }; - - let queue = place_rs_latch_prefix(config, world, &prefix_plan, state); - let queue = retain_valid_d_latch_prefix(config, queue, &prefix_plan); - - let mut routed = Vec::new(); - let limit = take_sampling_limit(config.step_sampling_policy); - let rs_config = LocalPlacerConfig { - step_sampling_policy: SamplingPolicy::Random(32), - route_step_sampling_policy: SamplingPolicy::Random(32), - not_route_step_sampling_policy: SamplingPolicy::Random(32), - max_not_route_step: 8, - max_route_step: 8, - ..*config - }; - for (world, prefix_state) in queue { - let Some((set_position, reset_position)) = prefix_plan.source_positions(&prefix_state) - else { - continue; - }; - let mut state = state.clone(); - prefix_plan.publish_rs_inputs(&mut state, set_position, reset_position); - for (world, state) in - generate_rs_latch_gate_routes(&rs_config, &rs_node, &rs_sequential, &world, &state) - { - let Some(q) = state.port_position(node.id, "q") else { - continue; - }; - let Some(nq) = state.port_position(node.id, "nq") else { - continue; - }; - if std::panic::catch_unwind(|| { - d_latch_candidate_behaves(&world, data_input, enable_input, &state, q, nq) - }) - .unwrap_or(false) - { - routed.push((world, state)); - break; - } - } - if limit.is_some_and(|limit| routed.len() >= limit) { - break; - } - if !routed.is_empty() { - break; - } - } - sample_d_latch_step(config, 6, routed) -} - -fn place_rs_latch_prefix( - config: &LocalPlacerConfig, - world: &World3D, - prefix_plan: &RsLatchPrefixPlan, - outer_state: &PlacementState, -) -> PlacerQueue { - let mut prefix_state = PlacementState::default(); - for &(prefix_input, outer_input) in &prefix_plan.input_mappings { - let Some(position) = outer_state.node_position(outer_input) else { - return Vec::new(); - }; - prefix_state.set_node_position(prefix_input, position); - } - - let Ok(prefix_placer) = LocalPlacer::new(prefix_plan.graph.clone(), *config) else { - return Vec::new(); - }; - prefix_placer.generate_queue_from(vec![(world.clone(), prefix_state)], None, None) -} - -fn retain_valid_d_latch_prefix( - config: &LocalPlacerConfig, - queue: PlacerQueue, - prefix_plan: &RsLatchPrefixPlan, -) -> PlacerQueue { - let Some(data) = prefix_plan.input_position("d") else { - return Vec::new(); - }; - let Some(enable) = prefix_plan.input_position("en") else { - return Vec::new(); - }; - let queue = queue - .into_iter() - .filter(|(world, state)| { - d_latch_prefix_behaves( - world, - data, - enable, - prefix_plan.set_source, - prefix_plan.reset_source, - state, - ) - }) - .collect_vec(); - SamplingPolicy::Random(64).sample_with_seed(queue, config.sampling_seed(7, 6)) -} - -fn d_latch_prefix_behaves( - world: &World3D, - data: Position, - enable: Position, - set_node_id: GraphNodeId, - reset_node_id: GraphNodeId, - state: &PlacementState, -) -> bool { - let Some(set) = state.node_position(set_node_id) else { - return false; - }; - let Some(reset) = state.node_position(reset_node_id) else { - return false; - }; - - [(false, false), (false, true), (true, false), (true, true)] - .into_iter() - .all(|(data_value, enable_value)| { - d_latch_input_gate_values(world, data, enable, set, reset, data_value, enable_value) - .is_some_and(|(set_value, reset_value)| { - set_value == (data_value && enable_value) - && reset_value == (!data_value && enable_value) - }) - }) -} - -fn d_latch_input_gate_values( - world: &World3D, - data: Position, - enable: Position, - set: Position, - reset: Position, - data_value: bool, - enable_value: bool, -) -> Option<(bool, bool)> { - let mut world = pad_world_top_for_simulation(world, 2); - world[data].kind = BlockKind::Switch { is_on: data_value }; - world[enable].kind = BlockKind::Switch { - is_on: enable_value, - }; - world.initialize_redstone_states(); - let world = World::from(&world); - let sim = Simulator::from_with_limits_and_trace(&world, 64, 20_000, 0).ok()?; - let set_value = match sim.world()[set].kind { - BlockKind::Torch { is_on } => is_on, - _ => return None, - }; - let reset_value = match sim.world()[reset].kind { - BlockKind::Torch { is_on } => is_on, - _ => return None, - }; - Some((set_value, reset_value)) -} - -fn d_latch_candidate_behaves( - world: &World3D, - data_node_id: GraphNodeId, - enable_node_id: GraphNodeId, - state: &PlacementState, - q: Position, - nq: Position, -) -> bool { - let Some(data) = state.node_position(data_node_id) else { - return false; - }; - let Some(enable) = state.node_position(enable_node_id) else { - return false; - }; - - let mut reset_world = pad_world_top_for_simulation(world, 2); - reset_world[enable].kind = BlockKind::Switch { is_on: true }; - reset_world[data].kind = BlockKind::Switch { is_on: false }; - reset_world.initialize_redstone_states(); - let world = World::from(&reset_world); - let Ok(mut sim) = Simulator::from_with_limits_and_trace(&world, 64, 20_000, 0) else { - return false; - }; - - if torch_is_on_in_world3d(sim.world(), q) || !torch_is_on_in_world3d(sim.world(), nq) { - return false; - } - if sim - .change_state_with_limits(vec![(enable, false)], 64, 20_000) - .is_err() - { - return false; - } - if sim - .change_state_with_limits(vec![(data, true)], 64, 20_000) - .is_err() - { - return false; - } - if torch_is_on_in_world3d(sim.world(), q) || !torch_is_on_in_world3d(sim.world(), nq) { - return false; - } - if sim - .change_state_with_limits(vec![(enable, true)], 64, 20_000) - .is_err() - { - return false; - } - if !torch_is_on_in_world3d(sim.world(), q) || torch_is_on_in_world3d(sim.world(), nq) { - return false; - } - if sim - .change_state_with_limits(vec![(enable, false)], 64, 20_000) - .is_err() - { - return false; - } - if sim - .change_state_with_limits(vec![(data, false)], 64, 20_000) - .is_err() - { - return false; - } - if !torch_is_on_in_world3d(sim.world(), q) || torch_is_on_in_world3d(sim.world(), nq) { - return false; - } - if sim - .change_state_with_limits(vec![(enable, true)], 64, 20_000) - .is_err() - { - return false; - } - - !torch_is_on_in_world3d(sim.world(), q) && torch_is_on_in_world3d(sim.world(), nq) -} - -fn torch_is_on_in_world3d(world: &World3D, position: Position) -> bool { - matches!(world[position].kind, BlockKind::Torch { is_on: true }) -} - -fn pad_world_top_for_simulation(world: &World3D, extra_z: usize) -> World3D { - let mut padded = World3D::new(DimSize(world.size.0, world.size.1, world.size.2 + extra_z)); - for (position, mut block) in world.iter_block() { - if block.kind.is_cobble() { - block.kind = BlockKind::Cobble { - on_count: 0, - on_base_count: 0, - }; - } - padded[position] = block; - } - padded -} - -fn outer_input_node_id( - node: &GraphNode, - sequential: &SequentialPrimitive, - port: &str, -) -> Option { - node.inputs - .iter() - .zip(&sequential.input_ports) - .find_map(|(input_node, input_port)| (input_port == port).then_some(*input_node)) -} - -fn output_source_node_id(graph: &LogicGraph, output_name: &str) -> Option { - graph.nodes.iter().find_map(|node| { - (matches!(&node.kind, GraphNodeKind::Output(name) if name == output_name) - && node.inputs.len() == 1) - .then(|| node.inputs[0]) - }) -} - -fn sample_d_latch_step( - config: &LocalPlacerConfig, - step: usize, - candidates: PlacerQueue, -) -> PlacerQueue { - config - .step_sampling_policy - .sample_with_seed(candidates, config.sampling_seed(7, step)) -} - -fn take_sampling_limit(policy: SamplingPolicy) -> Option { - match policy { - SamplingPolicy::Take(count) => Some(count), - SamplingPolicy::None | SamplingPolicy::Random(_) => None, - } -} - -pub(super) fn generate_rs_latch_gate_routes( - config: &LocalPlacerConfig, - node: &GraphNode, - sequential: &SequentialPrimitive, - world: &World3D, - state: &PlacementState, -) -> PlacerQueue { - let raw_pairs = generate_rs_latch_not_pairs(world); - let pairs = select_rs_latch_not_pairs(config, node, sequential, state, raw_pairs.clone()); - let mut routed = Vec::new(); - let limit = take_sampling_limit(config.step_sampling_policy); - for pair in pairs { - for placed in route_rs_latch_branches(config, node, sequential, state, pair) { - let mut state = state.clone(); - state.set_port_position(node.id, "q".to_owned(), placed.q_torch); - state.set_port_position(node.id, "nq".to_owned(), placed.nq_torch); - let primary_output = sequential - .output_ports - .first() - .map(String::as_str) - .unwrap_or("q"); - let primary_position = match primary_output { - "q" => placed.q_torch, - "nq" => placed.nq_torch, - _ => placed.q_torch, - }; - state.set_node_position(node.id, primary_position); - routed.push((placed.world, state)); - if limit.is_some_and(|limit| routed.len() >= limit) { - break; - } - } - if limit.is_some_and(|limit| routed.len() >= limit) { - break; - } - } - - if routed.is_empty() { - generate_sequential_macro_routes(config, node, sequential, world, state) - } else { - routed - } -} - -pub(super) fn select_rs_latch_not_pairs( - config: &LocalPlacerConfig, - node: &GraphNode, - sequential: &SequentialPrimitive, - state: &PlacementState, - mut pairs: Vec, -) -> Vec { - let Some(set_source) = rs_latch_input_source(node, sequential, state, "s") else { - return config - .step_sampling_policy - .sample_with_seed(pairs, config.sampling_seed(4, 0)); - }; - let Some(reset_source) = rs_latch_input_source(node, sequential, state, "r") else { - return Vec::new(); - }; - - pairs.sort_by_key(|placed| { - let reset_distance = reset_source.manhattan_distance(&placed.q_cobble); - let set_distance = set_source.manhattan_distance(&placed.nq_cobble); - let input_block_penalty = if lies_between(reset_source, placed.q_cobble, placed.q_torch) { - 1_000 - } else { - 0 - } + if lies_between(set_source, placed.nq_cobble, placed.nq_torch) - { - 1_000 - } else { - 0 - }; - let input_space_penalty = (reset_distance.abs_diff(set_distance) * 10) - + usize::from(reset_distance < 2) * 100 - + usize::from(set_distance < 2) * 100; - reset_distance - + set_distance - + placed.q_torch.manhattan_distance(&placed.nq_torch) - + input_block_penalty - + input_space_penalty - }); - - match config.step_sampling_policy { - SamplingPolicy::None => pairs, - SamplingPolicy::Take(count) | SamplingPolicy::Random(count) => { - pairs.into_iter().take(count).collect() - } - } -} - -fn lies_between(start: Position, end: Position, position: Position) -> bool { - start.manhattan_distance(&position) + position.manhattan_distance(&end) - == start.manhattan_distance(&end) - && position != start - && position != end -} - -pub(super) fn route_rs_latch_branches( - config: &LocalPlacerConfig, - node: &GraphNode, - sequential: &SequentialPrimitive, - state: &PlacementState, - placed: RsLatchGatePlacement, -) -> Vec { - let Some(set_source) = rs_latch_input_source(node, sequential, state, "s") else { - return route_rs_latch_feedback(config, placed); - }; - let Some(reset_source) = rs_latch_input_source(node, sequential, state, "r") else { - return Vec::new(); - }; - - let q_torch = placed.q_torch; - let q_cobble = placed.q_cobble; - let nq_torch = placed.nq_torch; - let nq_cobble = placed.nq_cobble; - route_rs_latch_signals_to_cobble(config, placed, nq_torch, reset_source, q_torch, q_cobble) - .into_iter() - .flat_map(|placed| { - route_rs_latch_signals_to_cobble( - config, placed, q_torch, set_source, nq_torch, nq_cobble, - ) - }) - .collect() -} - -fn route_rs_latch_signals_to_cobble( - config: &LocalPlacerConfig, - placed: RsLatchGatePlacement, - first_source: Position, - second_source: Position, - target_torch: Position, - target_cobble: Position, -) -> Vec { - let worlds = generate_two_routes_to_cobble( - config, - &placed.world, - first_source, - second_source, - target_torch, - target_cobble, - ); - config - .route_step_sampling_policy - .sample_with_seed( - worlds, - config.sampling_seed(8, target_cobble.0 + target_cobble.1), - ) - .into_iter() - .map(|world| RsLatchGatePlacement { world, ..placed }) - .collect() -} - -fn generate_two_routes_to_cobble( - config: &LocalPlacerConfig, - world: &World3D, - first_source: Position, - second_source: Position, - target_torch: Position, - target_cobble: Position, -) -> Vec { - let first_routes = generate_routes_to_cobble_with_paths( - config, - world, - first_source, - target_torch, - target_cobble, - ); - let second_routes = generate_routes_to_cobble_with_paths( - config, - world, - second_source, - target_torch, - target_cobble, - ); - - let first_then_second = first_routes.iter().flat_map(|(first_world, first_path)| { - let network = redstone_route_network(first_world, first_path, target_cobble); - generate_routes_to_cobble_or_network( - config, - first_world, - second_source, - target_torch, - target_cobble, - &network, - ) - .into_iter() - .map(|(world, _)| world) - .collect_vec() - }); - - let second_then_first = second_routes - .iter() - .flat_map(|(second_world, second_path)| { - let network = redstone_route_network(second_world, second_path, target_cobble); - generate_routes_to_cobble_or_network( - config, - second_world, - first_source, - target_torch, - target_cobble, - &network, - ) - .into_iter() - .map(|(world, _)| world) - .collect_vec() - }); - - first_then_second.chain(second_then_first).collect() -} - -fn redstone_route_network( - world: &World3D, - path: &[Position], - target_cobble: Position, -) -> HashSet { - path.iter() - .copied() - .filter(|&position| { - world[position].kind.is_redstone() - && redstone_powers_cobble(world, position, target_cobble) - }) - .collect() -} - -fn rs_latch_input_source( - node: &GraphNode, - sequential: &SequentialPrimitive, - state: &PlacementState, - port: &str, -) -> Option { - node.inputs - .iter() - .zip(&sequential.input_ports) - .find_map(|(input_node, input_port)| { - (input_port == port) - .then(|| state.node_position(*input_node)) - .flatten() - }) -} - -pub(super) fn generate_rs_latch_not_pairs(world: &World3D) -> Vec { - let cardinal = [ - Direction::East, - Direction::West, - Direction::South, - Direction::North, - ]; - let support_positions = iproduct!(0..world.size.0, 0..world.size.1, 1..world.size.2) - .map(|(x, y, z)| Position(x, y, z)) - .collect_vec(); - let mut candidates = Vec::new(); - - for &q_direction in &cardinal { - for q_cobble in &support_positions { - let Some(q_torch) = q_cobble.walk(q_direction.inverse()) else { - continue; - }; - if !world.size.bound_on(q_torch) { - continue; - } - let q_torch_block = Block { - kind: BlockKind::Torch { is_on: false }, - direction: q_direction, - }; - let Some((q_world, q_torch, q_cobble)) = - place_torch_with_cobble(world, q_torch_block, q_torch) - else { - continue; - }; - - for &nq_direction in &cardinal { - for nq_cobble in &support_positions { - if q_cobble == *nq_cobble || q_cobble.2 != nq_cobble.2 { - continue; - } - let support_distance = q_cobble.manhattan_distance(nq_cobble); - if !(2..=3).contains(&support_distance) || q_direction.inverse() != nq_direction - { - continue; - } - let Some(nq_torch) = nq_cobble.walk(nq_direction.inverse()) else { - continue; - }; - if !q_world.size.bound_on(nq_torch) - || q_torch == nq_torch - || !(3..=5).contains(&q_torch.manhattan_distance(&nq_torch)) - { - continue; - } - let nq_torch_block = Block { - kind: BlockKind::Torch { is_on: false }, - direction: nq_direction, - }; - let Some((world, nq_torch, nq_cobble)) = - place_torch_with_cobble(&q_world, nq_torch_block, nq_torch) - else { - continue; - }; - candidates.push(RsLatchGatePlacement { - world, - q_torch, - q_cobble, - nq_torch, - nq_cobble, - }); - } - } - } - } - - candidates -} - -fn route_rs_latch_feedback( - config: &LocalPlacerConfig, - placed: RsLatchGatePlacement, -) -> Vec { - generate_routes_to_cobble( - config, - &placed.world, - placed.nq_torch, - placed.q_torch, - placed.q_cobble, - ) - .into_iter() - .flat_map(|(world, _)| { - generate_routes_to_cobble( - config, - &world, - placed.q_torch, - placed.nq_torch, - placed.nq_cobble, - ) - .into_iter() - .map(|(world, _)| RsLatchGatePlacement { - world, - q_torch: placed.q_torch, - q_cobble: placed.q_cobble, - nq_torch: placed.nq_torch, - nq_cobble: placed.nq_cobble, - }) - .collect_vec() - }) - .collect() -} - -pub(super) fn generate_sequential_macro_routes( - config: &LocalPlacerConfig, - node: &GraphNode, - sequential: &SequentialPrimitive, - world: &World3D, - state: &PlacementState, -) -> PlacerQueue { - SequentialMacro::candidates(sequential) - .into_iter() - .flat_map(|candidate| { - iproduct!(0..world.size.0, 0..world.size.1, 0..world.size.2) - .map(|(x, y, z)| Position(x, y, z)) - .filter_map(move |anchor| place_sequential_macro(world, &candidate, anchor)) - .flat_map(|placed| { - route_sequential_inputs(config, node, sequential, state, &placed) - .into_iter() - .map(|world| { - let mut state = state.clone(); - for (output_port, position) in &placed.output_ports { - state.set_port_position(node.id, output_port.to_owned(), *position); - } - let primary_output = sequential - .output_ports - .first() - .unwrap_or(&placed.primary_output_port); - let primary_position = placed.output_ports[primary_output]; - state.set_node_position(node.id, primary_position); - (world, state) - }) - .collect_vec() - }) - .collect_vec() - }) - .collect() -} - -#[derive(Debug, Clone)] -pub(super) struct PlacedSequentialMacro { - world: World3D, - input_ports: HashMap, - output_ports: HashMap, - primary_output_port: String, -} - -pub(super) fn place_sequential_macro( - world: &World3D, - candidate: &SequentialMacro, - anchor: Position, -) -> Option { - let mut new_world = world.clone(); - for (relative, block) in candidate.world.iter_block() { - let position = translate_position(anchor, relative)?; - if !world.size.bound_on(position) || !world[position].kind.is_air() { - return None; - } - new_world[position] = block; - } - new_world.initialize_redstone_states(); - - Some(PlacedSequentialMacro { - world: new_world, - input_ports: translate_ports(anchor, &candidate.input_ports)?, - output_ports: translate_ports(anchor, &candidate.output_ports)?, - primary_output_port: candidate.primary_output_port.clone(), - }) -} - -pub(super) fn route_sequential_inputs( - config: &LocalPlacerConfig, - node: &GraphNode, - sequential: &SequentialPrimitive, - state: &PlacementState, - placed: &PlacedSequentialMacro, -) -> Vec { - let mut worlds = vec![placed.world.clone()]; - for (input_node, input_port) in node.inputs.iter().zip(&sequential.input_ports) { - let Some(source) = state.node_position(*input_node) else { - return Vec::new(); - }; - let Some(&target) = placed.input_ports.get(input_port) else { - return Vec::new(); - }; - - worlds = worlds - .into_iter() - .flat_map(|world| { - generate_or_routes(config, &world, source, target) - .routes - .into_iter() - .map(|(world, _)| world) - .collect_vec() - }) - .collect_vec(); - } - worlds -} - -fn translate_ports( - anchor: Position, - ports: &HashMap, -) -> Option> { - ports - .iter() - .map(|(port, position)| Some((port.clone(), translate_position(anchor, *position)?))) - .collect() -} - -fn translate_position(anchor: Position, relative: Position) -> Option { - Some(Position( - anchor.0.checked_add(relative.0)?, - anchor.1.checked_add(relative.1)?, - anchor.2.checked_add(relative.2)?, - )) -} diff --git a/src/transform/place_and_route/local_placer/sequential/d_latch.rs b/src/transform/place_and_route/local_placer/sequential/d_latch.rs new file mode 100644 index 0000000..f701a10 --- /dev/null +++ b/src/transform/place_and_route/local_placer/sequential/d_latch.rs @@ -0,0 +1,286 @@ +use itertools::Itertools; + +use super::prefix::RsLatchPrefixPlan; +use super::rs_latch::generate_rs_latch_gate_routes; +use super::*; +use crate::world::simulator::Simulator; +use crate::world::World; + +pub(in super::super) fn generate_d_latch_gate_routes( + config: &LocalPlacerConfig, + node: &GraphNode, + sequential: &SequentialPrimitive, + world: &World3D, + state: &PlacementState, +) -> PlacerQueue { + let Some(prefix_plan) = RsLatchPrefixPlan::build(node, sequential, state) else { + return Vec::new(); + }; + let Some((data_input, enable_input)) = prefix_plan.d_latch_input_node_ids() else { + return Vec::new(); + }; + + let rs_sequential = SequentialPrimitive::new( + SequentialType::RsLatch, + vec!["s".to_owned(), "r".to_owned()], + sequential.output_ports.clone(), + ); + let rs_node = GraphNode { + id: node.id, + kind: GraphNodeKind::Sequential(rs_sequential.clone()), + inputs: prefix_plan.rs_node_inputs(), + ..Default::default() + }; + + let queue = prefix_plan.place(config, world, state); + let queue = retain_valid_d_latch_prefix(config, queue, &prefix_plan); + let routed = route_d_latch_core( + config, + node, + state, + &prefix_plan, + data_input, + enable_input, + &rs_node, + &rs_sequential, + queue, + ); + + config + .step_sampling_policy + .sample_with_seed(routed, config.sampling_seed(7, 6)) +} + +fn route_d_latch_core( + config: &LocalPlacerConfig, + node: &GraphNode, + outer_state: &PlacementState, + prefix_plan: &RsLatchPrefixPlan, + data_input: GraphNodeId, + enable_input: GraphNodeId, + rs_node: &GraphNode, + rs_sequential: &SequentialPrimitive, + queue: PlacerQueue, +) -> PlacerQueue { + let mut routed = Vec::new(); + let limit = take_sampling_limit(config.step_sampling_policy); + let rs_config = LocalPlacerConfig { + step_sampling_policy: SamplingPolicy::Random(32), + route_step_sampling_policy: SamplingPolicy::Random(32), + not_route_step_sampling_policy: SamplingPolicy::Random(32), + max_not_route_step: 8, + max_route_step: 8, + ..*config + }; + + for (world, prefix_state) in queue { + let Some((set_position, reset_position)) = + prefix_plan.prefix_output_positions(&prefix_state) + else { + continue; + }; + let mut state = outer_state.clone(); + prefix_plan.publish_core_inputs(&mut state, set_position, reset_position); + for (world, state) in + generate_rs_latch_gate_routes(&rs_config, rs_node, rs_sequential, &world, &state) + { + let Some(q) = state.port_position(node.id, "q") else { + continue; + }; + let Some(nq) = state.port_position(node.id, "nq") else { + continue; + }; + if std::panic::catch_unwind(|| { + d_latch_candidate_behaves(&world, data_input, enable_input, &state, q, nq) + }) + .unwrap_or(false) + { + routed.push((world, state)); + break; + } + } + if limit.is_some_and(|limit| routed.len() >= limit) { + break; + } + if !routed.is_empty() { + break; + } + } + + routed +} + +fn retain_valid_d_latch_prefix( + config: &LocalPlacerConfig, + queue: PlacerQueue, + prefix_plan: &RsLatchPrefixPlan, +) -> PlacerQueue { + let Some(data) = prefix_plan.input_position("d") else { + return Vec::new(); + }; + let Some(enable) = prefix_plan.input_position("en") else { + return Vec::new(); + }; + let queue = queue + .into_iter() + .filter(|(world, state)| { + d_latch_prefix_behaves( + world, + data, + enable, + prefix_plan.set_source(), + prefix_plan.reset_source(), + state, + ) + }) + .collect_vec(); + SamplingPolicy::Random(64).sample_with_seed(queue, config.sampling_seed(7, 6)) +} + +fn d_latch_prefix_behaves( + world: &World3D, + data: Position, + enable: Position, + set_node_id: GraphNodeId, + reset_node_id: GraphNodeId, + state: &PlacementState, +) -> bool { + let Some(set) = state.node_position(set_node_id) else { + return false; + }; + let Some(reset) = state.node_position(reset_node_id) else { + return false; + }; + + [(false, false), (false, true), (true, false), (true, true)] + .into_iter() + .all(|(data_value, enable_value)| { + d_latch_input_gate_values(world, data, enable, set, reset, data_value, enable_value) + .is_some_and(|(set_value, reset_value)| { + set_value == (data_value && enable_value) + && reset_value == (!data_value && enable_value) + }) + }) +} + +fn d_latch_input_gate_values( + world: &World3D, + data: Position, + enable: Position, + set: Position, + reset: Position, + data_value: bool, + enable_value: bool, +) -> Option<(bool, bool)> { + let mut world = pad_world_top_for_simulation(world, 2); + world[data].kind = BlockKind::Switch { is_on: data_value }; + world[enable].kind = BlockKind::Switch { + is_on: enable_value, + }; + world.initialize_redstone_states(); + let world = World::from(&world); + let sim = Simulator::from_with_limits_and_trace(&world, 64, 20_000, 0).ok()?; + let set_value = match sim.world()[set].kind { + BlockKind::Torch { is_on } => is_on, + _ => return None, + }; + let reset_value = match sim.world()[reset].kind { + BlockKind::Torch { is_on } => is_on, + _ => return None, + }; + Some((set_value, reset_value)) +} + +fn d_latch_candidate_behaves( + world: &World3D, + data_node_id: GraphNodeId, + enable_node_id: GraphNodeId, + state: &PlacementState, + q: Position, + nq: Position, +) -> bool { + let Some(data) = state.node_position(data_node_id) else { + return false; + }; + let Some(enable) = state.node_position(enable_node_id) else { + return false; + }; + + let mut reset_world = pad_world_top_for_simulation(world, 2); + reset_world[enable].kind = BlockKind::Switch { is_on: true }; + reset_world[data].kind = BlockKind::Switch { is_on: false }; + reset_world.initialize_redstone_states(); + let world = World::from(&reset_world); + let Ok(mut sim) = Simulator::from_with_limits_and_trace(&world, 64, 20_000, 0) else { + return false; + }; + + if torch_is_on_in_world3d(sim.world(), q) || !torch_is_on_in_world3d(sim.world(), nq) { + return false; + } + if sim + .change_state_with_limits(vec![(enable, false)], 64, 20_000) + .is_err() + { + return false; + } + if sim + .change_state_with_limits(vec![(data, true)], 64, 20_000) + .is_err() + { + return false; + } + if torch_is_on_in_world3d(sim.world(), q) || !torch_is_on_in_world3d(sim.world(), nq) { + return false; + } + if sim + .change_state_with_limits(vec![(enable, true)], 64, 20_000) + .is_err() + { + return false; + } + if !torch_is_on_in_world3d(sim.world(), q) || torch_is_on_in_world3d(sim.world(), nq) { + return false; + } + if sim + .change_state_with_limits(vec![(enable, false)], 64, 20_000) + .is_err() + { + return false; + } + if sim + .change_state_with_limits(vec![(data, false)], 64, 20_000) + .is_err() + { + return false; + } + if !torch_is_on_in_world3d(sim.world(), q) || torch_is_on_in_world3d(sim.world(), nq) { + return false; + } + if sim + .change_state_with_limits(vec![(enable, true)], 64, 20_000) + .is_err() + { + return false; + } + + !torch_is_on_in_world3d(sim.world(), q) && torch_is_on_in_world3d(sim.world(), nq) +} + +fn torch_is_on_in_world3d(world: &World3D, position: Position) -> bool { + matches!(world[position].kind, BlockKind::Torch { is_on: true }) +} + +fn pad_world_top_for_simulation(world: &World3D, extra_z: usize) -> World3D { + let mut padded = World3D::new(DimSize(world.size.0, world.size.1, world.size.2 + extra_z)); + for (position, mut block) in world.iter_block() { + if block.kind.is_cobble() { + block.kind = BlockKind::Cobble { + on_count: 0, + on_base_count: 0, + }; + } + padded[position] = block; + } + padded +} diff --git a/src/transform/place_and_route/local_placer/sequential/macro_routes.rs b/src/transform/place_and_route/local_placer/sequential/macro_routes.rs new file mode 100644 index 0000000..b094ef0 --- /dev/null +++ b/src/transform/place_and_route/local_placer/sequential/macro_routes.rs @@ -0,0 +1,120 @@ +use std::collections::HashMap; + +use itertools::{iproduct, Itertools}; + +use super::*; + +pub(in super::super) fn generate_sequential_macro_routes( + config: &LocalPlacerConfig, + node: &GraphNode, + sequential: &SequentialPrimitive, + world: &World3D, + state: &PlacementState, +) -> PlacerQueue { + SequentialMacro::candidates(sequential) + .into_iter() + .flat_map(|candidate| { + iproduct!(0..world.size.0, 0..world.size.1, 0..world.size.2) + .map(|(x, y, z)| Position(x, y, z)) + .filter_map(move |anchor| place_sequential_macro(world, &candidate, anchor)) + .flat_map(|placed| { + route_sequential_inputs(config, node, sequential, state, &placed) + .into_iter() + .map(|world| { + let mut state = state.clone(); + for (output_port, position) in &placed.output_ports { + state.set_port_position(node.id, output_port.to_owned(), *position); + } + let primary_output = sequential + .output_ports + .first() + .unwrap_or(&placed.primary_output_port); + let primary_position = placed.output_ports[primary_output]; + state.set_node_position(node.id, primary_position); + (world, state) + }) + .collect_vec() + }) + .collect_vec() + }) + .collect() +} + +#[derive(Debug, Clone)] +pub(in super::super) struct PlacedSequentialMacro { + world: World3D, + input_ports: HashMap, + output_ports: HashMap, + primary_output_port: String, +} + +pub(in super::super) fn place_sequential_macro( + world: &World3D, + candidate: &SequentialMacro, + anchor: Position, +) -> Option { + let mut new_world = world.clone(); + for (relative, block) in candidate.world.iter_block() { + let position = translate_position(anchor, relative)?; + if !world.size.bound_on(position) || !world[position].kind.is_air() { + return None; + } + new_world[position] = block; + } + new_world.initialize_redstone_states(); + + Some(PlacedSequentialMacro { + world: new_world, + input_ports: translate_ports(anchor, &candidate.input_ports)?, + output_ports: translate_ports(anchor, &candidate.output_ports)?, + primary_output_port: candidate.primary_output_port.clone(), + }) +} + +pub(in super::super) fn route_sequential_inputs( + config: &LocalPlacerConfig, + node: &GraphNode, + sequential: &SequentialPrimitive, + state: &PlacementState, + placed: &PlacedSequentialMacro, +) -> Vec { + let mut worlds = vec![placed.world.clone()]; + for (input_node, input_port) in node.inputs.iter().zip(&sequential.input_ports) { + let Some(source) = state.node_position(*input_node) else { + return Vec::new(); + }; + let Some(&target) = placed.input_ports.get(input_port) else { + return Vec::new(); + }; + + worlds = worlds + .into_iter() + .flat_map(|world| { + generate_or_routes(config, &world, source, target) + .routes + .into_iter() + .map(|(world, _)| world) + .collect_vec() + }) + .collect_vec(); + } + worlds +} + +fn translate_ports( + anchor: Position, + ports: &HashMap, +) -> Option> { + ports + .iter() + .map(|(port, position)| Some((port.clone(), translate_position(anchor, *position)?))) + .collect() +} + +fn translate_position(anchor: Position, relative: Position) -> Option { + Some(Position( + anchor.0.checked_add(relative.0)?, + anchor.1.checked_add(relative.1)?, + anchor.2.checked_add(relative.2)?, + )) +} diff --git a/src/transform/place_and_route/local_placer/sequential/mod.rs b/src/transform/place_and_route/local_placer/sequential/mod.rs new file mode 100644 index 0000000..ac9dfde --- /dev/null +++ b/src/transform/place_and_route/local_placer/sequential/mod.rs @@ -0,0 +1,49 @@ +use super::*; + +mod d_latch; +mod macro_routes; +mod prefix; +mod rs_latch; + +pub(super) use d_latch::generate_d_latch_gate_routes; +pub(super) use macro_routes::generate_sequential_macro_routes; +#[cfg(test)] +pub(super) use macro_routes::{place_sequential_macro, route_sequential_inputs}; +pub(super) use rs_latch::generate_rs_latch_gate_routes; +#[cfg(test)] +pub(super) use rs_latch::{ + generate_rs_latch_not_pairs, route_rs_latch_branches, select_rs_latch_not_pairs, +}; + +pub(super) fn supports_sequential_primitive(sequential: &SequentialPrimitive) -> bool { + matches!( + sequential.sequential_type, + SequentialType::RsLatch | SequentialType::DLatch + ) && (sequential.rs_latch_core().is_some() + || !SequentialMacro::candidates(sequential).is_empty()) +} + +#[derive(Debug, Clone, Copy)] +pub(super) struct RsLatchInputNodeIds { + pub(super) set: GraphNodeId, + pub(super) reset: GraphNodeId, +} + +const SEQUENTIAL_SYNTHETIC_NODE_ID_STRIDE: usize = 4; + +// Prefix placement runs in its own graph, then publishes only the set/reset +// endpoints back into the outer placement state for the RS latch core router. +pub(super) fn rs_latch_input_node_ids(node_id: GraphNodeId) -> RsLatchInputNodeIds { + let base = usize::MAX - node_id * SEQUENTIAL_SYNTHETIC_NODE_ID_STRIDE; + RsLatchInputNodeIds { + set: base - 1, + reset: base - 2, + } +} + +fn take_sampling_limit(policy: SamplingPolicy) -> Option { + match policy { + SamplingPolicy::Take(count) => Some(count), + SamplingPolicy::None | SamplingPolicy::Random(_) => None, + } +} diff --git a/src/transform/place_and_route/local_placer/sequential/prefix.rs b/src/transform/place_and_route/local_placer/sequential/prefix.rs new file mode 100644 index 0000000..19a3a30 --- /dev/null +++ b/src/transform/place_and_route/local_placer/sequential/prefix.rs @@ -0,0 +1,134 @@ +use std::collections::HashMap; + +use super::*; +use crate::sequential::core::rs_latch_prefix_graph; + +pub(super) struct RsLatchPrefixPlan { + graph: LogicGraph, + input_mappings: Vec<(GraphNodeId, GraphNodeId)>, + input_node_ids_by_port: HashMap, + input_positions_by_port: HashMap, + set_source: GraphNodeId, + reset_source: GraphNodeId, + rs_input_nodes: RsLatchInputNodeIds, +} + +impl RsLatchPrefixPlan { + pub(super) fn build( + node: &GraphNode, + sequential: &SequentialPrimitive, + state: &PlacementState, + ) -> Option { + let core = sequential.rs_latch_core()?; + let graph = rs_latch_prefix_graph(&sequential.inner_graph, &core) + .and_then(|graph| graph.prepare_place().ok())?; + let set_source = output_source_node_id(&graph, "s")?; + let reset_source = output_source_node_id(&graph, "r")?; + + let mut input_mappings = Vec::new(); + let mut input_node_ids_by_port = HashMap::new(); + let mut input_positions_by_port = HashMap::new(); + for prefix_input in graph.nodes.iter().filter_map(|node| match &node.kind { + GraphNodeKind::Input(name) => Some((node.id, name.as_str())), + _ => None, + }) { + let outer_input = outer_input_node_id(node, sequential, prefix_input.1)?; + let position = state.node_position(outer_input)?; + input_mappings.push((prefix_input.0, outer_input)); + input_node_ids_by_port.insert(prefix_input.1.to_owned(), outer_input); + input_positions_by_port.insert(prefix_input.1.to_owned(), position); + } + + Some(Self { + graph, + input_mappings, + input_node_ids_by_port, + input_positions_by_port, + set_source, + reset_source, + rs_input_nodes: rs_latch_input_node_ids(node.id), + }) + } + + pub(super) fn rs_node_inputs(&self) -> Vec { + vec![self.rs_input_nodes.set, self.rs_input_nodes.reset] + } + + pub(super) fn input_position(&self, port: &str) -> Option { + self.input_positions_by_port.get(port).copied() + } + + pub(super) fn d_latch_input_node_ids(&self) -> Option<(GraphNodeId, GraphNodeId)> { + Some(( + self.input_node_ids_by_port.get("d").copied()?, + self.input_node_ids_by_port.get("en").copied()?, + )) + } + + pub(super) fn prefix_output_positions( + &self, + state: &PlacementState, + ) -> Option<(Position, Position)> { + Some(( + state.node_position(self.set_source)?, + state.node_position(self.reset_source)?, + )) + } + + pub(super) fn publish_core_inputs( + &self, + state: &mut PlacementState, + set_position: Position, + reset_position: Position, + ) { + state.set_node_position(self.rs_input_nodes.set, set_position); + state.set_node_position(self.rs_input_nodes.reset, reset_position); + } + + pub(super) fn place( + &self, + config: &LocalPlacerConfig, + world: &World3D, + outer_state: &PlacementState, + ) -> PlacerQueue { + let mut prefix_state = PlacementState::default(); + for &(prefix_input, outer_input) in &self.input_mappings { + let Some(position) = outer_state.node_position(outer_input) else { + return Vec::new(); + }; + prefix_state.set_node_position(prefix_input, position); + } + + let Ok(prefix_placer) = LocalPlacer::new(self.graph.clone(), *config) else { + return Vec::new(); + }; + prefix_placer.generate_queue_from(vec![(world.clone(), prefix_state)], None, None) + } + + pub(super) fn set_source(&self) -> GraphNodeId { + self.set_source + } + + pub(super) fn reset_source(&self) -> GraphNodeId { + self.reset_source + } +} + +fn outer_input_node_id( + node: &GraphNode, + sequential: &SequentialPrimitive, + port: &str, +) -> Option { + node.inputs + .iter() + .zip(&sequential.input_ports) + .find_map(|(input_node, input_port)| (input_port == port).then_some(*input_node)) +} + +fn output_source_node_id(graph: &LogicGraph, output_name: &str) -> Option { + graph.nodes.iter().find_map(|node| { + (matches!(&node.kind, GraphNodeKind::Output(name) if name == output_name) + && node.inputs.len() == 1) + .then(|| node.inputs[0]) + }) +} diff --git a/src/transform/place_and_route/local_placer/sequential/rs_latch.rs b/src/transform/place_and_route/local_placer/sequential/rs_latch.rs new file mode 100644 index 0000000..a2d71c4 --- /dev/null +++ b/src/transform/place_and_route/local_placer/sequential/rs_latch.rs @@ -0,0 +1,364 @@ +use std::collections::HashSet; + +use itertools::{iproduct, Itertools}; + +use super::macro_routes::generate_sequential_macro_routes; +use super::*; + +#[derive(Debug, Clone)] +pub(in super::super) struct RsLatchGatePlacement { + pub(in super::super) world: World3D, + pub(in super::super) q_torch: Position, + pub(in super::super) q_cobble: Position, + pub(in super::super) nq_torch: Position, + pub(in super::super) nq_cobble: Position, +} + +pub(in super::super) fn generate_rs_latch_gate_routes( + config: &LocalPlacerConfig, + node: &GraphNode, + sequential: &SequentialPrimitive, + world: &World3D, + state: &PlacementState, +) -> PlacerQueue { + let raw_pairs = generate_rs_latch_not_pairs(world); + let pairs = select_rs_latch_not_pairs(config, node, sequential, state, raw_pairs.clone()); + let mut routed = Vec::new(); + let limit = take_sampling_limit(config.step_sampling_policy); + for pair in pairs { + for placed in route_rs_latch_branches(config, node, sequential, state, pair) { + let mut state = state.clone(); + state.set_port_position(node.id, "q".to_owned(), placed.q_torch); + state.set_port_position(node.id, "nq".to_owned(), placed.nq_torch); + let primary_output = sequential + .output_ports + .first() + .map(String::as_str) + .unwrap_or("q"); + let primary_position = match primary_output { + "q" => placed.q_torch, + "nq" => placed.nq_torch, + _ => placed.q_torch, + }; + state.set_node_position(node.id, primary_position); + routed.push((placed.world, state)); + if limit.is_some_and(|limit| routed.len() >= limit) { + break; + } + } + if limit.is_some_and(|limit| routed.len() >= limit) { + break; + } + } + + if routed.is_empty() { + generate_sequential_macro_routes(config, node, sequential, world, state) + } else { + routed + } +} + +pub(in super::super) fn select_rs_latch_not_pairs( + config: &LocalPlacerConfig, + node: &GraphNode, + sequential: &SequentialPrimitive, + state: &PlacementState, + mut pairs: Vec, +) -> Vec { + let Some(set_source) = rs_latch_input_source(node, sequential, state, "s") else { + return config + .step_sampling_policy + .sample_with_seed(pairs, config.sampling_seed(4, 0)); + }; + let Some(reset_source) = rs_latch_input_source(node, sequential, state, "r") else { + return Vec::new(); + }; + + pairs.sort_by_key(|placed| { + let reset_distance = reset_source.manhattan_distance(&placed.q_cobble); + let set_distance = set_source.manhattan_distance(&placed.nq_cobble); + let input_block_penalty = if lies_between(reset_source, placed.q_cobble, placed.q_torch) { + 1_000 + } else { + 0 + } + if lies_between(set_source, placed.nq_cobble, placed.nq_torch) + { + 1_000 + } else { + 0 + }; + let input_space_penalty = (reset_distance.abs_diff(set_distance) * 10) + + usize::from(reset_distance < 2) * 100 + + usize::from(set_distance < 2) * 100; + reset_distance + + set_distance + + placed.q_torch.manhattan_distance(&placed.nq_torch) + + input_block_penalty + + input_space_penalty + }); + + match config.step_sampling_policy { + SamplingPolicy::None => pairs, + SamplingPolicy::Take(count) | SamplingPolicy::Random(count) => { + pairs.into_iter().take(count).collect() + } + } +} + +fn lies_between(start: Position, end: Position, position: Position) -> bool { + start.manhattan_distance(&position) + position.manhattan_distance(&end) + == start.manhattan_distance(&end) + && position != start + && position != end +} + +pub(in super::super) fn route_rs_latch_branches( + config: &LocalPlacerConfig, + node: &GraphNode, + sequential: &SequentialPrimitive, + state: &PlacementState, + placed: RsLatchGatePlacement, +) -> Vec { + let Some(set_source) = rs_latch_input_source(node, sequential, state, "s") else { + return route_rs_latch_feedback(config, placed); + }; + let Some(reset_source) = rs_latch_input_source(node, sequential, state, "r") else { + return Vec::new(); + }; + + let q_torch = placed.q_torch; + let q_cobble = placed.q_cobble; + let nq_torch = placed.nq_torch; + let nq_cobble = placed.nq_cobble; + route_rs_latch_signals_to_cobble(config, placed, nq_torch, reset_source, q_torch, q_cobble) + .into_iter() + .flat_map(|placed| { + route_rs_latch_signals_to_cobble( + config, placed, q_torch, set_source, nq_torch, nq_cobble, + ) + }) + .collect() +} + +fn route_rs_latch_signals_to_cobble( + config: &LocalPlacerConfig, + placed: RsLatchGatePlacement, + first_source: Position, + second_source: Position, + target_torch: Position, + target_cobble: Position, +) -> Vec { + let worlds = generate_two_routes_to_cobble( + config, + &placed.world, + first_source, + second_source, + target_torch, + target_cobble, + ); + config + .route_step_sampling_policy + .sample_with_seed( + worlds, + config.sampling_seed(8, target_cobble.0 + target_cobble.1), + ) + .into_iter() + .map(|world| RsLatchGatePlacement { world, ..placed }) + .collect() +} + +fn generate_two_routes_to_cobble( + config: &LocalPlacerConfig, + world: &World3D, + first_source: Position, + second_source: Position, + target_torch: Position, + target_cobble: Position, +) -> Vec { + let first_routes = generate_routes_to_cobble_with_paths( + config, + world, + first_source, + target_torch, + target_cobble, + ); + let second_routes = generate_routes_to_cobble_with_paths( + config, + world, + second_source, + target_torch, + target_cobble, + ); + + let first_then_second = first_routes.iter().flat_map(|(first_world, first_path)| { + let network = redstone_route_network(first_world, first_path, target_cobble); + generate_routes_to_cobble_or_network( + config, + first_world, + second_source, + target_torch, + target_cobble, + &network, + ) + .into_iter() + .map(|(world, _)| world) + .collect_vec() + }); + + let second_then_first = second_routes + .iter() + .flat_map(|(second_world, second_path)| { + let network = redstone_route_network(second_world, second_path, target_cobble); + generate_routes_to_cobble_or_network( + config, + second_world, + first_source, + target_torch, + target_cobble, + &network, + ) + .into_iter() + .map(|(world, _)| world) + .collect_vec() + }); + + first_then_second.chain(second_then_first).collect() +} + +fn redstone_route_network( + world: &World3D, + path: &[Position], + target_cobble: Position, +) -> HashSet { + path.iter() + .copied() + .filter(|&position| { + world[position].kind.is_redstone() + && redstone_powers_cobble(world, position, target_cobble) + }) + .collect() +} + +fn rs_latch_input_source( + node: &GraphNode, + sequential: &SequentialPrimitive, + state: &PlacementState, + port: &str, +) -> Option { + node.inputs + .iter() + .zip(&sequential.input_ports) + .find_map(|(input_node, input_port)| { + (input_port == port) + .then(|| state.node_position(*input_node)) + .flatten() + }) +} + +pub(in super::super) fn generate_rs_latch_not_pairs( + world: &World3D, +) -> Vec { + let cardinal = [ + Direction::East, + Direction::West, + Direction::South, + Direction::North, + ]; + let support_positions = iproduct!(0..world.size.0, 0..world.size.1, 1..world.size.2) + .map(|(x, y, z)| Position(x, y, z)) + .collect_vec(); + let mut candidates = Vec::new(); + + for &q_direction in &cardinal { + for q_cobble in &support_positions { + let Some(q_torch) = q_cobble.walk(q_direction.inverse()) else { + continue; + }; + if !world.size.bound_on(q_torch) { + continue; + } + let q_torch_block = Block { + kind: BlockKind::Torch { is_on: false }, + direction: q_direction, + }; + let Some((q_world, q_torch, q_cobble)) = + place_torch_with_cobble(world, q_torch_block, q_torch) + else { + continue; + }; + + for &nq_direction in &cardinal { + for nq_cobble in &support_positions { + if q_cobble == *nq_cobble || q_cobble.2 != nq_cobble.2 { + continue; + } + let support_distance = q_cobble.manhattan_distance(nq_cobble); + if !(2..=3).contains(&support_distance) || q_direction.inverse() != nq_direction + { + continue; + } + let Some(nq_torch) = nq_cobble.walk(nq_direction.inverse()) else { + continue; + }; + if !q_world.size.bound_on(nq_torch) + || q_torch == nq_torch + || !(3..=5).contains(&q_torch.manhattan_distance(&nq_torch)) + { + continue; + } + let nq_torch_block = Block { + kind: BlockKind::Torch { is_on: false }, + direction: nq_direction, + }; + let Some((world, nq_torch, nq_cobble)) = + place_torch_with_cobble(&q_world, nq_torch_block, nq_torch) + else { + continue; + }; + candidates.push(RsLatchGatePlacement { + world, + q_torch, + q_cobble, + nq_torch, + nq_cobble, + }); + } + } + } + } + + candidates +} + +fn route_rs_latch_feedback( + config: &LocalPlacerConfig, + placed: RsLatchGatePlacement, +) -> Vec { + generate_routes_to_cobble( + config, + &placed.world, + placed.nq_torch, + placed.q_torch, + placed.q_cobble, + ) + .into_iter() + .flat_map(|(world, _)| { + generate_routes_to_cobble( + config, + &world, + placed.q_torch, + placed.nq_torch, + placed.nq_cobble, + ) + .into_iter() + .map(|(world, _)| RsLatchGatePlacement { + world, + q_torch: placed.q_torch, + q_cobble: placed.q_cobble, + nq_torch: placed.nq_torch, + nq_cobble: placed.nq_cobble, + }) + .collect_vec() + }) + .collect() +} From 3e43a53e20d93db88a02a40fd5e814c9568771c5 Mon Sep 17 00:00:00 2001 From: rollrat Date: Mon, 25 May 2026 08:31:49 +0900 Subject: [PATCH 4/5] Name local placer tuning constants --- .../place_and_route/local_placer/mod.rs | 46 ++++++---- .../place_and_route/local_placer/routing.rs | 17 ++-- .../local_placer/sequential/d_latch.rs | 89 +++++++++++-------- .../local_placer/sequential/rs_latch.rs | 58 ++++++++---- 4 files changed, 135 insertions(+), 75 deletions(-) diff --git a/src/transform/place_and_route/local_placer/mod.rs b/src/transform/place_and_route/local_placer/mod.rs index a0121fb..282677b 100644 --- a/src/transform/place_and_route/local_placer/mod.rs +++ b/src/transform/place_and_route/local_placer/mod.rs @@ -54,6 +54,15 @@ pub struct LocalPlacer { } type PlacerQueue = Vec<(World3D, PlacementState)>; + +const STEP_SAMPLE_SCOPE: u64 = 1; +const RANKED_RANDOM_TAIL_SAMPLE_SCOPE: u64 = 2; +const LEAK_SAMPLING_QUEUE_THRESHOLD: usize = 10_000; +const PLACEMENT_DIVERSITY_SPAN_BUCKET_SIZE: usize = 4; +const RANKED_DIVERSITY_SLOT_DIVISOR: usize = 4; +const LOCAL_DENSITY_COST_WEIGHT: usize = 3; +const FUTURE_JOIN_DISTANCE_COST_WEIGHT: usize = 8; + impl LocalPlacer { pub fn new(graph: LogicGraph, config: LocalPlacerConfig) -> eyre::Result { let visit_orders = graph.topological_order(); @@ -375,16 +384,17 @@ impl LocalPlacer { fn sample(&self, step: usize, queue: PlacerQueue) -> PlacerQueue { match self.config.placement_sampling_policy { PlacementSamplingPolicy::StepPolicy => { - if self.config.leak_sampling && queue.len() > 10_000 { + if self.config.leak_sampling && queue.len() > LEAK_SAMPLING_QUEUE_THRESHOLD { // TODO: deallocate on other thread let leak = Box::leak(Box::new(queue)); - self.config - .step_sampling_policy - .sample_with_taking_seed(leak, self.config.sampling_seed(1, step)) + self.config.step_sampling_policy.sample_with_taking_seed( + leak, + self.config.sampling_seed(STEP_SAMPLE_SCOPE, step), + ) } else { self.config .step_sampling_policy - .sample_with_seed(queue, self.config.sampling_seed(1, step)) + .sample_with_seed(queue, self.config.sampling_seed(STEP_SAMPLE_SCOPE, step)) } } PlacementSamplingPolicy::Cost { @@ -395,7 +405,7 @@ impl LocalPlacer { if step < start_step { self.config .step_sampling_policy - .sample_with_seed(queue, self.config.sampling_seed(1, step)) + .sample_with_seed(queue, self.config.sampling_seed(STEP_SAMPLE_SCOPE, step)) } else { self.sample_by_cost(step, queue, count, random_count) } @@ -408,7 +418,7 @@ impl LocalPlacer { if step < start_step { self.config .step_sampling_policy - .sample_with_seed(queue, self.config.sampling_seed(1, step)) + .sample_with_seed(queue, self.config.sampling_seed(STEP_SAMPLE_SCOPE, step)) } else { self.sample_by_ranked(step, queue, count, random_count) } @@ -472,8 +482,11 @@ impl LocalPlacer { let mut best = scored.into_iter().map(|(_, item)| item).collect_vec(); let rest = rest.into_iter().map(|(_, item)| item).collect_vec(); best.extend( - SamplingPolicy::Random(random_count) - .sample_with_seed(rest, self.config.sampling_seed(2, step)), + SamplingPolicy::Random(random_count).sample_with_seed( + rest, + self.config + .sampling_seed(RANKED_RANDOM_TAIL_SAMPLE_SCOPE, step), + ), ); best } @@ -503,7 +516,7 @@ impl LocalPlacer { // diversity 보존용 coarse geometry bucket이다. 완전한 placement // identity가 아니므로 semantic deduplication 용도로 쓰면 안 된다. signature: bounding_box_of_positions(item.1.node_positions()) - .map(|bounds| bounds.manhattan_span() / 4) + .map(|bounds| bounds.manhattan_span() / PLACEMENT_DIVERSITY_SPAN_BUCKET_SIZE) .unwrap_or_default(), index, item, @@ -516,7 +529,7 @@ impl LocalPlacer { // config knob으로 드러내는 것이 좋다. let ranked_count = count.min(scored.len()); let diversity_count = if ranked_count > 1 { - (ranked_count / 4).max(1) + (ranked_count / RANKED_DIVERSITY_SLOT_DIVISOR).max(1) } else { 0 }; @@ -556,8 +569,11 @@ impl LocalPlacer { let mut best = selected.into_iter().map(|item| item.item).collect_vec(); let random_pool = overflow.map(|item| item.item).collect_vec(); best.extend( - SamplingPolicy::Random(random_count) - .sample_with_seed(random_pool, self.config.sampling_seed(2, step)), + SamplingPolicy::Random(random_count).sample_with_seed( + random_pool, + self.config + .sampling_seed(RANKED_RANDOM_TAIL_SAMPLE_SCOPE, step), + ), ); best } @@ -567,7 +583,7 @@ impl LocalPlacer { let mut cost = world_compact_cost(world); if let Some(position) = state.node_position(current_node_id) { - cost += local_density(world, position) * 3; + cost += local_density(world, position) * LOCAL_DENSITY_COST_WEIGHT; } for pair in &self.cost_join_pairs_by_step[step] { @@ -575,7 +591,7 @@ impl LocalPlacer { else { continue; }; - cost += a.manhattan_distance(&b) * pair.weight * 8; + cost += a.manhattan_distance(&b) * pair.weight * FUTURE_JOIN_DISTANCE_COST_WEIGHT; } cost diff --git a/src/transform/place_and_route/local_placer/routing.rs b/src/transform/place_and_route/local_placer/routing.rs index 5ffdd92..6d39eb3 100644 --- a/src/transform/place_and_route/local_placer/routing.rs +++ b/src/transform/place_and_route/local_placer/routing.rs @@ -1,5 +1,8 @@ use super::*; +const OR_ROUTE_STEP_SAMPLE_SCOPE: u64 = 3; +const NOT_ROUTE_STEP_SAMPLE_SCOPE: u64 = 5; + pub(super) fn input_node_kind() -> Vec { vec![ BlockKind::Switch { is_on: false }, @@ -404,9 +407,10 @@ fn generate_redstone_routes( } } - queue = config - .route_step_sampling_policy - .sample_with_seed(next_queue, config.sampling_seed(5, step)); + queue = config.route_step_sampling_policy.sample_with_seed( + next_queue, + config.sampling_seed(NOT_ROUTE_STEP_SAMPLE_SCOPE, step), + ); step += 1; } @@ -591,9 +595,10 @@ pub(super) fn generate_or_routes( } depth_debug.next_frontier_before_sampling = next_queue.len(); - queue = config - .route_step_sampling_policy - .sample_with_seed(next_queue, config.sampling_seed(3, step)); + queue = config.route_step_sampling_policy.sample_with_seed( + next_queue, + config.sampling_seed(OR_ROUTE_STEP_SAMPLE_SCOPE, step), + ); depth_debug.next_frontier_after_sampling = queue.len(); debug.depths.push(depth_debug); step += 1; diff --git a/src/transform/place_and_route/local_placer/sequential/d_latch.rs b/src/transform/place_and_route/local_placer/sequential/d_latch.rs index f701a10..9189133 100644 --- a/src/transform/place_and_route/local_placer/sequential/d_latch.rs +++ b/src/transform/place_and_route/local_placer/sequential/d_latch.rs @@ -6,6 +6,16 @@ use super::*; use crate::world::simulator::Simulator; use crate::world::World; +const D_LATCH_SAMPLE_SCOPE: u64 = 7; +const D_LATCH_OUTPUT_SAMPLE_STEP: usize = 6; +const D_LATCH_PREFIX_SAMPLE_COUNT: usize = 64; +const RS_CORE_SEARCH_SAMPLE_COUNT: usize = 32; +const RS_CORE_SEARCH_MAX_ROUTE_STEP: usize = 8; +const D_LATCH_SIM_EXTRA_Z: usize = 2; +const D_LATCH_SIM_MAX_CYCLES: usize = 64; +const D_LATCH_SIM_MAX_EVENTS: usize = 20_000; +const D_LATCH_SIM_TRACE_LIMIT: usize = 0; + pub(in super::super) fn generate_d_latch_gate_routes( config: &LocalPlacerConfig, node: &GraphNode, @@ -46,9 +56,10 @@ pub(in super::super) fn generate_d_latch_gate_routes( queue, ); - config - .step_sampling_policy - .sample_with_seed(routed, config.sampling_seed(7, 6)) + config.step_sampling_policy.sample_with_seed( + routed, + config.sampling_seed(D_LATCH_SAMPLE_SCOPE, D_LATCH_OUTPUT_SAMPLE_STEP), + ) } fn route_d_latch_core( @@ -65,11 +76,11 @@ fn route_d_latch_core( let mut routed = Vec::new(); let limit = take_sampling_limit(config.step_sampling_policy); let rs_config = LocalPlacerConfig { - step_sampling_policy: SamplingPolicy::Random(32), - route_step_sampling_policy: SamplingPolicy::Random(32), - not_route_step_sampling_policy: SamplingPolicy::Random(32), - max_not_route_step: 8, - max_route_step: 8, + step_sampling_policy: SamplingPolicy::Random(RS_CORE_SEARCH_SAMPLE_COUNT), + route_step_sampling_policy: SamplingPolicy::Random(RS_CORE_SEARCH_SAMPLE_COUNT), + not_route_step_sampling_policy: SamplingPolicy::Random(RS_CORE_SEARCH_SAMPLE_COUNT), + max_not_route_step: RS_CORE_SEARCH_MAX_ROUTE_STEP, + max_route_step: RS_CORE_SEARCH_MAX_ROUTE_STEP, ..*config }; @@ -134,7 +145,10 @@ fn retain_valid_d_latch_prefix( ) }) .collect_vec(); - SamplingPolicy::Random(64).sample_with_seed(queue, config.sampling_seed(7, 6)) + SamplingPolicy::Random(D_LATCH_PREFIX_SAMPLE_COUNT).sample_with_seed( + queue, + config.sampling_seed(D_LATCH_SAMPLE_SCOPE, D_LATCH_OUTPUT_SAMPLE_STEP), + ) } fn d_latch_prefix_behaves( @@ -172,14 +186,14 @@ fn d_latch_input_gate_values( data_value: bool, enable_value: bool, ) -> Option<(bool, bool)> { - let mut world = pad_world_top_for_simulation(world, 2); + let mut world = pad_world_top_for_simulation(world, D_LATCH_SIM_EXTRA_Z); world[data].kind = BlockKind::Switch { is_on: data_value }; world[enable].kind = BlockKind::Switch { is_on: enable_value, }; world.initialize_redstone_states(); let world = World::from(&world); - let sim = Simulator::from_with_limits_and_trace(&world, 64, 20_000, 0).ok()?; + let sim = run_d_latch_simulation(&world)?; let set_value = match sim.world()[set].kind { BlockKind::Torch { is_on } => is_on, _ => return None, @@ -206,67 +220,68 @@ fn d_latch_candidate_behaves( return false; }; - let mut reset_world = pad_world_top_for_simulation(world, 2); + let mut reset_world = pad_world_top_for_simulation(world, D_LATCH_SIM_EXTRA_Z); reset_world[enable].kind = BlockKind::Switch { is_on: true }; reset_world[data].kind = BlockKind::Switch { is_on: false }; reset_world.initialize_redstone_states(); let world = World::from(&reset_world); - let Ok(mut sim) = Simulator::from_with_limits_and_trace(&world, 64, 20_000, 0) else { + let Some(mut sim) = run_d_latch_simulation(&world) else { return false; }; if torch_is_on_in_world3d(sim.world(), q) || !torch_is_on_in_world3d(sim.world(), nq) { return false; } - if sim - .change_state_with_limits(vec![(enable, false)], 64, 20_000) - .is_err() - { + if !change_d_latch_switch(&mut sim, enable, false) { return false; } - if sim - .change_state_with_limits(vec![(data, true)], 64, 20_000) - .is_err() - { + if !change_d_latch_switch(&mut sim, data, true) { return false; } if torch_is_on_in_world3d(sim.world(), q) || !torch_is_on_in_world3d(sim.world(), nq) { return false; } - if sim - .change_state_with_limits(vec![(enable, true)], 64, 20_000) - .is_err() - { + if !change_d_latch_switch(&mut sim, enable, true) { return false; } if !torch_is_on_in_world3d(sim.world(), q) || torch_is_on_in_world3d(sim.world(), nq) { return false; } - if sim - .change_state_with_limits(vec![(enable, false)], 64, 20_000) - .is_err() - { + if !change_d_latch_switch(&mut sim, enable, false) { return false; } - if sim - .change_state_with_limits(vec![(data, false)], 64, 20_000) - .is_err() - { + if !change_d_latch_switch(&mut sim, data, false) { return false; } if !torch_is_on_in_world3d(sim.world(), q) || torch_is_on_in_world3d(sim.world(), nq) { return false; } - if sim - .change_state_with_limits(vec![(enable, true)], 64, 20_000) - .is_err() - { + if !change_d_latch_switch(&mut sim, enable, true) { return false; } !torch_is_on_in_world3d(sim.world(), q) && torch_is_on_in_world3d(sim.world(), nq) } +fn run_d_latch_simulation(world: &World) -> Option { + Simulator::from_with_limits_and_trace( + world, + D_LATCH_SIM_MAX_CYCLES, + D_LATCH_SIM_MAX_EVENTS, + D_LATCH_SIM_TRACE_LIMIT, + ) + .ok() +} + +fn change_d_latch_switch(sim: &mut Simulator, position: Position, value: bool) -> bool { + sim.change_state_with_limits( + vec![(position, value)], + D_LATCH_SIM_MAX_CYCLES, + D_LATCH_SIM_MAX_EVENTS, + ) + .is_ok() +} + fn torch_is_on_in_world3d(world: &World3D, position: Position) -> bool { matches!(world[position].kind, BlockKind::Torch { is_on: true }) } diff --git a/src/transform/place_and_route/local_placer/sequential/rs_latch.rs b/src/transform/place_and_route/local_placer/sequential/rs_latch.rs index a2d71c4..c75db1e 100644 --- a/src/transform/place_and_route/local_placer/sequential/rs_latch.rs +++ b/src/transform/place_and_route/local_placer/sequential/rs_latch.rs @@ -5,6 +5,18 @@ use itertools::{iproduct, Itertools}; use super::macro_routes::generate_sequential_macro_routes; use super::*; +const RS_LATCH_PAIR_SELECTION_SAMPLE_SCOPE: u64 = 4; +const RS_LATCH_BRANCH_ROUTE_SAMPLE_SCOPE: u64 = 8; +const RS_LATCH_INPUT_BLOCK_PENALTY: usize = 1_000; +const RS_LATCH_INPUT_DISTANCE_BALANCE_WEIGHT: usize = 10; +const RS_LATCH_CLOSE_INPUT_DISTANCE: usize = 2; +const RS_LATCH_CLOSE_INPUT_PENALTY: usize = 100; +const RS_LATCH_MIN_SUPPORT_DISTANCE: usize = 2; +const RS_LATCH_MAX_SUPPORT_DISTANCE: usize = 3; +const RS_LATCH_MIN_TORCH_DISTANCE: usize = 3; +const RS_LATCH_MAX_TORCH_DISTANCE: usize = 5; +const RS_LATCH_MIN_SUPPORT_Z: usize = 1; + #[derive(Debug, Clone)] pub(in super::super) struct RsLatchGatePlacement { pub(in super::super) world: World3D, @@ -66,9 +78,10 @@ pub(in super::super) fn select_rs_latch_not_pairs( mut pairs: Vec, ) -> Vec { let Some(set_source) = rs_latch_input_source(node, sequential, state, "s") else { - return config - .step_sampling_policy - .sample_with_seed(pairs, config.sampling_seed(4, 0)); + return config.step_sampling_policy.sample_with_seed( + pairs, + config.sampling_seed(RS_LATCH_PAIR_SELECTION_SAMPLE_SCOPE, 0), + ); }; let Some(reset_source) = rs_latch_input_source(node, sequential, state, "r") else { return Vec::new(); @@ -78,18 +91,21 @@ pub(in super::super) fn select_rs_latch_not_pairs( let reset_distance = reset_source.manhattan_distance(&placed.q_cobble); let set_distance = set_source.manhattan_distance(&placed.nq_cobble); let input_block_penalty = if lies_between(reset_source, placed.q_cobble, placed.q_torch) { - 1_000 + RS_LATCH_INPUT_BLOCK_PENALTY } else { 0 } + if lies_between(set_source, placed.nq_cobble, placed.nq_torch) { - 1_000 + RS_LATCH_INPUT_BLOCK_PENALTY } else { 0 }; - let input_space_penalty = (reset_distance.abs_diff(set_distance) * 10) - + usize::from(reset_distance < 2) * 100 - + usize::from(set_distance < 2) * 100; + let input_space_penalty = reset_distance.abs_diff(set_distance) + * RS_LATCH_INPUT_DISTANCE_BALANCE_WEIGHT + + usize::from(reset_distance < RS_LATCH_CLOSE_INPUT_DISTANCE) + * RS_LATCH_CLOSE_INPUT_PENALTY + + usize::from(set_distance < RS_LATCH_CLOSE_INPUT_DISTANCE) + * RS_LATCH_CLOSE_INPUT_PENALTY; reset_distance + set_distance + placed.q_torch.manhattan_distance(&placed.nq_torch) @@ -160,7 +176,10 @@ fn route_rs_latch_signals_to_cobble( .route_step_sampling_policy .sample_with_seed( worlds, - config.sampling_seed(8, target_cobble.0 + target_cobble.1), + config.sampling_seed( + RS_LATCH_BRANCH_ROUTE_SAMPLE_SCOPE, + target_cobble.0 + target_cobble.1, + ), ) .into_iter() .map(|world| RsLatchGatePlacement { world, ..placed }) @@ -255,18 +274,20 @@ fn rs_latch_input_source( }) } -pub(in super::super) fn generate_rs_latch_not_pairs( - world: &World3D, -) -> Vec { +pub(in super::super) fn generate_rs_latch_not_pairs(world: &World3D) -> Vec { let cardinal = [ Direction::East, Direction::West, Direction::South, Direction::North, ]; - let support_positions = iproduct!(0..world.size.0, 0..world.size.1, 1..world.size.2) - .map(|(x, y, z)| Position(x, y, z)) - .collect_vec(); + let support_positions = iproduct!( + 0..world.size.0, + 0..world.size.1, + RS_LATCH_MIN_SUPPORT_Z..world.size.2 + ) + .map(|(x, y, z)| Position(x, y, z)) + .collect_vec(); let mut candidates = Vec::new(); for &q_direction in &cardinal { @@ -293,7 +314,9 @@ pub(in super::super) fn generate_rs_latch_not_pairs( continue; } let support_distance = q_cobble.manhattan_distance(nq_cobble); - if !(2..=3).contains(&support_distance) || q_direction.inverse() != nq_direction + if !(RS_LATCH_MIN_SUPPORT_DISTANCE..=RS_LATCH_MAX_SUPPORT_DISTANCE) + .contains(&support_distance) + || q_direction.inverse() != nq_direction { continue; } @@ -302,7 +325,8 @@ pub(in super::super) fn generate_rs_latch_not_pairs( }; if !q_world.size.bound_on(nq_torch) || q_torch == nq_torch - || !(3..=5).contains(&q_torch.manhattan_distance(&nq_torch)) + || !(RS_LATCH_MIN_TORCH_DISTANCE..=RS_LATCH_MAX_TORCH_DISTANCE) + .contains(&q_torch.manhattan_distance(&nq_torch)) { continue; } From 3068f4f3c0fb9dea2375fef438bd540489a507e4 Mon Sep 17 00:00:00 2001 From: rollrat Date: Mon, 25 May 2026 09:15:53 +0900 Subject: [PATCH 5/5] Table-drive sequential validation scenario --- .../local_placer/sequential/d_latch.rs | 115 ++++++++++-------- .../local_placer/sequential/mod.rs | 1 + .../local_placer/sequential/scenario.rs | 57 +++++++++ 3 files changed, 123 insertions(+), 50 deletions(-) create mode 100644 src/transform/place_and_route/local_placer/sequential/scenario.rs diff --git a/src/transform/place_and_route/local_placer/sequential/d_latch.rs b/src/transform/place_and_route/local_placer/sequential/d_latch.rs index 9189133..43e23e6 100644 --- a/src/transform/place_and_route/local_placer/sequential/d_latch.rs +++ b/src/transform/place_and_route/local_placer/sequential/d_latch.rs @@ -2,6 +2,7 @@ use itertools::Itertools; use super::prefix::RsLatchPrefixPlan; use super::rs_latch::generate_rs_latch_gate_routes; +use super::scenario::{run_sequential_scenario, torch_output_value, SequentialScenarioStep}; use super::*; use crate::world::simulator::Simulator; use crate::world::World; @@ -194,14 +195,8 @@ fn d_latch_input_gate_values( world.initialize_redstone_states(); let world = World::from(&world); let sim = run_d_latch_simulation(&world)?; - let set_value = match sim.world()[set].kind { - BlockKind::Torch { is_on } => is_on, - _ => return None, - }; - let reset_value = match sim.world()[reset].kind { - BlockKind::Torch { is_on } => is_on, - _ => return None, - }; + let set_value = torch_output_value(sim.world(), set)?; + let reset_value = torch_output_value(sim.world(), reset)?; Some((set_value, reset_value)) } @@ -229,38 +224,71 @@ fn d_latch_candidate_behaves( return false; }; - if torch_is_on_in_world3d(sim.world(), q) || !torch_is_on_in_world3d(sim.world(), nq) { - return false; - } - if !change_d_latch_switch(&mut sim, enable, false) { - return false; - } - if !change_d_latch_switch(&mut sim, data, true) { - return false; - } - if torch_is_on_in_world3d(sim.world(), q) || !torch_is_on_in_world3d(sim.world(), nq) { - return false; - } - if !change_d_latch_switch(&mut sim, enable, true) { - return false; - } - if !torch_is_on_in_world3d(sim.world(), q) || torch_is_on_in_world3d(sim.world(), nq) { - return false; - } - if !change_d_latch_switch(&mut sim, enable, false) { - return false; - } - if !change_d_latch_switch(&mut sim, data, false) { - return false; - } - if !torch_is_on_in_world3d(sim.world(), q) || torch_is_on_in_world3d(sim.world(), nq) { - return false; + #[derive(Clone, Copy)] + enum DLatchInput { + Data, + Enable, } - if !change_d_latch_switch(&mut sim, enable, true) { - return false; + + #[derive(Clone, Copy)] + enum DLatchOutput { + Q, + Nq, } - !torch_is_on_in_world3d(sim.world(), q) && torch_is_on_in_world3d(sim.world(), nq) + let reset_outputs = [(DLatchOutput::Q, false), (DLatchOutput::Nq, true)]; + let set_outputs = [(DLatchOutput::Q, true), (DLatchOutput::Nq, false)]; + let scenario = [ + // Start from en=1, d=0, so the latch must be reset. + SequentialScenarioStep::ExpectOutputs(&reset_outputs), + // While closed, changing d must not change the latched value. + SequentialScenarioStep::SetInput { + input: DLatchInput::Enable, + value: false, + }, + SequentialScenarioStep::SetInput { + input: DLatchInput::Data, + value: true, + }, + SequentialScenarioStep::ExpectOutputs(&reset_outputs), + // Opening the latch captures d=1. + SequentialScenarioStep::SetInput { + input: DLatchInput::Enable, + value: true, + }, + SequentialScenarioStep::ExpectOutputs(&set_outputs), + // While closed again, changing d must preserve the set state. + SequentialScenarioStep::SetInput { + input: DLatchInput::Enable, + value: false, + }, + SequentialScenarioStep::SetInput { + input: DLatchInput::Data, + value: false, + }, + SequentialScenarioStep::ExpectOutputs(&set_outputs), + // Opening the latch captures d=0. + SequentialScenarioStep::SetInput { + input: DLatchInput::Enable, + value: true, + }, + SequentialScenarioStep::ExpectOutputs(&reset_outputs), + ]; + + run_sequential_scenario( + &mut sim, + &scenario, + |input| match input { + DLatchInput::Data => data, + DLatchInput::Enable => enable, + }, + |world, output| match output { + DLatchOutput::Q => torch_output_value(world, q), + DLatchOutput::Nq => torch_output_value(world, nq), + }, + D_LATCH_SIM_MAX_CYCLES, + D_LATCH_SIM_MAX_EVENTS, + ) } fn run_d_latch_simulation(world: &World) -> Option { @@ -273,19 +301,6 @@ fn run_d_latch_simulation(world: &World) -> Option { .ok() } -fn change_d_latch_switch(sim: &mut Simulator, position: Position, value: bool) -> bool { - sim.change_state_with_limits( - vec![(position, value)], - D_LATCH_SIM_MAX_CYCLES, - D_LATCH_SIM_MAX_EVENTS, - ) - .is_ok() -} - -fn torch_is_on_in_world3d(world: &World3D, position: Position) -> bool { - matches!(world[position].kind, BlockKind::Torch { is_on: true }) -} - fn pad_world_top_for_simulation(world: &World3D, extra_z: usize) -> World3D { let mut padded = World3D::new(DimSize(world.size.0, world.size.1, world.size.2 + extra_z)); for (position, mut block) in world.iter_block() { diff --git a/src/transform/place_and_route/local_placer/sequential/mod.rs b/src/transform/place_and_route/local_placer/sequential/mod.rs index ac9dfde..9a9ab22 100644 --- a/src/transform/place_and_route/local_placer/sequential/mod.rs +++ b/src/transform/place_and_route/local_placer/sequential/mod.rs @@ -4,6 +4,7 @@ mod d_latch; mod macro_routes; mod prefix; mod rs_latch; +mod scenario; pub(super) use d_latch::generate_d_latch_gate_routes; pub(super) use macro_routes::generate_sequential_macro_routes; diff --git a/src/transform/place_and_route/local_placer/sequential/scenario.rs b/src/transform/place_and_route/local_placer/sequential/scenario.rs new file mode 100644 index 0000000..928d363 --- /dev/null +++ b/src/transform/place_and_route/local_placer/sequential/scenario.rs @@ -0,0 +1,57 @@ +use crate::world::block::BlockKind; +use crate::world::position::Position; +use crate::world::simulator::Simulator; +use crate::world::World3D; + +#[derive(Clone, Copy)] +pub(super) enum SequentialScenarioStep<'a, Input: Copy, Output: Copy> { + SetInput { input: Input, value: bool }, + ExpectOutputs(&'a [(Output, bool)]), +} + +pub(super) fn run_sequential_scenario<'a, Input, Output>( + sim: &mut Simulator, + steps: &[SequentialScenarioStep<'a, Input, Output>], + input_position: impl Fn(Input) -> Position, + output_value: impl Fn(&World3D, Output) -> Option, + max_cycles: usize, + max_events: usize, +) -> bool +where + Input: Copy, + Output: Copy, +{ + for step in steps { + match *step { + SequentialScenarioStep::SetInput { input, value } => { + if sim + .change_state_with_limits( + vec![(input_position(input), value)], + max_cycles, + max_events, + ) + .is_err() + { + return false; + } + } + SequentialScenarioStep::ExpectOutputs(expectations) => { + if expectations + .iter() + .any(|&(output, expected)| output_value(sim.world(), output) != Some(expected)) + { + return false; + } + } + } + } + + true +} + +pub(super) fn torch_output_value(world: &World3D, position: Position) -> Option { + match world[position].kind { + BlockKind::Torch { is_on } => Some(is_on), + _ => None, + } +}