From 753a52b56c9bbe8a48025acb251e2159dedc28b5 Mon Sep 17 00:00:00 2001 From: Martin Whitaker Date: Sun, 5 Oct 2025 12:34:25 +0100 Subject: [PATCH] Add support for $fmonitor tasks (issue #1280) --- vpi/sys_display.c | 295 ++++++++++++++++++++++++++++++++-------------- vpi/sys_fileio.c | 5 +- vpi/sys_icarus.c | 22 +--- vpi/sys_priv.h | 4 +- 4 files changed, 212 insertions(+), 114 deletions(-) diff --git a/vpi/sys_display.c b/vpi/sys_display.c index d3960b5b7..d4d8d9e15 100644 --- a/vpi/sys_display.c +++ b/vpi/sys_display.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999-2021 Stephen Williams (steve@icarus.com) + * Copyright (c) 1999-2025 Stephen Williams (steve@icarus.com) * * This source code is free software; you can redistribute it * and/or modify it in source code form under the terms of the GNU @@ -167,7 +167,7 @@ static int get_default_format(const char *name) int default_format; switch(name[ strlen(name)-1 ]){ - /* writE/strobE or monitoR or displaY/fdisplaY or sformaT/sformatF */ + /* (f)writE/(f)strobE or (f)monitoR or (f)displaY or sformaT/sformatF */ case 'e': case 'r': case 't': @@ -1429,55 +1429,86 @@ static PLI_INT32 sys_strobe_calltf(ICARUS_VPI_CONST PLI_BYTE8*name) } /* - * The $monitor system task works by managing these static variables, - * and the cbValueChange callbacks associated with registers and - * nets. Note that it is proper to keep the state in static variables - * because there can only be one monitor at a time pending (even - * though that monitor may be watching many variables). + * The monitor_info array records info for all the active monitor tasks. + * monitor_info[0] points to the info for the most recently called $monitor + * task (any preceding call to $monitor gets cancelled by that call) and is + * allocated before we register the VPI tasks. Further entries in monitor_info + * are allocated on demand by calls to $fmonitor. */ -static struct strobe_cb_info monitor_info = { 0, 0, 0, 0, 0, 0, 0, 0 }; -static vpiHandle *monitor_callbacks = 0; -static int monitor_scheduled = 0; -static int monitor_enabled = 1; +struct monitor_cb_info { + struct strobe_cb_info strobe; + vpiHandle *callbacks; + vpiHandle scheduled; + int disabled; +}; + +static struct monitor_cb_info**monitor_info = 0; +static unsigned monitor_info_len = 0; + +static struct monitor_cb_info*allocate_monitor_info(void) +{ + unsigned idx; + for (idx = 0; idx < monitor_info_len; idx += 1) { + if (monitor_info[idx] == 0) + goto found_empty_slot; + } + monitor_info = realloc(monitor_info, ++monitor_info_len * sizeof(struct monitor_cb_info*)); +found_empty_slot: + monitor_info[idx] = calloc(1, sizeof(struct monitor_cb_info)); + return monitor_info[idx]; +} + +static void cancel_monitor(struct monitor_cb_info*monitor) +{ + unsigned idx; + + if (monitor == 0) return; + + if (monitor->scheduled) { + vpi_remove_cb(monitor->scheduled); + monitor->scheduled = 0; + } + for (idx = 0; idx < monitor->strobe.nitems; idx += 1) { + if (monitor->callbacks[idx]) + vpi_remove_cb(monitor->callbacks[idx]); + } + free(monitor->callbacks); + monitor->callbacks = 0; + free(monitor->strobe.filename); + free(monitor->strobe.items); + monitor->strobe.items = 0; + monitor->strobe.nitems = 0; + monitor->strobe.name = 0; +} + +static void cancel_fmonitor(unsigned idx) +{ + cancel_monitor(monitor_info[idx]); + free(monitor_info[idx]); + monitor_info[idx] = 0; +} static PLI_INT32 monitor_cb_2(p_cb_data cb) { + struct monitor_cb_info*monitor = (struct monitor_cb_info*)cb->user_data; char* result; unsigned int size; - - (void)cb; /* Parameter is not used. */ - /* Because %u and %z may put embedded NULL characters into the * returned string strlen() may not match the real size! */ - result = get_display(&size, &monitor_info); - my_mcd_rawwrite(monitor_info.fd_mcd, result, size); - my_mcd_rawwrite(monitor_info.fd_mcd, "\n", 1); - monitor_scheduled = 0; + result = get_display(&size, &(monitor->strobe)); + my_mcd_rawwrite(monitor->strobe.fd_mcd, result, size); + my_mcd_rawwrite(monitor->strobe.fd_mcd, "\n", 1); + monitor->scheduled = 0; free(result); return 0; } -/* - * The monitor_cb_1 callback is called when an event occurs somewhere - * in the simulation. All this function does is schedule the actual - * display to occur in a ReadOnlySync callback. The monitor_scheduled - * flag is used to allow only one monitor strobe to be scheduled. - */ -static PLI_INT32 monitor_cb_1(p_cb_data cause) +static void schedule_monitor_display(struct monitor_cb_info*monitor) { struct t_cb_data cb; struct t_vpi_time timerec; - (void)cause; /* Parameter is not used. */ - - if (monitor_enabled == 0) return 0; - if (monitor_scheduled) return 0; - - /* This this action caused the first trigger, then schedule - the monitor to happen at the end of the time slice and mark - it as scheduled. */ - monitor_scheduled += 1; timerec.type = vpiSimTime; timerec.low = 0; timerec.high = 0; @@ -1487,81 +1518,95 @@ static PLI_INT32 monitor_cb_1(p_cb_data cause) cb.time = &timerec; cb.obj = 0; cb.value = 0; - vpi_register_cb(&cb); + cb.user_data = (char*)monitor; + monitor->scheduled = vpi_register_cb(&cb); +} + +/* + * The monitor_cb_1 callback is called when a monitored value changes. + * All this function does is schedule the actual display to occur in a + * ReadOnlySync callback. The scheduled event handle in the monitor info + * is used to ensure only one display is scheduled in each time slot, + * and can also be used to cancel the display event if the monitor gets + * cancelled. + */ +static PLI_INT32 monitor_cb_1(p_cb_data cb) +{ + struct monitor_cb_info*monitor = (struct monitor_cb_info*)cb->user_data; + + if (monitor->disabled) return 0; + if (monitor->scheduled) return 0; + + schedule_monitor_display(monitor); return 0; } +/* Check both the $monitor and $fmonitor based tasks. */ static PLI_INT32 sys_monitor_compiletf(ICARUS_VPI_CONST PLI_BYTE8 *name) { - vpiHandle callh = vpi_handle(vpiSysTfCall, 0); - vpiHandle argv = vpi_iterate(vpiArgument, callh); - - int rtn = sys_check_args(callh, argv, name, 1, 1); - if (rtn) { - if (rtn == 1) vpip_set_return_value(1); - vpi_control(vpiFinish, 1); - } - return 0; + /* These tasks can not have automatic variables and are monitor. */ + return sys_common_compiletf(name, 1, 1); } +/* This implements both the $monitor and $fmonitor based tasks. */ static PLI_INT32 sys_monitor_calltf(ICARUS_VPI_CONST PLI_BYTE8*name) { vpiHandle callh, argv, scope; - unsigned idx; + struct monitor_cb_info*monitor; struct t_cb_data cb; struct t_vpi_time timerec; - - (void)name; /* Parameter is not used. */ + PLI_UINT32 fd_mcd; + unsigned idx; callh = vpi_handle(vpiSysTfCall, 0); argv = vpi_iterate(vpiArgument, callh); - /* If there was a previous $monitor, then remove the callbacks - related to it. */ - if (monitor_callbacks) { - for (idx = 0 ; idx < monitor_info.nitems ; idx += 1) - if (monitor_callbacks[idx]) - vpi_remove_cb(monitor_callbacks[idx]); - - free(monitor_callbacks); - monitor_callbacks = 0; - - free(monitor_info.filename); - free(monitor_info.items); - monitor_info.items = 0; - monitor_info.nitems = 0; - monitor_info.name = 0; + if (name[1] == 'f') { + vpiHandle fd = vpi_scan(argv); + if (get_fd_mcd_from_arg(&fd_mcd, fd, callh, name)) { + vpi_free_object(argv); + return 0; + } + monitor = allocate_monitor_info(); + } else { + fd_mcd = 1; + assert(monitor_info); + monitor = monitor_info[0]; + /* If there was a previous $monitor, cancel it. */ + if (monitor->strobe.name) + cancel_monitor(monitor); } scope = vpi_handle(vpiScope, callh); assert(scope); /* Make an array of handles from the argument list. */ - array_from_iterator(&monitor_info, argv); - monitor_info.name = name; - monitor_info.filename = strdup(vpi_get_str(vpiFile, callh)); - monitor_info.lineno = (int)vpi_get(vpiLineNo, callh); - monitor_info.default_format = get_default_format(name); - monitor_info.scope = scope; - monitor_info.fd_mcd = 1; + array_from_iterator(&(monitor->strobe), argv); + monitor->strobe.name = name; + monitor->strobe.filename = strdup(vpi_get_str(vpiFile, callh)); + monitor->strobe.lineno = (int)vpi_get(vpiLineNo, callh); + monitor->strobe.default_format = get_default_format(name); + monitor->strobe.scope = scope; + monitor->strobe.fd_mcd = fd_mcd; /* Attach callbacks to all the parameters that might change. */ - monitor_callbacks = calloc(monitor_info.nitems, sizeof(vpiHandle)); + monitor->callbacks = calloc(monitor->strobe.nitems, sizeof(vpiHandle)); timerec.type = vpiSuppressTime; cb.reason = cbValueChange; cb.cb_rtn = monitor_cb_1; cb.time = &timerec; cb.value = NULL; - for (idx = 0 ; idx < monitor_info.nitems ; idx += 1) { + cb.user_data = (char*)monitor; + for (idx = 0 ; idx < monitor->strobe.nitems ; idx += 1) { - switch (vpi_get(vpiType, monitor_info.items[idx])) { + switch (vpi_get(vpiType, monitor->strobe.items[idx])) { case vpiMemoryWord: /* * We only support constant selections. Make this * better when we add a real compiletf routine. */ - assert(vpi_get(vpiConstantSelect, monitor_info.items[idx])); + assert(vpi_get(vpiConstantSelect, monitor->strobe.items[idx])); case vpiNet: case vpiReg: case vpiIntegerVar: @@ -1573,20 +1618,17 @@ static PLI_INT32 sys_monitor_calltf(ICARUS_VPI_CONST PLI_BYTE8*name) case vpiRealVar: case vpiPartSelect: /* Monitoring reg and net values involves setting - a callback for value changes. Pass the storage - pointer for the callback itself as user_data so - that the callback can refresh itself. */ - cb.user_data = (char*)(monitor_callbacks+idx); - cb.obj = monitor_info.items[idx]; - monitor_callbacks[idx] = vpi_register_cb(&cb); + a callback for value changes. */ + cb.obj = monitor->strobe.items[idx]; + monitor->callbacks[idx] = vpi_register_cb(&cb); break; } } - /* When the $monitor is called, it schedules a first display + /* When the monitor task is called, it schedules a first display for the end of the current time, like a $strobe. */ - monitor_cb_1(0); + schedule_monitor_display(monitor); return 0; } @@ -1594,18 +1636,44 @@ static PLI_INT32 sys_monitor_calltf(ICARUS_VPI_CONST PLI_BYTE8*name) static PLI_INT32 sys_monitoron_calltf(ICARUS_VPI_CONST PLI_BYTE8*name) { (void)name; /* Parameter is not used. */ - monitor_enabled = 1; - monitor_cb_1(0); + assert(monitor_info); + monitor_info[0]->disabled = 0; + schedule_monitor_display(monitor_info[0]); return 0; } static PLI_INT32 sys_monitoroff_calltf(ICARUS_VPI_CONST PLI_BYTE8*name) { (void)name; /* Parameter is not used. */ - monitor_enabled = 0; + assert(monitor_info); + monitor_info[0]->disabled = 1; return 0; } +void sys_monitor_fclose(PLI_UINT32 fd_mcd) +{ + unsigned idx; + + if (IS_MCD(fd_mcd)) { + // The LRM doesn't specify the exact behaviour in this case, + // but we want to cancel any active fmonitors where there is + // an intersection between the MCD being closed and the MCD + // being monitored. Otherwise a subsequent call to $fopen could + // reuse the bit(s) in the MCD and cause the monitor output to + // be written to the newly opened file(s). + for (idx = 1; idx < monitor_info_len; idx += 1) { + if (monitor_info[idx] && (monitor_info[idx]->strobe.fd_mcd & fd_mcd)) + cancel_fmonitor(idx); + } + } else { + // Cancel any active fmonitors with a matching FD. + for (idx = 1; idx < monitor_info_len; idx += 1) { + if (monitor_info[idx] && (monitor_info[idx]->strobe.fd_mcd == fd_mcd)) + cancel_fmonitor(idx); + } + } +} + static PLI_INT32 sys_swrite_compiletf(ICARUS_VPI_CONST PLI_BYTE8 *name) { vpiHandle callh = vpi_handle(vpiSysTfCall, 0); @@ -2181,14 +2249,18 @@ static PLI_INT32 sys_severity_calltf(ICARUS_VPI_CONST PLI_BYTE8*name) static PLI_INT32 sys_end_of_simulation(p_cb_data cb_data) { + unsigned idx; + (void)cb_data; /* Parameter is not used. */ - free(monitor_callbacks); - monitor_callbacks = 0; - free(monitor_info.filename); - free(monitor_info.items); - monitor_info.items = 0; - monitor_info.nitems = 0; - monitor_info.name = 0; + + assert(monitor_info); + for (idx = 0; idx < monitor_info_len; idx +=1) { + cancel_monitor(monitor_info[idx]); + free(monitor_info[idx]); + } + free(monitor_info); + monitor_info_len = 0; + monitor_info = 0; free(timeformat_info.suff); timeformat_info.suff = 0; @@ -2201,6 +2273,10 @@ void sys_display_register(void) s_vpi_systf_data tf_data; vpiHandle res; + // Allocate the first entry in monitor_info for use by $monitor + // based tasks. + (void)allocate_monitor_info(); + check_command_line_args(); /*============================== display */ @@ -2406,6 +2482,43 @@ void sys_display_register(void) res = vpi_register_systf(&tf_data); vpip_make_systf_system_defined(res); + /*============================== fmonitor */ + tf_data.type = vpiSysTask; + tf_data.tfname = "$fmonitor"; + tf_data.calltf = sys_monitor_calltf; + tf_data.compiletf = sys_monitor_compiletf; + tf_data.sizetf = 0; + tf_data.user_data = "$fmonitor"; + res = vpi_register_systf(&tf_data); + vpip_make_systf_system_defined(res); + + tf_data.type = vpiSysTask; + tf_data.tfname = "$fmonitorh"; + tf_data.calltf = sys_monitor_calltf; + tf_data.compiletf = sys_monitor_compiletf; + tf_data.sizetf = 0; + tf_data.user_data = "$fmonitorh"; + res = vpi_register_systf(&tf_data); + vpip_make_systf_system_defined(res); + + tf_data.type = vpiSysTask; + tf_data.tfname = "$fmonitoro"; + tf_data.calltf = sys_monitor_calltf; + tf_data.compiletf = sys_monitor_compiletf; + tf_data.sizetf = 0; + tf_data.user_data = "$fmonitoro"; + res = vpi_register_systf(&tf_data); + vpip_make_systf_system_defined(res); + + tf_data.type = vpiSysTask; + tf_data.tfname = "$fmonitorb"; + tf_data.calltf = sys_monitor_calltf; + tf_data.compiletf = sys_monitor_compiletf; + tf_data.sizetf = 0; + tf_data.user_data = "$fmonitorb"; + res = vpi_register_systf(&tf_data); + vpip_make_systf_system_defined(res); + /*============================== fdisplay */ tf_data.type = vpiSysTask; tf_data.tfname = "$fdisplay"; diff --git a/vpi/sys_fileio.c b/vpi/sys_fileio.c index ef28e59d3..25ed8d9d5 100644 --- a/vpi/sys_fileio.c +++ b/vpi/sys_fileio.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2023 Stephen Williams (steve@icarus.com) + * Copyright (c) 2003-2025 Stephen Williams (steve@icarus.com) * * This source code is free software; you can redistribute it * and/or modify it in source code form under the terms of the GNU @@ -221,6 +221,9 @@ static PLI_INT32 sys_fclose_calltf(ICARUS_VPI_CONST PLI_BYTE8*name) /* Get the file/MC descriptor and verify that it is valid. */ if (get_fd_mcd_from_arg(&fd_mcd, fd, callh, name)) return 0; + /* Cancel any active $fmonitor()'s for this FD/MCD. */ + sys_monitor_fclose(fd_mcd); + /* We need to cancel any active $fstrobe()'s for this FD/MCD. * For now we check in the strobe callback and skip the output * generation when needed. */ diff --git a/vpi/sys_icarus.c b/vpi/sys_icarus.c index 273175e67..d3b0bb005 100644 --- a/vpi/sys_icarus.c +++ b/vpi/sys_icarus.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2021 Cary R. (cygcary@yahoo.com) + * Copyright (C) 2008-2025 Cary R. (cygcary@yahoo.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -92,26 +92,6 @@ void sys_special_register(void) tf_data.sizetf = 0; tf_data.compiletf = task_not_implemented_compiletf; - tf_data.tfname = "$fmonitor"; - tf_data.user_data = "$fmonitor"; - res = vpi_register_systf(&tf_data); - vpip_make_systf_system_defined(res); - - tf_data.tfname = "$fmonitorb"; - tf_data.user_data = "$fmonitorb"; - res = vpi_register_systf(&tf_data); - vpip_make_systf_system_defined(res); - - tf_data.tfname = "$fmonitoro"; - tf_data.user_data = "$fmonitoro"; - res = vpi_register_systf(&tf_data); - vpip_make_systf_system_defined(res); - - tf_data.tfname = "$fmonitorh"; - tf_data.user_data = "$fmonitorh"; - res = vpi_register_systf(&tf_data); - vpip_make_systf_system_defined(res); - tf_data.tfname = "$async$and$array"; tf_data.user_data = "$async$and$array"; res = vpi_register_systf(&tf_data); diff --git a/vpi/sys_priv.h b/vpi/sys_priv.h index 878507b4b..9f6eeb4bf 100644 --- a/vpi/sys_priv.h +++ b/vpi/sys_priv.h @@ -1,7 +1,7 @@ #ifndef IVL_sys_priv_H #define IVL_sys_priv_H /* - * Copyright (c) 2002-2023 Stephen Williams (steve@icarus.com) + * Copyright (c) 2002-2025 Stephen Williams (steve@icarus.com) * * This source code is free software; you can redistribute it * and/or modify it in source code form under the terms of the GNU @@ -67,6 +67,8 @@ extern unsigned get_fd_mcd_from_arg(PLI_UINT32 *fd_mcd, vpiHandle arg, extern vpiHandle sys_func_module(vpiHandle obj); +extern void sys_monitor_fclose(PLI_UINT32 fd_mcd); + /* * The standard compiletf routines. */