Skip to content

MorphoCircuit.kt

src/jvmMain/kotlin/afield/lang/morpho/MorphoCircuit.kt · 137 lines

package afield.lang.morpho

import java.io.Closeable

The grow-and-draw handle: a compiled MorphoHDL circuit that expands a few cells per frame while its layout relaxes between expansions.

MorphoCircuit.compile takes a design name and a bus signature and returns something compiled but not yet expanded. step is shaped to be the condition of a run loop, so the recursive division is watchable rather than already finished; growFully is the same circuit with the watching skipped. Either way the current MorphoNetlist and MorphoTiming are readable between steps, which is what lets a neighbouring Kotlin box draw a half-built circuit.

A morpho box publishes one of these on Box.morphoCircuit; a Kotlin box builds its own with morphoCircuit(...) from MorphoFLines.kt. It holds a guest-side session and a WASM layout, so it has to be closed.

class MorphoCircuit private constructor

A MorphoHDL circuit that is being grown and laid out at the same time — the thing a morpho box holds, and the thing a Kotlin box draws.

Growth and layout are deliberately interleaved rather than sequenced. Compiling the whole design and then laying it out gives you a finished object that snaps into place; expanding a few cells per frame and relaxing the layout between expansions gives you a circuit that unfolds, which is both the point of a language built on recursive division and the only version worth live-coding against.

val c = MorphoCircuit.compile(design = "brent_kung_adder", signature = intArrayOf(32, 32, 1))
while (c.step()) { /* draw c.flines() each frame */ }
println(c.summary())
class MorphoCircuit private constructor(
    val design: String,
    val signature: IntArray,
    private val session: MorphoSession,
    val layout: MorphoLayout3D,
) : Closeable {

companion object

    companion object {

fun compile

Compile design at signature out of source, ready to grow.

source defaults to morpho's own standard library, so MorphoCircuit.compile(design = "wallace_multiplier", signature = intArrayOf(16, 16)) works with no source at all.

        fun compile(
            source: String = MorphoRuntime.standardLibrary,
            design: String,
            signature: IntArray,
            optimize: Boolean = true,
            seed: Int = 1337,
        ): MorphoCircuit {
            val session = MorphoRuntime.compile(source, design, signature, optimize)
            val circuit = MorphoCircuit(design, signature, session, MorphoLayout3D(session, seed))
            circuit.refresh()
            return circuit
        }
    }

var netlist: MorphoNetlist

The circuit as it stands. Replaced, not mutated, on every step.

    var netlist: MorphoNetlist = session.netlist()
        private set

var timing: MorphoTiming

Signal arrival times for netlist.

    var timing: MorphoTiming = session.timing()
        private set

var growing: Boolean

False once every cell has been expanded.

    var growing: Boolean = true
        private set

val expansions: Int get() = netlist.steps

Expansions performed so far.

    private fun refresh() {
        netlist = session.netlist()
        timing = session.timing()
        layout.sync()
    }

fun step(grow: Int = 16, relax: Int = 6, params: MorphoLayout3D.Params = MorphoLayout3D.Params()): Boolean

Advance one frame: expand up to grow cells, then relax the layout relax iterations.

@return true while there is still growing to do, so a run loop can be while (c.step()) { … }. Layout keeps relaxing after growth finishes, so calling step on a fully grown circuit is still useful — it settles.

    fun step(grow: Int = 16, relax: Int = 6, params: MorphoLayout3D.Params = MorphoLayout3D.Params()): Boolean {
        if (grow > 0 && growing) {
            val expanded = session.grow(grow)
            if (expanded == 0) growing = false
            refresh()
        }
        if (relax > 0) layout.relax(relax, params)
        return growing
    }

fun growFully(relaxSteps: Int = 200): MorphoCircuit

Expand to completion, then settle the layout for relaxSteps iterations.

    fun growFully(relaxSteps: Int = 200): MorphoCircuit {
        session.growFully()
        growing = false
        refresh()
        if (relaxSteps > 0) layout.relax(relaxSteps)
        return this
    }

fun evaluate(vararg samples: LongArray): Array<LongArray>

Run the circuit on a batch of integer test vectors. See MorphoSim.evaluate.

    fun evaluate(vararg samples: LongArray): Array<LongArray> =
        MorphoSim.evaluate(netlist, arrayOf(*samples))

fun evaluateOne(vararg ports: Long): LongArray = MorphoSim.evaluateOne(netlist, *ports)

Run the circuit on one test vector.

val depth: Float get() = timing.depth(netlist)

Critical-path depth: the number that separates a ripple adder from a Brent–Kung.

fun summary(): String

A one-line description with every number that matters.

    fun summary(): String = buildString {
        append(design).append('(').append(signature.joinToString(", ")).append(") ")
        append(if (growing) "growing" else "grown")
        append(": ").append(netlist.activeCount).append(" cells, ")
        append(netlist.gateCount).append(" gates, ")
        append(netlist.nNets).append(" nets, depth ").append(depth.toInt())
        append(", ").append(expansions).append(" expansions")
        if (!growing) append(", z/xy ").append(String.format("%.2f", layout.flatness()))
    }

override fun close()

    override fun close() {
        layout.close()
        session.close()
    }
}