I always wanted to write scapy-like framework for PCIE TLP serialization and This is a quick and dirty prototype I wrote at 1 AM. So, Don’t judge me.

The final output of the serializer would be as follows. basically, something similar to scapy or wireshark breakdown of header fields.

name:memwr32 size:96 0000000000000000000000100000000000000000100000001100000001111111100000000000000010010000000000000
    name:hdr size:32 000000000000000000000010000000000
        name:RESERVED size:1 0
        name:FMT size:2 00
        name:TYPE size:5 00000
        name:RESERVED size:1 0
        name:TC size:3 000
        name:RESERVED size:4 0000
        name:TD size:1 0
        name:EP size:1 0
        name:ATTR size:2 00
        name:AT size:2 00
        name:LENGTH size:10 10000000000
    name:requestID size:16 0000000100000001
        name:BUS size:8 00000001
        name:DEVICE size:4 0000
        name:FUNCTION size:4 0001
    name:TAG size:8 10000000
    name:LASTDWBE size:4 1111
    name:STDWBE size:4 1111
    name:ADDR size:32 00000000000000010010000000000000

The PCIE TLP (Tranaction Layer Packet) header depends on type of transaction: Memory, I/O, Configuration, and Messages. Also, there is routing type which depends on type of transaction:

  • Address: used with Memory and IO
  • ID: used with configuration
  • implicit: used with messages

This the header format for Memory Read 32bit address.

Example image

I wanted scalable infrastructure to create difference types of headers. So, I wrote Field base-class which contains value attribute

 53 class Field:
 54     def __init__(self, name, size, value=0x0):
 55         self.name = name
 56         self.size = size
 57         self.value =  value

The attribute value is important because it can work raw int value or parent field containing sub-fields

 79     def binary(self):
 80         v = ""
 81         if type(self.value) is list:
 82             for f in self.value:
 83                 v += f.binary()
 84         else:
 85             v = f'{self.value:0{self.size}b}'
 86         return v

This is the full code for Memory 32bit Address Header.

from enum import Enum
import collections

class Field:
    def __init__(self, name, size, value=0x0):
        self.name = name
        self.size = size
        self.value =  value

    def _printf(self, lvl):
        space = lvl * "    "
        print(f"{space}name:{self.name} size:{self.size} {self.binary()}")
        if type(self.value) is list:
            for i in self.value:
                i._printf(lvl+1)
    def printf(self):
        self._printf(0)

    def binary(self):
        v = ""
        if type(self.value) is list:
            for f in self.value:
                v += f.binary()
        else:
            v = f'{self.value:0{self.size}b}'
        return v

    def __getattr__(self,name):
        for i in self.value:
            if i.name == name:
                return i
        raise AttributeError("can't get attribute")


class TLPHdr(Field):
    def __init__(self):
        super(TLPHdr, self).__init__("hdr", 4 * 8, value=[])
        self.value.append(Field("RESERVED", 1, 0x0))
        self.value.append(Field("FMT", 2))
        self.value.append(Field("TYPE", 5))
        self.value.append(Field("RESERVED", 1, 0x0))
        self.value.append(Field("TC", 3))
        self.value.append(Field("RESERVED", 4, 0x0))
        self.value.append(Field("TD", 1))
        self.value.append(Field("EP", 1))
        self.value.append(Field("ATTR", 2))
        self.value.append(Field("AT", 2))
        self.value.append(Field("LENGTH", 10))

class RequestID(Field):
    def __init__(self):
        super(RequestID, self).__init__("requestID", 2 * 8, value=[])
        self.value.append(Field("BUS", 8, 0x0))
        self.value.append(Field("DEVICE", 4, 0x0))
        self.value.append(Field("FUNCTION", 4, 0x0))

class MemWr32(Field):
    def __init__(self, bus, device, function, tag, addr, length):
        super(MemWr32, self).__init__("memwr32", 4 * 8 * 3,value=[])
        self.value.append(TLPHdr())
        self.value.append(RequestID())
        self.value.append(Field("TAG", 8))
        self.value.append(Field("LASTDWBE", 4))
        self.value.append(Field("STDWBE", 4))
        self.value.append(Field("ADDR", 32))

        self.TAG.value      = tag
        self.LASTDWBE.value = int ("1111", base=2)
        self.STDWBE.value   = int ("1111", base=2)
        self.ADDR.value     = addr

        self.hdr.FMT.value      = int ("00", base=2)
        self.hdr.TYPE.value     = int ("00000", base=2)
        self.hdr.TC.value       = int ("0", base=2)
        self.hdr.EP.value       = int ("0", base=2)
        self.hdr.ATTR.value     = int ("0", base=2)
        self.hdr.AT.value       = int ("0", base=2)
        self.hdr.LENGTH.value   = int(length/4)

        self.requestID.BUS.value        = bus
        self.requestID.DEVICE.value     = device
        self.requestID.FUNCTION.value   = function

m  = MemWr32( bus=1, device=0,function=1, tag=0x80, addr=0x12000, length=4096)
m.printf()