Esempio n. 1
0
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')
Esempio n. 2
0
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')
Esempio n. 3
0
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')
Esempio n. 4
0
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')
Esempio n. 5
0
####################################################################################################

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'))
Esempio n. 6
0
# 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')
Esempio n. 8
0
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")