PCIe Modeling Library High level design
PCIe Modeling Library  / 

Quickstart

Endpoint

To create a PCIe endpoint:

Figure 1. Simple Endpoint
dml 1.4;
device endpoint;
import "pcie/common.dml";

is pcie_endpoint;

bank pcie_config {
    register vendor_id { param init_val = 0x8086; }
    register device_id { param init_val = 0x4042; }
    register bar0 size 8 @ 0x10 is memory_base_address {
        // size 8 makes this a 64-bit BAR
        param map_obj = app0.obj;
    }
    register bar2 size 4 @ 0x18 is memory_base_address {
        param map_obj = app2.obj;
    }
    register capabilities_ptr {
        // points to the base address of the first capability
        param init_val = 0x40;
    }

    group ssid is ssid_capability {
        param base = 0x40;
        param next_ptr = 0x60;
        register ssvid { param init_val = 0x8086; }
    }

    group msi is msi_capability {
        param base = 0x60;
        param next_ptr = 0x0;
        param is_64bit_capable = true;
        param is_pvm_capable = true;
        param is_emd_capable = true;
        param num_vectors = 2;
    }
}

bank app0 {
    // defines application logic tied to BAR0
    register intr size 1 @ 0x0 is write {
        method write(uint64 value) {
            pcie_config.msi.raise(0);  // raise MSI vector 0 on write
        }
    }
}

bank app2 {
    // application logic tied to BAR2 goes here
}

Multi-Function Endpoint

To create a Multi-Function PCIe endpoint:

Figure 2. Simple Multi-Function Endpoint
dml 1.4;
device endpoint;
import "pcie/common.dml";

is pcie_multifunction_endpoint;

bank f0 is type_0_bank {
    param function = 0;

    register vendor_id { param init_val = 0x8086; }
    register device_id { param init_val = 0x4042; }
    register bar0 size 8 @ 0x10 is memory_base_address {
        param map_obj = app0.obj;
    }
    register capabilities_ptr { param init_val = 0x40; }

    group ssid is ssid_capability {
        param base = 0x40;
        param next_ptr = 0x60;
        register ssvid { param init_val = 0x8086; }
    }

    group msi is msi_capability {
        param base = 0x60;
        param next_ptr = 0x0;
        param is_64bit_capable = true;
        param is_pvm_capable = true;
        param is_emd_capable = true;
        param num_vectors = 2;
    }
}
bank f1 is type_0_bank {
    param function = 1;

    register vendor_id { param init_val = 0x8086; }
    register device_id { param init_val = 0x4043; }

    register bar0 size 4 @ 0x10 is memory_base_address {
        param map_obj = app2.obj;
    }
}

bank app0 {
    // defines application logic tied to f0.BAR0
}

bank app2 {
    // application logic tied to f1.BAR0 goes here
}

Switch

Here is a simple Switch example with one upstream port and four downstream ports. The upstream port has MSI-X capability and built-in application logic tied to BAR0:

Figure 3. Simple Switch
dml 1.4;
device pcie_switch;
import "pcie/common.dml";

subdevice usp "Upstream Port" {
    is pcie_upstream_port;
    bank pcie_config {
        register vendor_id { param init_val = 0x8086; }
        register device_id { param init_val = 0x4042; }
    }
    register bar0 size 4 @ 0x10 is memory_base_address {
        param map_obj = app0.obj;
    }

    register capabilities_ptr { param init_val = 0x40; }

    group msix is msix_capability {
        param base = 0x40;
        param next_ptr = 0x0;
        param num_vectors = 32;
        param table_offset_bir = 0x1000;
        param pba_offset_bir = 0x5000;
        param data_bank = msix_data;
    }
}

subdevice dsp[i < 3] "Downstream Port" {
    is pcie_downstream_port;
    is post_init;
    method post_init() {
        // connect this port to the internal bus of the upstream port
        // the second argument is the DeviceID, i.e. bus/device/function
        // where the device-number is bits 7:3
        pcie_device.connected(usp.downstream_port.obj, 1 << 3);
    }
    bank pcie_config {
        register vendor_id { param init_val = 0x8086; }
        register device_id { param init_val = 0x4043; }
    }
}

bank app0 {
    // application logic tied to BAR0 in the upstream port goes here
}

bank msix_data is msix_table;  // storage for MSI-X table and pba

Root Complex

Here is an example root complex with one root port and two integrated endpoints, one implemented directly in the code and one that is created as a subobject, using another class:

Figure 4. Simple Root Complex
dml 1.4;
device root_complex;
import "pcie/common.dml";

is pcie_bridge;

subdevice rp "PCIe Root Port" {
    is pcie_root_port;
    is post_init;
    method post_init() {
        // connect this port to the internal bus of the RC
        // the second argument is the DeviceID, i.e. bus/device/function
        // where the device-number is bits 7:3
        pcie_device.connected(dev.downstream_port.obj, 0);
    }
    bank pcie_config {
        register vendor_id { param init_val = 0x8086; }
        register device_id { param init_val = 0x4043; }
        register class_code { param init_val = 0x20000; }
    }
}

subdevice iep_A "Integrated Endpoint A" {
    is pcie_endpoint;
    is hreset;
    is post_init;
    method post_init() {
        // connect this integrated endpoint to the internal bus of the RC
        // the second argument is the DeviceID, i.e. bus/device/function
        // where the device-number is bits 7:3
        pcie_device.connected(dev.downstream_port.obj, 1 << 3);
    }
    bank pcie_config {
        register vendor_id { param init_val = 0x8086; }
        register device_id { param init_val = 0x4044; }

        register bar0 size 4 @ 0x10 is memory_base_address {
            param map_obj = app0.obj;
        }

        register capabilities_ptr { param init_val = 0x40; }

        group msix is msix_capability {
            param base = 0x40;
            param next_ptr = 0x0;
            param num_vectors = 32;
            param table_offset_bir = 0x1000;
            param pba_offset_bir = 0x5000;
            param data_bank = msix_data;
        }
    }

    bank app0 {
        // application logic tied to BAR0 in the integrated endpoint goes here
    }

    bank msix_data is msix_table;  // storage for MSI-X table and pba
}

connect iep_B "Integrated Endpoint B"{
    is post_init;
    is init_as_subobj;
    param classname = "some_class";
    interface pcie_device;
    method post_init() {
        // connect this integrated endpoint to the internal bus of the RC
        // the second argument is the DeviceID, i.e. bus/device/function
        // where the device-number is bits 7:3
        pcie_device.connected(dev.downstream_port.obj, 2 << 3);
    }
}

Sample Devices

There are some sample PCIe devices distributed in Simics base. Currently, this includes sample-pcie-device, which is a simple PCIe device with some capabilities and a memory BAR mapped register bank. There is also the standard-pcie-switch, which also has a component wrapper standard-pcie-switch-comp. It's a switch with 4 downstream ports. All ports in implement 3 capabilities. This switch can be used in any platform that supports PCIe to provide the ability to expand the PCIe hierarchy.

PCIe Modeling Library High level design