Skip to content
Merged
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: 3 additions & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ jobs:
- '3.10'
- '3.11'
- '3.12'
- '3.13'
- '3.14'
- 'pypy-3.8'
- 'pypy-3.9'
- 'pypy-3.10'
- 'pypy-3.11'
allow-failure:
- false
include:
- python-version: '3.13-dev'
- python-version: '3.15-dev'
allow-failure: true
continue-on-error: ${{ matrix.allow-failure }}
name: 'test (${{ matrix.python-version }})'
Expand Down
15 changes: 8 additions & 7 deletions amaranth/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@ def main_parser(parser=None):
p_generate.add_argument("--no-src", dest="emit_src", default=True, action="store_false",
help="suppress generation of source location attributes")
p_generate.add_argument("generate_file",
metavar="FILE", type=argparse.FileType("w"), nargs="?",
metavar="FILE", type=str, nargs="?",
help="write generated code to FILE")

p_simulate = p_action.add_parser(
"simulate", help="simulate the design")
p_simulate.add_argument("-v", "--vcd-file",
metavar="VCD-FILE", type=argparse.FileType("w"),
metavar="VCD-FILE", type=str,
help="write execution trace to VCD-FILE")
p_simulate.add_argument("-w", "--gtkw-file",
metavar="GTKW-FILE", type=argparse.FileType("w"),
metavar="GTKW-FILE", type=str,
help="write GTKWave configuration to GTKW-FILE")
p_simulate.add_argument("-p", "--period", dest="sync_period",
metavar="TIME", type=float, default=1e-6,
Expand All @@ -48,11 +48,11 @@ def main_runner(parser, args, design, platform=None, name="top", ports=()):
fragment = Fragment.get(design, platform)
generate_type = args.generate_type
if generate_type is None and args.generate_file:
if args.generate_file.name.endswith(".il"):
if args.generate_file.endswith(".il"):
generate_type = "il"
if args.generate_file.name.endswith(".cc"):
if args.generate_file.endswith(".cc"):
generate_type = "cc"
if args.generate_file.name.endswith(".v"):
if args.generate_file.endswith(".v"):
generate_type = "v"
if generate_type is None:
parser.error("Unable to auto-detect language, specify explicitly with -t/--type")
Expand All @@ -63,7 +63,8 @@ def main_runner(parser, args, design, platform=None, name="top", ports=()):
if generate_type == "v":
output = verilog.convert(fragment, name=name, ports=ports, emit_src=args.emit_src)
if args.generate_file:
args.generate_file.write(output)
with open(args.generate_file, "w") as f:
f.write(output)
else:
print(output)

Expand Down
38 changes: 34 additions & 4 deletions amaranth/lib/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
from collections.abc import Mapping, Sequence
import warnings
import operator
try:
import annotationlib # py3.14+
except ImportError:
annotationlib = None # py3.13-

from amaranth._utils import final
from amaranth.hdl import *
Expand Down Expand Up @@ -66,6 +70,9 @@ def width(self):
"""
return Shape.cast(self.shape).width

def __hash__(self):
return hash((self.shape, self.offset))

def __eq__(self, other):
"""Compare fields.

Expand Down Expand Up @@ -167,6 +174,11 @@ def as_shape(self):
"""
return unsigned(self.size)

def __hash__(self):
if not hasattr(self, "_Layout__hash"):
setattr(self, "_Layout__hash", hash((self.size, frozenset(iter(self)))))
return self.__hash

def __eq__(self, other):
"""Compare layouts.

Expand Down Expand Up @@ -1124,6 +1136,9 @@ def __len__(self):
f"`len()` can only be used on constants of array layout, not {self.__layout!r}")
return self.__layout.length

def __hash__(self):
return hash((self.__target, self.__layout))

def __eq__(self, other):
if isinstance(other, View) and self.__layout == other._View__layout:
return self.as_value() == other._View__target
Expand Down Expand Up @@ -1198,7 +1213,19 @@ def __repr__(self):

class _AggregateMeta(ShapeCastable, type):
def __new__(metacls, name, bases, namespace):
if "__annotations__" not in namespace:
annotations = None
skipped_annotations = set()
wrapped_annotate = None
if annotationlib is not None:
if annotate := annotationlib.get_annotate_from_class_namespace(namespace):
annotations = annotationlib.call_annotate_function(
annotate, format=annotationlib.Format.VALUE)
def wrapped_annotate(format):
annos = annotationlib.call_annotate_function(annotate, format, owner=cls)
return {k: v for k, v in annos.items() if k not in skipped_annotations}
else:
annotations = namespace.get("__annotations__")
if annotations is None:
# This is a base class without its own layout. It is not shape-castable, and cannot
# be instantiated. It can be used to share behavior.
return type.__new__(metacls, name, bases, namespace)
Expand All @@ -1207,13 +1234,14 @@ def __new__(metacls, name, bases, namespace):
# be instantiated. It can also be subclassed, and used to share layout and behavior.
layout = dict()
default = dict()
for field_name in {**namespace["__annotations__"]}:
for field_name in {**annotations}:
try:
Shape.cast(namespace["__annotations__"][field_name])
Shape.cast(annotations[field_name])
except TypeError:
# Not a shape-castable annotation; leave as-is.
continue
layout[field_name] = namespace["__annotations__"].pop(field_name)
skipped_annotations.add(field_name)
layout[field_name] = annotations.pop(field_name)
if field_name in namespace:
default[field_name] = namespace.pop(field_name)
cls = type.__new__(metacls, name, bases, namespace)
Expand All @@ -1224,6 +1252,8 @@ def __new__(metacls, name, bases, namespace):
.format(", ".join(default.keys())))
cls.__layout = cls.__layout_cls(layout)
cls.__default = default
if wrapped_annotate is not None:
cls.__annotate__ = wrapped_annotate
return cls
else:
# This is a class that has a base class with a layout and annotations. Such a class
Expand Down
13 changes: 12 additions & 1 deletion amaranth/lib/wiring.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
import enum
import re
import warnings
try:
import annotationlib # py3.14+
except ImportError:
annotationlib = None # py3.13-

from .. import tracer
from ..hdl._ast import Shape, ShapeCastable, Const, Signal, Value, ValueCastable
Expand Down Expand Up @@ -1669,7 +1673,14 @@ def __init__(self, signature=None, *, src_loc_at=0):
cls = type(self)
members = {}
for base in reversed(cls.mro()[:cls.mro().index(Component)]):
for name, annot in base.__dict__.get("__annotations__", {}).items():
annotations = None
if annotationlib is not None:
if annotate := annotationlib.get_annotate_from_class_namespace(base.__dict__):
annotations = annotationlib.call_annotate_function(
annotate, format=annotationlib.Format.VALUE)
if annotations is None:
annotations = base.__dict__.get("__annotations__", {})
for name, annot in annotations.items():
if name.startswith("_"):
continue
if type(annot) is Member:
Expand Down
4 changes: 2 additions & 2 deletions amaranth/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ def get_var_name(depth=2, default=_raise_exception):
return code.co_cellvars[imm]
else:
return code.co_freevars[imm - len(code.co_cellvars)]
elif opc in ("LOAD_GLOBAL", "LOAD_NAME", "LOAD_ATTR", "LOAD_FAST", "LOAD_DEREF",
"DUP_TOP", "BUILD_LIST", "CACHE", "COPY"):
elif opc in ("LOAD_GLOBAL", "LOAD_NAME", "LOAD_ATTR", "LOAD_FAST", "LOAD_FAST_BORROW",
"LOAD_DEREF", "DUP_TOP", "BUILD_LIST", "CACHE", "COPY"):
imm = 0
index += 2
else:
Expand Down
2 changes: 1 addition & 1 deletion amaranth/vendor/_gowin.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ def _osc_div(self):
"{{name}}.sdc": r"""
// {{autogenerated}}
{% for signal, frequency in platform.iter_signal_clock_constraints() -%}
create_clock -name {{ "{" }}{{signal.name}}{{ "}" }} -period {{1000000000/frequency}} [get_nets {{signal|hierarchy("/")|tcl_quote}}]
create_clock -name {{ "{" }}{{signal.name}}{{ "}" }} -period {{1000000000/frequency}} [get_nets {{ "{" }}{{signal|hierarchy("/")}}{{ "}" }}]
{% endfor %}
{% for port, frequency in platform.iter_port_clock_constraints() -%}
create_clock -name {{ "{" }}{{port.name}}{{ "}" }} -period {{1000000000/frequency}} [get_ports
Expand Down
7 changes: 7 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Documentation for past releases

Documentation for past releases of the Amaranth language and toolchain is available online:

* `Amaranth 0.5.7 <https://amaranth-lang.org/docs/amaranth/v0.5.7/>`_
* `Amaranth 0.5.6 <https://amaranth-lang.org/docs/amaranth/v0.5.6/>`_
* `Amaranth 0.5.5 <https://amaranth-lang.org/docs/amaranth/v0.5.5/>`_
* `Amaranth 0.5.4 <https://amaranth-lang.org/docs/amaranth/v0.5.4/>`_
Expand All @@ -25,6 +26,12 @@ Documentation for past releases of the Amaranth language and toolchain is availa
* `Amaranth 0.3 <https://amaranth-lang.org/docs/amaranth/v0.3/>`_


Version 0.5.8
=============

Updated to address compatibility with Python 3.14.


Version 0.5.7
=============

Expand Down
19 changes: 18 additions & 1 deletion tests/test_lib_data.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from enum import Enum
import operator
from unittest import TestCase
try:
import annotationlib # py3.14+
except ImportError:
annotationlib = None # py3.13-

from amaranth.hdl import *
from amaranth.lib import data
Expand Down Expand Up @@ -74,6 +78,9 @@ def test_immutable(self):
with self.assertRaises(AttributeError):
data.Field(1, 0).offset = 1

def test_hash(self):
hash(data.Field(unsigned(2), 1))


class StructLayoutTestCase(FHDLTestCase):
def test_construct(self):
Expand Down Expand Up @@ -520,6 +527,9 @@ def test_signal_init(self):
self.assertEqual(Signal(sl).as_value().init, 0)
self.assertEqual(Signal(sl, init={"a": 0b1, "b": 0b10}).as_value().init, 5)

def test_hash(self):
hash(data.StructLayout({}))


class ViewTestCase(FHDLTestCase):
def test_construct(self):
Expand Down Expand Up @@ -1167,6 +1177,9 @@ def test_repr(self):
s1 = data.Const(data.StructLayout({"a": unsigned(2)}), 2)
self.assertRepr(s1, "Const(StructLayout({'a': unsigned(2)}), 2)")

def test_hash(self):
hash(data.Const(data.StructLayout({"a": unsigned(2)}), 2))


class StructTestCase(FHDLTestCase):
def test_construct(self):
Expand Down Expand Up @@ -1310,7 +1323,11 @@ class S(data.Struct):
c: str = "x"

self.assertEqual(data.Layout.cast(S), data.StructLayout({"a": unsigned(1)}))
self.assertEqual(S.__annotations__, {"b": int, "c": str})
if annotationlib is not None:
annotations = annotationlib.get_annotations(S, format=annotationlib.Format.VALUE)
else:
annotations = S.__annotations__
self.assertEqual(annotations, {"b": int, "c": str})
self.assertEqual(S.c, "x")

def test_signal_like(self):
Expand Down
Loading