gary/lib/DelayLine.bsv

105 lines
3.8 KiB
Plaintext
Raw Permalink Normal View History

package DelayLine;
import List::*;
2024-08-14 05:53:47 +02:00
// 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();
2024-08-14 05:53:47 +02:00
(* always_ready *)
method Bool ready();
endinterface
2024-08-14 05:53:47 +02:00
// 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));
2024-08-14 05:53:47 +02:00
// 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();
2024-08-14 05:53:47 +02:00
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;
2024-08-14 05:53:47 +02:00
// 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
2024-08-14 05:53:47 +02:00
let r <- mkReg(Invalid);
delay = cons(r, delay);
end
// pump_line ingests a new value into the first register in the
// line, and shifts older values one register down. The
// registers always shift forward even when nobody's writing, in
// which case that slot in the pipeline is occupied by an empty
// Maybe.
(* 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
2024-08-14 05:53:47 +02:00
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