Exemple #1
0
    def __setitem__(self, i, y):
        """ 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.data[0], '_pyha_update_registers'):
            # object already knows how to handle registers
            # copy relevant stuff only
            for k, v in y.__dict__.items():
                if k.startswith('_pyha'):
                    continue
                setattr(self.data[i], k, v)
                # self.data[i].__dict__['_pyha_next'][k] = v

        else:
            if isinstance(self.data[i], (Sfix, Complex)):
                with SimPath(f'{self.var_name}[{i}]='):
                    y = auto_resize(self.data[i], y)

                # lazy bounds feature, if bounds is None, take the bound from assigned value
                if self.data[i].left is None:
                    for x, xn in zip(self.data, self._pyha_next):
                        x.left = y.left
                        xn.left = y.left

                if self.data[i].right is None:
                    for x, xn in zip(self.data, self._pyha_next):
                        x.right = y.right
                        xn.right = y.right

            if RegisterBehaviour.is_enabled():
                self._pyha_next[i] = y
            else:
                self.data[i] = y
Exemple #2
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
Exemple #3
0
 def _pyha_update_registers(self):
     """ Update registers (eveyrthing in self), called after the return of toplevel 'main' """
     if RegisterBehaviour.is_force_disabled():
         return
     if hasattr(self.data[0], '_pyha_update_registers'):  # is submodule
         for x in self.data:
             x._pyha_update_registers()
     else:
         self.data = self._pyha_next[:]
Exemple #4
0
    def _pyha_update_registers(self):
        """ Update registers (everything in self), called after the return of toplevel 'main' """
        if RegisterBehaviour.is_force_disabled() or self._pyha_is_local():
            return
        # update atoms
        self.__dict__.update(self._pyha_next)

        # update all childs
        for x in self._pyha_updateable:
            x._pyha_update_registers()
Exemple #5
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
Exemple #6
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
Exemple #7
0
    def test_force_disable(self):
        with RegisterBehaviour.force_disable():
            dut = self.T1()
            dut._pyha_update_registers()

            assert dut.i == [1, 2, 3]
            assert dut.b == [True, False, True]

            dut.main(2, False)

            assert dut.i == [2, 2, 3]
            assert dut.b == [False, True, False]

            dut._pyha_update_registers()

            assert dut.i == [2, 2, 3]
            assert dut.b == [False, True, False]
Exemple #8
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
Exemple #9
0
    def test_force_disable(self):
        with RegisterBehaviour.force_disable():
            dut = self.T0()
            dut._pyha_update_registers()

            assert dut.i == 1
            assert dut._pyha_next['i'] == 1
            assert dut.b == True
            assert dut._pyha_next['b'] == True

            dut.main(2, False)

            assert dut.i == 2
            assert dut._pyha_next['i'] == 1
            assert dut.b == False
            assert dut._pyha_next['b'] == True

            dut._pyha_update_registers()

            assert dut.i == 2
            assert dut._pyha_next['i'] == 1
            assert dut.b == False
            assert dut._pyha_next['b'] == True
Exemple #10
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