leonardt / fault

A Python package for testing hardware (part of the magma ecosystem)
BSD 3-Clause "New" or "Revised" License
41 stars 13 forks source link

[Feature Request] Support for multiple clocks with different frequencies #83

Open rdaly525 opened 5 years ago

rdaly525 commented 5 years ago

It would be great to have fault generate the relative ordering between all the edges for multiple clocks.

In terms of API, I think something along the lines of:

tester = fault.Tester(circ,circ.CLK0=<period0>,circ.CLK1=<period1>,timescale="ns")
tester.step(duration=<length_of_time>)

The semantics of step in this case would be to execute the clock edges of the clocks in the correct ordering for a certain duration (at least for verilator/CoreIR)

@leonardt, let me know if this is an API you would be okay with. I can work on the feature otherwise.

leonardt commented 5 years ago

I think I need some clarification on the semantics of this, but overall sounds like a great idea. Basically, one has a design with multiple clocks that operate at different frequencies. In this case, there's an interface to the tester that allows specifying multiple clocks and a common underlying timescale. Then, step is defined in terms of continuous time (e.g. step 2 ns) instead of discrete clock periods (since now there's a conflict w.r.t. the definition of a period given multiple clocks). The reason for adding the API is that the user will specify a duration of time to step, and the underlying test bench would ensure that the proper clock events are triggered for the duration of the time step (e.g. if we step 3ns and there's a clock with a 1.5 ns period, it should trigger two events, while a different clock might have a 2ns period, triggering only one event). It seems like the tester will need to keep track of the current time step relative to the starting point, and keep track of the position in the clock periods relative to the absolute time steps.

In terms of API, perhaps it could looks something like

tester = fault.Tester(circ, clocks={circ.CLK0=1.5, circ.CLK1=2}, timescale="ns")
tester.step(duration=3)

It's nice that duration is an explicit kwarg to step, so the user is explicitly noting they're stepping in absolute/real time (versus clock periods). It might be good to provide another default kwarg for normal use like step(half_cycles=2) (and also provide a full cycle step). I don't think this is too important to work out for now (we can always change the frontend/user interface), so don't let this block you.

rdaly525 commented 5 years ago

Yeah, I think there are some ambiguous semantics. Specifically step() without duration is ambiguous in terms of multiple clocks. I think there are a couple ways to resolve this.

lets say we have the following code:

tester = fault.Tester(circ, clocks={circ.CLK0=1.5, circ.CLK1=2}, timescale="ns")
tester.step(duration=2.5)
tester.step(2)

it is ambiguous which "half-cycles" the last step is referring to. Either we can associate it with a specific clock (step CLK0 twice), or we can change the semantics of step() to be "step to the next clock edge of any clock". For the latter choice, with just a single clock, this is the same semantics as "step a half cycle".

It is also ambiguous what happens if the beginning or end of the duration is exactly at a clock edge. There also might be some weirdness here in regards to floating point accumulation errors.