100 lines
3.6 KiB
Plaintext
100 lines
3.6 KiB
Plaintext
package DelayLine;
|
|
|
|
import List::*;
|
|
|
|
// A DelayLine mirrors writes back as reads after a number of delay
|
|
// cycles. Delay lines are pipelined and multiple values can be "in
|
|
// flight" at once.
|
|
//
|
|
// Reads block until a value is available, but the DelayLine doesn't
|
|
// stall if there are no readers - the value is available for reading
|
|
// after the set delay, and is lost if not read by anything.
|
|
//
|
|
// For callers that need to check for a value without tripping over an
|
|
// implicit condition, ready() can poll the delay line's output
|
|
// without blocking.
|
|
interface DelayLine#(type value);
|
|
(* always_ready *)
|
|
method Action _write (value v);
|
|
method value _read();
|
|
(* always_ready *)
|
|
method Bool ready();
|
|
endinterface
|
|
|
|
// mkDelayLine constructs a DelayLine with delay_cycles of latency
|
|
// between writes and reads. delay_cycles may be zero, with the result
|
|
// being Wire/RWire like scheduling semantics.
|
|
module mkDelayLine(Integer delay_cycles, DelayLine#(a) ifc)
|
|
provisos (Bits#(a, a_sz));
|
|
|
|
// In most cases a DelayLine is a pipeline of registers through
|
|
// which values flow, and so it inherits the scheduling properties
|
|
// of registers: all reads from the delay line happen before any
|
|
// writes take effect.
|
|
//
|
|
// The exception is when the caller asks for zero cycles of delay:
|
|
// in that case, a write must be visible to reads within the same
|
|
// cycle, and thus the write to the delay line must execute before
|
|
// any reads.
|
|
//
|
|
// So, handle a delay of 0 cycles specially and implement it as an
|
|
// RWire, which has write-before-read behavior. Otherwise, build a
|
|
// pipeline of registers with read-before-write behavior.
|
|
//
|
|
// You might wonder why even bother allowing a DelayLine with zero
|
|
// delay. It's because doing so is handy in parameterized
|
|
// modules. Say for example you're wrapping a blackbox module that
|
|
// has optional input and output registers. Depending on the
|
|
// parameters, the in->out delay could be 0 cycles (pure
|
|
// combinatorial circuit), 1 cycle (input or output register) or 2
|
|
// cycles (input and output register). It's very handy to be able
|
|
// to plop down a DelayLine regardless of the requested
|
|
// configuration, and have it gracefully go all the way to zero
|
|
// latency.
|
|
if (delay_cycles == 0) begin
|
|
RWire#(a) w <- mkRWire();
|
|
|
|
method Action _write(a value);
|
|
w.wset(value);
|
|
endmethod
|
|
method a _read() if (w.wget matches tagged Valid .val);
|
|
return val;
|
|
endmethod
|
|
method Bool ready();
|
|
return isValid(w.wget);
|
|
endmethod
|
|
end
|
|
else begin
|
|
RWire#(a) inputVal <- mkRWire;
|
|
|
|
// Note that in rules and modules, for loops get unrolled
|
|
// statically at compile time. We're not specifying a circuit
|
|
// that runs a loop in hardware here, this is shorthand for
|
|
// "splat out N registers and wire them together in a line".
|
|
List#(Reg#(Maybe#(a))) delay = Nil;
|
|
for (Integer i = 0; i < delay_cycles; i = i+1) begin
|
|
let r <- mkReg(Invalid);
|
|
delay = cons(r, delay);
|
|
end
|
|
|
|
(* no_implicit_conditions, fire_when_enabled *)
|
|
rule pump_line;
|
|
delay[0] <= inputVal.wget();
|
|
for (Integer i = 0; i < delay_cycles-1; i = i+1)
|
|
delay[i+1] <= delay[i];
|
|
endrule
|
|
|
|
method Action _write(a value);
|
|
inputVal.wset(value);
|
|
endmethod
|
|
method a _read() if (delay[delay_cycles-1] matches tagged Valid .val);
|
|
return val;
|
|
endmethod
|
|
method Bool ready();
|
|
return isValid(delay[delay_cycles-1]);
|
|
endmethod
|
|
end
|
|
endmodule
|
|
|
|
endpackage
|