diff --git a/CHANGELOG.md b/CHANGELOG.md index ae74f42..5322e4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add support for hardware counters (HWC) in nOS-V. + +### Changed + +- Increase nOS-V model version to 2.6.0. + ### Fixed - Fix a bug in ovniemu when loading loom CPUs from multiple threads. diff --git a/doc/user/emulation/events.md b/doc/user/emulation/events.md index c8737e6..d1b4ab9 100644 --- a/doc/user/emulation/events.md +++ b/doc/user/emulation/events.md @@ -1,7 +1,7 @@ # Emulator events This is a exhaustive list of the events recognized by the emulator. -Built on May 2 2025. +Built on Jul 28 2025. ## Model nanos6 @@ -633,7 +633,7 @@ List of events for the model *tampi* with identifier **`T`** at version `1.0.0`: ## Model nosv -List of events for the model *nosv* with identifier **`V`** at version `2.5.1`: +List of events for the model *nosv* with identifier **`V`** at version `2.6.0`:
VTc(u32 taskid, u32 typeid)
creates task %{taskid} with type %{typeid}
@@ -761,4 +761,6 @@ List of events for the model *nosv* with identifier **`V`** at version `2.5.1`:
sets progress state to Resting
VPa
sets progress state to Absorbing
+
VWC+(i64 value)
+
set hardware counters (first %{value})
diff --git a/doc/user/emulation/versions.md b/doc/user/emulation/versions.md index ed5101e..6b886ba 100644 --- a/doc/user/emulation/versions.md +++ b/doc/user/emulation/versions.md @@ -45,6 +45,8 @@ Track changes in emulator model versions. ## nOS-V +- nosv 2.6.0 + - Add support for hardware counters event `VWC`. - nosv 2.5.1 - Remove task complete callback from between `VTx` and `VTe` events. - nosv 2.5.0 diff --git a/src/emu/CMakeLists.txt b/src/emu/CMakeLists.txt index 2ed5a00..fe3d82d 100644 --- a/src/emu/CMakeLists.txt +++ b/src/emu/CMakeLists.txt @@ -54,8 +54,9 @@ add_library(emu STATIC nanos6/event.c nanos6/breakdown.c nosv/breakdown.c - nosv/setup.c nosv/event.c + nosv/hwc.c + nosv/setup.c nodes/setup.c nodes/event.c mpi/setup.c diff --git a/src/emu/emu_prv.h b/src/emu/emu_prv.h index 63174c5..91e03a2 100644 --- a/src/emu/emu_prv.h +++ b/src/emu/emu_prv.h @@ -38,7 +38,9 @@ enum emu_prv_types { PRV_OPENMP_BREAKDOWN = 53, PRV_OVNI_MARK = 100, /* User marks [100, 200) */ - PRV_RESERVED = 200, + PRV_NOSV_HWC = 200, + /* nOS-V HWC [200, 300) */ + PRV_RESERVED = 300, }; #endif /* EMU_PRV_H */ diff --git a/src/emu/nosv/event.c b/src/emu/nosv/event.c index 4139187..566ba2a 100644 --- a/src/emu/nosv/event.c +++ b/src/emu/nosv/event.c @@ -596,6 +596,8 @@ process_ev(struct emu *emu) return pre_task(emu); case 'Y': return pre_type(emu); + case 'W': + return hwc_event(emu); default: err("unknown nOS-V event category"); return -1; diff --git a/src/emu/nosv/hwc.c b/src/emu/nosv/hwc.c new file mode 100644 index 0000000..1b6a79d --- /dev/null +++ b/src/emu/nosv/hwc.c @@ -0,0 +1,545 @@ +#include "hwc.h" + +#include "chan.h" +#include "cpu.h" +#include "emu.h" +#include "emu_ev.h" +#include "emu_prv.h" +#include "inttypes.h" +#include "nosv_priv.h" +#include "ovni.h" +#include "parson.h" +#include "proc.h" +#include "pv/cfg_file.h" +#include "pv/pcf.h" +#include "pv/prv.h" +#include "pv/pvt.h" +#include "thread.h" +#include "track.h" +#include "uthash.h" +#include + +static int +parse_hwc(struct nosv_hwc_emu *hwc_emu, const char *indexstr, JSON_Value *hwcval) +{ + errno = 0; + char *endptr = NULL; + size_t index = (size_t) strtol(indexstr, &endptr, 10); + + if (errno != 0 || endptr == indexstr || endptr[0] != '\0') { + err("failed to parse hwc index: %s", indexstr); + return -1; + } + + if (index >= 100) { + err("hwc index should be in [0, 100) range: %zd", index); + return -1; + } + + if (index >= hwc_emu->n) { + err("hwc index %zd exceeds allocated counters %zd", + index, hwc_emu->n); + return -1; + } + + JSON_Object *hwc = json_value_get_object(hwcval); + if (hwc == NULL) { + err("json_value_get_object() failed"); + return -1; + } + + const char *name = json_object_get_string(hwc, "name"); + if (name == NULL) { + err("json_object_get_string() for name failed"); + return -1; + } + + if (hwc_emu->name[index] == NULL) { + hwc_emu->name[index] = strdup(name); + if (hwc_emu->name[index] == NULL) { + err("strdup failed"); + return -1; + } + dbg("got hwc with index %zd and name %s", index, hwc_emu->name[index]); + } else if (strcmp(hwc_emu->name[index], name) != 0) { + err("hwc at %zd already defined as %s", + index, hwc_emu->name[index]); + return -1; + } + + return 0; +} + +static int +scan_thread(struct nosv_hwc_emu *hwc_emu, struct thread *t) +{ + JSON_Object *obj = json_object_dotget_object(t->meta, "nosv.hwc"); + + /* No HWC in this thread */ + if (obj == NULL) + return 0; + + size_t n = json_object_get_count(obj); + + /* Ignore empty dictionaries */ + if (n == 0) + return 0; + + if (hwc_emu->n == 0) { + hwc_emu->name = calloc(n, sizeof(char *)); + hwc_emu->n = n; + } else if (hwc_emu->n != n) { + /* We have a mismatch */ + err("thread %s defines %zd hardware counters, but emu already has %zd", + t->id, n, hwc_emu->n); + return -1; + } + + for (size_t i = 0; i < n; i++) { + const char *indexstr = json_object_get_name(obj, i); + if (indexstr == NULL) { + err("json_object_get_name failed"); + return -1; + } + JSON_Value *hwcval = json_object_get_value_at(obj, i); + if (hwcval == NULL) { + err("json_object_get_value_at failed"); + return -1; + } + + if (parse_hwc(hwc_emu, indexstr, hwcval) != 0) { + err("cannot parse HWC"); + return -1; + } + } + + return 0; +} + +static int +create_thread_chan(struct nosv_hwc_emu *emu, struct bay *bay, struct thread *th) +{ + struct nosv_thread *nosv_thread = EXT(th, 'V'); + struct nosv_hwc_thread *t = &nosv_thread->hwc; + + /* Create as many channels as required */ + t->chan = calloc(emu->n, sizeof(struct chan)); + if (t->chan == NULL) { + err("calloc failed:"); + return -1; + } + + t->n = emu->n; + + for (size_t i = 0; i < t->n; i++) { + struct chan *ch = &t->chan[i]; + chan_init(ch, CHAN_SINGLE, "nosv.thread%"PRIi64".hwc%zd", + th->gindex, i); + + /* Allow duplicates, we can emit the same HWC value twice */ + chan_prop_set(ch, CHAN_ALLOW_DUP, 1); + + if (bay_register(bay, ch) != 0) { + err("bay_register failed"); + return -1; + } + } + + /* Setup tracking */ + t->track = calloc(t->n, sizeof(struct track)); + if (t->track == NULL) { + err("calloc failed:"); + return -1; + } + + for (size_t i = 0; i < t->n; i++) { + struct track *track = &t->track[i]; + /* For now only tracking to active thread is supported */ + if (track_init(track, bay, TRACK_TYPE_TH, TRACK_TH_ACT, + "nosv.thread%"PRIi64".hwc%zd", + th->gindex, i) != 0) { + err("track_init failed"); + return -1; + } + } + + return 0; +} + +static int +init_cpu(struct nosv_hwc_emu *emu, struct bay *bay, struct cpu *cpu) +{ + struct nosv_cpu *nosv_cpu = EXT(cpu, 'V'); + struct nosv_hwc_cpu *c = &nosv_cpu->hwc; + + /* Setup tracking */ + c->track = calloc(emu->n, sizeof(struct track)); + if (c->track == NULL) { + err("calloc failed:"); + return -1; + } + + c->n = emu->n; + + for (size_t i = 0; i < c->n; i++) { + struct track *track = &c->track[i]; + /* For now only tracking to running thread is supported */ + if (track_init(track, bay, TRACK_TYPE_TH, TRACK_TH_RUN, + "nosv.cpu%"PRIi64".hwc%zd", + cpu->gindex, i) != 0) { + err("track_init failed"); + return -1; + } + } + + return 0; +} + +int +hwc_create(struct emu *emu) +{ + struct nosv_emu *nosv_emu = EXT(emu, 'V'); + struct nosv_hwc_emu *hwc_emu = &nosv_emu->hwc; + + for (struct thread *th = emu->system.threads; th; th = th->gnext) { + if (scan_thread(hwc_emu, th) != 0) { + err("scan_thread failed"); + return -1; + } + } + + /* Early exit if no counters found */ + if (hwc_emu->n == 0) { + dbg("no hwc counters found"); + return 0; + } + + /* Create a buffer to do aligned reads */ + hwc_emu->values = calloc(hwc_emu->n, sizeof(int64_t)); + if (hwc_emu->values == NULL) { + err("calloc failed:"); + return -1; + } + + /* Once we know how many HWC we have, allocate the channels for threads + * and CPUs. */ + + for (struct thread *th = emu->system.threads; th; th = th->gnext) { + if (create_thread_chan(hwc_emu, &emu->bay, th) != 0) { + err("create_thread_chan failed"); + return -1; + } + } + + for (struct cpu *cpu = emu->system.cpus; cpu; cpu = cpu->next) { + if (init_cpu(hwc_emu, &emu->bay, cpu) != 0) { + err("init_cpu failed"); + return -1; + } + } + + return 0; +} + +static int +connect_thread_prv(struct bay *bay, struct thread *systh, struct prv *prv) +{ + struct nosv_thread *nosv_thread = EXT(systh, 'V'); + struct nosv_hwc_thread *t = &nosv_thread->hwc; + + for (size_t i = 0; i < t->n; i++) { + struct track *track = &t->track[i]; + struct chan *ch = &t->chan[i]; + struct chan *sel = &systh->chan[TH_CHAN_STATE]; + + /* Connect the input and sel channel to the mux */ + if (track_connect_thread(track, ch, sel, 1) != 0) { + err("track_connect_thread failed"); + return -1; + } + + /* Then connect the output of the tracking module to the prv + * trace for the current thread */ + struct chan *out = track_get_output(track); + long row = (long) systh->gindex; + long flags = PRV_SKIPDUPNULL | PRV_ZERO; + long prvtype = (long) (PRV_NOSV_HWC + i); + if (prv_register(prv, row, prvtype, bay, out, flags)) { + err("prv_register failed"); + return -1; + } + } + + return 0; +} + +static int +init_pcf(struct emu *emu, struct pcf *pcf) +{ + struct nosv_emu *nosv_emu = EXT(emu, 'V'); + struct nosv_hwc_emu *hwc_emu = &nosv_emu->hwc; + + for (size_t i = 0; i < hwc_emu->n; i++) { + long prvtype = (long) (PRV_NOSV_HWC + i); + const char *name = hwc_emu->name[i]; + struct pcf_type *pcftype = pcf_add_type(pcf, (int) prvtype, name); + if (pcftype == NULL) { + err("pcf_add_type failed"); + return -1; + } + } + + return 0; +} + +static int +connect_thread(struct emu *emu) +{ + struct pvt *pvt = recorder_find_pvt(&emu->recorder, "thread"); + if (pvt == NULL) { + err("cannot find thread pvt"); + return -1; + } + + /* Connect thread channels to PRV */ + struct prv *prv = pvt_get_prv(pvt); + for (struct thread *t = emu->system.threads; t; t = t->gnext) { + if (connect_thread_prv(&emu->bay, t, prv) != 0) { + err("connect_thread_prv failed"); + return -1; + } + } + + /* Init thread PCF */ + struct pcf *pcf = pvt_get_pcf(pvt); + if (init_pcf(emu, pcf) != 0) { + err("init_pcf failed"); + return -1; + } + + return 0; +} + +static int +connect_cpu_prv(struct emu *emu, struct cpu *syscpu, struct prv *prv) +{ + struct nosv_emu *nosv_emu = EXT(emu, 'V'); + struct nosv_hwc_emu *hwc_emu = &nosv_emu->hwc; + + struct nosv_cpu *nosv_cpu = EXT(syscpu, 'V'); + struct nosv_hwc_cpu *hwc_cpu = &nosv_cpu->hwc; + + for (size_t i = 0; i < hwc_emu->n; i++) { + struct track *track = &hwc_cpu->track[i]; + struct chan *sel = cpu_get_th_chan(syscpu); + + int64_t nthreads = (int64_t) emu->system.nthreads; + if (track_set_select(track, sel, NULL, nthreads) != 0) { + err("track_select failed"); + return -1; + } + + /* Add each thread as input */ + for (struct thread *t = emu->system.threads; t; t = t->gnext) { + struct nosv_thread *nosv_thread = EXT(t, 'V'); + struct nosv_hwc_thread *hwc_thread = &nosv_thread->hwc; + + /* Use the input thread directly */ + struct chan *inp = &hwc_thread->chan[i]; + + if (track_set_input(track, t->gindex, inp) != 0) { + err("track_add_input failed"); + return -1; + } + } + + /* Then connect the output of the tracking module to the prv + * trace for the current cpu */ + struct chan *out = track_get_output(track); + long row = (long) syscpu->gindex; + long flags = PRV_SKIPDUPNULL | PRV_ZERO; + long prvtype = (long) (PRV_NOSV_HWC + i); + if (prv_register(prv, row, prvtype, &emu->bay, out, flags)) { + err("prv_register failed"); + return -1; + } + } + + return 0; +} + +static int +connect_cpu(struct emu *emu) +{ + /* Get cpu PRV */ + struct pvt *pvt = recorder_find_pvt(&emu->recorder, "cpu"); + if (pvt == NULL) { + err("cannot find thread pvt"); + return -1; + } + + /* Connect cpu channels to PRV */ + struct prv *prv = pvt_get_prv(pvt); + for (struct cpu *cpu = emu->system.cpus; cpu; cpu = cpu->next) { + if (connect_cpu_prv(emu, cpu, prv) != 0) { + err("connect_cpu_prv failed"); + return -1; + } + } + + /* Init cpu PCF */ + struct pcf *pcf = pvt_get_pcf(pvt); + if (init_pcf(emu, pcf) != 0) { + err("init_pcf failed"); + return -1; + } + + return 0; +} + +/* Connect the channels to the output PVTs */ +int +hwc_connect(struct emu *emu) +{ + struct nosv_emu *nosv_emu = EXT(emu, 'V'); + struct nosv_hwc_emu *hwc_emu = &nosv_emu->hwc; + + /* No HWC, so nothing to connect */ + if (hwc_emu->n == 0) + return 0; + + if (connect_thread(emu) != 0) { + err("connect_thread() failed"); + return -1; + } + + if (connect_cpu(emu) != 0) { + err("connect_cpu() failed"); + return -1; + } + + return 0; +} + +static int +event_hwc_count(struct emu *emu) +{ + struct nosv_emu *nosv_emu = EXT(emu, 'V'); + struct nosv_hwc_emu *hwc_emu = &nosv_emu->hwc; + + if (!emu->ev->is_jumbo) { + err("expecting a jumbo event"); + return -1; + } + + /* Make sure size matches */ + size_t array_size = (size_t) emu->ev->payload->jumbo.size; + size_t expected_size = hwc_emu->n * sizeof(int64_t); + if (array_size != expected_size) { + err("unexpected hwc event with jumbo payload size %zd (expecting %zd)", + array_size, expected_size); + return -1; + } + + /* Use memcpy to align array */ + memcpy(hwc_emu->values, &emu->ev->payload->jumbo.data[0], array_size); + + /* Update all HWC channels for the given thread */ + for (size_t i = 0; i < hwc_emu->n; i++) { + struct nosv_thread *nosv_thread = EXT(emu->thread, 'V'); + struct nosv_hwc_thread *hwc_thread = &nosv_thread->hwc; + struct chan *ch = &hwc_thread->chan[i]; + if (chan_set(ch, value_int64(hwc_emu->values[i])) != 0) { + err("chan_set failed for hwc channel %s", ch->name); + return -1; + } + } + + return 0; +} + +int +hwc_event(struct emu *emu) +{ + struct nosv_emu *nosv_emu = EXT(emu, 'V'); + struct nosv_hwc_emu *hwc_emu = &nosv_emu->hwc; + + /* Panic on HWC event with no counters */ + if (hwc_emu->n == 0) { + err("got hwc event %s but no counters enabled", emu->ev->mcv); + return -1; + } + + switch (emu->ev->v) { + case 'C': + return event_hwc_count(emu); + default: + err("unknown nosv hwc event %s", emu->ev->mcv); + return -1; + } + + return 0; +} + +static int +write_cfg(const char *path, size_t i, const char *fmt, const char *name) +{ + char title[MAX_LABEL]; + + /* May truncate silently, but is safe */ + snprintf(title, MAX_LABEL, fmt, name); + + long type = (long) (PRV_NOSV_HWC + i); + + struct cfg_file cf; + cfg_file_init(&cf, type, title); + cfg_file_color_mode(&cf, CFG_NGRAD); + cfg_file_semantic_thread(&cf, CFG_NEXT_EV_VAL); + + if (cfg_file_write(&cf, path) != 0) { + err("cfg_file_write failed"); + return -1; + } + + return 0; +} + +int +hwc_finish(struct emu *emu) +{ + struct nosv_emu *nosv_emu = EXT(emu, 'V'); + struct nosv_hwc_emu *hwc_emu = &nosv_emu->hwc; + + if (hwc_emu->n == 0) + return 0; + + /* Write CFG files with HWC names. */ + + for (size_t i = 0; i < hwc_emu->n; i++) { + const char *dir = emu->args.tracedir; + const char *name = hwc_emu->name[i]; + char path[PATH_MAX]; + + /* Create thread configs */ + if (snprintf(path, PATH_MAX, "%s/cfg/thread/nosv/hwc-%s.cfg", dir, name) >= PATH_MAX) { + err("hwc thread cfg path too long"); + return -1; + } + if (write_cfg(path, i, "Thread: nOS-V %s of the ACTIVE thread", name) != 0) { + err("write_cfg failed"); + return -1; + } + + /* Same for CPUs */ + if (snprintf(path, PATH_MAX, "%s/cfg/cpu/nosv/hwc-%s.cfg", dir, name) >= PATH_MAX) { + err("hwc cpu cfg path too long"); + return -1; + } + if (write_cfg(path, i, "CPU: nOS-V %s of the RUNNING thread", name) != 0) { + err("write_cfg failed"); + return -1; + } + } + + return 0; +} diff --git a/src/emu/nosv/hwc.h b/src/emu/nosv/hwc.h new file mode 100644 index 0000000..0db0830 --- /dev/null +++ b/src/emu/nosv/hwc.h @@ -0,0 +1,32 @@ +#ifndef HWC_H +#define HWC_H + +#include "common.h" + +struct emu; +struct chan; + +/* Store each HWC channel per emu */ +struct nosv_hwc_emu { + char **name; + size_t n; + int64_t *values; +}; + +struct nosv_hwc_thread { + struct track *track; + struct chan *chan; + size_t n; +}; + +struct nosv_hwc_cpu { + struct track *track; + size_t n; +}; + +USE_RET int hwc_create(struct emu *emu); +USE_RET int hwc_connect(struct emu *emu); +USE_RET int hwc_event(struct emu *emu); +USE_RET int hwc_finish(struct emu *emu); + +#endif /* HWC_H */ diff --git a/src/emu/nosv/nosv_priv.h b/src/emu/nosv/nosv_priv.h index e22a96c..fcdc393 100644 --- a/src/emu/nosv/nosv_priv.h +++ b/src/emu/nosv/nosv_priv.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2021-2024 Barcelona Supercomputing Center (BSC) +/* Copyright (c) 2021-2025 Barcelona Supercomputing Center (BSC) * SPDX-License-Identifier: GPL-3.0-or-later */ #ifndef NOSV_PRIV_H @@ -6,9 +6,10 @@ #include "breakdown.h" #include "emu.h" -#include "task.h" +#include "hwc.h" #include "model_cpu.h" #include "model_thread.h" +#include "task.h" /* Private enums */ @@ -58,11 +59,13 @@ enum nosv_ss_values { struct nosv_thread { struct model_thread m; struct task_stack task_stack; + struct nosv_hwc_thread hwc; }; struct nosv_cpu { struct model_cpu m; struct nosv_breakdown_cpu breakdown; + struct nosv_hwc_cpu hwc; }; struct nosv_proc { @@ -73,6 +76,7 @@ struct nosv_emu { int connected; int event; struct nosv_breakdown_emu breakdown; + struct nosv_hwc_emu hwc; }; enum nosv_progress { diff --git a/src/emu/nosv/setup.c b/src/emu/nosv/setup.c index ce2ed8e..4746236 100644 --- a/src/emu/nosv/setup.c +++ b/src/emu/nosv/setup.c @@ -12,6 +12,7 @@ #include "emu_prv.h" #include "ev_spec.h" #include "extend.h" +#include "loom.h" #include "model.h" #include "model_chan.h" #include "model_cpu.h" @@ -27,6 +28,7 @@ #include "thread.h" #include "track.h" #include "value.h" +#include "hwc.h" static const char model_name[] = "nosv"; enum { model_id = 'V' }; @@ -81,12 +83,14 @@ static struct ev_decl model_evlist[] = { { "VPr", "sets progress state to Resting" }, { "VPa", "sets progress state to Absorbing" }, + { "VWC+(i64 value)", "set hardware counters (first %{value})" }, + { NULL, NULL }, }; struct model_spec model_nosv = { .name = model_name, - .version = "2.5.1", + .version = "2.6.0", .evlist = model_evlist, .model = model_id, .create = model_nosv_create, @@ -319,6 +323,11 @@ model_nosv_create(struct emu *emu) return -1; } + if (hwc_create(emu) != 0) { + err("hwc_create failed"); + return -1; + } + return 0; } @@ -340,6 +349,11 @@ model_nosv_connect(struct emu *emu) return -1; } + if (hwc_connect(emu) != 0) { + err("hwc_connect failed"); + return -1; + } + for (struct thread *th = emu->system.threads; th; th = th->gnext) { struct nosv_thread *mth = EXT(th, model_id); struct chan *idle = &mth->m.ch[CH_IDLE]; @@ -441,6 +455,11 @@ model_nosv_finish(struct emu *emu) return -1; } + if (hwc_finish(emu) != 0) { + err("hwc_finish failed"); + return -1; + } + /* When running in linter mode perform additional checks */ if (emu->args.linter_mode && end_lint(emu) != 0) { err("end_lint failed"); diff --git a/test/emu/nosv/CMakeLists.txt b/test/emu/nosv/CMakeLists.txt index dcfe850..2ea7c10 100644 --- a/test/emu/nosv/CMakeLists.txt +++ b/test/emu/nosv/CMakeLists.txt @@ -38,3 +38,5 @@ test_emu(bad-nest-from-parallel.c SHOULD_FAIL test_emu(events-from-outside-cpu.c SHOULD_FAIL REGEX "current thread .* out of CPU") + +test_emu(hwc.c) diff --git a/test/emu/nosv/hwc.c b/test/emu/nosv/hwc.c new file mode 100644 index 0000000..2e9699d --- /dev/null +++ b/test/emu/nosv/hwc.c @@ -0,0 +1,35 @@ +/* Copyright (c) 2025 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "compat.h" +#include "instr.h" +#include "instr_nosv.h" + +int +main(void) +{ + instr_start(0, 1); + instr_nosv_init(); + + enum hwc { PAPI_TOT_INS = 0, PAPI_TOT_CYC = 1, MAX_HWC }; + int64_t hwc[MAX_HWC] = { 0 }; + + ovni_attr_set_str("nosv.hwc.0.name", "PAPI_TOT_INS"); + ovni_attr_set_str("nosv.hwc.1.name", "PAPI_TOT_CYC"); + + instr_nosv_hwc(MAX_HWC, hwc); + + for (int i = 0; i < 10; i++) { + sleep_us(100); + + /* Dummy counters */ + hwc[PAPI_TOT_INS] = 50 + (rand() % 100); + hwc[PAPI_TOT_CYC] = 100 + (rand() % 200); + + instr_nosv_hwc(MAX_HWC, hwc); + } + + instr_end(); + + return 0; +} diff --git a/test/emu/nosv/instr_nosv.h b/test/emu/nosv/instr_nosv.h index 9311214..6c38cf9 100644 --- a/test/emu/nosv/instr_nosv.h +++ b/test/emu/nosv/instr_nosv.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2021-2024 Barcelona Supercomputing Center (BSC) +/* Copyright (c) 2021-2025 Barcelona Supercomputing Center (BSC) * SPDX-License-Identifier: GPL-3.0-or-later */ #ifndef INSTR_NOSV_H @@ -38,6 +38,18 @@ instr_nosv_type_create(int32_t typeid) return task_get_type_gid(p); } +static inline void +instr_nosv_hwc(size_t n, int64_t *counters) +{ + struct ovni_ev ev = {0}; + + ovni_ev_set_mcv(&ev, "VWC"); + ovni_ev_set_clock(&ev, (uint64_t) get_clock()); + + uint32_t nbytes = (uint32_t) (n * sizeof(int64_t)); + ovni_ev_jumbo_emit(&ev, (uint8_t *) counters, (uint32_t) nbytes); +} + INSTR_2ARG(instr_nosv_task_create, "VTc", uint32_t, taskid, uint32_t, typeid) INSTR_2ARG(instr_nosv_task_create_par, "VTC", uint32_t, taskid, uint32_t, typeid) INSTR_2ARG(instr_nosv_task_execute, "VTx", uint32_t, taskid, uint32_t, bodyid)