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 // 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 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