From 4013be675e60bd0638f51d47b591ede412278b08 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 9 Sep 2024 12:47:19 -0700 Subject: [PATCH] lib/Strobe: add a Strobe module to generate synchronization pulses --- lib/Strobe.bsv | 57 +++++++++++++++++++++++++++++++++++++++++++++ lib/Strobe_Test.bsv | 53 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 lib/Strobe.bsv create mode 100644 lib/Strobe_Test.bsv diff --git a/lib/Strobe.bsv b/lib/Strobe.bsv new file mode 100644 index 0000000..e7c4a92 --- /dev/null +++ b/lib/Strobe.bsv @@ -0,0 +1,57 @@ +package Strobe; + +import Real::*; +import Printf::*; + +// A Strobe provides a synchronization signal to other modules, when +// an event happens at a cadence other than the module clock. +(* always_ready *) +interface Strobe; + method Bool _read(); + // reset resets the strobe cycle, starting with a strobe on the + // cycle following reset. + method Action reset(); +endinterface + +// mkStrobe returns a Strobe that triggers at the given +// target_frequency, assuming mkStrobe is being clocked at the given +// higher clock_frequency. +module mkStrobe(Integer clock_frequency, Integer target_frequency, Strobe ifc); + if (target_frequency > clock_frequency) + error("mkStrobe target_frequency must be less than clock_frequency"); + let strobe_every = round(fromInteger(clock_frequency)/fromInteger(target_frequency)); + + // Because we're using integer counters to divide frequencies, + // unless the clock and target frequencies divide cleanly we'll end + // up with a small amount of error. + // + // Strobes like this tend to be used for relatively short + // operations before some other synchronization event happens + // (e.g. sending one byte on UART), so we can allow a small amount + // of frequency error. For now, the target frequency error is fixed + // at <=0.1%. + Real actual_frequency = fromInteger(clock_frequency)/fromInteger(strobe_every); + Real frequency_error_pct = abs(fromInteger(target_frequency)-actual_frequency) / fromInteger(target_frequency) * 100; + if (frequency_error_pct > 0.1) + error(sprintf("mkStrobe actual frequency is %0f, %0f%% error vs. requested %0d. Your clock_frequency and target_frequency are probably too near each other.", actual_frequency, frequency_error_pct, target_frequency)); + + Reg#(UInt#(32)) cnt[2] <- mkCReg(2, 0); + + (* no_implicit_conditions, fire_when_enabled *) + rule increment; + if (cnt[0] == fromInteger(strobe_every-1)) + cnt[0] <= 0; + else + cnt[0] <= cnt[0]+1; + endrule + + method Bool _read(); + return cnt[0] == 0; + endmethod + + method Action reset(); + cnt[1] <= 0; + endmethod +endmodule + +endpackage diff --git a/lib/Strobe_Test.bsv b/lib/Strobe_Test.bsv new file mode 100644 index 0000000..a1cd63d --- /dev/null +++ b/lib/Strobe_Test.bsv @@ -0,0 +1,53 @@ +package Strobe_Test; + +import Assert::*; +import StmtFSM::*; + +import Strobe::*; +import Testing::*; + +module mkTB(); + let testflags <- mkTestFlags(); + let cycles <- mkCycleCounter(); + + // For this test, we assume we're clocked at 25MHz, and want a + // 115_200bps strobe for a serial port. That translates to a strobe + // every 217 cycles. + let dut <- mkStrobe(25_000_000, 115_200); + let want_pulse_every = 217; + + function Action check_dut(Bool want); + return action + if (testflags.verbose) + $display("%0d (%0d): strobe = %0d, want %0d", cycles.all, cycles, dut, want); + dynamicAssert(dut == want, "incorrect strobe state"); + endaction; + endfunction + + function Stmt check_one_cycle(); + return seq + action + check_dut(True); + cycles.reset(); + endaction + while (cycles < want_pulse_every) + check_dut(False); + endseq; + endfunction + + runTest(2000, + mkTest("Strobe", seq + dut.reset(); + repeat(3) check_one_cycle(); + + // Reset should actually reset + repeat(10) noAction; + par + check_dut(False); + dut.reset(); + endpar + repeat(3) check_one_cycle(); + endseq)); +endmodule + +endpackage