From 3dfc51d7d275c4ecc779a0a372336966d82e4cda Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Mon, 7 Oct 2019 15:14:03 -0700 Subject: [PATCH 01/74] Quick fix adding the minimum amount of code to get the functionality I need --- fault/actions.py | 3 ++- fault/spice_target.py | 8 ++++++ tests/test_read_port.py | 60 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 tests/test_read_port.py diff --git a/fault/actions.py b/fault/actions.py index 10f46cc0..43977259 100644 --- a/fault/actions.py +++ b/fault/actions.py @@ -88,7 +88,7 @@ def is_output(port): class Expect(PortAction): def __init__(self, port, value, strict=False, abs_tol=None, rel_tol=None, - above=None, below=None): + above=None, below=None, save_for_later=False): # call super constructor super().__init__(port, value) @@ -112,6 +112,7 @@ def __init__(self, port, value, strict=False, abs_tol=None, rel_tol=None, self.strict = strict self.above = above self.below = below + self.save_for_later = save_for_later class Assume(PortAction): diff --git a/fault/spice_target.py b/fault/spice_target.py index 529c3bb4..1bd438fd 100644 --- a/fault/spice_target.py +++ b/fault/spice_target.py @@ -118,6 +118,9 @@ def __init__(self, circuit, directory="build/", simulator='ngspice', self.ic = ic if ic is not None else {} self.disp_type = disp_type + # place for saving expects that were "save_for_later" + self.saved_for_later = [] + def run(self, actions): # compile the actions comp = self.compile_actions(actions) @@ -397,6 +400,11 @@ def impl_expect(self, results, time, action): else: raise A2DError(f'Invalid logic level: {value}.') + if action.save_for_later: + # save the value and don't check + self.saved_for_later.append(value) + return + # implement the requested check if action.above is not None: if action.below is not None: diff --git a/tests/test_read_port.py b/tests/test_read_port.py new file mode 100644 index 00000000..f2e90e1c --- /dev/null +++ b/tests/test_read_port.py @@ -0,0 +1,60 @@ +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 +): + # 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 we'll 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" + From 8c92eb08bfe439c22d0b9020f8545cd319a4c277 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Mon, 7 Oct 2019 17:16:35 -0700 Subject: [PATCH 02/74] Fixed style in new test --- tests/test_read_port.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_read_port.py b/tests/test_read_port.py index f2e90e1c..aa444d67 100644 --- a/tests/test_read_port.py +++ b/tests/test_read_port.py @@ -7,6 +7,7 @@ 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 @@ -33,7 +34,7 @@ def test_inv_tf( for k in [.4, .5, .6]: in_ = k * vsup tester.poke(dut.in_, in_) - # We might not know the expected value now, but we'll want to check later + # 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 @@ -57,4 +58,3 @@ def test_inv_tf( # for our own tests assert b <= a, "Inverter tf is not always decreasing" assert c <= b, "Inverter tf is not always decreasing" - From e744e1e7f73717975fbfcb794a49c508ec4c8999 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Wed, 16 Oct 2019 13:47:03 -0700 Subject: [PATCH 03/74] Fix indexing into one bit of a spice bus --- fault/spice_target.py | 10 ++++++++++ tests/test_spice_bus.py | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/fault/spice_target.py b/fault/spice_target.py index 1bd438fd..faf0049a 100644 --- a/fault/spice_target.py +++ b/fault/spice_target.py @@ -202,6 +202,16 @@ def compile_actions(self, actions): if isinstance(action, (Poke, Expect)) \ and isinstance(action.port, m.BitsType): _actions += self.expand_bus(action) + elif (isinstance(action, (Poke, Expect)) + and isinstance(action.port.name, m.ref.ArrayRef)): + # 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 = action.port.name.array.name + bus_index = action.port.name.index + bit_name = self.bit_from_bus(bus_name, bus_index) + action.port = m.BitType(name=bit_name) + _actions.append(action) else: _actions.append(action) actions = _actions diff --git a/tests/test_spice_bus.py b/tests/test_spice_bus.py index 187c776e..adeafe06 100644 --- a/tests/test_spice_bus.py +++ b/tests/test_spice_bus.py @@ -33,6 +33,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, From 311d6d303f274c4a97c2ab7e0cf2b59f64666cac Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Tue, 12 Nov 2019 16:12:31 -0800 Subject: [PATCH 04/74] Wrote background poke Thread class --- fault/actions.py | 3 +- fault/background_poke.py | 97 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 fault/background_poke.py diff --git a/fault/actions.py b/fault/actions.py index 0b0ec1d4..f2f50a1a 100644 --- a/fault/actions.py +++ b/fault/actions.py @@ -46,11 +46,12 @@ def is_input(port): class Poke(PortAction): - def __init__(self, port, value, delay=None): + def __init__(self, port, value, delay=None, background_params=None): if is_input(port): raise ValueError(f"Can only poke inputs: {port.debug_name} " f"{type(port)}") self.delay = delay + self.background_params = background_params super().__init__(port, value) diff --git a/fault/background_poke.py b/fault/background_poke.py new file mode 100644 index 00000000..82f5b4e2 --- /dev/null +++ b/fault/background_poke.py @@ -0,0 +1,97 @@ +from fault.actions import Poke +import heapq, math +from functools import total_ordering + + +@total_ordering +class Thread(): + # if sin wave dt is unspecified, use period/default_steps_per_cycle + default_steps_per_cycle = 20 + + # when checking clock value at time t, check time t+epsilon instead + # this avoids ambiguity when landing exactly on the clock edge + epsilon = 1e-18 + + + def __init__(self, time, poke): + self.poke = poke.copy() + self.poke.params = None + self.poke.delay = None + params = poke.params + self.start = time + self.next_update = 0 + type_ = params.get('type', 'clock') + + # 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 cycle_location > (1 - duty_cycle) + else: + return cycle_location < duty_cycle + + 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 + 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. + ''' + missed_update_msg = 'Background Poke thread not updated in time' + assert t <= self.next_update + self.epsilon, missed_update_msg + value = self.get_val(t) + poke = self.poke.copy() + poke.value = value + return poke + + + def __lt__(self, other): + return self.next_update < other + +class ThreadPool(): + pass + + +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. + """ + + new_action_list = [] + t = 0 + for a in actions: + if isinstance(a, Poke) and a.background_params is not None: + #do something + pass + else: + new_action_list.append(a) + + From 26a0eb49b3502aea9b5b7d50e512cc1331374583 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Tue, 12 Nov 2019 18:00:01 -0800 Subject: [PATCH 05/74] Finished writing background poke interleaving but have not even run it yet --- fault/background_poke.py | 114 ++++++++++++++++++++++++++++++++++----- 1 file changed, 101 insertions(+), 13 deletions(-) diff --git a/fault/background_poke.py b/fault/background_poke.py index 82f5b4e2..a16a8641 100644 --- a/fault/background_poke.py +++ b/fault/background_poke.py @@ -1,5 +1,6 @@ from fault.actions import Poke -import heapq, math +import heapq +import math from functools import total_ordering @@ -12,7 +13,6 @@ class Thread(): # this avoids ambiguity when landing exactly on the clock edge epsilon = 1e-18 - def __init__(self, time, poke): self.poke = poke.copy() self.poke.params = None @@ -62,6 +62,7 @@ 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 value = self.get_val(t) @@ -69,29 +70,116 @@ def step(self, t): poke.value = value return poke - def __lt__(self, other): return self.next_update < other + class ThreadPool(): - pass + # 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. + ''' + t = self.t + t_end = self.t + t_delay + free_time = self.get_next_update_time() - self.t + if free_time > t_delay: + self.t = t_end + return (t_delay, []) + else: + actions = [] + while self.get_next_update_time() <= t_end + self.epsilon: + thread = heapq.heappop(self.background_threads) + action = thread.step() + next_thing_time = min(self.get_next_update_time(), t_end) + action.delay = next_thing_time - t + t = next_thing_time + actions.append(action) + heapq.heappush(self.background_threads, thread) + + # t_end has less floating point error than t + self.t = 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 == port: + offender = thread + self.background_threads.remove(offender) + poke = offender.step(self.t) + if poke is None: + return [] + else: + return [poke] + + def process(self, action, delay): + new_action_list = [] + is_background = (isinstance(action, Poke) + and action.background_params is not None) + + # check whether this is currently a background port + if 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: + new_action = action.copy() + new_action.delay = new_delay + new_action_list.append(new_action) + + new_action_list += actions + 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 + Throws a NotImplementedError if there's a background task during an interval of time not known at compile time. """ - new_action_list = [] - t = 0 - for a in actions: - if isinstance(a, Poke) and a.background_params is not None: - #do something - pass + def get_delay(a): + if a.delay is not None: + return a.delay else: - new_action_list.append(a) - + 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 From 03686703893e524d13626e2a8f6f46e805ac5ece Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Wed, 13 Nov 2019 15:30:51 -0800 Subject: [PATCH 06/74] I think it is working for spice targets. --- fault/background_poke.py | 47 +++++++++++---- fault/spice_target.py | 6 ++ tests/test_background_poke.py | 109 ++++++++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+), 12 deletions(-) create mode 100644 tests/test_background_poke.py diff --git a/fault/background_poke.py b/fault/background_poke.py index a16a8641..15de4595 100644 --- a/fault/background_poke.py +++ b/fault/background_poke.py @@ -2,6 +2,7 @@ import heapq import math from functools import total_ordering +import copy @total_ordering @@ -14,14 +15,18 @@ class Thread(): epsilon = 1e-18 def __init__(self, time, poke): - self.poke = poke.copy() + print('creating thread for', poke, 'at time', time) + self.poke = copy.copy(poke) self.poke.params = None self.poke.delay = None - params = poke.params + params = poke.delay self.start = time - self.next_update = 0 + 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) @@ -56,8 +61,10 @@ def get_val(t): self.get_val = get_val self.dt = params.get('dt', 1 / (freq*self.default_steps_per_cycle)) + print('calculate dt of', self.dt, 'from', freq, self.default_steps_per_cycle) def step(self, t): + #print('stepping thread ', self, 'at time', t) ''' Returns a new Poke object with the correct value set for time t. Sets the port and value but NOT the delay. @@ -65,9 +72,11 @@ def step(self, t): # 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 + self.next_update = t + self.dt value = self.get_val(t) - poke = self.poke.copy() + poke = copy.copy(self.poke) poke.value = value + #print('after step, next scheduled update is', self.next_update) return poke def __lt__(self, other): @@ -97,25 +106,33 @@ def delay(self, t_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 free_time = self.get_next_update_time() - self.t if free_time > t_delay: + #print('pool update not needed', t) self.t = t_end return (t_delay, []) else: actions = [] + #print('entering while loop') while self.get_next_update_time() <= t_end + self.epsilon: thread = heapq.heappop(self.background_threads) - action = thread.step() + 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('calculated next thing time', next_thing_time, 'at time', t) action.delay = next_thing_time - t t = next_thing_time actions.append(action) - heapq.heappush(self.background_threads, thread) # 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): @@ -140,10 +157,11 @@ def remove(self, port): def process(self, action, delay): new_action_list = [] is_background = (isinstance(action, Poke) - and action.background_params is not None) + and type(action.delay) == dict) - # check whether this is currently a background port - if action.port in self.active_ports: + # 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 @@ -155,7 +173,7 @@ def process(self, action, delay): # now we add this (shortened) action back in if not is_background: - new_action = action.copy() + new_action = copy.copy(action) new_action.delay = new_delay new_action_list.append(new_action) @@ -172,8 +190,13 @@ def process_action_list(actions, clock_step_delay): """ def get_delay(a): - if a.delay is not None: - return a.delay + 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 diff --git a/fault/spice_target.py b/fault/spice_target.py index faf0049a..9092cffb 100644 --- a/fault/spice_target.py +++ b/fault/spice_target.py @@ -12,6 +12,7 @@ from fault.pwl import pwc_to_pwl from fault.actions import Poke, Expect, Delay, Print from fault.select_path import SelectPath +from fault.background_poke import process_action_list # define a custom error for A2D conversion to make it easier @@ -122,6 +123,11 @@ def __init__(self, circuit, directory="build/", simulator='ngspice', self.saved_for_later = [] def run(self, actions): + # expand background pokes into regular pokes + print('before', len(actions)) + actions = process_action_list(actions, self.clock_step_delay) + print('after', len(actions)) + # compile the actions comp = self.compile_actions(actions) diff --git a/tests/test_background_poke.py b/tests/test_background_poke.py new file mode 100644 index 00000000..c09abe47 --- /dev/null +++ b/tests/test_background_poke.py @@ -0,0 +1,109 @@ +import magma as m +import fault +import tempfile +import pytest +from pathlib import Path +from .common import pytest_sim_params, TestBasicCircuit + +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): + 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) # should fail + 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) + +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) + tester.poke(dut.in_, 0, delay = { + 'type': 'sin', + 'freq': 1e3, + 'amplitude': .8, + 'offset': 0.6, + 'phase_degrees': 90 + }) + + for k in range(20): + tester.expect(dut.out, 0, save_for_later=True) + tester.delay(0.1e-3) + + #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) + + for k in range(20): + value = tester.results[target].saved_for_later[k] + print('%2d\t'%k, value) + From 95ddd0b857a5c58029d6efdc5e07ab649f7a2ccf Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Mon, 18 Nov 2019 13:58:15 -0800 Subject: [PATCH 07/74] Working on tests for background pokes --- fault/background_poke.py | 13 +++++----- fault/system_verilog_target.py | 4 ++++ tests/test_background_poke.py | 44 ++++++++++++++++++++++++++-------- tests/test_read_port.py | 4 ++++ 4 files changed, 49 insertions(+), 16 deletions(-) diff --git a/fault/background_poke.py b/fault/background_poke.py index 15de4595..fb972a55 100644 --- a/fault/background_poke.py +++ b/fault/background_poke.py @@ -8,7 +8,7 @@ @total_ordering class Thread(): # if sin wave dt is unspecified, use period/default_steps_per_cycle - default_steps_per_cycle = 20 + 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 @@ -38,9 +38,9 @@ def __init__(self, time, poke): def get_val(t): cycle_location = ((t - self.start + self.epsilon) / period) % 1 if initial_value == 0: - return cycle_location > (1 - duty_cycle) + return 1 if cycle_location > (1 - duty_cycle) else 0 else: - return cycle_location < duty_cycle + return 1 if cycle_location < duty_cycle else 0 self.get_val = get_val self.dt = period/2 @@ -49,10 +49,11 @@ def get_val(t): freq = params.get('freq', 1e6) period = params.get('period', 1/freq) freq = 1 / period + print('Settled on frequency', freq) amplitude = params.get('amplitude', 1) offset = params.get('offset', 0) phase_degrees = params.get('phase_degrees', 0) - conv = math.pi / 180 + conv = math.pi / 180.0 phase_radians = params.get('phase_radians', phase_degrees * conv) def get_val(t): @@ -61,10 +62,10 @@ def get_val(t): self.get_val = get_val self.dt = params.get('dt', 1 / (freq*self.default_steps_per_cycle)) - print('calculate dt of', self.dt, 'from', freq, self.default_steps_per_cycle) + print('calculated dt of', self.dt, 'from', freq, self.default_steps_per_cycle) def step(self, t): - #print('stepping thread ', self, 'at time', t) + print('stepping thread ', self, 'at time', t) ''' Returns a new Poke object with the correct value set for time t. Sets the port and value but NOT the delay. diff --git a/fault/system_verilog_target.py b/fault/system_verilog_target.py index aca0a68c..89e87674 100644 --- a/fault/system_verilog_target.py +++ b/fault/system_verilog_target.py @@ -8,6 +8,7 @@ from fault.select_path import SelectPath from fault.wrapper import PortWrapper from fault.subprocess_run import subprocess_run +from fault.background_poke import process_action_list import fault import fault.expression as expression from fault.real_type import RealKind @@ -535,6 +536,9 @@ def run(self, actions, power_args=None): # set defaults power_args = power_args if power_args is not None else {} + # expand background pokes into regular pokes + actions = process_action_list(actions, self.clock_step_delay) + # assemble list of sources files vlog_srcs = [] if not self.ext_test_bench: diff --git a/tests/test_background_poke.py b/tests/test_background_poke.py index c09abe47..5be48407 100644 --- a/tests/test_background_poke.py +++ b/tests/test_background_poke.py @@ -5,13 +5,22 @@ 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') +#@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() @@ -30,7 +39,10 @@ def test_clock_verilog(target, simulator): tester.expect(circ.O, 0) # should fail - #tester.print("%08x", circ.O) + tester.expect(circ.O, 0, save_for_later=True) + + + tester.print("%08x", circ.O) #with tempfile.TemporaryDirectory(dir=".") as _dir: @@ -41,7 +53,10 @@ def test_clock_verilog(target, simulator): 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 @@ -69,17 +84,22 @@ def test_sin_spice(vsup=1.5, vil_rel=0.4, vih_rel=0.6, 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': 1e3, - 'amplitude': .8, + 'freq': freq, + 'amplitude': 0.4, 'offset': 0.6, 'phase_degrees': 90 }) - for k in range(20): - tester.expect(dut.out, 0, save_for_later=True) - tester.delay(0.1e-3) + num_reads = 100 + xs = [] + dt = 1/(freq * 50) + for k in range(num_reads): + tester.expect(dut.in_, 0, save_for_later=True) + tester.delay(dt) + xs.append(k*dt) #for k in [.4, .5, .6]: # in_ = k * vsup @@ -95,7 +115,8 @@ def test_sin_spice(vsup=1.5, vil_rel=0.4, vih_rel=0.6, simulator=simulator, model_paths=[Path('tests/spice/myinv.sp').resolve()], vsup=vsup, - tmp_dir=True + tmp_dir=True, + clock_step_delay = 0 ) if target == 'verilog-ams': kwargs['use_spice'] = ['myinv'] @@ -103,7 +124,10 @@ def test_sin_spice(vsup=1.5, vil_rel=0.4, vih_rel=0.6, # run the simulation tester.compile_and_run(**kwargs) - for k in range(20): - value = tester.results[target].saved_for_later[k] + ys = [] + for k in range(num_reads): + value = tester.targets[target].saved_for_later[k] + ys.append(value) print('%2d\t'%k, value) + plot(xs, ys) diff --git a/tests/test_read_port.py b/tests/test_read_port.py index aa444d67..113e7d29 100644 --- a/tests/test_read_port.py +++ b/tests/test_read_port.py @@ -12,6 +12,10 @@ 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', From 794d7a3261d02be42bda3db825107c68d9a0e867 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Mon, 18 Nov 2019 14:54:12 -0800 Subject: [PATCH 08/74] Changed location of unit conversion in spice_target --- fault/spice_target.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fault/spice_target.py b/fault/spice_target.py index 9092cffb..9e1fb143 100644 --- a/fault/spice_target.py +++ b/fault/spice_target.py @@ -34,7 +34,7 @@ def __init__(self, pwls, checks, prints, stop_time, saves): 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='alpha', bus_delim='<>', bus_order='descend', flags=None, ic=None, disp_type='on_error'): @@ -130,6 +130,7 @@ def run(self, actions): # compile the actions comp = self.compile_actions(actions) + print('compiled action pwls:', comp.pwls) # write the testbench tb_file = self.write_test_bench(comp) @@ -246,7 +247,7 @@ 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): From 2ff5d922870c710c6a4967606d46492b4330ab63 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Tue, 19 Nov 2019 17:53:24 -0800 Subject: [PATCH 09/74] Added Print action and nothing more --- fault/actions.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/fault/actions.py b/fault/actions.py index a95e624b..18871d5d 100644 --- a/fault/actions.py +++ b/fault/actions.py @@ -72,6 +72,16 @@ def retarget(self, new_circuit, clock): return cls(self.format_str, *new_ports) +class Read(Action): + def __init__(self, port): + super().__init__() + + self.port = port + + def __str__(self): + return f"Read({self.port.debug_name})" + + def is_inout(port): if isinstance(port, SelectPath): port = port[-1] From 5276903b1e828fd8752acea3b4d1a144a003f55f Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Wed, 20 Nov 2019 10:48:47 -0800 Subject: [PATCH 10/74] Added read to VerilogTarget but no implementation --- fault/verilog_target.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fault/verilog_target.py b/fault/verilog_target.py index ae2b6e80..0a01f8d6 100644 --- a/fault/verilog_target.py +++ b/fault/verilog_target.py @@ -89,6 +89,8 @@ def generate_action_code(self, i, action): return self.make_poke(i, action) if isinstance(action, actions.Print): return self.make_print(i, action) + if isinstance(action, actions.Read): + return self.make_read(i, action) if isinstance(action, actions.Expect): return self.make_expect(i, action) if isinstance(action, actions.Eval): @@ -129,6 +131,10 @@ def make_poke(self, i, action): def make_print(self, i, action): pass + @abstractmethod + def make_read(self, i, action): + pass + @abstractmethod def make_expect(self, i, action): pass From 7c94e678333130aaccb2ba27ab7c9b911b343b00 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Wed, 20 Nov 2019 17:44:59 -0800 Subject: [PATCH 11/74] Reading now works for systemverilog targets --- fault/actions.py | 15 ++++++++- fault/real_type.py | 4 +++ fault/system_verilog_target.py | 60 +++++++++++++++++++++++++++++++--- fault/tester.py | 9 +++++ tests/test_read_verilog.py | 44 +++++++++++++++++++++++++ 5 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 tests/test_read_verilog.py diff --git a/fault/actions.py b/fault/actions.py index 18871d5d..1ed7e93e 100644 --- a/fault/actions.py +++ b/fault/actions.py @@ -75,12 +75,25 @@ def retarget(self, new_circuit, clock): class Read(Action): def __init__(self, port): super().__init__() - self.port = port + def __getattr__(self, name): + if name == 'value': + err_msg = '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): diff --git a/fault/real_type.py b/fault/real_type.py index dd94c2ed..cfe837db 100644 --- a/fault/real_type.py +++ b/fault/real_type.py @@ -11,6 +11,8 @@ def __init__(self, *largs, **kwargs): def isoriented(cls, direction): return cls.direction == direction + __hash__ = Type.__hash__ + class RealKind(Kind): def __init__(cls, name, bases, dct): @@ -53,6 +55,8 @@ def flip(cls): return RealIn return cls + __hash__ = Kind.__hash__ + def MakeReal(**kwargs): return RealKind('Real', (RealType,), kwargs) diff --git a/fault/system_verilog_target.py b/fault/system_verilog_target.py index aca0a68c..831e0826 100644 --- a/fault/system_verilog_target.py +++ b/fault/system_verilog_target.py @@ -13,6 +13,7 @@ from fault.real_type import RealKind import os from numbers import Number +import re src_tpl = """\ @@ -43,7 +44,8 @@ def __init__(self, circuit, circuit_name=None, directory="build/", 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'): + use_input_wires=False, parameters=None, disp_type='on_error', + read_tag='fault_read<{read_hash}><{value}>'): """ circuit: a magma circuit @@ -115,6 +117,9 @@ def __init__(self, circuit, circuit_name=None, directory="build/", disp_type: 'on_error', 'realtime'. If 'on_error', only print if there is an error. If 'realtime', print out STDOUT as lines come in, then print STDERR after the process completes. + + read_tag: Text tag for formatting read action hashes and values dumped + by the simulator. """ # set default for list of external sources if include_verilog_libraries is None: @@ -146,6 +151,9 @@ def __init__(self, circuit, circuit_name=None, directory="build/", if simulator not in {"vcs", "ncsim", "iverilog"}: raise ValueError(f"Unsupported simulator {simulator}") + # dictionary of Read actions that will need their 'value's set + self.read_dict = {} + # save settings self.simulator = simulator self.timescale = timescale @@ -165,6 +173,7 @@ def __init__(self, circuit, circuit_name=None, directory="build/", self.use_input_wires = use_input_wires self.parameters = parameters if parameters is not None else {} self.disp_type = disp_type + self.read_tag = read_tag def add_decl(self, *decls): self.declarations.extend(decls) @@ -256,7 +265,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 @@ -270,6 +279,46 @@ def make_print(self, i, action): args = ', '.join(args) return [f'$write({args});'] + def make_read(self, i, action): + # yes it's weird that we are using a hash of something as its key, + # but we only get back the text of the hash so I think this is the + # best way to get the object back from that text + self.read_dict[hash(action)] = action + + type_ = type(action.port) + if isinstance(type_, m.ArrayKind): + # TODO + raise NotImplementedError + elif isinstance(type_, RealKind): + format_string = '%f' + else: + format_string = '%d' + text = self.read_tag.format(read_hash = hash(action), + value = format_string) + #text += '\n' + + # give this the attributes of a read action + action.format_str = text + action.ports = [action.port] + return self.make_print(i, action) + + def process_reads(self, text): + def unstring(s): + try: + return int(s) + except ValueError: + pass + try: + return float(s) + except ValueError: + pass + raise NotImplementedError(f'Cannot interpret value "{s}"') + + regex = self.read_tag.format(read_hash='(.*?)', value='(.*?)') + for read_hash, value_str in re.findall(regex, text): + value = unstring(value_str) + self.read_dict[int(read_hash)].value = value + def make_loop(self, i, action): self.declarations.append(f"integer {action.loop_var};") code = [] @@ -566,13 +615,16 @@ def run(self, actions, power_args=None): sim_cmd += self.flags # compile the simulation - subprocess_run(sim_cmd, cwd=self.directory, env=self.sim_env, + print('calling subprocess with args', sim_cmd, self.directory, self.sim_env, self.disp_type) + completed_sim = subprocess_run(sim_cmd, cwd=self.directory, env=self.sim_env, disp_type=self.disp_type) # run the simulation binary (if applicable) if bin_cmd is not None: - subprocess_run(bin_cmd, cwd=self.directory, env=self.sim_env, + completed_sim = subprocess_run(bin_cmd, cwd=self.directory, env=self.sim_env, err_str=sim_err_str, disp_type=self.disp_type) + result_text = completed_sim.stdout + self.process_reads(result_text) def write_test_bench(self, actions, power_args): # determine the path of the testbench file diff --git a/fault/tester.py b/fault/tester.py index 4cc9f8a8..bb8b9969 100644 --- a/fault/tester.py +++ b/fault/tester.py @@ -161,6 +161,15 @@ def print(self, format_str, *args): """ self.actions.append(actions.Print(format_str, *args)) + def read(self, port): + """ + Returns a Read action. After running the simulation, the value of + `port` will be saved in the Read object's 'value' attribute. + """ + r = actions.Read(port) + self.actions.append(r) + return r + def expect(self, port, value, strict=None, **kwargs): """ Expect the current value of `port` to be `value` 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 From 78abb7c2039d961e82836837d720ecdb59f5e665 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Wed, 20 Nov 2019 17:51:40 -0800 Subject: [PATCH 12/74] Reads work for spice too, based on minimal testing --- fault/spice_target.py | 18 +++++++++-- tests/test_read_spice.py | 67 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 tests/test_read_spice.py diff --git a/fault/spice_target.py b/fault/spice_target.py index 529c3bb4..10f22e40 100644 --- a/fault/spice_target.py +++ b/fault/spice_target.py @@ -10,7 +10,7 @@ from fault.psf_parse import psf_parse from fault.subprocess_run import subprocess_run from fault.pwl import pwc_to_pwl -from fault.actions import Poke, Expect, Delay, Print +from fault.actions import Poke, Expect, Delay, Print, Read from fault.select_path import SelectPath @@ -22,10 +22,11 @@ class A2DError(Exception): class CompiledSpiceActions: - def __init__(self, pwls, checks, prints, stop_time, saves): + def __init__(self, pwls, checks, prints, reads, stop_time, saves): self.pwls = pwls self.checks = checks self.prints = prints + self.reads = reads self.stop_time = stop_time self.saves = saves @@ -151,6 +152,9 @@ def run(self, actions): # print results self.print_results(results=results, prints=comp.prints) + # set values on reads + self.process_reads(results, comp.reads) + # check results self.check_results(results=results, checks=comp.checks) @@ -191,6 +195,7 @@ def compile_actions(self, actions): pwc_dict = {} checks = [] prints = [] + reads = [] saves = set() # expand buses as needed @@ -237,6 +242,9 @@ def compile_actions(self, actions): prints.append((t, action)) for port in action.ports: saves.add(f'{port.name}') + elif isinstance(action, Read): + reads.append((t, action)) + saves.add(f'{action.port.name}') elif isinstance(action, Delay): t += action.time else: @@ -255,6 +263,7 @@ def compile_actions(self, actions): pwls=pwls, checks=checks, prints=prints, + reads=reads, stop_time=t, saves=saves ) @@ -423,6 +432,11 @@ 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: + value = results[f'{read.port.name}'](time) + read.value = value + def ngspice_cmds(self, tb_file): # build up the command cmd = [] diff --git a/tests/test_read_spice.py b/tests/test_read_spice.py new file mode 100644 index 00000000..5baeb280 --- /dev/null +++ b/tests/test_read_spice.py @@ -0,0 +1,67 @@ +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 test_inv_tf( + target, simulator, vsup=1.5, vil_rel=0.4, vih_rel=0.6, + 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 + ) + + # 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) + 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) + + # 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" + + From 64bd2641c258e48a34ef8e1acf4eb162f3003f4e Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Thu, 21 Nov 2019 18:02:27 -0800 Subject: [PATCH 13/74] Edge finder is now working. Need to clean up prints --- fault/actions.py | 6 ++- fault/spice_target.py | 87 ++++++++++++++++++++++++++++++++++++- fault/tester.py | 4 +- tests/test_read_spice.py | 92 +++++++++++++++++++++++++++++++++++++--- 4 files changed, 177 insertions(+), 12 deletions(-) diff --git a/fault/actions.py b/fault/actions.py index 1ed7e93e..8307ecd1 100644 --- a/fault/actions.py +++ b/fault/actions.py @@ -73,13 +73,15 @@ def retarget(self, new_circuit, clock): class Read(Action): - def __init__(self, port): + def __init__(self, port, style='single', params={}): super().__init__() self.port = port + self.style = style + self.params = params def __getattr__(self, name): if name == 'value': - err_msg = 'value has not been set for {self}' + err_msg = f'value has not been set for {self}' err_msg += ', did the simulation finish running yet?' assert False, err_msg else: diff --git a/fault/spice_target.py b/fault/spice_target.py index 10f22e40..dcb75490 100644 --- a/fault/spice_target.py +++ b/fault/spice_target.py @@ -4,6 +4,7 @@ import magma as m import fault import hwtypes +import numpy as np from fault.target import Target from fault.spice import SpiceNetlist from fault.nutascii_parse import nutascii_parse @@ -20,6 +21,9 @@ class A2DError(Exception): pass +# edge finder is used for measuring phase, freq, etc. +class EdgeNotFoundError(Exception): + pass class CompiledSpiceActions: def __init__(self, pwls, checks, prints, reads, stop_time, saves): @@ -149,6 +153,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) @@ -434,8 +448,77 @@ def print_results(self, results, prints): def process_reads(self, results, reads): for time, read in reads: - value = results[f'{read.port.name}'](time) - read.value = value + res = results[f'{read.port.name}'] + if read.style == 'single': + value = res(time) + read.value = value + elif read.style == 'edge': + value = self.find_edge(res.x, res.y, time, **read.params) + read.value = value + else: + raise NotImplementedError(f'Unknown read style "{read.style}"') + + def find_edge(self, x, y, t_start, height=None, 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?) + ''' + print('\nNEW REQUEST', forward, rising, t_start) + + if height is None: + # default comes out to 0.5 + height = self.vsup * (self.vih_rel + self.vil_rel) / 2 + + if not rising: + print('Flipping thing') + y = [(-1*z + 2*height) for z in y] + + # deal with `forward` + 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 + # print('SPECIAL CASE') + # start_index -= 1 + #print('found start index', start_index, 'between', x[start_index], x[start_index+1]) + i = start_index + edges = [] + while len(edges) < count: + print('i starts at', i, 'x is', x[i], 'y is', y[i]) + print('wil iterate as long as y[i] is higher than', height) + 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) + print('middle is at', i, 'x is', x[i], 'y is', y[i]) + # now move backwards until we hit the 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) + + + # TODO: there's an issue because of the discrepancy between the requested + # start time and actually staring at the nearest edge + + print('finishes at', i, 'x is', x[i], 'y is', y[i]) + # the crossing happens from i to i+1 + fraction = (height-y[i]) / (y[i-direction]-y[i]) + print('fraction is', fraction) + t = x[i] + fraction * (x[i+1] - x[i]) + print('found edge at ', t) + print('which maps to offset of ', t-t_start) + if t==t_start: + print('EDGE EXACTLY AT EDGE FIND REQUEST') + edges.append(t-t_start) + return edges def ngspice_cmds(self, tb_file): # build up the command diff --git a/fault/tester.py b/fault/tester.py index bb8b9969..a983a79d 100644 --- a/fault/tester.py +++ b/fault/tester.py @@ -161,12 +161,12 @@ def print(self, format_str, *args): """ self.actions.append(actions.Print(format_str, *args)) - def read(self, port): + def read(self, port, style='single', params={}): """ Returns a Read action. After running the simulation, the value of `port` will be saved in the Read object's 'value' attribute. """ - r = actions.Read(port) + r = actions.Read(port, style, params) self.actions.append(r) return r diff --git a/tests/test_read_spice.py b/tests/test_read_spice.py index 5baeb280..1671d662 100644 --- a/tests/test_read_spice.py +++ b/tests/test_read_spice.py @@ -8,11 +8,7 @@ def pytest_generate_tests(metafunc): #pytest_sim_params(metafunc, 'verilog-ams', 'spice') pytest_sim_params(metafunc, '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 -): +def get_inv_tester(target, vsup): # declare circuit myinv = m.DeclareCircuit( 'myinv', @@ -28,10 +24,18 @@ def test_inv_tf( else: dut = myinv - # define the test 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 @@ -40,6 +44,11 @@ def test_inv_tf( 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, @@ -64,4 +73,75 @@ def test_inv_tf( assert b <= a, "Inverter tf is not always decreasing" assert c <= b, "Inverter tf is not always decreasing" + print(edge.value) + + + +def 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, [-0.5e-3, -2.5e-3]) + assert eq(b.value, [-0e-3, -2e-3])# TODO should this be [0, 2] ? + assert eq(c.value, [5.5e-3, 15.5e-3]) + assert eq(d.value, [0.5e-3, 10.5e-3]) + + From 6198fa2f3fd722399123937605009c2045ba3e90 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Fri, 22 Nov 2019 14:38:41 -0800 Subject: [PATCH 14/74] Allow indexing into spice buses --- fault/spice_target.py | 10 ++++++++++ tests/test_spice_bus.py | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/fault/spice_target.py b/fault/spice_target.py index dcb75490..fa064496 100644 --- a/fault/spice_target.py +++ b/fault/spice_target.py @@ -218,6 +218,16 @@ def compile_actions(self, actions): if isinstance(action, (Poke, Expect)) \ and isinstance(action.port, m.BitsType): _actions += self.expand_bus(action) + elif (isinstance(action, (Poke, Expect)) + and isinstance(action.port.name, m.ref.ArrayRef)): + # 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 = action.port.name.array.name + bus_index = action.port.name.index + bit_name = self.bit_from_bus(bus_name, bus_index) + action.port = m.BitType(name=bit_name) + _actions.append(action) else: _actions.append(action) actions = _actions diff --git a/tests/test_spice_bus.py b/tests/test_spice_bus.py index 187c776e..adeafe06 100644 --- a/tests/test_spice_bus.py +++ b/tests/test_spice_bus.py @@ -33,6 +33,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, From e5f9ecbe7dd4d5d336e49355faa5c093f2beb1c5 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Fri, 22 Nov 2019 15:43:55 -0800 Subject: [PATCH 15/74] Phase measurement now working in spice --- fault/spice_target.py | 57 ++++++++++++++++++++++++++------------ tests/test_read_spice.py | 60 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 96 insertions(+), 21 deletions(-) diff --git a/fault/spice_target.py b/fault/spice_target.py index fa064496..9a2d80e4 100644 --- a/fault/spice_target.py +++ b/fault/spice_target.py @@ -215,18 +215,12 @@ def compile_actions(self, actions): # 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.BitsType): _actions += self.expand_bus(action) - elif (isinstance(action, (Poke, Expect)) + elif (isinstance(action, (Poke, Expect, Read)) and isinstance(action.port.name, m.ref.ArrayRef)): - # 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 = action.port.name.array.name - bus_index = action.port.name.index - bit_name = self.bit_from_bus(bus_name, bus_index) - action.port = m.BitType(name=bit_name) + action.port = self.select_bit_from_bus(action.port) _actions.append(action) else: _actions.append(action) @@ -269,6 +263,12 @@ def compile_actions(self, actions): elif isinstance(action, Read): reads.append((t, action)) 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 + saves.add(f'{action.params["ref"].name}') elif isinstance(action, Delay): t += action.time else: @@ -314,6 +314,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.BitType(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() @@ -465,6 +476,15 @@ def process_reads(self, results, reads): elif read.style == 'edge': value = self.find_edge(res.x, res.y, time, **read.params) read.value = value + elif read.style == 'phase': + assert 'ref' in read.params, 'Phase read requires reference signal param' + res_ref = results[f'{read.params["ref"].name}'] + ref = self.find_edge(res_ref.x, res_ref.y, time, count=2) + edge = self.find_edge(res.x, res.y, time + ref[0]) + fraction = (edge[0] - ref[1]) / (ref[0] -ref[1]) + print(ref, edge) + # TODO multiply by 2pi? + read.value = fraction else: raise NotImplementedError(f'Unknown read style "{read.style}"') @@ -480,34 +500,35 @@ def find_edge(self, x, y, t_start, height=None, forward=False, count=1, rising=T # default comes out to 0.5 height = self.vsup * (self.vih_rel + self.vil_rel) / 2 - if not rising: + # deal with `rising` and `forward` + # normally a low-to-high finder + if (rising ^ forward): print('Flipping thing') y = [(-1*z + 2*height) for z in y] - - # deal with `forward` 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 - # print('SPECIAL CASE') - # start_index -= 1 + if start_index == len(x): + # happens when forward=False and the edge find is the end of the sim + print('SPECIAL CASE') + start_index -= 1 #print('found start index', start_index, 'between', x[start_index], x[start_index+1]) i = start_index edges = [] while len(edges) < count: print('i starts at', i, 'x is', x[i], 'y is', y[i]) - print('wil iterate as long as y[i] is higher than', height) + print('will iterate as long as y[i] is higher than', height) + # 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) print('middle is at', i, 'x is', x[i], 'y is', y[i]) - # now move backwards until we hit the low + # now move until we hit the high while y[i] <= height: i += direction if i < 0 or i >= len(y): diff --git a/tests/test_read_spice.py b/tests/test_read_spice.py index 1671d662..ebaceaf8 100644 --- a/tests/test_read_spice.py +++ b/tests/test_read_spice.py @@ -77,7 +77,7 @@ def dont_test_inv_tf( -def test_edge( +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 ): @@ -138,10 +138,64 @@ def eq(xs, ys): print(c.value) print(d.value) - assert eq(a.value, [-0.5e-3, -2.5e-3]) - assert eq(b.value, [-0e-3, -2e-3])# TODO should this be [0, 2] ? + 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], 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) + + # we'll just test on the clean input signals for now + tester.read(dut.a[1], style='phase', params={'ref':dut.a[0]}) + + + # 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) From 6a61712f533e111d07d99b0f74be4c2c3e66c4ea Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Fri, 22 Nov 2019 15:45:47 -0800 Subject: [PATCH 16/74] Deleted some debugging prints --- fault/spice_target.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/fault/spice_target.py b/fault/spice_target.py index 9a2d80e4..cdd88989 100644 --- a/fault/spice_target.py +++ b/fault/spice_target.py @@ -482,7 +482,6 @@ def process_reads(self, results, reads): ref = self.find_edge(res_ref.x, res_ref.y, time, count=2) edge = self.find_edge(res.x, res.y, time + ref[0]) fraction = (edge[0] - ref[1]) / (ref[0] -ref[1]) - print(ref, edge) # TODO multiply by 2pi? read.value = fraction else: @@ -494,7 +493,6 @@ def find_edge(self, x, y, t_start, height=None, forward=False, count=1, rising=T waveform crosses height (defaut is ???). Searches backwards by default (frequency now is probably based on the last few edges?) ''' - print('\nNEW REQUEST', forward, rising, t_start) if height is None: # default comes out to 0.5 @@ -503,7 +501,6 @@ def find_edge(self, x, y, t_start, height=None, forward=False, count=1, rising=T # deal with `rising` and `forward` # normally a low-to-high finder if (rising ^ forward): - print('Flipping thing') 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 @@ -513,21 +510,17 @@ def find_edge(self, x, y, t_start, height=None, forward=False, count=1, rising=T 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 - print('SPECIAL CASE') start_index -= 1 - #print('found start index', start_index, 'between', x[start_index], x[start_index+1]) + i = start_index edges = [] while len(edges) < count: - print('i starts at', i, 'x is', x[i], 'y is', y[i]) - print('will iterate as long as y[i] is higher than', height) # 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) - print('middle is at', i, 'x is', x[i], 'y is', y[i]) # now move until we hit the high while y[i] <= height: i += direction @@ -539,13 +532,9 @@ def find_edge(self, x, y, t_start, height=None, forward=False, count=1, rising=T # TODO: there's an issue because of the discrepancy between the requested # start time and actually staring at the nearest edge - print('finishes at', i, 'x is', x[i], 'y is', y[i]) # the crossing happens from i to i+1 fraction = (height-y[i]) / (y[i-direction]-y[i]) - print('fraction is', fraction) t = x[i] + fraction * (x[i+1] - x[i]) - print('found edge at ', t) - print('which maps to offset of ', t-t_start) if t==t_start: print('EDGE EXACTLY AT EDGE FIND REQUEST') edges.append(t-t_start) From 4272bb4f39d9d1cccd600f2280fde286cd501c58 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Fri, 22 Nov 2019 16:04:44 -0800 Subject: [PATCH 17/74] Fixed bug in phase measurement and added test --- fault/spice_target.py | 4 ++-- tests/test_read_spice.py | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/fault/spice_target.py b/fault/spice_target.py index cdd88989..afd65a74 100644 --- a/fault/spice_target.py +++ b/fault/spice_target.py @@ -480,8 +480,8 @@ def process_reads(self, results, reads): assert 'ref' in read.params, 'Phase read requires reference signal param' res_ref = results[f'{read.params["ref"].name}'] ref = self.find_edge(res_ref.x, res_ref.y, time, count=2) - edge = self.find_edge(res.x, res.y, time + ref[0]) - fraction = (edge[0] - ref[1]) / (ref[0] -ref[1]) + before_cycle_end = self.find_edge(res.x, res.y, time + ref[0]) + fraction = 1 + before_cycle_end[0] / (ref[0] -ref[1]) # TODO multiply by 2pi? read.value = fraction else: diff --git a/tests/test_read_spice.py b/tests/test_read_spice.py index ebaceaf8..9ba73030 100644 --- a/tests/test_read_spice.py +++ b/tests/test_read_spice.py @@ -173,6 +173,8 @@ def test_phase( # 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) @@ -182,8 +184,9 @@ def test_phase( tester.poke(dut.a[0], 1, delay = 0.2e-3) tester.poke(dut.a[1], 1, delay = 0.3e-3) - # we'll just test on the clean input signals for now - tester.read(dut.a[1], style='phase', params={'ref':dut.a[0]}) + 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 @@ -199,3 +202,12 @@ def test_phase( # 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 From 35faadf391a7a8317f9e37c44338f58215d8b17e Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Fri, 22 Nov 2019 16:51:32 -0800 Subject: [PATCH 18/74] Changed background poke so it would not break spice reads. Still an issue with verilog reads --- fault/background_poke.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/fault/background_poke.py b/fault/background_poke.py index fb972a55..af18e778 100644 --- a/fault/background_poke.py +++ b/fault/background_poke.py @@ -174,9 +174,15 @@ def process(self, action, delay): # now we add this (shortened) action back in if not is_background: - new_action = copy.copy(action) - new_action.delay = new_delay - new_action_list.append(new_action) + # 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 + 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 return new_action_list From 893c859d7600b940fb942a7c6e8a79778361e3b9 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Mon, 2 Dec 2019 17:04:20 -0800 Subject: [PATCH 19/74] Fixed default clock step --- fault/system_verilog_target.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fault/system_verilog_target.py b/fault/system_verilog_target.py index a003fb62..f072186a 100644 --- a/fault/system_verilog_target.py +++ b/fault/system_verilog_target.py @@ -40,7 +40,7 @@ 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_vcd=True, no_warning=False, sim_env=None, ext_model_file=None, ext_libs=None, defines=None, flags=None, inc_dirs=None, From 7aa57ff1538e80160228422f39d578bf083d578a Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Wed, 4 Dec 2019 17:04:55 -0800 Subject: [PATCH 20/74] Fixed bug with clocks in background_poke --- fault/background_poke.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/fault/background_poke.py b/fault/background_poke.py index af18e778..116d2dd8 100644 --- a/fault/background_poke.py +++ b/fault/background_poke.py @@ -15,7 +15,7 @@ class Thread(): epsilon = 1e-18 def __init__(self, time, poke): - print('creating thread for', poke, 'at time', time) + #print('creating thread for', poke, 'at time', time) self.poke = copy.copy(poke) self.poke.params = None self.poke.delay = None @@ -24,8 +24,8 @@ def __init__(self, time, poke): self.next_update = time type_ = params.get('type', 'clock') - print('type_ is', type_) - print(params) + #print('type_ is', type_) + #print(params) # Each type must set a get_val(t) function and a dt if type_ == 'clock': @@ -49,7 +49,6 @@ def get_val(t): freq = params.get('freq', 1e6) period = params.get('period', 1/freq) freq = 1 / period - print('Settled on frequency', freq) amplitude = params.get('amplitude', 1) offset = params.get('offset', 0) phase_degrees = params.get('phase_degrees', 0) @@ -62,10 +61,8 @@ def get_val(t): self.get_val = get_val self.dt = params.get('dt', 1 / (freq*self.default_steps_per_cycle)) - print('calculated dt of', self.dt, 'from', freq, self.default_steps_per_cycle) def step(self, t): - print('stepping thread ', self, 'at time', t) ''' Returns a new Poke object with the correct value set for time t. Sets the port and value but NOT the delay. @@ -73,11 +70,11 @@ def step(self, t): # 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 - self.next_update = t + self.dt + 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 - #print('after step, next scheduled update is', self.next_update) return poke def __lt__(self, other): @@ -185,6 +182,11 @@ def process(self, action, 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 From bfeba86208fcd552337121cc0161866974356b4a Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Wed, 4 Dec 2019 17:05:46 -0800 Subject: [PATCH 21/74] Changed pwc to pwl conversion to allow two points within rt of each other --- fault/pwl.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/fault/pwl.py b/fault/pwl.py index 5965fc4f..ce2278d4 100644 --- a/fault/pwl.py +++ b/fault/pwl.py @@ -11,10 +11,31 @@ 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 retval[-2][0] == t_curr: + # two values at the same time, just drop the earlier + #print('dropping old thing') + retval.pop() + old_t, old_v = retval.pop() + v_prev = old_v + #print('prev is now', retval[-2:]) + 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 From 2dc806331408eb1a60188032431b0e3cc553ae43 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Wed, 4 Dec 2019 17:07:41 -0800 Subject: [PATCH 22/74] Changed spice_target to print stderr of simulation command --- fault/spice_target.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/fault/spice_target.py b/fault/spice_target.py index ece2d910..73412daf 100644 --- a/fault/spice_target.py +++ b/fault/spice_target.py @@ -129,13 +129,10 @@ def __init__(self, circuit, directory="build/", simulator='ngspice', def run(self, actions): # expand background pokes into regular pokes - print('before', len(actions)) actions = process_action_list(actions, self.clock_step_delay) - print('after', len(actions)) # compile the actions comp = self.compile_actions(actions) - print('compiled action pwls:', comp.pwls) # write the testbench tb_file = self.write_test_bench(comp) @@ -152,8 +149,10 @@ def run(self, actions): # run the simulation commands for sim_cmd in sim_cmds: - subprocess_run(sim_cmd, cwd=self.directory, env=self.sim_env, + res = subprocess_run(sim_cmd, cwd=self.directory, env=self.sim_env, disp_type=self.disp_type) + #print(res.stdout) + print(res.stderr.strip()) # process the results if self.simulator in {'ngspice', 'spectre'}: From ae39e7a7eefc90e4c47962a0743e8f3fef14d176 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Wed, 4 Dec 2019 17:08:21 -0800 Subject: [PATCH 23/74] Marked read action as not implemented in verilator_target --- fault/verilator_target.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fault/verilator_target.py b/fault/verilator_target.py index ebd4bee8..bd61a913 100644 --- a/fault/verilator_target.py +++ b/fault/verilator_target.py @@ -281,6 +281,10 @@ def make_print(self, i, action): ports = ", " + ports return [f'printf("{action.format_str}"{ports});'] + def make_read(self, i, action): + msg = 'read not implemented for Verilator target' + raise NotImplementedError(msg) + def make_expect(self, i, action): # For verilator, if an expect is "AnyValue" we don't need to # perform the expect. From a6542d0de246a7c79c266d50b1ce60f88d02c33c Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Mon, 6 Jan 2020 09:55:19 -0800 Subject: [PATCH 24/74] Bugfix and labeling of error output. --- fault/spice_target.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fault/spice_target.py b/fault/spice_target.py index 73412daf..a613a339 100644 --- a/fault/spice_target.py +++ b/fault/spice_target.py @@ -152,7 +152,10 @@ def run(self, actions): res = subprocess_run(sim_cmd, cwd=self.directory, env=self.sim_env, disp_type=self.disp_type) #print(res.stdout) - print(res.stderr.strip()) + stderr = res.stderr.strip() + if stderr != '': + print('Stderr from spice simulator:') + print(stderr) # process the results if self.simulator in {'ngspice', 'spectre'}: @@ -486,6 +489,8 @@ def process_reads(self, results, reads): res = results[f'{read.port.name}'] if read.style == 'single': value = res(time) + if type(value) == np.ndarray: + value = value.tolist() read.value = value elif read.style == 'edge': value = self.find_edge(res.x, res.y, time, **read.params) From 4939b873704e4429de3029583eebfadd36228fe4 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Tue, 21 Jan 2020 16:24:57 -0800 Subject: [PATCH 25/74] New read styles, decreased default step size in spice transient sim --- fault/spice_target.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/fault/spice_target.py b/fault/spice_target.py index a613a339..8f2be23a 100644 --- a/fault/spice_target.py +++ b/fault/spice_target.py @@ -411,7 +411,7 @@ def write_test_bench(self, comp, tb_file=None): # specify the transient analysis t_step = (self.t_step if self.t_step is not None - else comp.stop_time / 1000) + else comp.stop_time / 100000) uic = self.ic != {} netlist.tran(t_step=t_step, t_stop=comp.stop_time, uic=uic) @@ -495,6 +495,10 @@ def process_reads(self, results, reads): elif read.style == 'edge': value = self.find_edge(res.x, res.y, time, **read.params) read.value = value + elif read.style == 'frequency': + edges = self.find_edge(res.x, res.y, time, count=2) + freq = 1 / (edges[0] - edges[1]) + read.value = freq elif read.style == 'phase': assert 'ref' in read.params, 'Phase read requires reference signal param' res_ref = results[f'{read.params["ref"].name}'] @@ -503,6 +507,18 @@ def process_reads(self, results, reads): fraction = 1 + before_cycle_end[0] / (ref[0] -ref[1]) # TODO multiply by 2pi? read.value = fraction + elif read.style == 'block': + assert 'duration' in read.params, 'Block read requires duration' + duration = read.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.x > time) - 1) + end= min(len(res.x)-1, np.argmax(res.x >= time + duration)) + + + x = res.x[start:end] - time + y = res.y[start:end] + read.value = (x, y) else: raise NotImplementedError(f'Unknown read style "{read.style}"') From 096a74fb3819de7bfab2f9db923c998b75a9595e Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Mon, 27 Jan 2020 12:47:39 -0800 Subject: [PATCH 26/74] fixed issue with block reads beyond the end of simulation time --- fault/spice_target.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fault/spice_target.py b/fault/spice_target.py index 8f2be23a..740b4b0e 100644 --- a/fault/spice_target.py +++ b/fault/spice_target.py @@ -513,11 +513,14 @@ def process_reads(self, results, reads): # make sure to grab points surrounding requested times so user can interpolate # the exact start and end. start = max(0, np.argmax(res.x > time) - 1) - end= min(len(res.x)-1, np.argmax(res.x >= time + duration)) + end= (len(res.x)-1) if res.x[-1] < time + duration else np.argmax(res.x >= time + duration) x = res.x[start:end] - time y = res.y[start:end] + # if len(x) < 100 or len(y) < 100: + # print('trouble with block read') + # pass read.value = (x, y) else: raise NotImplementedError(f'Unknown read style "{read.style}"') From 657e003ef884f34fdadd6ef5446fdad0ad7c96e5 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Mon, 24 Feb 2020 14:44:41 -0800 Subject: [PATCH 27/74] Got rid of some references to old style read using save_for_later. Still need to fix tests, but will wait until after merge --- fault/actions.py | 3 +-- fault/spice_target.py | 8 -------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/fault/actions.py b/fault/actions.py index c47f13fc..911c7242 100644 --- a/fault/actions.py +++ b/fault/actions.py @@ -118,7 +118,7 @@ def is_output(port): class Expect(PortAction): def __init__(self, port, value, strict=False, abs_tol=None, rel_tol=None, - above=None, below=None, save_for_later=False): + above=None, below=None): # call super constructor super().__init__(port, value) @@ -142,7 +142,6 @@ def __init__(self, port, value, strict=False, abs_tol=None, rel_tol=None, self.strict = strict self.above = above self.below = below - self.save_for_later = save_for_later class Assume(PortAction): diff --git a/fault/spice_target.py b/fault/spice_target.py index 740b4b0e..fddb468d 100644 --- a/fault/spice_target.py +++ b/fault/spice_target.py @@ -124,9 +124,6 @@ def __init__(self, circuit, directory="build/", simulator='ngspice', self.ic = ic if ic is not None else {} self.disp_type = disp_type - # place for saving expects that were "save_for_later" - self.saved_for_later = [] - def run(self, actions): # expand background pokes into regular pokes actions = process_action_list(actions, self.clock_step_delay) @@ -453,11 +450,6 @@ def impl_expect(self, results, time, action): else: raise A2DError(f'Invalid logic level: {value}.') - if action.save_for_later: - # save the value and don't check - self.saved_for_later.append(value) - return - # implement the requested check if action.above is not None: if action.below is not None: From dfa051026e683af98068bfb7a438bd1c410a7000 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Tue, 3 Mar 2020 18:45:46 -0800 Subject: [PATCH 28/74] Removed many references to read action. --- fault/actions.py | 6 +-- fault/spice_target.py | 118 ++++++++++++++++++++++-------------------- fault/tester.py | 13 +---- 3 files changed, 67 insertions(+), 70 deletions(-) diff --git a/fault/actions.py b/fault/actions.py index dc576ba3..54f2041c 100644 --- a/fault/actions.py +++ b/fault/actions.py @@ -78,10 +78,9 @@ def retarget(self, new_circuit, clock): class Read(Action): - def __init__(self, port, style='single', params={}): + def __init__(self, port, params=None): super().__init__() self.port = port - self.style = style self.params = params def __getattr__(self, name): @@ -121,9 +120,10 @@ def is_output(port): 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/spice_target.py b/fault/spice_target.py index f0a0d3eb..7ce8bf70 100644 --- a/fault/spice_target.py +++ b/fault/spice_target.py @@ -28,11 +28,10 @@ class EdgeNotFoundError(Exception): pass class CompiledSpiceActions: - def __init__(self, pwls, checks, prints, stop_time, saves, gets, reads): + def __init__(self, pwls, checks, prints, stop_time, saves, gets): self.pwls = pwls self.checks = checks self.prints = prints - self.reads = reads self.stop_time = stop_time self.saves = saves self.gets = gets @@ -273,8 +272,6 @@ def run(self, actions): # implement all of the gets self.impl_all_gets(results=results, gets=comp.gets) - # set values on reads - self.process_reads(results, comp.reads) # check results self.check_results(results=results, checks=comp.checks) @@ -316,7 +313,6 @@ def compile_actions(self, actions): pwc_dict = {} checks = [] prints = [] - reads = [] gets = [] # TODO is this still necessary? @@ -371,15 +367,16 @@ def compile_actions(self, actions): # TODO: is this still necessary? for port in action.ports: saves.add(f'{port.name}') - 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}') + # 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): @@ -402,7 +399,6 @@ def compile_actions(self, actions): pwls=pwls, checks=checks, prints=prints, - reads=reads, gets=gets, stop_time=t, saves=self.saves @@ -629,43 +625,6 @@ def print_results(self, results, prints): def process_reads(self, results, reads): for time, read in reads: res = results[f'{read.port.name}'] - if read.style == 'single': - value = res(time) - if type(value) == np.ndarray: - value = value.tolist() - read.value = value - elif read.style == 'edge': - value = self.find_edge(res.t, res.v, time, **read.params) - read.value = value - elif read.style == 'frequency': - edges = self.find_edge(res.t, res.v, time, count=2) - freq = 1 / (edges[0] - edges[1]) - read.value = freq - elif read.style == 'phase': - assert 'ref' in read.params, 'Phase read requires reference signal param' - res_ref = results[f'{read.params["ref"].name}'] - ref = self.find_edge(res_ref.t, res_ref.v, time, count=2) - before_cycle_end = self.find_edge(res.t, res.v, time + ref[0]) - fraction = 1 + before_cycle_end[0] / (ref[0] -ref[1]) - # TODO multiply by 2pi? - read.value = fraction - elif read.style == 'block': - assert 'duration' in read.params, 'Block read requires duration' - duration = read.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) - - - x = res.t[start:end] - time - y = res.v[start:end] - # if len(x) < 100 or len(y) < 100: - # print('trouble with block read') - # pass - read.value = (x, y) - else: - raise NotImplementedError(f'Unknown read style "{read.style}"') def find_edge(self, x, y, t_start, height=None, forward=False, count=1, rising=True): ''' @@ -725,10 +684,57 @@ def impl_all_gets(self, results, 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 + # grab the relevant info + res = results[f'{action.port.name}'] + if action.params == None: + # straightforward read of voltage + # get port values + port_value = res(time) + # write value back to action + action.value = port_value + else: + # requires some analysis of signal + style = action.params['style'] + 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 self.find_edge for possible parameters + value = self.find_edge(res.t, res.v, time, **action.params) + action.value = value + elif style == 'frequency': + # frequency based on the (previous?) two rising edges + edges = self.find_edge(res.t, res.v, time, 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'{action.params["ref"].name}'] + ref = self.find_edge(res_ref.t, res_ref.v, time, count=2) + before_cycle_end = self.find_edge(res.t, res.v, time + ref[0]) + 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 ngspice_cmds(self, tb_file): # build up the command diff --git a/fault/tester.py b/fault/tester.py index 74a76923..4d83e816 100644 --- a/fault/tester.py +++ b/fault/tester.py @@ -202,15 +202,6 @@ def print(self, format_str, *args): """ self.actions.append(actions.Print(format_str, *args)) - def read(self, port, style='single', params={}): - """ - Returns a Read action. After running the simulation, the value of - `port` will be saved in the Read object's 'value' attribute. - """ - r = actions.Read(port, style, params) - self.actions.append(r) - return r - def assert_(self, expr): if not isinstance(expr, expression.Expression): raise TypeError("Expected instance of Expression") @@ -274,12 +265,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 From f14e6a4fc5684cda3c1773cb3641ec7a0158abb0 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Wed, 4 Mar 2020 14:29:15 -0800 Subject: [PATCH 29/74] Fixed mistake from earlier merge --- fault/system_verilog_target.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fault/system_verilog_target.py b/fault/system_verilog_target.py index 9d05dd96..45b49c79 100644 --- a/fault/system_verilog_target.py +++ b/fault/system_verilog_target.py @@ -50,6 +50,8 @@ class SystemVerilogTarget(VerilogTarget): LOOP_VAR_TYPE = None 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=5e-9, num_cycles=10000, dump_waveforms=True, dump_vcd=None, no_warning=False, sim_env=None, ext_model_file=None, From 24210297938c484454616b3c9f39416023a0e0bb Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Wed, 4 Mar 2020 14:29:27 -0800 Subject: [PATCH 30/74] Changed background poke to a decorator --- fault/actions.py | 3 +-- fault/background_poke.py | 8 ++++++++ fault/spice_target.py | 5 ++--- tests/test_background_poke.py | 16 ++++++---------- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/fault/actions.py b/fault/actions.py index 54f2041c..83d3ad6c 100644 --- a/fault/actions.py +++ b/fault/actions.py @@ -50,12 +50,11 @@ def is_input(port): class Poke(PortAction): - def __init__(self, port, value, delay=None, background_params=None): + def __init__(self, port, value, delay=None): if is_input(port): raise ValueError(f"Can only poke inputs: {port.debug_name} " f"{type(port)}") self.delay = delay - self.background_params = background_params super().__init__(port, value) diff --git a/fault/background_poke.py b/fault/background_poke.py index 116d2dd8..9e1b9ced 100644 --- a/fault/background_poke.py +++ b/fault/background_poke.py @@ -3,6 +3,7 @@ import math from functools import total_ordering import copy +from fault.target import Target @total_ordering @@ -215,3 +216,10 @@ def get_delay(a): 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/spice_target.py b/fault/spice_target.py index 7ce8bf70..5a4d41e7 100644 --- a/fault/spice_target.py +++ b/fault/spice_target.py @@ -13,7 +13,7 @@ from fault.subprocess_run import subprocess_run from fault.pwl import pwc_to_pwl from fault.actions import Poke, Expect, Delay, Print, GetValue, Eval, Read -from fault.background_poke import process_action_list +from fault.background_poke import background_poke_target from fault.select_path import SelectPath from .fault_errors import A2DError, ExpectError @@ -81,6 +81,7 @@ 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, @@ -217,8 +218,6 @@ def __init__(self, circuit, directory="build/", simulator='ngspice', self.saves.add(f'{name}') def run(self, actions): - # expand background pokes into regular pokes - actions = process_action_list(actions, self.clock_step_delay) # compile the actions comp = self.compile_actions(actions) diff --git a/tests/test_background_poke.py b/tests/test_background_poke.py index 5be48407..1fc62bbc 100644 --- a/tests/test_background_poke.py +++ b/tests/test_background_poke.py @@ -35,15 +35,10 @@ def test_clock_verilog(target, simulator): # take default initial_value of 0 }) - tester.expect(circ.O, 1) # should fail - tester.expect(circ.O, 0) # should fail - - - tester.expect(circ.O, 0, save_for_later=True) - + 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: @@ -56,7 +51,7 @@ def test_clock_verilog(target, simulator): print('JUST FINISHED COMPILENANDRUN') -@pytest.mark.skip(reason='Turn this back on later') +#@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 @@ -96,8 +91,9 @@ def test_sin_spice(vsup=1.5, vil_rel=0.4, vih_rel=0.6, num_reads = 100 xs = [] dt = 1/(freq * 50) + gets = [] for k in range(num_reads): - tester.expect(dut.in_, 0, save_for_later=True) + gets.append(tester.get_value(dut.in_)) tester.delay(dt) xs.append(k*dt) @@ -126,7 +122,7 @@ def test_sin_spice(vsup=1.5, vil_rel=0.4, vih_rel=0.6, ys = [] for k in range(num_reads): - value = tester.targets[target].saved_for_later[k] + value = gets[k].value ys.append(value) print('%2d\t'%k, value) From 764feb2294522f92bfbe71feab598d62b3d5b011 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Wed, 4 Mar 2020 14:32:00 -0800 Subject: [PATCH 31/74] updated sv target to use new background poke decorator --- fault/system_verilog_target.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/fault/system_verilog_target.py b/fault/system_verilog_target.py index 45b49c79..29bef687 100644 --- a/fault/system_verilog_target.py +++ b/fault/system_verilog_target.py @@ -13,7 +13,7 @@ from fault.select_path import SelectPath from fault.wrapper import PortWrapper from fault.subprocess_run import subprocess_run -from fault.background_poke import process_action_list +from fault.background_poke import background_poke_target import fault import fault.expression as expression from fault.real_type import RealKind @@ -41,7 +41,7 @@ endmodule """ - +@background_poke_target class SystemVerilogTarget(VerilogTarget): # Language properties of SystemVerilog used in generating code blocks @@ -687,9 +687,6 @@ def run(self, actions, power_args=None): # set defaults power_args = power_args if power_args is not None else {} - # expand background pokes into regular pokes - actions = process_action_list(actions, self.clock_step_delay) - # assemble list of sources files vlog_srcs = [] if not self.ext_test_bench: From 8181e6a18a5a578dfe973348373579e1195f63eb Mon Sep 17 00:00:00 2001 From: Steven Herbst Date: Thu, 5 Mar 2020 15:34:48 -0800 Subject: [PATCH 32/74] add timeout to buildkite flow --- .buildkite/pipeline.yml | 1 + 1 file changed, 1 insertion(+) 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: | From b8e2087659c2becddd56a8af4fefccf5fbeb9621 Mon Sep 17 00:00:00 2001 From: Lenny Truong Date: Tue, 10 Mar 2020 09:58:54 -0700 Subject: [PATCH 33/74] Init synchronous tester --- fault/__init__.py | 3 ++- fault/system_verilog_target.py | 21 +++++++++++++++++ fault/tester/__init__.py | 1 + fault/tester/staged_tester.py | 3 +++ fault/tester/synchronous.py | 21 +++++++++++++++++ tests/test_tester/test_synchronous.py | 33 +++++++++++++++++++++++++++ 6 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 fault/tester/synchronous.py create mode 100644 tests/test_tester/test_synchronous.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/system_verilog_target.py b/fault/system_verilog_target.py index d29cd8a7..38f54f58 100644 --- a/fault/system_verilog_target.py +++ b/fault/system_verilog_target.py @@ -25,6 +25,8 @@ module {top_module}; {declarations} {assigns} +{clock_drivers} + {circuit_name} #( {param_list} ) dut ( @@ -175,6 +177,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 " @@ -619,10 +622,13 @@ def generate_code(self, actions, power_args): # add timescale timescale = f'`timescale {self.timescale}' + clock_drivers = self.TAB + "\n{self.TAB}".join(self.clock_drivers) + # fill out values in the testbench template src = src_tpl.format( timescale=timescale, declarations=declarations, + clock_drivers=clock_drivers, assigns=assigns, initial_body=initial_body, port_list=port_list, @@ -999,3 +1005,18 @@ def iverilog_cmd(self, sources): # 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/staged_tester.py b/fault/tester/staged_tester.py index d6ddc7c9..969e8b67 100644 --- a/fault/tester/staged_tester.py +++ b/fault/tester/staged_tester.py @@ -422,3 +422,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..b13ac6be --- /dev/null +++ b/fault/tester/synchronous.py @@ -0,0 +1,21 @@ +from .staged_tester import StagedTester +from ..system_verilog_target import SynchronousSystemVerilogTarget + + +class SynchronousTester(StagedTester): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # 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) + return super().make_target(target, **kwargs) diff --git a/tests/test_tester/test_synchronous.py b/tests/test_tester/test_synchronous.py new file mode 100644 index 00000000..cb3ff165 --- /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) From 4e3176a10e126e4b3e02d4a16aed7e2533420793 Mon Sep 17 00:00:00 2001 From: Lenny Truong Date: Tue, 10 Mar 2020 10:01:20 -0700 Subject: [PATCH 34/74] Require clock --- fault/tester/synchronous.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fault/tester/synchronous.py b/fault/tester/synchronous.py index b13ac6be..0bfc0ec1 100644 --- a/fault/tester/synchronous.py +++ b/fault/tester/synchronous.py @@ -5,6 +5,8 @@ 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) From 2b7485a1a6860c6f88e60c0f17aebda621e8e852 Mon Sep 17 00:00:00 2001 From: Lenny Truong Date: Tue, 10 Mar 2020 10:19:35 -0700 Subject: [PATCH 35/74] Add verilator target --- fault/tester/synchronous.py | 4 ++++ fault/verilator_target.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/fault/tester/synchronous.py b/fault/tester/synchronous.py index 0bfc0ec1..0cba848b 100644 --- a/fault/tester/synchronous.py +++ b/fault/tester/synchronous.py @@ -1,5 +1,6 @@ from .staged_tester import StagedTester from ..system_verilog_target import SynchronousSystemVerilogTarget +from ..verilator_target import SynchronousVerilatorTarget class SynchronousTester(StagedTester): @@ -20,4 +21,7 @@ def make_target(self, target, **kwargs): if target == "system-verilog": return SynchronousSystemVerilogTarget(self._circuit, clock=self.clock, **kwargs) + elif target == "system-verilog": + return SynchronousVerilatorTarget(self._circuit, clock=self.clock, + **kwargs) return super().make_target(target, **kwargs) diff --git a/fault/verilator_target.py b/fault/verilator_target.py index e75c8976..5ebf8149 100644 --- a/fault/verilator_target.py +++ b/fault/verilator_target.py @@ -696,3 +696,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 From f1701c1c6dbdf08be1304f2bf05331a15ccd3cdd Mon Sep 17 00:00:00 2001 From: Lenny Truong Date: Tue, 10 Mar 2020 10:20:37 -0700 Subject: [PATCH 36/74] Use open API --- tests/test_tester/test_core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_tester/test_core.py b/tests/test_tester/test_core.py index cf03d221..c86ae90b 100644 --- a/tests/test_tester/test_core.py +++ b/tests/test_tester/test_core.py @@ -398,7 +398,8 @@ 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()) + with SimpleALU_inst0.open(): + ConfigReg() circ = m.DefineCircuit("top", "a", m.In(m.Bits[16]), From c59cd1633bb7209a07d5ee9e107c9984e949db46 Mon Sep 17 00:00:00 2001 From: Lenny Truong Date: Tue, 10 Mar 2020 10:36:10 -0700 Subject: [PATCH 37/74] Fix name --- tests/test_tester/test_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_tester/test_core.py b/tests/test_tester/test_core.py index c86ae90b..c6550c8d 100644 --- a/tests/test_tester/test_core.py +++ b/tests/test_tester/test_core.py @@ -398,7 +398,7 @@ 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"]) - with SimpleALU_inst0.open(): + with SimpleALU.open(): ConfigReg() circ = m.DefineCircuit("top", From ec47f77a24254b5c32fc50c3f1e129898d74eb25 Mon Sep 17 00:00:00 2001 From: Lenny Truong Date: Tue, 10 Mar 2020 10:36:32 -0700 Subject: [PATCH 38/74] Fix whitespace --- tests/test_tester/test_synchronous.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_tester/test_synchronous.py b/tests/test_tester/test_synchronous.py index cb3ff165..46f9c84b 100644 --- a/tests/test_tester/test_synchronous.py +++ b/tests/test_tester/test_synchronous.py @@ -8,7 +8,7 @@ def pytest_generate_tests(metafunc): def test_synchronous_basic(target, simulator): - ops =[ + ops = [ lambda x, y: x + y, lambda x, y: x - y, lambda x, y: x * y, From 1f8ae6c224022c3406fb3bb68d054210114d767a Mon Sep 17 00:00:00 2001 From: Lenny Truong Date: Wed, 11 Mar 2020 12:59:15 -0700 Subject: [PATCH 39/74] Style --- fault/tester/synchronous.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fault/tester/synchronous.py b/fault/tester/synchronous.py index 0cba848b..60a23f48 100644 --- a/fault/tester/synchronous.py +++ b/fault/tester/synchronous.py @@ -21,7 +21,7 @@ def make_target(self, target, **kwargs): if target == "system-verilog": return SynchronousSystemVerilogTarget(self._circuit, clock=self.clock, **kwargs) - elif target == "system-verilog": + if target == "system-verilog": return SynchronousVerilatorTarget(self._circuit, clock=self.clock, **kwargs) return super().make_target(target, **kwargs) From 5b42aaaffe61d0131268e8d4b64530e9f0b048bb Mon Sep 17 00:00:00 2001 From: Lenny Truong Date: Wed, 11 Mar 2020 13:47:15 -0700 Subject: [PATCH 40/74] Release 3.0.8 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 733b7edf..2410a627 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ setup( name='fault', - version='3.0.7', + version='3.0.8', description=DESCRIPTION, scripts=[], packages=[ From 620bebddbe1cc14188343d6ab422059691cd2d82 Mon Sep 17 00:00:00 2001 From: Caleb Donovick Date: Thu, 12 Mar 2020 15:33:19 -0700 Subject: [PATCH 41/74] Add failing test case --- tests/test_protocol.py | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/test_protocol.py diff --git a/tests/test_protocol.py b/tests/test_protocol.py new file mode 100644 index 00000000..120b064e --- /dev/null +++ b/tests/test_protocol.py @@ -0,0 +1,46 @@ + +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) + From 95234fc534c60925620a7fbebdc4fe30c7114402 Mon Sep 17 00:00:00 2001 From: Lenny Truong Date: Thu, 12 Mar 2020 16:27:34 -0700 Subject: [PATCH 42/74] Support MagmaProtocol --- fault/value_utils.py | 3 +++ tests/test_protocol.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) 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/tests/test_protocol.py b/tests/test_protocol.py index 120b064e..215d944c 100644 --- a/tests/test_protocol.py +++ b/tests/test_protocol.py @@ -43,4 +43,4 @@ def test_proto(): tester.circuit.val = 1 tester.eval() tester.circuit.O.expect(2) - + tester.compile_and_run("verilator", flags=['-Wno-unused']) From 0c7820f43a4962dfc194101c6c722770e65cca20 Mon Sep 17 00:00:00 2001 From: Lenny Truong Date: Thu, 12 Mar 2020 17:05:37 -0700 Subject: [PATCH 43/74] Fix style --- tests/test_protocol.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_protocol.py b/tests/test_protocol.py index 215d944c..5b8c6fd7 100644 --- a/tests/test_protocol.py +++ b/tests/test_protocol.py @@ -2,6 +2,7 @@ import magma as m import fault + class MWrapperMeta(m.MagmaProtocolMeta): def __getitem__(cls, T): assert cls is MWrapper @@ -19,6 +20,7 @@ def _flip_magma_(cls): 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_): @@ -31,13 +33,16 @@ def _get_magma_value_(self): 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 From 97b5401982618a799f3a7408aaffcd2c0c09406d Mon Sep 17 00:00:00 2001 From: Steven Herbst Date: Thu, 12 Mar 2020 23:55:27 -0700 Subject: [PATCH 44/74] implement top_module for all simulators --- fault/system_verilog_target.py | 47 ++++++++++++++---------------- tests/test_top_module.py | 29 ++++++++++++++++++ tests/verilog/myinv_extra_module.v | 21 +++++++++++++ 3 files changed, 72 insertions(+), 25 deletions(-) create mode 100644 tests/test_top_module.py create mode 100644 tests/verilog/myinv_extra_module.v diff --git a/fault/system_verilog_target.py b/fault/system_verilog_target.py index 38f54f58..d4281d5f 100644 --- a/fault/system_verilog_target.py +++ b/fault/system_verilog_target.py @@ -160,6 +160,15 @@ def __init__(self, circuit, circuit_name=None, directory="build/", # set default for magma compilation options magma_opts = magma_opts if magma_opts is not None else {} + # set default for top_module + if top_module is None: + if use_kratos: + top_module = 'TOP' + elif ext_test_bench: + top_module = f'{circuit_name}' + else: + top_module = f'{circuit_name}_tb' + # call the super constructor super().__init__(circuit, circuit_name, directory, skip_compile, include_verilog_libraries, magma_output, @@ -195,7 +204,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 @@ -613,12 +622,6 @@ def generate_code(self, actions, power_args): 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}' @@ -634,7 +637,7 @@ def generate_code(self, actions, power_args): 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 @@ -813,15 +816,10 @@ def write_vivado_tcl(self, sources=None, proj_name='project', proj_dir=None, 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 - else: - # have Vivado pick the top module automatically if not specified - tcl_cmds += [f'update_compile_order -fileset sim_1'] + # if in the future we want to run simulations without defining the top + # module, then this TCL command can be used instead: + # "update_compile_order -fileset sim_1" + tcl_cmds += [f'set_property -name top -value {self.top_module} -objects [get_fileset sim_1]'] # noqa # 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 @@ -852,15 +850,8 @@ 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 - # send name of top module to the simulator - if top is not None: - cmd += ['-top', f'{top}'] + cmd += ['-top', f'{self.top_module}'] # timescale cmd += ['-timescale', f'{self.timescale}'] @@ -963,6 +954,9 @@ def vcs_cmd(self, sources): if self.dump_waveforms: cmd += ['+vcs+vcdpluson', '-debug_pp'] + # specify top module + cmd += ['-top', f'{self.top_module}'] + # return arg list and binary file location return cmd, './simv' @@ -1003,6 +997,9 @@ def iverilog_cmd(self, sources): # source files cmd += [f'{src}' for src in sources] + # set the top module + cmd += ['-s', f'{self.top_module}'] + # return arg list and binary file location return cmd, bin_file diff --git a/tests/test_top_module.py b/tests/test_top_module.py new file mode 100644 index 00000000..d07aad05 --- /dev/null +++ b/tests/test_top_module.py @@ -0,0 +1,29 @@ +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') + + +def test_ext_vlog(target, simulator): + # declare circuit + class myinv(m.Circuit): + io = m.IO( + in_=m.In(m.Bit), + out=m.Out(m.Bit) + ) + + # define test + tester = fault.InvTester(myinv) + + # run the test + tester.compile_and_run( + target=target, + simulator=simulator, + ext_srcs=[Path('tests/verilog/myinv_extra_module.v').resolve()], + ext_model_file=True, + tmp_dir=True + ) diff --git a/tests/verilog/myinv_extra_module.v b/tests/verilog/myinv_extra_module.v new file mode 100644 index 00000000..e87b9069 --- /dev/null +++ b/tests/verilog/myinv_extra_module.v @@ -0,0 +1,21 @@ +module extra_module #( + parameter file_name="file.mem" +) ( + input [1:0] addr, + output [2:0] data +); + // read into rom + reg [2:0] rom [0:3]; + initial begin + $readmemb(file_name, rom); + end + // assign to output + assign data=rom[addr]; +endmodule + +module myinv( + input in_, + output out +); + assign out = ~in_; +endmodule From 7e6717277ab8299f2e52a1d891d6fc4667a8d0cc Mon Sep 17 00:00:00 2001 From: Steven Herbst Date: Fri, 13 Mar 2020 00:10:22 -0700 Subject: [PATCH 45/74] fix bug related to external testbenches --- fault/system_verilog_target.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/fault/system_verilog_target.py b/fault/system_verilog_target.py index d4281d5f..85ab64d9 100644 --- a/fault/system_verilog_target.py +++ b/fault/system_verilog_target.py @@ -160,19 +160,20 @@ def __init__(self, circuit, circuit_name=None, directory="build/", # set default for magma compilation options magma_opts = magma_opts if magma_opts is not None else {} - # set default for top_module + # call the super constructor + super().__init__(circuit, circuit_name, directory, skip_compile, + 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'{circuit_name}' + top_module = f'{self.circuit_name}' else: - top_module = f'{circuit_name}_tb' - - # call the super constructor - super().__init__(circuit, circuit_name, directory, skip_compile, - include_verilog_libraries, magma_output, - magma_opts, coverage=coverage, use_kratos=use_kratos) + top_module = f'{self.circuit_name}_tb' # sanity check if simulator is None: From 6b409df8b784ddcb20ccec690c9f7ec4b6b15ed5 Mon Sep 17 00:00:00 2001 From: Steven Herbst Date: Fri, 13 Mar 2020 01:09:23 -0700 Subject: [PATCH 46/74] remove new top_module test temporarily due to macOS issue --- tests/test_top_module.py | 29 ----------------------------- tests/verilog/myinv_extra_module.v | 21 --------------------- 2 files changed, 50 deletions(-) delete mode 100644 tests/test_top_module.py delete mode 100644 tests/verilog/myinv_extra_module.v diff --git a/tests/test_top_module.py b/tests/test_top_module.py deleted file mode 100644 index d07aad05..00000000 --- a/tests/test_top_module.py +++ /dev/null @@ -1,29 +0,0 @@ -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') - - -def test_ext_vlog(target, simulator): - # declare circuit - class myinv(m.Circuit): - io = m.IO( - in_=m.In(m.Bit), - out=m.Out(m.Bit) - ) - - # define test - tester = fault.InvTester(myinv) - - # run the test - tester.compile_and_run( - target=target, - simulator=simulator, - ext_srcs=[Path('tests/verilog/myinv_extra_module.v').resolve()], - ext_model_file=True, - tmp_dir=True - ) diff --git a/tests/verilog/myinv_extra_module.v b/tests/verilog/myinv_extra_module.v deleted file mode 100644 index e87b9069..00000000 --- a/tests/verilog/myinv_extra_module.v +++ /dev/null @@ -1,21 +0,0 @@ -module extra_module #( - parameter file_name="file.mem" -) ( - input [1:0] addr, - output [2:0] data -); - // read into rom - reg [2:0] rom [0:3]; - initial begin - $readmemb(file_name, rom); - end - // assign to output - assign data=rom[addr]; -endmodule - -module myinv( - input in_, - output out -); - assign out = ~in_; -endmodule From 6d3bb5b87e7185644d4120e129ac8bc917bc5737 Mon Sep 17 00:00:00 2001 From: Steven Herbst Date: Fri, 13 Mar 2020 01:09:55 -0700 Subject: [PATCH 47/74] add no_top_module escape hatch --- fault/system_verilog_target.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/fault/system_verilog_target.py b/fault/system_verilog_target.py index 85ab64d9..0045f796 100644 --- a/fault/system_verilog_target.py +++ b/fault/system_verilog_target.py @@ -59,7 +59,7 @@ def __init__(self, circuit, circuit_name=None, directory="build/", 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 @@ -141,6 +141,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: @@ -220,6 +223,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 @@ -817,10 +821,10 @@ def write_vivado_tcl(self, sources=None, proj_name='project', proj_dir=None, 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 in the future we want to run simulations without defining the top - # module, then this TCL command can be used instead: - # "update_compile_order -fileset sim_1" - tcl_cmds += [f'set_property -name top -value {self.top_module} -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: + 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 @@ -852,7 +856,8 @@ def ncsim_cmd(self, sources, cmd_file): cmd += ['irun'] # send name of top module to the simulator - cmd += ['-top', f'{self.top_module}'] + if not self.no_top_module: + cmd += ['-top', f'{self.top_module}'] # timescale cmd += ['-timescale', f'{self.timescale}'] @@ -956,7 +961,8 @@ def vcs_cmd(self, sources): cmd += ['+vcs+vcdpluson', '-debug_pp'] # specify top module - cmd += ['-top', f'{self.top_module}'] + if not self.no_top_module: + cmd += ['-top', f'{self.top_module}'] # return arg list and binary file location return cmd, './simv' @@ -999,7 +1005,8 @@ def iverilog_cmd(self, sources): cmd += [f'{src}' for src in sources] # set the top module - cmd += ['-s', f'{self.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 From 26f242aef223dcdf3bab883f4de6c124cac0a00b Mon Sep 17 00:00:00 2001 From: Steven Herbst Date: Fri, 13 Mar 2020 01:10:21 -0700 Subject: [PATCH 48/74] fix some of the deprecation warnings --- tests/test_bidir.py | 10 +++++----- tests/test_def_vlog.py | 10 +++++----- tests/test_env_mod.py | 16 +++++++++++----- tests/test_ext_tb.py | 6 ++++-- tests/test_ext_vlog.py | 10 +++++----- tests/test_get_value_analog.py | 12 ++++++------ tests/test_hi_z.py | 14 +++++++------- tests/test_inc_dir.py | 10 +++++----- tests/test_init_cond.py | 13 +++++++------ 9 files changed, 55 insertions(+), 46 deletions(-) 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_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_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': From 2ce651ca161440149c94533b5a8921573a96e04c Mon Sep 17 00:00:00 2001 From: Steven Herbst Date: Fri, 13 Mar 2020 01:24:30 -0700 Subject: [PATCH 49/74] finish fixing circuit declaration deprecation warnings --- tests/test_inv_tf.py | 14 +++++----- tests/test_logic.py | 47 +++++++++++++++++++--------------- tests/test_param_vlog.py | 10 ++++---- tests/test_real_val.py | 6 ++++- tests/test_spice_bus.py | 15 ++++++----- tests/test_spice_port_order.py | 15 ++++++----- tests/test_vams_wrap.py | 18 ++++++------- tests/test_while_loop.py | 15 ++++++----- 8 files changed, 77 insertions(+), 63 deletions(-) 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_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_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..671e0dd8 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) 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_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) From ba3dd205aa79d72ad52c1b5984943bba2fe5cb7a Mon Sep 17 00:00:00 2001 From: Steven Herbst Date: Fri, 13 Mar 2020 01:30:40 -0700 Subject: [PATCH 50/74] add some files to gitignore that are generated while running pytest --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) 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 From b799181cddbc405273130af2019503a607d34dd7 Mon Sep 17 00:00:00 2001 From: Steven Herbst Date: Fri, 13 Mar 2020 01:57:44 -0700 Subject: [PATCH 51/74] fix a few more deprecation warnings --- fault/tester/base.py | 4 ++-- fault/verilogams.py | 19 +++++++++------- tests/test_include_verilog.py | 27 +++++++++++++++-------- tests/test_tester/test_core.py | 40 +++++++++++++++++++--------------- 4 files changed, 53 insertions(+), 37 deletions(-) diff --git a/fault/tester/base.py b/fault/tester/base.py index 1ef5ca68..e296754c 100644 --- a/fault/tester/base.py +++ b/fault/tester/base.py @@ -270,8 +270,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/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/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_tester/test_core.py b/tests/test_tester/test_core.py index c6550c8d..15fb6200 100644 --- a/tests/test_tester/test_core.py +++ b/tests/test_tester/test_core.py @@ -395,27 +395,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"]) + 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() - 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() + 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") From 7267b3e6f7684ea6575c729440976525946a2be4 Mon Sep 17 00:00:00 2001 From: Steven Herbst Date: Fri, 13 Mar 2020 02:00:55 -0700 Subject: [PATCH 52/74] fix pycodestyle warnings --- fault/tester/base.py | 1 - fault/tester/interactive_tester.py | 4 ++-- fault/verilator_target.py | 3 ++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fault/tester/base.py b/fault/tester/base.py index e296754c..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) 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/verilator_target.py b/fault/verilator_target.py index 5ebf8149..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: From 7b92980597f12aa795d91f4528d24d0dffb4fe90 Mon Sep 17 00:00:00 2001 From: Steven Herbst Date: Fri, 13 Mar 2020 02:01:11 -0700 Subject: [PATCH 53/74] fix deprecation warning --- fault/user_cfg.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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() From 27e487879f3f447501ac774ca77056531ce4aa6a Mon Sep 17 00:00:00 2001 From: Steven Herbst Date: Fri, 13 Mar 2020 11:36:54 -0700 Subject: [PATCH 54/74] add support for paths with spaces to vivado --- fault/system_verilog_target.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/fault/system_verilog_target.py b/fault/system_verilog_target.py index 0045f796..4a410885 100644 --- a/fault/system_verilog_target.py +++ b/fault/system_verilog_target.py @@ -791,38 +791,40 @@ 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}"' 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 not self.no_top_module: - tcl_cmds += [f'set_property -name top -value {self.top_module} -objects [get_fileset sim_1]'] # noqa + tcl_cmds += [f'set_property -name "top" -value "{self.top_module}" -objects [get_fileset sim_1]'] # noqa else: tcl_cmds += ['update_compile_order -fileset sim_1'] From 246f2bfc316a20357fa38a017163d880ceacf863 Mon Sep 17 00:00:00 2001 From: Steven Herbst Date: Fri, 13 Mar 2020 19:06:53 -0700 Subject: [PATCH 55/74] fix handling of define macros with quotes --- fault/system_verilog_target.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/fault/system_verilog_target.py b/fault/system_verilog_target.py index 4a410885..c8575397 100644 --- a/fault/system_verilog_target.py +++ b/fault/system_verilog_target.py @@ -791,22 +791,22 @@ 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 [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) + 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 @@ -814,22 +814,22 @@ def write_vivado_tcl(self, sources=None, proj_name='project', proj_dir=None, 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) vlog_defs = f'[list {vlog_defs}]' - tcl_cmds += [f'set_property -name "verilog_define" -value {vlog_defs} -objects [get_fileset sim_1]'] # noqa + 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 not self.no_top_module: - tcl_cmds += [f'set_property -name "top" -value "{self.top_module}" -objects [get_fileset sim_1]'] # noqa + tcl_cmds += [f'set_property -name top -value {{{self.top_module}}} -objects [get_fileset sim_1]'] # noqa else: 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'] From aeb708e468c34f2a51aca3413abbd359a2c2f971 Mon Sep 17 00:00:00 2001 From: Steven Herbst Date: Fri, 13 Mar 2020 20:15:54 -0700 Subject: [PATCH 56/74] Release 3.0.9 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2410a627..eb8140c1 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ setup( name='fault', - version='3.0.8', + version='3.0.9', description=DESCRIPTION, scripts=[], packages=[ From cfa323791af4209f59dda7a53ece242b9a8ea84c Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Tue, 17 Mar 2020 12:06:10 -0700 Subject: [PATCH 57/74] Tiny change to avoid mantle comparison that broke on bit type --- fault/background_poke.py | 2 +- tests/test_background_poke.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fault/background_poke.py b/fault/background_poke.py index 9e1b9ced..a64fa5ad 100644 --- a/fault/background_poke.py +++ b/fault/background_poke.py @@ -144,7 +144,7 @@ def add(self, background_poke): def remove(self, port): self.active_ports.remove(port) for thread in self.background_threads: - if thread.poke.port == port: + if thread.poke.port is port: offender = thread self.background_threads.remove(offender) poke = offender.step(self.t) diff --git a/tests/test_background_poke.py b/tests/test_background_poke.py index 1fc62bbc..8a208d3e 100644 --- a/tests/test_background_poke.py +++ b/tests/test_background_poke.py @@ -126,4 +126,4 @@ def test_sin_spice(vsup=1.5, vil_rel=0.4, vih_rel=0.6, ys.append(value) print('%2d\t'%k, value) - plot(xs, ys) + #plot(xs, ys) From 30e621b846cca32a76f0add4ad5d88e8134fc8ba Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Wed, 18 Mar 2020 18:35:41 -0700 Subject: [PATCH 58/74] First pass of understanding mLingua waveforms --- fault/mlingua_interface.py | 55 +++++++++++ fault/system_verilog_target.py | 20 +++- tests/test_mlingua_target.py | 174 +++++++++++++++++++++++++++++++++ tests/verilog/mlingua_add.sv | 26 +++++ 4 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 fault/mlingua_interface.py create mode 100644 tests/test_mlingua_target.py create mode 100644 tests/verilog/mlingua_add.sv diff --git a/fault/mlingua_interface.py b/fault/mlingua_interface.py new file mode 100644 index 00000000..e301727a --- /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', '') + # TODO this next line is kind of an awful hack + self.includes.append('mLingua_pwl.vh') + + # sort out pokes + pokes = defaultdict(list) + time = 0 + for a in actions: + if isinstance(a, Poke): + 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 = float(getattr(a, 'delay', 0)) + 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/system_verilog_target.py b/fault/system_verilog_target.py index 29bef687..00183019 100644 --- a/fault/system_verilog_target.py +++ b/fault/system_verilog_target.py @@ -14,6 +14,7 @@ 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 @@ -23,6 +24,7 @@ src_tpl = """\ +{includes} {timescale} module {top_module}; {declarations} @@ -42,6 +44,7 @@ """ @background_poke_target +@mlingua_target class SystemVerilogTarget(VerilogTarget): # Language properties of SystemVerilog used in generating code blocks @@ -225,6 +228,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: @@ -574,7 +578,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", []): @@ -621,7 +629,9 @@ def generate_code(self, actions, power_args): initial_body = [] # set up probing + print('about to set up probing') if self.dump_waveforms and self.simulator == "vcs": + print('vcs') initial_body += [f'$vcdplusfile("{self.waveform_file}");', f'$vcdpluson();', f'$vcdplusmemon();'] @@ -629,6 +639,7 @@ def generate_code(self, actions, power_args): # https://iverilog.fandom.com/wiki/GTKWAVE initial_body += [f'$dumpfile("{self.waveform_file}");', f'$dumpvars(0, dut);'] + print('initial body is now', initial_body) # if we're using the GetValue feature, then we need to open a file to # which GetValue results will be written @@ -655,7 +666,8 @@ 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) @@ -667,9 +679,13 @@ def generate_code(self, actions, power_args): # add timescale timescale = f'`timescale {self.timescale}' + + # add includes + includes = '\n'.join(f'`include "{f}"' for f in self.includes) # fill out values in the testbench template src = src_tpl.format( + includes=includes, timescale=timescale, declarations=declarations, assigns=assigns, 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/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 From f944500694091e457300f3503b21d67145bd70e9 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Mon, 23 Mar 2020 14:29:48 -0700 Subject: [PATCH 59/74] Working on get_value in different domains. I think it works for systemverilog, but I broke functionality for spice. Have a lingering TODO to force a wave dump on certain nodes when global waveform dumping is turned off --- fault/actions.py | 2 + fault/domain_read.py | 127 +++++++++++++++++++++++++ fault/mlingua_interface.py | 2 +- fault/result_parse.py | 82 ++++++++++++++++ fault/spice_target.py | 104 +------------------- fault/system_verilog_target.py | 14 ++- fault/verilog_target.py | 15 ++- tests/test_get_value_domain_digital.py | 89 +++++++++++++++++ 8 files changed, 332 insertions(+), 103 deletions(-) create mode 100644 fault/domain_read.py create mode 100644 tests/test_get_value_domain_digital.py diff --git a/fault/actions.py b/fault/actions.py index 83d3ad6c..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): @@ -118,6 +119,7 @@ def is_output(port): return not port.is_output() +@domain_read class GetValue(Action): def __init__(self, port, params): super().__init__() diff --git a/fault/domain_read.py b/fault/domain_read.py new file mode 100644 index 00000000..12fa9dbf --- /dev/null +++ b/fault/domain_read.py @@ -0,0 +1,127 @@ +import numpy as np + +# edge finder is used for measuring phase, freq, etc. +class EdgeNotFoundError(Exception): + pass + +def get_value_domain(results, action, time): + style = action.params['style'] + # TODO is this the right way to get the name? + res = results[action.port.name.name] + # 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.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.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'{action.params["ref"].name}'] + ref = find_edge(res_ref.t, res_ref.v, time, height, count=2) + before_cycle_end = find_edge(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(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?) + ''' + + if height is None: + # default comes out to 0.5 + height = self.vsup * (self.vih_rel + self.vil_rel) / 2 + + # 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) + + # TODO: there's an issue because of the discrepancy between the requested + # start time and actually staring at the nearest edge + + # the crossing happens from i to i+1 + fraction = (height - y[i]) / (y[i - direction] - y[i]) + t = x[i] + fraction * (x[i + 1] - x[i]) + if t == t_start: + print('EDGE EXACTLY AT EDGE FIND REQUEST') + 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 index e301727a..3b883cf3 100644 --- a/fault/mlingua_interface.py +++ b/fault/mlingua_interface.py @@ -22,7 +22,7 @@ def run(self, actions): pokes = defaultdict(list) time = 0 for a in actions: - if isinstance(a, Poke): + 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 diff --git a/fault/result_parse.py b/fault/result_parse.py index 808b3fc4..87c42e19 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,15 @@ def __init__(self, t, v): def __call__(self, t): return self.func(t) + +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 +128,73 @@ def data_to_interp(data, time, strip_vi=True): # return results return retval + + +def parse_vcd(filename, io, 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 name not in io.ports: + # we don't know what the type is, so we leave it as a string + return val_str + a = io.ports[name] + b = isinstance(type(a), type(Real)) + + if b: + return float(val_str) + else: + 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 5a4d41e7..877a1a2f 100644 --- a/fault/spice_target.py +++ b/fault/spice_target.py @@ -16,6 +16,7 @@ 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 @@ -23,10 +24,6 @@ except ModuleNotFoundError: print('Failed to import DeCiDa or Numpy for SpiceTarget.') -# edge finder is used for measuring phase, freq, etc. -class EdgeNotFoundError(Exception): - pass - class CompiledSpiceActions: def __init__(self, pwls, checks, prints, stop_time, saves, gets): self.pwls = pwls @@ -625,59 +622,6 @@ def process_reads(self, results, reads): for time, read in reads: res = results[f'{read.port.name}'] - def find_edge(self, x, y, t_start, height=None, 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?) - ''' - - if height is None: - # default comes out to 0.5 - height = self.vsup * (self.vih_rel + self.vil_rel) / 2 - - # 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) - - - # TODO: there's an issue because of the discrepancy between the requested - # start time and actually staring at the nearest edge - - # the crossing happens from i to i+1 - fraction = (height-y[i]) / (y[i-direction]-y[i]) - t = x[i] + fraction * (x[i+1] - x[i]) - if t==t_start: - print('EDGE EXACTLY AT EDGE FIND REQUEST') - edges.append(t-t_start) - return edges - def impl_all_gets(self, results, gets): for get in gets: self.impl_get(results=results, time=get[0], action=get[1]) @@ -691,49 +635,11 @@ def impl_get(self, results, time, action): port_value = res(time) # write value back to action action.value = port_value - else: + elif type(action.params) == dict and 'style' in action.params: # requires some analysis of signal - style = action.params['style'] - 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 self.find_edge for possible parameters - value = self.find_edge(res.t, res.v, time, **action.params) - action.value = value - elif style == 'frequency': - # frequency based on the (previous?) two rising edges - edges = self.find_edge(res.t, res.v, time, 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'{action.params["ref"].name}'] - ref = self.find_edge(res_ref.t, res_ref.v, time, count=2) - before_cycle_end = self.find_edge(res.t, res.v, time + ref[0]) - 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}"') + domain_read.analyze(results, time, action) + 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 00183019..fae82827 100644 --- a/fault/system_verilog_target.py +++ b/fault/system_verilog_target.py @@ -629,7 +629,6 @@ def generate_code(self, actions, power_args): initial_body = [] # set up probing - print('about to set up probing') if self.dump_waveforms and self.simulator == "vcs": print('vcs') initial_body += [f'$vcdplusfile("{self.waveform_file}");', @@ -639,13 +638,24 @@ def generate_code(self, actions, power_args): # https://iverilog.fandom.com/wiki/GTKWAVE initial_body += [f'$dumpfile("{self.waveform_file}");', f'$dumpvars(0, dut);'] - print('initial body is now', initial_body) # if we're using the GetValue feature, then we need to open a file to # which GetValue results will be written 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): diff --git a/fault/verilog_target.py b/fault/verilog_target.py index 59d45138..9a046b30 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): """ @@ -292,11 +293,23 @@ 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) + + 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.io) + for a in get_value_actions_params: + # the time has already been temporarily stored in a.value + get_value_domain(res, a, a.value) @staticmethod def in_var(file): 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) From 0f0f90b04fc38c0660cd4623fb96ab1677e5ae6e Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Mon, 23 Mar 2020 14:57:07 -0700 Subject: [PATCH 60/74] Fix domain read for spice. Still some pending todos --- fault/domain_read.py | 4 ---- fault/spice_target.py | 6 +++++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/fault/domain_read.py b/fault/domain_read.py index 12fa9dbf..5469c7ba 100644 --- a/fault/domain_read.py +++ b/fault/domain_read.py @@ -59,10 +59,6 @@ def find_edge(x, y, t_start, height, forward=False, count=1, rising=True): default (frequency now is probably based on the last few edges?) ''' - if height is None: - # default comes out to 0.5 - height = self.vsup * (self.vih_rel + self.vil_rel) / 2 - # deal with `rising` and `forward` # normally a low-to-high finder if (rising ^ forward): diff --git a/fault/spice_target.py b/fault/spice_target.py index 877a1a2f..1b3443af 100644 --- a/fault/spice_target.py +++ b/fault/spice_target.py @@ -637,7 +637,11 @@ def impl_get(self, results, time, action): action.value = port_value elif type(action.params) == dict and 'style' in action.params: # requires some analysis of signal - domain_read.analyze(results, time, action) + 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 + get_value_domain(results, action, time) else: raise NotImplementedError From 84edb56aefc70b7e63485ae5d351b93db0e174ea Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Tue, 24 Mar 2020 16:14:10 -0700 Subject: [PATCH 61/74] Can now read frequency/phase with systemverilog target --- fault/domain_read.py | 76 +++++++++++++++++++++++++++++++++++--- fault/mlingua_interface.py | 4 +- fault/result_parse.py | 23 +++++++++--- fault/verilog_target.py | 2 +- 4 files changed, 91 insertions(+), 14 deletions(-) diff --git a/fault/domain_read.py b/fault/domain_read.py index 5469c7ba..3455fe69 100644 --- a/fault/domain_read.py +++ b/fault/domain_read.py @@ -1,4 +1,5 @@ import numpy as np +from fault.result_parse import SpiceResult, ResultInterp # edge finder is used for measuring phase, freq, etc. class EdgeNotFoundError(Exception): @@ -8,6 +9,12 @@ def get_value_domain(results, action, time): style = action.params['style'] # TODO is this the right way to get the name? res = results[action.port.name.name] + if isinstance(res, ResultInterp): + res_style = 'pwc' + elif isinstance(res, SpiceInterp): + 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': @@ -19,19 +26,19 @@ def get_value_domain(results, action, time): elif style == 'edge': # looking for a nearby rising/falling edge # look at find_edge for possible parameters - value = find_edge(res.t, res.v, time, height, **action.params) + 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.t, res.v, time, height, count=2) + 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'{action.params["ref"].name}'] - ref = find_edge(res_ref.t, res_ref.v, time, height, count=2) - before_cycle_end = find_edge(res.t, res.v, time + ref[0], height) + 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 @@ -51,8 +58,63 @@ def get_value_domain(results, action, time): else: raise NotImplementedError(f'Unknown style "{style}"') +def find_edge(res_style, *args, **kwargs): + if res_style == 'spice': + return find_edge(*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) + + # TODO: there's an issue because of the discrepancy between the requested + # start time and actually staring at the nearest edge + + # 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') + edges.append(t - t_start) + return edges -def find_edge(x, y, t_start, height, forward=False, count=1, rising=True): +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 @@ -93,8 +155,10 @@ def find_edge(x, y, t_start, height, forward=False, count=1, rising=True): # start time and actually staring at the nearest edge # 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]) - t = x[i] + fraction * (x[i + 1] - x[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') edges.append(t - t_start) diff --git a/fault/mlingua_interface.py b/fault/mlingua_interface.py index 3b883cf3..6f8616f8 100644 --- a/fault/mlingua_interface.py +++ b/fault/mlingua_interface.py @@ -15,7 +15,6 @@ def run(self, actions): #self.add_assign('pm', 'new') self.add_decl('PWLMethod', 'pm=new') self.add_decl('`get_timeunit', '') - # TODO this next line is kind of an awful hack self.includes.append('mLingua_pwl.vh') # sort out pokes @@ -27,7 +26,8 @@ def run(self, actions): 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 = float(getattr(a, 'delay', 0)) + delay_attr = getattr(a, 'delay', 0) + delay = 0 if delay_attr == None else float(delay_attr) time += delay # update pokes diff --git a/fault/result_parse.py b/fault/result_parse.py index 87c42e19..d3b3ebdc 100644 --- a/fault/result_parse.py +++ b/fault/result_parse.py @@ -18,6 +18,7 @@ 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 @@ -130,7 +131,7 @@ def data_to_interp(data, time, strip_vi=True): return retval -def parse_vcd(filename, io, interp='previous'): +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' @@ -170,15 +171,27 @@ def get_name_from_v(v): return name_fault def format(name, val_str): - if name not in io.ports: - # we don't know what the type is, so we leave it as a string - return val_str - a = io.ports[name] + 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() diff --git a/fault/verilog_target.py b/fault/verilog_target.py index 9a046b30..ce68a61b 100644 --- a/fault/verilog_target.py +++ b/fault/verilog_target.py @@ -306,7 +306,7 @@ def post_process_get_value_actions(self, all_actions): 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.io) + 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) From d0e7dada03733887c7a71d11743a92e50564688c Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Wed, 25 Mar 2020 11:03:09 -0700 Subject: [PATCH 62/74] Fixed a couple bugs in background poke --- fault/background_poke.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/fault/background_poke.py b/fault/background_poke.py index a64fa5ad..94c6b6dd 100644 --- a/fault/background_poke.py +++ b/fault/background_poke.py @@ -4,6 +4,7 @@ from functools import total_ordering import copy from fault.target import Target +from fault.actions import Delay @total_ordering @@ -13,6 +14,8 @@ class Thread(): # 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): @@ -108,13 +111,15 @@ def delay(self, t_delay): #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: - #print('pool update not needed', t) 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) @@ -124,8 +129,9 @@ def delay(self, t_delay): # 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('calculated next thing time', next_thing_time, 'at time', t) + #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) @@ -147,7 +153,9 @@ def remove(self, port): 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: @@ -176,7 +184,10 @@ def process(self, action, delay): # 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 - action.delay = new_delay + 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 From eb22b9a44055ca1016dcb23e2e869473f0346cdd Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Wed, 25 Mar 2020 11:58:54 -0700 Subject: [PATCH 63/74] Edge find bug fix --- fault/domain_read.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fault/domain_read.py b/fault/domain_read.py index 3455fe69..05a7065f 100644 --- a/fault/domain_read.py +++ b/fault/domain_read.py @@ -111,6 +111,10 @@ def find_edge_pwc(x, y, t_start, height, forward=False, count=1, rising=True): 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 @@ -161,6 +165,10 @@ def find_edge_spice(x, y, t_start, height, forward=False, count=1, rising=True): 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 From 417361d3b879d3d3841bebeeabac782826dbfd41 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Wed, 25 Mar 2020 11:58:54 -0700 Subject: [PATCH 64/74] Edge find bug fix --- fault/domain_read.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/fault/domain_read.py b/fault/domain_read.py index 3455fe69..1da4e830 100644 --- a/fault/domain_read.py +++ b/fault/domain_read.py @@ -103,14 +103,15 @@ def find_edge_pwc(x, y, t_start, height, forward=False, count=1, rising=True): msg = f'only {len(edges)} of requested {count} edges found' raise EdgeNotFoundError(msg) - # TODO: there's an issue because of the discrepancy between the requested - # start time and actually staring at the nearest edge - # 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 @@ -151,9 +152,6 @@ def find_edge_spice(x, y, t_start, height, forward=False, count=1, rising=True): msg = f'only {len(edges)} of requested {count} edges found' raise EdgeNotFoundError(msg) - # TODO: there's an issue because of the discrepancy between the requested - # start time and actually staring at the nearest edge - # 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]) @@ -161,6 +159,10 @@ def find_edge_spice(x, y, t_start, height, forward=False, count=1, rising=True): 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 From 49ab608f292ef1f256d6e4f1699e2f8a06a6507a Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Fri, 27 Mar 2020 15:28:43 -0700 Subject: [PATCH 65/74] Fix tiny bug with recent changes --- fault/domain_read.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fault/domain_read.py b/fault/domain_read.py index 1da4e830..b4269c3f 100644 --- a/fault/domain_read.py +++ b/fault/domain_read.py @@ -11,7 +11,7 @@ def get_value_domain(results, action, time): res = results[action.port.name.name] if isinstance(res, ResultInterp): res_style = 'pwc' - elif isinstance(res, SpiceInterp): + elif isinstance(res, SpiceResult): res_style = 'spice' else: assert False, f'Unrecognized result class {type(res)} for result {res}' @@ -60,7 +60,7 @@ def get_value_domain(results, action, time): def find_edge(res_style, *args, **kwargs): if res_style == 'spice': - return find_edge(*args, **kwargs) + return find_edge_spice(*args, **kwargs) elif res_style == 'pwc': return find_edge_pwc(*args, **kwargs) else: From 3af3399ff21279c4363f133738a3fc4c829b3452 Mon Sep 17 00:00:00 2001 From: Lenny Truong Date: Mon, 30 Mar 2020 11:27:43 -0700 Subject: [PATCH 66/74] Insert flags before other command options --- fault/system_verilog_target.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/fault/system_verilog_target.py b/fault/system_verilog_target.py index c8575397..a64ce716 100644 --- a/fault/system_verilog_target.py +++ b/fault/system_verilog_target.py @@ -697,9 +697,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() @@ -857,6 +854,9 @@ def ncsim_cmd(self, sources, cmd_file): # binary name cmd += ['irun'] + # add any extra flags + cmd += self.flags + # send name of top module to the simulator if not self.no_top_module: cmd += ['-top', f'{self.top_module}'] @@ -906,6 +906,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'] @@ -925,6 +928,9 @@ def vcs_cmd(self, sources): # binary name cmd += ['vcs'] + # add any extra flags + cmd += self.flags + # timescale cmd += [f'-timescale={self.timescale}'] @@ -975,6 +981,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}'] From 7e9d1901b311c8ee0100070f0d96b98cd301c8bc Mon Sep 17 00:00:00 2001 From: Lenny Truong Date: Mon, 30 Mar 2020 11:31:20 -0700 Subject: [PATCH 67/74] Initial clocks to zero --- fault/tester/staged_tester.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/fault/tester/staged_tester.py b/fault/tester/staged_tester.py index 969e8b67..2f88f0b5 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 = [] @@ -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 From a874eb25295501a8641a4177adff151997fb2616 Mon Sep 17 00:00:00 2001 From: Lenny Truong Date: Mon, 30 Mar 2020 11:54:13 -0700 Subject: [PATCH 68/74] Update tests --- tests/test_tester/test_core.py | 41 +++++++++++----------------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/tests/test_tester/test_core.py b/tests/test_tester/test_core.py index 15fb6200..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) """ From f98beb7c2ed410129aa79d164d711e2ece6a3b81 Mon Sep 17 00:00:00 2001 From: Lenny Truong Date: Tue, 31 Mar 2020 13:58:35 -0700 Subject: [PATCH 69/74] Release 3.0.10 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index eb8140c1..d8087c63 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ setup( name='fault', - version='3.0.9', + version='3.0.10', description=DESCRIPTION, scripts=[], packages=[ From a62a3b4539586029e44ad81ba96e4791745f46fe Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Thu, 2 Apr 2020 12:20:13 -0700 Subject: [PATCH 70/74] Add flatten method to real_type --- fault/real_type.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fault/real_type.py b/fault/real_type.py index 0c521602..fad54740 100644 --- a/fault/real_type.py +++ b/fault/real_type.py @@ -23,6 +23,9 @@ def value(self): def driven(self): return self._wire.driven() + def flatten(self): + return [self] + class RealKind(Kind): __hash__ = Kind.__hash__ From 7c5fe1044511f86758e8b804293bb5464939b0b1 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Thu, 2 Apr 2020 12:34:01 -0700 Subject: [PATCH 71/74] Delete traces of old Read action (replaced by GetValue) --- fault/verilator_target.py | 4 ---- fault/verilog_target.py | 6 ------ 2 files changed, 10 deletions(-) diff --git a/fault/verilator_target.py b/fault/verilator_target.py index 67dd5e14..b256620f 100644 --- a/fault/verilator_target.py +++ b/fault/verilator_target.py @@ -344,10 +344,6 @@ def make_print(self, i, action): ports = ", " + ports return [f'printf("{action.format_str}"{ports});'] - def make_read(self, i, action): - msg = 'read not implemented for Verilator target' - raise NotImplementedError(msg) - def make_expect(self, i, action): # For verilator, if an expect is "AnyValue" we don't need to # perform the expect. diff --git a/fault/verilog_target.py b/fault/verilog_target.py index ce68a61b..4ffc424d 100644 --- a/fault/verilog_target.py +++ b/fault/verilog_target.py @@ -123,8 +123,6 @@ def generate_action_code(self, i, action): return self.make_poke(i, action) elif isinstance(action, actions.Print): return self.make_print(i, action) - elif isinstance(action, actions.Read): - return self.make_read(i, action) elif isinstance(action, actions.Expect): return self.make_expect(i, action) elif isinstance(action, actions.Eval): @@ -169,10 +167,6 @@ def make_poke(self, i, action): def make_print(self, i, action): pass - @abstractmethod - def make_read(self, i, action): - pass - @abstractmethod def make_expect(self, i, action): pass From a255b2c1a7ea83498f7cb32079bccbcc48b7d3b9 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Mon, 13 Apr 2020 14:59:16 -0700 Subject: [PATCH 72/74] Fix floating point equality comparison --- fault/pwl.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fault/pwl.py b/fault/pwl.py index ce2278d4..1310163a 100644 --- a/fault/pwl.py +++ b/fault/pwl.py @@ -14,13 +14,12 @@ def pwc_to_pwl(pwc, t_stop, t_tr, init=0): if retval[-1][0] >= t_curr: assert retval[-2][0] <= t_curr, \ 'non-increasing pwc steps at time' % t_curr - if retval[-2][0] == 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') + #print('DROPPING old thing, diff:', retval[-2][0] - t_curr) retval.pop() old_t, old_v = retval.pop() v_prev = old_v - #print('prev is now', retval[-2:]) else: # make the previous thing happen faster than t_tr #print('DOING THE HALFWAY THING') From b921caa982221ddc2f4f3d4595bb36de9b002200 Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Wed, 15 Apr 2020 18:22:56 -0700 Subject: [PATCH 73/74] Fixes for arrays of analog and outputs that are arrays --- fault/domain_read.py | 6 +++--- fault/spice_target.py | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/fault/domain_read.py b/fault/domain_read.py index b4269c3f..97054007 100644 --- a/fault/domain_read.py +++ b/fault/domain_read.py @@ -5,10 +5,10 @@ class EdgeNotFoundError(Exception): pass -def get_value_domain(results, action, time): +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[action.port.name.name] + res = results[get_name(action.port)] if isinstance(res, ResultInterp): res_style = 'pwc' elif isinstance(res, SpiceResult): @@ -36,7 +36,7 @@ def get_value_domain(results, action, time): 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'{action.params["ref"].name}'] + 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]) diff --git a/fault/spice_target.py b/fault/spice_target.py index 1b3443af..4212864f 100644 --- a/fault/spice_target.py +++ b/fault/spice_target.py @@ -208,7 +208,8 @@ 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: @@ -627,8 +628,15 @@ def impl_all_gets(self, results, gets): self.impl_get(results=results, time=get[0], action=get[1]) def impl_get(self, results, time, action): + # 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[f'{action.port.name}'] + res = results[get_spice_name(action.port)] if action.params == None: # straightforward read of voltage # get port values @@ -637,11 +645,13 @@ def impl_get(self, results, time, action): action.value = port_value elif type(action.params) == dict and 'style' in action.params: # requires some analysis of signal - print(action.params) + #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 - get_value_domain(results, action, time) + # 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 From f99011365b7f1ffccbf0bcd4baf30ec66b12fabf Mon Sep 17 00:00:00 2001 From: Daniel Stanley Date: Wed, 3 Jun 2020 16:48:14 -0700 Subject: [PATCH 74/74] Slight update to verilog_target for domain_read --- fault/verilog_target.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fault/verilog_target.py b/fault/verilog_target.py index 4ffc424d..8315668d 100644 --- a/fault/verilog_target.py +++ b/fault/verilog_target.py @@ -297,13 +297,15 @@ def post_process_get_value_actions(self, all_actions): 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_value_domain(res, a, a.value, get_name) @staticmethod def in_var(file):