Skip to content

Provide template for an efficient private RAM #379

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions lib/1.4/utility.dml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import "simics/devs/signal.dml";
import "simics/devs/ram.dml";
import "simics/devs/memory-space.dml";
import "simics/devs/translator.dml";
import "simics/model-iface/direct-memory.dml";
import "simics/model-iface/transaction.dml";
import "simics/simulator/conf-object.dml";

template _reg_or_field {
param is_register : bool;
Expand Down Expand Up @@ -1393,6 +1395,157 @@ template map_target is (connect, _qname) {
}
}

/**
### ram

This template can be instantiated on a `port`, `device`, `bank` or
`subdevice` object for an efficient implementation of a private RAM
area. This is implemented by automatically creating a separate Simics object
of class `ram`, with an image attached to it, and using the `direct_memory`
interface to access the image efficiently. This gives a small memory
footprint and efficient checkpointing; this can make a visible impact for
internal memories that are several megabytes in size.

This template adds an implementation of the `direct_memory_update` interface
to the instantiating object. Because of current limitations, the `queue`
attribute of the corresponding Simics object must be configured before the
first RAM access.

The template requires a single integer parameter `size`, which must be set
to a multiple of 8192.

The template provides the following methods:
*/
typedef struct {
direct_memory_handle_t handle;
uint8 *data;
} _ram_page_t;
template ram {
param size;
connect ram is (init_as_subobj, destroy) {
Comment on lines +1423 to +1425
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A better design is probably to create a template for a private RAM connect, without DMI support, and then create a separate template which adds DMI magic to all private RAMs within a bank/subdev/port

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this adds some pain: the use of get_user_data requires that you have one separate direct_memory_update ports for each RAM; if we want a shared impl, then we instead need to keep a private hashtable from handles to pages.

param documentation = "dm";
interface direct_memory;
interface ram;
param page_size = 8192;
param size = parent.size;
#if (size % page_size != 0) { error "size must be a multiple of 8192"; }
param classname = "ram";
session _ram_page_t pages[size / page_size];
method init() {
default();
SIM_set_attribute_default(this.obj, "image", SIM_make_attr_nil());
SIM_set_attribute_default(
this.obj, "self_allocated_image_size", SIM_make_attr_uint64(size));
}

method invalidate(direct_memory_handle_t handle) {
local int page = cast(direct_memory.get_user_data(handle), uintptr_t);
pages[page].data = NULL;
}
method request(uint64 offs) -> (uint8 *) {
local int page = offs / page_size;
if (pages[page].data == NULL) {
if (pages[page].handle == NULL) {
pages[page].handle = direct_memory.get_handle(
parent.obj, 0, page * page_size, page_size);
direct_memory.set_user_data(
pages[page].handle, cast(cast(page, uintptr_t), void *));
}
local direct_memory_t mem = direct_memory.request(
pages[page].handle, Sim_Access_Read | Sim_Access_Write,
Sim_Access_Read | Sim_Access_Write | Sim_Access_Execute);
pages[page].data = mem.data;
}
return pages[page].data + offs % page_size;
}

method destroy() {
for (local int i = 0; i < pages.len; i++) {
if (pages[i].handle != NULL) {
direct_memory.release(pages[i].handle);
}
}
}

method read(int64 offs, buffer_t buf) {
assert offs + buf.len <= size;
local int page_mask = page_size - 1;
if (buf.len <= page_size - (offs & page_mask)) {
memcpy(buf.data, request(offs), buf.len);
} else {
local size_t copied = page_size - (offs & page_mask);
memcpy(buf.data, request(offs), copied);
while (buf.len - copied > page_size) {
memcpy(buf.data + copied, request(offs + copied), page_size);
copied += page_size;
}
memcpy(buf.data + copied, request(offs + copied), buf.len - copied);
}
}
}

/**
* `get_u8(int64 offs) -> (uint8)`

Return a single byte from a given offset.
*/
method get_u8(uint64 offs) -> (uint8) {
assert offs < ram.size;
return *ram.request(offs);
}

/**
* `get_u8(int64 offs) -> (uint8)`

Read a 64-bit little-endian integer from a given offset
*/
method get_u64_le(uint64 offs) -> (uint64) {
local uint64_le_t ret;
ram.read(offs, {.data=cast(&ret, uint8 *), .len=8});
return ret;
}
/**
* `set_u8(int64 offs) -> (uint8)`

Update a single byte at a given offset.
*/
method set_u8(uint64 offs, uint8 val) {
assert offs < ram.size;
*ram.request(offs) = val;
}

implement direct_memory_update {
method release(conf_object_t *target, direct_memory_handle_t handle,
direct_memory_ack_id_t id) {
assert target == ram.obj;
ram.invalidate(handle);
local int page = cast(
ram.direct_memory.get_user_data(handle), uintptr_t);
ram.pages[page].handle = NULL;
ram.direct_memory.ack(id);
}
method update_permission(conf_object_t *target,
direct_memory_handle_t handle,
access_t lost_access,
access_t lost_permission,
access_t lost_inhibit,
direct_memory_ack_id_t id) {
assert target == ram.obj;
ram.invalidate(handle);
ram.direct_memory.ack(id);
}
method conflicting_access(conf_object_t *target,
direct_memory_handle_t handle,
access_t conflicting_permission,
direct_memory_ack_id_t id) {
assert target == ram.obj;
ram.invalidate(handle);
ram.direct_memory.ack(id);
}
}
}


/**
## Signal related templates

Expand Down
29 changes: 29 additions & 0 deletions test/1.4/lib/T_ram.dml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
© 2025 Intel Corporation
SPDX-License-Identifier: MPL-2.0
*/
dml 1.4;

device test;

import "utility.dml";

subdevice ram {
is ram;
param size = 65536;
}

attribute trigger_test is bool_attr {
// cannot run from init: RAM access requires queue to be set
method set(attr_value_t _) throws {
// integer across page boundary
assert ram.ram.page_size == 0x2000;
ram.set_u8(0x1ffe, 0x11);
ram.set_u8(0x1fff, 0x22);
ram.set_u8(0x2000, 0x33);
ram.set_u8(0x2001, 0x44);
assert ram.get_u64_le(0x1ff8) == 0x2211_0000_0000_0000;
assert ram.get_u64_le(0x2000) == 0x4433;
assert ram.get_u64_le(0x1ffe) == 0x44332211;
}
}
11 changes: 11 additions & 0 deletions test/1.4/lib/T_ram.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# © 2025 Intel Corporation
# SPDX-License-Identifier: MPL-2.0

clock = SIM_create_object('clock', 'clock', freq_mhz=1)
# implementer of direct_memory_update must have a clock
obj.ram.queue = clock
obj.trigger_test = True

b=simics.buffer_t(100)
obj.ram.ram.iface.ram.read(None, 0, b, 0)
print(bytes(b))