Initial commit
commit
ffc1e47162
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
**/*.rs.bk
|
|
@ -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/).
|
|
@ -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"] }
|
|
@ -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.
|
|
@ -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
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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>,
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue