From dd5bd1c88d5dbe812743874a3ae0d0f6ebfcf263 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Thu, 18 Apr 2019 20:11:15 +0200 Subject: [PATCH] build: add DSL for defining platform resources. --- nmigen/build/__init__.py | 0 nmigen/build/dsl.py | 82 +++++++++++++++++++++++++ nmigen/test/test_build_dsl.py | 111 ++++++++++++++++++++++++++++++++++ 3 files changed, 193 insertions(+) create mode 100644 nmigen/build/__init__.py create mode 100644 nmigen/build/dsl.py create mode 100644 nmigen/test/test_build_dsl.py diff --git a/nmigen/build/__init__.py b/nmigen/build/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nmigen/build/dsl.py b/nmigen/build/dsl.py new file mode 100644 index 0000000..7fcdb94 --- /dev/null +++ b/nmigen/build/dsl.py @@ -0,0 +1,82 @@ +__all__ = ["Pins", "Subsignal", "DiffPairs", "Resource"] + + +class Pins: + def __init__(self, names, dir="io"): + if not isinstance(names, str): + raise TypeError("Names must be a whitespace-separated string, not {!r}" + .format(names)) + self.names = names.split() + + if dir not in ("i", "o", "io"): + raise TypeError("Direction must be one of \"i\", \"o\" or \"io\", not {!r}" + .format(dir)) + self.dir = dir + + def __repr__(self): + return "(pins {} {})".format(" ".join(self.names), self.dir) + + +class DiffPairs: + def __init__(self, p, n, dir="io"): + self.p = Pins(p, dir=dir) + self.n = Pins(n, dir=dir) + + if len(self.p.names) != len(self.n.names): + raise TypeError("Positive and negative pins must have the same width, but {!r} " + "and {!r} do not" + .format(self.p, self.n)) + + self.dir = self.p.dir + + def __repr__(self): + return "(diffpairs {} {})".format(self.p, self.n) + + +class Subsignal: + def __init__(self, name, *io, extras=()): + self.name = name + + if not io: + raise TypeError("Missing I/O constraints") + for c in io: + if not isinstance(c, (Pins, DiffPairs, Subsignal)): + raise TypeError("I/O constraint must be one of Pins, DiffPairs or Subsignal, " + "not {!r}" + .format(c)) + if isinstance(io[0], (Pins, DiffPairs)) and len(io) > 1: + raise TypeError("Pins and DiffPairs cannot be followed by more I/O constraints, but " + "{!r} is followed by {!r}" + .format(io[0], io[1])) + if isinstance(io[0], Subsignal): + for c in io[1:]: + if not isinstance(c, Subsignal): + raise TypeError("A Subsignal can only be followed by more Subsignals, but " + "{!r} is followed by {!r}" + .format(io[0], c)) + self.io = io + + for c in extras: + if not isinstance(c, str): + raise TypeError("Extra constraint must be a string, not {!r}".format(c)) + self.extras = list(extras) + + if isinstance(self.io[0], Subsignal): + for sub in self.io: + sub.extras += self.extras + + def __repr__(self): + return "(subsignal {} {} {})".format(self.name, + " ".join(map(repr, self.io)), + " ".join(self.extras)) + + +class Resource(Subsignal): + def __init__(self, name, number, *io, extras=()): + self.number = number + super().__init__(name, *io, extras=extras) + + def __repr__(self): + return "(resource {} {} {} {})".format(self.name, self.number, + " ".join(map(repr, self.io)), + " ".join(self.extras)) diff --git a/nmigen/test/test_build_dsl.py b/nmigen/test/test_build_dsl.py new file mode 100644 index 0000000..176e59f --- /dev/null +++ b/nmigen/test/test_build_dsl.py @@ -0,0 +1,111 @@ +from ..build.dsl import * +from .tools import * + + +class PinsTestCase(FHDLTestCase): + def test_basic(self): + p = Pins("A0 A1 A2") + self.assertEqual(repr(p), "(pins A0 A1 A2 io)") + self.assertEqual(len(p.names), 3) + self.assertEqual(p.dir, "io") + + def test_wrong_names(self): + with self.assertRaises(TypeError, + msg="Names must be a whitespace-separated string, not ['A0', 'A1', 'A2']"): + p = Pins(["A0", "A1", "A2"]) + + def test_wrong_dir(self): + with self.assertRaises(TypeError, + msg="Direction must be one of \"i\", \"o\" or \"io\", not 'wrong'"): + p = Pins("A0 A1", dir="wrong") + + +class DiffPairsTestCase(FHDLTestCase): + def test_basic(self): + dp = DiffPairs(p="A0 A1", n="B0 B1") + self.assertEqual(repr(dp), "(diffpairs (pins A0 A1 io) (pins B0 B1 io))") + self.assertEqual(dp.p.names, ["A0", "A1"]) + self.assertEqual(dp.n.names, ["B0", "B1"]) + self.assertEqual(dp.dir, "io") + + def test_dir(self): + dp = DiffPairs("A0", "B0", dir="o") + self.assertEqual(dp.dir, "o") + self.assertEqual(dp.p.dir, "o") + self.assertEqual(dp.n.dir, "o") + + def test_wrong_width(self): + with self.assertRaises(TypeError, + msg="Positive and negative pins must have the same width, but (pins A0 io) and " + "(pins B0 B1 io) do not"): + dp = DiffPairs("A0", "B0 B1") + + +class SubsignalTestCase(FHDLTestCase): + def test_basic_pins(self): + s = Subsignal("a", Pins("A0"), extras=["IOSTANDARD=LVCMOS33"]) + self.assertEqual(repr(s), "(subsignal a (pins A0 io) IOSTANDARD=LVCMOS33)") + + def test_basic_diffpairs(self): + s = Subsignal("a", DiffPairs("A0", "B0")) + self.assertEqual(repr(s), "(subsignal a (diffpairs (pins A0 io) (pins B0 io)) )") + + def test_basic_subsignals(self): + s = Subsignal("a", + Subsignal("b", Pins("A0")), + Subsignal("c", Pins("A1"))) + self.assertEqual(repr(s), + "(subsignal a (subsignal b (pins A0 io) ) (subsignal c (pins A1 io) ) )") + + def test_extras(self): + s = Subsignal("a", + Subsignal("b", Pins("A0")), + Subsignal("c", Pins("A0"), extras=["SLEW=FAST"]), + extras=["IOSTANDARD=LVCMOS33"]) + self.assertEqual(s.extras, ["IOSTANDARD=LVCMOS33"]) + self.assertEqual(s.io[0].extras, ["IOSTANDARD=LVCMOS33"]) + self.assertEqual(s.io[1].extras, ["SLEW=FAST", "IOSTANDARD=LVCMOS33"]) + + def test_empty_io(self): + with self.assertRaises(TypeError, msg="Missing I/O constraints"): + s = Subsignal("a") + + def test_wrong_io(self): + with self.assertRaises(TypeError, + msg="I/O constraint must be one of Pins, DiffPairs or Subsignal, not 'wrong'"): + s = Subsignal("a", "wrong") + + def test_wrong_pins(self): + with self.assertRaises(TypeError, + msg="Pins and DiffPairs cannot be followed by more I/O constraints, but " + "(pins A0 io) is followed by (pins A1 io)"): + s = Subsignal("a", Pins("A0"), Pins("A1")) + + def test_wrong_diffpairs(self): + with self.assertRaises(TypeError, + msg="Pins and DiffPairs cannot be followed by more I/O constraints, but " + "(diffpairs (pins A0 io) (pins B0 io)) is followed by (pins A1 io)"): + s = Subsignal("a", DiffPairs("A0", "B0"), Pins("A1")) + + def test_wrong_subsignals(self): + with self.assertRaises(TypeError, + msg="A Subsignal can only be followed by more Subsignals, but " + "(subsignal b (pins A0 io) ) is followed by (pins B0 io)"): + s = Subsignal("a", Subsignal("b", Pins("A0")), Pins("B0")) + + def test_wrong_extras(self): + with self.assertRaises(TypeError, + msg="Extra constraint must be a string, not (pins B0 io)"): + s = Subsignal("a", Pins("A0"), extras=[Pins("B0")]) + + +class ResourceTestCase(FHDLTestCase): + def test_basic(self): + r = Resource("serial", 0, + Subsignal("tx", Pins("A0", dir="o")), + Subsignal("rx", Pins("A1", dir="i")), + extras=["IOSTANDARD=LVCMOS33"]) + self.assertEqual(repr(r), "(resource serial 0" + " (subsignal tx (pins A0 o) IOSTANDARD=LVCMOS33)" + " (subsignal rx (pins A1 i) IOSTANDARD=LVCMOS33)" + " IOSTANDARD=LVCMOS33)") -- 2.30.2