Example #1
0
    def __deepcopy__(self, memo):
        cls = self.__class__
        result = cls.__new__(cls)
        memo[id(self)] = result

        with RegisterBehaviour.force_disable():
            with AutoResize.force_disable():
                if SimulationRunning.is_enabled():
                    for k, v in self.__dict__.items():
                        # try:
                        #     local = self._pyha_is_local
                        # except:
                        #     local = False

                        if k.startswith('_pyha'):
                            continue
                        # elif local and isinstance(v, PyhaFunc): # PyhaFunc MUST be copied for initial objects...everything breaks otherwise
                        #     continue
                        else:
                            setattr(result, k, deepcopy(v, memo))
                else:
                    for k, v in self.__dict__.items():
                        if k == '_pyha_initial_self' or k == '_pyha_next' or isinstance(v,
                                                                                        Hardware):  # dont waste time on endless deepcopy
                            setattr(result, k, copy(v))
                            # print(k, v)
                        else:
                            setattr(result, k, deepcopy(v, memo))
        return result
Example #2
0
    def test_basic(self):
        dut = self.A1('saturate', 'round')
        with RegisterBehaviour.enable():
            with AutoResize.enable():
                dut.main(Sfix(0.1, 2, -27))

                assert dut.a._pyha_next[0].left == 0
                assert dut.a._pyha_next[0].right == -4
                assert dut.a._pyha_next[0].val == 0.125

                assert dut.a._pyha_next[1].left == 0
                assert dut.a._pyha_next[1].right == -4
                assert dut.a._pyha_next[1].val == 0.125
Example #3
0
    def __call__(self, *args, **kwargs):
        if PyhaFunc.bypass:
            return self.func(*args, **kwargs)

        self.update_input_types(args, kwargs)
        self.calls += 1

        with SimPath(f'{self.class_name}.{self.function_name}()'):
            with RegisterBehaviour.enable():
                with AutoResize.enable():
                    ret = self.call_with_locals_discovery(*args, **kwargs)

        self.update_output_types(ret)
        return ret
Example #4
0
    def test_basic(self):
        dut = self.A4()
        dut._pyha_enable_function_profiling_for_types()

        with AutoResize.enable():
            dut.main(Sfix(0.1, 2, -27))

            assert dut.a._pyha_next[0].left == 2
            assert dut.a._pyha_next[0].right == -27
            assert dut.a._pyha_next[0].val == 0.09999999403953552

            assert dut.b._pyha_next[0].left == 1
            assert dut.b._pyha_next[0].right == -27
            assert dut.b._pyha_next[0].val == 0.09999999403953552

            assert dut.c._pyha_next[0].left == 2
            assert dut.c._pyha_next[0].right == -4
            assert dut.c._pyha_next[0].val == 0.0625
Example #5
0
    def __setattr__(self, name, value):
        """ Implements auto-resize feature, ie resizes all assigns to Sfix registers.
        Also implements the register behaviour i.e saves assigned value to shadow variable, that is later used by the '_pyha_update_registers' function.
        """
        if hasattr(self, '_pyha_is_initialization') or self._pyha_is_local() or not RegisterBehaviour.is_enabled():
            self.__dict__[name] = value
            return

        if AutoResize.is_enabled():
            target = getattr(self._pyha_initial_self, name)
            with SimPath(f'{name}='):
                value = auto_resize(target, value)

        if isinstance(value, list):
            # list assign
            # example: self.i = [i] + self.i[:-1]
            assert isinstance(self.__dict__[name], PyhaList)
            if hasattr(self.__dict__[name][0], '_pyha_update_registers'):
                # list of submodules -> need to copy each value to submodule next
                for elem, new in zip(self.__dict__[name], value):
                    # for deeper submodules, deepcopy was not necessary..
                    # copy relevant stuff only
                    for k, v in new.__dict__.items():
                        if k.startswith('_pyha'):
                            continue
                        elem.__dict__['_pyha_next'][k] = v
            else:
                self.__dict__[name]._pyha_next = value
            return

        if isinstance(value, Hardware):
            for k, v in value.__dict__.items():
                if k.startswith('_pyha'):
                    continue
                n = self.__dict__[name]
                setattr(n, k, v)
            return

        self.__dict__['_pyha_next'][name] = value
Example #6
0
    def test_basic(self):
        dut = self.A6()
        dut._pyha_enable_function_profiling_for_types()

        with AutoResize.enable():
            dut.main(0)

            assert dut._pyha_next['a'].left == 0
            assert dut._pyha_next['a'].right == -17
            assert dut._pyha_next['a'].val == 0.12299346923828125

            assert dut._pyha_next['b'].left == 2
            assert dut._pyha_next['b'].right == -17
            assert dut._pyha_next['b'].val == -2

            assert dut._pyha_next['c'].left == 0
            assert dut._pyha_next['c'].right == -17
            assert dut._pyha_next['c'].val == 0.779998779296875 - 0.56000518798828125j

            assert dut._pyha_next['c2'].left == 0
            assert dut._pyha_next['c2'].right == -17
            assert dut._pyha_next['c2'].val == 0.779998779296875 - 0.56000518798828125j
Example #7
0
def auto_resize(target, value):
    if not AutoResize.is_enabled() or not isinstance(target, (Sfix, Complex)) or Sfix._float_mode.enabled:
        return value
    if target.bits is not None:
        right = value.right
        try:
            left = right + target.bits
            if target.signed:
                left -= 1  # -1 is to count for the sign bit!
        except TypeError:  # right was None?
            left = None
    elif target.upper_bits is not None:
        left = value.left
        try:
            right = left - target.upper_bits
            if target.signed:
                right += 1  # +1 is to count for the sign bit!
        except TypeError:  # left was None?
            right = None
    else:
        left = target.left if target.left is not None else value.left
        right = target.right if target.right is not None else value.right
    return target(value, left, right)
Example #8
0
def simulate(model,
             *args,
             simulations=None,
             conversion_path=None,
             input_types=None,
             pipeline_flush='self.DELAY',
             trace=False):
    """
    Run simulations on model.

    :param model: Object derived from ``Hardware``. Must have ``main`` function with input/outputs.
    :param *x: Inputs to the 'main' function.
    :param conversion_path: Where the VHDL sources are written, default is temporary directory.
    :param input_types: Force inputs types, default for floats is Sfix[0:-17].
    :param simulations: List of simulations to execute:
    * ``'MODEL'`` passes inputs directly to ``model`` function. If ``model`` does not exist, uses the ``main`` function by turning off register- and fixed point effects.
    * ``'HARDWARE'`` cycle accurate simulator in Python domain, debuggable.
    * ``'RTL'`` converts sources to VHDL and runs RTL simulation by using GHDL simulator.
    * ``'NETLIST'`` runs VHDL sources trough Quartus and uses the generated generated netlist for simulation. Use to gain ~full confidence in your design. It is slow!

    :returns: Dict of output lists for each simulation. Select the output like ``out['MODEL']``.

    Args:
        model: Object derived from *Hardware*. Must have a *main* function.
        *args: Simulation inputs, a list for each argument of the *main* function.
        simulations: List of simulations to execute:
            * 'MODEL'    : executes the *model* function.
            * 'HARDWARE' : executes the *main* function for each input sample i.e. each sample is tied to a clock cycle.
                           Uses a cycle-accurate simulator implemented in Python.
                           TIP: use a debugger to step trough the simulation.
            * 'RTL'      : converts to VHDL and executes with GHDL simulator. Slow!
            * 'NETLIST'  : converts VHDL sources to a 'Quartus netlist' and simulates with GHDL. Painfully slow!

        conversion_path:
        input_types:
        pipeline_flush:
        trace:

    Returns:
        dict:

    """
    from pyha.simulation.tracer import Tracer
    Tracer.traced_objects.clear()
    set_simulator_quartus(None)

    if simulations is None:
        if hasattr(model, 'model'):
            simulations = ['MODEL', 'HARDWARE', 'RTL', 'NETLIST']
        else:
            simulations = ['MODEL_PYHA', 'HARDWARE', 'RTL', 'NETLIST']

    if 'MODEL' in simulations:
        if not hasattr(model, 'model'):
            logger.info('SKIPPING **MODEL** simulations -> no "model()" found')
            simulations.remove('MODEL')

    if 'RTL' in simulations:
        if 'HARDWARE' not in simulations:
            logger.warning(
                'SKIPPING **RTL** simulations -> You need to run "HARDWARE" simulation before "RTL" simulation'
            )
            simulations.remove('RTL')
        elif 'PYHA_SKIP_RTL' in os.environ:
            logger.warning(
                'SKIPPING **RTL** simulations -> "PYHA_SKIP_RTL" environment variable is set'
            )
            simulations.remove('RTL')
        elif Sfix._float_mode.enabled:
            logger.warning(
                'SKIPPING **RTL** simulations -> Sfix._float_mode is active')
            simulations.remove('RTL')

        # TODO: test for docker instead...
        # elif not have_ghdl():
        #     logger.warning('SKIPPING **RTL** simulations -> no GHDL found')

    if 'NETLIST' in simulations:
        if 'HARDWARE' not in simulations:
            logger.warning(
                'SKIPPING **GATE** simulations -> You need to run "HARDWARE" simulation before "NETLIST" simulation'
            )
            simulations.remove('NETLIST')
        elif 'PYHA_SKIP_GATE' in os.environ:
            logger.warning(
                'SKIPPING **GATE** simulations -> "PYHA_SKIP_GATE" environment variable is set'
            )
            simulations.remove('NETLIST')
        elif Sfix._float_mode.enabled:
            logger.warning(
                'SKIPPING **GATE** simulations -> Sfix._float_mode is active')
            simulations.remove('NETLIST')

    if trace:
        simulations = ['MODEL', 'HARDWARE']
        logger.info(
            f'Tracing is enabled, running "MODEL" and "HARDWARE" simulations')
        model._pyha_insert_tracer(label='self')

    out = {}
    if 'MODEL' in simulations:
        logger.info(f'Running "MODEL" simulation...')

        r = model.model(*args)

        try:
            if r.size != 1:
                r = r.squeeze()
        except:
            pass

        # r = np_to_py(r)
        if isinstance(r, tuple):
            r = list(r)

        out['MODEL'] = r
        logger.info(f'OK!')

    if 'MODEL_PYHA' in simulations:
        logger.info(f'Running "MODEL_PYHA" simulation...')
        with RegisterBehaviour.force_disable():
            with Sfix._float_mode:
                tmpmodel = deepcopy(model)
                tmpmodel._pyha_floats_to_fixed(silence=True)

                tmpargs = deepcopy(args)
                tmpargs = convert_input_types(tmpargs,
                                              input_types,
                                              silence=True)
                tmpargs = transpose(tmpargs)

                ret = []
                for input in tmpargs:
                    returns = tmpmodel.main(*input)
                    returns = pyha_to_python(returns)
                    ret.append(returns)
                    tmpmodel._pyha_update_registers()

                ret = process_outputs(0, ret)
        out['MODEL_PYHA'] = ret
        logger.info(f'OK!')

    if 'HARDWARE' in simulations:
        if 'RTL' in simulations or 'NETLIST' in simulations:
            logger.info(
                f'Simulaton needs to support conversion to VHDL -> major slowdown'
            )
            model._pyha_enable_function_profiling_for_types()

        model._pyha_floats_to_fixed()
        if hasattr(model, '_pyha_simulation_input_callback'):
            args = model._pyha_simulation_input_callback(args)
        else:
            args = convert_input_types(args, input_types)
            args = transpose(args)

        delay_compensate = 0
        if pipeline_flush == 'self.DELAY':
            with suppress(AttributeError):
                delay_compensate = model.DELAY

            # duplicate input args to flush pipeline
            target_len = len(args) + delay_compensate
            args += args * int(np.ceil(delay_compensate / len(args)))
            args = args[:target_len]

        logger.info(f'Running "HARDWARE" simulation...')
        ret = []
        valid_samples = 0
        with SimulationRunning.enable():
            with RegisterBehaviour.enable():
                with AutoResize.enable():
                    for input in tqdm(args, file=sys.stderr):
                        returns = model.main(*input)
                        returns = pyha_to_python(returns)
                        if returns is not None:
                            valid_samples += 1
                            ret.append(returns)
                        model._pyha_update_registers()

                    if pipeline_flush == 'auto' and valid_samples != len(
                            out['MODEL']):
                        args = list(args)
                        logger.info(
                            f'Flushing the pipeline to collect {len(out["MODEL"])} valid samples (currently have {valid_samples})'
                        )
                        hardware_delay = 0
                        while valid_samples != len(out["MODEL"]):
                            hardware_delay += 1
                            returns = model.main(*args[-1])
                            returns = pyha_to_python(returns)
                            if returns is not None:
                                valid_samples += 1
                                ret.append(returns)
                            model._pyha_update_registers()
                            args.append(
                                args[-1]
                            )  # collect samples needed to flush the system, so RTL and GATE sims work also!
                        logger.info(f'Flush took {hardware_delay} cycles.')

        out['HARDWARE'] = process_outputs(delay_compensate, ret)
        logger.info(f'OK!')

    if 'RTL' in simulations or 'NETLIST' in simulations:
        converter = Converter(model, output_dir=conversion_path).to_vhdl()
        if 'NETLIST' in simulations:
            make_quartus_project(converter)

    if 'RTL' in simulations:
        logger.info(f'Running "RTL" simulation...')
        ret = run_ghdl_cocotb(*args, converter=converter)
        out['RTL'] = process_outputs(delay_compensate, ret)
        logger.info(f'OK!')

    if 'NETLIST' in simulations:

        logger.info(f'Running "NETLIST" simulation...')
        quartus = QuartusDockerWrapper(converter.base_path)
        set_simulator_quartus(quartus)

        ret = run_ghdl_cocotb(*args,
                              converter=converter,
                              netlist=quartus.get_netlist())
        out['NETLIST'] = process_outputs(delay_compensate, ret)
        logger.info(f'OK!')

    logger.info('Simulations completed!')
    return out