369 lines
12 KiB
Rust
369 lines
12 KiB
Rust
// Copyright 2019 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#![feature(drain_filter)]
|
|
|
|
mod canvas;
|
|
mod channel_router;
|
|
mod circuit;
|
|
mod loader;
|
|
mod placer;
|
|
|
|
extern crate rayon;
|
|
|
|
use crate::canvas::*;
|
|
use crate::channel_router::*;
|
|
use crate::circuit::*;
|
|
use crate::loader::*;
|
|
use crate::placer::place_gates;
|
|
use clap::{App, Arg};
|
|
use core::convert::TryFrom;
|
|
use rayon::prelude::*;
|
|
use std::fs::File;
|
|
use std::io::Read;
|
|
|
|
fn resolve_gate_dependencies(
|
|
mut circuits: Vec<Circuit>,
|
|
output_ports: Vec<Port>,
|
|
) -> Vec<Vec<Circuit>> {
|
|
let mut gate_hierarchy = Vec::new();
|
|
let mut nets_available = Vec::new();
|
|
let mut required_nets = Vec::new();
|
|
|
|
while !circuits.is_empty() {
|
|
// Find circuits where all inputs are satisfied.
|
|
let mut placable_gates: Vec<Circuit> = circuits
|
|
.drain_filter(|c| {
|
|
c.inputs.iter().all(|input| match input.connection {
|
|
PortConnection::Constant(_) => true,
|
|
PortConnection::Net(net) => nets_available.contains(&net),
|
|
})
|
|
})
|
|
.collect();
|
|
|
|
if placable_gates.is_empty() {
|
|
panic!("Circular dependency detected");
|
|
}
|
|
|
|
// Sort gates by output net number.
|
|
placable_gates.sort_by(|a, b| a.outputs[0].cmp(&b.outputs[0]));
|
|
|
|
for g in placable_gates.iter() {
|
|
for o in g.outputs.iter() {
|
|
if let PortConnection::Net(net) = o.connection {
|
|
if !nets_available.contains(&net) {
|
|
nets_available.push(net);
|
|
}
|
|
}
|
|
}
|
|
for i in g.inputs.iter() {
|
|
if let PortConnection::Net(net) = i.connection {
|
|
if !required_nets.contains(&net) {
|
|
required_nets.push(net);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
gate_hierarchy.push(placable_gates);
|
|
}
|
|
|
|
gate_hierarchy.push(
|
|
output_ports
|
|
.iter()
|
|
.map(|&bit| Circuit::new_external_output_pin(bit))
|
|
.collect(),
|
|
);
|
|
|
|
let output_nets = output_ports
|
|
.iter()
|
|
.filter(|n| !n.connection.is_constant())
|
|
.map(|n| match n.connection {
|
|
PortConnection::Net(i) => i,
|
|
_ => unreachable!(),
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
for n in nets_available
|
|
.iter()
|
|
.filter(|v| !required_nets.contains(v) && !output_nets.contains(v))
|
|
{
|
|
panic!("[!] Net {} seems to be not used - bug?", n);
|
|
}
|
|
|
|
gate_hierarchy
|
|
}
|
|
|
|
fn parse_json(filepath: &str) -> std::io::Result<Vec<Vec<Circuit>>> {
|
|
let mut file = File::open(filepath)?;
|
|
let mut buf = String::new();
|
|
file.read_to_string(&mut buf)?;
|
|
|
|
let v: YosysJson = serde_json::from_str(&*buf)?;
|
|
if v.modules.len() != 1 {
|
|
panic!("Input file contained no or more than one module");
|
|
}
|
|
let m = &v.modules.values().next().unwrap();
|
|
|
|
// Convert cells to `Circuit`s.
|
|
let mut circuits: Vec<_> = m
|
|
.cells
|
|
.values()
|
|
.map(|v| Circuit::try_from(v).unwrap())
|
|
.collect();
|
|
|
|
// Add input connections.
|
|
for bit in m
|
|
.ports
|
|
.iter()
|
|
.map(|p| p.1)
|
|
.filter(|p| p.direction == YosysJsonPortDirection::Input)
|
|
.flat_map(|p| &p.bits)
|
|
{
|
|
circuits.push(Circuit::new_external_input_pin(Port::from(bit)));
|
|
}
|
|
|
|
let mut output_pins = m
|
|
.ports
|
|
.iter()
|
|
.map(|p| p.1)
|
|
.filter(|p| p.direction == YosysJsonPortDirection::Output)
|
|
.flat_map(|p| &p.bits)
|
|
.map(|bits| Port::from(&bits))
|
|
.collect::<Vec<_>>();
|
|
output_pins.sort();
|
|
|
|
// Let's keep the input layout consistent.
|
|
circuits.sort_by(|a, b| match (a.circuit_type, b.circuit_type) {
|
|
(CircuitType::INPUT, CircuitType::INPUT) => a.outputs[0].cmp(&b.outputs[0]),
|
|
(CircuitType::INPUT, _) => std::cmp::Ordering::Less,
|
|
(_, _) => a.inputs[0].cmp(&b.inputs[0]),
|
|
});
|
|
|
|
println!("[*] Calculating gate layout.");
|
|
Ok(resolve_gate_dependencies(circuits, output_pins))
|
|
}
|
|
|
|
fn main() -> std::io::Result<()> {
|
|
let parameters = App::new("Minetest HDL")
|
|
.version("0.1")
|
|
.author("Kevin Hamacher <hamacher@google.com>")
|
|
.about("Converts synthesized circuit (yosys json output) to a minetest schematic that can be placed in minetest")
|
|
.arg(
|
|
Arg::with_name("text")
|
|
.short("t")
|
|
.long("text")
|
|
.help("Print text overview on STDOUT"),
|
|
)
|
|
.arg(
|
|
Arg::with_name("write_lua")
|
|
.short("l")
|
|
.long("write_lua")
|
|
.help("Writes a lua blueprint")
|
|
.takes_value(true),
|
|
)
|
|
.arg(
|
|
Arg::with_name("write_mts")
|
|
.short("m")
|
|
.long("write_mts")
|
|
.help("Writes a MTS blueprint (binary format)")
|
|
.takes_value(true),
|
|
)
|
|
.arg(
|
|
Arg::with_name("INPUT")
|
|
.help("Sets the input file to use")
|
|
.required(true)
|
|
.index(1),
|
|
)
|
|
.get_matches();
|
|
|
|
let lua_filename = parameters.value_of("write_lua");
|
|
let mts_filename = parameters.value_of("write_mts");
|
|
|
|
let mut gate_hierarchy = parse_json(parameters.value_of("INPUT").unwrap())?;
|
|
|
|
println!("[*] Adding 'forwarding' gates to keep unused nets.");
|
|
{
|
|
// Starting with 2 here since we will always have all inputs at the 1st
|
|
// stage (0th = input bits for the whole circuitry), so we need to start
|
|
// checking that the 1st stage will 'reexport' the required bits.
|
|
for idx in (2..gate_hierarchy.len()).rev() {
|
|
let required_inputs: Vec<_> = gate_hierarchy[idx]
|
|
.iter()
|
|
.flat_map(|c| c.inputs.iter().map(|i| i.connection))
|
|
.filter(|v| !v.is_constant())
|
|
.map(|v| v.get_net().unwrap())
|
|
.collect();
|
|
|
|
let mut outputs_available: Vec<_> = gate_hierarchy[idx - 1]
|
|
.iter()
|
|
.flat_map(|c| c.outputs.iter().map(|o| o.connection))
|
|
.filter(|v| !v.is_constant())
|
|
.map(|v| v.get_net().unwrap())
|
|
.collect();
|
|
|
|
for ri in required_inputs {
|
|
if !outputs_available.contains(&ri) {
|
|
// The previous segment did not provide the required output,
|
|
// so add a dependency.
|
|
gate_hierarchy[idx - 1].push(Circuit::new_forwarding_pin(Port::new_unplaced(
|
|
PortConnection::Net(ri),
|
|
)));
|
|
outputs_available.push(ri);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
println!("[*] Performing channel routing.");
|
|
|
|
// Place the first circuit block.
|
|
let mut block_x_start = 0;
|
|
{
|
|
let mut gate_y = 0;
|
|
let mut max_w = 0;
|
|
for c in gate_hierarchy[0].iter_mut() {
|
|
c.place(Position2D(block_x_start, gate_y));
|
|
gate_y += c.height();
|
|
if c.width() > max_w {
|
|
max_w = c.width();
|
|
}
|
|
}
|
|
block_x_start += max_w;
|
|
}
|
|
|
|
// Go at least a little bit straight before starting the channel router.
|
|
block_x_start += 2;
|
|
|
|
// 1) Place
|
|
println!("[*] Placing gates");
|
|
for gategroup_idx in 0..gate_hierarchy.len() - 1 {
|
|
// Get the current input layout (left side of the channel).
|
|
let channel_layout =
|
|
determine_channel_layout(gate_hierarchy[gategroup_idx].iter(), IOType::Output);
|
|
println!(
|
|
" [+] Step {}/{} - {} inputs to {} gates",
|
|
gategroup_idx + 1,
|
|
gate_hierarchy.len() - 1,
|
|
channel_layout.len(),
|
|
gate_hierarchy[gategroup_idx + 1].len()
|
|
);
|
|
|
|
// Determine required channel layout (input pins of the next group).
|
|
place_gates(&channel_layout, &mut gate_hierarchy[gategroup_idx + 1]);
|
|
}
|
|
|
|
println!("[*] Routing");
|
|
let ops_per_step = (0..gate_hierarchy.len() - 1)
|
|
.into_par_iter()
|
|
.map(|gategroup_idx| {
|
|
let channel_layout =
|
|
determine_channel_layout(gate_hierarchy[gategroup_idx].iter(), IOType::Output);
|
|
// Determine required channel layout (input pins of the next group).
|
|
let desired_channel_layout =
|
|
determine_channel_layout(gate_hierarchy[gategroup_idx + 1].iter(), IOType::Input);
|
|
route_channel(&channel_layout, &desired_channel_layout)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let mut canvas = Canvas::new();
|
|
let mut place_constants_here = Vec::new();
|
|
println!("[*] Drawing to canvas");
|
|
for (gategroup_idx, ops) in ops_per_step.iter().enumerate() {
|
|
let channel_layout =
|
|
determine_channel_layout(gate_hierarchy[gategroup_idx].iter(), IOType::Output);
|
|
// Determine required channel layout (input pins of the next group).
|
|
let desired_channel_layout =
|
|
determine_channel_layout(gate_hierarchy[gategroup_idx + 1].iter(), IOType::Input);
|
|
|
|
// Let's draw our channels.
|
|
// 1 pixel initial wires
|
|
const WIRE_LENGTH_AFTER_GATE: usize = 1;
|
|
let mut x = block_x_start;
|
|
for xi in 0..WIRE_LENGTH_AFTER_GATE {
|
|
for (ly, cly) in channel_layout.iter().enumerate() {
|
|
if cly.contains_net() {
|
|
canvas.set(x as usize + xi, ly as usize, BlockType::WireH);
|
|
}
|
|
}
|
|
}
|
|
x += WIRE_LENGTH_AFTER_GATE as u32;
|
|
|
|
canvas.set_channel_wires(&ops, &mut x);
|
|
|
|
for c in gate_hierarchy[gategroup_idx + 1].iter_mut() {
|
|
c.reposition(x);
|
|
}
|
|
|
|
// Place constant inputs
|
|
for pos in desired_channel_layout
|
|
.iter()
|
|
.enumerate()
|
|
.filter(|(_, p)| p.is_constant_on())
|
|
.map(|(idx, _)| idx)
|
|
{
|
|
place_constants_here.push((x as usize, pos));
|
|
}
|
|
|
|
block_x_start = x + 3;
|
|
}
|
|
|
|
for gate_group in gate_hierarchy.iter() {
|
|
for g in gate_group.iter() {
|
|
let gate_pos = g
|
|
.position
|
|
.unwrap_or_else(|| panic!("Circuit {:#?} was not placed!", g));
|
|
canvas.set(
|
|
gate_pos.0 as usize,
|
|
gate_pos.1 as usize,
|
|
BlockType::Gate(g.circuit_type),
|
|
);
|
|
for i in &g.inputs {
|
|
let input_pos = i.position.expect("Input was not placed");
|
|
canvas.set(input_pos.0 as usize, input_pos.1 as usize, BlockType::Input);
|
|
}
|
|
for o in &g.outputs {
|
|
let output_pos = o.position.expect("Output was not placed");
|
|
canvas.set(
|
|
output_pos.0 as usize,
|
|
output_pos.1 as usize,
|
|
BlockType::Output,
|
|
);
|
|
}
|
|
g.draw(&mut canvas);
|
|
}
|
|
}
|
|
|
|
for (x, y) in place_constants_here.iter() {
|
|
canvas.set(*x, *y, BlockType::Constant);
|
|
}
|
|
|
|
println!("[*] Canvas dimensions: {:?}", canvas.dimensions());
|
|
if parameters.occurrences_of("text") > 0 {
|
|
println!("*** text overview ***");
|
|
canvas.draw();
|
|
}
|
|
if let Some(f) = lua_filename {
|
|
println!("[*] Generating lua schematic file");
|
|
canvas.generate_lua_schematic(f)?;
|
|
}
|
|
|
|
if let Some(f) = mts_filename {
|
|
println!("[*] Generating MTS schematic file");
|
|
canvas.serialize_to_mts(f)?
|
|
}
|
|
|
|
Ok(())
|
|
}
|