lib: add a DelayLine module

A delay line takes a write and echoes it back N cycles later,
with N fixed at compile time. It's a handy primitive to have
when wrapping Verilog blackbox modules because the blackbox
often specifies something like having 2 cycles of latency,
and so you need to bubble the fact that a write occurred 2
cycles ago through to the output so that you can wire up the
right implicit conditions.
This commit is contained in:
David Anderson 2024-08-13 20:53:47 -07:00
parent 27da4958d2
commit 85e27554ec
4 changed files with 196 additions and 12 deletions

60
lib/DelayLine.bsv Normal file
View File

@ -0,0 +1,60 @@
package DelayLine;
import List::*;
interface DelayLine#(type value);
method Action _write (value v);
method value _read();
method Bool ready();
endinterface
module mkDelayLine(Integer delay_cycles, DelayLine#(a) ifc)
provisos (Bits#(a, a_sz));
DelayLine#(a) ret = ?;
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);
end
else begin
RWire#(a) inputVal <- mkRWire;
List#(Reg#(Maybe#(a))) delay = tagged Nil;
for (Integer i = 0; i < delay_cycles; i = i+1) begin
let r <- mkReg(tagged 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
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);
end
return ret;
endmodule
endpackage

98
lib/DelayLine_Test.bsv Normal file
View File

@ -0,0 +1,98 @@
package DelayLine_Test;
import Assert::*;
import StmtFSM::*;
import Testing::*;
import Printf::*;
import List::*;
import DelayLine::*;
module mkTB();
let cycles <- mkTestCycleLimiter(100);
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);
endaction
repeat (wantDelay-1)
action
if (delay.ready) begin
$display("delay line ready after %0d cycles, want %0d", cycles.count, wantDelay);
$finish;
end
endaction
// Check the value coming off the delay line and the timing
// separately, since the delay line read can be blocked by
// implicit conditions.
par
dynamicAssert(delay == 42, "delay output was wrong value");
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);
$finish;
end
$display(" ready cycle: %0d", cycles.cycles);
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");
dynamicAssert(delay0.ready == False, "delay line ready before put");
par
action
delay0 <= 42;
$display(" write cycle: %0d", cycles.cycles);
endaction
action
dynamicAssert(delay0.ready == True, "delay line not ready on same cycle");
$display(" read cycle: %0d", cycles.cycles);
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);
endmodule
endpackage

View File

@ -3,13 +3,31 @@ package Testing;
import Assert::*; import Assert::*;
import StmtFSM::*; import StmtFSM::*;
module mkTestCycleLimit#(Integer max_cycles)(); interface TestCycleLimiter;
Reg#(UInt#(64)) c <- mkReg(fromInteger(max_cycles)); method Bit#(32) cycles();
rule count; method Bit#(32) count();
dynamicAssert(c > 0, "FAIL: test timed out"); method Action reset_count();
c <= c-1; 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);
(* no_implicit_conditions, fire_when_enabled *)
rule count_up;
dynamicAssert(cnt < max, "FAIL: test timed out");
cnt <= cnt+1;
endrule endrule
method cycles = cnt._read;
method Bit#(32) count();
return cnt - lastReset;
endmethod
method Action reset_count();
lastReset <= cnt;
endmethod
endmodule endmodule
module mkTest#(String name, RStmt#(Bit#(0)) test)(); module mkTest#(String name, RStmt#(Bit#(0)) test)();

View File

@ -92,13 +92,18 @@ def build(c, target="."):
c.run(f"bsc -aggressive-conditions -check-assert -u -verilog -info-dir {out_info} -vdir {out_verilog} -bdir {out_bsc} -p {target.parent}:lib:%/Libraries -show-module-use -show-compiles {target}") c.run(f"bsc -aggressive-conditions -check-assert -u -verilog -info-dir {out_info} -vdir {out_verilog} -bdir {out_bsc} -p {target.parent}:lib:%/Libraries -show-module-use -show-compiles {target}")
module_name = Path(f"mk{target.stem}") module_name = Path(f"mk{target.stem}")
verilog_files.append(out_verilog / module_name.with_suffix(".v")) verilog_main_file = out_verilog / module_name.with_suffix(".v")
with open(out_verilog / module_name.with_suffix(".use")) as f: if verilog_main_file.is_file():
verilog_files.extend(find_verilog_modules(c, f.read().splitlines())) verilog_files.append(verilog_main_file)
use_file = out_verilog / module_name.with_suffix(".use")
if use_file.is_file():
with open(out_verilog / module_name.with_suffix(".use")) as f:
verilog_files.extend(find_verilog_modules(c, f.read().splitlines()))
print("\nVerilog files for synthesis:") if verilog_files:
for v in verilog_files: print("\nVerilog files for synthesis:")
print(" "+str(v)) for v in verilog_files:
print(" "+str(v))
return verilog_files return verilog_files
@ -110,6 +115,9 @@ def synth(c, target):
module_name = Path(f"mk{target.stem}") module_name = Path(f"mk{target.stem}")
verilog_files = build(c, target) verilog_files = build(c, target)
if not verilog_files:
print("\nWARNING: bluespec compile didn't output any verilog files, did you (* synthesize *) something?")
return
phase("Logic synthesis") phase("Logic synthesis")
yosys_script = out_yosys / "script.ys" yosys_script = out_yosys / "script.ys"
@ -159,7 +167,7 @@ def synth(c, target):
print(f"Wrote bitstream to {bitstream}") print(f"Wrote bitstream to {bitstream}")
@task @task
def test(c, target="."): def test(c, target):
for target in expand_test_target(target): for target in expand_test_target(target):
out_info, out_sim, out_bsc = ensure_build_dirs(target, "info", "sim", "bsc") out_info, out_sim, out_bsc = ensure_build_dirs(target, "info", "sim", "bsc")
c.run(f"bsc -show-schedule -aggressive-conditions -check-assert -u -sim -info-dir {out_info} -simdir {out_sim} -bdir {out_bsc} -g mkTB -p {target.parent}:lib:%/Libraries {target}") c.run(f"bsc -show-schedule -aggressive-conditions -check-assert -u -sim -info-dir {out_info} -simdir {out_sim} -bdir {out_bsc} -g mkTB -p {target.parent}:lib:%/Libraries {target}")