diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index f9bbb02b..e5f85e6c 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -33,6 +33,7 @@ steps: # upload coverage results bash <(curl -s https://codecov.io/bash) label: "test" + timeout_in_minutes: 60 agents: fault2: "true" - command: | diff --git a/.gitignore b/.gitignore index db36abc2..34aa9960 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,8 @@ dist fault.egg-info pytest.ini vnc_logs +.coverage +.magma +coverage.xml +parser.out +parsetab.py diff --git a/fault/__init__.py b/fault/__init__.py index 134526a2..08e2fc92 100644 --- a/fault/__init__.py +++ b/fault/__init__.py @@ -1,7 +1,8 @@ from .wrapped_internal_port import WrappedVerilogInternalPort from .real_type import RealIn, RealOut, RealKind, RealType from .elect_type import ElectIn, ElectOut, ElectKind, ElectType -from .tester import Tester, SymbolicTester, PythonTester, TesterBase +from .tester import (Tester, SymbolicTester, PythonTester, TesterBase, + SynchronousTester) from .power_tester import PowerTester from .value import Value, AnyValue, UnknownValue, HiZ import fault.random diff --git a/fault/actions.py b/fault/actions.py index c3c532c5..004dcbe8 100644 --- a/fault/actions.py +++ b/fault/actions.py @@ -6,6 +6,7 @@ import fault from fault.select_path import SelectPath import fault.expression as expression +from fault.domain_read import domain_read class Action(ABC): @@ -76,6 +77,30 @@ def retarget(self, new_circuit, clock): return cls(self.format_str, *new_ports) +class Read(Action): + def __init__(self, port, params=None): + super().__init__() + self.port = port + self.params = params + + def __getattr__(self, name): + if name == 'value': + err_msg = f'value has not been set for {self}' + err_msg += ', did the simulation finish running yet?' + assert False, err_msg + else: + raise AttributeError + + def __str__(self): + return f"Read({self.port.debug_name})" + + def retarget(self, new_circuit, clock): + cls = type(self) + new_port = new_circuit.interface.ports[str(self.port.name)] + return cls(new_port) + + + def is_inout(port): if isinstance(port, SelectPath): port = port[-1] @@ -94,10 +119,12 @@ def is_output(port): return not port.is_output() +@domain_read class GetValue(Action): - def __init__(self, port): + def __init__(self, port, params): super().__init__() self.port = port + self.params = params self.value = None # value to be assigned after simulation @property diff --git a/fault/background_poke.py b/fault/background_poke.py new file mode 100644 index 00000000..94c6b6dd --- /dev/null +++ b/fault/background_poke.py @@ -0,0 +1,236 @@ +from fault.actions import Poke +import heapq +import math +from functools import total_ordering +import copy +from fault.target import Target +from fault.actions import Delay + + +@total_ordering +class Thread(): + # if sin wave dt is unspecified, use period/default_steps_per_cycle + default_steps_per_cycle = 10 + + # when checking clock value at time t, check time t+epsilon instead + # this avoids ambiguity when landing exactly on the clock edge + # Also, used to check whether a step was supposed to have happened exactly + # on a scheduled update, or is too early and we should reschedule + epsilon = 1e-18 + + def __init__(self, time, poke): + #print('creating thread for', poke, 'at time', time) + self.poke = copy.copy(poke) + self.poke.params = None + self.poke.delay = None + params = poke.delay + self.start = time + self.next_update = time + type_ = params.get('type', 'clock') + + #print('type_ is', type_) + #print(params) + + # Each type must set a get_val(t) function and a dt + if type_ == 'clock': + freq = params.get('freq', 1e6) + period = params.get('period', 1/freq) + freq = 1 / period + duty_cycle = params.get('duty_cycle', 0.5) + initial_value = params.get('initial_value', 0) + + def get_val(t): + cycle_location = ((t - self.start + self.epsilon) / period) % 1 + if initial_value == 0: + return 1 if cycle_location > (1 - duty_cycle) else 0 + else: + return 1 if cycle_location < duty_cycle else 0 + + self.get_val = get_val + self.dt = period/2 + + elif type_ == 'sin': + freq = params.get('freq', 1e6) + period = params.get('period', 1/freq) + freq = 1 / period + amplitude = params.get('amplitude', 1) + offset = params.get('offset', 0) + phase_degrees = params.get('phase_degrees', 0) + conv = math.pi / 180.0 + phase_radians = params.get('phase_radians', phase_degrees * conv) + + def get_val(t): + x = math.sin((t-self.start)*freq*2*math.pi + phase_radians) + return amplitude * x + offset + + self.get_val = get_val + self.dt = params.get('dt', 1 / (freq*self.default_steps_per_cycle)) + + def step(self, t): + ''' + Returns a new Poke object with the correct value set for time t. + Sets the port and value but NOT the delay. + ''' + # TODO don't poke at the same time twice + missed_update_msg = 'Background Poke thread not updated in time' + assert t <= self.next_update + self.epsilon, missed_update_msg + if abs(t - self.next_update) < self.epsilon: + self.next_update = t + self.dt + value = self.get_val(t) + poke = copy.copy(self.poke) + poke.value = value + return poke + + def __lt__(self, other): + return self.next_update < other + + +class ThreadPool(): + # if the next background update is within epsilon of the next manual + # update, then the background one comes first + # Which comes first is arbitrary, but this epsilon makes it consistent + epsilon = 1e-18 + + def __init__(self, time): + self.t = time + self.background_threads = [] + self.active_ports = set() + + def get_next_update_time(self): + if len(self.background_threads) == 0: + return float('inf') + else: + return self.background_threads[0].next_update + + def delay(self, t_delay): + ''' + Create a list of actions that need to happen during this delay. + Returns (free_time, actions), where free_time is the delay that should + happen before the first action in actions. + ''' + #print('Thread pool is doing a delay of ', t_delay, 'at time', self.t) + t = self.t + t_end = self.t + t_delay + # note that free_time here is actually the free time until the next pool update, + # the actual time returned is until the next action and might be less than free_time + free_time = self.get_next_update_time() - self.t + if free_time > t_delay: + self.t = t_end + return (t_delay, []) + else: + actions = [] + t += free_time; + #print('entering while loop') + while self.get_next_update_time() <= t_end + self.epsilon: + thread = heapq.heappop(self.background_threads) + action = thread.step(t) + heapq.heappush(self.background_threads, thread) + + # we had to put the thread back on the heap in order to + # calculate the next update + next_thing_time = min(self.get_next_update_time(), t_end) + #print('\ncalculated next thing time', next_thing_time, 'at time', t) + action.delay = next_thing_time - t + #print('also adding action', action, 'delay', action.delay) + t = next_thing_time + actions.append(action) + + # t_end has less floating point error than t + self.t = t_end + #print('ending updates at time', t_end) + return (free_time, actions) + + def add(self, background_poke): + error_msg = 'Cannot add existing background thread' + assert background_poke.port not in self.active_ports, error_msg + self.active_ports.add(background_poke.port) + thread = Thread(self.t, background_poke) + heapq.heappush(self.background_threads, thread) + + def remove(self, port): + self.active_ports.remove(port) + for thread in self.background_threads: + if thread.poke.port is port: + offender = thread + self.background_threads.remove(offender) + heapq.heapify(self.background_threads) + poke = offender.step(self.t) + poke.delay = 0 + if poke is None: + return [] + else: + return [poke] + + def process(self, action, delay): + new_action_list = [] + is_background = (isinstance(action, Poke) + and type(action.delay) == dict) + + # check whether this is a poke taking over a background port + if (isinstance(action, Poke) + and action.port in self.active_ports): + new_action_list += self.remove(action.port) + + # if the new port is background we must add it before doing delay + if is_background: + self.add(action) + + # we might cut action's delay short to allow some background pokes + new_delay, actions = self.delay(delay) + + # now we add this (shortened) action back in + if not is_background: + # TODO: we used to use copies of the action so we weren't editing + # the delay of an action owned by someone else. But with the new + # Read action it's important that the object doesn't change + # because the user is holding a pointer to the old Read object + if isinstance(action, Delay): + action.time = new_delay + else: + action.delay = new_delay + new_action_list.append(action) + #new_action = copy.copy(action) + #new_action.delay = new_delay + #new_action_list.append(new_action) + + new_action_list += actions + #print('ended up with', len(new_action_list), 'new actions') + #print('delay of first', new_action_list[0].delay, 'last', new_action_list[-1].delay) + #if len(new_action_list) > 1: + # for action in new_action_list: + # print('\t', action, '\t', action.delay) + return new_action_list + + +def process_action_list(actions, clock_step_delay): + """ + Replace Pokes with background_params with many individual pokes. + Automatically interleaves multiple background tasks with other pokes. + Throws a NotImplementedError if there's a background task during an + interval of time not known at compile time. + """ + + def get_delay(a): + if not hasattr(a, 'delay'): + return getattr(a, 'time', 0) + elif a.delay is not None: + if type(a.delay) == dict: + return 0 + else: + return a.delay + else: + return clock_step_delay + + background_pool = ThreadPool(0) + new_action_list = [] + for a in actions: + delay = get_delay(a) + new_action_list += background_pool.process(a, delay) + return new_action_list + +def background_poke_target(cls): + class BackgroundPokeTarget(cls): + def run(self, actions): + actions = process_action_list(actions, self.clock_step_delay) + super().run(actions) + return BackgroundPokeTarget diff --git a/fault/domain_read.py b/fault/domain_read.py new file mode 100644 index 00000000..97054007 --- /dev/null +++ b/fault/domain_read.py @@ -0,0 +1,189 @@ +import numpy as np +from fault.result_parse import SpiceResult, ResultInterp + +# edge finder is used for measuring phase, freq, etc. +class EdgeNotFoundError(Exception): + pass + +def get_value_domain(results, action, time, get_name): + style = action.params['style'] + # TODO is this the right way to get the name? + res = results[get_name(action.port)] + if isinstance(res, ResultInterp): + res_style = 'pwc' + elif isinstance(res, SpiceResult): + res_style = 'spice' + else: + assert False, f'Unrecognized result class {type(res)} for result {res}' + # TODO default height for spice is different and depends on vsup + height = action.params.get('height', 0.5) + if style == 'single': + # equivalent to a regular get_value + value = res(time) + if type(value) == np.ndarray: + value = value.tolist() + action.value = value + elif style == 'edge': + # looking for a nearby rising/falling edge + # look at find_edge for possible parameters + value = find_edge(res_style, res.t, res.v, time, height, **action.params) + action.value = value + elif style == 'frequency': + # frequency based on the (previous?) two rising edges + edges = find_edge(res_style, res.t, res.v, time, height, count=2) + freq = 1 / (edges[0] - edges[1]) + action.value = freq + elif style == 'phase': + # phase of this signal relative to another + assert 'ref' in action.params, 'Phase read requires reference signal param' + res_ref = results[f'{get_name(action.params["ref"])}'] + ref = find_edge(res_style, res_ref.t, res_ref.v, time, height, count=2) + before_cycle_end = find_edge(res_style, res.t, res.v, time + ref[0], height) + fraction = 1 + before_cycle_end[0] / (ref[0] - ref[1]) + # TODO multiply by 2pi? + action.value = fraction + elif style == 'block': + # return a whole chunk of the waveform. + # returns (t, v) where t is time relative to the get_value action + assert 'duration' in action.params, 'Block read requires duration' + duration = action.params['duration'] + # make sure to grab points surrounding requested times so user can interpolate + # the exact start and end. + start = max(0, np.argmax(res.t > time) - 1) + end = (len(res.t) - 1) if res.t[-1] < time + duration else np.argmax(res.t >= time + duration) + + t = res.t[start:end] - time + v = res.v[start:end] + action.value = (t, v) + else: + raise NotImplementedError(f'Unknown style "{style}"') + +def find_edge(res_style, *args, **kwargs): + if res_style == 'spice': + return find_edge_spice(*args, **kwargs) + elif res_style == 'pwc': + return find_edge_pwc(*args, **kwargs) + else: + assert False, f'Unrecognized result style "{res_style}"' + +def find_edge_pwc(x, y, t_start, height, forward=False, count=1, rising=True): + ''' + Search through data (x,y) starting at time t_start for when the + waveform crosses height (defaut is ???). Searches backwards by + default (frequency now is probably based on the last few edges?) + ''' + + # deal with `rising` and `forward` + # normally a low-to-high finder + if (rising ^ forward): + y = [(-1 * z + 2 * height) for z in y] + direction = 1 if forward else -1 + # we want to start on the far side of the interval containing t_start + # to make sure we catch any edge near t_start + side = 'left' if forward else 'right' + + start_index = np.searchsorted(x, t_start, side=side) + if start_index == len(x): + # happens when forward=False and the edge find is the end of the sim + start_index -= 1 + + i = start_index + edges = [] + while len(edges) < count: + # move until we hit low + while y[i] > height: + i += direction + if i < 0 or i >= len(y): + msg = f'only {len(edges)} of requested {count} edges found' + raise EdgeNotFoundError(msg) + # now move until we hit the high + while y[i] <= height: + i += direction + if i < 0 or i >= len(y): + msg = f'only {len(edges)} of requested {count} edges found' + raise EdgeNotFoundError(msg) + + # moving forward the crossing happens eactly when we hit the high value + # moving backward it happens at the last low one we saw (one thing ago) + t = x[i] if direction == 1 else x[i+1] + if t == t_start: + print('EDGE EXACTLY AT EDGE FIND REQUEST') + elif (t - t_start) * direction < 0: + # we probably backed up to consider the point half a step before t_start + # don't count this one, and keep looking + continue + edges.append(t - t_start) + return edges + +def find_edge_spice(x, y, t_start, height, forward=False, count=1, rising=True): + ''' + Search through data (x,y) starting at time t_start for when the + waveform crosses height (defaut is ???). Searches backwards by + default (frequency now is probably based on the last few edges?) + ''' + + # deal with `rising` and `forward` + # normally a low-to-high finder + if (rising ^ forward): + y = [(-1 * z + 2 * height) for z in y] + direction = 1 if forward else -1 + # we want to start on the far side of the interval containing t_start + # to make sure we catch any edge near t_start + side = 'left' if forward else 'right' + + start_index = np.searchsorted(x, t_start, side=side) + if start_index == len(x): + # happens when forward=False and the edge find is the end of the sim + start_index -= 1 + + i = start_index + edges = [] + while len(edges) < count: + # move until we hit low + while y[i] > height: + i += direction + if i < 0 or i >= len(y): + msg = f'only {len(edges)} of requested {count} edges found' + raise EdgeNotFoundError(msg) + # now move until we hit the high + while y[i] <= height: + i += direction + if i < 0 or i >= len(y): + msg = f'only {len(edges)} of requested {count} edges found' + raise EdgeNotFoundError(msg) + + # the crossing happens from i to i+1 + # fraction is how far you must go from i to (i-direction) to hit the crossing + fraction = (height - y[i]) / (y[i - direction] - y[i]) + # TODO for a long time this said "x[i + 1] - x[i]", I should triple-check that it's correct now + t = x[i] + fraction * (x[i-direction] - x[i]) + if t == t_start: + print('EDGE EXACTLY AT EDGE FIND REQUEST') + elif (t - t_start) * direction < 0: + # we probably backed up to consider the point half a step before t_start + # don't count this one, and keep looking + continue + edges.append(t - t_start) + return edges + +def domain_read(cls): + class DomainGetValue(cls): + def is_domain_read(self): + return type(self.params) == dict and 'style' in self.params + def get_format(self): + if self.is_domain_read(): + # this is a domain read + # this is pretty hacky... + return '%0.15f\\n", $realtime/1s);//' + else: + return super().get_format() + + def update_from_line(self, line): + if self.is_domain_read(): + # here we temporily put the time in self.value + # the time will be used as an index into the waveform file, and value will be changed later + self.value = float(line.strip()) + else: + super().update_from_line(line) + + return DomainGetValue diff --git a/fault/mlingua_interface.py b/fault/mlingua_interface.py new file mode 100644 index 00000000..6f8616f8 --- /dev/null +++ b/fault/mlingua_interface.py @@ -0,0 +1,55 @@ +from collections import defaultdict +from fault.actions import Poke + +def mlingua_target(cls): + class MLinguaTarget(cls): + + def is_mlingua(self, action): + return hasattr(action, 'port') and getattr(action.port, 'representation', None)=='mlingua' + + def run(self, actions): + # check if there are any mLingua actions and set up mLingua acordingly + has_ml_poke = any(self.is_mlingua(a) for a in actions) + if has_ml_poke: + #self.add_decl('PWLMethod', 'pm') + #self.add_assign('pm', 'new') + self.add_decl('PWLMethod', 'pm=new') + self.add_decl('`get_timeunit', '') + self.includes.append('mLingua_pwl.vh') + + # sort out pokes + pokes = defaultdict(list) + time = 0 + for a in actions: + if self.is_mlingua(a): + p = a.port + pokes[p].append((time, a)) + # TODO am I missing other ways the time can advance? certainly flow control will be a problem + # Cast to float here is only to make it throw an exception earlier + delay_attr = getattr(a, 'delay', 0) + delay = 0 if delay_attr == None else float(delay_attr) + time += delay + + # update pokes + for poke_list in pokes.values(): + # look at the value of the current and next point, replace value with 'pm.write(p0, slope, time)' + for i,(t,poke) in enumerate(poke_list): + next_val = poke_list[i+1][1].value if i != len(poke_list)-1 else poke.value + next_time = poke_list[i+1][0] if i != len(poke_list)-1 else 1 + assert next_time != t, f'Cannot have 2 PWL pokes at the same time, check {poke.port}' + slope = (next_val - poke.value) / (next_time - t) + poke.value = f'pm.write({poke.value}, {slope}, `get_time)' + + super().run(actions) + + # Originally I was going to do everything in make_poke, but I can't because I need to know where the + # value is going next + #def make_poke(self, i, action): + # if is_mlingua(action): + # # edit the action's value to be mLingua friendly + # style = getattr(action, 'style', 'pwc') + # pwl = (style == 'pwl' or style == 'pwc_or_pwl') + # + # + # super().make_poke(i, action) + return MLinguaTarget diff --git a/fault/pwl.py b/fault/pwl.py index 5965fc4f..1310163a 100644 --- a/fault/pwl.py +++ b/fault/pwl.py @@ -11,10 +11,30 @@ def pwc_to_pwl(pwc, t_stop, t_tr, init=0): t_prev, v_prev = pwc[k - 1] t_curr, v_curr = pwc[k] + if retval[-1][0] >= t_curr: + assert retval[-2][0] <= t_curr, \ + 'non-increasing pwc steps at time' % t_curr + if abs(retval[-2][0] - t_curr) < 1e-19: + # two values at the same time, just drop the earlier + #print('DROPPING old thing, diff:', retval[-2][0] - t_curr) + retval.pop() + old_t, old_v = retval.pop() + v_prev = old_v + else: + # make the previous thing happen faster than t_tr + #print('DOING THE HALFWAY THING') + halfway_time = (retval[-2][0] + t_curr)/2 + retval[-1] = (halfway_time, retval[-1][1]) + + #print('times', t_curr, t_curr + t_tr) retval += [(t_curr, v_prev)] retval += [(t_curr + t_tr, v_curr)] # add final value + # cut off anything after t_stop + while len(retval) > 0 and retval[-1][0] >= t_stop: + #print('removing one from end') + retval.pop() retval += [(t_stop, pwc[-1][1])] # return new waveform diff --git a/fault/real_type.py b/fault/real_type.py index 0c521602..967b93cb 100644 --- a/fault/real_type.py +++ b/fault/real_type.py @@ -11,6 +11,9 @@ def __init__(self, *largs, **kwargs): def is_oriented(cls, direction): return cls.direction == direction + # TODO: dstanley - is this hash still necessary? + __hash__ = Type.__hash__ + def wired(self): return self._wire.wired() @@ -23,6 +26,9 @@ def value(self): def driven(self): return self._wire.driven() + def flatten(self): + return [self] + class RealKind(Kind): __hash__ = Kind.__hash__ @@ -67,6 +73,8 @@ def flip(cls): return RealIn return cls + __hash__ = Kind.__hash__ + def MakeReal(**kwargs): return RealKind('Real', (RealType,), kwargs) diff --git a/fault/result_parse.py b/fault/result_parse.py index 808b3fc4..d3b3ebdc 100644 --- a/fault/result_parse.py +++ b/fault/result_parse.py @@ -1,3 +1,6 @@ +import re +from fault.real_type import Real + try: import numpy as np from scipy.interpolate import interp1d @@ -14,6 +17,16 @@ def __init__(self, t, v): def __call__(self, t): return self.func(t) + +# TODO should probably rename this to something like "PWCResult" +class ResultInterp: + def __init__(self, t, v, interp='linear'): + self.t = t + self.v = v + self.func = interp1d(t, v, bounds_error=False, fill_value=(v[0], v[-1]), kind=interp) + + def __call__(self, t): + return self.func(t) # temporary measure -- CSDF parsing is broken in @@ -116,3 +129,85 @@ def data_to_interp(data, time, strip_vi=True): # return results return retval + + +def parse_vcd(filename, dut, interp='previous'): + # unfortunately this vcd parser has an annoyin quirk: + # it throws away the format specifier for numbers so we can't tell if they're binary or real + # so "reg [7:0] a = 8'd4;" and "real a = 100.0;" both come back as the string '100' + # TODO fix this + filename = 'build/' + filename + + # library doesn't grab timescale, so we do it manually + with open(filename) as f: + next = False + for line in f: + if next: + ts = line.strip().split() + break + if '$timescale' in line: + next = True + else: + assert False, f'Timescale not found in vcd {filename}' + + scales = { + 'fs': 1e-15, + 'ps': 1e-12, + 'ns': 1e-9, + 'us': 1e-6, + 'ms': 1e-3, + 's': 1e0 + } + if ts[1] not in scales: + assert False, f'Unrecognized timescale {ts[1]}' + timescale = float(ts[0]) * scales[ts[1]] + + from vcdvcd import VCDVCD + obj = VCDVCD(filename) + + def get_name_from_v(v): + name_vcd = v['references'][0].split('.')[-1] + name_fault = re.sub('\[[0-9]*:[0-9]*\]', '', name_vcd) + return name_fault + + def format(name, val_str): + if not hasattr(dut, name): + # we don't know what the type is, but we know scipy will try to cast it to float + # we can't assume bits becasue mlingua etol stuff is in this category + # we can't assume real becuase bit types with value 'x' come through here too + try: + return float(val_str) + except ValueError: + return float('nan') + + a = getattr(dut, name) + b = isinstance(type(a), type(Real)) + + if b: + return float(val_str) + else: + # TODO deal with x and z + # atm it breaks if the value can't be cast to a float + if val_str == 'x': + return float('nan') + elif val_str == 'z': + return float('nan') + return int(val_str, 2) + + data = obj.get_data() + data = {get_name_from_v(v): v['tv'] for v in data.values()} + end_time = obj.get_endtime() + + retval = {} + for port, vs in data.items(): + t, v = zip(*vs) + t, v = list(t), list(v) + # append an additional point at the end time + t.append(end_time) + v.append(v[-1]) + t, v = [time * timescale for time in t], [format(port, val) for val in v] + + r = ResultInterp(t, v, interp=interp) + retval[port] = r + + return retval diff --git a/fault/spice_target.py b/fault/spice_target.py index 043775ce..4212864f 100644 --- a/fault/spice_target.py +++ b/fault/spice_target.py @@ -5,15 +5,18 @@ import magma as m import fault import hwtypes +import numpy as np from fault.target import Target from fault.spice import SpiceNetlist from fault.real_type import RealInOut from fault.result_parse import nut_parse, hspice_parse, psf_parse from fault.subprocess_run import subprocess_run from fault.pwl import pwc_to_pwl -from fault.actions import Poke, Expect, Delay, Print, GetValue, Eval +from fault.actions import Poke, Expect, Delay, Print, GetValue, Eval, Read +from fault.background_poke import background_poke_target from fault.select_path import SelectPath from .fault_errors import A2DError, ExpectError +from fault.domain_read import get_value_domain try: from decida.SimulatorNetlist import SimulatorNetlist @@ -21,7 +24,6 @@ except ModuleNotFoundError: print('Failed to import DeCiDa or Numpy for SpiceTarget.') - class CompiledSpiceActions: def __init__(self, pwls, checks, prints, stop_time, saves, gets): self.pwls = pwls @@ -76,10 +78,11 @@ def DeclareFromSpice(file_name, subckt_name=None, mode='digital'): ''' +@background_poke_target class SpiceTarget(Target): def __init__(self, circuit, directory="build/", simulator='ngspice', vsup=1.0, rout=1, model_paths=None, sim_env=None, - t_step=None, clock_step_delay=5, t_tr=0.2e-9, vil_rel=0.4, + t_step=None, clock_step_delay=5e-9, t_tr=0.2e-9, vil_rel=0.4, vih_rel=0.6, rz=1e9, conn_order='parse', bus_delim='<>', bus_order='descend', flags=None, ic=None, cap_loads=None, disp_type='on_error', mc_runs=0, mc_variations='all', @@ -205,13 +208,15 @@ def __init__(self, circuit, directory="build/", simulator='ngspice', # set list of signals to save self.saves = set() for name, port in self.circuit.interface.ports.items(): - if isinstance(port, m.BitsType): + if (isinstance(port, m.BitsType) + or isinstance(port, m.ArrayType)): for k in range(len(port)): self.saves.add(self.bit_from_bus(name, k)) else: self.saves.add(f'{name}') def run(self, actions): + # compile the actions comp = self.compile_actions(actions) @@ -230,8 +235,13 @@ def run(self, actions): # run the simulation commands if not self.no_run: - subprocess_run(cmd, cwd=self.directory, env=self.sim_env, + res = subprocess_run(cmd, cwd=self.directory, env=self.sim_env, disp_type=self.disp_type) + #print(res.stdout) + stderr = res.stderr.strip() + if stderr != '': + print('Stderr from spice simulator:') + print(stderr) # process the results for raw_file in raw_files: @@ -244,6 +254,16 @@ def run(self, actions): else: raise NotImplementedError(self.simulator) + #import matplotlib.pyplot as plt + #print(results.keys()) + #print('HELLO') + #in_ = results['in_'] + #out = results['out'] + #plt.plot(in_.x, in_.y, '-o') + #plt.plot(out.x, out.y, '-o') + #plt.grid() + #plt.show() + # print results self.print_results(results=results, prints=comp.prints) @@ -292,12 +312,19 @@ def compile_actions(self, actions): prints = [] gets = [] + # TODO is this still necessary? + saves = set() + # expand buses as needed _actions = [] for action in actions: - if isinstance(action, (Poke, Expect)) \ + if isinstance(action, (Poke, Expect, Read)) \ and isinstance(action.port, m.Bits): _actions += self.expand_bus(action) + elif (isinstance(action, (Poke, Expect, Read)) + and isinstance(action.port.name, m.ref.ArrayRef)): + action.port = self.select_bit_from_bus(action.port) + _actions.append(action) else: _actions.append(action) actions = _actions @@ -326,13 +353,27 @@ def compile_actions(self, actions): pwc_dict[action_port_name][1].append((t, stim_s)) # increment time if desired if action.delay is None: - t += self.clock_step_delay * 1e-9 + t += self.clock_step_delay else: t += action.delay elif isinstance(action, Expect): checks.append((t, action)) elif isinstance(action, Print): prints.append((t, action)) + + # TODO: is this still necessary? + for port in action.ports: + saves.add(f'{port.name}') + # TODO read do we need this code about refs? + # elif isinstance(action, Read): + # reads.append((t, action)) + # self.saves.add(f'{action.port.name}') + # # phase could be relative to another signal + # if 'ref' in action.params: + # if isinstance(action.params['ref'].name, m.ref.ArrayRef): + # ref = self.select_bit_from_bus(action.params['ref']) + # action.params['ref'] = ref + # self.saves.add(f'{action.params["ref"].name}') elif isinstance(action, GetValue): gets.append((t, action)) elif isinstance(action, Delay): @@ -382,6 +423,17 @@ def bit_from_bus(self, port, k): else: raise Exception(f'Unknown bus delimeter: {self.bus_delim}') + def select_bit_from_bus(self, port): + # The default way magma deals with naming one pin of a bus + # does not match our spice convention. We need to get the + # name of the original bus and index it ourselves. + bus_name = port.name.array.name + bus_index = port.name.index + bit_name = self.bit_from_bus(bus_name, bus_index) + new_port = m.Bit(name=bit_name) + return new_port + + def get_alpha_ordered_ports(self): # get ports sorted in alphabetical order port_names = self.circuit.interface.ports.keys() @@ -567,15 +619,41 @@ def print_results(self, results, prints): for print_ in prints: self.impl_print(results=results, time=print_[0], action=print_[1]) + def process_reads(self, results, reads): + for time, read in reads: + res = results[f'{read.port.name}'] + def impl_all_gets(self, results, gets): for get in gets: self.impl_get(results=results, time=get[0], action=get[1]) def impl_get(self, results, time, action): - # get port values - port_value = results[f'{action.port.name}'](time) - # write value back to action - action.value = port_value + # TODO: use this same function in more places? + def get_spice_name(p): + if isinstance(p.name, m.ref.ArrayRef): + return str(self.select_bit_from_bus(p)) + else: + return f'{p.name}' + + # grab the relevant info + res = results[get_spice_name(action.port)] + if action.params == None: + # straightforward read of voltage + # get port values + port_value = res(time) + # write value back to action + action.value = port_value + elif type(action.params) == dict and 'style' in action.params: + # requires some analysis of signal + #print(action.params) + # get height of slice point based on spice config if not specified + if 'height' not in action.params: + action.params['height'] = self.vsup * (self.vih_rel + self.vil_rel) / 2 + # some syles (e.g. phase) might need to reference another port, + # so passing in the name is not good enough + get_value_domain(results, action, time, get_spice_name) + else: + raise NotImplementedError def ngspice_cmds(self, tb_file): # build up the command diff --git a/fault/system_verilog_target.py b/fault/system_verilog_target.py index d29cd8a7..d1305da8 100644 --- a/fault/system_verilog_target.py +++ b/fault/system_verilog_target.py @@ -13,18 +13,24 @@ from fault.select_path import SelectPath from fault.wrapper import PortWrapper from fault.subprocess_run import subprocess_run +from fault.background_poke import background_poke_target +from fault.mlingua_interface import mlingua_target import fault import fault.expression as expression from fault.real_type import RealKind import os from numbers import Number +import re src_tpl = """\ +{includes} {timescale} module {top_module}; {declarations} {assigns} +{clock_drivers} + {circuit_name} #( {param_list} ) dut ( @@ -39,7 +45,8 @@ endmodule """ - +@background_poke_target +@mlingua_target class SystemVerilogTarget(VerilogTarget): # Language properties of SystemVerilog used in generating code blocks @@ -50,14 +57,14 @@ class SystemVerilogTarget(VerilogTarget): def __init__(self, circuit, circuit_name=None, directory="build/", skip_compile=None, magma_output="coreir-verilog", magma_opts=None, include_verilog_libraries=None, - simulator=None, timescale="1ns/1ns", clock_step_delay=5, + simulator=None, timescale="1ns/1ns", clock_step_delay=5e-9, num_cycles=10000, dump_waveforms=True, dump_vcd=None, no_warning=False, sim_env=None, ext_model_file=None, ext_libs=None, defines=None, flags=None, inc_dirs=None, ext_test_bench=False, top_module=None, ext_srcs=None, use_input_wires=False, parameters=None, disp_type='on_error', waveform_file=None, coverage=False, use_kratos=False, - use_sva=False, skip_run=False): + use_sva=False, skip_run=False, no_top_module=False): """ circuit: a magma circuit @@ -139,6 +146,9 @@ def __init__(self, circuit, circuit_name=None, directory="build/", skip_run: If True, generate all the files (testbench, tcl, etc.) but do not run the simulator. + + no_top_module: If True, do not specify a top module for simulation + (default is False, meaning *do* specify the top module) """ # set default for list of external sources if include_verilog_libraries is None: @@ -163,6 +173,16 @@ def __init__(self, circuit, circuit_name=None, directory="build/", include_verilog_libraries, magma_output, magma_opts, coverage=coverage, use_kratos=use_kratos) + # set default for top_module. this comes after the super constructor + # invocation, because that is where the self.circuit_name is assigned + if top_module is None: + if use_kratos: + top_module = 'TOP' + elif ext_test_bench: + top_module = f'{self.circuit_name}' + else: + top_module = f'{self.circuit_name}_tb' + # sanity check if simulator is None: raise ValueError("Must specify simulator when using system-verilog" @@ -175,6 +195,7 @@ def __init__(self, circuit, circuit_name=None, directory="build/", self.timescale = timescale self.clock_step_delay = clock_step_delay self.num_cycles = num_cycles + self.clock_drivers = [] self.dump_waveforms = dump_waveforms if dump_vcd is not None: warnings.warn("tester.compile_and_run parameter dump_vcd is " @@ -192,7 +213,7 @@ def __init__(self, circuit, circuit_name=None, directory="build/", self.flags = flags if flags is not None else [] self.inc_dirs = inc_dirs if inc_dirs is not None else [] self.ext_test_bench = ext_test_bench - self.top_module = top_module if not use_kratos else "TOP" + self.top_module = top_module self.use_input_wires = use_input_wires self.parameters = parameters if parameters is not None else {} self.disp_type = disp_type @@ -207,6 +228,7 @@ def __init__(self, circuit, circuit_name=None, directory="build/", raise NotImplementedError(self.simulator) self.use_kratos = use_kratos self.skip_run = skip_run + self.no_top_module = no_top_module # check to see if runtime is installed if use_kratos: import sys @@ -216,6 +238,7 @@ def __init__(self, circuit, circuit_name=None, directory="build/", raise ImportError("Cannot find kratos-runtime in the system. " "Please do \"pip install kratos-runtime\" " "to install.") + self.includes = [] def add_decl(self, type_, name, exist_ok=False): if str(name) in self.declarations: @@ -320,7 +343,7 @@ def make_poke(self, i, action): return retval def make_delay(self, i, action): - return [f'#({action.time}*1s);'] + return [f'$write("MAKING DELAY\\n"); #({action.time}*1s);'] def make_print(self, i, action): # build up argument list for the $write command @@ -525,7 +548,11 @@ def generate_port_code(self, name, type_, power_args): issubclass(type_.T, m.Digital): width_str = f" [{len(type_) - 1}:0]" if isinstance(type_, RealKind): - t = "real" + print('name,type', name, type_) + if getattr(getattr(self.circuit, name), 'representation', None) == 'mlingua': + t = "pwl" + else: + t = "real" elif name in power_args.get("supply0s", []): t = "supply0" elif name in power_args.get("supply1s", []): @@ -573,6 +600,7 @@ def generate_code(self, actions, power_args): # set up probing if self.dump_waveforms and self.simulator == "vcs": + print('vcs') initial_body += [f'$vcdplusfile("{self.waveform_file}");', f'$vcdpluson();', f'$vcdplusmemon();'] @@ -586,6 +614,18 @@ def generate_code(self, actions, power_args): if any(isinstance(action, GetValue) for action in actions): actions = [FileOpen(self.value_file)] + actions actions += [FileClose(self.value_file)] + + # check for reading in other domains + domain_read_ports = set() + for action in actions: + if not isinstance(action, GetValue): + continue + if type(action.params) == dict and 'style' in action.params: + domain_read_ports.add( action.port) + for port in domain_read_ports: + print('Interesting read on', port) + # TODO in the future, if not self.dump_waveforms, we need to dump this port anyway + pass # handle all of user-specified actions in the testbench for i, action in enumerate(actions): @@ -606,29 +646,31 @@ def generate_code(self, actions, power_args): declarations = '\n'.join(declarations) # format assignments - assigns = [f'{self.TAB}assign {lhs}={rhs};' + assigns = [f'{self.TAB}assign {lhs}' \ + f'={rhs};' for lhs, rhs in self.assigns.values()] assigns = '\n'.join(assigns) - # determine the top module name - if self.top_module: - top_module = self.top_module - else: - top_module = f'{self.circuit_name}_tb' - # add timescale timescale = f'`timescale {self.timescale}' + + # add includes + includes = '\n'.join(f'`include "{f}"' for f in self.includes) + + clock_drivers = self.TAB + "\n{self.TAB}".join(self.clock_drivers) # fill out values in the testbench template src = src_tpl.format( + includes=includes, timescale=timescale, declarations=declarations, + clock_drivers=clock_drivers, assigns=assigns, initial_body=initial_body, port_list=port_list, param_list=param_list, circuit_name=self.circuit_name, - top_module=top_module + top_module=self.top_module ) # return the string representing the system-verilog testbench @@ -683,9 +725,6 @@ def run(self, actions, power_args=None): else: raise NotImplementedError(self.simulator) - # add any extra flags - sim_cmd += self.flags - # link the library over if using kratos to debug if self.use_kratos: self.link_kratos_lib() @@ -694,7 +733,7 @@ def run(self, actions, power_args=None): return # compile the simulation - subprocess_run(sim_cmd, cwd=self.directory, env=self.sim_env, + completed_sim = subprocess_run(sim_cmd, cwd=self.directory, env=self.sim_env, err_str=sim_err_str, disp_type=self.disp_type) # run the simulation binary (if applicable) @@ -705,6 +744,7 @@ def run(self, actions, power_args=None): # post-process GetValue actions self.post_process_get_value_actions(actions) + def write_test_bench(self, actions, power_args): # determine the path of the testbench file tb_file = self.directory / Path(f'{self.circuit_name}_tb.sv') @@ -777,48 +817,45 @@ def write_vivado_tcl(self, sources=None, proj_name='project', proj_dir=None, tcl_cmds = [] # create the project - create_proj = f'create_project -force {proj_name} {proj_dir}' + create_proj = f'create_project -force {{{proj_name}}} {{{proj_dir}}}' if proj_part is not None: - create_proj += f' -part "{proj_part}"' + create_proj += f' -part {{{proj_part}}}' tcl_cmds += [create_proj] # add source files and library files vlog_add_files = [] - vlog_add_files += [f'{src}' for src in sources] - vlog_add_files += [f'{lib}' for lib in self.ext_libs] + vlog_add_files += [f'{{{src}}}' for src in sources] + vlog_add_files += [f'{{{lib}}}' for lib in self.ext_libs] if len(vlog_add_files) > 0: vlog_add_files = ' '.join(vlog_add_files) - tcl_cmds += [f'add_files "{vlog_add_files}"'] + tcl_cmds += [f'add_files [list {vlog_add_files}]'] # add include file search paths if len(self.inc_dirs) > 0: - vlog_inc_dirs = ' '.join(f'{dir_}' for dir_ in self.inc_dirs) - tcl_cmds += [f'set_property include_dirs "{vlog_inc_dirs}" [get_fileset sim_1]'] # noqa + vlog_inc_dirs = ' '.join(f'{{{dir_}}}' for dir_ in self.inc_dirs) + vlog_inc_dirs = f'[list {vlog_inc_dirs}]' + tcl_cmds += [f'set_property include_dirs {vlog_inc_dirs} [get_fileset sim_1]'] # noqa # add verilog `defines vlog_defs = [] for key, val in self.defines.items(): if val is not None: - vlog_defs += [f'{key}={val}'] + vlog_defs += [f'{{{key}={val}}}'] else: - vlog_defs += [f'{key}'] + vlog_defs += [f'{{{key}}}'] if len(vlog_defs) > 0: vlog_defs = ' '.join(vlog_defs) - tcl_cmds += [f'set_property -name "verilog_define" -value {{{vlog_defs}}} -objects [get_fileset sim_1]'] # noqa + vlog_defs = f'[list {vlog_defs}]' + tcl_cmds += [f'set_property -name verilog_define -value {vlog_defs} -objects [get_fileset sim_1]'] # noqa # set the name of the top module - if self.top_module is None and not self.ext_test_bench: - top = f'{self.circuit_name}_tb' - else: - top = self.top_module - if top is not None: - tcl_cmds += [f'set_property -name top -value {top} -objects [get_fileset sim_1]'] # noqa + if not self.no_top_module: + tcl_cmds += [f'set_property -name top -value {{{self.top_module}}} -objects [get_fileset sim_1]'] # noqa else: - # have Vivado pick the top module automatically if not specified - tcl_cmds += [f'update_compile_order -fileset sim_1'] + tcl_cmds += ['update_compile_order -fileset sim_1'] # run until $finish (as opposed to running for a certain amount of time) - tcl_cmds += [f'set_property -name "xsim.simulate.runtime" -value "-all" -objects [get_fileset sim_1]'] # noqa + tcl_cmds += [f'set_property -name xsim.simulate.runtime -value -all -objects [get_fileset sim_1]'] # noqa # run the simulation tcl_cmds += ['launch_simulation'] @@ -846,15 +883,12 @@ def ncsim_cmd(self, sources, cmd_file): # binary name cmd += ['irun'] - # determine the name of the top module - if self.top_module is None and not self.ext_test_bench: - top = f'{self.circuit_name}_tb' if not self.use_kratos else "TOP" - else: - top = self.top_module + # add any extra flags + cmd += self.flags # send name of top module to the simulator - if top is not None: - cmd += ['-top', f'{top}'] + if not self.no_top_module: + cmd += ['-top', f'{self.top_module}'] # timescale cmd += ['-timescale', f'{self.timescale}'] @@ -901,6 +935,9 @@ def vivado_cmd(self, cmd_file): # binary name cmd += ['vivado'] + # add any extra flags + cmd += self.flags + # run from an external script cmd += ['-mode', 'batch'] @@ -920,6 +957,9 @@ def vcs_cmd(self, sources): # binary name cmd += ['vcs'] + # add any extra flags + cmd += self.flags + # timescale cmd += [f'-timescale={self.timescale}'] @@ -957,6 +997,10 @@ def vcs_cmd(self, sources): if self.dump_waveforms: cmd += ['+vcs+vcdpluson', '-debug_pp'] + # specify top module + if not self.no_top_module: + cmd += ['-top', f'{self.top_module}'] + # return arg list and binary file location return cmd, './simv' @@ -966,6 +1010,9 @@ def iverilog_cmd(self, sources): # binary name cmd += ['iverilog'] + # add any extra flags + cmd += self.flags + # output file bin_file = f'{self.circuit_name}_tb' cmd += [f'-o{bin_file}'] @@ -997,5 +1044,24 @@ def iverilog_cmd(self, sources): # source files cmd += [f'{src}' for src in sources] + # set the top module + if not self.no_top_module: + cmd += ['-s', f'{self.top_module}'] + # return arg list and binary file location return cmd, bin_file + + +class SynchronousSystemVerilogTarget(SystemVerilogTarget): + def __init__(self, *args, clock=None, **kwargs): + if clock is None: + raise ValueError("Clock required") + + super().__init__(*args, **kwargs) + name = verilog_name(clock.name) + self.clock_drivers.append( + f"always #{self.clock_step_delay} {name} = ~{name};" + ) + + def make_step(self, i, action): + return [f"#{self.clock_step_delay * action.steps}"] diff --git a/fault/tester/__init__.py b/fault/tester/__init__.py index a744d770..4e4926d3 100644 --- a/fault/tester/__init__.py +++ b/fault/tester/__init__.py @@ -2,3 +2,4 @@ from .staged_tester import Tester from .symbolic_tester import SymbolicTester from .interactive_tester import PythonTester +from .synchronous import SynchronousTester diff --git a/fault/tester/base.py b/fault/tester/base.py index 1ef5ca68..97fe9b9c 100644 --- a/fault/tester/base.py +++ b/fault/tester/base.py @@ -214,7 +214,6 @@ def wait_until_low(self, signal): def wait_until_high(self, signal): raise NotImplementedError - def wait_until_negedge(self, signal): self.wait_until_high(signal) self.wait_until_low(signal) @@ -270,8 +269,8 @@ def __call__(self, *args, **kwargs): defn_outputs = [port for port in self._circuit.interface.outputs() if not isinstance(port, m.ClockTypes)] if num_args != len(defn_outputs): - logging.warn("Number of arguments to __call__ did not match " - "number of circuit inputs") + logging.warning("Number of arguments to __call__ did not match " + "number of circuit inputs") for arg, port in zip(args, defn_outputs): self.poke(port, arg) for key, value in kwargs.items(): diff --git a/fault/tester/interactive_tester.py b/fault/tester/interactive_tester.py index 6f956cb3..2a2c0e73 100644 --- a/fault/tester/interactive_tester.py +++ b/fault/tester/interactive_tester.py @@ -92,8 +92,8 @@ def print(self, format_str, *args): values = () for value, port in zip(got, args): if (isinstance(port, m.Array) and - issubclass(port.T, m.Digital)): - value = BitVector[len(port)](value).as_uint() + issubclass(port.T, m.Digital)): + value = BitVector[len(port)](value).as_uint() elif isinstance(port, m.Array): raise NotImplementedError("Printing complex nested " "arrays") diff --git a/fault/tester/staged_tester.py b/fault/tester/staged_tester.py index d6ddc7c9..3d919bca 100644 --- a/fault/tester/staged_tester.py +++ b/fault/tester/staged_tester.py @@ -79,6 +79,9 @@ def __init__(self, circuit: m.Circuit, clock: m.Clock = None, """ super().__init__(circuit, clock, reset, poke_delay_default, expect_strict_default) + if clock is not None: + # Default initalize clock to zero (e.g. for SV targets) + self.poke(clock, 0) self.targets = {} # For public verilator modules self.verilator_includes = [] @@ -162,12 +165,12 @@ def delay(self, time): """ self.actions.append(actions.Delay(time=time)) - def get_value(self, port): + def get_value(self, port, params=None): """ Returns an object with a "value" property that will be filled after the simulation completes. """ - action = actions.GetValue(port=port) + action = actions.GetValue(port=port, params=params) self.actions.append(action) return action @@ -178,12 +181,6 @@ def step(self, steps=1): if self.clock is None: raise RuntimeError("Stepping tester without a clock (did you " "specify a clock during initialization?)") - if not self.clock_initialized and \ - not self.clock_init_warning_reported: - warnings.warn("Clock has not been initialized, the initial value " - "will be X for system verilog targets. In a future " - "release, this will be an error", DeprecationWarning) - self.clock_init_warning_reported = True self.actions.append(actions.Step(self.clock, steps)) def serialize(self): @@ -352,13 +349,11 @@ def _while(self, cond): `actions` list. """ while_tester = LoopTester(self._circuit, self.clock) - while_tester.clock_initialized = self.clock_initialized self.actions.append(While(cond, while_tester.actions)) return while_tester def _if(self, cond): if_tester = IfTester(self._circuit, self.clock) - if_tester.clock_initialized = self.clock_initialized self.actions.append(If(cond, if_tester.actions, if_tester.else_actions)) return if_tester @@ -422,3 +417,6 @@ def __init__(self, circuit: m.Circuit, clock: m.Clock = None): def _else(self): return ElseTester(self.else_actions, self.circuit, self.clock) + + +StagedTester = Tester diff --git a/fault/tester/synchronous.py b/fault/tester/synchronous.py new file mode 100644 index 00000000..60a23f48 --- /dev/null +++ b/fault/tester/synchronous.py @@ -0,0 +1,27 @@ +from .staged_tester import StagedTester +from ..system_verilog_target import SynchronousSystemVerilogTarget +from ..verilator_target import SynchronousVerilatorTarget + + +class SynchronousTester(StagedTester): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if self.clock is None: + raise ValueError("SynchronousTester requires a clock") + # Default clock to 0 + self.poke(self.clock, 0) + + def eval(self): + raise TypeError("Cannot eval with synchronous tester") + + def advance_cycle(self): + self.step(2) + + def make_target(self, target, **kwargs): + if target == "system-verilog": + return SynchronousSystemVerilogTarget(self._circuit, + clock=self.clock, **kwargs) + if target == "system-verilog": + return SynchronousVerilatorTarget(self._circuit, clock=self.clock, + **kwargs) + return super().make_target(target, **kwargs) diff --git a/fault/user_cfg.py b/fault/user_cfg.py index b58ed863..242649c0 100644 --- a/fault/user_cfg.py +++ b/fault/user_cfg.py @@ -15,8 +15,8 @@ def read_cfg_files(self): try: import yaml except ModuleNotFoundError: - logging.warn('pyyaml not found, cannot parse config files.') - logging.warn('Please run "pip install pyyaml" to fix this.') + logging.warning('pyyaml not found, cannot parse config files.') + logging.warning('Please run "pip install pyyaml" to fix this.') return locs = [Path.home() / '.faultrc', Path('.') / 'fault.yml'] @@ -27,8 +27,8 @@ def read_cfg_files(self): new_opts = yaml.safe_load(f) self.opts.update(new_opts) except yaml.YAMLError as yaml_err: - logging.warn(f'Skipping config file {loc} due to a parsing error. Error message:') # noqa - logging.warn(f'{yaml_err}') + logging.warning(f'Skipping config file {loc} due to a parsing error. Error message:') # noqa + logging.warning(f'{yaml_err}') def get_sim_env(self): env = os.environ.copy() diff --git a/fault/value_utils.py b/fault/value_utils.py index 39317a35..b9b1b452 100644 --- a/fault/value_utils.py +++ b/fault/value_utils.py @@ -1,5 +1,6 @@ import fault import magma +import magma as m from hwtypes import BitVector, Bit from fault.value import AnyValue, UnknownValue, HiZ from fault.real_type import RealType, RealKind @@ -9,6 +10,8 @@ def make_value(type_, value): + if isinstance(type_, m.MagmaProtocolMeta): + type_ = type_._to_magma_() if issubclass(type_, RealType): return make_real(value) if issubclass(type_, magma.Digital): diff --git a/fault/verilator_target.py b/fault/verilator_target.py index e75c8976..b256620f 100644 --- a/fault/verilator_target.py +++ b/fault/verilator_target.py @@ -659,7 +659,8 @@ def add_assumptions(self, circuit, actions, i): if assumption.has_randvals: randval = next(assumption.randvals)[str(port.name)] else: - randval = constrained_random_bv(len(assume_port), pred) + randval = constrained_random_bv(len(assume_port), + pred) code = self.make_poke( len(actions) + i, Poke(port, randval)) for line in code: @@ -696,3 +697,21 @@ def add_guarantees(self, circuit, actions, i): }} """ return main_body + + +class SynchronousVerilatorTarget(VerilatorTarget): + def __init__(self, *args, clock=None, **kwargs): + if clock is None: + raise ValueError("Clock required") + self.clock = verilator_name(clock.name) + + def make_step(self, i, action): + code = [] + for _ in range(action.steps): + code.append(f"top->{self.clock} ^= 1;") + code.append("top->eval();") + code.append("main_time++;") + code.append("#if VM_TRACE") + code.append("tracer->dump(main_time);") + code.append("#endif") + return code diff --git a/fault/verilog_target.py b/fault/verilog_target.py index 91b29551..8315668d 100644 --- a/fault/verilog_target.py +++ b/fault/verilog_target.py @@ -7,7 +7,8 @@ from fault.util import flatten import os from fault.select_path import SelectPath - +from fault.result_parse import parse_vcd +from fault.domain_read import get_value_domain class VerilogTarget(Target): """ @@ -286,12 +287,26 @@ def make_block(self, i, name, cond, actions): def post_process_get_value_actions(self, all_actions): get_value_actions = [action for action in all_actions if isinstance(action, actions.GetValue)] + + # sort out actions with parmams + get_value_actions_params = [action for action in get_value_actions if action.params != None] + print(get_value_actions_params) + if len(get_value_actions) > 0: with open(self.value_file.name, 'r') as f: lines = f.readlines() for line, action in zip(lines, get_value_actions): action.update_from_line(line) + def get_name(port): + return str(port.name) + if len(get_value_actions_params) > 0: + # TODO waveform_file is technically a property of systemverilog target only, so this is weird for verilator + res = parse_vcd(self.waveform_file, self.circuit) + for a in get_value_actions_params: + # the time has already been temporarily stored in a.value + get_value_domain(res, a, a.value, get_name) + @staticmethod def in_var(file): '''Name of variable used to read in contents of file.''' diff --git a/fault/verilogams.py b/fault/verilogams.py index 5abcbc70..828cb858 100644 --- a/fault/verilogams.py +++ b/fault/verilogams.py @@ -92,15 +92,18 @@ def VAMSWrap(circ, wrap_name=None, inst_name=None, tab=' ', nl='\n'): gen.end_module() # Create magma circuit for wrapper - magma_args = [] + io_kwargs = {} for name, type_ in circ.IO.ports.items(): - magma_args += [name, type_] - retval = m.DeclareCircuit(f'{wrap_name}', *magma_args) + io_kwargs[f'{name}'] = type_ - # Poke the VerilogAMS code into the magma circuit - # (a bit hacky, probably should use a subclass instead) - retval.vams_code = gen.text - retval.vams_inst_name = inst_name + # declare circuit that will be returned + class wrap_cls(m.Circuit): + name = f'{wrap_name}' + io = m.IO(**io_kwargs) + + # Poke the VerilogAMS code into the magma circuit (a bit hacky...) + wrap_cls.vams_code = gen.text + wrap_cls.vams_inst_name = inst_name # Return the magma circuit - return retval + return wrap_cls diff --git a/setup.py b/setup.py index 733b7edf..d8087c63 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ setup( name='fault', - version='3.0.7', + version='3.0.10', description=DESCRIPTION, scripts=[], packages=[ diff --git a/tests/test_background_poke.py b/tests/test_background_poke.py new file mode 100644 index 00000000..8a208d3e --- /dev/null +++ b/tests/test_background_poke.py @@ -0,0 +1,129 @@ +import magma as m +import fault +import tempfile +import pytest +from pathlib import Path +from .common import pytest_sim_params, TestBasicCircuit + +def plot(xs, ys): + import matplotlib.pyplot as plt + plt.plot(xs, ys, '*') + plt.grid() + plt.show() + +def pytest_generate_tests(metafunc): + #pytest_sim_params(metafunc, 'verilator', 'system-verilog') + #pytest_sim_params(metafunc, 'spice') + pytest_sim_params(metafunc, 'system-verilog') + +#@pytest.mark.skip(reason='Not yet implemented') +def test_clock_verilog(target, simulator): + print('target, sim', target, simulator) + # TODO delete the next line; my iverilog is just broken so I can't test it + simulator = 'ncsim' + circ = TestBasicCircuit + tester = fault.Tester(circ) + tester.zero_inputs() + tester.poke(circ.I, 1) + #tester.eval() + tester.expect(circ.O, 1) + + # register clock + tester.poke(circ.I, 0, delay={ + 'freq': 0.125, + 'duty_cycle': 0.625, + # take default initial_value of 0 + }) + + tester.expect(circ.O, 1) + #tester.expect(circ.O, 0) # should fail + + tester.print("%08x", circ.O) + + #with tempfile.TemporaryDirectory(dir=".") as _dir: + #with open('build/') as _dir: + if True: + _dir = 'build' + if target == "verilator": + tester.compile_and_run(target, directory=_dir, flags=["-Wno-fatal"]) + else: + tester.compile_and_run(target, directory=_dir, simulator=simulator) + print('JUST FINISHED COMPILENANDRUN') + + +#@pytest.mark.skip(reason='Turn this back on later') +def test_sin_spice(vsup=1.5, vil_rel=0.4, vih_rel=0.6, + vol_rel=0.1, voh_rel=0.9): + # TODO make pytest choose target/simulator + target = 'spice' + simulator = 'ngspice' + + + + # declare circuit + myinv = m.DeclareCircuit( + 'myinv', + 'in_', fault.RealIn, + 'out', fault.RealOut, + 'vdd', fault.RealIn, + 'vss', fault.RealIn + ) + + # wrap if needed + if target == 'verilog-ams': + dut = fault.VAMSWrap(myinv) + else: + dut = myinv + + # define the test + tester = fault.Tester(dut) + tester.poke(dut.vdd, vsup) + tester.poke(dut.vss, 0) + freq = 1e3 + tester.poke(dut.in_, 0, delay = { + 'type': 'sin', + 'freq': freq, + 'amplitude': 0.4, + 'offset': 0.6, + 'phase_degrees': 90 + }) + + num_reads = 100 + xs = [] + dt = 1/(freq * 50) + gets = [] + for k in range(num_reads): + gets.append(tester.get_value(dut.in_)) + tester.delay(dt) + xs.append(k*dt) + + #for k in [.4, .5, .6]: + # in_ = k * vsup + # tester.poke(dut.in_, in_) + # # We might not know the expected value now but will want to check later + # tester.expect(dut.out, 0, save_for_later=True) + + + + # set options + kwargs = dict( + target=target, + simulator=simulator, + model_paths=[Path('tests/spice/myinv.sp').resolve()], + vsup=vsup, + tmp_dir=True, + clock_step_delay = 0 + ) + if target == 'verilog-ams': + kwargs['use_spice'] = ['myinv'] + + # run the simulation + tester.compile_and_run(**kwargs) + + ys = [] + for k in range(num_reads): + value = gets[k].value + ys.append(value) + print('%2d\t'%k, value) + + #plot(xs, ys) diff --git a/tests/test_bidir.py b/tests/test_bidir.py index 40add809..62859876 100644 --- a/tests/test_bidir.py +++ b/tests/test_bidir.py @@ -11,11 +11,11 @@ def pytest_generate_tests(metafunc): def test_bidir(target, simulator): # declare an external circuit that shorts together its two outputs - bidir = m.DeclareCircuit( - 'bidir', - 'a', m.InOut(m.Bit), - 'b', m.InOut(m.Bit) - ) + class bidir(m.Circuit): + io = m.IO( + a=m.InOut(m.Bit), + b=m.InOut(m.Bit) + ) # instantiate the tester tester = fault.Tester(bidir, poke_delay_default=0) diff --git a/tests/test_def_vlog.py b/tests/test_def_vlog.py index d06a78d2..3caad09e 100644 --- a/tests/test_def_vlog.py +++ b/tests/test_def_vlog.py @@ -10,11 +10,11 @@ def pytest_generate_tests(metafunc): def test_def_vlog(target, simulator, n_bits=8, b_val=42): # declare circuit - defadd = m.DeclareCircuit( - 'defadd', - 'a_val', m.In(m.Bits[n_bits]), - 'c_val', m.Out(m.Bits[n_bits]) - ) + class defadd(m.Circuit): + io = m.IO( + a_val=m.In(m.Bits[n_bits]), + c_val=m.Out(m.Bits[n_bits]) + ) # instantiate tester tester = fault.Tester(defadd) diff --git a/tests/test_env_mod.py b/tests/test_env_mod.py index 2618e564..5736fad5 100644 --- a/tests/test_env_mod.py +++ b/tests/test_env_mod.py @@ -9,10 +9,16 @@ def pytest_generate_tests(metafunc): def test_env_mod(target, simulator): - myinv = m.DefineCircuit('myinv', 'a', m.In(m.Bit), 'y', m.Out(m.Bit)) - m.wire(~myinv.a, myinv.y) - m.EndDefine() + class myinv(m.Circuit): + io = m.IO( + a=m.In(m.Bit), + y=m.Out(m.Bit) + ) + io.y @= ~io.a tester = fault.InvTester(myinv, in_='a', out='y') - - tester.compile_and_run(target=target, simulator=simulator, tmp_dir=True) + tester.compile_and_run( + target=target, + simulator=simulator, + tmp_dir=True + ) diff --git a/tests/test_ext_tb.py b/tests/test_ext_tb.py index 6d6b74e6..f5ffd7a9 100644 --- a/tests/test_ext_tb.py +++ b/tests/test_ext_tb.py @@ -8,9 +8,11 @@ def pytest_generate_tests(metafunc): pytest_sim_params(metafunc, 'system-verilog') -def test_ext_vlog(target, simulator): - tester = fault.Tester(m.DeclareCircuit('mytb')) +def test_ext_tb(target, simulator): + class mytb(m.Circuit): + io = m.IO() + tester = fault.Tester(mytb) tester.compile_and_run( target=target, simulator=simulator, diff --git a/tests/test_ext_vlog.py b/tests/test_ext_vlog.py index 874cf5a8..c2af7005 100644 --- a/tests/test_ext_vlog.py +++ b/tests/test_ext_vlog.py @@ -10,11 +10,11 @@ def pytest_generate_tests(metafunc): def test_ext_vlog(target, simulator): # declare circuit - myinv = m.DeclareCircuit( - 'myinv', - 'in_', m.In(m.Bit), - 'out', m.Out(m.Bit) - ) + class myinv(m.Circuit): + io = m.IO( + in_=m.In(m.Bit), + out=m.Out(m.Bit) + ) # define test tester = fault.InvTester(myinv) diff --git a/tests/test_get_value_analog.py b/tests/test_get_value_analog.py index 444a30cc..67c3dcbb 100644 --- a/tests/test_get_value_analog.py +++ b/tests/test_get_value_analog.py @@ -10,12 +10,12 @@ def pytest_generate_tests(metafunc): def test_get_value_analog(target, simulator): # declare circuit - myblend = m.DeclareCircuit( - 'myblend', - 'a', fault.RealIn, - 'b', fault.RealIn, - 'c', fault.RealOut, - ) + class myblend(m.Circuit): + io = m.IO( + a=fault.RealIn, + b=fault.RealIn, + c=fault.RealOut + ) # wrap if needed if target == 'verilog-ams': diff --git a/tests/test_get_value_domain_digital.py b/tests/test_get_value_domain_digital.py new file mode 100644 index 00000000..bf5329be --- /dev/null +++ b/tests/test_get_value_domain_digital.py @@ -0,0 +1,89 @@ +from pathlib import Path +import fault +import magma as m +from .common import pytest_sim_params + + +def pytest_generate_tests(metafunc): + pytest_sim_params(metafunc, 'system-verilog') + # TODO I don't think this works in verilator yet? + #pytest_sim_params(metafunc, 'system-verilog', 'verilator') + + +class MyAdder(m.Circuit): + io = m.IO(a=m.In(m.UInt[4]), + b=m.Out(m.UInt[4])) + + io.b @= io.a + 1 + + +def test_get_value_digital(target, simulator): + # TODO get rid of this when we test on a system with things installed + #target = 'system-verilog' + simulator = 'ncsim' + + # define test + tester = fault.Tester(MyAdder) + + # provide stimulus + stim = [2, 3, 2, 3, 2, 3] + output = [] + freq = 20e6 + for a in stim: + tester.poke(MyAdder.a, a, delay=(.5/freq)) + tester.eval() + + output.append(tester.get_value(MyAdder.b, params = { + 'style': 'frequency', + 'height': 3.5 + })) + + + + # run the test + kwargs = dict( + target=target, + #tmp_dir=True + ) + if target == 'system-verilog': + kwargs['simulator'] = simulator + elif target == 'verilator': + kwargs['flags'] = ['-Wno-fatal'] + tester.compile_and_run(**kwargs) + + # check the results + assert freq/1.05 < output[0].value < freq*1.05 + + +def test_real_val(target, simulator): + simulator = 'ncsim' + # define the circuit + class realadd(m.Circuit): + io = m.IO(a_val=fault.RealIn, b_val=fault.RealIn, c_val=fault.RealOut) + + # define test content + stim = [1, 5, 1, 5, 1, 5] + tester = fault.Tester(realadd) + tester.poke(realadd.b_val, 3) + tester.eval() + + for v in stim: + tester.poke(realadd.a_val, v, delay=125e-9) + tester.eval() + + res = tester.get_value(realadd.c_val, params = { + 'style' : 'frequency', + 'height' : 6 + }) + + # run the test + tester.compile_and_run( + target=target, + simulator=simulator, + ext_libs=[Path('tests/verilog/realadd.sv').resolve()], + defines={f'__{simulator.upper()}__': None}, + ext_model_file=True, + #tmp_dir=True + ) + + print(res.value) diff --git a/tests/test_hi_z.py b/tests/test_hi_z.py index 734b8750..530d2a56 100644 --- a/tests/test_hi_z.py +++ b/tests/test_hi_z.py @@ -10,13 +10,13 @@ def pytest_generate_tests(metafunc): def test_hi_z(target, simulator): - # declare an external circuit that shorts together its two outputs - hizmod = m.DeclareCircuit( - 'hizmod', - 'a', m.In(m.Bit), - 'b', m.In(m.Bit), - 'c', m.Out(m.Bit) - ) + # declare circuit + class hizmod(m.Circuit): + io = m.IO( + a=m.In(m.Bit), + b=m.In(m.Bit), + c=m.Out(m.Bit) + ) # instantiate the tester tester = fault.Tester(hizmod, poke_delay_default=0) diff --git a/tests/test_inc_dir.py b/tests/test_inc_dir.py index a36c503d..ee98c305 100644 --- a/tests/test_inc_dir.py +++ b/tests/test_inc_dir.py @@ -10,11 +10,11 @@ def pytest_generate_tests(metafunc): def test_ext_vlog(target, simulator): # declare circuit - mybuf_inc_test = m.DeclareCircuit( - 'mybuf_inc_test', - 'in_', m.In(m.Bit), - 'out', m.Out(m.Bit) - ) + class mybuf_inc_test(m.Circuit): + io = m.IO( + in_=m.In(m.Bit), + out=m.Out(m.Bit) + ) # define the test tester = fault.BufTester(mybuf_inc_test) diff --git a/tests/test_include_verilog.py b/tests/test_include_verilog.py index baa11075..716d4641 100644 --- a/tests/test_include_verilog.py +++ b/tests/test_include_verilog.py @@ -17,15 +17,22 @@ def pytest_generate_tests(metafunc): def test_include_verilog(target, simulator): - SB_DFF = m.DeclareCircuit('SB_DFF', "D", m.In(m.Bit), "Q", m.Out(m.Bit), - "C", m.In(m.Clock)) - main = m.DefineCircuit('main', "I", m.In(m.Bit), "O", m.Out(m.Bit), - *m.ClockInterface()) - ff = SB_DFF() - m.wire(ff.D, main.I) - m.wire(ff.Q, main.O) - m.EndDefine() - + # define flip-flop (external implementation) + class SB_DFF(m.Circuit): + io = m.IO( + D=m.In(m.Bit), + Q=m.Out(m.Bit), + C=m.In(m.Clock) + ) + + # define main circuit that instantiates external flip-flop + class main(m.Circuit): + io = m.IO(I=m.In(m.Bit), O=m.Out(m.Bit)) + m.ClockIO() + ff = SB_DFF() + ff.D @= io.I + io.O @= ff.Q + + # define the test tester = fault.Tester(main, main.CLK) tester.poke(main.CLK, 0) tester.poke(main.I, 1) @@ -33,6 +40,8 @@ def test_include_verilog(target, simulator): tester.expect(main.O, 0) tester.step(2) tester.expect(main.O, 1) + + # define location of flip-flop implementation sb_dff_filename = pathlib.Path("tests/sb_dff_sim.v").resolve() kwargs = {} diff --git a/tests/test_init_cond.py b/tests/test_init_cond.py index 602f7e95..04fa2d91 100644 --- a/tests/test_init_cond.py +++ b/tests/test_init_cond.py @@ -11,12 +11,13 @@ def pytest_generate_tests(metafunc): def test_init_cond(target, simulator, va=1.234, vb=2.345, vc=3.456, abs_tol=1e-3): # declare circuit - mycirc = m.DeclareCircuit( - 'my_init_cond', - 'va', fault.RealOut, - 'vb', fault.RealOut, - 'vc', fault.RealOut - ) + class mycirc(m.Circuit): + name = 'my_init_cond' + io = m.IO( + va=fault.RealOut, + vb=fault.RealOut, + vc=fault.RealOut + ) # wrap if needed if target == 'verilog-ams': diff --git a/tests/test_inv_tf.py b/tests/test_inv_tf.py index 3fc5c0bb..e2161ede 100644 --- a/tests/test_inv_tf.py +++ b/tests/test_inv_tf.py @@ -13,13 +13,13 @@ def test_inv_tf( vol_rel=0.1, voh_rel=0.9 ): # declare circuit - myinv = m.DeclareCircuit( - 'myinv', - 'in_', fault.RealIn, - 'out', fault.RealOut, - 'vdd', fault.RealIn, - 'vss', fault.RealIn - ) + class myinv(m.Circuit): + io = m.IO( + in_=fault.RealIn, + out=fault.RealOut, + vdd=fault.RealIn, + vss=fault.RealIn + ) # wrap if needed if target == 'verilog-ams': diff --git a/tests/test_logic.py b/tests/test_logic.py index 61577573..5fa3f99c 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -9,33 +9,40 @@ def pytest_generate_tests(metafunc): def test_inv(target, simulator): - name = 'myinv' - ports = ['in_', m.BitIn, 'out', m.BitOut] - run_generic(name=name, ports=ports, tester_cls=fault.InvTester, - target=target, simulator=simulator) + circuit_name = 'myinv' + ports = dict(in_=m.BitIn, out=m.BitOut) + run_generic(circuit_name=circuit_name, ports=ports, + tester_cls=fault.InvTester, target=target, + simulator=simulator) def test_nand(target, simulator): - name = 'mynand' - ports = ['a', m.BitIn, 'b', m.BitIn, 'out', m.BitOut] - run_generic(name=name, ports=ports, tester_cls=fault.NandTester, - target=target, simulator=simulator) + circuit_name = 'mynand' + ports = dict(a=m.BitIn, b=m.BitIn, out=m.BitOut) + run_generic(circuit_name=circuit_name, ports=ports, + tester_cls=fault.NandTester, target=target, + simulator=simulator) def test_sram(target, simulator): - name = 'mysram' - ports = ['wl', m.BitIn, 'lbl', m.BitInOut, 'lblb', m.BitInOut] - run_generic(name=name, ports=ports, tester_cls=fault.SRAMTester, - target=target, simulator=simulator) + circuit_name = 'mysram' + ports = dict(wl=m.BitIn, lbl=m.BitInOut, lblb=m.BitInOut) + run_generic(circuit_name=circuit_name, ports=ports, + tester_cls=fault.SRAMTester, target=target, + simulator=simulator) -def run_generic(name, ports, tester_cls, target, simulator, vsup=1.5): - # declare the circuit, adding supply pins if needed +def run_generic(circuit_name, ports, tester_cls, target, simulator, vsup=1.5): + # add supply pins if needed if target in ['verilog-ams', 'spice']: ports = ports.copy() - ports += ['vdd', m.BitIn] - ports += ['vss', m.BitIn] - dut = m.DeclareCircuit(name, *ports) + ports['vdd'] = m.BitIn + ports['vss'] = m.BitIn + + # declare circuit + class dut(m.Circuit): + name = circuit_name + io = m.IO(**ports) # define the test content tester = tester_cls(dut) @@ -49,12 +56,12 @@ def run_generic(name, ports, tester_cls, target, simulator, vsup=1.5): if target in ['verilog-ams', 'system-verilog']: kwargs['ext_model_file'] = True if target in ['verilog-ams', 'spice']: - kwargs['model_paths'] = [Path(f'tests/spice/{name}.sp').resolve()] + kwargs['model_paths'] = [Path(f'tests/spice/{circuit_name}.sp').resolve()] # noqa kwargs['vsup'] = vsup if target == 'verilog-ams': - kwargs['use_spice'] = [name] + kwargs['use_spice'] = [circuit_name] if target == 'system-verilog': - kwargs['ext_libs'] = [Path(f'tests/verilog/{name}.v').resolve()] + kwargs['ext_libs'] = [Path(f'tests/verilog/{circuit_name}.v').resolve()] # noqa # compile and run tester.compile_and_run(**kwargs) diff --git a/tests/test_mlingua_target.py b/tests/test_mlingua_target.py new file mode 100644 index 00000000..a7f6b81f --- /dev/null +++ b/tests/test_mlingua_target.py @@ -0,0 +1,174 @@ +import magma as m +import fault +import tempfile +import pytest +from pathlib import Path +from .common import pytest_sim_params, TestBasicCircuit +from fault.real_type import RealIn, RealOut, Real + +def plot(xs, ys): + import matplotlib.pyplot as plt + plt.plot(xs, ys, '*') + plt.grid() + plt.show() + +def pytest_generate_tests(metafunc): + #pytest_sim_params(metafunc, 'verilator', 'system-verilog') + #pytest_sim_params(metafunc, 'spice') + pytest_sim_params(metafunc, 'system-verilog') + +#@pytest.mark.skip(reason='Not yet implemented') +def test_system_verilog(target, simulator): + print('target, sim', target, simulator) + # TODO delete the next line; my iverilog is just broken so I can't test it + simulator = 'ncsim' + + # class RealPwl(Real): + # pass + # RealPwlIn = m.In(RealPwl) + + circ = m.DeclareCircuit( + 'mlingua_add', + 'a_val', RealIn, + 'b_val', RealIn, + 'c_val', RealOut, + ) + + #print(circ.a_val, type(circ.a_val)) + #print(circ.b_val, type(circ.b_val)) + #asdf + + for p in [circ.a_val, circ.b_val, circ.c_val]: + p.representation = 'mlingua' + + tester = fault.Tester(circ) + #tester.zero_inputs() + tester.poke(circ.a_val, 0.0) + tester.delay(100e-9) + tester.poke(circ.a_val, 5.0) + #tester.eval() + #tester.expect(circ.O, 1) + + ## register clock + #tester.poke(circ.I, 0, delay={ + # 'freq': 0.125, + # 'duty_cycle': 0.625, + # # take default initial_value of 0 + # }) + + #tester.expect(circ.O, 1) + + #tester.print("%08x", circ.O) + + #with tempfile.TemporaryDirectory(dir=".") as _dir: + #with open('build/') as _dir: + + # set up some mLingua things + defines = {'NCVLOG':True, 'DAVE_TIMEUNIT':'1fs'} + inc_dirs = ['${mLINGUA_DIR}/samples', + '${mLINGUA_DIR}/samples/prim', + '${mLINGUA_DIR}/samples/stim', + '${mLINGUA_DIR}/samples/meas', + '${mLINGUA_DIR}/samples/misc'] + flags = ['+libext+.sv', + '+libext+.vp', + '-loadpli1 ${mLINGUA_DIR}/samples/pli/libpli.so:dave_boot'] + # for mLingua to work the following file needs to be imported at the top of the tb + # Path('${mLINGUA_DIR}/samples/mLingua_pwl.vh').resolve() + + ext_libs = [Path('tests/verilog/mlingua_add.sv').resolve()] + #flags.append('-Wno-fatal') + + if True: + _dir = 'build' + if target == "verilator": + assert False + tester.compile_and_run(target, directory=_dir, flags=["-Wno-fatal"]) + else: + tester.compile_and_run(target, + directory=_dir, + simulator=simulator, + defines=defines, + inc_dirs=inc_dirs, + flags=flags, + ext_libs=ext_libs, + ext_model_file=True) + print('JUST FINISHED COMPILENANDRUN') + + +#@pytest.mark.skip(reason='Turn this back on later') +def test_sin_spice(vsup=1.5, vil_rel=0.4, vih_rel=0.6, + vol_rel=0.1, voh_rel=0.9): + # TODO make pytest choose target/simulator + target = 'spice' + simulator = 'ngspice' + + + + # declare circuit + myinv = m.DeclareCircuit( + 'myinv', + 'in_', fault.RealIn, + 'out', fault.RealOut, + 'vdd', fault.RealIn, + 'vss', fault.RealIn + ) + + # wrap if needed + if target == 'verilog-ams': + dut = fault.VAMSWrap(myinv) + else: + dut = myinv + + # define the test + tester = fault.Tester(dut) + tester.poke(dut.vdd, vsup) + tester.poke(dut.vss, 0) + freq = 1e3 + tester.poke(dut.in_, 0, delay = { + 'type': 'sin', + 'freq': freq, + 'amplitude': 0.4, + 'offset': 0.6, + 'phase_degrees': 90 + }) + + num_reads = 100 + xs = [] + dt = 1/(freq * 50) + gets = [] + for k in range(num_reads): + gets.append(tester.get_value(dut.in_)) + tester.delay(dt) + xs.append(k*dt) + + #for k in [.4, .5, .6]: + # in_ = k * vsup + # tester.poke(dut.in_, in_) + # # We might not know the expected value now but will want to check later + # tester.expect(dut.out, 0, save_for_later=True) + + + + # set options + kwargs = dict( + target=target, + simulator=simulator, + model_paths=[Path('tests/spice/myinv.sp').resolve()], + vsup=vsup, + tmp_dir=True, + clock_step_delay = 0 + ) + if target == 'verilog-ams': + kwargs['use_spice'] = ['myinv'] + + # run the simulation + tester.compile_and_run(**kwargs) + + ys = [] + for k in range(num_reads): + value = gets[k].value + ys.append(value) + print('%2d\t'%k, value) + + #plot(xs, ys) diff --git a/tests/test_param_vlog.py b/tests/test_param_vlog.py index 91647fe4..39bb87a7 100644 --- a/tests/test_param_vlog.py +++ b/tests/test_param_vlog.py @@ -10,11 +10,11 @@ def pytest_generate_tests(metafunc): def test_def_vlog(target, simulator, n_bits=8, b_val=76): # declare circuit - paramadd = m.DeclareCircuit( - 'paramadd', - 'a_val', m.In(m.Bits[n_bits]), - 'c_val', m.Out(m.Bits[n_bits]) - ) + class paramadd(m.Circuit): + io = m.IO( + a_val=m.In(m.Bits[n_bits]), + c_val=m.Out(m.Bits[n_bits]) + ) # instantiate tester tester = fault.Tester(paramadd) diff --git a/tests/test_protocol.py b/tests/test_protocol.py new file mode 100644 index 00000000..5b8c6fd7 --- /dev/null +++ b/tests/test_protocol.py @@ -0,0 +1,51 @@ + +import magma as m +import fault + + +class MWrapperMeta(m.MagmaProtocolMeta): + def __getitem__(cls, T): + assert cls is MWrapper + return type(cls)(f'MWrapper[{T}]', (cls,), {'_T_': T}) + + def _to_magma_(cls): + return cls._T_ + + def _qualify_magma_(cls, d): + return MWrapper[cls._T_.qualify(d)] + + def _flip_magma_(cls): + return MWrapper[cls._T_.flip()] + + def _from_magma_value_(cls, value): + return cls(value) + + +class MWrapper(m.MagmaProtocol, metaclass=MWrapperMeta): + def __init__(self, val): + if not isinstance(val, type(self)._T_): + raise TypeError() + self._value_ = val + + def _get_magma_value_(self): + return self._value_ + + def apply(self, f): + return f(self._value_) + + +WrappedBits8 = MWrapper[m.UInt[8]] + + +@m.syntax.sequential.sequential +class Foo: + def __call__(self, val: WrappedBits8) -> m.UInt[8]: + return val.apply(lambda x: x + 1) + + +def test_proto(): + tester = fault.Tester(Foo) + tester.circuit.val = 1 + tester.eval() + tester.circuit.O.expect(2) + tester.compile_and_run("verilator", flags=['-Wno-unused']) diff --git a/tests/test_read_port.py b/tests/test_read_port.py new file mode 100644 index 00000000..113e7d29 --- /dev/null +++ b/tests/test_read_port.py @@ -0,0 +1,64 @@ +import magma as m +import fault +from pathlib import Path +from .common import pytest_sim_params + + +def pytest_generate_tests(metafunc): + pytest_sim_params(metafunc, 'verilog-ams', 'spice') + + +def test_inv_tf( + target, simulator, vsup=1.5, vil_rel=0.4, vih_rel=0.6, + vol_rel=0.1, voh_rel=0.9 +): + #target = 'verilog-ams' + #simulator = 'ncsim' + target = 'spice' + simulator = 'ngspice' + # declare circuit + myinv = m.DeclareCircuit( + 'myinv', + 'in_', fault.RealIn, + 'out', fault.RealOut, + 'vdd', fault.RealIn, + 'vss', fault.RealIn + ) + + # wrap if needed + if target == 'verilog-ams': + dut = fault.VAMSWrap(myinv) + else: + dut = myinv + + # define the test + tester = fault.Tester(dut) + tester.poke(dut.vdd, vsup) + tester.poke(dut.vss, 0) + for k in [.4, .5, .6]: + in_ = k * vsup + tester.poke(dut.in_, in_) + # We might not know the expected value now but will want to check later + tester.expect(dut.out, 0, save_for_later=True) + + # set options + kwargs = dict( + target=target, + simulator=simulator, + model_paths=[Path('tests/spice/myinv.sp').resolve()], + vsup=vsup, + tmp_dir=True + ) + if target == 'verilog-ams': + kwargs['use_spice'] = ['myinv'] + + # run the simulation + tester.compile_and_run(**kwargs) + + # look at the results we decided to save earlier + results = tester.targets[target].saved_for_later + a, b, c = results + # now we can save these to a file, post-process them, or use them + # for our own tests + assert b <= a, "Inverter tf is not always decreasing" + assert c <= b, "Inverter tf is not always decreasing" diff --git a/tests/test_read_spice.py b/tests/test_read_spice.py new file mode 100644 index 00000000..9ba73030 --- /dev/null +++ b/tests/test_read_spice.py @@ -0,0 +1,213 @@ +import magma as m +import fault +from pathlib import Path +from .common import pytest_sim_params + + +def pytest_generate_tests(metafunc): + #pytest_sim_params(metafunc, 'verilog-ams', 'spice') + pytest_sim_params(metafunc, 'spice') + +def get_inv_tester(target, vsup): + # declare circuit + myinv = m.DeclareCircuit( + 'myinv', + 'in_', fault.RealIn, + 'out', fault.RealOut, + 'vdd', fault.RealIn, + 'vss', fault.RealIn + ) + + # wrap if needed + if target == 'verilog-ams': + dut = fault.VAMSWrap(myinv) + else: + dut = myinv + + tester = fault.Tester(dut) + tester.poke(dut.vdd, vsup) + tester.poke(dut.vss, 0) + return dut, tester + +def dont_test_inv_tf( + target, simulator, vsup=1.5, vil_rel=0.4, vih_rel=0.6, + vol_rel=0.1, voh_rel=0.9 +): + + # define the test + dut, tester = get_inv_tester(target, vsup) + reads = [] + for k in [.4, .5, .6]: + in_ = k * vsup + tester.poke(dut.in_, in_) + # We might not know the expected value now but will want to check later + read_object = tester.read(dut.out) + reads.append(read_object) + + tester.poke(dut.in_, vsup, delay=1e-6) + tester.poke(dut.in_, 0, delay=42e-6) + + edge = tester.read(dut.out, style = 'edge') + + # set options + kwargs = dict( + target=target, + simulator=simulator, + model_paths=[Path('tests/spice/myinv.sp').resolve()], + vsup=vsup, + tmp_dir=True + ) + if target == 'verilog-ams': + kwargs['use_spice'] = ['myinv'] + + # run the simulation + tester.compile_and_run(**kwargs) + + # look at the results we decided to save earlier + print(reads) + results = [r.value for r in reads] + print(results) + a, b, c = results + # now we can save these to a file, post-process them, or use them + # for our own tests + assert b <= a, "Inverter tf is not always decreasing" + assert c <= b, "Inverter tf is not always decreasing" + + print(edge.value) + + + +def dont_test_edge( + target, simulator, vsup=1.5, vil_rel=0.4, vih_rel=0.6, + vol_rel=0.1, voh_rel=0.9 +): + dut, tester = get_inv_tester(target, vsup) + + tester.poke(dut.in_, 0, delay = 1.5e-3) + tester.poke(dut.in_, 1, delay = 0.5e-3) + tester.poke(dut.in_, 0, delay = 1.5e-3) + tester.poke(dut.in_, 1, delay = 0.5e-3) + tester.poke(dut.in_, 0, delay = 1.5e-3) + tester.poke(dut.in_, 1, delay = 0.5e-3) + + a = tester.read(dut.out, style='edge', params={'count':2}) + b = tester.read(dut.out, style='edge', params={'count':2, 'rising':False}) + c = tester.read(dut.out, style='edge', params={'count':2, 'forward':True}) # seems to be counting in 0->1 transitions + d = tester.read(dut.out, style='edge', params={'count':2, 'forward':True, 'rising':False}) + + tester.poke(dut.in_, 0, delay = 0.5e-3) + tester.poke(dut.in_, 1, delay = 5e-3) + tester.poke(dut.in_, 0, delay = 5e-3) + tester.poke(dut.in_, 1, delay = 5e-3) + tester.poke(dut.in_, 0, delay = 5e-3) + tester.poke(dut.in_, 1, delay = 5e-3) + + tester.read(dut.in_) + + # set options + kwargs = dict( + target=target, + simulator=simulator, + model_paths=[Path('tests/spice/myinv.sp').resolve()], + vsup=vsup, + #tmp_dir=True + ) + if target == 'verilog-ams': + kwargs['use_spice'] = ['myinv'] + + # run the simulation + tester.compile_and_run(**kwargs) + + + + def eq(xs, ys): + print('testing eq') + print(xs) + print(ys) + for x, y in zip(xs, ys): + if abs(x-y) > 5e-5: + return False + return True + + print(eq([1, 2, 3], [1, 2, 3-1e-11])) + print(eq([1, 2, 3], [1, 2, 3-2e-19])) + + print('TESTING EDGE') + print(a.value) + print(b.value) + print(c.value) + print(d.value) + + assert eq(a.value, [-0e-3, -2e-3])# TODO should this be [0, 2] ? + assert eq(b.value, [-0.5e-3, -2.5e-3]) + assert eq(c.value, [5.5e-3, 15.5e-3]) + assert eq(d.value, [0.5e-3, 10.5e-3]) + + + + +def test_phase( + target, simulator, vsup=1.5, vil_rel=0.4, vih_rel=0.6, + vol_rel=0.1, voh_rel=0.9 +): + # declare circuit + mybus = m.DeclareCircuit( + 'mybus', + 'a', m.In(m.Bits[2]), + 'b', m.Out(m.Bits[3]), + 'vdd', m.BitIn, + 'vss', m.BitIn + ) + + # wrap if needed + if target == 'verilog-ams': + dut = fault.VAMSWrap(mybus) + else: + dut = mybus + + # define the test + tester = fault.Tester(dut) + tester.poke(dut.vdd, 1) + tester.poke(dut.vss, 0) + + # in[0] gets inverted, in[1] gets buffered + # I want in[0] to be a 1kHz clock + # I want in[1] to be a 1kHz clock but delayed by 0.2 ms, so 0.2 cycles + tester.poke(dut.a[0], 1, delay = 0.2e-3) + tester.poke(dut.a[1], 1, delay = 0.3e-3) + tester.poke(dut.a[0], 0, delay = 0.2e-3) + tester.poke(dut.a[1], 0, delay = 0.3e-3) + tester.poke(dut.a[0], 1, delay = 0.2e-3) + tester.poke(dut.a[1], 1, delay = 0.3e-3) + tester.poke(dut.a[0], 0, delay = 0.2e-3) + tester.poke(dut.a[1], 0, delay = 0.3e-3) + tester.poke(dut.a[0], 1, delay = 0.2e-3) + tester.poke(dut.a[1], 1, delay = 0.3e-3) + + a = tester.read(dut.a[1], style='phase', params={'ref':dut.a[0]}) + b = tester.read(dut.a[1], style='phase', params={'ref':dut.b[0]}) + c = tester.read(dut.a[0], style='phase', params={'ref':dut.a[1]}) + + + # set options + kwargs = dict( + target=target, + simulator=simulator, + model_paths=[Path('tests/spice/mybus.sp').resolve()], + vsup=vsup, + #tmp_dir=True + ) + if target == 'verilog-ams': + kwargs['use_spice'] = ['mybus'] + + # run the simulation + tester.compile_and_run(**kwargs) + + print('Look at measured phases') + print(a.value) + print(b.value) + print(c.value) + + assert abs(a.value - 0.2) < 1e-2 + assert abs(b.value - 0.7) < 1e-2 + assert abs(c.value - 0.8) < 1e-2 diff --git a/tests/test_read_verilog.py b/tests/test_read_verilog.py new file mode 100644 index 00000000..40c30451 --- /dev/null +++ b/tests/test_read_verilog.py @@ -0,0 +1,44 @@ +import fault +import magma as m +from pathlib import Path +from .common import pytest_sim_params + + +def pytest_generate_tests(metafunc): + pytest_sim_params(metafunc, 'system-verilog') + + +def test_real_val(target, simulator): + # define the circuit + realadd = m.DeclareCircuit( + 'realadd', + 'a_val', fault.RealIn, + 'b_val', fault.RealIn, + 'c_val', fault.RealOut + ) + + # define test content + tester = fault.Tester(realadd) + tester.poke(realadd.a_val, 1.125) + tester.poke(realadd.b_val, 2.5) + read_a = tester.read(realadd.a_val) + read_b = tester.read(realadd.b_val) + read_c = tester.read(realadd.c_val) + + + # run the test + tester.compile_and_run( + target=target, + simulator=simulator, + ext_libs=[Path('tests/verilog/realadd.sv').resolve()], + defines={f'__{simulator.upper()}__': None}, + ext_model_file=True, + #tmp_dir=True + ) + + a = read_a.value + b = read_b.value + c = read_c.value + assert a == 1.125 + assert b == 2.5 + assert c == 3.625 diff --git a/tests/test_real_val.py b/tests/test_real_val.py index c7299a73..64850848 100644 --- a/tests/test_real_val.py +++ b/tests/test_real_val.py @@ -11,7 +11,11 @@ def pytest_generate_tests(metafunc): def test_real_val(target, simulator): # define the circuit class realadd(m.Circuit): - io = m.IO(a_val=fault.RealIn, b_val=fault.RealIn, c_val=fault.RealOut) + io = m.IO( + a_val=fault.RealIn, + b_val=fault.RealIn, + c_val=fault.RealOut + ) # define test content tester = fault.Tester(realadd) diff --git a/tests/test_spice_bus.py b/tests/test_spice_bus.py index 187c776e..183b1d18 100644 --- a/tests/test_spice_bus.py +++ b/tests/test_spice_bus.py @@ -10,13 +10,14 @@ def pytest_generate_tests(metafunc): def test_spice_bus(target, simulator, vsup=1.5): # declare circuit - dut = m.DeclareCircuit( - 'mybus', - 'a', m.In(m.Bits[2]), - 'b', m.Out(m.Bits[3]), - 'vdd', m.BitIn, - 'vss', m.BitIn - ) + class dut(m.Circuit): + name = 'mybus' + io = m.IO( + a=m.In(m.Bits[2]), + b=m.Out(m.Bits[3]), + vdd=m.BitIn, + vss=m.BitIn + ) # define the test tester = fault.Tester(dut) @@ -33,6 +34,19 @@ def test_spice_bus(target, simulator, vsup=1.5): tester.poke(dut.a, 0b011) tester.expect(dut.b, 0b110) + # test one bit of the bus at a time + tester.poke(dut.a[0], 0) + tester.expect(dut.b[0], 1) + tester.poke(dut.a[0], 1) + tester.expect(dut.b[0], 0) + tester.expect(dut.b[2], 1) + + tester.poke(dut.a[1], 0) + tester.expect(dut.b[1], 0) + tester.poke(dut.a[1], 1) + tester.expect(dut.b[1], 1) + tester.expect(dut.b[2], 1) + # set options kwargs = dict( target=target, diff --git a/tests/test_spice_port_order.py b/tests/test_spice_port_order.py index effe3e83..b1c9a35d 100644 --- a/tests/test_spice_port_order.py +++ b/tests/test_spice_port_order.py @@ -5,13 +5,14 @@ def test_spice_port_order(): # declare a circuit with - circ = m.DeclareCircuit( - 's', - 'p', fault.ElectIn, - 'i', m.BitIn, - 'c', m.Out(m.Bits[3]), - 'e', fault.RealOut - ) + class circ(m.Circuit): + name = 's' + io = m.IO( + p=fault.ElectIn, + i=m.BitIn, + c=m.Out(m.Bits[3]), + e=fault.RealOut + ) target = SpiceTarget(circ, conn_order='alpha') diff --git a/tests/test_tester/test_core.py b/tests/test_tester/test_core.py index cf03d221..3fb2ebc2 100644 --- a/tests/test_tester/test_core.py +++ b/tests/test_tester/test_core.py @@ -118,12 +118,12 @@ def test_tester_peek(target, simulator): tester.poke(circ.I, 0) tester.eval() tester.expect(circ.O, 0) - check(tester.actions[0], Poke(circ.I, 0)) - check(tester.actions[2], Expect(circ.O, 0)) + check(tester.actions[1], Poke(circ.I, 0)) + check(tester.actions[3], Expect(circ.O, 0)) tester.poke(circ.CLK, 0) - check(tester.actions[3], Poke(circ.CLK, 0)) + check(tester.actions[4], Poke(circ.CLK, 0)) tester.step() - check(tester.actions[4], Step(circ.CLK, 1)) + check(tester.actions[5], Step(circ.CLK, 1)) with tempfile.TemporaryDirectory(dir=".") as _dir: if target == "verilator": tester.compile_and_run(target, directory=_dir, flags=["-Wno-fatal"]) @@ -131,22 +131,6 @@ def test_tester_peek(target, simulator): tester.compile_and_run(target, directory=_dir, simulator=simulator) -def test_tester_no_clock_init(): - circ = TestBasicClkCircuit - tester = fault.Tester(circ, circ.CLK) - tester.poke(circ.I, 0) - tester.expect(circ.O, 0) - with pytest.warns(DeprecationWarning, - match="Clock has not been initialized, the initial " - "value will be X for system verilog targets. In " - "a future release, this will be an error"): - tester.step() - # should not warn more than once - with pytest.warns(None) as record: - tester.step() - assert len(record) == 0 - - def test_tester_peek_input(target, simulator): circ = TestBasicCircuit tester = fault.Tester(circ) @@ -314,7 +298,7 @@ def test_retarget_tester(target, simulator): tester.step() tester.print("%08x", circ.O) for i, exp in enumerate(expected): - check(tester.actions[i], exp) + check(tester.actions[i + 1], exp) circ_copy = TestBasicClkCircuitCopy copy = tester.retarget(circ_copy, circ_copy.CLK) @@ -327,7 +311,7 @@ def test_retarget_tester(target, simulator): Print("%08x", circ_copy.O) ] for i, exp in enumerate(copy_expected): - check(copy.actions[i], exp) + check(copy.actions[i + 1], exp) with tempfile.TemporaryDirectory(dir=".") as _dir: if target == "verilator": copy.compile_and_run(target, directory=_dir, flags=["-Wno-fatal"]) @@ -357,12 +341,13 @@ def test_print_tester(capsys): out, err = capsys.readouterr() assert "\n".join(out.splitlines()[1:]) == """\ Actions: - 0: Poke(BasicClkCircuit.I, Bit(False)) - 1: Eval() - 2: Expect(BasicClkCircuit.O, Bit(False)) - 3: Poke(BasicClkCircuit.CLK, Bit(False)) - 4: Step(BasicClkCircuit.CLK, steps=1) - 5: Print("%08x", BasicClkCircuit.O) + 0: Poke(BasicClkCircuit.CLK, Bit(False)) + 1: Poke(BasicClkCircuit.I, Bit(False)) + 2: Eval() + 3: Expect(BasicClkCircuit.O, Bit(False)) + 4: Poke(BasicClkCircuit.CLK, Bit(False)) + 5: Step(BasicClkCircuit.CLK, steps=1) + 6: Print("%08x", BasicClkCircuit.O) """ @@ -395,26 +380,31 @@ def test_print_arrays(capsys): def test_tester_verilog_wrapped(target, simulator): - ConfigReg, SimpleALU = m.DefineFromVerilogFile( - "tests/simple_alu.v", type_map={"CLK": m.In(m.Clock)}, - target_modules=["SimpleALU", "ConfigReg"]) - SimpleALU.place(ConfigReg()) - - circ = m.DefineCircuit("top", - "a", m.In(m.Bits[16]), - "b", m.In(m.Bits[16]), - "c", m.Out(m.Bits[16]), - "config_data", m.In(m.Bits[2]), - "config_en", m.In(m.Bit), - "CLK", m.In(m.Clock)) - simple_alu = SimpleALU() - m.wire(simple_alu.a, circ.a) - m.wire(simple_alu.b, circ.b) - m.wire(simple_alu.c, circ.c) - m.wire(simple_alu.config_data, circ.config_data) - m.wire(simple_alu.config_en, circ.config_en) - m.wire(simple_alu.CLK, circ.CLK) - m.EndDefine() + ConfigReg, SimpleALU = m.define_from_verilog_file( + "tests/simple_alu.v", + type_map={"CLK": m.In(m.Clock)}, + target_modules=["SimpleALU", "ConfigReg"] + ) + with SimpleALU.open(): + ConfigReg() + + class circ(m.Circuit): + name = 'top' + io = m.IO( + a=m.In(m.Bits[16]), + b=m.In(m.Bits[16]), + c=m.Out(m.Bits[16]), + config_data=m.In(m.Bits[2]), + config_en=m.In(m.Bit), + CLK=m.In(m.Clock) + ) + simple_alu = SimpleALU() + simple_alu.a @= io.a + simple_alu.b @= io.b + io.c @= simple_alu.c + simple_alu.config_data @= io.config_data + simple_alu.config_en @= io.config_en + simple_alu.CLK @= io.CLK tester = fault.Tester(circ, circ.CLK) tester.verilator_include("SimpleALU") diff --git a/tests/test_tester/test_synchronous.py b/tests/test_tester/test_synchronous.py new file mode 100644 index 00000000..46f9c84b --- /dev/null +++ b/tests/test_tester/test_synchronous.py @@ -0,0 +1,33 @@ +from fault import SynchronousTester +from hwtypes import BitVector +from ..common import SimpleALU, pytest_sim_params + + +def pytest_generate_tests(metafunc): + pytest_sim_params(metafunc, 'verilator', 'system-verilog') + + +def test_synchronous_basic(target, simulator): + ops = [ + lambda x, y: x + y, + lambda x, y: x - y, + lambda x, y: x * y, + lambda x, y: y - x + ] + tester = SynchronousTester(SimpleALU, SimpleALU.CLK) + for i in range(4): + tester.circuit.a = a = BitVector.random(16) + tester.circuit.b = b = BitVector.random(16) + tester.circuit.config_data = i + tester.circuit.config_en = 1 + tester.advance_cycle() + # Make sure enable low works + tester.circuit.config_data = BitVector.random(2) + tester.circuit.config_en = 0 + tester.circuit.c.expect(ops[i](a, b)) + tester.advance_cycle() + + if target == "verilator": + tester.compile_and_run("verilator", flags=['-Wno-unused']) + else: + tester.compile_and_run(target, simulator=simulator) diff --git a/tests/test_vams_wrap.py b/tests/test_vams_wrap.py index 68991a02..3c33ac73 100644 --- a/tests/test_vams_wrap.py +++ b/tests/test_vams_wrap.py @@ -4,15 +4,15 @@ def test_vams_wrap(): # declare the circuit - myblk = m.DeclareCircuit( - 'myblk', - 'a', RealIn, - 'b', RealOut, - 'c', m.In(m.Bit), - 'd', m.Out(m.Bits[2]), - 'e', ElectIn, - 'f', ElectOut - ) + class myblk(m.Circuit): + io = m.IO( + a=RealIn, + b=RealOut, + c=m.In(m.Bit), + d=m.Out(m.Bits[2]), + e=ElectIn, + f=ElectOut + ) wrap_circ = VAMSWrap(myblk) # check magma representation of wrapped circuit diff --git a/tests/test_while_loop.py b/tests/test_while_loop.py index 1a907d19..057c15f0 100644 --- a/tests/test_while_loop.py +++ b/tests/test_while_loop.py @@ -21,13 +21,14 @@ def debug_print(tester, dut): def test_while_loop(target, simulator, n_cyc=3, n_bits=8): - dut = m.DeclareCircuit( - 'clkdelay', - 'clk', m.In(m.Clock), - 'rst', m.In(m.Reset), - 'count', m.Out(m.Bits[n_bits]), - 'n_done', m.Out(m.Bit) - ) + class dut(m.Circuit): + name = 'clkdelay' + io = m.IO( + clk=m.In(m.Clock), + rst=m.In(m.Reset), + count=m.Out(m.Bits[n_bits]), + n_done=m.Out(m.Bit) + ) # instantiate the tester tester = fault.Tester(dut, clock=dut.clk, reset=dut.rst) diff --git a/tests/verilog/mlingua_add.sv b/tests/verilog/mlingua_add.sv new file mode 100644 index 00000000..e722b2b1 --- /dev/null +++ b/tests/verilog/mlingua_add.sv @@ -0,0 +1,26 @@ +`include "mLingua_pwl.vh" +`include "pwl_add2.v" + +module mlingua_add ( + input pwl a_val, + input pwl b_val, + output pwl c_val +); + + + real a_val_a; + real a_val_b; + real a_val_t; + + always @(*) begin + a_val_a = a_val.a; + a_val_b = a_val.b; + a_val_t = a_val.t0; + end + + pwl_add2 add2_inst(.in1(a_val), .in2(b_val), + .scale1(1.0), .scale2(1.0), + .out(c_val), + .enable(1)); + +endmodule