Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions lib/circuit-json-to-spice/addSimulationVoltageSourcesToNetlist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import type { AnyCircuitElement } from "circuit-json"
import { su } from "@tscircuit/soup-util"
import { SpiceComponent } from "../spice-classes/SpiceComponent"
import { VoltageSourceCommand } from "../spice-commands/VoltageSourceCommand"
import type { SpiceNetlist } from "../spice-classes/SpiceNetlist"

interface AddSimulationVoltageSourcesArgs {
circuitJson: AnyCircuitElement[]
netlist: SpiceNetlist
nodeMap: Map<string, string>
}

export function addSimulationVoltageSourcesToNetlist({
circuitJson,
netlist,
nodeMap,
}: AddSimulationVoltageSourcesArgs) {
const simulationVoltageSources =
su(circuitJson).simulation_voltage_source.list()

for (const simSource of simulationVoltageSources) {
if (simSource.type !== "simulation_voltage_source") continue

if ((simSource as any).is_dc_source === false) {
if (
"terminal1_source_port_id" in simSource &&
"terminal2_source_port_id" in simSource &&
(simSource as any).terminal1_source_port_id &&
(simSource as any).terminal2_source_port_id
) {
const positiveNode =
nodeMap.get((simSource as any).terminal1_source_port_id) || "0"
const negativeNode =
nodeMap.get((simSource as any).terminal2_source_port_id) || "0"

let value = ""
const wave_shape = (simSource as any).wave_shape
if (wave_shape === "sinewave") {
const v_offset = 0
const v_peak = (simSource as any).voltage ?? 0
const freq = (simSource as any).frequency ?? 0
const delay = 0
const damping_factor = 0
const phase = (simSource as any).phase ?? 0
if (freq > 0) {
value = `SIN(${v_offset} ${v_peak} ${freq} ${delay} ${damping_factor} ${phase})`
} else {
value = `DC ${(simSource as any).voltage ?? 0}`
}
} else if (wave_shape === "square") {
const v_initial = 0
const v_pulsed = (simSource as any).voltage ?? 0
const freq = (simSource as any).frequency ?? 0
const period_from_freq = freq === 0 ? Infinity : 1 / freq
const period = (simSource as any).period ?? period_from_freq
const duty_cycle = (simSource as any).duty_cycle ?? 0.5
const pulse_width = period * duty_cycle
const delay = 0
const rise_time = "1n"
const fall_time = "1n"
value = `PULSE(${v_initial} ${v_pulsed} ${delay} ${rise_time} ${fall_time} ${pulse_width} ${period})`
} else if ((simSource as any).voltage !== undefined) {
value = `DC ${(simSource as any).voltage}`
}

if (value) {
const voltageSourceCmd = new VoltageSourceCommand({
name: simSource.simulation_voltage_source_id,
positiveNode,
negativeNode,
value,
})

const spiceComponent = new SpiceComponent(
simSource.simulation_voltage_source_id,
voltageSourceCmd,
[positiveNode, negativeNode],
)
netlist.addComponent(spiceComponent)
}
}
} else {
const positivePortId =
(simSource as any).positive_source_port_id ??
(simSource as any).terminal1_source_port_id
const negativePortId =
(simSource as any).negative_source_port_id ??
(simSource as any).terminal2_source_port_id

if (
positivePortId &&
negativePortId &&
"voltage" in simSource &&
(simSource as any).voltage !== undefined
) {
const positiveNode = nodeMap.get(positivePortId) || "0"
const negativeNode = nodeMap.get(negativePortId) || "0"

const voltageSourceCmd = new VoltageSourceCommand({
name: simSource.simulation_voltage_source_id,
positiveNode,
negativeNode,
value: `DC ${(simSource as any).voltage}`,
})

const spiceComponent = new SpiceComponent(
simSource.simulation_voltage_source_id,
voltageSourceCmd,
[positiveNode, negativeNode],
)
netlist.addComponent(spiceComponent)
}
}
}
}
274 changes: 274 additions & 0 deletions lib/circuit-json-to-spice/addSourceComponentsToNetlist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
import type { AnyCircuitElement } from "circuit-json"
import { su } from "@tscircuit/soup-util"
import { SpiceComponent } from "../spice-classes/SpiceComponent"
import { ResistorCommand } from "../spice-commands/ResistorCommand"
import { CapacitorCommand } from "../spice-commands/CapacitorCommand"
import { VoltageSourceCommand } from "../spice-commands/VoltageSourceCommand"
import { DiodeCommand } from "../spice-commands/DiodeCommand"
import { InductorCommand } from "../spice-commands/InductorCommand"
import { VoltageControlledSwitchCommand } from "../spice-commands/VoltageControlledSwitchCommand"
import {
formatCapacitance,
formatInductance,
formatResistance,
} from "../spice-utils/valueFormatters"
import { sanitizeIdentifier } from "../spice-utils/identifier"
import { buildSimulationSwitchControlValue } from "../spice-utils/simulationSwitchControl"
import type { SpiceNetlist } from "../spice-classes/SpiceNetlist"
import type { SimulationSwitchLike } from "./types"

interface AddSourceComponentsArgs {
circuitJson: AnyCircuitElement[]
nodeMap: Map<string, string>
netlist: SpiceNetlist
simulationSwitchMap: Map<string, SimulationSwitchLike>
sourceComponents: Array<
{
type?: string
ftype?: string
source_component_id: string
} & Record<string, unknown>
>
}

export function addSourceComponentsToNetlist({
circuitJson,
nodeMap,
netlist,
simulationSwitchMap,
sourceComponents,
}: AddSourceComponentsArgs) {
for (const component of sourceComponents) {
if (component.type !== "source_component") continue

const componentPorts = su(circuitJson)
.source_port.list({
source_component_id: component.source_component_id,
})
.sort((a, b) => (a.pin_number ?? 0) - (b.pin_number ?? 0))

const nodes = componentPorts.map((port) => {
return nodeMap.get(port.source_port_id) || "0"
})

if ("ftype" in component) {
let spiceComponent: SpiceComponent | null = null

switch (component.ftype) {
case "simple_resistor": {
if ("resistance" in component && "name" in component) {
const resistorCmd = new ResistorCommand({
name: component.name as string,
positiveNode: nodes[0] || "0",
negativeNode: nodes[1] || "0",
value: formatResistance(component.resistance as number),
})
spiceComponent = new SpiceComponent(
component.name as string,
resistorCmd,
nodes,
)
}
break
}
case "simple_switch": {
const sanitizedBase = sanitizeIdentifier(
(component.name as string | undefined) ??
(component.source_component_id as string),
"SW",
)
const positiveNode = nodes[0] || "0"
const negativeNode = nodes[1] || "0"
const controlNode = `NCTRL_${sanitizedBase}`
const modelName = `SW_${sanitizedBase}`

const componentWithMaybeSwitchId = component as unknown as {
simulation_switch_id?: string
name?: string
}

const candidateSwitchIds = [
componentWithMaybeSwitchId.simulation_switch_id,
component.source_component_id,
componentWithMaybeSwitchId.name,
].filter((id): id is string => Boolean(id))

let associatedSimulationSwitch: SimulationSwitchLike | undefined
for (const switchId of candidateSwitchIds) {
associatedSimulationSwitch = simulationSwitchMap.get(switchId)
if (associatedSimulationSwitch) break
}

const controlValue = buildSimulationSwitchControlValue(
associatedSimulationSwitch,
)

const switchCmd = new VoltageControlledSwitchCommand({
name: sanitizedBase,
positiveNode,
negativeNode,
positiveControl: controlNode,
negativeControl: "0",
model: modelName,
})

spiceComponent = new SpiceComponent(sanitizedBase, switchCmd, [
positiveNode,
negativeNode,
controlNode,
"0",
])

if (!netlist.models.has(modelName)) {
netlist.models.set(
modelName,
`.MODEL ${modelName} SW(Ron=0.1 Roff=1e9 Vt=2.5 Vh=0.1)`,
)
}

const controlSourceName = `CTRL_${sanitizedBase}`
const controlSourceCmd = new VoltageSourceCommand({
name: controlSourceName,
positiveNode: controlNode,
negativeNode: "0",
value: controlValue,
})

const controlComponent = new SpiceComponent(
controlSourceName,
controlSourceCmd,
[controlNode, "0"],
)

netlist.addComponent(controlComponent)
break
}

case "simple_capacitor": {
if ("capacitance" in component && "name" in component) {
const capacitorCmd = new CapacitorCommand({
name: component.name as string,
positiveNode: nodes[0] || "0",
negativeNode: nodes[1] || "0",
value: formatCapacitance(component.capacitance as number),
})
spiceComponent = new SpiceComponent(
component.name as string,
capacitorCmd,
nodes,
)
}
break
}
case "simple_diode": {
if ("name" in component) {
const componentPortsByName = componentPorts as Array<{
name?: string | null
port_hints?: string[] | null
source_port_id: string
}>

const anodePort = componentPortsByName.find(
(p) =>
p.name?.toLowerCase() === "anode" ||
p.port_hints?.includes("anode"),
)
const cathodePort = componentPortsByName.find(
(p) =>
p.name?.toLowerCase() === "cathode" ||
p.port_hints?.includes("cathode"),
)
const positiveNode =
nodeMap.get(anodePort?.source_port_id ?? "") || "0"
const negativeNode =
nodeMap.get(cathodePort?.source_port_id ?? "") || "0"

const modelName = "D"
const diodeCmd = new DiodeCommand({
name: component.name as string,
positiveNode,
negativeNode,
model: modelName,
})
netlist.models.set(modelName, `.MODEL ${modelName} D`)
spiceComponent = new SpiceComponent(
component.name as string,
diodeCmd,
[positiveNode, negativeNode],
)
}
break
}
case "simple_inductor": {
if ("inductance" in component && "name" in component) {
const inductorCmd = new InductorCommand({
name: component.name as string,
positiveNode: nodes[0] || "0",
negativeNode: nodes[1] || "0",
value: formatInductance(component.inductance as number),
})
spiceComponent = new SpiceComponent(
component.name as string,
inductorCmd,
nodes,
)
}
break
}
case "simple_mosfet": {
if ("name" in component) {
const componentPortsByName = componentPorts as Array<{
name?: string | null
port_hints?: string[] | null
source_port_id: string
}>

const drainPort = componentPortsByName.find(
(p) =>
p.name?.toLowerCase() === "drain" ||
p.port_hints?.includes("drain"),
)
const gatePort = componentPortsByName.find(
(p) =>
p.name?.toLowerCase() === "gate" ||
p.port_hints?.includes("gate"),
)
const sourcePort = componentPortsByName.find(
(p) =>
p.name?.toLowerCase() === "source" ||
p.port_hints?.includes("source"),
)

const drainNode =
nodeMap.get(drainPort?.source_port_id ?? "") || "0"
const gateNode = nodeMap.get(gatePort?.source_port_id ?? "") || "0"
const sourceNode =
nodeMap.get(sourcePort?.source_port_id ?? "") || "0"

const modelName = "SWMOD"
const switchCmd = new VoltageControlledSwitchCommand({
name: component.name as string,
positiveNode: drainNode,
negativeNode: sourceNode,
positiveControl: gateNode,
negativeControl: sourceNode,
model: modelName,
})
netlist.models.set(modelName, `.MODEL ${modelName} SW`)

spiceComponent = new SpiceComponent(
component.name as string,
switchCmd,
[drainNode, gateNode, sourceNode],
)
}
break
}
}

if (spiceComponent) {
netlist.addComponent(spiceComponent)
}
}
}
}
Loading