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: m-labs/nmigen
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 29fee01f8629
Choose a base ref
...
head repository: m-labs/nmigen
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 94e13effad26
Choose a head ref
  • 2 commits
  • 6 files changed
  • 1 contributor

Commits on Aug 3, 2019

  1. hdl.ast, back.rtlil: add source locations to anonymous wires.

    This might help with propagation of locations through optimizer
    passes, since not all of them take care to preserve cells at all,
    but usually wires stay intact when possible.
    
    Also fixes incorrect source location on value.part().
    whitequark committed Aug 3, 2019
    Copy the full SHA
    bcdc280 View commit details
  2. hdl.ast: deprecate Value.part, add Value.{bit,word}_select.

    Fixes #148.
    whitequark committed Aug 3, 2019
    Copy the full SHA
    94e13ef View commit details
Showing with 116 additions and 38 deletions.
  1. +12 −10 nmigen/back/pysim.py
  2. +13 −9 nmigen/back/rtlil.py
  3. +39 −8 nmigen/hdl/ast.py
  4. +2 −1 nmigen/hdl/xfrm.py
  5. +34 −6 nmigen/test/test_hdl_ast.py
  6. +16 −4 nmigen/test/test_sim.py
22 changes: 12 additions & 10 deletions nmigen/back/pysim.py
Original file line number Diff line number Diff line change
@@ -177,11 +177,12 @@ def on_Slice(self, value):
return lambda state: normalize((arg(state) >> shift) & mask, shape)

def on_Part(self, value):
shape = value.shape()
arg = self(value.value)
shift = self(value.offset)
mask = (1 << value.width) - 1
return lambda state: normalize((arg(state) >> shift(state)) & mask, shape)
shape = value.shape()
arg = self(value.value)
shift = self(value.offset)
mask = (1 << value.width) - 1
stride = value.stride
return lambda state: normalize((arg(state) >> shift(state) * stride) & mask, shape)

def on_Cat(self, value):
shape = value.shape()
@@ -260,13 +261,14 @@ def eval(state, rhs):
return eval

def on_Part(self, value):
lhs_r = self.rhs_compiler(value.value)
lhs_l = self(value.value)
shift = self.rhs_compiler(value.offset)
mask = (1 << value.width) - 1
lhs_r = self.rhs_compiler(value.value)
lhs_l = self(value.value)
shift = self.rhs_compiler(value.offset)
mask = (1 << value.width) - 1
stride = value.stride
def eval(state, rhs):
lhs_value = lhs_r(state)
shift_value = shift(state)
shift_value = shift(state) * stride
lhs_value &= ~(mask << shift_value)
lhs_value |= (rhs & mask) << shift_value
lhs_l(state, lhs_value)
22 changes: 13 additions & 9 deletions nmigen/back/rtlil.py
Original file line number Diff line number Diff line change
@@ -227,12 +227,14 @@ def update(self, lhs, rhs):


def src(src_loc):
if src_loc is None:
return None
file, line = src_loc
return "{}:{}".format(file, line)


def srcs(src_locs):
return "|".join(sorted(map(src, src_locs)))
return "|".join(sorted(filter(lambda x: x, map(src, src_locs))))


class LegalizeValue(Exception):
@@ -402,7 +404,7 @@ def on_AnyConst(self, value):
return self.s.anys[value]

res_bits, res_sign = value.shape()
res = self.s.rtlil.wire(width=res_bits)
res = self.s.rtlil.wire(width=res_bits, src=src(value.src_loc))
self.s.rtlil.cell("$anyconst", ports={
"\\Y": res,
}, params={
@@ -416,7 +418,7 @@ def on_AnySeq(self, value):
return self.s.anys[value]

res_bits, res_sign = value.shape()
res = self.s.rtlil.wire(width=res_bits)
res = self.s.rtlil.wire(width=res_bits, src=src(value.src_loc))
self.s.rtlil.cell("$anyseq", ports={
"\\Y": res,
}, params={
@@ -433,7 +435,7 @@ def on_Operator_unary(self, value):
arg, = value.operands
arg_bits, arg_sign = arg.shape()
res_bits, res_sign = value.shape()
res = self.s.rtlil.wire(width=res_bits)
res = self.s.rtlil.wire(width=res_bits, src=src(value.src_loc))
self.s.rtlil.cell(self.operator_map[(1, value.op)], ports={
"\\A": self(arg),
"\\Y": res,
@@ -452,7 +454,7 @@ def match_shape(self, value, new_bits, new_sign):
if new_bits <= value_bits:
return self(ast.Slice(value, 0, new_bits))

res = self.s.rtlil.wire(width=new_bits)
res = self.s.rtlil.wire(width=new_bits, src=src(value.src_loc))
self.s.rtlil.cell("$pos", ports={
"\\A": self(value),
"\\Y": res,
@@ -476,7 +478,7 @@ def on_Operator_binary(self, value):
lhs_wire = self.match_shape(lhs, lhs_bits, lhs_sign)
rhs_wire = self.match_shape(rhs, rhs_bits, rhs_sign)
res_bits, res_sign = value.shape()
res = self.s.rtlil.wire(width=res_bits)
res = self.s.rtlil.wire(width=res_bits, src=src(value.src_loc))
self.s.rtlil.cell(self.operator_map[(2, value.op)], ports={
"\\A": lhs_wire,
"\\B": rhs_wire,
@@ -498,7 +500,7 @@ def on_Operator_mux(self, value):
val1_bits = val0_bits = res_bits = max(val1_bits, val0_bits, res_bits)
val1_wire = self.match_shape(val1, val1_bits, val1_sign)
val0_wire = self.match_shape(val0, val0_bits, val0_sign)
res = self.s.rtlil.wire(width=res_bits)
res = self.s.rtlil.wire(width=res_bits, src=src(value.src_loc))
self.s.rtlil.cell("$mux", ports={
"\\A": val0_wire,
"\\B": val1_wire,
@@ -524,16 +526,18 @@ def _prepare_value_for_Slice(self, value):
if isinstance(value, (ast.Signal, ast.Slice, ast.Cat)):
sigspec = self(value)
else:
sigspec = self.s.rtlil.wire(len(value))
sigspec = self.s.rtlil.wire(len(value), src=src(value.src_loc))
self.s.rtlil.connect(sigspec, self(value))
return sigspec

def on_Part(self, value):
lhs, rhs = value.value, value.offset
if value.stride != 1:
rhs *= value.stride
lhs_bits, lhs_sign = lhs.shape()
rhs_bits, rhs_sign = rhs.shape()
res_bits, res_sign = value.shape()
res = self.s.rtlil.wire(width=res_bits)
res = self.s.rtlil.wire(width=res_bits, src=src(value.src_loc))
# Note: Verilog's x[o+:w] construct produces a $shiftx cell, not a $shift cell.
# However, Migen's semantics defines the out-of-range bits to be zero, so it is correct
# to use a $shift cell here instead, even though it produces less idiomatic Verilog.
47 changes: 39 additions & 8 deletions nmigen/hdl/ast.py
Original file line number Diff line number Diff line change
@@ -146,15 +146,41 @@ def implies(premise, conclusion):
"""
return ~premise | conclusion

# TODO(nmigen-0.2): move this to nmigen.compat and make it a deprecated extension
@deprecated("instead of `.part`, use `.bit_slip`")
def part(self, offset, width):
"""Indexed part-select.
return Part(self, offset, width, src_loc_at=1)

Selects a constant width but variable offset part of a ``Value``.
def bit_select(self, offset, width):
"""Part-select with bit granularity.
Selects a constant width but variable offset part of a ``Value``, such that successive
parts overlap by all but 1 bit.
Parameters
----------
offset : Value, in
index of first selected bit
width : int
number of selected bits
Returns
-------
Part, out
Selected part of the ``Value``
"""
return Part(self, offset, width, stride=1, src_loc_at=1)

def word_select(self, offset, width):
"""Part-select with word granularity.
Selects a constant width but variable offset part of a ``Value``, such that successive
parts do not overlap.
Parameters
----------
offset : Value, in
start point of the selected bits
index of first selected word
width : int
number of selected bits
@@ -163,7 +189,7 @@ def part(self, offset, width):
Part, out
Selected part of the ``Value``
"""
return Part(self, offset, width)
return Part(self, offset, width, stride=width, src_loc_at=1)

def eq(self, value):
"""Assignment.
@@ -434,14 +460,17 @@ def __repr__(self):

@final
class Part(Value):
def __init__(self, value, offset, width, *, src_loc_at=0):
def __init__(self, value, offset, width, stride=1, *, src_loc_at=0):
if not isinstance(width, int) or width < 0:
raise TypeError("Part width must be a non-negative integer, not '{!r}'".format(width))
if not isinstance(stride, int) or stride <= 0:
raise TypeError("Part stride must be a positive integer, not '{!r}'".format(stride))

super().__init__(src_loc_at=src_loc_at)
self.value = value
self.offset = Value.wrap(offset)
self.width = width
self.stride = stride

def shape(self):
return self.width, False
@@ -453,7 +482,8 @@ def _rhs_signals(self):
return self.value._rhs_signals() | self.offset._rhs_signals()

def __repr__(self):
return "(part {} {} {})".format(repr(self.value), repr(self.offset), self.width)
return "(part {} {} {} {})".format(repr(self.value), repr(self.offset),
self.width, self.stride)


@final
@@ -1240,7 +1270,7 @@ def __hash__(self):
return hash((ValueKey(self.value.value), self.value.start, self.value.end))
elif isinstance(self.value, Part):
return hash((ValueKey(self.value.value), ValueKey(self.value.offset),
self.value.width))
self.value.width, self.value.stride))
elif isinstance(self.value, Cat):
return hash(tuple(ValueKey(o) for o in self.value.parts))
elif isinstance(self.value, ArrayProxy):
@@ -1276,7 +1306,8 @@ def __eq__(self, other):
elif isinstance(self.value, Part):
return (ValueKey(self.value.value) == ValueKey(other.value.value) and
ValueKey(self.value.offset) == ValueKey(other.value.offset) and
self.value.width == other.value.width)
self.value.width == other.value.width and
self.value.stride == other.value.stride)
elif isinstance(self.value, Cat):
return all(ValueKey(a) == ValueKey(b)
for a, b in zip(self.value.parts, other.value.parts))
3 changes: 2 additions & 1 deletion nmigen/hdl/xfrm.py
Original file line number Diff line number Diff line change
@@ -157,7 +157,8 @@ def on_Slice(self, value):
return Slice(self.on_value(value.value), value.start, value.end)

def on_Part(self, value):
return Part(self.on_value(value.value), self.on_value(value.offset), value.width)
return Part(self.on_value(value.value), self.on_value(value.offset),
value.width, value.stride)

def on_Cat(self, value):
return Cat(self.on_value(o) for o in value.parts)
40 changes: 34 additions & 6 deletions nmigen/test/test_hdl_ast.py
Original file line number Diff line number Diff line change
@@ -294,24 +294,52 @@ def test_repr(self):
self.assertEqual(repr(s1), "(slice (const 4'd10) 2:3)")


class PartTestCase(FHDLTestCase):
class BitSelectTestCase(FHDLTestCase):
def setUp(self):
self.c = Const(0, 8)
self.s = Signal(max=self.c.nbits)

def test_shape(self):
s1 = self.c.part(self.s, 2)
s1 = self.c.bit_select(self.s, 2)
self.assertEqual(s1.shape(), (2, False))
s2 = self.c.part(self.s, 0)
s2 = self.c.bit_select(self.s, 0)
self.assertEqual(s2.shape(), (0, False))

def test_stride(self):
s1 = self.c.bit_select(self.s, 2)
self.assertEqual(s1.stride, 1)

def test_width_bad(self):
with self.assertRaises(TypeError):
self.c.bit_select(self.s, -1)

def test_repr(self):
s = self.c.bit_select(self.s, 2)
self.assertEqual(repr(s), "(part (const 8'd0) (sig s) 2 1)")


class WordSelectTestCase(FHDLTestCase):
def setUp(self):
self.c = Const(0, 8)
self.s = Signal(max=self.c.nbits)

def test_shape(self):
s1 = self.c.word_select(self.s, 2)
self.assertEqual(s1.shape(), (2, False))

def test_stride(self):
s1 = self.c.word_select(self.s, 2)
self.assertEqual(s1.stride, 2)

def test_width_bad(self):
with self.assertRaises(TypeError):
self.c.part(self.s, -1)
self.c.word_select(self.s, 0)
with self.assertRaises(TypeError):
self.c.word_select(self.s, -1)

def test_repr(self):
s = self.c.part(self.s, 2)
self.assertEqual(repr(s), "(part (const 8'd0) (sig s) 2)")
s = self.c.word_select(self.s, 2)
self.assertEqual(repr(s), "(part (const 8'd0) (sig s) 2 2)")


class CatTestCase(FHDLTestCase):
20 changes: 16 additions & 4 deletions nmigen/test/test_sim.py
Original file line number Diff line number Diff line change
@@ -151,18 +151,30 @@ def test_slice_lhs(self):
stmt2 = lambda y, a: y[2:4].eq(a)
self.assertStatement(stmt2, [C(0b01, 2)], C(0b11110111, 8), reset=0b11111011)

def test_part(self):
stmt = lambda y, a, b: y.eq(a.part(b, 3))
def test_bit_select(self):
stmt = lambda y, a, b: y.eq(a.bit_select(b, 3))
self.assertStatement(stmt, [C(0b10110100, 8), C(0)], C(0b100, 3))
self.assertStatement(stmt, [C(0b10110100, 8), C(2)], C(0b101, 3))
self.assertStatement(stmt, [C(0b10110100, 8), C(3)], C(0b110, 3))

def test_part_lhs(self):
stmt = lambda y, a, b: y.part(a, 3).eq(b)
def test_bit_select_lhs(self):
stmt = lambda y, a, b: y.bit_select(a, 3).eq(b)
self.assertStatement(stmt, [C(0), C(0b100, 3)], C(0b11111100, 8), reset=0b11111111)
self.assertStatement(stmt, [C(2), C(0b101, 3)], C(0b11110111, 8), reset=0b11111111)
self.assertStatement(stmt, [C(3), C(0b110, 3)], C(0b11110111, 8), reset=0b11111111)

def test_word_select(self):
stmt = lambda y, a, b: y.eq(a.word_select(b, 3))
self.assertStatement(stmt, [C(0b10110100, 8), C(0)], C(0b100, 3))
self.assertStatement(stmt, [C(0b10110100, 8), C(1)], C(0b110, 3))
self.assertStatement(stmt, [C(0b10110100, 8), C(2)], C(0b010, 3))

def test_word_select_lhs(self):
stmt = lambda y, a, b: y.word_select(a, 3).eq(b)
self.assertStatement(stmt, [C(0), C(0b100, 3)], C(0b11111100, 8), reset=0b11111111)
self.assertStatement(stmt, [C(1), C(0b101, 3)], C(0b11101111, 8), reset=0b11111111)
self.assertStatement(stmt, [C(2), C(0b110, 3)], C(0b10111111, 8), reset=0b11111111)

def test_cat(self):
stmt = lambda y, *xs: y.eq(Cat(*xs))
self.assertStatement(stmt, [C(0b10, 2), C(0b01, 2)], C(0b0110, 4))