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
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
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
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
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
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
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)
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