lib/Strobe: rewrite, using better math and some sad type hacking

The numeric types vs numeric value thing sucks, but there's a mild
workaround where you just recurse through numeric types until you
find one that matches the value you wanted. It's icky, but it ensures
registers are exactly the correct width instead of relying on later
synthesis to find and execute the width reduction.
This commit is contained in:
David Anderson 2024-09-09 12:47:19 -07:00
parent b527a62ab8
commit 1b85c3e216
2 changed files with 139 additions and 41 deletions

View File

@ -2,6 +2,7 @@ package Strobe;
import Real::*; import Real::*;
import Printf::*; import Printf::*;
import DReg::*;
// A Strobe provides a synchronization signal to other modules, when // A Strobe provides a synchronization signal to other modules, when
// an event happens at a cadence other than the module clock. // an event happens at a cadence other than the module clock.
@ -9,49 +10,134 @@ import Printf::*;
interface Strobe; interface Strobe;
method Bool _read(); method Bool _read();
// reset resets the strobe cycle, starting with a strobe on the // reset resets the strobe cycle, starting with a strobe on the
// cycle following reset. // cycle following the reset.
method Action reset(); method Action reset();
endinterface 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 // mkStrobe returns a Strobe that triggers at the given
// target_frequency, assuming mkStrobe is being clocked at the given // target_frequency, assuming mkStrobe is being clocked at the given
// higher clock_frequency. // higher clock_frequency.
module mkStrobe(Integer clock_frequency, Integer target_frequency, Strobe ifc); module mkStrobe(Integer clock_frequency, Integer target_frequency, Strobe ifc);
if (target_frequency > clock_frequency) if (target_frequency > clock_frequency)
error("mkStrobe target_frequency must be less than 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, // This is how many main clock ticks there are per strobe at the
// unless the clock and target frequencies divide cleanly we'll end // target frequency. For all but the simplest cases, this will
// up with a small amount of error. // 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 // We can improve on this by counting in larger increments modulo
// operations before some other synchronization event happens // some maximum, and strobing on every overflow. This will make the
// (e.g. sending one byte on UART), so we can allow a small amount // strobe period vary slightly by the main clock's reckoning,
// of frequency error. For now, the target frequency error is fixed // jittering around the "true" strobe time as error accumulates and
// at <=0.1%. // is then subtracted back out.
Real actual_frequency = fromInteger(clock_frequency)/fromInteger(strobe_every); Real target_error = 1/1000; // 0.1%
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); 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 *) // Exited the loop, so either we found a good counter width to hit
rule increment; // the requested error, or we maxed out on a 64-bit counter.
if (cnt[0] == fromInteger(strobe_every-1)) if (actual_error > target_error)
cnt[0] <= 0; 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));
else
cnt[0] <= cnt[0]+1;
endrule
method Bool _read(); // The loop over-incremented by 1 after finding an acceptable error amount.
return cnt[0] == 0; incr = incr-1;
endmethod
method Action reset(); // Debug messages, uncomment if you're wondering what the logic
cnt[1] <= 0; // above decided for your main clock and target strobe rate.
endmethod 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 endmodule
endpackage endpackage

View File

@ -11,10 +11,12 @@ module mkTB();
let cycles <- mkCycleCounter(); let cycles <- mkCycleCounter();
// For this test, we assume we're clocked at 25MHz, and want a // 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 // 16x115_200bps strobe for a serial port. That translates to a
// every 217 cycles. // strobe that happens every 13 or 14 cycles, depending on the
let dut <- mkStrobe(25_000_000, 115_200); // amount of accumulated error from the imprecise frequency
let want_pulse_every = 217; // division.
let dut <- mkStrobe(25_000_000, 115_200*16);
let want_pulse_every = 14;
function Action check_dut(Bool want); function Action check_dut(Bool want);
return action return action
@ -24,29 +26,39 @@ module mkTB();
endaction; endaction;
endfunction endfunction
function Stmt check_one_cycle(); function Stmt check_one_cycle(Integer cycle_len);
return seq return seq
action action
$display("%0d: cycle start", cycles.all);
check_dut(True); check_dut(True);
cycles.reset(); cycles.reset();
endaction endaction
while (cycles < want_pulse_every) while (cycles < fromInteger(cycle_len))
check_dut(False); check_dut(False);
endseq; endseq;
endfunction endfunction
runTest(2000, runTest(500,
mkTest("Strobe", seq mkTest("Strobe", seq
dut.reset(); 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 // Reset should actually reset
repeat(10) noAction; repeat(10) noAction;
par
check_dut(False);
dut.reset(); dut.reset();
endpar check_one_cycle(14);
repeat(3) check_one_cycle(); check_one_cycle(14);
endseq)); endseq));
endmodule endmodule