diff --git a/lib/DelayLine.bsv b/lib/DelayLine.bsv index 974e1e3..302c556 100644 --- a/lib/DelayLine.bsv +++ b/lib/DelayLine.bsv @@ -2,36 +2,77 @@ 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); 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)); - DelayLine#(a) ret = ?; - + // 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(); - ret = (interface DelayLine; - 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 - endinterface); + + 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; - List#(Reg#(Maybe#(a))) delay = tagged Nil; + + // 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(tagged Invalid); + let r <- mkReg(Invalid); delay = cons(r, delay); end @@ -42,19 +83,16 @@ module mkDelayLine(Integer delay_cycles, DelayLine#(a) ifc) delay[i+1] <= delay[i]; endrule - ret = (interface DelayLine; - 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 - endinterface); + 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 - return ret; endmodule endpackage diff --git a/lib/DelayLine_Test.bsv b/lib/DelayLine_Test.bsv index faf482d..1673a19 100644 --- a/lib/DelayLine_Test.bsv +++ b/lib/DelayLine_Test.bsv @@ -9,22 +9,20 @@ import List::*; import DelayLine::*; module mkTB(); - let cycles <- mkTestCycleLimiter(100); + let cycles <- mkCycleCounter(); function Stmt testDelayLine(DelayLine#(Int#(8)) delay, Bit#(32) wantDelay); seq - $display(" RUN delay=%0d", wantDelay); - action delay <= 42; - cycles.reset_count(); - $display(" write cycle: %0d", cycles.cycles); + cycles.reset(); + $display(" write cycle: %0d", cycles.all); endaction repeat (wantDelay-1) action if (delay.ready) begin - $display("delay line ready after %0d cycles, want %0d", cycles.count, wantDelay); + $display("delay line ready after %0d cycles, want %0d (on cycle %0d)", cycles, wantDelay, cycles.all); $finish; end endaction @@ -37,62 +35,51 @@ module mkTB(); action dynamicAssert(delay.ready == True, "delay line not ready when expected"); - if (cycles.count != wantDelay) begin - $display("delay line ready after %0d cycles, want %0d", cycles.count, wantDelay); + if (cycles != wantDelay) begin + $display("delay line became ready after %0d cycles, want %0d (on cycle %0d)", cycles, wantDelay, cycles.all); $finish; end - $display(" ready cycle: %0d", cycles.cycles); + $display(" read cycle: %0d", cycles.all); endaction endpar dynamicAssert(delay.ready == False, "delay line still ready after value yield"); - - $display(" OK delay=%0d", wantDelay); endseq; endfunction let delay0 <- mkDelayLine(0); - let test0 = seq - $display(" RUN delay=0"); + let delay1 <- mkDelayLine(1); + let delay2 <- mkDelayLine(2); + let delay3 <- mkDelayLine(3); + let delay4 <- mkDelayLine(4); + let test0 = seq dynamicAssert(delay0.ready == False, "delay line ready before put"); par action delay0 <= 42; - $display(" write cycle: %0d", cycles.cycles); + $display(" write cycle: %0d", cycles.all); endaction action dynamicAssert(delay0.ready == True, "delay line not ready on same cycle"); - $display(" read cycle: %0d", cycles.cycles); + $display(" read cycle: %0d", cycles.all); endaction dynamicAssert(delay0 == 42, "delay line has wrong value"); endpar dynamicAssert(delay0.ready == False, "delay line ready without write"); - - $display(" OK delay=0"); endseq; - let delay1 <- mkDelayLine(1); - let test1 = testDelayLine(delay1, 1); - let delay2 <- mkDelayLine(2); - let test2 = testDelayLine(delay2, 2); - - let delay3 <- mkDelayLine(3); - let test3 = testDelayLine(delay3, 3); - - let delay4 <- mkDelayLine(4); - let test4 = testDelayLine(delay4, 4); - - mkTest("DelayLine", seq - test0; - test1; - test2; - test3; - test4; - endseq); + runTest(100, + mkTest("DelayLine", seq + mkTest("DelayLine/0", test0); + mkTest("DelayLine/1", testDelayLine(delay1, 1)); + mkTest("DelayLine/2", testDelayLine(delay2, 2)); + mkTest("DelayLine/3", testDelayLine(delay3, 3)); + mkTest("DelayLine/4", testDelayLine(delay4, 4)); + endseq)); endmodule endpackage diff --git a/lib/Testing.bsv b/lib/Testing.bsv index 2cb5ca5..4d529f2 100644 --- a/lib/Testing.bsv +++ b/lib/Testing.bsv @@ -2,40 +2,65 @@ package Testing; import Assert::*; import StmtFSM::*; +import Cntrs::*; -interface TestCycleLimiter; - method Bit#(32) cycles(); +// A CycleCounter keeps a count of total elapsed cycles in a +// simulation, as well as cycles since the last reset. +interface CycleCounter; + // _read returns the number of cycles since the last time reset() + // was called. + method Bit#(32) _read(); + // reset resets the cycle counter. The counter will read 1 on the + // cycle following the call to reset. + method Action reset(); - method Bit#(32) count(); - method Action reset_count(); + // all returns the number of cycles elapsed since simulation start, + // for use in $display and the like. + method Bit#(32) all(); endinterface -module mkTestCycleLimiter#(Integer max_cycles)(TestCycleLimiter); - Bit#(32) max = fromInteger(max_cycles); - Reg#(Bit#(32)) cnt <- mkReg(0); - Reg#(Bit#(32)) lastReset <- mkReg(0); +module mkCycleCounter(CycleCounter); + let total <- mkCount(0); + let cnt <- mkCount(0); (* no_implicit_conditions, fire_when_enabled *) rule count_up; - dynamicAssert(cnt < max, "FAIL: test timed out"); - cnt <= cnt+1; + cnt.incr(1); + total.incr(1); endrule - method cycles = cnt._read; - method Bit#(32) count(); - return cnt - lastReset; - endmethod - method Action reset_count(); - lastReset <= cnt; + method _read = cnt._read; + method Action reset(); + cnt.update(0); endmethod + method all = total._read; endmodule -module mkTest#(String name, RStmt#(Bit#(0)) test)(); +// mkTest runs the given test, printing status text before and after +// the run. Tests can be nested. +function Stmt mkTest(String name, Stmt test); + seq + $display("RUN %s", name); + test; + $display("OK %s", name); + endseq; +endfunction + +// runTest runs the given test with a timeout. +module runTest(Integer cycle_limit, Stmt test, Empty ifc); + Bit#(32) max = fromInteger(cycle_limit); + let cnt <- mkCount(0); + + (* no_implicit_conditions, fire_when_enabled *) + rule cycle_deadline; + dynamicAssert(cnt < max, "Test timed out"); + cnt.incr(1); + endrule + mkAutoFSM(seq - $display("RUN ", name); - test; - $display("OK ", name); - endseq); + $display("Running test with %0d cycle timeout", max); + test; + endseq); endmodule endpackage