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: 48d13e47ec08
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: 568d3c5b7d23
Choose a head ref
  • 2 commits
  • 4 files changed
  • 1 contributor

Commits on Dec 21, 2018

  1. Copy the full SHA
    fa2af27 View commit details
  2. compat: provide Memory shim.

    whitequark committed Dec 21, 2018
    Copy the full SHA
    568d3c5 View commit details
Showing with 113 additions and 12 deletions.
  1. +4 −4 doc/COMPAT_SUMMARY.md
  2. +82 −1 nmigen/compat/fhdl/specials.py
  3. +21 −6 nmigen/hdl/mem.py
  4. +6 −1 nmigen/test/test_sim.py
8 changes: 4 additions & 4 deletions doc/COMPAT_SUMMARY.md
Original file line number Diff line number Diff line change
@@ -54,11 +54,11 @@ Compatibility summary
- (−) `Tristate` ?
- (+) `TSTriple``.lib.io.TSTriple`, `bits_sign=``shape=`
- (−) `Instance` ?
- () `Memory` id
- () `.get_port` **obs**`.read_port()` + `.write_port()`
- () `_MemoryPort` **obs**
- (+) `Memory` id
- (+) `.get_port` **obs**`.read_port()` + `.write_port()`
- (+) `_MemoryPort` **obs**
<br>Note: nMigen separates read and write ports.
- () `READ_FIRST`/`WRITE_FIRST` **obs**
- (+) `READ_FIRST`/`WRITE_FIRST` **obs**
<br>Note: `READ_FIRST` corresponds to `mem.read_port(transparent=False)`, and `WRITE_FIRST` to `mem.read_port(transparent=True)`.
- (-) `NO_CHANGE` **brk**
<br>Note: in designs using `NO_CHANGE`, repalce it with an asynchronous read port and logic implementing required semantics explicitly.
83 changes: 82 additions & 1 deletion nmigen/compat/fhdl/specials.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import warnings

from ...tools import deprecated, extend
from ...hdl.ast import *
from ...hdl.mem import Memory as NativeMemory
from ...hdl.ir import Fragment
from ...lib.io import TSTriple as NativeTSTriple
from .module import Module as CompatModule


__all__ = ["TSTriple"]
__all__ = ["TSTriple", "READ_FIRST", "WRITE_FIRST", "NO_CHANGE", "_MemoryPort", "Memory"]


class CompatTSTriple(NativeTSTriple):
@@ -16,3 +23,77 @@ def get_tristate(self, target):


TSTriple = CompatTSTriple


(READ_FIRST, WRITE_FIRST, NO_CHANGE) = range(3)


class _MemoryPort(CompatModule):
def __init__(self, adr, dat_r, we=None, dat_w=None, async_read=False, re=None,
we_granularity=0, mode=WRITE_FIRST, clock_domain="sys"):
self.adr = adr
self.dat_r = dat_r
self.we = we
self.dat_w = dat_w
self.async_read = async_read
self.re = re
self.we_granularity = we_granularity
self.mode = mode
self.clock = ClockSignal(clock_domain)


@extend(NativeMemory)
@deprecated("it is not necessary or permitted to add Memory as a special or submodule")
def get_fragment(self, platform):
return Fragment()


class CompatMemory(NativeMemory):
@deprecated("instead of `get_port()`, use `read_port()` and `write_port()`")
def get_port(self, write_capable=False, async_read=False, has_re=False, we_granularity=0,
mode=WRITE_FIRST, clock_domain="sys"):
if we_granularity >= self.width:
warnings.warn("do not specify `we_granularity` greater than memory width, as it "
"is a hard error in non-compatibility mode",
DeprecationWarning, stacklevel=1)
we_granularity = 0
if we_granularity == 0:
warnings.warn("instead of `we_granularity=0`, use `we_granularity=None` or avoid "
"specifying it at all, as it is a hard error in non-compatibility mode",
DeprecationWarning, stacklevel=1)
we_granularity = None
assert mode != NO_CHANGE
rdport = self.read_port(synchronous=not async_read, transparent=mode == WRITE_FIRST)
adr = rdport.addr
dat_r = rdport.data
if write_capable:
wrport = self.write_port(granularity=we_granularity)
wrport.addr = rdport.addr
we = wrport.en
dat_w = wrport.data
else:
we = None
dat_w = None
if has_re:
if mode == READ_FIRST:
re = rdport.en
else:
warnings.warn("the combination of `has_re=True` and `mode=WRITE_FIRST` has "
"surprising behavior: keeping `re` low would merely latch "
"the address, while the data will change with changing memory "
"contents; avoid using `re` with transparent ports as it is a hard "
"error in non-compatibility mode",
DeprecationWarning, stacklevel=1)
re = Signal()
else:
re = None
mp = _MemoryPort(adr, dat_r, we, dat_w,
async_read, re, we_granularity, mode,
clock_domain)
mp.submodules.rdport = rdport
if write_capable:
mp.submodules.wrport = wrport
return mp


Memory = CompatMemory
27 changes: 21 additions & 6 deletions nmigen/hdl/mem.py
Original file line number Diff line number Diff line change
@@ -88,23 +88,38 @@ def get_fragment(self, platform):
i_ADDR=self.addr,
o_DATA=self.data,
)
read_data = self.data.eq(self.memory._array[self.addr])
if self.synchronous and not self.transparent:
# Synchronous, read-before-write port
f.add_statements(Switch(self.en, { 1: read_data }))
f.add_statements(
Switch(self.en, {
1: self.data.eq(self.memory._array[self.addr])
})
)
f.add_driver(self.data, self.domain)
elif self.synchronous:
# Synchronous, write-through port
# This model is a bit unconventional. We model transparent ports as asynchronous ports
# that are latched when the clock is high. This isn't exactly correct, but it is very
# close to the correct behavior of a transparent port, and the difference should only
# be observable in pathological cases of clock gating.
f.add_statements(Switch(ClockSignal(self.domain),
{ 1: self.data.eq(self.data), 0: read_data }))
# be observable in pathological cases of clock gating. A register is injected to
# the address input to achieve the correct address-to-data latency. Also, the reset
# value of the data output is forcibly set to the 0th initial value, if any--note that
# many FPGAs do not guarantee this behavior!
if len(self.memory.init) > 0:
self.data.reset = self.memory.init[0]
latch_addr = Signal.like(self.addr)
f.add_statements(
latch_addr.eq(self.addr),
Switch(ClockSignal(self.domain), {
0: self.data.eq(self.data),
1: self.data.eq(self.memory._array[latch_addr]),
}),
)
f.add_driver(latch_addr, self.domain)
f.add_driver(self.data)
else:
# Asynchronous port
f.add_statements(read_data)
f.add_statements(self.data.eq(self.memory._array[self.addr]))
f.add_driver(self.data)
return f

7 changes: 6 additions & 1 deletion nmigen/test/test_sim.py
Original file line number Diff line number Diff line change
@@ -419,13 +419,14 @@ def test_memory_init(self):
self.setUp_memory()
with self.assertSimulation(self.m) as sim:
def process():
yield
self.assertEqual((yield self.rdport.data), 0xaa)
yield self.rdport.addr.eq(1)
yield
yield
self.assertEqual((yield self.rdport.data), 0x55)
yield self.rdport.addr.eq(2)
yield
yield
self.assertEqual((yield self.rdport.data), 0x00)
sim.add_clock(1e-6)
sim.add_sync_process(process)
@@ -493,6 +494,10 @@ def process():
self.assertEqual((yield self.rdport.data), 0xaa)
yield Delay(1e-6) # let comb propagate
self.assertEqual((yield self.rdport.data), 0x33)
yield
yield self.rdport.addr.eq(1)
yield Delay(1e-6) # let comb propagate
self.assertEqual((yield self.rdport.data), 0x33)
sim.add_clock(1e-6)
sim.add_sync_process(process)