diff --git a/lib/Strobe.bsv b/lib/Strobe.bsv index e7c4a92..66bc082 100644 --- a/lib/Strobe.bsv +++ b/lib/Strobe.bsv @@ -2,6 +2,7 @@ package Strobe; import Real::*; import Printf::*; +import DReg::*; // A Strobe provides a synchronization signal to other modules, when // an event happens at a cadence other than the module clock. @@ -9,49 +10,134 @@ import Printf::*; interface Strobe; method Bool _read(); // reset resets the strobe cycle, starting with a strobe on the - // cycle following reset. + // cycle following the reset. method Action reset(); endinterface +module mkStrobeRaw(UInt#(sz) increment, UInt#(sz) max, Strobe ifc); + Reg#(UInt#(sz)) cnt <- mkReg(0); + PulseWire want_reset <- mkPulseWire(); + Reg#(Bool) strobe_out <- mkDReg(False); + + (* no_implicit_conditions, fire_when_enabled *) + rule increment; + if (want_reset) begin + cnt <= 0; + strobe_out <= True; + end + else begin + UInt#(TAdd#(sz, 1)) new_cnt = extend(cnt)+extend(increment); + if (new_cnt >= extend(max)) begin + new_cnt = new_cnt - extend(max); + strobe_out <= True; + end + cnt <= truncate(new_cnt); + end + endrule + + method Bool _read = strobe_out._read; + method Action reset(); + want_reset.send(); + endmethod +endmodule + +module mkStrobeRawOld(UInt#(sz) increment, UInt#(sz) max, Strobe ifc); + Reg#(UInt#(sz)) cnt[2] <- mkCReg(2, 0); + Reg#(Bool) pulse <- mkDReg(False); + PulseWire pw <- mkPulseWireOR(); + + (* no_implicit_conditions, fire_when_enabled *) + rule increment; + UInt#(TAdd#(sz, 1)) new_cnt = extend(cnt[0])+extend(increment); + if (new_cnt >= extend(max)) begin + cnt[0] <= truncate(new_cnt - extend(max)); + pulse <= True; + end + else + cnt[0] <= truncate(new_cnt); + endrule + + (* no_implicit_conditions, fire_when_enabled *) + rule strobe (pulse); + pw.send(); + endrule + + method _read = pw._read; + method Action reset(); + cnt[1] <= increment; + pw.send(); + endmethod +endmodule + +module mkStrobeRawRec(UInt#(n) hack, Integer increment, Integer max, Strobe ifc); + let register_width = ceil(log2(fromInteger(max))); + if (valueOf(n) < register_width) begin + UInt#(TAdd#(n, 1)) hack2 = 0; + let _ret <- mkStrobeRawRec(hack2, increment, max); + return _ret; + end + else begin + UInt#(n) hack2 = fromInteger(increment); + let _ret <- mkStrobeRaw(hack2, fromInteger(max)); + return _ret; + end +endmodule + // 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. + // This is how many main clock ticks there are per strobe at the + // target frequency. For all but the simplest cases, this will + // include fractional clock cycles due to the two frequencies not + // dividing evenly. + Real target_ratio = fromInteger(clock_frequency)/fromInteger(target_frequency); + + // At this point we could round the target ratio and instantiate a + // counter for that amount, but the resulting strobe frequency can + // be quite drastically off target (empirically I've seen up to 5% + // without trying). // - // 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)); + // We can improve on this by counting in larger increments modulo + // some maximum, and strobing on every overflow. This will make the + // strobe period vary slightly by the main clock's reckoning, + // jittering around the "true" strobe time as error accumulates and + // is then subtracted back out. + Real target_error = 1/1000; // 0.1% - Reg#(UInt#(32)) cnt[2] <- mkCReg(2, 0); + Integer incr; + Integer max; + Real actual_error = 1; + for (incr=1; incr<=32 && actual_error > target_error; incr=incr+1) begin + Real mul_ratio = fromInteger(incr)*target_ratio; + max = round(mul_ratio); + actual_error = abs(mul_ratio - fromInteger(max))/fromInteger(max); + end - (* 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 + // Exited the loop, so either we found a good counter width to hit + // the requested error, or we maxed out on a 64-bit counter. + if (actual_error > target_error) + error(sprintf("cannot construct a strobe running at %0dHz from a main clock of %0dHz without exceeding the target frequency error of %0d%%", target_frequency, clock_frequency, target_error*100)); - method Bool _read(); - return cnt[0] == 0; - endmethod + // The loop over-incremented by 1 after finding an acceptable error amount. + incr = incr-1; - method Action reset(); - cnt[1] <= 0; - endmethod + // Debug messages, uncomment if you're wondering what the logic + // above decided for your main clock and target strobe rate. + Bool show_debug = True; + if (show_debug) begin + Real actual_ratio = fromInteger(max)/fromInteger(incr); + Real actual_freq_low = fromInteger(clock_frequency)/fromInteger(ceil(actual_ratio)); + Real actual_freq_high = fromInteger(clock_frequency)/fromInteger(floor(actual_ratio)); + Real actual_freq_avg = fromInteger(clock_frequency)/actual_ratio; + messageM(sprintf("Strobe at %0dHz given clock at %0dHz:\n count by %0d mod %0d\n strobing every %d to %d cycles\n effective strobe frequency %0.2fHz to %0.2fHz\n avg frequency %0.2fHz (%0.2f%% error)", target_frequency, clock_frequency, incr, max, floor(actual_ratio), ceil(actual_ratio), actual_freq_low, actual_freq_high, actual_freq_avg, actual_error*100)); + end + + let _ret <- mkStrobeRawRec(UInt#(1) ' (0), incr, max); + return _ret; endmodule endpackage diff --git a/lib/Strobe_Test.bsv b/lib/Strobe_Test.bsv index a1cd63d..d1e9373 100644 --- a/lib/Strobe_Test.bsv +++ b/lib/Strobe_Test.bsv @@ -11,10 +11,12 @@ module mkTB(); 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; + // 16x115_200bps strobe for a serial port. That translates to a + // strobe that happens every 13 or 14 cycles, depending on the + // amount of accumulated error from the imprecise frequency + // division. + let dut <- mkStrobe(25_000_000, 115_200*16); + let want_pulse_every = 14; function Action check_dut(Bool want); return action @@ -24,29 +26,39 @@ module mkTB(); endaction; endfunction - function Stmt check_one_cycle(); + function Stmt check_one_cycle(Integer cycle_len); return seq action + $display("%0d: cycle start", cycles.all); check_dut(True); cycles.reset(); endaction - while (cycles < want_pulse_every) + while (cycles < fromInteger(cycle_len)) check_dut(False); endseq; endfunction - runTest(2000, + runTest(500, mkTest("Strobe", seq dut.reset(); - repeat(3) check_one_cycle(); + check_one_cycle(14); + check_one_cycle(14); + check_one_cycle(13); + check_one_cycle(14); + check_one_cycle(13); + check_one_cycle(14); + check_one_cycle(13); + check_one_cycle(14); + check_one_cycle(14); + check_one_cycle(13); + check_one_cycle(14); + check_one_cycle(13); // Reset should actually reset repeat(10) noAction; - par - check_dut(False); - dut.reset(); - endpar - repeat(3) check_one_cycle(); + dut.reset(); + check_one_cycle(14); + check_one_cycle(14); endseq)); endmodule