From 85e27554eccd9841c590aafe8b530723b93a02ed Mon Sep 17 00:00:00 2001 From: David Anderson Date: Tue, 13 Aug 2024 20:53:47 -0700 Subject: [PATCH] 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. --- lib/DelayLine.bsv | 60 ++++++++++++++++++++++++++ lib/DelayLine_Test.bsv | 98 ++++++++++++++++++++++++++++++++++++++++++ lib/Testing.bsv | 28 +++++++++--- tasks.py | 22 +++++++--- 4 files changed, 196 insertions(+), 12 deletions(-) create mode 100644 lib/DelayLine.bsv create mode 100644 lib/DelayLine_Test.bsv diff --git a/lib/DelayLine.bsv b/lib/DelayLine.bsv new file mode 100644 index 0000000..974e1e3 --- /dev/null +++ b/lib/DelayLine.bsv @@ -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 diff --git a/lib/DelayLine_Test.bsv b/lib/DelayLine_Test.bsv new file mode 100644 index 0000000..faf482d --- /dev/null +++ b/lib/DelayLine_Test.bsv @@ -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 diff --git a/lib/Testing.bsv b/lib/Testing.bsv index 4135a50..2cb5ca5 100644 --- a/lib/Testing.bsv +++ b/lib/Testing.bsv @@ -3,13 +3,31 @@ package Testing; import Assert::*; import StmtFSM::*; -module mkTestCycleLimit#(Integer max_cycles)(); - Reg#(UInt#(64)) c <- mkReg(fromInteger(max_cycles)); +interface TestCycleLimiter; + method Bit#(32) cycles(); - rule count; - dynamicAssert(c > 0, "FAIL: test timed out"); - c <= c-1; + method Bit#(32) count(); + method Action reset_count(); +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 + + method cycles = cnt._read; + method Bit#(32) count(); + return cnt - lastReset; + endmethod + method Action reset_count(); + lastReset <= cnt; + endmethod endmodule module mkTest#(String name, RStmt#(Bit#(0)) test)(); diff --git a/tasks.py b/tasks.py index b6170c4..ddd3720 100644 --- a/tasks.py +++ b/tasks.py @@ -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}") module_name = Path(f"mk{target.stem}") - verilog_files.append(out_verilog / module_name.with_suffix(".v")) - with open(out_verilog / module_name.with_suffix(".use")) as f: - verilog_files.extend(find_verilog_modules(c, f.read().splitlines())) + verilog_main_file = out_verilog / module_name.with_suffix(".v") + if verilog_main_file.is_file(): + 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:") - for v in verilog_files: - print(" "+str(v)) + if verilog_files: + print("\nVerilog files for synthesis:") + for v in verilog_files: + print(" "+str(v)) return verilog_files @@ -110,6 +115,9 @@ def synth(c, target): module_name = Path(f"mk{target.stem}") 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") yosys_script = out_yosys / "script.ys" @@ -159,7 +167,7 @@ def synth(c, target): print(f"Wrote bitstream to {bitstream}") @task -def test(c, target="."): +def test(c, target): for target in expand_test_target(target): 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}")