Initial commit

master
Kevin Hamacher 2019-08-27 16:40:06 +02:00
commit ffc1e47162
11 changed files with 2278 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
**/*.rs.bk

28
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,28 @@
# How to Contribute
We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.
## Contributor License Agreement
Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution;
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to <https://cla.developers.google.com/> to see
your current agreements on file or to sign a new one.
You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.
## Code reviews
All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.
## Community Guidelines
This project follows [Google's Open Source Community
Guidelines](https://opensource.google.com/conduct/).

15
Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "minetest_pnr"
version = "0.1.0"
authors = ["Kevin Hamacher <hamacher@google.com>"]
edition = "2018"
[dependencies]
bitmap = "3.1.3"
byteorder = "1"
clap = "2.33.0"
deflate = "0.7.19"
itertools = "0.8.0"
rayon = "1.0.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1", features = ["preserve_order"] }

202
LICENSE Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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
http://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.

199
README.md Normal file
View File

@ -0,0 +1,199 @@
# MinetestPnR
Simple 2D placer & router for [minetest] developed as part of a [Google CTF] challenge ([video writeup](https://www.youtube.com/watch?v=nI8Q1bqT8QU)).
This also means that the project is mostly aimed towards creating this challenge,
not towards being feature complete and being well engineered, so don't expect
optimal/minimal output.
It currently supports only basic gates (i.e. **no DFF / latches**), so I'm afraid you
can't just place your RISC-V CPU in minetest, feel free to send pull requests though ;).
Supported gates:
- AND
- NAND
- ANDNOT
- OR
- NOR
- ORNOT
- NOT
- XNOR
- XOR
The result will look similar to this (text version):
```Text
░──────────────────¬─────────────┐┌───┐ ┌───────┐ ┌─────┐ ┌──┐ ┌─┐
░────────┬─────────¬─────────┐ ││ ^──┘ ^──┘ ^─────»──┘ ^──┘ ^────▓
░────────╂──────┬──¬─────────╂───╂┘┌──┘ ┌───────┘ ┌─────┘ ┌──┘ ┌─┘
░────────╂──────╂┬─¬─────┬───╂───╂─╂──┐ │┌──────┐ │┌────┐ │┌─┐ │
░──────┬─╂──────╂╂─¬─────╂───╂───╂─╂┐ ^──┘│ ^──┘│ v─────¬──┘│ ^──┘
░──────╂─╂─┬────╂╂─»──┐ │ │ │ │├─┘ │┌─────┘ ┌╂────┘ ┌╂─┘
░──────╂─╂─╂┬───╂╂─¬──╂──╂───╂──┐└─╂╂─┐ ┌╂╂─────┐ ││┌───┐ ││
░──────╂┐└─╂╂───╂╂─»──╂──╂───╂──╂──┤│ ^──╂┘│ v──╂┘│ ^─────»──╂┘
░──────╂╂──╂╂───╂╂─»──╂──╂───╂──╂──╂╂─┘ │ │┌────┘ │ │┌──┘ ┌──┐ │
░──────╂╂──╂╂──┬╂╂─¬──╂──╂───╂┐┌╂──╂╂─┐ │┌╂╂────┐ │┌╂╂──┐ │ ^──┘
░──────╂╂──╂╂─┐└╂╂─»──╂┐ │ ││││ ││ v──┘│││ v──┘│││ ^──┘┌─┘
░────┬─╂╂──╂╂─╂─╂╂─¬──╂╂─╂───╂╂╂╂┐┌╂╂─┘ │││┌───┘ ┌╂╂╂──┘ │
░────╂─╂╂──╂╂┬╂─╂╂─¬──╂╂┐│ ││││││││ ││││┌──┐ ││││┌─┐ │
░────╂─╂╂┐ ├╂╂╂─╂╂─┐ ││││ ││││││││ │││││ ^──╂╂┘││ ^───┘
░────╂─╂╂╂┐││││ ││ v──╂╂╂╂───╂╂╂╂╂╂╂╂─»───╂┘│││┌─┘ ││┌╂╂─┘
░────╂┐││├╂╂╂╂╂─╂╂─┘ └╂╂╂───╂╂╂╂╂╂╂╂─┐ ┌╂─╂╂╂╂─┐ │││││
░───┐└╂╂╂╂╂╂╂╂╂─╂╂─»───╂╂╂─┐ ││││││││ v──╂┘ ││││ ^──╂┘│││
░───╂─╂╂╂╂╂╂╂╂╂─╂╂─»───╂╂╂─╂─╂╂┘└╂╂╂╂─┘ │ ┌╂╂╂╂─┘ │ │││
░──┐│ │└╂╂╂╂╂╂╂─╂╂─»───╂╂╂─╂┐└╂──╂╂╂╂─┐ │ │││││ │ │││
░──╂╂─╂─╂╂╂╂╂╂╂─╂╂─¬───╂╂╂─╂╂─╂──╂┘││ v──╂─╂╂╂╂╂─»──╂─╂┘│
│└─╂─╂╂╂╂╂╂╂─╂╂─┐ └╂╂─╂╂─╂──╂─╂╂─┘ │┌╂╂╂╂╂─┐ │ │ │
│ │ │││││││ ││ v────╂╂─╂╂─╂──╂─╂╂─»──╂╂╂┘│││ ^──┘ │ │
└──╂─╂╂╂╂╂╂╂─╂╂─┘ ││ ││ │ │ ││ │││┌╂╂╂─┘ │ │
│ └╂╂╂╂╂╂─╂╂─┐ ││ ││ │ │ ││ │││││││ │ │
│ ││││││ ││ v────╂╂─╂╂─╂──╂─╂╂─»──╂╂╂╂┘││ │ │
└──╂╂╂╂╂╂─╂╂─┘ ││ ││ │ │ ││ ││││ ││ │ │
││││││ ├╂─┐ ││ ││ │ │ ││ ││││ ││ │ │
││││││ ││ ^────╂╂─╂╂─╂──╂─╂╂─¬──┘│││ ││ │ │
│││││├─╂╂─┘ │└─╂╂─╂──╂─╂╂─┐ │││ ││ │ │
││└╂╂╂─╂╂─┐ │ ││ │ │ ││ v───╂╂╂─┘│ │ │
││ │││ ││ ^────╂─┐└╂─╂──╂─╂╂─┘ │││ │ │ │
└╂─╂╂╂─╂╂─┘ │ │ │ │ │ └╂─┐ │││ │ │ │
│ ├╂╂─╂╂─┐ │ │ │ │ │ │ v───╂╂╂──┘ │ │
│ │││ ││ ^────╂┐│ │ └──╂──╂─┘ │││ │ │
├─╂╂╂─╂╂─┘ └╂╂─╂────╂──╂─┐ │││ │ │
│ └╂╂─╂╂─┐ ││ │ │ │ v───╂┘│ │ │
│ ││ ││ v────┐││ └────╂──╂─┘ │ │ │ │
└──╂╂─╂╂─┘ │││ │ └─┐ │ │ │ │
││ └╂─┐ │││ │ v───┘ │ │ │
││ │ v──┐┌╂╂╂──────╂────┘ │ │ │
│└──╂─┘ │││││ └────┐ │ │ │
└───╂─»──╂┘│││ v─────┘ │ │
└─»──╂─╂╂╂───────────┘ │ │
│ ││└───────────┐ │ │
│ ││ v──────────¬────╂─┘
│ │└────────────┘ │
│ └─────────────┐ │
│ ^──────────»────┘
└───────────────┘
```
For ingame footage, look at the CTF writeup video mentioned above.
## What does it do?
MinetestPnR takes a synthesized circuit, places the components and routes them (= wires things together).
This means that it'll allow you to write your circuit in a HDL (e.g. `verilog`), synthesize it (e.g. using `yosys`)
and then use it inside your minetest world.
## How can I use it?
Putting your circuit in minetest takes three steps:
- Generate the minetest schematic file
- Place it in minetest
- Fix the mesecon parts. More on this below.
### Generating minetest schematic
- Synthesize your circuit and create a json file containing the basic blocks using [yosys].
Example command: `yosys -p 'synth; abc -g AND,OR,XOR,XNOR,ANDNOT,ORNOT; write_json schematic.json' schematic.v`
- Place & route the resulting `schematic.json` file, creating a MTS(minetest schematic) file using this project:
`cargo run --release -- ./schematic.json --write_mts schematic.mts`
### Placing MTS in minetest using worldedit
- Install [mesecons] + [worldedit]
- Create world in minetest (`type=single node` if you only want to have the circuit in the world)
- Remember to enable both mods
- Create `schems` directory in the world directory (`worlds/${worldname}/schems`)
- Generate MTS file using the project, place it in that folder
- Start minetest + load your map
- Set pos1 (`//1` / `//fixedpos set1 0 1 0`) where the schematic should be placed
- `//mtschemplace <name without mts>` to place the schematic
### Fixing mesecon wires / gates
- Fix mesecon wires + gates - those blocks introduce some internal state that is not created when
placed using worldedit. This can be done by adding a function to the mesecons code that will
set up that internal state. This can be done by adding this function:
```lua
local function fix_single_chunk(pos)
local found_nodes = minetest.find_nodes_in_area(pos, vector.add(pos, { x = 16, y = 16, z = 16 }), {
"mesecons_gates:diode_off",
"mesecons_gates:not_off",
"mesecons_gates:and_off",
"mesecons_gates:nand_off",
"mesecons_gates:xor_off",
"mesecons_gates:nor_off",
"mesecons_gates:or_off",
"mesecons:mesecons_off",
"mesecons:wire_00000000_off",
})
local cnt = 0
for i=0, #found_nodes do
if (found_nodes[i] ~= nil) then
mesecon.on_placenode(found_nodes[i], minetest.get_node(found_nodes[i]))
cnt = cnt + 1
end
end
return cnt
end
-- Adapted from:
-- https://rubenwardy.com/minetest_modding_book/en/map/environment.html
local function emerge_callback(pos, action,
num_calls_remaining, context)
-- On first call, record number of blocks
if not context.total_blocks then
context.total_blocks = num_calls_remaining + 1
context.loaded_blocks = 0
context.nodes_fixed = 0
end
context.loaded_blocks = context.loaded_blocks + 1
local perc = 100 * context.loaded_blocks / context.total_blocks
local msg = string.format("Handling block %d/%d (%.2f%%)",
context.loaded_blocks, context.total_blocks, perc)
context.nodes_fixed = context.nodes_fixed + fix_single_chunk(pos)
minetest.chat_send_all(msg)
-- Are we done yet?
if context.total_blocks == context.loaded_blocks then
minetest.chat_send_all("Done, " .. context.nodes_fixed .. " nodes fixed")
end
end
minetest.register_chatcommand("fix_gates", {
params = "",
description = "Fix gates by triggering I/O reevaluation",
func = function(name, param)
local context = {}
minetest.emerge_area({x=0, y=2, z=0}, {x=2000, y=2, z=2000}, emerge_callback, context)
return true, "Emerge started"
end,
})
```
e.g. at the end of `mesecons_gates/init.lua`.
Then load up the map and send `/fix_gates` in chat.
This sometimes does not seem to work reliably, so you might need to use it multiple
times when standing at different locations to cover different chunks.
**Note that the snippet contains the area where the wires should be fixed** (`minetest.emerge_area({x=0, y=2, z=0}, {x=2000, y=2, z=2000}`)
so you might want to adjust this.
## Caveats
### My circuit is too large!
The application uses a 8k * 8k tiles canvas where the circuit gets placed onto.
The maximum size that the MTS format supports is 64k * 64k, so depending on how
big your circuit is you can place your bigger circuit by changing the canvas
size in `src/canvas.rs`:
```rust
const CANVAS_MAX_W: usize = 8 * 1024;
const CANVAS_MAX_H: usize = 8 * 1024;
```
Why not using 64k * 64k by default? 64k * 64k = 4G - that's a lot of ram ;).
**NOTE**: The minetest source code is not consistent whether the dimensions are
stored as `u16` or `i16`, so the code might need additional patches in
`serialize_to_mts` if your circuit is bigger than 32k * 32k.
## Disclaimer
This is not an officially supported Google product.
[minetest]: http://www.minetest.net/
[worldedit]: https://github.com/Uberi/Minetest-WorldEdit
[mesecons]: http://mesecons.net/
[yosys]: http://www.clifford.at/yosys/
[Google CTF]: https://capturetheflag.withgoogle.com

515
src/canvas.rs Normal file
View File

@ -0,0 +1,515 @@
// 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.
use crate::channel_router::{ChannelOp, ChannelSubState, WireConnection};
use crate::CircuitType;
use byteorder::{BigEndian, WriteBytesExt};
use deflate::write::ZlibEncoder;
use deflate::Compression;
use std::fs::File;
use std::io::{BufWriter, Write};
const BLOCK_IDS: &[&str; 16] = &[
"air",
"stone",
"mesecons_lamp:lamp_off",
"mesecons_walllever:wall_lever_off",
// Gates
"mesecons_gates:and_off",
"mesecons_gates:nand_off",
"mesecons_gates:nor_off",
"mesecons_gates:not_off",
"mesecons_gates:or_off",
"mesecons_gates:xor_off",
// Regular wire
"mesecons:mesecon_off",
// Insulated wires
"mesecons_insulated:insulated_off",
"mesecons_extrawires:corner_off",
"mesecons_extrawires:tjunction_off",
"mesecons_extrawires:crossover_off",
// For constant inputs
"mesecons_torch:mesecon_torch_off",
];
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum CornerOrientation {
LeftUp,
LeftDown,
DownRight,
UpRight,
}
impl CornerOrientation {
pub fn get_param2(self) -> u8 {
use self::CornerOrientation::*;
match self {
LeftUp => 0,
LeftDown => 3,
DownRight => 2,
UpRight => 1,
}
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum TRotation {
LeftRightDown,
LeftRightUp,
RightUpDown,
LeftUpDown,
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum BlockType {
Air,
WireH,
WireV,
WireCrossing,
WireT(TRotation),
WireCorner(CornerOrientation),
WireStar,
Gate(CircuitType),
Input,
Output,
Constant,
}
impl Default for BlockType {
fn default() -> Self {
BlockType::Air
}
}
impl BlockType {
pub fn c(self) -> char {
use self::BlockType::*;
match self {
Air => ' ',
WireH => '─',
WireV => '│',
WireCrossing => '╂',
WireCorner(CornerOrientation::DownRight) => '┌',
WireCorner(CornerOrientation::LeftUp) => '┘',
WireCorner(CornerOrientation::LeftDown) => '┐',
WireCorner(CornerOrientation::UpRight) => '└',
WireT(TRotation::LeftRightDown) => '┬',
WireT(TRotation::LeftRightUp) => '┴',
WireT(TRotation::RightUpDown) => '├',
WireT(TRotation::LeftUpDown) => '┤',
WireStar => '┼',
Gate(CircuitType::INPUT) => '░',
Gate(CircuitType::FORWARD) => '»',
Gate(CircuitType::NOT) => '¬',
Gate(CircuitType::OR) => 'v',
Gate(CircuitType::AND) => '^',
Gate(_) => '▓',
Input => '─',
Output => '─',
Constant => 'o',
}
}
pub fn minetest_type(self) -> &'static str {
use self::BlockType::*;
match self {
Air => "air",
WireH => "mesecons_insulated:insulated_off",
WireV => "mesecons_insulated:insulated_off",
WireCrossing => "mesecons_extrawires:crossover_off",
WireCorner(_) => "mesecons_extrawires:corner_off",
WireT(_) => "mesecons_extrawires:tjunction_off",
WireStar => "mesecons:mesecon_off",
Gate(gate) => gate.mesecon_id(),
Input => "mesecons_insulated:insulated_off",
Output => "mesecons_insulated:insulated_off",
Constant => "mesecons_torch:mesecon_torch_off",
}
}
pub fn get_param2(self) -> u8 {
use self::BlockType::*;
match self {
WireV => 0,
WireH => 3,
WireCorner(co) => co.get_param2(),
WireT(TRotation::LeftUpDown) => 0,
WireT(TRotation::LeftRightUp) => 1,
WireT(TRotation::RightUpDown) => 2,
WireT(TRotation::LeftRightDown) => 3,
// gate 'input / output' pseudo types (are actually wires)
Input => 3,
Output => 3,
Gate(CircuitType::OUTPUT) => 0,
Gate(CircuitType::INPUT) => 0,
Gate(_) => 3,
// Can be rotated in any way.
Air => 0,
WireCrossing => 0,
WireStar => 0,
Constant => 0,
}
}
}
// Note that they do not need to be bigger than 64 * 1024 as that's a limitation
// of minetest.
const CANVAS_MAX_W: usize = 8 * 1024;
const CANVAS_MAX_H: usize = 8 * 1024;
pub struct Canvas {
data: Box<[[BlockType; CANVAS_MAX_H]]>,
width: usize,
height: usize,
}
impl Canvas {
pub fn new() -> Self {
Self {
data: vec![[BlockType::Air; CANVAS_MAX_H]; CANVAS_MAX_W].into_boxed_slice(),
height: 0,
width: 0,
}
}
pub fn set(&mut self, x: usize, y: usize, c: BlockType) {
if x >= std::u16::MAX as _ || y >= std::u16::MAX as _ {
panic!("Sorry, circuit too large for minetest (MTS format limitation)");
}
if x >= CANVAS_MAX_W || y >= CANVAS_MAX_H {
panic!("Sorry, circuit too large for the internal canvas, consider increasing CANVAS_MAX_{{W/H}}");
}
if x > self.width {
self.width = x;
}
if y > self.height {
self.height = y;
}
self.data[x][y] = c;
}
pub fn get(&self, x: usize, y: usize) -> BlockType {
if x >= std::u16::MAX as _ || y >= std::u16::MAX as _ {
panic!("Sorry, circuit too large for minetest (MTS format limitation)");
}
if x >= CANVAS_MAX_W || y >= CANVAS_MAX_H {
panic!("Sorry, circuit too large for the internal canvas, consider increasing CANVAS_MAX_{{W/H}}");
}
self.data[x][y]
}
pub fn dimensions(&self) -> (usize, usize) {
(self.width + 1, self.height + 1)
}
pub fn draw(&self) {
let d = self.dimensions();
let stdout = std::io::stdout();
let mut lock = stdout.lock();
for line in 0..d.1 {
for c in 0..d.0 {
let c = &self.get(c, line).c();
let mut buf = [0u8; 4];
c.encode_utf8(&mut buf);
let buf = &buf[0..c.len_utf8()];
lock.write_all(&buf).unwrap();
}
lock.write_all(b"\n").unwrap();
}
}
pub fn set_channel_wires(&mut self, ops: &[ChannelSubState], x: &mut u32) {
// Draw actual channel.
// Defines how many additional blocks of space should be next to the wires.
const CHANNEL_WIRE_PADDING: usize = 0;
for state in ops.iter() {
for xi in 0..(1 + CHANNEL_WIRE_PADDING) {
// Go through the current channel state.
for channel_idx in 0..state.occupancy_map.len() {
if state.occupancy_map.get(channel_idx).unwrap() != 0 {
// Channel is occupied, draw a wire (or crossing).
let x = *x as usize;
if self.get(x + xi, channel_idx) == BlockType::WireV {
self.set(x + xi, channel_idx, BlockType::WireCrossing);
} else {
self.set(x + xi, channel_idx, BlockType::WireH);
}
}
}
}
for op in state.wires.iter() {
let WireConnection {
from: source,
to: mut destination,
mode: op,
} = op.clone();
if op == ChannelOp::Copy {
destination.push(source);
}
// Connect all destination pins of the same net if there is more than one.
let mi = *destination.iter().min().unwrap();
let ma = *destination.iter().max().unwrap();
if mi != ma {
for y in mi..=ma {
if y == mi {
self.set(
*x as _,
y as _,
BlockType::WireCorner(CornerOrientation::DownRight),
);
} else if y == ma {
self.set(
*x as _,
y as _,
BlockType::WireCorner(CornerOrientation::UpRight),
);
} else if destination.contains(&y) {
self.set(*x as _, y as _, BlockType::WireT(TRotation::RightUpDown));
} else if self.get(*x as _, y as _) == BlockType::WireH {
self.set(*x as _, y as _, BlockType::WireCrossing);
} else if self.get(*x as _, y as _) == BlockType::Air {
self.set(*x as _, y as _, BlockType::WireV);
}
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
enum SourcePosition {
Above,
Below,
}
let (start, end, spos) = if source < mi {
(source, mi, SourcePosition::Above)
} else if source > ma {
(ma, source, SourcePosition::Below)
} else {
if destination.contains(&source) {
if source == mi {
self.set(*x as _, source, BlockType::WireT(TRotation::LeftRightDown));
} else if source == ma {
self.set(*x as _, source, BlockType::WireT(TRotation::LeftRightUp));
} else {
self.set(*x as _, source, BlockType::WireStar);
}
} else {
self.set(*x as _, source, BlockType::WireT(TRotation::LeftUpDown));
}
continue;
};
// Connect input to ranges.
for y in start..=end {
if y == start || y == end {
let x = *x as usize;
let y = y as usize;
if y == source && op == ChannelOp::Copy {
continue;
}
let block_type = match (y == source, spos, self.get(x, y)) {
// @ Source - should be empty.
(true, SourcePosition::Above, BlockType::Air) => {
BlockType::WireCorner(CornerOrientation::LeftDown)
}
(true, SourcePosition::Below, BlockType::Air) => {
BlockType::WireCorner(CornerOrientation::LeftUp)
}
// Destination, should not be empty.
// If we're coming from above, we either have already the correct connection (single pin)
(
false,
SourcePosition::Above,
BlockType::WireCorner(CornerOrientation::DownRight),
) => BlockType::WireT(TRotation::RightUpDown),
(
false,
SourcePosition::Below,
BlockType::WireCorner(CornerOrientation::UpRight),
) => BlockType::WireT(TRotation::RightUpDown),
(false, SourcePosition::Below, BlockType::WireH) => {
BlockType::WireCorner(CornerOrientation::DownRight)
}
(false, SourcePosition::Above, BlockType::WireH) => {
BlockType::WireCorner(CornerOrientation::UpRight)
}
(is_start, pos, prev) => {
println!(
"Unexpected block type {:?} is_start={} pos={:?} - {}:{}",
prev, is_start, pos, x, y
);
BlockType::Constant
}
};
self.set(x as _, y as _, block_type);
} else if self.get(*x as _, y as _) == BlockType::Air {
self.set(*x as _, y as _, BlockType::WireV);
} else if self.get(*x as _, y as _) == BlockType::WireH {
self.set(*x as _, y as _, BlockType::WireCrossing);
}
}
if op == ChannelOp::Copy {
self.set(
*x as _,
source as usize,
BlockType::WireT(match spos {
SourcePosition::Below => TRotation::LeftRightUp,
SourcePosition::Above => TRotation::LeftRightDown,
}),
);
}
}
*x += 1 + CHANNEL_WIRE_PADDING as u32;
}
}
pub fn generate_lua_schematic(&self, fname: &str) -> std::io::Result<()> {
let d = self.dimensions();
let mut file = File::create(fname)?;
file.write_all("schematic = {\n".as_bytes())?;
file.write_all(format!("\tsize = {{x={}, y=2, z={}}},\n", d.1, d.0).as_bytes())?;
file.write_all("\tdata = {\n".as_bytes())?;
for x in 0..d.0 {
for z in 0..2 {
for y in 0..d.1 {
file.write_all(
if z == 0 {
"\t\t{name=\"stone\"},\n".to_string()
} else {
format!(
"\t\t{{name=\"{}\", param2=param2}},\n",
self.get(x, y).minetest_type()
)
}
.as_bytes(),
)?;
}
}
}
file.write_all("\t}\n".as_bytes())?;
file.write_all("}\n".as_bytes())?;
Ok(())
}
pub fn serialize_to_mts(&self, fname: &str) -> std::io::Result<()> {
// Map size.
let d = self.dimensions();
// The minetest source is not consistent when it comes to the type of
// this field (i16 vs u16), so picking the conservative option here.
if d.1 > std::i16::MAX as _ || d.0 >= std::i16::MAX as _ {
panic!("Schematic too big to export to a mts :/");
}
let mut file = File::create(fname)?;
file.write_all(b"MTSM")?; // MTSCHEM_FILE_SIGNATURE
file.write_u16::<BigEndian>(1)?; // Version 1
file.write_i16::<BigEndian>(d.1 as i16)?;
file.write_i16::<BigEndian>(2)?;
file.write_i16::<BigEndian>(d.0 as i16)?;
// No need to do the prob table as we're totally old.
// Write # node names.
file.write_u16::<BigEndian>(BLOCK_IDS.len() as u16)?;
let serialize_string = |f: &mut File, s: &str| -> std::io::Result<()> {
if s.len() > std::u16::MAX as usize {
panic!("String too large to serialize.");
}
let s = s.as_bytes();
f.write_u16::<BigEndian>(s.len() as u16)?;
f.write_all(s)?;
Ok(())
};
for b in BLOCK_IDS.iter() {
serialize_string(&mut file, b)?;
}
let mut encoder = BufWriter::new(ZlibEncoder::new(file, Compression::Best));
// Generate reverse lookup table for block ids.
let mut block_lookup_table: std::collections::HashMap<&'static str, usize> =
std::collections::HashMap::new();
for (idx, val) in BLOCK_IDS.iter().enumerate() {
block_lookup_table.insert(val, idx);
}
println!(" [+] Writing node types");
// Write node types.
for x in 0..d.0 {
for z in 0..2 {
for y in 0..d.1 {
let t = if z == 0 {
"stone"
} else {
self.get(x, y).minetest_type()
};
encoder.write_u16::<BigEndian>(
*block_lookup_table
.get(t)
.unwrap_or_else(|| panic!("Block type {:?} not found?", t))
as u16,
)?;
}
}
}
println!(" [+] Writing param1");
// Write param1
for _ in 0..2 {
for _ in 0..d.0 {
for _ in 0..d.1 {
encoder.write_u8(0)?;
}
}
}
println!(" [+] Writing param2");
// Write param2
for x in 0..d.0 {
for z in 0..2 {
for y in 0..d.1 {
encoder.write_u8(if z == 0 {
0
} else {
self.get(x, y).get_param2()
})?;
}
}
}
Ok(())
}
}

350
src/channel_router.rs Normal file
View File

@ -0,0 +1,350 @@
// 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.
use bitmap;
use std::cmp;
use std::collections::HashMap;
struct Ranges {
ranges: Vec<std::ops::Range<usize>>,
}
impl Ranges {
fn new() -> Self {
Ranges { ranges: Vec::new() }
}
fn add(&mut self, start: usize, end: usize) {
let (start, end) = (cmp::min(start, end), cmp::max(start, end) + 1);
self.ranges.push(std::ops::Range { start, end });
}
fn contains(&self, start: usize, end: usize) -> bool {
let (start, end) = (cmp::min(start, end), cmp::max(start, end));
(start..=end).any(|v| self.ranges.iter().any(|r| r.contains(&v)))
}
fn contains_range(&self, range: &std::ops::Range<usize>) -> bool {
self.contains(range.start, range.end)
}
fn range_sum(&self) -> usize {
self.ranges.iter().map(|r| r.end - r.start).sum()
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum ChannelState {
Free,
// Occupied means no connection. This is the same as a constant false.
Occupied,
// Constant true.
Constant,
Net(usize),
}
pub type ChannelLayout = [ChannelState];
impl ChannelState {
pub fn is_free(&self) -> bool {
self == &ChannelState::Free
}
pub fn contains_net(&self) -> bool {
match self {
ChannelState::Net(_) => true,
_ => false,
}
}
pub fn is_constant_on(&self) -> bool {
match self {
ChannelState::Constant => true,
_ => false,
}
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum ChannelOp {
Move,
Copy,
}
#[derive(Debug, Clone)]
pub struct WireConnection {
pub from: usize,
pub to: Vec<usize>,
pub mode: ChannelOp,
}
#[derive(Debug)]
pub struct ChannelSubState {
pub wires: Vec<WireConnection>,
pub occupancy_map: bitmap::Bitmap<Vec<usize>, bitmap::OneBit>,
}
#[derive(Debug)]
struct Task {
net: usize,
from: usize,
to: Vec<usize>,
}
impl Task {
fn channel_range_required(&self) -> std::ops::Range<usize> {
let from = [self.from];
let min = self.to.iter().chain(&from).min().unwrap();
let max = self.to.iter().chain(&from).max().unwrap();
std::ops::Range {
start: *min,
end: max + 1,
}
}
fn channel_width_required(&self) -> usize {
let r = self.channel_range_required();
r.end - r.start
}
fn occupied_target_pins(&self, layout: &ChannelLayout) -> Vec<usize> {
let mut occupied = Vec::new();
for &idx in &self.to {
if layout[idx].contains_net() && layout[idx] != ChannelState::Net(self.net) {
occupied.push(idx);
}
}
occupied
}
// Returns how 'good' a new 'from' position is for this task (when evicting)
// so that we can prefer nice spots.
fn eviction_cost(&self, new_pos: usize) -> usize {
let min = self.to.iter().min().unwrap();
let max = self.to.iter().max().unwrap();
let dist = (self.from as isize - new_pos as isize).abs() as usize;
if new_pos > *max {
2 * (new_pos - *max) + dist
} else if new_pos < *min {
2 * (*min - new_pos) + dist
} else {
dist
}
}
}
#[derive(Default)]
struct RouteTasks {
// source idx -> vec<target idx>
tasks: HashMap<usize, Vec<usize>>,
}
impl RouteTasks {
fn add(&mut self, from: usize, to: usize) {
if let Some(k) = self.tasks.get_mut(&from) {
k.push(to);
} else {
self.tasks.insert(from, vec![to]);
}
}
fn into_tasks(mut self, src: &ChannelLayout) -> Vec<Task> {
self.tasks
.drain()
.map(|(k, v)| {
let net = match src[k] {
ChannelState::Net(i) => i,
_ => unreachable!(),
};
Task {
net,
from: k,
to: v,
}
})
.collect::<Vec<_>>()
}
}
pub fn route_channel(start: &ChannelLayout, end: &ChannelLayout) -> Vec<ChannelSubState> {
let mut state = start.to_owned();
// Expand the state to be at least end.len() wide.
while state.len() < end.len() {
state.push(ChannelState::Free);
}
let mut tasks = RouteTasks::default();
for end_idx in 0..end.len() {
if !end[end_idx].contains_net() || end[end_idx] == state[end_idx] {
continue;
}
let state_idx = state
.iter()
.position(|v| v == &end[end_idx])
.unwrap_or_else(|| panic!("Required field '{:?}' not found", end[end_idx]));
tasks.add(state_idx, end_idx);
}
let mut tasks = tasks.into_tasks(&state);
// Order by how much of the channel this task occupies.
tasks.sort_by(|a, b| a.channel_width_required().cmp(&b.channel_width_required()));
let mut steps: Vec<ChannelSubState> = Vec::new();
loop {
// Ranges of the channel that is currently occupied.
let mut ranges = Ranges::new();
// Instruction on how to connect pins in the current part of the channel.
let mut wires = Vec::new();
// To detect if we were unable to do anything due to blocked pins.
let old_task_len = tasks.len();
tasks = tasks
.drain(0..tasks.len())
.filter(|task| {
// Speed things up by only 'enforcing' 50% channel utilization.
if ranges.range_sum() > (cmp::max(state.len(), end.len()) / 2) {
return true;
}
// Do we have the required part of the channel available?
if ranges.contains_range(&task.channel_range_required()) {
return true;
}
let blocking_pins = task.occupied_target_pins(&state);
if blocking_pins.is_empty() {
// Targets are free, directly move (or copy) it there.
let keep = if task.from >= end.len() || state[task.from] != end[task.from] {
state[task.from] = ChannelState::Free;
false
} else {
true
};
wires.push(WireConnection {
from: task.from,
to: task.to.clone(),
mode: if keep {
ChannelOp::Copy
} else {
ChannelOp::Move
},
});
let r = task.channel_range_required();
// -1 here since .add() + channel_range_required() will do +1.
ranges.add(r.start, r.end - 1);
for &to in &task.to {
state[to] = ChannelState::Net(task.net);
}
// We successfully handled this one.
return false;
}
true
})
.collect::<Vec<_>>();
// We were unable to handle any tasks -> we need to evict some channels.
if old_task_len == tasks.len() {
// Find available positions where we can evict to.
let mut free_positions = state
.iter()
.enumerate()
.filter(|(_, v)| !v.contains_net())
.map(|(k, _)| k)
.filter(|&k| k >= end.len() || !end[k].contains_net())
.collect::<Vec<_>>();
if free_positions.is_empty() {
println!("[!] No free positions found, expanding channel");
// Make sure that we have some room, scaling with the number of
// remaining tasks as a random tradeoff.
for _ in 0..(tasks.len() / 10 + 1) {
state.push(ChannelState::Free);
free_positions.push(state.len() - 1);
}
}
for task_idx in 0..tasks.len() {
let blocking_pins = tasks[task_idx].occupied_target_pins(&state);
for to_evict in blocking_pins {
// Find corresponding task.
let task_idx_to_evict = tasks
.iter()
.position(|t| t.from == to_evict)
.unwrap_or_else(|| panic!("Could not find task blocking {}", to_evict));
// Find a good place for this task to evict to.
free_positions.sort_by(|&a, &b| {
// Comparing in the opposite order on purpose here so
// that we can use pop() later.
tasks[task_idx_to_evict]
.eviction_cost(b)
.cmp(&tasks[task_idx_to_evict].eviction_cost(a))
});
let from = tasks[task_idx_to_evict].from;
let new_pos = *free_positions.last().unwrap();
// Check whether the space is actually available.
let req_range = std::ops::Range {
start: cmp::min(from, new_pos),
end: cmp::max(from, new_pos) + 1,
};
if !ranges.contains_range(&req_range) {
free_positions.pop();
ranges.add(from, new_pos);
wires.push(WireConnection {
from,
to: vec![new_pos],
mode: ChannelOp::Move,
});
tasks[task_idx_to_evict].from = new_pos;
state[new_pos] = ChannelState::Net(tasks[task_idx_to_evict].net);
state[to_evict] = ChannelState::Free;
}
}
}
}
let mut bitmap =
bitmap::Bitmap::from_storage(state.len(), (), vec![0; (state.len() + 63) / 64])
.unwrap();
for idx in state
.iter()
.enumerate()
.filter(|(_, v)| v.contains_net())
.map(|(k, _)| k)
{
bitmap.set(idx, 1);
}
steps.push(ChannelSubState {
wires,
occupancy_map: bitmap,
});
if tasks.is_empty() {
return steps;
}
}
}

428
src/circuit.rs Normal file
View File

@ -0,0 +1,428 @@
// 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.
extern crate itertools;
use itertools::Itertools;
use crate::canvas::{BlockType, Canvas, CornerOrientation};
use crate::channel_router::{ChannelLayout, ChannelState};
use crate::loader::{YosysJsonCell, YosysJsonPortDirection};
use serde_json::Value;
use std::convert::TryFrom;
#[derive(PartialEq, Debug)]
pub enum IOType {
Input,
Output,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Position2D(pub u32, pub u32);
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum PortConnection {
Net(usize),
Constant(bool),
}
impl PortConnection {
pub fn is_constant(&self) -> bool {
match self {
PortConnection::Constant(_) => true,
_ => false,
}
}
pub fn get_net(&self) -> Option<usize> {
match self {
PortConnection::Net(n) => Some(*n),
_ => None,
}
}
}
impl Into<ChannelState> for PortConnection {
fn into(self) -> ChannelState {
match self {
PortConnection::Net(n) => ChannelState::Net(n),
PortConnection::Constant(false) => ChannelState::Occupied,
PortConnection::Constant(true) => ChannelState::Constant,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq)]
pub struct Port {
pub connection: PortConnection,
pub position: Option<Position2D>,
}
impl Port {
pub fn new_unplaced(connection: PortConnection) -> Self {
Self {
connection,
position: None,
}
}
pub fn from(s: &Value) -> Self {
let connection = match s {
Value::Number(n) => PortConnection::Net(n.as_u64().unwrap() as usize),
Value::String(ref s) => match s.as_ref() {
"0" | "x" | "y" => PortConnection::Constant(false),
"1" => PortConnection::Constant(true),
_ => panic!("Unknown constant value '{}'", s),
},
_ => unreachable!(),
};
Self {
connection,
position: None,
}
}
}
impl std::cmp::Ord for Port {
fn cmp(&self, other: &Port) -> std::cmp::Ordering {
use PortConnection::*;
match (self.connection, other.connection) {
(Net(a), Net(b)) => a.cmp(&b),
(_, _) => std::cmp::Ordering::Equal,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum CircuitType {
INPUT,
OUTPUT,
FORWARD,
AND,
NAND,
ANDNOT,
OR,
NOR,
ORNOT,
NOT,
XNOR,
XOR,
}
impl CircuitType {
pub fn from_verilog(s: &str) -> Self {
match s {
"$_ANDNOT_" => CircuitType::ANDNOT, // A & (~B)
"$_NAND_" => CircuitType::NAND, // and but inverted
"$_NOR_" => CircuitType::NOR, // or but inverted
"$_OR_" => CircuitType::OR,
"$_AND_" => CircuitType::AND,
"$_ORNOT_" => CircuitType::ORNOT, // A | (~B)
"$_XNOR_" => CircuitType::XNOR, // ~(XOR)
"$_XOR_" => CircuitType::XOR, // XOR
"$_NOT_" => CircuitType::NOT,
_ => panic!(
"Unsupported gate type '{}', make sure to run an abc pass",
s
),
}
}
pub fn mesecon_id(self) -> &'static str {
use self::CircuitType::*;
match self {
INPUT => "mesecons_walllever:wall_lever_off",
OUTPUT => "mesecons_lamp:lamp_off",
FORWARD => "mesecons_insulated:insulated_off",
OR => "mesecons_gates:or_off",
XOR => "mesecons_gates:xor_off",
AND => "mesecons_gates:and_off",
NAND => "mesecons_gates:nand_off",
NOR => "mesecons_gates:nor_off",
NOT => "mesecons_gates:not_off",
_ => panic!("{:?} not a mesecon thing", &self),
}
}
}
pub trait CircuitT {
fn get_type(&self) -> CircuitType;
fn width(&self) -> u32 {
use self::CircuitType::*;
match self.get_type() {
INPUT | NOT | OUTPUT | FORWARD => 1,
AND | OR | XOR | NAND | ANDNOT | NOR | ORNOT | XNOR => 2,
}
}
fn height(&self) -> u32 {
use self::CircuitType::*;
match self.get_type() {
INPUT | NOT | OUTPUT | FORWARD => 1,
AND | OR | XOR | NAND | ANDNOT | NOR | ORNOT | XNOR => {
assert!(self.inputs().len() == 2 || self.inputs().len() == 1);
3
}
}
}
fn inputs(&self) -> &Vec<Port>;
fn outputs(&self) -> &Vec<Port>;
fn input_positions(&self) -> Vec<(Position2D, Port)>;
fn reposition(&mut self, px: u32);
fn draw(&self, canvas: &mut Canvas);
fn place(&mut self, position: Position2D);
}
#[derive(Debug)]
pub struct Circuit {
pub circuit_type: CircuitType,
pub inputs: Vec<Port>,
pub outputs: Vec<Port>,
pub position: Option<Position2D>,
}
impl Circuit {
pub fn new_external_input_pin(net: Port) -> Self {
Self {
circuit_type: CircuitType::INPUT,
inputs: Vec::new(),
outputs: vec![net],
position: None,
}
}
pub fn new_external_output_pin(net: Port) -> Self {
Self {
circuit_type: CircuitType::OUTPUT,
inputs: vec![net],
outputs: Vec::new(),
position: None,
}
}
pub fn new_forwarding_pin(net: Port) -> Self {
Self {
circuit_type: CircuitType::FORWARD,
inputs: vec![net],
outputs: vec![net],
position: None,
}
}
pub fn width(&self) -> u32 {
use self::CircuitType::*;
match self.circuit_type {
INPUT | NOT | OUTPUT | FORWARD => 1,
AND | OR | XOR | NAND | ANDNOT | NOR | ORNOT | XNOR => 2,
}
}
pub fn height(&self) -> u32 {
use self::CircuitType::*;
match self.circuit_type {
INPUT | NOT | OUTPUT | FORWARD => 1,
AND | OR | XOR | NAND | ANDNOT | NOR | ORNOT | XNOR => {
assert!(self.inputs.len() == 2 || self.inputs.len() == 1);
3
}
}
}
pub fn can_swap_inputs(&self) -> bool {
use self::CircuitType::*;
match self.circuit_type {
INPUT | NOT | OUTPUT | FORWARD | ANDNOT | ORNOT => false,
AND | OR | XOR | NAND | NOR | XNOR => {
assert!(self.inputs.len() == 2);
true
}
}
}
pub fn swap_inputs(&mut self) {
assert!(self.can_swap_inputs());
self.inputs.swap(0, 1);
}
pub fn reposition(&mut self, px: u32) {
assert!(self.position.is_some());
if let Some(ref mut p) = self.position {
p.0 = px + 1;
}
for i in self.inputs.iter_mut() {
if let Some(ref mut p) = i.position {
p.0 = px;
}
}
let w = self.width();
for i in self.outputs.iter_mut() {
if let Some(ref mut p) = i.position {
p.0 = px + w + 1;
}
}
}
pub fn draw(&self, canvas: &mut Canvas) {
if self.circuit_type == CircuitType::INPUT
|| self.circuit_type == CircuitType::NOT
|| self.circuit_type == CircuitType::OUTPUT
|| self.circuit_type == CircuitType::FORWARD
{
// Nothing to do.
} else {
// We need to do the input/output wiring.
let p = self.position.unwrap();
let w = self.width();
// Input
canvas.set(
p.0 as usize,
p.1 as usize - 1,
BlockType::WireCorner(CornerOrientation::LeftDown),
);
canvas.set(
p.0 as usize,
p.1 as usize + 1,
BlockType::WireCorner(CornerOrientation::LeftUp),
);
// Output:
canvas.set(
p.0 as usize + w as usize - 1,
p.1 as usize,
BlockType::WireH,
);
// XNOR / ORNOT / ANDNOT are not mesecon things and need to be monkeypatched.
let invert_second = match self.circuit_type {
CircuitType::XNOR => {
canvas.set(
p.0 as usize,
p.1 as usize,
BlockType::Gate(CircuitType::XOR),
);
canvas.set(
p.0 as usize + w as usize - 1,
p.1 as usize,
BlockType::Gate(CircuitType::NOT),
);
false
}
CircuitType::ORNOT => {
canvas.set(p.0 as usize, p.1 as usize, BlockType::Gate(CircuitType::OR));
true
}
CircuitType::ANDNOT => {
canvas.set(
p.0 as usize,
p.1 as usize,
BlockType::Gate(CircuitType::AND),
);
true
}
_ => false,
};
if invert_second {
// Invert second input.
canvas.set(
p.0 as usize - 1,
p.1 as usize + 1,
BlockType::Gate(CircuitType::NOT),
);
}
}
}
pub fn place(&mut self, position: Position2D) {
if self.position.is_some() {
// This was already placed.
panic!("Circuit was already placed");
}
self.position = Some(Position2D(position.0 + 1, position.1 + self.height() / 2));
let mut off_y = 0;
for i in self.inputs.iter_mut() {
i.position = Some(Position2D(position.0, position.1 + off_y));
off_y += 2;
}
let w = self.width();
if !self.outputs.is_empty() {
assert!(self.outputs.len() == 1);
if self.circuit_type == CircuitType::FORWARD
|| self.circuit_type == CircuitType::INPUT
|| self.circuit_type == CircuitType::NOT
{
self.outputs[0].position = Some(Position2D(position.0 + w + 1, position.1));
} else {
self.outputs[0].position = Some(Position2D(position.0 + w + 2, position.1 + 1));
}
}
}
}
impl TryFrom<&YosysJsonCell> for Circuit {
type Error = ();
fn try_from(cell: &YosysJsonCell) -> Result<Self, Self::Error> {
for i in cell.connections.iter() {
assert!(i.1.len() == 1);
}
let inputs: Vec<_> = cell
.connections
.iter()
.filter(|(k, _)| cell.port_directions[*k] == YosysJsonPortDirection::Input)
.sorted_by(|(k0, _), (k1, _)| k0.cmp(k1))
.map(|(_, v)| Port::from(&v[0]))
.collect();
let outputs: Vec<_> = cell
.connections
.iter()
.filter(|(k, _)| cell.port_directions[*k] == YosysJsonPortDirection::Output)
.map(|(_, v)| Port::from(&v[0]))
.collect();
Ok(Self {
circuit_type: CircuitType::from_verilog(&cell.cell_type),
inputs,
outputs,
position: None,
})
}
}
// Determine the channel layout for the given list of circuits.
pub fn determine_channel_layout<'a, T: Iterator<Item = &'a Circuit>>(
circuits: T,
io: IOType,
) -> Box<ChannelLayout> {
let mut res = Vec::new();
for c in circuits {
let it = match io {
IOType::Input => c.inputs.iter(),
IOType::Output => c.outputs.iter(),
};
for i in it {
let off = i.position.unwrap().1 as usize;
while off >= res.len() {
res.push(ChannelState::Occupied);
}
res[off] = i.connection.into();
}
}
res.into_boxed_slice()
}

56
src/loader.rs Normal file
View File

@ -0,0 +1,56 @@
// 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.
use serde::{Deserialize, Serialize};
use serde_json::Value;
// Structured - at least a little.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum YosysJsonPortDirection {
#[serde(rename = "input")]
Input,
#[serde(rename = "output")]
Output,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct YosysJsonPort {
pub direction: YosysJsonPortDirection,
pub bits: Vec<Value>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct YosysJsonCell {
hide_name: usize,
#[serde(rename = "type")]
pub cell_type: String,
parameters: Value, // can be ignored
attributes: Value, // can be ignored
pub port_directions: std::collections::HashMap<String, YosysJsonPortDirection>,
pub connections: std::collections::HashMap<String, Vec<Value>>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct YosysJsonModule {
attributes: Value,
pub ports: std::collections::HashMap<String, YosysJsonPort>,
pub cells: std::collections::HashMap<String, YosysJsonCell>,
netnames: Value,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct YosysJson {
creator: String,
pub modules: std::collections::HashMap<String, YosysJsonModule>,
}

368
src/main.rs Normal file
View File

@ -0,0 +1,368 @@
// 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(())
}

115
src/placer.rs Normal file
View File

@ -0,0 +1,115 @@
// 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.
use crate::channel_router::{ChannelLayout, ChannelState};
use crate::circuit::{Circuit, Position2D};
fn get_net_index_in_layout(channel_layout: &ChannelLayout, net: usize) -> Option<usize> {
channel_layout
.iter()
.enumerate()
.filter(|(_, v)| v == &&ChannelState::Net(net))
.map(|(p, _)| p)
.next()
}
pub fn place_gates(channel_layout: &ChannelLayout, circuits: &mut Vec<Circuit>) {
let mut desired_channel_layout = vec![ChannelState::Free; channel_layout.len()];
// Place output circuits (right side of the channel).
// Since we don't know the width of the channel yet, assign temporary
// coordinates there.
// First iteration: Step gates where the inputs are aligned with
// the outputs of the previous step and swap inputs if necessary.
for circuit in circuits.iter_mut() {
// If we have two inputs, swap them if they would cause unnecessary wire crossings.
if circuit.inputs.len() == 2 {
if !circuit.can_swap_inputs() {
continue;
}
// Check where the inputs are that we need.
let p1 = get_net_index_in_layout(
channel_layout,
circuit.inputs[0].connection.get_net().unwrap(),
)
.unwrap();
let p2 = get_net_index_in_layout(
channel_layout,
circuit.inputs[1].connection.get_net().unwrap(),
)
.unwrap();
if p1 > p2 {
circuit.swap_inputs();
}
} else if circuit.inputs.len() == 1 {
if let Some(req_input) = circuit.inputs[0].connection.get_net() {
let p = get_net_index_in_layout(channel_layout, req_input)
.unwrap_or_else(|| panic!("Could not find net {}", req_input));
// If the target slot is still available, place ourselves here.
// Note: This will also only work with 1x1 gates, circuits might
// overlap.
if desired_channel_layout[p].is_free() {
circuit.place(Position2D(100_000, p as u32));
desired_channel_layout[p] = ChannelState::Net(req_input);
}
}
}
}
// Second iteration: Place everything else.
for circuit in circuits.iter_mut() {
// Skip circuits that were already placed.
if circuit.position.is_some() {
continue;
}
// Find free space to place this circuit.
let space_pattern = vec![ChannelState::Free; circuit.height() as usize];
let free_pos = desired_channel_layout
.windows(circuit.height() as usize)
.position(|window| window == &*space_pattern)
// TODO: This adds it at the end, there should be more
// efficient things to do.
.unwrap_or_else(|| desired_channel_layout.len());
circuit.place(Position2D(100_000, free_pos as u32));
for i in circuit.inputs.iter() {
let off = i.position.unwrap().1 as usize;
while off >= desired_channel_layout.len() {
desired_channel_layout.push(ChannelState::Free);
}
assert!(desired_channel_layout[off].is_free());
desired_channel_layout[off] = i.connection.into();
}
// Hack: Make sure to mark the space between the inputs as occupied.
assert!(circuit.inputs.len() == 1 || circuit.inputs.len() == 2);
if circuit.inputs.len() == 2 {
let off = circuit.inputs[0].position.unwrap().1 as usize;
assert!(desired_channel_layout[off + 1].is_free());
desired_channel_layout[off + 1] = ChannelState::Occupied;
}
}
#[cfg(debug_assertions)]
for c in circuits.iter() {
if c.position.is_none() {
panic!("Some circuits were not placed");
}
}
}