Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: GlasgowEmbedded/glasgow
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: b6cc3b5afb37
Choose a base ref
...
head repository: GlasgowEmbedded/glasgow
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: dd944bccf5c9
Choose a head ref
  • 2 commits
  • 5 files changed
  • 1 contributor

Commits on Mar 12, 2019

  1. gateware.clockgen: implement universal clock generator.

    This replaces gateware.uart.uart_bit_cyc.
    
    Fixes #80.
    whitequark committed Mar 12, 2019
    Copy the full SHA
    12165c7 View commit details
  2. Copy the full SHA
    dd944bc View commit details
11 changes: 11 additions & 0 deletions software/glasgow/applet/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import re
import argparse

from ..gateware.clockgen import *


__all__ = ["GlasgowAppletError", "GlasgowApplet", "GlasgowAppletTool"]

@@ -30,6 +32,15 @@ def __init_subclass__(cls, name, **kwargs):
def add_build_arguments(cls, parser, access):
access.add_build_arguments(parser)

def derive_clock(self, *args, clock_name=None, **kwargs):
try:
return ClockGen.derive(*args, **kwargs, logger=self.logger, clock_name=None)
except ValueError as e:
if clock_name is None:
raise GlasgowAppletError(e)
else:
raise GlasgowAppletError("clock {}: {}".format(clock_name, e))

def build(self, target):
raise NotImplemented

38 changes: 14 additions & 24 deletions software/glasgow/applet/audio/yamaha_opl/__init__.py
Original file line number Diff line number Diff line change
@@ -69,6 +69,14 @@
# takes slightly more than one YM3812 sample clock (which is slightly less than one VGM sample
# clock). This means that two YM3812 writes followed by a 1 sample delay in VGM "invalidate"
# the delay, by borrowing time from it.
#
# Overclocking
# ------------
#
# It's useful to overclock the synthesizer to get results faster than realtime. Since it's fully
# synchronous digital logic, that doesn't generally affect the output until it breaks.
#
# * YM3812 stops working between 15 MHz (good) and 30 MHz (bad).

import os.path
import logging
@@ -85,6 +93,7 @@
from migen.genlib.cdc import MultiReg

from ....gateware.pads import *
from ....gateware.clockgen import *
from ....protocol.vgm import *
from ... import *

@@ -110,26 +119,8 @@ def __init__(self, pads, master_cyc):

###

half_master_cyc = int(master_cyc // 2)

cyc_m = Signal(max=half_master_cyc)
self.sync += [
If(cyc_m == 0,
cyc_m.eq(half_master_cyc - 1),
).Else(
cyc_m.eq(cyc_m - 1)
)
]

clk_m_s = Signal()
clk_m_r = Signal()
self.sync += [
If(cyc_m == 0,
clk_m_s.eq(~clk_m_s)
),
clk_m_r.eq(clk_m_s),
self.stb_m.eq(~clk_m_r & clk_m_s)
]
self.submodules.clkgen = ClockGen(master_cyc)
self.comb += self.stb_m.eq(self.clkgen.stb_r)

clk_sy_s = Signal()
clk_sy_r = Signal()
@@ -146,7 +137,7 @@ def __init__(self, pads, master_cyc):

self.comb += [
pads.clk_m_t.oe.eq(1),
pads.clk_m_t.o.eq(clk_m_s),
pads.clk_m_t.o.eq(self.clkgen.clk),
pads.d_t.oe.eq(self.oe),
pads.d_t.o.eq(Cat((self.do))),
self.di.eq(Cat((pads.d_t.i))),
@@ -702,9 +693,8 @@ def build(self, target, args):
out_fifo=iface.get_out_fifo(depth=512),
in_fifo=iface.get_in_fifo(depth=8192, auto_flush=False),
# It's useful to run the synthesizer at a frequency significantly higher than real-time
# to reduce the time spent waiting. The choice of 7.5 MHz is somewhat arbitrary but
# it works well for both the divider in the applet and the synthesizer.
master_cyc=target.sys_clk_freq / 7.5e6,
# to reduce the time spent waiting.
master_cyc=self.derive_clock(input_hz=target.sys_clk_freq, output_hz=15e6),
read_pulse_cyc=int(target.sys_clk_freq * 200e-9),
write_pulse_cyc=int(target.sys_clk_freq * 100e-9),
latch_clocks=self.latch_clocks,
10 changes: 2 additions & 8 deletions software/glasgow/applet/interface/uart/__init__.py
Original file line number Diff line number Diff line change
@@ -59,19 +59,13 @@ def add_build_arguments(cls, parser, access):
" (default: %(default)s)")

def build(self, target, args):
try:
bit_cyc, actual_baud = uart_bit_cyc(target.sys_clk_freq, args.baud, args.tolerance)
self.logger.debug("requested baud rate %d, actual %d",
args.baud, actual_baud)
except ValueError as e:
raise GlasgowAppletError(e)

self.mux_interface = iface = target.multiplexer.claim_interface(self, args)
iface.add_subtarget(UARTSubtarget(
pads=iface.get_pads(args, pins=self.__pins),
out_fifo=iface.get_out_fifo(),
in_fifo=iface.get_in_fifo(),
bit_cyc=bit_cyc,
bit_cyc=self.derive_clock(input_hz=target.sys_clk_freq, output_hz=args.baud,
min_cyc=2, max_deviation_ppm=args.tolerance),
parity=args.parity,
))

183 changes: 183 additions & 0 deletions software/glasgow/gateware/clockgen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
from migen import *


__all__ = ["ClockGen"]


class ClockGen(Module):
"""
A clock generator. The purpose of a clock generator is to use an input clock signal to
generate an output clock (50% duty cycle pulses) and rising/falling strobe (1 input clock
period wide pulses preceding the rising/falling edge of output clock pulses) signals using
nothing but LUT/FF logic, i.e. no PLLs, hard dividers, etc. This implies that the output clock
has no precise phase relationship to the input clock.
There are two primary contexts where this is useful. The first is using the system clock
to generate a derived clocks for some synchronous interface, which does not have to be very
precise or in phase to any other clock, but has to cover a very wide range of frequencies.
For example, an SPI interface with a user-defined frequency should cover the range from,
at least, 20 MHz to 1 kHz--that's five orders of magnitude. A PLL is flexible, but would
not be able to cover most of this range, and it is also a very scarce resource; a counter
is cheap and limited only in resolution.
The second is triggering events in the system clock domain at an approximate phase relationship
to the derived clock domain, typically to a) setup or sample a bus, and b) insert wait cycles.
The output of a PLL can be used to do so, but it complicates timing; a strobe signal fully
synchronous to the system clock domain is guaranteed to work.
The primary limitations of this approach is that the output frequency is at most the same as
the input frequency, and the maximum error, i.e. discrepancy between requested and actual
output frequency, can be as high as 33.(3)%, generally increasing towards the higher end of
the output frequency range.
While the gateware may appear trivial (a counter and a register), there are two edge cases
that need to be handled, namely output of 50% of input frequency and 100% of input frequency.
:type cyc: int
:param cyc:
Output clock period, in terms of input clock periods. Use :meth:`derive` to compute this
value, check for edge cases, and (optionally) log the deviation from requested frequency
as well as 50% duty cycle.
"""

def __init__(self, cyc):
self.clk = Signal()
self.stb_r = Signal()
self.stb_f = Signal()

###

if cyc == 0:
# Special case: output frequency equal to input frequency.
# Implementation: wire.
self.comb += [
self.clk.eq(ClockSignal()),
self.stb_r.eq(1),
self.stb_f.eq(1),
]

if cyc == 1:
# Special case: output frequency half of input frequency.
# Implementation: flip-flop.
self.sync += [
self.clk.eq(~self.clk),
]
self.comb += [
self.stb_r.eq(~self.clk),
self.stb_f.eq(self.clk),
]

if cyc >= 2:
# General case.
# Implementation: counter.
counter = Signal(max=cyc)
self.sync += [
counter.eq(counter - 1),
If(counter == 0,
counter.eq(cyc - 1),
),
If(self.stb_r,
self.clk.eq(1),
).Elif(self.stb_f,
self.clk.eq(0),
),
]
self.comb += [
self.stb_r.eq(counter == cyc // 2),
self.stb_f.eq(counter == 0),
]

@staticmethod
def calculate(input_hz, output_hz, max_deviation_ppm=None, min_cyc=None):
"""
Calculate the integer period ratio for dividing an ``input_hz`` clock to an approximately
``output_hz`` clock, and return the divisor as well as the actual output frequency and
its deviation from requested output frequency.
Raises ``ValueError`` on any of the following conditions:
* The output frequency is higher than input frequency.
* The output frequency differs from requested output frequency by more than
``max_deviation_ppm`` parts per million.
* The output period is lower than ``min_cyc`` input periods.
"""
if output_hz <= 0:
raise ValueError("output frequency {:.3f} kHz is not positive"
.format(output_hz / 1000))
if output_hz > input_hz:
raise ValueError("output frequency {:.3f} kHz is higher than input frequency "
"{:.3f} kHz"
.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)
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,
deviation_ppm, max_deviation_ppm))

return cyc, actual_output_hz, deviation_ppm

@classmethod
def derive(cls, input_hz, output_hz, max_deviation_ppm=None, min_cyc=None,
logger=None, clock_name=None):
"""
Derive the parameter for :class:`ClockGen`, and log the input frequency, requested
output frequency, actual output frequency, frequency deviation, and actual duty cycle.
See :meth:`calculate` for details.
"""
cyc, actual_output_hz, deviation_ppm = \
cls.calculate(input_hz, output_hz, max_deviation_ppm, min_cyc)

if logger is not None:
if clock_name is None:
clock = "clock"
else:
clock = "clock {}".format(clock)
if cyc in (0, 1):
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

# -------------------------------------------------------------------------------------------------

import unittest
import re


class ClockGenTestCase(unittest.TestCase):
def test_freq_negative(self):
with self.assertRaisesRegex(ValueError,
re.escape("output frequency -0.001 kHz is not positive")):
ClockGen.calculate(input_hz=1e6, output_hz=-1)

def test_freq_too_high(self):
with self.assertRaisesRegex(ValueError,
re.escape("output frequency 2000.000 kHz is higher than input frequency "
"1000.000 kHz")):
ClockGen.calculate(input_hz=1e6, output_hz=2e6)

def test_period_too_low(self):
with self.assertRaisesRegex(ValueError,
re.escape("output frequency 500.000 kHz requires a period smaller than 3 cycles "
"at input frequency 1000.000 kHz")):
ClockGen.calculate(input_hz=1e6, output_hz=500e3, min_cyc=3)

def test_deviation_too_high(self):
with self.assertRaisesRegex(ValueError,
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)
41 changes: 2 additions & 39 deletions software/glasgow/gateware/uart.py
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
from migen.genlib.cdc import MultiReg


__all__ = ['UART', 'uart_bit_cyc']
__all__ = ["UART"]


class UARTBus(Module):
@@ -36,49 +36,14 @@ def __init__(self, pads):
]


def uart_bit_cyc(clk_freq, baud_rate, max_deviation=50000):
"""
Calculate bit time from clock frequency and baud rate.
:param clk_freq:
Input clock frequency, in Hz.
:type clk_freq: int or float
:param baud_rate:
Baud rate, in bits per second.
:type baud_rate: int or float
:param max_deviation:
Maximum deviation of actual baud rate from ``baud_rate``, in parts per million.
:type max_deviation: int or float
:returns: (int, int or float) -- bit time as a multiple of clock period, and actual baud rate
as calculated based on bit time.
:raises: ValueError -- if the baud rate is too high for the specified clock frequency,
or if actual baud rate deviates from requested baud rate by more than a specified amount.
"""

bit_cyc = round(clk_freq // baud_rate)
if bit_cyc <= 0:
raise ValueError("baud rate {} is too high for input clock frequency {}"
.format(baud_rate, clk_freq))

actual_baud_rate = round(clk_freq // bit_cyc)
deviation = round(1000000 * (actual_baud_rate - baud_rate) // baud_rate)
if deviation > max_deviation:
raise ValueError("baud rate {} deviation from {} ({} ppm) is higher than {} ppm"
.format(actual_baud_rate, baud_rate, deviation, max_deviation))

return bit_cyc + 1, actual_baud_rate


class UART(Module):
"""
Asynchronous serial receiver-transmitter.
Any number of data bits, any parity, and 1 stop bit are supported.
:param bit_cyc:
Bit time expressed as a multiple of system clock periods. Use :func:`uart_bit_cyc`
to calculate bit time from system clock frequency and baud rate.
Bit time expressed as a multiple of system clock periods.
:type bit_cyc: int
:param data_bits:
Data bit count.
@@ -129,8 +94,6 @@ def __init__(self, pads, bit_cyc, data_bits=8, parity="none"):

###

bit_cyc = int(bit_cyc)

def calc_parity(sig, kind):
if kind in ("zero", "none"):
return C(0, 1)