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_64 or the memory_base_address_32 template.
To map in the application logic is done by setting the map_obj
parameter to the banks you created in the previous step.
Sizing the BAR is done by setting the size_bits parameter
in the Base Address Register.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 @ 0x10 is memory_base_address_64 {
// 4k BAR allocation
param size_bits = 12;
param map_obj = app0.obj;
}
register bar2 @ 0x18 is memory_base_address_64 {
// 32 MB BAR allocation
param size_bits = 25;
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_bankdml 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 @ 0x10 is memory_base_address_64 {
// 32 MB BAR allocation
param size_bits = 25;
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 @ 0x10 is memory_base_address_64 {
// 16 MB BAR allocation
param size_bits = 24;
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.cap in registers bank_config.prefetchable.base and
bank_config.prefetchable.limit to make it 32-bit.memory_base_address_64
or the memory_base_address_32 template.
To map in the application logic is done by setting the map_obj
parameter to the banks you created in the previous step.
Sizing the BAR is done by setting the size_bits parameter
in the Base Address Register.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 @ 0x10 is memory_base_address_64 {
// 8 MB BAR allocation
param size_bits = 23;
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.cap in registers bank_config.prefetchable.base and
bank_config.prefetchable.limit to make it 32-bit.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;
param use_io_memory = true;
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 @ 0x10 is memory_base_address_64 {
param size_bits = 12;
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.