Add support for HWC in nOS-V
This commit is contained in:
parent
e0871bd391
commit
a164642653
@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add support for hardware counters (HWC) in nOS-V.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Increase nOS-V model version to 2.6.0.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix a bug in ovniemu when loading loom CPUs from multiple threads.
|
- Fix a bug in ovniemu when loading loom CPUs from multiple threads.
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
# Emulator events
|
# Emulator events
|
||||||
|
|
||||||
This is a exhaustive list of the events recognized by the emulator.
|
This is a exhaustive list of the events recognized by the emulator.
|
||||||
Built on May 2 2025.
|
Built on Jul 28 2025.
|
||||||
|
|
||||||
## Model nanos6
|
## Model nanos6
|
||||||
|
|
||||||
@ -633,7 +633,7 @@ List of events for the model *tampi* with identifier **`T`** at version `1.0.0`:
|
|||||||
|
|
||||||
## Model nosv
|
## 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`:
|
||||||
<dl>
|
<dl>
|
||||||
<dt><a id="VTc" href="#VTc"><pre>VTc(u32 taskid, u32 typeid)</pre></a></dt>
|
<dt><a id="VTc" href="#VTc"><pre>VTc(u32 taskid, u32 typeid)</pre></a></dt>
|
||||||
<dd>creates task %{taskid} with type %{typeid}</dd>
|
<dd>creates task %{taskid} with type %{typeid}</dd>
|
||||||
@ -761,4 +761,6 @@ List of events for the model *nosv* with identifier **`V`** at version `2.5.1`:
|
|||||||
<dd>sets progress state to Resting</dd>
|
<dd>sets progress state to Resting</dd>
|
||||||
<dt><a id="VPa" href="#VPa"><pre>VPa</pre></a></dt>
|
<dt><a id="VPa" href="#VPa"><pre>VPa</pre></a></dt>
|
||||||
<dd>sets progress state to Absorbing</dd>
|
<dd>sets progress state to Absorbing</dd>
|
||||||
|
<dt><a id="VWC" href="#VWC"><pre>VWC+(i64 value)</pre></a></dt>
|
||||||
|
<dd>set hardware counters (first %{value})</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|||||||
@ -45,6 +45,8 @@ Track changes in emulator model versions.
|
|||||||
|
|
||||||
## nOS-V
|
## nOS-V
|
||||||
|
|
||||||
|
- nosv 2.6.0
|
||||||
|
- Add support for hardware counters event `VWC`.
|
||||||
- nosv 2.5.1
|
- nosv 2.5.1
|
||||||
- Remove task complete callback from between `VTx` and `VTe` events.
|
- Remove task complete callback from between `VTx` and `VTe` events.
|
||||||
- nosv 2.5.0
|
- nosv 2.5.0
|
||||||
|
|||||||
@ -54,8 +54,9 @@ add_library(emu STATIC
|
|||||||
nanos6/event.c
|
nanos6/event.c
|
||||||
nanos6/breakdown.c
|
nanos6/breakdown.c
|
||||||
nosv/breakdown.c
|
nosv/breakdown.c
|
||||||
nosv/setup.c
|
|
||||||
nosv/event.c
|
nosv/event.c
|
||||||
|
nosv/hwc.c
|
||||||
|
nosv/setup.c
|
||||||
nodes/setup.c
|
nodes/setup.c
|
||||||
nodes/event.c
|
nodes/event.c
|
||||||
mpi/setup.c
|
mpi/setup.c
|
||||||
|
|||||||
@ -38,7 +38,9 @@ enum emu_prv_types {
|
|||||||
PRV_OPENMP_BREAKDOWN = 53,
|
PRV_OPENMP_BREAKDOWN = 53,
|
||||||
PRV_OVNI_MARK = 100,
|
PRV_OVNI_MARK = 100,
|
||||||
/* User marks [100, 200) */
|
/* User marks [100, 200) */
|
||||||
PRV_RESERVED = 200,
|
PRV_NOSV_HWC = 200,
|
||||||
|
/* nOS-V HWC [200, 300) */
|
||||||
|
PRV_RESERVED = 300,
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* EMU_PRV_H */
|
#endif /* EMU_PRV_H */
|
||||||
|
|||||||
@ -596,6 +596,8 @@ process_ev(struct emu *emu)
|
|||||||
return pre_task(emu);
|
return pre_task(emu);
|
||||||
case 'Y':
|
case 'Y':
|
||||||
return pre_type(emu);
|
return pre_type(emu);
|
||||||
|
case 'W':
|
||||||
|
return hwc_event(emu);
|
||||||
default:
|
default:
|
||||||
err("unknown nOS-V event category");
|
err("unknown nOS-V event category");
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
545
src/emu/nosv/hwc.c
Normal file
545
src/emu/nosv/hwc.c
Normal file
@ -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 <errno.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
32
src/emu/nosv/hwc.h
Normal file
32
src/emu/nosv/hwc.h
Normal file
@ -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 */
|
||||||
@ -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 */
|
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||||
|
|
||||||
#ifndef NOSV_PRIV_H
|
#ifndef NOSV_PRIV_H
|
||||||
@ -6,9 +6,10 @@
|
|||||||
|
|
||||||
#include "breakdown.h"
|
#include "breakdown.h"
|
||||||
#include "emu.h"
|
#include "emu.h"
|
||||||
#include "task.h"
|
#include "hwc.h"
|
||||||
#include "model_cpu.h"
|
#include "model_cpu.h"
|
||||||
#include "model_thread.h"
|
#include "model_thread.h"
|
||||||
|
#include "task.h"
|
||||||
|
|
||||||
/* Private enums */
|
/* Private enums */
|
||||||
|
|
||||||
@ -58,11 +59,13 @@ enum nosv_ss_values {
|
|||||||
struct nosv_thread {
|
struct nosv_thread {
|
||||||
struct model_thread m;
|
struct model_thread m;
|
||||||
struct task_stack task_stack;
|
struct task_stack task_stack;
|
||||||
|
struct nosv_hwc_thread hwc;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct nosv_cpu {
|
struct nosv_cpu {
|
||||||
struct model_cpu m;
|
struct model_cpu m;
|
||||||
struct nosv_breakdown_cpu breakdown;
|
struct nosv_breakdown_cpu breakdown;
|
||||||
|
struct nosv_hwc_cpu hwc;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct nosv_proc {
|
struct nosv_proc {
|
||||||
@ -73,6 +76,7 @@ struct nosv_emu {
|
|||||||
int connected;
|
int connected;
|
||||||
int event;
|
int event;
|
||||||
struct nosv_breakdown_emu breakdown;
|
struct nosv_breakdown_emu breakdown;
|
||||||
|
struct nosv_hwc_emu hwc;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum nosv_progress {
|
enum nosv_progress {
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
#include "emu_prv.h"
|
#include "emu_prv.h"
|
||||||
#include "ev_spec.h"
|
#include "ev_spec.h"
|
||||||
#include "extend.h"
|
#include "extend.h"
|
||||||
|
#include "loom.h"
|
||||||
#include "model.h"
|
#include "model.h"
|
||||||
#include "model_chan.h"
|
#include "model_chan.h"
|
||||||
#include "model_cpu.h"
|
#include "model_cpu.h"
|
||||||
@ -27,6 +28,7 @@
|
|||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
#include "track.h"
|
#include "track.h"
|
||||||
#include "value.h"
|
#include "value.h"
|
||||||
|
#include "hwc.h"
|
||||||
|
|
||||||
static const char model_name[] = "nosv";
|
static const char model_name[] = "nosv";
|
||||||
enum { model_id = 'V' };
|
enum { model_id = 'V' };
|
||||||
@ -81,12 +83,14 @@ static struct ev_decl model_evlist[] = {
|
|||||||
{ "VPr", "sets progress state to Resting" },
|
{ "VPr", "sets progress state to Resting" },
|
||||||
{ "VPa", "sets progress state to Absorbing" },
|
{ "VPa", "sets progress state to Absorbing" },
|
||||||
|
|
||||||
|
{ "VWC+(i64 value)", "set hardware counters (first %{value})" },
|
||||||
|
|
||||||
{ NULL, NULL },
|
{ NULL, NULL },
|
||||||
};
|
};
|
||||||
|
|
||||||
struct model_spec model_nosv = {
|
struct model_spec model_nosv = {
|
||||||
.name = model_name,
|
.name = model_name,
|
||||||
.version = "2.5.1",
|
.version = "2.6.0",
|
||||||
.evlist = model_evlist,
|
.evlist = model_evlist,
|
||||||
.model = model_id,
|
.model = model_id,
|
||||||
.create = model_nosv_create,
|
.create = model_nosv_create,
|
||||||
@ -319,6 +323,11 @@ model_nosv_create(struct emu *emu)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hwc_create(emu) != 0) {
|
||||||
|
err("hwc_create failed");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,6 +349,11 @@ model_nosv_connect(struct emu *emu)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hwc_connect(emu) != 0) {
|
||||||
|
err("hwc_connect failed");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
for (struct thread *th = emu->system.threads; th; th = th->gnext) {
|
for (struct thread *th = emu->system.threads; th; th = th->gnext) {
|
||||||
struct nosv_thread *mth = EXT(th, model_id);
|
struct nosv_thread *mth = EXT(th, model_id);
|
||||||
struct chan *idle = &mth->m.ch[CH_IDLE];
|
struct chan *idle = &mth->m.ch[CH_IDLE];
|
||||||
@ -441,6 +455,11 @@ model_nosv_finish(struct emu *emu)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hwc_finish(emu) != 0) {
|
||||||
|
err("hwc_finish failed");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/* When running in linter mode perform additional checks */
|
/* When running in linter mode perform additional checks */
|
||||||
if (emu->args.linter_mode && end_lint(emu) != 0) {
|
if (emu->args.linter_mode && end_lint(emu) != 0) {
|
||||||
err("end_lint failed");
|
err("end_lint failed");
|
||||||
|
|||||||
@ -38,3 +38,5 @@ test_emu(bad-nest-from-parallel.c SHOULD_FAIL
|
|||||||
|
|
||||||
test_emu(events-from-outside-cpu.c SHOULD_FAIL
|
test_emu(events-from-outside-cpu.c SHOULD_FAIL
|
||||||
REGEX "current thread .* out of CPU")
|
REGEX "current thread .* out of CPU")
|
||||||
|
|
||||||
|
test_emu(hwc.c)
|
||||||
|
|||||||
35
test/emu/nosv/hwc.c
Normal file
35
test/emu/nosv/hwc.c
Normal file
@ -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;
|
||||||
|
}
|
||||||
@ -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 */
|
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||||
|
|
||||||
#ifndef INSTR_NOSV_H
|
#ifndef INSTR_NOSV_H
|
||||||
@ -38,6 +38,18 @@ instr_nosv_type_create(int32_t typeid)
|
|||||||
return task_get_type_gid(p);
|
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, "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_create_par, "VTC", uint32_t, taskid, uint32_t, typeid)
|
||||||
INSTR_2ARG(instr_nosv_task_execute, "VTx", uint32_t, taskid, uint32_t, bodyid)
|
INSTR_2ARG(instr_nosv_task_execute, "VTx", uint32_t, taskid, uint32_t, bodyid)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user