def main(): # Monkeypatch the exec_command() to not bail if it detects an 'error' in stderr, # these 'errors' don't seem to actually prevent the sim from working correctly Shared.NgSpiceShared.exec_command = exec_command libraries_path = find_libraries() spice_library = SpiceLibrary(libraries_path) directory_path = Path(__file__).resolve().parent kicad_netlist_path = directory_path.joinpath('simulation.cir') netlist_without_quotes_path = Path('./simulation-without-quotes.cir') remove_include_quotes(kicad_netlist_path, netlist_without_quotes_path) parser = SpiceParser(path=str(netlist_without_quotes_path)) circuit = parser.build_circuit(ground=0) simulator = circuit.simulator(temperature=25, nominal_temperature=25) analysis = simulator.transient(step_time=100 @ u_ps, end_time=100 @ u_ns) v_out = analysis['OUT'] fig, ax = plt.subplots(1, figsize=(10, 5)) ax.plot(v_out.abscissa.as_ndarray() * 1e9, v_out.as_ndarray()) ax.legend(('OUT', ), loc=(.8, .8)) ax.grid() ax.set_xlabel('Time (ns)') ax.set_ylabel('Voltage (V)') plt.tight_layout() plt.savefig('out.png')
def main(): parser = argparse.ArgumentParser(description='Convert a circuit file to PySpice') parser.add_argument('circuit_file', # metavar='circuit_file', help='.cir file') parser.add_argument('-o', '--output', default=None, help='Output file') parser.add_argument('--ground', type=int, default=0, help='Ground node') parser.add_argument('--build', default=False, action='store_true', help='Build circuit') args = parser.parse_args() ############################################## parser = SpiceParser(path=args.circuit_file) if args.build: parser.build_circuit() circuit = parser.to_python_code(ground=args.ground) if args.output is not None: with open(args.output, 'w') as f: f.write(circuit) else: print(circuit)
def main(): # Monkeypatch the exec_command() to not bail if it detects an 'error' in stderr, # these 'errors' don't seem to actually prevent the sim from working correctly Shared.NgSpiceShared.exec_command = exec_command libraries_path = find_libraries() spice_library = SpiceLibrary(libraries_path) directory_path = Path(__file__).resolve().parent kicad_netlist_path = directory_path.joinpath( 'wien-bridge-oscillator-sim.cir') parser = SpiceParser(path=str(kicad_netlist_path)) circuit = parser.build_circuit(ground=0) simulator = circuit.simulator(temperature=25, nominal_temperature=25) analysis = simulator.transient(step_time=10 @ u_us, end_time=60 @ u_ms) v_sine_out = analysis['SINE_OUT'] first_index_of_interest = np.where( v_sine_out.abscissa.as_ndarray() > 0.04)[0][0] fig, ax = plt.subplots(1, figsize=(10, 5)) ax.plot(v_sine_out.abscissa.as_ndarray()[first_index_of_interest:], v_sine_out.as_ndarray()[first_index_of_interest:]) ax.legend(('SINE_OUT', ), loc=(.8, .8)) ax.grid() ax.set_xlabel('Time (s)') ax.set_ylabel('Voltage (V)') plt.tight_layout() plt.savefig('v-sine-out.png')
def parser_netlist(self): """ 解析netlist :return:self.circurt属性绑定解析的netlist电路 """ parser = SpiceParser(path=self._path) self.circuit = parser.build_circuit() print(str(self.circuit))
def circuit_gft(prb): circuit_file = SpiceParser(source=prb[0]) circuit = circuit_file.build_circuit() circuit.parameter('prb', str(prb[1])) # Fixme: simulate with Xyce, CI !!! simulator = circuit.simulator(simulator='xyce-serial') simulator.save(['all']) return simulator
def is_valid_netlist(textfile, name=None): try: parser = SpiceParser(source=textfile) circuit = parser.build_circuit() return len(circuit.elements) > 0 except: if name: print(f'invalid spice file: {name}', file=sys.stderr) return False
def main(): # Monkeypatch the exec_command() to not bail if it detects an 'error' in stderr, # these 'errors' don't seem to actually prevent the sim from working correctly Shared.NgSpiceShared.exec_command = exec_command libraries_path = find_libraries() spice_library = SpiceLibrary(libraries_path) directory_path = Path(__file__).resolve().parent kicad_netlist_path = directory_path.joinpath('schematic.cir') netlist_without_quotes_path = Path('./simulation-without-quotes.cir') remove_include_quotes(kicad_netlist_path, netlist_without_quotes_path) parser = SpiceParser(path=str(netlist_without_quotes_path)) circuit = parser.build_circuit(ground=0) simulator = circuit.simulator(temperature=25, nominal_temperature=25) # analysis = simulator.transient(step_time=100@u_us, end_time=1000@u_ms) analysis = simulator.ac(start_frequency=100@u_mHz, stop_frequency=10@u_kHz, number_of_points=50, variation='dec') v_out = analysis['v_out'] mag = 20*np.log10(np.abs(v_out.as_ndarray())) print(mag) freq = analysis.frequency.as_ndarray() r1_Ohms = 2e3 r2_Ohms = 10e3 ratio = r2_Ohms / (r1_Ohms + r2_Ohms) ratio_dB = 20*np.log10(ratio) ratio_dB = -1.75 r_parallel = (r1_Ohms * r2_Ohms) / (r1_Ohms + r2_Ohms) c_F = 10e-6 fc = 1 / (2*np.pi*r_parallel*c_F) print(ratio_dB) fig, ax = plt.subplots(1, figsize=(10, 7)) ax.plot(freq, mag, label='$V_{OUT}$') # ax.legend(('$V_{OUT}$',), loc=(.8,.8)) ax.grid() ax.set_xscale('log') ax.set_xlabel('Frequency (Hz)') ax.set_ylabel('Voltage Magnitude (dB)') ax.axvline(fc, color='C2', label='$f_c$') ax.axhline(ratio_dB, color='C1', label=f'{ratio_dB:.2f}dB (DC gain)') ax.axhline(ratio_dB - 3.0, color='C1', label=f'{ratio_dB - 3:.2f}dB (DC gain - 3dB)') ax.legend() plt.tight_layout() plt.savefig('out.png')
def fetch_graphs(files, min_edges=0): filenames = h.valid_netlist_sources(files) source = next(filenames) parser = SpiceParser(source=source) circuit = parser.build_circuit() (nodes, edges) = h.get_nodes_edges(circuit) # TODO: randomly remove one each time until it is empty graphs = [] while len(edges) > min_edges: graphs.append((nodes, edges)) (nodes, edges) = a.remove_random_node(nodes, edges) return graphs
def build_cgraph_lib(ffn_netlist, dir_graph_out=None, ground=0, remove_dummy=False, dir_node_edge_list=None, dir_lib_out=None, guess_by_name=True, add_edge_annotation=False): """ ffn_netlist: full file path of netlist to parse dir_graph_out: directory path to save the resulting circuit topology graph in yaml format dir_lib_out: directory path to save circuit topology graph lib in pickle format remove_dummy: when True, remove dummy mos guess_by_name: when True, some attributes such as signal type is inferred/guessed from net/instance name add_edge_annotation: when True, add matching/group edge constraints to the graph return dict (i.e. {circuit_name:networkx.Graph}) """ from PySpice.Spice.Parser import SpiceParser parser = SpiceParser(path=ffn_netlist) circuit = parser.build_circuit(ground=ground) cgraph_dict = {} top_name, ext = os.path.splitext(os.path.basename(ffn_netlist)) G = build_cgraph(circuit, top_name, guess_by_name=guess_by_name, add_edge_annotation=add_edge_annotation) cgraph_dict[top_name] = G for c in (circuit.subcircuits): for e in (c.elements): G = build_cgraph(c, c.name, guess_by_name=guess_by_name, add_edge_annotation=add_edge_annotation) cgraph_dict[c.name] = G for name, G in cgraph_dict.items(): if(dir_graph_out is not None): nx.write_yaml(G, os.path.join(dir_graph_out, "%s.yaml" % name)) if(dir_node_edge_list is not None): node_number = {} with open(os.path.join(dir_node_edge_list, "%s.nodelist" % name), 'w') as f: for ii, n in enumerate(G.nodes()): f.write("%s %d\n" % (n, ii)) node_number[n]=ii with open(os.path.join(dir_node_edge_list, "%s.edgelist" % name), 'w') as f: for e in G.edges(): f.write('%s %s\n' % (node_number[e[0]], node_number[e[1]])) if(dir_lib_out is not None): with open(os.path.join(dir_lib_out, "%s.cgraph.pkl" % top_name), 'wb') as f: pickle.dump(cgraph_dict, f, -1) return cgraph_dict
def main(): # Monkeypatch the exec_command() to not bail if it detects an 'error' in stderr, # these 'errors' don't seem to actually prevent the sim from working correctly Shared.NgSpiceShared.exec_command = exec_command libraries_path = find_libraries() spice_library = SpiceLibrary(libraries_path) directory_path = Path(__file__).resolve().parent kicad_netlist_path = directory_path.joinpath('schematic.cir') netlist_without_quotes_path = Path('./simulation-without-quotes.cir') remove_include_quotes(kicad_netlist_path, netlist_without_quotes_path) parser = SpiceParser(path=str(netlist_without_quotes_path)) circuit = parser.build_circuit(ground=0) simulator = circuit.simulator(temperature=25, nominal_temperature=25) analysis = simulator.transient(step_time=1 @ u_us, end_time=1000 @ u_us) # analysis = simulator.ac(start_frequency=100@u_mHz, stop_frequency=10@u_kHz, number_of_points=50, variation='dec') v_in = analysis['/v_in'] v_out = analysis['v_out'] fig, ax = plt.subplots(1, figsize=(7, 5)) ax.plot(v_in.abscissa.as_ndarray() * 1e6, v_in.as_ndarray(), label='$v_{in}$') ax.plot(v_out.abscissa.as_ndarray() * 1e6, v_out.as_ndarray(), label='$v_{out}$') ax.axhline(-5.0, ls='--', color='C0') ax.axhline(5.0, ls='--', color='C0') ax.axhline(0, ls='--', color='C1') ax.axhline(3.3, ls='--', color='C1') ax.grid() ax.set_xlabel('Time [us]') ax.set_ylabel('Voltage [V]') ax.legend() plt.tight_layout() plt.savefig('out.png')
def netlist_as_graph(textfile): parser = SpiceParser(source=textfile) circuit = parser.build_circuit() component_list = components(circuit) adj = {} for element in circuit.elements: element_id = component_list.index(element) if element_id not in adj: adj[element_id] = [] nodes = [pin.node for pin in element.pins] node_ids = [component_list.index(node) for node in nodes] adj[element_id].extend(node_ids) for node_id in node_ids: if node_id not in adj: adj[node_id] = [] adj[node_id].append(element_id) adj = nx.adjacency_matrix(nx.from_dict_of_lists(adj)).toarray() return component_list, adj
############################################## def __init__(self): super().__init__() self.R('load', 'output', 'x', 10 @ u_Ω) #################################################################################################### #!# We read the generated netlist. kicad_netlist_path = os.path.join(os.path.realpath(os.path.dirname(__file__)), 'kicad-pyspice-example', 'kicad-pyspice-example.cir') parser = SpiceParser(path=kicad_netlist_path) #!# We build the circuit and translate the ground (5 to 0). circuit = parser.build_circuit(ground=5) #!# We include the operational amplifier module. circuit.include(spice_library['LMV981']) #!# We define the subcircuits. for subcircuit in (PowerIn(), Opamp(), JackIn(), JackOut()): circuit.subcircuit(subcircuit) # print(str(circuit)) #!# We perform a transient simulation. simulator = circuit.simulator(temperature=25, nominal_temperature=25)
############################################## def __init__(self): super().__init__() self.R('load', 'output', 'x', 10) #################################################################################################### #!# We read the generated netlist. kicad_netlist_path = os.path.join(os.path.realpath(os.path.dirname(__file__)), 'kicad-pyspice-example', 'kicad-pyspice-example.cir') parser = SpiceParser(path=kicad_netlist_path) #!# We build the circuit and translate the ground (5 to 0). circuit = parser.build_circuit(ground=5) #!# We include the operational amplifier module. circuit.include(spice_library['LMV981']) #!# We define the subcircuits. for subcircuit in (PowerIn(), Opamp(), JackIn(), JackOut()): circuit.subcircuit(subcircuit) # print(str(circuit)) #!# We perform a transient simulation. simulator = circuit.simulator(temperature=25, nominal_temperature=25)
# #.lib CMOS_035_Spice_Model.lib tt # #.end # """ source = """ .title Test V1 vp GND dc 1.65 ac 0.5 M7 Vout /6 VDD VDD p_33 l=0.9u w=25.9u M6 Vout /3 GND GND n_33 l=1.4u w=92.04u M2 /3 vp /1 VDD p_33 l=0.9u w=51.78u M1 /2 vn /1 VDD p_33 l=0.9u w=51.78u M4 /3 /2 GND GND n_33 l=1.4u w=46.02u M3 /2 /2 GND GND n_33 l=1.4u w=46.02u M5 /1 /6 VDD VDD p_33 l=0.9u w=12.95u V0 VDD GND 3.3 M8 /6 /6 VDD VDD p_33 l=0.9u w=1.3u I1 /6 GND 10u .lib CMOS_035_Spice_Model.lib tt """.strip() parser = SpiceParser(source=source) circuit = parser.build_circuit() source2 = str(circuit) for line1, line2 in zip(source.splitlines(), source2.splitlines()): print('-' * 100) print(line1 + '|') print(line2 + '|') assert (line1 == line2)
#################################################################################################### circuit = Circuit('STM AN1476: Low-Cost Power Supply For Home Appliances') circuit.include(spice_library['1N4148']) # 1N5919B: 5.6 V, 3.0 W Zener Diode Voltage Regulator circuit.include(spice_library['d1n5919brl']) ac_line = circuit.AcLine('input', 'out', 'in', rms_voltage=230, frequency=50) circuit.R('load', 'out', circuit.gnd, kilo(1)) circuit.C('load', 'out', circuit.gnd, micro(220)) circuit.X('D1', '1N4148', circuit.gnd, 1) circuit.D(1, circuit.gnd, 1, model='DIODE1', off=True) circuit.X('Dz1', 'd1n5919brl', 1, 'out') circuit.C('ac', 1, 2, nano(470)) circuit.R('ac', 2, 'in', 470) # Fixme: , m=1, temperature='{25}' source = str(circuit) print(source) #################################################################################################### parser = SpiceParser(source=source) bootstrap_circuit = parser.build_circuit() bootstrap_source = str(bootstrap_circuit) print(bootstrap_source) assert(source == bootstrap_source)
from PySpice.Doc.ExampleTools import find_libraries from PySpice.Probe.Plot import plot from PySpice.Spice.Library import SpiceLibrary from PySpice.Spice.Netlist import Circuit from PySpice.Spice.Parser import SpiceParser from PySpice.Spice.Netlist import SubCircuitFactory from PySpice.Unit import * #################################################################################################### # IMPORT SPICE NETLIST OF CIRCUIT: spice_library = SpiceLibrary('../libraries/') netlist_path = os.path.join(os.path.realpath(os.path.dirname(__file__)), 'NAND2.sp') parser = SpiceParser(path=netlist_path) circuit = parser.build_circuit() circuit.include(spice_library['NAND2']) #################################################################################################### # CONFIGURE VOLTAGE SOURCES supply = 1@u_V circuit.V('supply', 'vdd', 'vss', supply) circuit.V('ground', 'vss', circuit.gnd, circuit.gnd) #################################################################################################### # SIMULATE IMPORTED CIRCUIT
# #.lib CMOS_035_Spice_Model.lib tt # #.end # """ source = """ .title Test V1 vp GND dc 1.65 ac 0.5 M7 Vout /6 VDD VDD p_33 l=0.9u w=25.9u M6 Vout /3 GND GND n_33 l=1.4u w=92.04u M2 /3 vp /1 VDD p_33 l=0.9u w=51.78u M1 /2 vn /1 VDD p_33 l=0.9u w=51.78u M4 /3 /2 GND GND n_33 l=1.4u w=46.02u M3 /2 /2 GND GND n_33 l=1.4u w=46.02u M5 /1 /6 VDD VDD p_33 l=0.9u w=12.95u V0 VDD GND 3.3 M8 /6 /6 VDD VDD p_33 l=0.9u w=1.3u I1 /6 GND 10u .lib CMOS_035_Spice_Model.lib tt """.strip() parser = SpiceParser(source=source) circuit = parser.build_circuit() source2 = str(circuit) for line1, line2 in zip(source.splitlines(), source2.splitlines()): print('-'*100) print(line1 + '|') print(line2 + '|') assert(line1 == line2)
class Circuit: parser = SpiceParser(source=".title" + os.linesep + ".end") # 1.28 ms -> 65 us def __init__(self, circuitTemplate, parameters): """A determined circuit with parameters all specified Attributes ---------- circuitTemplate : CircuitTemplate object the template from which the circuit is instantiated parameters : list or 1-D ndarray numeric parameters that will be substituted for those undetermined parameters in the circuit template Properties ---------- .circuitTemplate : CircuitTemplate object the template from which this circuit is instantiated .parameters : List """ self.circuitTemplate = circuitTemplate self.parameters = parameters try: mapping = dict(zip(self.circuitTemplate.parameters, parameters)) self._netlist = self.circuitTemplate.netlist.format(**mapping) except: traceback.print_exc() raise ValueError("insufficient number of parameters. Expect {} parameters: {}. Get {} parameters: {}".format(len(self.circuitTemplate.parameters), self.circuitTemplate.parameters, len(self.parameters), self.parameters)) self._circuit = self.parser.build_circuit() self._circuit.raw_spice += self._netlist self._circuit.raw_spice += self.circuitTemplate.rawSpice self._simulator = self._circuit.simulator(simulator="ngspice-subprocess") self.hints = dict( ac = dict( start = 1, end = 1e+9, variation = "dec" ), tran = dict( start = 0, end = 1e-3, points = 100 ) ) # self._cached = {} def getInput(self, nodeList): if "vin+" in nodeList: vin = nodeList["vin+"] - nodeList["vin-"] elif "vi+" in nodeList: vin = nodeList["vi+"] - nodeList["vi-"] elif "vin" in nodeList: vin = nodeList["vin"] elif "vi" in nodeList: vin = nodeList["vi"] elif "vp" in nodeList: vin = nodeList["vp"] - nodeList["vn"] else: raise KeyError("no input voltage node found. Tried `Vin+`, `vin+`, `Vi+`, `vi+`, `Vin`, `vin, `Vi`, `vi`, `Vp`, `vp`.") return np.array(vin) # remove units def getOutput(self, nodeList): if "vout+" in nodeList: vout = nodeList["vout+"] - nodeList["vout-"] elif "vo+" in nodeList: vout = nodeList["vo+"] - nodeList["vo-"] elif "vout" in nodeList: vout = nodeList["vout"] elif "vo" in nodeList: vout = nodeList["vo"] else: raise KeyError("no output voltage node found. Tried `Vout`, `vout`, `Vo`, `vo`.") return np.array(vout) # remove units def getResponse(self, nodeList): # Looks like PySpice will turn all node name into their lower case. vout = self.getOutput(nodeList) vin = self.getInput(nodeList) return vout / vin @property def netlist(self): return self._netlist # Methods for manual usage. They ignore `self.hints`. @functools.lru_cache() def getTransientModel(self, start=0, end=1e-6, points=1000): return self._simulator.transient(start_time=start, end_time=end, step_time=(end - start) / points) @functools.lru_cache() def getTransientResponse(self, start=0, end=1e-6, points=1000): analysis = self.getTransientModel(start, end, points) time = np.array(analysis.time) return (time, self.getResponse(analysis.nodes)) @functools.lru_cache() def getSmallSignalModel(self, start=1, end=1e+9, points=10, variation="dec"): """Do an AC small-signal simulation Attributes ---------- start : real number simulation frequency range start end : real number simulation frequency range end points : integer - when `variation == "lin"`, number of points in total - when `variation == "dec"`, number of points per decade - when `variation == "oct"`, number of points per octave variation : str sampling frequency point arrangement. Use "dec" or "oct" if you plan to draw Bode plot later. Returns ------- analysis : PySpice analysis object """ return self._simulator.ac(start_frequency=start, stop_frequency=end, number_of_points=points, variation=variation) @functools.lru_cache() # This boosts performance... def getFrequencyResponse(self, start=1, end=1e+9, points=10, variation="dec"): # analysis = self._simulator.ac(start_frequency=start, stop_frequency=end, number_of_points=points, variation=variation) analysis = self.getSmallSignalModel(start, end, points, variation) frequencies = np.array(analysis.frequency) return (frequencies, self.getResponse(analysis.nodes)) # High-level, convenient property-styled methods. These are affected by `self.hints` @property @functools.lru_cache(maxsize=1) def operationalPoint(self): return self._simulator.operating_point() @property @functools.lru_cache(maxsize=1) def staticPower(self): """static power, aka. absolute value of supply voltage multiplies by absolute value of current through supply.""" op = self.operationalPoint if "vdd+" in op.nodes: vdd = np.abs(op.nodes["vdd+"] - op.nodes["vdd-"]) elif "vcc+" in op.nodes: vdd = np.abs(op.nodes["vcc+"] - op.nodes["vcc-"]) elif "vdd" in op.nodes: vdd = np.abs(op.nodes["vdd"]) elif "vcc" in op.nodes: vdd = np.abs(op.nodes["vcc"]) else: raise KeyError("no supply found. Tried `VDD+`, `VDD`, `VCC+`, `VCC` and their case-insensitive variants.") if "vdd" in op.branches: idd = np.abs(op.branches["vdd"]) elif "v0" in op.branches: idd = np.abs(op.branches["v0"]) else: raise KeyError("no supply found. Tried `VDD`, `V0` and their case-insensitive variants.") return np.float(vdd) * np.float(idd) dcPower = staticPower @property def bandwidth(self): frequencyResponse = self.getFrequencyResponse(**self.hints["ac"]) return sizer.calculators.bandwidth(frequencyResponse[0], frequencyResponse[1]) @property def phaseMargin(self): frequencyResponse = self.getFrequencyResponse(**self.hints["ac"]) return sizer.calculators.phaseMargin(frequencyResponse[0], frequencyResponse[1]) @property def gainMargin(self): frequencyResponse = self.getFrequencyResponse(**self.hints["ac"]) return sizer.calculators.gainMargin(frequencyResponse[0], frequencyResponse[1]) @property def unityGainFrequency(self): frequencyResponse = self.getFrequencyResponse(**self.hints["ac"]) return sizer.calculators.unityGainFrequency(frequencyResponse[0], frequencyResponse[1]) @property def gain(self): frequencyResponse = self.getFrequencyResponse(**self.hints["ac"]) return sizer.calculators.gain(frequencyResponse[0], frequencyResponse[1]) @property def slewRate(self): analysis = self.getTransientModel(**self.hints["tran"]) return sizer.calculators.slewRate(np.array(analysis.time), np.array(self.getOutput(analysis.nodes)))
# 1N5919B: 5.6 V, 3.0 W Zener Diode Voltage Regulator circuit.include(spice_library['d1n5919brl']) ac_line = circuit.AcLine('input', 'out', 'in', rms_voltage=230, frequency=50) circuit.R('load', 'out', circuit.gnd, kilo(1)) circuit.C('load', 'out', circuit.gnd, micro(220)) circuit.X('D1', '1N4148', circuit.gnd, 1) circuit.D(1, circuit.gnd, 1, model='DIODE1', off=True) circuit.X('Dz1', 'd1n5919brl', 1, 'out') circuit.C('ac', 1, 2, nano(470)) circuit.R('ac', 2, 'in', 470, m=1, temperature='{25}') source = str(circuit) print(source) #################################################################################################### parser = SpiceParser(source=source) bootstrap_circuit = parser.build_circuit() bootstrap_source = str(bootstrap_circuit) print(bootstrap_source) assert (source == bootstrap_source) #################################################################################################### # # End # ####################################################################################################