Description
The ClockGen
module has an off-by-one error when deriving a clock using .derive
or .calculate
. The output clock period is one input clock cycle shorter than necessary. This happens only when the ratio between the input and output frequencies is >= 3.
Here, the input clock is 4Hz and the output clock is 1Hz. Since the input is evenly divisible by the output, we should get an output clock that is exactly 4 input clock cycles long. And, since the ratio is divisible by 2, we should get a 2 clock high pulse and a 2 clock low pulse on the output. Instead, we get 3 cycles - 1 high, 2 low.
The issue is that calculate
computes cyc = round(input_hz // output_hz) - 1
, and then the implementation uses this value for the counter if it's >= 2. If cyc == 0
or cyc == 1
, then we get into one of the two special cases (same frequency or half frequency), and there everything works fine.
I think the fix should be something like:
---
+++
@@ -41,33 +41,33 @@
self.stb_r = Signal()
self.stb_f = Signal()
def elaborate(self, platform):
m = Module()
- if self.cyc == 0:
+ if self.cyc == 1:
# Special case: output frequency equal to input frequency.
# Implementation: wire.
m.d.comb += [
self.clk.eq(ClockSignal()),
self.stb_r.eq(1),
self.stb_f.eq(1),
]
- if self.cyc == 1:
+ if self.cyc == 2:
# Special case: output frequency half of input frequency.
# Implementation: flip-flop.
m.d.sync += [
self.clk.eq(~self.clk),
]
m.d.comb += [
self.stb_r.eq(~self.clk),
self.stb_f.eq(self.clk),
]
- if self.cyc >= 2:
+ if self.cyc >= 3:
# General case.
# Implementation: counter.
counter = Signal(range(self.cyc))
clk_r = Signal()
m.d.sync += counter.eq(counter - 1)
@@ -110,14 +110,14 @@
.format(output_hz / 1000, input_hz / 1000))
if min_cyc is not None and output_hz * min_cyc > input_hz:
raise ValueError("output frequency {:.3f} kHz requires a period smaller than {:d} "
"cycles at input frequency {:.3f} kHz"
.format(output_hz / 1000, min_cyc, input_hz / 1000))
- cyc = round(input_hz // output_hz) - 1
- actual_output_hz = input_hz / (cyc + 1)
+ cyc = round(input_hz // output_hz)
+ actual_output_hz = input_hz / cyc
deviation_ppm = round(1000000 * (actual_output_hz - output_hz) // output_hz)
if max_deviation_ppm is not None and deviation_ppm > max_deviation_ppm:
raise ValueError("output frequency {:.3f} kHz deviates from requested frequency "
"{:.3f} kHz by {:d} ppm, which is higher than {:d} ppm"
.format(actual_output_hz / 1000, output_hz / 1000,
@@ -139,15 +139,15 @@
if logger is not None:
if clock_name is None:
clock = "clock"
else:
clock = f"clock {clock_name}"
- if cyc in (0, 1):
+ if cyc in (1, 2):
duty = 50
else:
duty = (cyc // 2) / cyc * 100
logger.debug("%s in=%.3f req=%.3f out=%.3f [kHz] error=%d [ppm] duty=%.1f%%",
clock, input_hz / 1000, output_hz / 1000, actual_output_hz / 1000,
deviation_ppm, duty)
return cyc
On a related note, the special case where the output frequency is half the input frequency has a strobe behaviour different from the normal case. In the special case stb_r
and stb_f
rise one cycle before their respective clock edges. In the normal case they rise on the same cycle. Not sure which behaviour is the correct one, but I think they should at least be consistent.
In terms of actual impact the whole thing may not be a big deal. Assuming most applets derive clocks that are much slower than the system clock, the off-by-one deviation should be negligible.