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(): # 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 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 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')
#################################################################################################### import PySpice.Logging.Logging as Logging logger = Logging.setup_logging() #################################################################################################### 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.Unit import * #################################################################################################### libraries_path = find_libraries() spice_library = SpiceLibrary(libraries_path) #################################################################################################### from HP54501A import HP54501A #f# literal_include('HP54501A.py') #################################################################################################### circuit = Circuit('HP54501A CEM') circuit.include(spice_library['1N4148']) diode_model = '1N4148' ac_line = circuit.AcLine('input', 'input', circuit.gnd, rms_voltage=230@u_V, frequency=50@u_Hz) # circuit.subcircuit(HP54501A(diode_model='1N4148'))
# https://pyspice.fabrice-salvaire.fr/examples/diode/rectification.html import os import matplotlib.pyplot as plt import PySpice.Logging.Logging as Logging logger = Logging.setup_logging() 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.Unit import * libraries_path = find_libraries() spice_library = SpiceLibrary(libraries_path) figure1 = plt.figure(1, (20, 10)) circuit = Circuit('half-wave rectification') circuit.include(spice_library['1N4148']) source = circuit.SinusoidalVoltageSource('input', 'in', circuit.gnd, amplitude=10 @ u_V, frequency=50 @ u_Hz) circuit.X('D1', '1N4148', 'in', 'output') circuit.R('load', 'output', circuit.gnd, 100 @ u_Ω) simulator = circuit.simulator(temperature=25, nominal_temperature=25) analysis = simulator.transient(step_time=source.period / 200,
def do_monte_carlo_sim(tech, put, step_time, num_sims, plot_result, cvs, logger): # initialisar el generador alatoria usando el tiempo del sistema random.seed() # Abrir el archivo de .cvs if (cvs != None): try: cvs_handle = open(cvs,"w+") except: logger.error("Failed to open %s for writing, aborting", cvs) return logger.info("Running Monte Carlo simulation with path %s, tech %s, with step_time %e, num_sims %d", put.name(), tech.NAME, step_time, num_sims) # ================== # Generar el netlist # ================== libraries_path = find_libraries() circuit = Circuit("Monte_Carlo_Sim_"+put.name()+"_"+tech.NAME) circuit.include(libraries_path + "/" + tech.LIB_NAME) #.inclcude "foo.lib" vdd = circuit.V('dd', 'Vdd', circuit.gnd, tech.VDD) # Fuente de tensión Vdd # Fuente de tensión de pulses que usamos como la entrada de la ruta vin = circuit.PulseVoltageSource("In", "In", circuit.gnd, initial_value=0, pulsed_value=tech.VDD, pulse_width=1e-9, period=2e-9, delay_time=10e-12, rise_time=20e-12, fall_time=20e-12) # La ruta que queremos probar put.add_to_circuit(circuit, 'Vdd', 'In', 'Out') # ====================== # Hacer las simulaciones # ====================== # Comienza simulando por 100ps sim_time = 100e-12 ts = time.time(); # if simulation times out without seeing the transition, increase sim time by ... sim_time_step = 100e-12 # Once we've seen a succesfull transisition (sim time was long enough) # we reduce simulation time to bestResult.tp + 50ps succesfull_run = False # Hay varios resultados con el mismo (o muy parecido) Tp # Si una simulación da un Tp un poco peor (1%?) peor usa menos area, deberíamos # usar esto resultado cómo el mejor. TP_FLEX_PERCENT = 1.0 # guardamos el mejor resultado bestResult = Result(100.0, put.get_widths()) # Y guardamos un array de todos los resultados allResults = [] for l in range(1,num_sims+1): widths = put.get_widths() totalWidth = sum(widths) logger.debug("Running simulation with widths: [%s], sim_time %e, step_time %e", _get_widths_str(widths), sim_time, step_time) tp = _get_tp(circuit, tech, put, sim_time, step_time, logger) if (tp < 0): # La duración de simulación no estuvo suficiente largo # Si vimos una transición antes, entonces es claro que esto no puede ser mejor. # Pero si nunca vimos una transición antes, incrementamos la duración if (not succesfull_run): sim_time += sim_time_step logger.verbose("Out never transititons, increasing simulation time to %e", sim_time) else: logger.debug("Out never transititons") else: succesfull_run = True logger.verbose("tp: %e, widths: [%s], totalWidth: %e", tp, _get_widths_str(widths), totalWidth) allResults.append(Result(tp, widths)) # este resultado tiene menor tp que el corriente mejor? if (tp < bestResult.tp): logger.verbose(" New Best Tp") bestResult = Result(tp, widths) # reducir la duración de la simulación a tp + 50ps sim_time = tp + 50e-12 # ===================== # Actualizar los anchos # ===================== # Usamos los mejores anchos que encontramos como base widths = list(bestResult.widths) # tomar una copia #------------------------------------------------------------------- # Método 1: Cambiar todos los ancho aleatoriamente #------------------------------------------------------------------- #for idx in range(1, len(widths)): # width = random.uniform(tech.W_MIN, put.get_max_width()) # widths[idx] = width #---------------------------------------------------------------- # Método 2: Cambiar un ancho aleatoriamente adentro todo el rango #---------------------------------------------------------------- # Generar un ancho aleatoriamente entre tech.W_MIN y put.get_max_width() #width = random.uniform(tech.W_MIN, put.get_max_width()) # Elegir cual ancho cambiar (no elegimos el primero) #idx = random.randint(1, len(widths)-1) #widths[idx] = width #---------------------------------------------------------------------- # Método 3: Cambiar todos los ancho aleatoriamente en el rango de 0.5w # a 2w. Dónde w es el ancho actual. #---------------------------------------------------------------------- for idx in range(1, len(widths)): minWidth = max(tech.W_MIN, widths[idx]/2) maxWidth = min(put.get_max_width(), widths[idx]*2) width = random.uniform(minWidth, maxWidth) logger.debug("curr_width %e, minWidth %e, maxWidth %e, new width %e", widths[idx], minWidth, maxWidth, width) widths[idx] = width # Hazlo put.set_widths(widths) if (l % 100 == 0): logger.info("Ran %d / %d (%.1f%%)", l, num_sims, (100.0 * l)/num_sims) te = time.time(); # ====================== # Mostrar los resultados # ====================== logger.info("Took %ds to run %d simulations", int(te - ts), num_sims); logger.info("Best Tp %e, Widths [%s]", bestResult.tp, _get_widths_str(bestResult.widths)) # ============================================ # Encontrar el Tp optimo usando logical effort # ============================================ LEwidths= put.get_logical_effort_optimal_widths() if (LEwidths == None): logger.warning("Optimal case not supported by this path") else: logger.info("Running test with optimal widths calculated via logical effort: [%s]", _get_widths_str(LEwidths)) put.set_widths(LEwidths) LEtp = _get_tp(circuit, tech, put, sim_time, step_time, logger) if (LEtp <= 0): logger.info("Optimal Case: Out never transititons") else: logger.info("Optimal Case: %e, widths: [%s]", LEtp, _get_widths_str(LEwidths)) # ================================================ # Escribir los resultados al .cvs # ================================================ if (cvs != None): # Comenzamos con un comentario cvs_handle.write("#%s: Step time %.1e, Num sims %d, path: %s, tech: %s, load: %.2f\n" % (datetime.now().strftime("%Y/%m/%d %H:%M:%S"), step_time, num_sims, put.name(), tech.NAME, put.get_load())) # La simulación de LE (comentario) if (LEwidths == None): cvs_handle.write("#Logical Effort not supported by this path\n") elif (LEtp <= 0): cvs_handle.write("#Logical Effort: out never transititons\n") else: cvs_handle.write("#Logical Effort: %e, widths: [%s]\n" % (LEtp, _get_widths_str(LEwidths))) # El titulo de los columnos: widthHeadings = "" for i in range(len(widths)): widthHeadings += "width[%d], " % i ratioHeadings = "" for i in range(len(widths) - 1): ratioHeadings += "ratio %d to %d, " % (i, i+1) ratioHeadings += "ratio %d to load, " % (i+1) cvs_handle.write("Tp, " + widthHeadings + "load width, Total width, " + ratioHeadings + "Average ratio (not including load), Average ratio (including load)\n") # Y los resultados for r in allResults: ratios = [] for i in range(len(r.widths) - 1): ratios.append(r.widths[i+1] / r.widths[i]) avgWithoutLoad = sum(ratios)/len(ratios) ratios.append(put.get_load() * tech.W_MIN / r.widths[i+1]) avgWithLoad = sum(ratios)/len(ratios) ratiosStr = "" for i, ratio in enumerate(ratios): if (i != 0): ratiosStr += ", " ratiosStr += "%.2f" % ratio cvs_handle.write("%e, %s, %e, %e, %s, %.2f, %.2f\n" % (r.tp, _get_widths_str(r.widths), put.get_load() * tech.W_MIN, sum(r.widths), ratiosStr, avgWithoutLoad, avgWithLoad)) cvs_handle.close() # ================================================ # Finalmente simula una vez más con anchos mejores # que encontramos y plotear el resultado # ================================================ if (plot_result and (bestResult.tp > 0.0) and (bestResult.tp < 1.0)): put.set_widths(bestResult.widths) simulator = circuit.simulator(temperature=27, nominal_temperature=27) analysis = simulator.transient(end_time=sim_time*1.5, step_time=step_time) put.plot(analysis, 'In', 'Out')
def script(): logger = Logging.setup_logging() libraries_path = find_libraries() print(libraries_path) spice_library = SpiceLibrary(libraries_path) print(spice_library) circuit = Circuit('Diode Characteristic Curve') circuit.include(spice_library['1N4148']) print(spice_library['1N4148']) # def defaultCircuitParams(): circuit.V('input', 'in', circuit.gnd, 10 @ u_V) circuit.R(1, 'in', 'out', 1 @ u_Ω) # not required for simulation circuit.X('D1', '1N4148', 'out', circuit.gnd) # Voltage and Resistance in 10e-06, X is diode type # def setCircuitParams(V, R, X): # circuit.V('input', 'in', circuit.gnd, V@u_V) # circuit.R(1, 'in', 'out', R@u_Ω) # not required for simulation # circuit.X('D1', X, 'out', circuit.gnd) #r# We simulate the circuit at these temperatures: 0, 25 and 100 °C. # Fixme: Xyce ??? temperatures = [0, 25, 100] @ u_Degree analyses = {} # def simulateTemp(): for temperature in temperatures: simulator = circuit.simulator(temperature=temperature, nominal_temperature=temperature) analysis = simulator.dc(Vinput=slice(-2, 5, .01)) analyses[float(temperature)] = analysis #################################################################################################### #r# We plot the characteristic curve and compare it to the Shockley diode model: #r# #r# .. math:: #r# #r# I_d = I_s \left( e^{\frac{V_d}{n V_T}} - 1 \right) #r# #r# where :math:`V_T = \frac{k T}{q}` #r# #r# In order to scale the reverse biased region, we have to do some hack with Matplotlib. #r# silicon_forward_voltage_threshold = .7 shockley_diode = ShockleyDiode(Is=4e-9, degree=25) def two_scales_tick_formatter(value, position): if value >= 0: return '{} mA'.format(value) else: return '{} nA'.format(value / 100) formatter = ticker.FuncFormatter(two_scales_tick_formatter) figure, (ax1, ax2) = plt.subplots(2, figsize=(20, 10)) ax1.set_title('1N4148 Characteristic Curve ') ax1.set_xlabel('Voltage [V]') ax1.set_ylabel('Current') ax1.grid() ax1.set_xlim(-2, 2) ax1.axvspan(-2, 0, facecolor='green', alpha=.2) ax1.axvspan(0, silicon_forward_voltage_threshold, facecolor='blue', alpha=.1) ax1.axvspan(silicon_forward_voltage_threshold, 2, facecolor='blue', alpha=.2) ax1.set_ylim(-500, 750) # Fixme: round ax1.yaxis.set_major_formatter(formatter) Vd = analyses[25].out # compute scale for reverse and forward region forward_region = Vd >= 0 @ u_V reverse_region = np.invert(forward_region) scale = reverse_region * 1e11 + forward_region * 1e3 #?# check temperature for temperature in temperatures: analysis = analyses[float(temperature)] ax1.plot(Vd, -analysis.Vinput * scale) ax1.plot(Vd, shockley_diode.I(Vd) * scale, 'black') ax1.legend(['@ {} °C'.format(temperature) for temperature in temperatures] + ['Shockley Diode Model Is = 4 nA'], loc=(.02, .8)) ax1.axvline(x=0, color='black') ax1.axhline(y=0, color='black') ax1.axvline(x=silicon_forward_voltage_threshold, color='red') ax1.text(-1, -100, 'Reverse Biased Region', ha='center', va='center') ax1.text(1, -100, 'Forward Biased Region', ha='center', va='center') #r# Now we compute and plot the static and dynamic resistance. #r# #r# .. math:: #r# #r# \frac{d I_d}{d V_d} = \frac{1}{n V_T}(I_d + I_s) #r# #r# .. math:: #r# #r# r_d = \frac{d V_d}{d I_d} \approx \frac{n V_T}{I_d} ax2.set_title('Resistance @ 25 °C') ax2.grid() ax2.set_xlim(-2, 3) ax2.axvspan(-2, 0, facecolor='green', alpha=.2) ax2.axvspan(0, silicon_forward_voltage_threshold, facecolor='blue', alpha=.1) ax2.axvspan(silicon_forward_voltage_threshold, 3, facecolor='blue', alpha=.2) analysis = analyses[25] static_resistance = -analysis.out / analysis.Vinput dynamic_resistance = np.diff(-analysis.out) / np.diff(analysis.Vinput) ax2.semilogy(analysis.out, static_resistance, basey=10) ax2.semilogy(analysis.out[10:-1], dynamic_resistance[10:], basey=10) ax2.axvline(x=0, color='black') ax2.axvline(x=silicon_forward_voltage_threshold, color='red') ax2.axhline(y=1, color='red') ax2.text(-1.5, 1.1, 'R limitation = 1 Ω', color='red') ax2.legend(['{} Resistance'.format(x) for x in ('Static', 'Dynamic')], loc=(.05, .2)) ax2.set_xlabel('Voltage [V]') ax2.set_ylabel('Resistance [Ω]') plt.tight_layout() plt.show()
def do_path_test(tech, put): # ================================ # Encontrar la carpeta de liberias # ================================ libraries_path = find_libraries() # ==================================== # Generar el parte general del netlist # ==================================== circuit = Circuit("Gate_Test" + type(put).__name__ + "_" + tech.NAME) circuit.include(libraries_path + "/" + tech.LIB_NAME) #.inclcude "foo.lib" vdd = circuit.V('dd', 'Vdd', circuit.gnd, tech.VDD) # Fuente de tensión Vdd # =========================== # La ruta que queremos probar # =========================== inSources = [] inSources.append(circuit.V('In', 'In', circuit.gnd, 0)) put.add_to_circuit(circuit, 'Vdd', 'In', 'Out') # Una ruta tiene otras entradas que están puesto en un valor por defecto # Por un fuente propio por cada entrada (que no es la entrada 'In') # Obtener una lista de estos fuentes inSources += put.get_input_sources() # muestra el netlist print(str(circuit)) errors = 0 # 1 entrada - 2 combinaciones # 2 entradas - 4 combinaciones # 3 entradas - 8 combinaciones # n entradas - 2^n combinaciones for inVal in range(2**len(inSources)): # bit 0 de inVal corresponde con entrada 0 # bit 1 de inVal corresponde con entrada 1 # ... # poner los valores correctos en los fuentes de tensión inputs = [] for i in range(len(inSources)): nodeInput = 1 if (inVal & (1 << i)) else 0 # esta entrada es un 0 o un 1? inputs.append(nodeInput) # Guardar una lista de entradas inSources[ i].dc_value = tech.VDD if nodeInput else 0 # Convertir en una tensión expectedOutput = put.get_output_value(inputs) # Simulación simulator = circuit.simulator(temperature=27, nominal_temperature=27) analysis = simulator.operating_point() outputVoltage = float(analysis.out) if (outputVoltage > tech.VDD - 0.1): actualOutput = 1 elif (outputVoltage < 0.1): actualOutput = 0 else: print(str(outputVoltage) + "V is not a valid logic level") errors += 1 continue print("Inputs: " + str(inputs) + " Output: " + str(actualOutput)) if (actualOutput != expectedOutput): print(" Error: " + str(expectedOutput) + " expected") errors += 1 print("Test finished with " + str(errors) + " errors")
def do_gate_test(tech, gut): # ================================ # Encontrar la carpeta de liberias # ================================ libraries_path = find_libraries() # ==================================== # Generar el parte general del netlist # ==================================== circuit = Circuit("Gate_Test" + type(gut).__name__ + "_" + tech.NAME) circuit.include(libraries_path + "/" + tech.LIB_NAME) #.inclcude "foo.lib" circuit.subcircuit(gut) # Añadir el subcircuito vdd = circuit.V('dd', 'Vdd', circuit.gnd, tech.VDD) # Fuente de tensión Vdd # ================================ # La compuerta que queremos probar # ================================ inNodes = [] inFuentes = [] for i in range(gut.get_num_inputs()): node = 'in' + str(i) # in0, in1, ... inNodes.append(node) # Añadir el nodo a la lista inFuentes.append(circuit.V(node, node, circuit.gnd, 0)) # Añadir una fuente de tensión gut.add_instance(circuit, 1, 'Vdd', inNodes, "Out", tech.W_MIN) # muestra el netlist print(str(circuit)) errors = 0 # 1 entrada - 2 combinaciones # 2 entradas - 4 combinaciones # 3 entradas - 8 combinaciones # n entradas - 2^n combinaciones for inVal in range(2**gut.get_num_inputs()): # bit 0 de inVal corresponde con entrada 0 # bit 1 de inVal corresponde con entrada 1 # ... # poner los valores correctos en los fuentes de tensión inputs = [] for i in range(gut.get_num_inputs()): nodeInput = 1 if (inVal & (1 << i)) else 0 # esta entrada es un 0 o un 1? inputs.append(nodeInput) # Guardar una lista de entradas inFuentes[ i].dc_value = tech.VDD if nodeInput else 0 # Convertir en una tensión expectedOutput = gut.get_output_value(inputs) # Simulación simulator = circuit.simulator(temperature=27, nominal_temperature=27) analysis = simulator.operating_point() outputVoltage = float(analysis.out) if (outputVoltage > tech.VDD - 0.1): actualOutput = 1 elif (outputVoltage < 0.1): actualOutput = 0 else: print(str(outputVoltage) + "V is not a valid logic level") errors += 1 continue print("Inputs: " + str(inputs) + " Output: " + str(actualOutput)) if (actualOutput != expectedOutput): print(" Error: " + str(expectedOutput) + " expected") errors += 1 print("Test finished with " + str(errors) + " errors")