To create a PCIe endpoint:
"pcie/common.dml"
pcie_endpoint
to the top level of your device code.init_val
for the registers device_id
and vendor_id
in the
pcie_config
bank.init_val
of
the capabilities_ptr
register and add capability templates for the
capabilities present.memory_base_address
template, setting the map_obj parameter to
the application logic banks you created in the previous step.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
}
To create a Multi-Function PCIe endpoint:
"pcie/common.dml"
pcie_multifunction_endpoint
to the top level of your device code.type_0_bank
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
}
"pcie/common.dml"
pcie_downstream_port
to the downstream ports and pcie_upstream_port
to the
upstream port.init_val
for the registers device_id
and vendor_id
in the
pcie_config
bank of each subdevice.init_val
of the
capabilities_ptr
register and add capability templates for the
capabilities present.memory_base_address
template, setting the map_obj parameter to the application
logic banks you've created.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:
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
"pcie/common.dml"
pcie_bridge
template to the top level of your device code.pcie_root_port
to all of them.downstream_port
object created
by the pcie_bridge
template.init_val
for the registers device_id
and vendor_id
in the
pcie_config
bank of each downstream port.init_val
of the
capabilities_ptr
register and add capability templates for the
capabilities present.downstream_port
of the
RC.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:
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);
}
}
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.