lib: add more documentation

This commit is contained in:
David Anderson 2024-08-13 20:53:47 -07:00
parent 85e27554ec
commit f1e705fd31
3 changed files with 133 additions and 83 deletions

View File

@ -2,20 +2,57 @@ package DelayLine;
import List::*; 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); interface DelayLine#(type value);
method Action _write (value v); method Action _write (value v);
method value _read(); method value _read();
(* always_ready *)
method Bool ready(); method Bool ready();
endinterface 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) module mkDelayLine(Integer delay_cycles, DelayLine#(a) ifc)
provisos (Bits#(a, a_sz)); 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 if (delay_cycles == 0) begin
RWire#(a) w <- mkRWire(); RWire#(a) w <- mkRWire();
ret = (interface DelayLine;
method Action _write(a value); method Action _write(a value);
w.wset(value); w.wset(value);
endmethod endmethod
@ -25,13 +62,17 @@ module mkDelayLine(Integer delay_cycles, DelayLine#(a) ifc)
method Bool ready(); method Bool ready();
return isValid(w.wget); return isValid(w.wget);
endmethod endmethod
endinterface);
end end
else begin else begin
RWire#(a) inputVal <- mkRWire; 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 for (Integer i = 0; i < delay_cycles; i = i+1) begin
let r <- mkReg(tagged Invalid); let r <- mkReg(Invalid);
delay = cons(r, delay); delay = cons(r, delay);
end end
@ -42,7 +83,6 @@ module mkDelayLine(Integer delay_cycles, DelayLine#(a) ifc)
delay[i+1] <= delay[i]; delay[i+1] <= delay[i];
endrule endrule
ret = (interface DelayLine;
method Action _write(a value); method Action _write(a value);
inputVal.wset(value); inputVal.wset(value);
endmethod endmethod
@ -52,9 +92,7 @@ module mkDelayLine(Integer delay_cycles, DelayLine#(a) ifc)
method Bool ready(); method Bool ready();
return isValid(delay[delay_cycles-1]); return isValid(delay[delay_cycles-1]);
endmethod endmethod
endinterface);
end end
return ret;
endmodule endmodule
endpackage endpackage

View File

@ -9,22 +9,20 @@ import List::*;
import DelayLine::*; import DelayLine::*;
module mkTB(); module mkTB();
let cycles <- mkTestCycleLimiter(100); let cycles <- mkCycleCounter();
function Stmt testDelayLine(DelayLine#(Int#(8)) delay, Bit#(32) wantDelay); function Stmt testDelayLine(DelayLine#(Int#(8)) delay, Bit#(32) wantDelay);
seq seq
$display(" RUN delay=%0d", wantDelay);
action action
delay <= 42; delay <= 42;
cycles.reset_count(); cycles.reset();
$display(" write cycle: %0d", cycles.cycles); $display(" write cycle: %0d", cycles.all);
endaction endaction
repeat (wantDelay-1) repeat (wantDelay-1)
action action
if (delay.ready) begin 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; $finish;
end end
endaction endaction
@ -37,62 +35,51 @@ module mkTB();
action action
dynamicAssert(delay.ready == True, "delay line not ready when expected"); dynamicAssert(delay.ready == True, "delay line not ready when expected");
if (cycles.count != wantDelay) begin if (cycles != wantDelay) begin
$display("delay line ready after %0d cycles, want %0d", cycles.count, wantDelay); $display("delay line became ready after %0d cycles, want %0d (on cycle %0d)", cycles, wantDelay, cycles.all);
$finish; $finish;
end end
$display(" ready cycle: %0d", cycles.cycles); $display(" read cycle: %0d", cycles.all);
endaction endaction
endpar endpar
dynamicAssert(delay.ready == False, "delay line still ready after value yield"); dynamicAssert(delay.ready == False, "delay line still ready after value yield");
$display(" OK delay=%0d", wantDelay);
endseq; endseq;
endfunction endfunction
let delay0 <- mkDelayLine(0); let delay0 <- mkDelayLine(0);
let test0 = seq let delay1 <- mkDelayLine(1);
$display(" RUN delay=0"); let delay2 <- mkDelayLine(2);
let delay3 <- mkDelayLine(3);
let delay4 <- mkDelayLine(4);
let test0 = seq
dynamicAssert(delay0.ready == False, "delay line ready before put"); dynamicAssert(delay0.ready == False, "delay line ready before put");
par par
action action
delay0 <= 42; delay0 <= 42;
$display(" write cycle: %0d", cycles.cycles); $display(" write cycle: %0d", cycles.all);
endaction endaction
action action
dynamicAssert(delay0.ready == True, "delay line not ready on same cycle"); dynamicAssert(delay0.ready == True, "delay line not ready on same cycle");
$display(" read cycle: %0d", cycles.cycles); $display(" read cycle: %0d", cycles.all);
endaction endaction
dynamicAssert(delay0 == 42, "delay line has wrong value"); dynamicAssert(delay0 == 42, "delay line has wrong value");
endpar endpar
dynamicAssert(delay0.ready == False, "delay line ready without write"); dynamicAssert(delay0.ready == False, "delay line ready without write");
$display(" OK delay=0");
endseq; 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);
runTest(100,
mkTest("DelayLine", seq mkTest("DelayLine", seq
test0; mkTest("DelayLine/0", test0);
test1; mkTest("DelayLine/1", testDelayLine(delay1, 1));
test2; mkTest("DelayLine/2", testDelayLine(delay2, 2));
test3; mkTest("DelayLine/3", testDelayLine(delay3, 3));
test4; mkTest("DelayLine/4", testDelayLine(delay4, 4));
endseq); endseq));
endmodule endmodule
endpackage endpackage

View File

@ -2,39 +2,64 @@ package Testing;
import Assert::*; import Assert::*;
import StmtFSM::*; import StmtFSM::*;
import Cntrs::*;
interface TestCycleLimiter; // A CycleCounter keeps a count of total elapsed cycles in a
method Bit#(32) cycles(); // 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(); // all returns the number of cycles elapsed since simulation start,
method Action reset_count(); // for use in $display and the like.
method Bit#(32) all();
endinterface endinterface
module mkTestCycleLimiter#(Integer max_cycles)(TestCycleLimiter); module mkCycleCounter(CycleCounter);
Bit#(32) max = fromInteger(max_cycles); let total <- mkCount(0);
Reg#(Bit#(32)) cnt <- mkReg(0); let cnt <- mkCount(0);
Reg#(Bit#(32)) lastReset <- mkReg(0);
(* no_implicit_conditions, fire_when_enabled *) (* no_implicit_conditions, fire_when_enabled *)
rule count_up; rule count_up;
dynamicAssert(cnt < max, "FAIL: test timed out"); cnt.incr(1);
cnt <= cnt+1; total.incr(1);
endrule endrule
method cycles = cnt._read; method _read = cnt._read;
method Bit#(32) count(); method Action reset();
return cnt - lastReset; cnt.update(0);
endmethod
method Action reset_count();
lastReset <= cnt;
endmethod endmethod
method all = total._read;
endmodule endmodule
module mkTest#(String name, RStmt#(Bit#(0)) test)(); // mkTest runs the given test, printing status text before and after
mkAutoFSM(seq // the run. Tests can be nested.
$display("RUN ", name); 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("Running test with %0d cycle timeout", max);
test; test;
$display("OK ", name);
endseq); endseq);
endmodule endmodule