Go to file
Kevin Hamacher ffc1e47162 Initial commit 2019-08-28 13:47:36 +02:00
src Initial commit 2019-08-28 13:47:36 +02:00
.gitignore Initial commit 2019-08-28 13:47:36 +02:00
CONTRIBUTING.md Initial commit 2019-08-28 13:47:36 +02:00
Cargo.toml Initial commit 2019-08-28 13:47:36 +02:00
LICENSE Initial commit 2019-08-28 13:47:36 +02:00
README.md Initial commit 2019-08-28 13:47:36 +02:00

README.md

MinetestPnR

Simple 2D placer & router for minetest developed as part of a Google CTF challenge (video writeup). 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):

 ░──────────────────¬─────────────┐┌───┐  ┌───────┐  ┌─────┐        ┌──┐  ┌─┐
 ░────────┬─────────¬─────────┐   ││   ^──┘       ^──┘     ^─────»──┘  ^──┘ ^────▓
 ░────────╂──────┬──¬─────────╂───╂┘┌──┘  ┌───────┘  ┌─────┘        ┌──┘  ┌─┘
 ░────────╂──────╂┬─¬─────┬───╂───╂─╂──┐  │┌──────┐  │┌────┐        │┌─┐  │
 ░──────┬─╂──────╂╂─¬─────╂───╂───╂─╂┐ ^──┘│      ^──┘│    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:
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:

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.