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: f96867893703
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: 850674637a38
Choose a head ref
  • 2 commits
  • 4 files changed
  • 1 contributor

Commits on Dec 17, 2018

  1. back.rtlil: implement Part.

    whitequark committed Dec 17, 2018
    Copy the full SHA
    87cd045 View commit details
  2. back.rtlil: implement Array.

    whitequark committed Dec 17, 2018
    Copy the full SHA
    8506746 View commit details
Showing with 162 additions and 25 deletions.
  1. +1 −1 doc/COMPAT_SUMMARY.md
  2. +31 −0 examples/gpio.py
  3. +96 −20 nmigen/back/rtlil.py
  4. +34 −4 nmigen/hdl/ast.py
2 changes: 1 addition & 1 deletion doc/COMPAT_SUMMARY.md
Original file line number Diff line number Diff line change
@@ -63,7 +63,7 @@ Compatibility summary
<br>Note: values no longer valid as keys in `dict` and `set`; use `ValueDict` and `ValueSet` instead.
- (+) `wrap``Value.wrap`
- (+) `_Operator``Operator`
- (+) `Mux` `Mux`
- (+) `Mux` id
- (+) `_Slice``Slice`, `stop=``end=`, `.stop``.end`
- (+) `_Part``Part`
- (+) `Cat` id, `.l``.operands`
31 changes: 31 additions & 0 deletions examples/gpio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from types import SimpleNamespace
from nmigen import *
from nmigen.back import rtlil, verilog, pysim


class GPIO:
def __init__(self, pins, bus):
self.pins = pins
self.bus = bus

def get_fragment(self, platform):
m = Module()
m.d.comb += self.bus.dat_r.eq(self.pins[self.bus.adr])
with m.If(self.bus.we):
m.d.sync += self.pins[self.bus.adr].eq(self.bus.dat_w)
return m.lower(platform)


# TODO: use Record
bus = SimpleNamespace(
adr=Signal(max=8),
dat_r=Signal(),
dat_w=Signal(),
we=Signal()
)
pins = Signal(8)
gpio = GPIO(Array(pins), bus)
frag = gpio.get_fragment(platform=None)

# print(rtlil.convert(frag, ports=[pins, bus.adr, bus.dat_r, bus.dat_w, bus.we]))
print(verilog.convert(frag, ports=[pins, bus.adr, bus.dat_r, bus.dat_w, bus.we]))
116 changes: 96 additions & 20 deletions nmigen/back/rtlil.py
Original file line number Diff line number Diff line change
@@ -204,12 +204,20 @@ def src(src_loc):
return "{}:{}".format(file, line)


class LegalizeValue(Exception):
def __init__(self, value, branches):
self.value = value
self.branches = list(branches)


class _ValueCompilerState:
def __init__(self, rtlil):
self.rtlil = rtlil
self.wires = ast.ValueDict()
self.driven = ast.ValueDict()
self.ports = ast.ValueDict()
self.rtlil = rtlil
self.wires = ast.ValueDict()
self.driven = ast.ValueDict()
self.ports = ast.ValueDict()

self.expansions = ast.ValueDict()

def add_driven(self, signal, sync):
self.driven[signal] = sync
@@ -255,11 +263,28 @@ def resolve_curr(self, signal, prefix=None):
wire_curr, wire_next = self.resolve(signal, prefix)
return wire_curr

def expand(self, value):
if not self.expansions:
return value
return self.expansions.get(value, value)

@contextmanager
def expand_to(self, value, expansion):
try:
assert value not in self.expansions
self.expansions[value] = expansion
yield
finally:
del self.expansions[value]


class _ValueCompiler(xfrm.AbstractValueTransformer):
def __init__(self, state):
self.s = state

def on_value(self, value):
return super().on_value(self.s.expand(value))

def on_unknown(self, value):
if value is None:
return None
@@ -288,6 +313,16 @@ def on_Slice(self, value):
else:
return "{} [{}:{}]".format(sigspec, value.end - 1, value.start)

def on_ArrayProxy(self, value):
index = self.s.expand(value.index)
if isinstance(index, ast.Const):
if index.value < len(value.elems):
return self(value.elems[index.value])
else:
return self(value.elems[-1])
else:
raise LegalizeValue(value.index, range(len(value.elems)))


class _RHSValueCompiler(_ValueCompiler):
operator_map = {
@@ -426,14 +461,30 @@ def _prepare_value_for_Slice(self, value):
return sigspec

def on_Part(self, value):
raise NotImplementedError
lhs, rhs = value.value, value.offset
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)
# 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.
self.s.rtlil.cell("$shift", ports={
"\\A": self(lhs),
"\\B": self(rhs),
"\\Y": res,
}, params={
"A_SIGNED": lhs_sign,
"A_WIDTH": lhs_bits,
"B_SIGNED": rhs_sign,
"B_WIDTH": rhs_bits,
"Y_WIDTH": res_bits,
}, src=src(value.src_loc))
return res

def on_Repl(self, value):
return "{{ {} }}".format(" ".join(self(value.value) for _ in range(value.count)))

def on_ArrayProxy(self, value):
raise NotImplementedError


class _LHSValueCompiler(_ValueCompiler):
def on_Const(self, value):
@@ -453,20 +504,33 @@ def _prepare_value_for_Slice(self, value):
return self(value)

def on_Part(self, value):
raise NotImplementedError
offset = self.s.expand(value.offset)
if isinstance(offset, ast.Const):
return self(ast.Slice(value.value, offset.value, offset.value + value.width))
else:
raise LegalizeValue(value.offset, range((1 << len(value.offset)) - 1))

def on_Repl(self, value):
raise TypeError # :nocov:

def on_ArrayProxy(self, value):
raise NotImplementedError


class _StatementCompiler(xfrm.AbstractStatementTransformer):
def __init__(self, rhs_compiler, lhs_compiler):
def __init__(self, state, rhs_compiler, lhs_compiler):
self.state = state
self.rhs_compiler = rhs_compiler
self.lhs_compiler = lhs_compiler

self._case = None

@contextmanager
def case(self, switch, value):
try:
old_case = self._case
with switch.case(value) as self._case:
yield
finally:
self._case = old_case

def on_Assign(self, stmt):
if isinstance(stmt, ast.Assign):
lhs_bits, lhs_sign = stmt.lhs.shape()
@@ -477,15 +541,27 @@ def on_Assign(self, stmt):
# In RTLIL, LHS and RHS of assignment must have exactly same width.
rhs_sigspec = self.rhs_compiler.match_shape(
stmt.rhs, lhs_bits, rhs_sign)
self.case.assign(self.lhs_compiler(stmt.lhs), rhs_sigspec)
self._case.assign(self.lhs_compiler(stmt.lhs), rhs_sigspec)

def on_Switch(self, stmt):
with self.case.switch(self.rhs_compiler(stmt.test)) as switch:
with self._case.switch(self.rhs_compiler(stmt.test)) as switch:
for value, stmts in stmt.cases.items():
old_case = self.case
with switch.case(value) as self.case:
with self.case(switch, value):
self.on_statements(stmts)
self.case = old_case

def on_statement(self, stmt):
try:
super().on_statement(stmt)
except LegalizeValue as legalize:
with self._case.switch(self.rhs_compiler(legalize.value)) as switch:
bits, sign = legalize.value.shape()
tests = ["{:0{}b}".format(v, bits) for v in legalize.branches]
tests[-1] = "-" * bits
for branch, test in zip(legalize.branches, tests):
with self.case(switch, test):
branch_value = ast.Const(branch, (bits, sign))
with self.state.expand_to(legalize.value, branch_value):
super().on_statement(stmt)

def on_statements(self, stmts):
for stmt in stmts:
@@ -497,7 +573,7 @@ def convert_fragment(builder, fragment, name, top):
compiler_state = _ValueCompilerState(module)
rhs_compiler = _RHSValueCompiler(compiler_state)
lhs_compiler = _LHSValueCompiler(compiler_state)
stmt_compiler = _StatementCompiler(rhs_compiler, lhs_compiler)
stmt_compiler = _StatementCompiler(compiler_state, rhs_compiler, lhs_compiler)

# Register all signals driven in the current fragment. This must be done first, as it
# affects further codegen; e.g. whether sig$next signals will be generated and used.
@@ -541,7 +617,7 @@ def convert_fragment(builder, fragment, name, top):
case.assign(lhs_compiler(signal), rhs_compiler(prev_value))

# Convert statements into decision trees.
stmt_compiler.case = case
stmt_compiler._case = case
stmt_compiler(fragment.statements)

# For every signal in the sync domain, assign \sig's initial value (which will end up
38 changes: 34 additions & 4 deletions nmigen/hdl/ast.py
Original file line number Diff line number Diff line change
@@ -890,13 +890,24 @@ def __init__(self, value):

def __hash__(self):
if isinstance(self.value, Const):
return hash(self.value)
return hash(self.value.value)
elif isinstance(self.value, Signal):
return hash(id(self.value))
elif isinstance(self.value, Operator):
return hash((self.value.op, tuple(ValueKey(o) for o in self.value.operands)))
elif isinstance(self.value, Slice):
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))
elif isinstance(self.value, Cat):
return hash(tuple(ValueKey(o) for o in self.value.operands))
elif isinstance(self.value, ArrayProxy):
return hash((ValueKey(self.value.index),
tuple(ValueKey(e) for e in self.value._iter_as_values())))
else: # :nocov:
raise TypeError("Object '{!r}' cannot be used as a key in value collections")
raise TypeError("Object '{!r}' cannot be used as a key in value collections"
.format(self.value))

def __eq__(self, other):
if not isinstance(other, ValueKey):
@@ -905,15 +916,34 @@ def __eq__(self, other):
return False

if isinstance(self.value, Const):
return self.value == other.value
return self.value.value == other.value.value
elif isinstance(self.value, Signal):
return id(self.value) == id(other.value)
elif isinstance(self.value, Operator):
return (self.value.op == other.value.op and
len(self.value.operands) == len(other.value.operands) and
all(ValueKey(a) == ValueKey(b)
for a, b in zip(self.value.operands, other.value.operands)))
elif isinstance(self.value, Slice):
return (ValueKey(self.value.value) == ValueKey(other.value.value) and
self.value.start == other.value.start and
self.value.end == other.value.end)
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)
elif isinstance(self.value, Cat):
return all(ValueKey(a) == ValueKey(b)
for a, b in zip(self.value.operands, other.value.operands))
elif isinstance(self.value, ArrayProxy):
return (ValueKey(self.value.index) == ValueKey(other.value.index) and
len(self.value.elems) == len(other.value.elems) and
all(ValueKey(a) == ValueKey(b)
for a, b in zip(self.value._iter_as_values(),
other.value._iter_as_values())))
else: # :nocov:
raise TypeError("Object '{!r}' cannot be used as a key in value collections")
raise TypeError("Object '{!r}' cannot be used as a key in value collections"
.format(self.value))

def __lt__(self, other):
if not isinstance(other, ValueKey):