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:
parent
b527a62ab8
commit
1b85c3e216
142
lib/Strobe.bsv
142
lib/Strobe.bsv
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue