text: ## Copyright (C) 2018 Rolf Neugebauer.  All rights reserved.
  ## Copyright (C) 2015 Netronome Systems, Inc.  All rights reserved.
  ##
  ## Licensed under the Apache License, Version 2.0 (the "License");
  ## you may not use this file except in compliance with the License.
  ## You may obtain a copy of the License at
  ##
  ##   http://www.apache.org/licenses/LICENSE-2.0
  ##
  ## Unless required by applicable law or agreed to in writing, software
  ## distributed under the License is distributed on an "AS IS" BASIS,
  ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ## See the License for the specific language governing permissions and
  ## limitations under the License.
  
  """General definitions for PCIe bandwidth calculations"""
  
  # pylint: disable=invalid-name
  # pylint: disable=too-many-instance-attributes
  # pylint: disable=too-many-arguments
  # pylint: disable=too-few-public-methods
  
  ##
  ## General PCIe variables from the Spec
  ##
  Vers = ['gen1', 'gen2', 'gen3', 'gen4', 'gen5']
  Laness = ['x1', 'x2', 'x4', 'x8', 'x16', 'x32']
  Laness_mul = [1, 2, 4, 8, 16, 32]
  
  # Transactions per second
  GTs = {'gen1' : 2.5,
         'gen2' : 5.0,
         'gen3' : 8.0,
         'gen4' : 16.0,
         'gen5' : 32.0}
  
  # Either 8b/10b or 128b/130b symbol encoding
  Gbs = {}
  for ver in GTs.keys():
      if GTs[ver] >= 8.0:
          Gbs[ver] = (128.0/130.0) * GTs[ver]
      else:
          Gbs[ver] = (8.0/10.0) * GTs[ver]
  
  
  # Raw bandwidth Gbs * Lanes
  Raw = {}
  for ver in Vers:
      for lanes in Laness:
          if not ver in Raw:
              Raw[ver] = {}
          Raw[ver][lanes] = Gbs[ver] * \
                            Laness_mul[Laness.index(lanes)]
  
  # Maximum Payload Size
  MPSs = [128, 256, 512, 1024, 2048, 4096]
  # Maximum Read Request Sizes
  MRRSs = [128, 256, 512, 1024, 2048, 4096]
  # Read Completion Boundaries
  RCBs = [64, 128, 256, 512]
  
  # FC Update Rate,
  # see PCIe Base Spec rev 5.0 Table 2-46, 2-47, and 2-48
  FC_Size = 8 # 2 B Phys + 4 B DLLP + 2B DLLP CRC
  FC_Guide = {
      'gen1' : {
          'x1'  : {128: 237, 256: 416, 512: 559, 1024: 1071, 2048: 2095, 4096: 4143},
          'x2'  : {128: 128, 256: 217, 512: 289, 1024:  545, 2048: 1057, 4096: 2081},
          'x4'  : {128:  73, 256: 118, 512: 154, 1024:  282, 2048:  538, 4096: 1050},
          'x8'  : {128:  67, 256: 107, 512:  86, 1024:  150, 2048:  278, 4096:  534},
          'x16' : {128:  48, 256:  72, 512:  86, 1024:  150, 2048:  278, 4096:  534},
          'x32' : {128:  33, 256:  45, 512:  52, 1024:   84, 2048:  248, 4096:  276},
          },
      'gen2' : {
          'x1'  : {128: 288, 256: 467, 512: 610, 1024: 1122, 2048: 2146, 4096: 4194},
          'x2'  : {128: 179, 256: 268, 512: 340, 1024:  596, 2048: 1108, 4096: 2132},
          'x4'  : {128: 124, 256: 169, 512: 205, 1024:  333, 2048:  589, 4096: 1101},
          'x8'  : {128: 118, 256: 158, 512: 137, 1024:  201, 2048:  329, 4096:  585},
          'x16' : {128:  99, 256: 123, 512: 137, 1024:  201, 2048:  329, 4096:  585},
          'x32' : {128:  84, 256:  96, 512: 103, 1024:  135, 2048:  199, 4096:  327},
          },
      'gen3' : {
          'x1'  : {128: 333, 256: 512, 512: 655, 1024: 1167, 2048: 2191, 4096: 4239},
          'x2'  : {128: 224, 256: 313, 512: 385, 1024:  641, 2048: 1153, 4096: 2177},
          'x4'  : {128: 169, 256: 214, 512: 250, 1024:  378, 2048:  634, 4096: 1146},
          'x8'  : {128: 163, 256: 203, 512: 182, 1024:  246, 2048:  374, 4096:  630},
          'x16' : {128: 144, 256: 168, 512: 182, 1024:  246, 2048:  374, 4096:  630},
          'x32' : {128: 129, 256: 141, 512: 148, 1024:  180, 2048:  244, 4096:  372},
          },
      'gen4' : {
          'x1'  : {128: 333, 256: 512, 512: 655, 1024: 1167, 2048: 2191, 4096: 4239},
          'x2'  : {128: 224, 256: 313, 512: 385, 1024:  641, 2048: 1153, 4096: 2177},
          'x4'  : {128: 169, 256: 214, 512: 250, 1024:  378, 2048:  634, 4096: 1146},
          'x8'  : {128: 163, 256: 203, 512: 182, 1024:  246, 2048:  374, 4096:  630},
          'x16' : {128: 144, 256: 168, 512: 182, 1024:  246, 2048:  374, 4096:  630},
          'x32' : {128: 129, 256: 141, 512: 148, 1024:  180, 2048:  244, 4096:  372},
          },
      'gen5' : {
          'x1'  : {128: 333, 256: 512, 512: 655, 1024: 1167, 2048: 2191, 4096: 4239},
          'x2'  : {128: 224, 256: 313, 512: 385, 1024:  641, 2048: 1153, 4096: 2177},
          'x4'  : {128: 169, 256: 214, 512: 250, 1024:  378, 2048:  634, 4096: 1146},
          'x8'  : {128: 163, 256: 203, 512: 182, 1024:  246, 2048:  374, 4096:  630},
          'x16' : {128: 144, 256: 168, 512: 182, 1024:  246, 2048:  374, 4096:  630},
          'x32' : {128: 129, 256: 141, 512: 148, 1024:  180, 2048:  244, 4096:  372},
          },
      }
  
  # Ack Limit,
  # see PCIe Base Spec rev 5.0 Table 3-7, 3-8, and 3-9
  Ack_Size = 8 # 2 B Phys + 4 B DLLP + 2B DLLP CRC
  Ack_Limits = {
      'gen1' : {
          'x1'  : {128: 237, 256: 416, 512: 559, 1024: 1071, 2048: 2095, 4096: 4143},
          'x2'  : {128: 128, 256: 217, 512: 289, 1024:  545, 2048: 1057, 4096: 2081},
          'x4'  : {128:  73, 256: 118, 512: 154, 1024:  282, 2048:  538, 4096: 1050},
          'x8'  : {128:  67, 256: 107, 512:  86, 1024:  150, 2048:  278, 4096:  534},
          'x16' : {128:  48, 256:  72, 512:  86, 1024:  150, 2048:  278, 4096:  534},
          'x32' : {128:  33, 256:  45, 512:  52, 1024:   84, 2048:  148, 4096:  276},
          },
      'gen2' : {
          'x1'  : {128: 288, 256: 467, 512: 610, 1024: 1122, 2048: 2146, 4096: 4194},
          'x2'  : {128: 179, 256: 268, 512: 340, 1024:  596, 2048: 1108, 4096: 2132},
          'x4'  : {128: 124, 256: 169, 512: 205, 1024:  333, 2048:  589, 4096: 1101},
          'x8'  : {128: 118, 256: 158, 512: 137, 1024:  201, 2048:  329, 4096:  585},
          'x16' : {128:  99, 256: 123, 512: 137, 1024:  201, 2048:  329, 4096:  585},
          'x32' : {128:  84, 256:  96, 512: 103, 1024:  135, 2048:  199, 4096:  237},
          },
      'gen3' : {
          'x1'  : {128: 333, 256: 512, 512: 655, 1024: 1167, 2048: 2191, 4096: 4239},
          'x2'  : {128: 224, 256: 313, 512: 385, 1024:  641, 2048: 1153, 4096: 2177},
          'x4'  : {128: 169, 256: 214, 512: 250, 1024:  378, 2048:  634, 4096: 1146},
          'x8'  : {128: 163, 256: 203, 512: 182, 1024:  246, 2048:  374, 4096:  630},
          'x16' : {128: 144, 256: 168, 512: 182, 1024:  246, 2048:  374, 4096:  630},
          'x32' : {128: 129, 256: 141, 512: 148, 1024:  180, 2048:  244, 4096:  372},
          },
      'gen4' : {
          'x1'  : {128: 333, 256: 512, 512: 655, 1024: 1167, 2048: 2191, 4096: 4239},
          'x2'  : {128: 224, 256: 313, 512: 385, 1024:  641, 2048: 1153, 4096: 2177},
          'x4'  : {128: 169, 256: 214, 512: 250, 1024:  378, 2048:  634, 4096: 1146},
          'x8'  : {128: 163, 256: 203, 512: 182, 1024:  246, 2048:  374, 4096:  630},
          'x16' : {128: 144, 256: 168, 512: 182, 1024:  246, 2048:  374, 4096:  630},
          'x32' : {128: 129, 256: 141, 512: 148, 1024:  180, 2048:  244, 4096:  372},
          },
      'gen5' : {
          'x1'  : {128: 333, 256: 512, 512: 655, 1024: 1167, 2048: 2191, 4096: 4239},
          'x2'  : {128: 224, 256: 313, 512: 385, 1024:  641, 2048: 1153, 4096: 2177},
          'x4'  : {128: 169, 256: 214, 512: 250, 1024:  378, 2048:  634, 4096: 1146},
          'x8'  : {128: 163, 256: 203, 512: 182, 1024:  246, 2048:  374, 4096:  630},
          'x16' : {128: 144, 256: 168, 512: 182, 1024:  246, 2048:  374, 4096:  630},
          'x32' : {128: 129, 256: 141, 512: 148, 1024:  180, 2048:  244, 4096:  372},
          },
      }
  
  # SKIP ordered sets for clock compensation (inserted on all lanes)
  SKIP_Interval = 1538
  SKIP_Length = 4
  # DLLP header (6 bytes) plus start and end symbol at Phys layer
  DLLP_Hdr = 8
  
  # Maximum Bandwidth usable at TLP layer. This takes into account the
  # recommended rates for ACKs and FC updates as per spec as well as the SKIP
  # ordered sets for clock compensation. The Bandwidth can be further reduced
  # due to bit errors or different chipset configurations
  TLP_bw = {}
  for ver in Vers:
      for lanes in Laness:
          for mps in MPSs:
              if not ver in TLP_bw:
                  TLP_bw[ver] = {}
              if not lanes in TLP_bw[ver]:
                  TLP_bw[ver][lanes] = {}
  
              ack_overhead = float(Ack_Size) / float(Ack_Limits[ver][lanes][mps])
              fc_overhead = float(FC_Size) / float(FC_Guide[ver][lanes][mps])
              skip_overhead = float(SKIP_Length) / float(SKIP_Interval)
              overheads = ack_overhead + fc_overhead + skip_overhead
              # deduct overheads for ACKs and FC updates
              TLP_bw[ver][lanes][mps] = Raw[ver][lanes] - Raw[ver][lanes] * overheads
  
  
  # TLP Types
  TLP_Hdr = 4         # 4 byte generic TLP header
  TLP_MWr_32_Hdr = 8  # Mem Write 4 byte for address + 4 bytes
  TLP_MWr_64_Hdr = 12 # Mem Write 8 byte for address + 4 bytes
  TLP_MRd_32_Hdr = 8  # Mem Read 4 byte for address + 4 bytes
  TLP_MRd_64_Hdr = 12 # Mem Read 8 byte for address + 4 bytes
  TLP_Msg = 16        # Message Request Header
  TLP_Cpl_Hdr = 8     # Completion no Data 4 bytes completer ID + extra
  TLP_CplD_Hdr = 8    # Completion with Data 4 bytes completer ID + extra
  TLP_Dig = 4         # Optional digest trailer, 4 bytes, e.g. for ECRC
  # There are a few other TLP Types (MRdLk, IORd, IOWr CfgRd0, CfgRd1,
  # CfgWr0, CfgWr1, CplLk, CplDLk) which we ignore for now
  
  # Indexed by address size and optional ECRC
  TLP_MWr_Hdr_Szs = {
      32: {0: DLLP_Hdr + TLP_Hdr + TLP_MWr_32_Hdr,
           1: DLLP_Hdr + TLP_Hdr + TLP_MWr_32_Hdr + TLP_Dig},
      64: {0: DLLP_Hdr + TLP_Hdr + TLP_MWr_64_Hdr,
           1: DLLP_Hdr + TLP_Hdr + TLP_MWr_64_Hdr + TLP_Dig}
  }
  TLP_MRd_Hdr_Szs = {
      32: {0: DLLP_Hdr + TLP_Hdr + TLP_MRd_32_Hdr,
           1: DLLP_Hdr + TLP_Hdr + TLP_MRd_32_Hdr + TLP_Dig},
      64: {0: DLLP_Hdr + TLP_Hdr + TLP_MRd_64_Hdr,
           1: DLLP_Hdr + TLP_Hdr + TLP_MRd_64_Hdr + TLP_Dig}
  }
  
  TLP_CplD_Hdr_Szs = {
      0: DLLP_Hdr + TLP_Hdr + TLP_CplD_Hdr,
      1: DLLP_Hdr + TLP_Hdr + TLP_CplD_Hdr + TLP_Dig
  }
  
  # SIze of a sending a MSI
  MSI_SIZE = 4
  
  class Cfg():
      """A glorified struct to represent a specific PCIe device configuration"""
  
      def __init__(self, version, lanes, addr, ecrc,
                   mps, mrrs, rcb, rcb_chunks=False):
          """Use this class as a struct for the PCI configuration
          @param version: String, 'gen1', 'gen2' 'gen3', 'gen4', 'gen5'
          @param lanes: String, 'x1', 'x2', 'x4, 'x8', 'x16', 'x32'
          @param addr: either 32 or 64. What type of addresses to use
          @param ecrc: either 0 or 1, indicating if ECRC was configured
          @param mps: Maximum Payload Size configured
          @param mrss: Maximum Read Request Size configured
          @param rcb: Read Completion Boundaries
          @param rcb_chunks: Boolean, are read requests chopped into RCB or MPS
          """
          if version not in Vers:
              raise Exception("Unknown PCIe version: %s" % version)
          self.version = version
          if lanes not in Laness:
              raise Exception("Unknown Lane configuration: %s" % lanes)
          self.lanes = lanes
          if addr not in [32, 64]:
              raise Exception("Unknown address lenght: %d" % addr)
          self.addr = addr
          if ecrc not in [0, 1]:
              raise Exception("Unknown ECRC value: %d" % ecrc)
          self.ecrc = ecrc
          if mps not in MPSs:
              raise Exception("Unknown MPS value: %d" % mps)
          self.mps = mps
          if mrrs not in MRRSs:
              raise Exception("Unknown MRRS value: %d" % mps)
          self.mrrs = mrrs
          if rcb not in RCBs:
              raise Exception("Unknown RCB value: %d" % mps)
          self.rcb = rcb
          self.rcb_chunks = rcb_chunks
  
          # derive Header Sizes for Memory Write, Read and Completion
          self.TLP_MWr_Hdr_Sz = TLP_MWr_Hdr_Szs[addr][ecrc]
          self.TLP_MRd_Hdr_Sz = TLP_MRd_Hdr_Szs[addr][ecrc]
          self.TLP_CplD_Hdr_Sz = TLP_CplD_Hdr_Szs[ecrc]
  
          self.TLP_bw = TLP_bw[version][lanes][mps]
          self.RAW_bw = Raw[version][lanes]
  
      def pp(self):
          """Print the configuration"""
          print("PCIe configuration: Version=%s, Lanes=%s" % (self.version, self.lanes))
          print("                    mps=%s, mrrs=%s, rcb=%s, rcb_chunks=%s" % \
                (self.mps, self.mrrs, self.rcb, self.rcb_chunks))
          print("                    addr=%d ecrc=%d" % (self.addr, self.ecrc))
          print("                    => TLP BW=%.2f Gb/s" % (self.TLP_bw))
  
  
  ## Functions to calculate PCIe bandwidth for different PCIe
  ## configurations and operations.
  
  # Direction of Transfer from the device perspective
  DIR_RX = 1
  DIR_TX = 2
  DIR_BOTH = DIR_RX | DIR_TX
  
  class BW_Spec():
      """
      All functions take a object of this class as a argument. It
      specifies bandwidth for the configuration.  Bandwidth is either
      RAW TLP level bandwidth and the function will work out a effective
      bandwidth which can be achieved with it.  Effective bandwidth is
      the ratio of data bytes versus the bytes actually transmitted
      (data bytes + overhead).
  
      Or, the bandwidth specification dictates a effective bandwidth to be
      achieved and the model will work out the required raw bandwidth.
  
      Bandwidth is specified in both direction, RX and TX, from the device
      perspective.
  
      This is basically a glorified struct.
      """
  
      BW_RAW = 0
      BW_EFF = 1
  
      def __init__(self, rx_bw=0.0, tx_bw=0.0, bw_type=0):
          self.rx_bw = rx_bw
          self.tx_bw = tx_bw
          if bw_type not in [self.BW_RAW, self.BW_EFF]:
              raise Exception("Unknown BW type")
          self.type = bw_type
          return
  
  class BW_Res():
      """
      A Bandwidth result object returned by all functions. Contains the
      required Raw TLP bandwidth and the effective bandwidth in each
      direction.  RX and TX are always seen from the PCIe peer
      initiating the transfer.
  
      Another glorified struct
      """
      def __init__(self, rx_raw, rx_eff, tx_raw, tx_eff):
          self.rx_raw = rx_raw
          self.rx_eff = rx_eff
          self.tx_raw = tx_raw
          self.tx_eff = tx_eff
  
  if __name__ == '__main__':
      # Print out some useful data
      for mps in [128, 256]:
          for ver in Vers:
              print("PCIe Version:", ver)
              print("Lanes Phys BW (Gb/s) Data BW (Gb/s) MPS=%d" % mps)
              for lanes in Laness:
                  print("%5s %6.2f %6.2f" % \
                        (lanes, Raw[ver][lanes],
                         TLP_bw[ver][lanes][mps]))
  