diff --git a/software/glasgow/applet/audio/dac/__init__.py b/software/glasgow/applet/audio/dac/__init__.py index 35cc400ea..cbfb0c42b 100644 --- a/software/glasgow/applet/audio/dac/__init__.py +++ b/software/glasgow/applet/audio/dac/__init__.py @@ -174,7 +174,7 @@ def add_build_arguments(cls, parser, access): def build(self, target, args): self.mux_interface = iface = target.multiplexer.claim_interface(self, args) if args.frequency is None: - pulse_cyc = 0 + pulse_cyc = 1 else: pulse_cyc = self.derive_clock(clock_name="modulation", input_hz=target.sys_clk_freq, output_hz=args.frequency * 1e6) @@ -182,7 +182,7 @@ def build(self, target, args): input_hz=target.sys_clk_freq, output_hz=args.sample_rate, # Drift of sampling clock is extremely bad, so ensure it only happens insofar as # the oscillator on the board is imprecise, and with no additional error. - max_deviation_ppm=0) + max_deviation_ppm=0) - 1 subtarget = iface.add_subtarget(AudioDACSubtarget( ports=iface.get_port_group(o = args.pin_set_o), out_fifo=iface.get_out_fifo(), diff --git a/software/glasgow/applet/interface/spi_controller/__init__.py b/software/glasgow/applet/interface/spi_controller/__init__.py index 72b88c6a1..7081041ef 100644 --- a/software/glasgow/applet/interface/spi_controller/__init__.py +++ b/software/glasgow/applet/interface/spi_controller/__init__.py @@ -350,7 +350,7 @@ def build_subtarget(self, target, args): min_cyc=4), delay_cyc=self.derive_clock(input_hz=target.sys_clk_freq, output_hz=1e6, - clock_name="delay"), + clock_name="delay") - 1, sck_idle=args.sck_idle, sck_edge=args.sck_edge, ) diff --git a/software/glasgow/applet/interface/uart/__init__.py b/software/glasgow/applet/interface/uart/__init__.py index f1feb8e63..530e4ba69 100644 --- a/software/glasgow/applet/interface/uart/__init__.py +++ b/software/glasgow/applet/interface/uart/__init__.py @@ -173,7 +173,7 @@ def build(self, target, args): # a build argument, even though the applet will never be rebuilt as long as you stay # above 9600. max_bit_cyc = self.derive_clock( - input_hz=target.sys_clk_freq, output_hz=min(9600, args.baud)) + input_hz=target.sys_clk_freq, output_hz=min(9600, args.baud)) - 1 self.__sys_clk_freq = target.sys_clk_freq @@ -215,7 +215,7 @@ async def run(self, device, args): # Load the manually set baud rate. manual_cyc = self.derive_clock( input_hz=self.__sys_clk_freq, output_hz=args.baud, - min_cyc=2, max_deviation_ppm=args.tolerance) + min_cyc=2, max_deviation_ppm=args.tolerance) - 1 await device.write_register(self.__addr_manual_cyc, manual_cyc, width=4) await device.write_register(self.__addr_use_auto, 0) diff --git a/software/glasgow/applet/program/m16c/__init__.py b/software/glasgow/applet/program/m16c/__init__.py index a1cdc643c..b809de4e4 100644 --- a/software/glasgow/applet/program/m16c/__init__.py +++ b/software/glasgow/applet/program/m16c/__init__.py @@ -332,7 +332,7 @@ def add_build_arguments(cls, parser, access): def build(self, target, args): self.__bit_cyc_for_baud = { - baud: self.derive_clock(input_hz=target.sys_clk_freq, output_hz=baud) + baud: self.derive_clock(input_hz=target.sys_clk_freq, output_hz=baud) - 1 for baud in BAUD_RATES } max_bit_cyc = max(self.__bit_cyc_for_baud.values()) diff --git a/software/glasgow/gateware/clockgen.py b/software/glasgow/gateware/clockgen.py index a88591ee6..d31e1623f 100644 --- a/software/glasgow/gateware/clockgen.py +++ b/software/glasgow/gateware/clockgen.py @@ -50,7 +50,10 @@ def __init__(self, cyc): def elaborate(self, platform): m = Module() - if self.cyc == 0: + if self.cyc <= 0: + raise ValueError(f"Invalid output clock period: {self.cyc}") + + if self.cyc == 1: # Special case: output frequency equal to input frequency. # Implementation: wire. m.d.comb += [ @@ -59,18 +62,18 @@ def elaborate(self, platform): 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), + 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)) @@ -119,8 +122,8 @@ def calculate(input_hz, output_hz, max_deviation_ppm=None, min_cyc=None): "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: @@ -148,7 +151,7 @@ def derive(cls, input_hz, output_hz, max_deviation_ppm=None, min_cyc=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 diff --git a/software/tests/gateware/test_clockgen.py b/software/tests/gateware/test_clockgen.py index eba448bb5..3deccaaeb 100644 --- a/software/tests/gateware/test_clockgen.py +++ b/software/tests/gateware/test_clockgen.py @@ -1,9 +1,27 @@ import unittest import re +import random +from amaranth import Elaboratable, Module +from amaranth.sim import Tick + +from glasgow.gateware import simulation_test from glasgow.gateware.clockgen import ClockGen +class ClockGenTestbench(Elaboratable): + def __init__(self): + self.cyc = None + + def elaborate(self, platform): + m = Module() + + assert self.cyc is not None + m.submodules.dut = self.dut = ClockGen(self.cyc) + + return m + + class ClockGenTestCase(unittest.TestCase): def test_freq_negative(self): with self.assertRaisesRegex(ValueError, @@ -27,3 +45,45 @@ def test_deviation_too_high(self): re.escape("output frequency 30000.000 kHz deviates from requested frequency " "18000.000 kHz by 666666 ppm, which is higher than 50000 ppm")): ClockGen.calculate(input_hz=30e6, output_hz=18e6, max_deviation_ppm=50000) + + def test_freq_exact(self): + cyc, actual_output_hz, deviation_ppm = ClockGen.calculate(input_hz=100, output_hz=2) + self.assertEqual(cyc, 50) + self.assertEqual(actual_output_hz, 2) + self.assertEqual(deviation_ppm, 0) + + +class ClockGenSimTestCase(unittest.TestCase): + def setUp(self): + self.tb = ClockGenTestbench() + + def configure(self, tb, cyc): + tb.cyc = cyc + + @simulation_test(cyc=2) + def test_half_freq(self, tb): + for _ in range(5): + yield Tick() + self.assertEqual((yield tb.dut.clk), 1) + self.assertEqual((yield tb.dut.stb_r), 1) + yield Tick() + self.assertEqual((yield tb.dut.clk), 0) + self.assertEqual((yield tb.dut.stb_f), 1) + + @simulation_test(cyc=random.randrange(3, 101)) + def test_freq_counter(self, tb): + while (yield tb.dut.clk) != 1: + yield Tick() + + for _ in range(5): + self.assertEqual((yield tb.dut.stb_r), 1) + for _ in range(tb.cyc // 2): + self.assertEqual((yield tb.dut.clk), 1) + yield Tick() + self.assertEqual((yield tb.dut.stb_r), 0) + + self.assertEqual((yield tb.dut.stb_f), 1) + for _ in range(tb.cyc - tb.cyc // 2): + self.assertEqual((yield tb.dut.clk), 0) + yield Tick() + self.assertEqual((yield tb.dut.stb_f), 0)