phy/lpddr4: extract command serialization logic into separate class

This commit is contained in:
Jędrzej Boczar 2021-05-27 14:20:47 +02:00
parent 5ccf3b57cc
commit 060dbcc70d
2 changed files with 89 additions and 50 deletions

View File

@ -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()

View File

@ -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