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 functools import reduce
|
||||
from collections import defaultdict
|
||||
|
||||
from migen import *
|
||||
|
||||
|
@ -15,8 +14,8 @@ from litex.soc.interconnect.csr import *
|
|||
from litedram.common import *
|
||||
from litedram.phy.dfi import *
|
||||
|
||||
from litedram.phy.utils import (
|
||||
bitpattern, delayed, ConstBitSlip, DQSPattern, Serializer, Deserializer, Latency)
|
||||
from litedram.phy.utils import (bitpattern, delayed, DQSPattern, Serializer, Deserializer, Latency,
|
||||
CommandsPipeline)
|
||||
from litedram.phy.lpddr4.commands import DFIPhaseAdapter
|
||||
|
||||
|
||||
|
@ -210,55 +209,20 @@ class LPDDR4PHY(Module, AutoCSR):
|
|||
]
|
||||
|
||||
# LPDDR4 Commands --------------------------------------------------------------------------
|
||||
# Each LPDDR4 command can span several phases (2 or 4), so the commands cannot overlap.
|
||||
# This should be guaranteed by the controller based on module timings, but 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
|
||||
# sent by the controller on DFI during 3 previous cycles. The extended version will instead
|
||||
# make sure no command is sent to DRAM if there was any command _actually sent to DRAM_
|
||||
# during 3 previous cycles. This is more expensive in terms of resources and generally not
|
||||
# needed.
|
||||
# Each LPDDR4 command can span several phases (2 or 4), so in theory the commands could
|
||||
# overlap. No overlap should be guaranteed by the controller based on module timings, but
|
||||
# we also include an overlaps check in PHY logic.
|
||||
self.submodules.commands = CommandsPipeline(adapters,
|
||||
cs_ser_width = len(self.out.cs),
|
||||
ca_ser_width = len(self.out.ca[0]),
|
||||
ca_nbits = len(self.out.ca),
|
||||
cmd_nphases_span = 4,
|
||||
extended_overlaps_check = extended_overlaps_check
|
||||
)
|
||||
|
||||
# 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)):
|
||||
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))
|
||||
self.comb += self.out.cs.eq(self.commands.cs)
|
||||
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_oe = Signal()
|
||||
|
|
|
@ -8,6 +8,7 @@ import re
|
|||
from fractions import Fraction
|
||||
from functools import reduce
|
||||
from operator import or_
|
||||
from collections import defaultdict
|
||||
|
||||
from migen import *
|
||||
|
||||
|
@ -120,6 +121,80 @@ class Latency:
|
|||
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):
|
||||
"""Serialize given input signal
|
||||
|
||||
|
|
Loading…
Reference in New Issue