//
// Copyright (c) 2019-2022 Markku Rossi
//
// All rights reserved.
//
package circuit
import (
"fmt"
"io"
"sort"
)
const (
ioWidth = 32
ioHeight = 32
ioPadX = 16
ioPadY = 32
gateWidth = 32
gateHeight = 32
gatePadX = 16
gatePadY = 64
)
type tile struct {
gate *Gate
avg float64
x float64
y float64
}
type point struct {
x, y float64
}
type wireType int
const (
wireTypeNormal wireType = iota
wireTypeZero
wireTypeOne
)
type wire struct {
t wireType
from point
to point
}
func (w *wire) svg(out io.Writer) {
label := "1"
switch w.t {
case wireTypeNormal:
midY := w.from.y + (w.to.y-w.from.y)/2 - 5
fmt.Fprintf(out, `
`,
w.from.x, w.from.y,
midY-w.from.y,
w.from.x, midY+10,
w.to.x, midY,
w.to.x, midY+10,
w.to.y-midY-10,
)
case wireTypeZero:
label = "0"
fallthrough
case wireTypeOne:
fmt.Fprintf(out, `
%v
`,
w.to.x, w.to.y-2, label)
}
}
type svgCtx struct {
wireStarts []point
zero Wire
one Wire
}
func (ctx *svgCtx) setWireType(input Wire, w *wire) {
if input == ctx.zero {
w.t = wireTypeZero
} else if input == ctx.one {
w.t = wireTypeOne
}
}
func (ctx *svgCtx) tileAvgInputX(t *tile) {
var count float64
switch t.gate.Op {
case XOR, XNOR, AND, OR:
if t.gate.Input1 != ctx.zero && t.gate.Input1 != ctx.one {
count++
t.avg += ctx.wireStarts[t.gate.Input1].x
}
fallthrough
case INV:
if count == 0 ||
(t.gate.Input0 != ctx.zero && t.gate.Input0 != ctx.one) {
count++
t.avg += ctx.wireStarts[t.gate.Input0].x
}
}
t.avg /= count
t.avg -= (gateWidth) / 2
}
// Svg creates an SVG output of the circuit.
func (c *Circuit) Svg(out io.Writer) {
c.AssignLevels()
cols := c.Stats[MaxWidth]
rows := c.Stats[NumLevels]
fmt.Printf("")
// Header.
ctx := &svgCtx{
wireStarts: make([]point, c.NumWires),
zero: InvalidWire,
one: InvalidWire,
}
iw := uint64(ioPadX + c.Inputs.Size()*(ioWidth+ioPadX))
ow := uint64(ioPadX + c.Outputs.Size()*(ioWidth+ioPadX))
width := cols * (gateWidth + gatePadX)
if iw > width {
width = iw
}
if ow > width {
width = ow
}
fmt.Fprintf(out,
`")
}
func renderRow(out io.Writer, width int, y float64, tiles []*tile,
ctx *svgCtx) []*wire {
var wires []*wire
for _, t := range tiles {
ctx.tileAvgInputX(t)
}
sort.Slice(tiles, func(i, j int) bool {
return tiles[i].avg < tiles[j].avg
})
// Assign x based on input average and push tiles right.
var next float64
for _, t := range tiles {
if next <= t.avg {
t.x = t.avg
} else {
t.x = next
}
next = t.x + gateWidth + gatePadX
}
// Starting from the right end, shift tiles left until they are on
// screen and not overlapping.
next = float64(width) - gateWidth - gatePadX
for i := len(tiles) - 1; i >= 0; i-- {
if tiles[i].x > next {
tiles[i].x = next
}
next -= gateWidth + gatePadX
}
for _, t := range tiles {
wires = append(wires, t.gate.svg(out, t.x, y, ctx)...)
}
return wires
}
func (g *Gate) svg(out io.Writer, x, y float64, ctx *svgCtx) []*wire {
fmt.Fprintf(out, `
`,
x, y)
tmpl := templates[g.Op]
if tmpl == nil {
tmpl = templates[Count]
}
out.Write([]byte(tmpl.Expand()))
fmt.Fprintln(out, ` `)
ctx.wireStarts[g.Output] = point{
x: x + gateWidth/2,
y: y + gateHeight,
}
var wires []*wire
switch g.Op {
case XOR, XNOR, AND, OR:
x0 := x + intCvt(35)
x1 := x + intCvt(65)
f0 := ctx.wireStarts[g.Input0]
f1 := ctx.wireStarts[g.Input1]
w0 := &wire{
from: f0,
to: point{
x: x0,
y: y,
},
}
w1 := &wire{
from: f1,
to: point{
x: x1,
y: y,
},
}
ctx.setWireType(g.Input0, w0)
ctx.setWireType(g.Input1, w1)
// The input pin order does not matter in the
// visualization. Swap input pins if input wires would cross
// each other.
if f0.x > f1.x {
w0.to, w1.to = w1.to, w0.to
}
wires = append(wires, w0)
wires = append(wires, w1)
case INV:
wire := &wire{
from: ctx.wireStarts[g.Input0],
to: point{
x: x + intCvt(50),
y: y,
},
}
ctx.setWireType(g.Input0, wire)
wires = append(wires, wire)
}
// Constant value gates.
switch g.Op {
case XOR:
if g.Input0 == g.Input1 {
ctx.zero = g.Output
staticOutput(out, x+gateWidth/2, y+gateHeight, "0")
}
case XNOR:
if g.Input0 == g.Input1 {
fmt.Printf("*** one!\n")
ctx.one = g.Output
staticOutput(out, x+gateWidth/2, y+gateHeight, "1")
}
}
return wires
}
func staticInput(out io.Writer, x, y float64, label string) {
fmt.Fprintf(out, `
%v
`,
x, y-2, label)
}
func staticOutput(out io.Writer, x, y float64, label string) {
fmt.Fprintf(out, `
%v
`,
x, y+10, label)
}
func scale(in int) float64 {
return float64(in) * gateWidth / 100
}
func path(out io.Writer) {
fmt.Fprintln(out, `
`)
templates[XNOR] = NewTemplate(`
`)
templates[AND] = NewTemplate(`
`)
templates[OR] = NewTemplate(`
`)
templates[INV] = NewTemplate(`
`)
templates[Count] = NewTemplate(``)
floatCvt = func(v float64) float64 {
return v * gateWidth / 100
}
intCvt = func(v int) float64 {
return float64(v) * gateWidth / 100
}
for op := XOR; op < Count+1; op++ {
if templates[op] != nil {
templates[op].FloatCvt = floatCvt
templates[op].IntCvt = intCvt
}
}
}