Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions software/glasgow/applet/audio/dac/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,15 +174,15 @@ 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)
sample_cyc = self.derive_clock(clock_name="sampling",
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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down
4 changes: 2 additions & 2 deletions software/glasgow/applet/interface/uart/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion software/glasgow/applet/program/m16c/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
19 changes: 11 additions & 8 deletions software/glasgow/gateware/clockgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 += [
Expand All @@ -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))
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
60 changes: 60 additions & 0 deletions software/tests/gateware/test_clockgen.py
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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)