phy/lpddr4: extract command serialization logic into separate class
This commit is contained in:
parent
5ccf3b57cc
commit
060dbcc70d
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
from operator import or_
|
from operator import or_
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
from migen import *
|
from migen import *
|
||||||
|
|
||||||
|
@ -15,8 +14,8 @@ from litex.soc.interconnect.csr import *
|
||||||
from litedram.common import *
|
from litedram.common import *
|
||||||
from litedram.phy.dfi import *
|
from litedram.phy.dfi import *
|
||||||
|
|
||||||
from litedram.phy.utils import (
|
from litedram.phy.utils import (bitpattern, delayed, DQSPattern, Serializer, Deserializer, Latency,
|
||||||
bitpattern, delayed, ConstBitSlip, DQSPattern, Serializer, Deserializer, Latency)
|
CommandsPipeline)
|
||||||
from litedram.phy.lpddr4.commands import DFIPhaseAdapter
|
from litedram.phy.lpddr4.commands import DFIPhaseAdapter
|
||||||
|
|
||||||
|
|
||||||
|
@ -210,55 +209,20 @@ class LPDDR4PHY(Module, AutoCSR):
|
||||||
]
|
]
|
||||||
|
|
||||||
# LPDDR4 Commands --------------------------------------------------------------------------
|
# LPDDR4 Commands --------------------------------------------------------------------------
|
||||||
# Each LPDDR4 command can span several phases (2 or 4), so the commands cannot overlap.
|
# Each LPDDR4 command can span several phases (2 or 4), so in theory the commands could
|
||||||
# This should be guaranteed by the controller based on module timings, but we also include
|
# overlap. No overlap should be guaranteed by the controller based on module timings, but
|
||||||
# an overlaps check in PHY logic.
|
# we also include an overlaps check in PHY logic.
|
||||||
# Basic check will make sure that no command will be sent to DRAM if there was any command
|
self.submodules.commands = CommandsPipeline(adapters,
|
||||||
# sent by the controller on DFI during 3 previous cycles. The extended version will instead
|
cs_ser_width = len(self.out.cs),
|
||||||
# make sure no command is sent to DRAM if there was any command _actually sent to DRAM_
|
ca_ser_width = len(self.out.ca[0]),
|
||||||
# during 3 previous cycles. This is more expensive in terms of resources and generally not
|
ca_nbits = len(self.out.ca),
|
||||||
# needed.
|
cmd_nphases_span = 4,
|
||||||
|
extended_overlaps_check = extended_overlaps_check
|
||||||
|
)
|
||||||
|
|
||||||
# Create a history of valid adapters used for masking overlapping ones
|
self.comb += self.out.cs.eq(self.commands.cs)
|
||||||
valids = ConstBitSlip(dw=nphases, cycles=1, slp=0)
|
|
||||||
self.submodules += valids
|
|
||||||
self.comb += valids.i.eq(Cat(a.valid for a in adapters))
|
|
||||||
valids_hist = valids.r
|
|
||||||
if extended_overlaps_check:
|
|
||||||
valids_hist = Signal.like(valids.r)
|
|
||||||
for i in range(len(valids_hist)):
|
|
||||||
was_valid_before = reduce(or_, valids_hist[max(0, i-3):i], 0)
|
|
||||||
self.comb += valids_hist[i].eq(valids.r[i] & ~was_valid_before)
|
|
||||||
|
|
||||||
cs_per_adapter = []
|
|
||||||
ca_per_adapter = defaultdict(list)
|
|
||||||
for phase, adapter in enumerate(adapters):
|
|
||||||
# The signals from an adapter can be used if there were no commands on 3 previous cycles
|
|
||||||
allowed = ~reduce(or_, valids_hist[nphases+phase - 3:nphases+phase])
|
|
||||||
|
|
||||||
# Use CS and CA of given adapter slipped by `phase` bits
|
|
||||||
cs_bs = ConstBitSlip(dw=nphases, cycles=1, slp=phase)
|
|
||||||
self.submodules += cs_bs
|
|
||||||
self.comb += cs_bs.i.eq(Cat(adapter.cs)),
|
|
||||||
cs_mask = Replicate(allowed, len(cs_bs.o))
|
|
||||||
cs = cs_bs.o & cs_mask
|
|
||||||
cs_per_adapter.append(cs)
|
|
||||||
|
|
||||||
# For CA we need to do the same for each bit
|
|
||||||
ca_bits = []
|
|
||||||
for bit in range(6):
|
|
||||||
ca_bs = ConstBitSlip(dw=nphases, cycles=1, slp=phase)
|
|
||||||
self.submodules += ca_bs
|
|
||||||
ca_bit_hist = [adapter.ca[i][bit] for i in range(4)]
|
|
||||||
self.comb += ca_bs.i.eq(Cat(*ca_bit_hist)),
|
|
||||||
ca_mask = Replicate(allowed, len(ca_bs.o))
|
|
||||||
ca = ca_bs.o & ca_mask
|
|
||||||
ca_per_adapter[bit].append(ca)
|
|
||||||
|
|
||||||
# OR all the masked signals
|
|
||||||
self.comb += self.out.cs.eq(reduce(or_, cs_per_adapter))
|
|
||||||
for bit in range(6):
|
for bit in range(6):
|
||||||
self.comb += self.out.ca[bit].eq(reduce(or_, ca_per_adapter[bit]))
|
self.comb += self.out.ca[bit].eq(self.commands.ca[bit])
|
||||||
|
|
||||||
# DQ ---------------------------------------------------------------------------------------
|
# DQ ---------------------------------------------------------------------------------------
|
||||||
dq_oe = Signal()
|
dq_oe = Signal()
|
||||||
|
|
|
@ -8,6 +8,7 @@ import re
|
||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from operator import or_
|
from operator import or_
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
from migen import *
|
from migen import *
|
||||||
|
|
||||||
|
@ -120,6 +121,80 @@ class Latency:
|
||||||
return "Latency({} sys clk)".format(self._sys)
|
return "Latency({} sys clk)".format(self._sys)
|
||||||
|
|
||||||
|
|
||||||
|
class CommandsPipeline(Module):
|
||||||
|
"""Commands pipeline logic for LPDDR4/LPDDR5
|
||||||
|
|
||||||
|
Single DFI command may require more than one LPDDR4/LPDDR5 command, effectively
|
||||||
|
spanning multiple DFI phases. This module given a list of DFI phase adapters
|
||||||
|
will use them to translate DFI commands to data on the CS/CA lines and will
|
||||||
|
handle any possible overlaps.
|
||||||
|
|
||||||
|
Basic check will make sure that no command will be sent to DRAM if there was any command
|
||||||
|
sent by the controller on DFI during previous phases. The extended version will instead
|
||||||
|
make sure no command is sent to DRAM if there was any command _actually sent to DRAM_
|
||||||
|
during the previous phasess. This is more expensive in terms of resources and generally
|
||||||
|
not needed.
|
||||||
|
|
||||||
|
Adapters have to provide the following fields: valid, cs, ca.
|
||||||
|
"""
|
||||||
|
def __init__(self, adapters, *,
|
||||||
|
cs_ser_width, # n bits serialized in controller cycle (depends on CS being SDR/DDR)
|
||||||
|
ca_ser_width, # n bits serialized in controller cycle (depends on CA being SDR/DDR)
|
||||||
|
ca_nbits, # number of CA lines (LPDDR4/5 -> 6/7)
|
||||||
|
cmd_nphases_span, # at most how many phases can a command span
|
||||||
|
extended_overlaps_check=False):
|
||||||
|
nphases = len(adapters)
|
||||||
|
self.cs = Signal(cs_ser_width)
|
||||||
|
self.ca = [Signal(ca_ser_width) for _ in range(ca_nbits)]
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
# Number of phases (before the current one) we need to check for overlaps
|
||||||
|
n_previous = cmd_nphases_span - 1
|
||||||
|
|
||||||
|
# Create a history of valid adapters used for masking overlapping ones
|
||||||
|
valids = ConstBitSlip(dw=nphases, cycles=1, slp=0)
|
||||||
|
self.submodules += valids
|
||||||
|
self.comb += valids.i.eq(Cat(a.valid for a in adapters))
|
||||||
|
valids_hist = valids.r
|
||||||
|
if extended_overlaps_check:
|
||||||
|
valids_hist = Signal.like(valids.r)
|
||||||
|
for i in range(len(valids_hist)):
|
||||||
|
hist_before = valids_hist[max(0, i-n_previous):i]
|
||||||
|
was_valid_before = reduce(or_, hist_before, 0)
|
||||||
|
self.comb += valids_hist[i].eq(valids.r[i] & ~was_valid_before)
|
||||||
|
|
||||||
|
cs_per_adapter = []
|
||||||
|
ca_per_adapter = defaultdict(list)
|
||||||
|
for phase, adapter in enumerate(adapters):
|
||||||
|
# The signals from an adapter can be used if there were no commands on previous cycles
|
||||||
|
allowed = ~reduce(or_, valids_hist[nphases+phase - n_previous:nphases+phase])
|
||||||
|
|
||||||
|
# Use CS and CA of given adapter slipped by `phase` bits
|
||||||
|
cs_bs = ConstBitSlip(dw=cs_ser_width, cycles=1, slp=phase)
|
||||||
|
self.submodules += cs_bs
|
||||||
|
self.comb += cs_bs.i.eq(Cat(adapter.cs)),
|
||||||
|
cs_mask = Replicate(allowed, len(cs_bs.o))
|
||||||
|
cs = cs_bs.o & cs_mask
|
||||||
|
cs_per_adapter.append(cs)
|
||||||
|
|
||||||
|
# For CA we need to do the same for each bit
|
||||||
|
ca_bits = []
|
||||||
|
for bit in range(ca_nbits):
|
||||||
|
ca_bs = ConstBitSlip(dw=ca_ser_width, cycles=1, slp=phase)
|
||||||
|
self.submodules += ca_bs
|
||||||
|
ca_bit_hist = [adapter.ca[i][bit] for i in range(cmd_nphases_span)]
|
||||||
|
self.comb += ca_bs.i.eq(Cat(*ca_bit_hist)),
|
||||||
|
ca_mask = Replicate(allowed, len(ca_bs.o))
|
||||||
|
ca = ca_bs.o & ca_mask
|
||||||
|
ca_per_adapter[bit].append(ca)
|
||||||
|
|
||||||
|
# OR all the masked signals
|
||||||
|
self.comb += self.cs.eq(reduce(or_, cs_per_adapter))
|
||||||
|
for bit in range(ca_nbits):
|
||||||
|
self.comb += self.ca[bit].eq(reduce(or_, ca_per_adapter[bit]))
|
||||||
|
|
||||||
|
|
||||||
class Serializer(Module):
|
class Serializer(Module):
|
||||||
"""Serialize given input signal
|
"""Serialize given input signal
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue