Skip to content

Commit 3debc64

Browse files
authored
Merge branch 'amaranth-lang:main' into main
2 parents 764c501 + f96604f commit 3debc64

22 files changed

+1483
-390
lines changed

.github/workflows/main.yaml

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ jobs:
6060
uses: actions/checkout@v3
6161
with:
6262
fetch-depth: 0
63+
- name: Fetch tags from upstream repository
64+
run: |
65+
git fetch --tags https://github.com/amaranth-lang/amaranth.git
6366
- name: Set up Python
6467
uses: actions/setup-python@v4
6568
with:
@@ -71,24 +74,62 @@ jobs:
7174
- name: Build documentation
7275
run: |
7376
sphinx-build docs docs/_build
77+
- name: Upload documentation archive
78+
uses: actions/upload-artifact@v3
79+
with:
80+
name: docs
81+
path: docs/_build
82+
publish-docs:
83+
needs: document
84+
if: github.repository == 'amaranth-lang/amaranth'
85+
runs-on: ubuntu-latest
86+
steps:
87+
- name: Check out source code
88+
uses: actions/checkout@v3
89+
with:
90+
fetch-depth: 0
91+
- name: Download documentation archive
92+
uses: actions/download-artifact@v3
93+
with:
94+
name: docs
95+
path: docs/
7496
- name: Publish development documentation
7597
if: github.event_name == 'push' && github.event.ref == 'refs/heads/main'
7698
uses: JamesIves/github-pages-deploy-action@releases/v4
7799
with:
78100
repository-name: amaranth-lang/amaranth-lang.github.io
79101
ssh-key: ${{ secrets.PAGES_DEPLOY_KEY }}
80102
branch: main
81-
folder: docs/_build
103+
folder: docs/
82104
target-folder: docs/amaranth/latest/
83-
- name: Extract release version
84-
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v')
85-
run: echo "VERSION=$(python setup.py --version)" >>$GITHUB_ENV
86105
- name: Publish release documentation
87106
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v')
88107
uses: JamesIves/github-pages-deploy-action@releases/v4
89108
with:
90109
repository-name: amaranth-lang/amaranth-lang.github.io
91110
ssh-key: ${{ secrets.PAGES_DEPLOY_KEY }}
92111
branch: main
93-
folder: docs/_build
94-
target-folder: docs/amaranth/v${{ env.VERSION }}/
112+
folder: docs/
113+
target-folder: docs/amaranth/${{ github.ref_name }}/
114+
publish-docs-dev:
115+
needs: document
116+
if: github.repository != 'amaranth-lang/amaranth'
117+
runs-on: ubuntu-latest
118+
steps:
119+
- name: Check out source code
120+
uses: actions/checkout@v3
121+
with:
122+
fetch-depth: 0
123+
- name: Download documentation archive
124+
uses: actions/download-artifact@v3
125+
with:
126+
name: docs
127+
path: pages/docs/${{ github.ref_name }}/
128+
- name: Disable Jekyll
129+
run: |
130+
touch pages/.nojekyll
131+
- name: Publish documentation for a branch
132+
uses: JamesIves/github-pages-deploy-action@releases/v4
133+
with:
134+
folder: pages/
135+
clean: false

amaranth/build/plat.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@ def _toolchain_env_var(self):
7676
# TODO(amaranth-0.5): remove
7777
@property
7878
def _all_toolchain_env_vars(self):
79-
return (f"AMARANTH_ENV_{self.toolchain}", self._toolchain_env_var,)
79+
return (
80+
f"AMARANTH_ENV_{self.toolchain.replace('-', '_').replace('+', 'X')}",
81+
self._toolchain_env_var,
82+
)
8083

8184
def build(self, elaboratable, name="top",
8285
build_dir="build", do_build=True,
@@ -333,7 +336,7 @@ def _extract_override(var, *, expected_type):
333336
return re.sub(r'^\"\"$', "", var_env_value)
334337
elif var in kwargs:
335338
kwarg = kwargs[var]
336-
if issubclass(expected_type, str) and isinstance(var, Iterable):
339+
if issubclass(expected_type, str) and not isinstance(kwarg, str) and isinstance(kwarg, Iterable):
337340
kwarg = " ".join(kwarg)
338341
if not isinstance(kwarg, expected_type) and not expected_type is None:
339342
raise TypeError("Override '{}' must be a {}, not {!r}".format(var, expected_type.__name__, kwarg))

amaranth/build/run.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ def mkdirs(path):
176176
# Show the output from the server while products are built.
177177
buf = channel.recv(1024)
178178
while buf:
179-
print(buf.decode("utf-8"), end="")
179+
print(buf.decode("utf-8", errors="replace"), end="")
180180
buf = channel.recv(1024)
181181

182182
return RemoteSSHBuildProducts(connect_to, root)

amaranth/hdl/ast.py

Lines changed: 71 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from abc import ABCMeta, abstractmethod
2+
import inspect
23
import warnings
34
import functools
45
from collections import OrderedDict
@@ -41,12 +42,16 @@ class ShapeCastable:
4142
a richer description of the shape than what is supported by the core Amaranth language, yet
4243
still be transparently used with it.
4344
"""
44-
def __new__(cls, *args, **kwargs):
45-
self = super().__new__(cls)
46-
if not hasattr(self, "as_shape"):
45+
def __init_subclass__(cls, **kwargs):
46+
if not hasattr(cls, "as_shape"):
4747
raise TypeError(f"Class '{cls.__name__}' deriving from `ShapeCastable` must override "
4848
f"the `as_shape` method")
49-
return self
49+
if not (hasattr(cls, "__call__") and inspect.isfunction(cls.__call__)):
50+
raise TypeError(f"Class '{cls.__name__}' deriving from `ShapeCastable` must override "
51+
f"the `__call__` method")
52+
if not hasattr(cls, "const"):
53+
raise TypeError(f"Class '{cls.__name__}' deriving from `ShapeCastable` must override "
54+
f"the `const` method")
5055

5156

5257
class Shape:
@@ -572,8 +577,6 @@ def _lhs_signals(self):
572577
def _rhs_signals(self):
573578
pass # :nocov:
574579

575-
__hash__ = None
576-
577580

578581
@final
579582
class Const(Value):
@@ -634,6 +637,13 @@ def __init__(self, value, shape=None, *, src_loc_at=0):
634637
elif isinstance(shape, int):
635638
shape = Shape(shape, signed=self.value < 0)
636639
else:
640+
if isinstance(shape, range) and self.value == shape.stop:
641+
warnings.warn(
642+
message="Value {!r} equals the non-inclusive end of the constant "
643+
"shape {!r}; this is likely an off-by-one error"
644+
.format(self.value, shape),
645+
category=SyntaxWarning,
646+
stacklevel=2)
637647
shape = Shape.cast(shape, src_loc_at=1 + src_loc_at)
638648
self.width = shape.width
639649
self.signed = shape.signed
@@ -943,8 +953,16 @@ def __repr__(self):
943953
return "(repl {!r} {})".format(self.value, self.count)
944954

945955

956+
class _SignalMeta(ABCMeta):
957+
def __call__(cls, shape=None, src_loc_at=0, **kwargs):
958+
signal = super().__call__(shape, **kwargs, src_loc_at=src_loc_at + 1)
959+
if isinstance(shape, ShapeCastable):
960+
return shape(signal)
961+
return signal
962+
963+
946964
# @final
947-
class Signal(Value, DUID):
965+
class Signal(Value, DUID, metaclass=_SignalMeta):
948966
"""A varying integer value.
949967
950968
Parameters
@@ -985,7 +1003,7 @@ class Signal(Value, DUID):
9851003
decoder : function
9861004
"""
9871005

988-
def __init__(self, shape=None, *, name=None, reset=0, reset_less=False,
1006+
def __init__(self, shape=None, *, name=None, reset=None, reset_less=False,
9891007
attrs=None, decoder=None, src_loc_at=0):
9901008
super().__init__(src_loc_at=src_loc_at)
9911009

@@ -1001,21 +1019,50 @@ def __init__(self, shape=None, *, name=None, reset=0, reset_less=False,
10011019
self.width = shape.width
10021020
self.signed = shape.signed
10031021

1004-
if isinstance(reset, Enum):
1005-
reset = reset.value
1006-
if not isinstance(reset, int):
1007-
raise TypeError("Reset value has to be an int or an integral Enum")
1008-
1009-
reset_width = bits_for(reset, self.signed)
1010-
if reset != 0 and reset_width > self.width:
1011-
warnings.warn("Reset value {!r} requires {} bits to represent, but the signal "
1012-
"only has {} bits"
1013-
.format(reset, reset_width, self.width),
1014-
SyntaxWarning, stacklevel=2 + src_loc_at)
1015-
1016-
self.reset = reset
1022+
orig_reset = reset
1023+
if isinstance(orig_shape, ShapeCastable):
1024+
try:
1025+
reset = Const.cast(orig_shape.const(reset))
1026+
except Exception:
1027+
raise TypeError("Reset value must be a constant initializer of {!r}"
1028+
.format(orig_shape))
1029+
if reset.shape() != Shape.cast(orig_shape):
1030+
raise ValueError("Constant returned by {!r}.const() must have the shape that "
1031+
"it casts to, {!r}, and not {!r}"
1032+
.format(orig_shape, Shape.cast(orig_shape),
1033+
reset.shape()))
1034+
else:
1035+
try:
1036+
reset = Const.cast(reset or 0)
1037+
except TypeError:
1038+
raise TypeError("Reset value must be a constant-castable expression, not {!r}"
1039+
.format(orig_reset))
1040+
if orig_reset not in (None, 0, -1): # Avoid false positives for all-zeroes and all-ones
1041+
if reset.shape().signed and not self.signed:
1042+
warnings.warn(
1043+
message="Reset value {!r} is signed, but the signal shape is {!r}"
1044+
.format(orig_reset, shape),
1045+
category=SyntaxWarning,
1046+
stacklevel=2)
1047+
elif (reset.shape().width > self.width or
1048+
reset.shape().width == self.width and
1049+
self.signed and not reset.shape().signed):
1050+
warnings.warn(
1051+
message="Reset value {!r} will be truncated to the signal shape {!r}"
1052+
.format(orig_reset, shape),
1053+
category=SyntaxWarning,
1054+
stacklevel=2)
1055+
self.reset = reset.value
10171056
self.reset_less = bool(reset_less)
10181057

1058+
if isinstance(orig_shape, range) and self.reset == orig_shape.stop:
1059+
warnings.warn(
1060+
message="Reset value {!r} equals the non-inclusive end of the signal "
1061+
"shape {!r}; this is likely an off-by-one error"
1062+
.format(self.reset, orig_shape),
1063+
category=SyntaxWarning,
1064+
stacklevel=2)
1065+
10191066
self.attrs = OrderedDict(() if attrs is None else attrs)
10201067

10211068
if decoder is None and isinstance(orig_shape, type) and issubclass(orig_shape, Enum):
@@ -1297,15 +1344,13 @@ class ValueCastable:
12971344
from :class:`ValueCastable` is mutable, it is up to the user to ensure that it is not mutated
12981345
in a way that changes its representation after the first call to :meth:`as_value`.
12991346
"""
1300-
def __new__(cls, *args, **kwargs):
1301-
self = super().__new__(cls)
1302-
if not hasattr(self, "as_value"):
1347+
def __init_subclass__(cls, **kwargs):
1348+
if not hasattr(cls, "as_value"):
13031349
raise TypeError(f"Class '{cls.__name__}' deriving from `ValueCastable` must override "
13041350
"the `as_value` method")
1305-
if not hasattr(self.as_value, "_ValueCastable__memoized"):
1351+
if not hasattr(cls.as_value, "_ValueCastable__memoized"):
13061352
raise TypeError(f"Class '{cls.__name__}' deriving from `ValueCastable` must decorate "
13071353
"the `as_value` method with the `ValueCastable.lowermethod` decorator")
1308-
return self
13091354

13101355
@staticmethod
13111356
def lowermethod(func):

0 commit comments

Comments
 (0)