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