2025年Iverilog 源码分析 -- VCD的实现机制

Iverilog 源码分析 -- VCD的实现机制VCD 是 Verilog LRM 中定义的 本文介绍 Iverilog 基于 VPI 机制的 VCD 的实现过程 在 vpi 目录下面 定义了不同模块加载的实现 如下显示了 Iverilog 下面已经支持的实现 void vlog startup routines void sys convert register

大家好,我是讯享网,很高兴认识大家。

VCD是Verilog LRM中定义的, 本文介绍Iverilog 基于VPI机制的VCD的实现过程。在vpi目录下面, 定义了不同模块加载的实现。如下显示了Iverilog下面已经支持的实现:

void (*vlog_startup_routines[])(void) = { sys_convert_register, sys_countdrivers_register, sys_darray_register, sys_fileio_register, sys_finish_register, sys_deposit_register, sys_display_register, sys_plusargs_register, sys_queue_register, sys_random_register, sys_random_mti_register, sys_readmem_register, sys_scanf_register, sys_time_register, sys_lxt_or_vcd_register, sys_sdf_register, sys_special_register, table_model_register, vams_simparam_register, 0 }; 

讯享网

VCD 的实现在sys_lxt_or_vcd_register启动函数里面,然后调用到sys_vcd_register:

讯享网void sys_vcd_register(void) { s_vpi_systf_data tf_data; vpiHandle res; /* All the compiletf routines are located in vcd_priv.c. */ tf_data.type = vpiSysTask; tf_data.tfname = "$dumpall"; tf_data.calltf = sys_dumpall_calltf; tf_data.compiletf = sys_no_arg_compiletf; tf_data.sizetf = 0; tf_data.user_data = "$dumpall"; res = vpi_register_systf(&tf_data); vpip_make_systf_system_defined(res); tf_data.type = vpiSysTask; tf_data.tfname = "$dumpfile"; tf_data.calltf = sys_dumpfile_calltf; tf_data.compiletf = sys_one_string_arg_compiletf; tf_data.sizetf = 0; tf_data.user_data = "$dumpfile"; res = vpi_register_systf(&tf_data); vpip_make_systf_system_defined(res); tf_data.type = vpiSysTask; tf_data.tfname = "$dumpflush"; tf_data.calltf = sys_dumpflush_calltf; tf_data.compiletf = sys_no_arg_compiletf; tf_data.sizetf = 0; tf_data.user_data = "$dumpflush"; res = vpi_register_systf(&tf_data); vpip_make_systf_system_defined(res); tf_data.type = vpiSysTask; tf_data.tfname = "$dumplimit"; tf_data.calltf = sys_dumplimit_calltf; tf_data.compiletf = sys_one_numeric_arg_compiletf; tf_data.sizetf = 0; tf_data.user_data = "$dumplimit"; res = vpi_register_systf(&tf_data); vpip_make_systf_system_defined(res); tf_data.type = vpiSysTask; tf_data.tfname = "$dumpoff"; tf_data.calltf = sys_dumpoff_calltf; tf_data.compiletf = sys_no_arg_compiletf; tf_data.sizetf = 0; tf_data.user_data = "$dumpoff"; res = vpi_register_systf(&tf_data); vpip_make_systf_system_defined(res); tf_data.type = vpiSysTask; tf_data.tfname = "$dumpon"; tf_data.calltf = sys_dumpon_calltf; tf_data.compiletf = sys_no_arg_compiletf; tf_data.sizetf = 0; tf_data.user_data = "$dumpon"; res = vpi_register_systf(&tf_data); vpip_make_systf_system_defined(res); tf_data.type = vpiSysTask; tf_data.tfname = "$dumpvars"; tf_data.calltf = sys_dumpvars_calltf; tf_data.compiletf = sys_dumpvars_compiletf; tf_data.sizetf = 0; tf_data.user_data = "$dumpvars"; res = vpi_register_systf(&tf_data); vpip_make_systf_system_defined(res); } 

上述的$dumpvars, $dumpoff, $dumpon等是LRM定义了的系统任务, 用来将仿真的结果输出到VCD文件里面, 因为VCD是所有的仿真器都支持的, 它的实现基本在vpi/vcd_private.cc里面。 如果要将仿真的数据, 输出到自定义的仿真数据库里面, 需要将具体的实现产生一个动态链接库, 在vpi下面注册系统任务, 并且将系统任务的实现由动态链接库里面的函数来进行实现。

$dumpfile

该任务是制定了产生VCD文件的完整路径, 创建一个VCD文件, 并且将date、 version, timescale等信息写入VCD文件:

static void open_dumpfile(vpiHandle callh) { if (dump_path == 0) dump_path = strdup("dump.vcd"); dump_file = fopen(dump_path, "w"); if (dump_file == 0) { vpi_printf("VCD Error: %s:%d: ", vpi_get_str(vpiFile, callh), (int)vpi_get(vpiLineNo, callh)); vpi_printf("Unable to open %s for output.\n", dump_path); vpi_control(vpiFinish, 1); free(dump_path); dump_path = 0; return; } else { int prec = vpi_get(vpiTimePrecision, 0); unsigned scale = 1; unsigned udx = 0; time_t walltime; vpi_printf("VCD info: dumpfile %s opened for output.\n", dump_path); time(&walltime); assert(prec >= -15); while (prec < 0) { udx += 1; prec += 3; } while (prec > 0) { scale *= 10; prec -= 1; } fprintf(dump_file, "$date\n"); fprintf(dump_file, "\t%s",asctime(localtime(&walltime))); fprintf(dump_file, "$end\n"); fprintf(dump_file, "$version\n"); fprintf(dump_file, "\tIcarus Verilog\n"); fprintf(dump_file, "$end\n"); fprintf(dump_file, "$timescale\n"); fprintf(dump_file, "\t%u%s\n", scale, units_names[udx]); fprintf(dump_file, "$end\n"); } } 

$dumpvars

install_dumpvars_callback注册产生VCD文件的hierarchy scope、signal的callback函数:

讯享网__inline__ static int install_dumpvars_callback(void) { struct t_cb_data cb; if (dumpvars_status == 1) return 0; if (dumpvars_status == 2) { vpi_printf("VCD warning: $dumpvars ignored, previously" " called at simtime %" PLI_UINT64_FMT "\n", dumpvars_time); return 1; } cb.time = &zero_delay; cb.reason = cbReadOnlySynch; cb.cb_rtn = dumpvars_cb; cb.user_data = 0x0; cb.obj = 0x0; // 注册dumpvars_cb vpi_register_cb(&cb); cb.reason = cbEndOfSimulation; cb.cb_rtn = finish_cb; vpi_register_cb(&cb); dumpvars_status = 1; return 0; } 

dumpvars_cb做的事情就是在指定的时间点, 将scope/var信息输出到VCD文件里面:

static PLI_INT32 dumpvars_cb(p_cb_data cause) { if (dumpvars_status != 1) return 0; dumpvars_status = 2; dumpvars_time = timerec_to_time64(cause->time); vcd_cur_time = dumpvars_time; fprintf(dump_file, "$enddefinitions $end\n"); if (!dump_is_off) { fprintf(dump_file, "#%" PLI_UINT64_FMT "\n", dumpvars_time); fprintf(dump_file, "$dumpvars\n"); vcd_checkpoint(); fprintf(dump_file, "$end\n"); } return 0; } 

dumpvars_cb 在什么时间点被调用哪? 顺着cbReadOnlySynch, 在vvp/vpi_callback.cc 找到:


讯享网

讯享网vpiHandle vpi_register_cb(p_cb_data data) { struct __vpiCallback*obj = 0; assert(data); switch (data->reason) { case cbValueChange: obj = make_value_change(data); break; case cbReadOnlySynch: obj = make_sync(data, true); break; ... } } 

make_sync创建一个callback对象, 并且将该对象按照时间的调度顺序加入到调度队列里面, 等待时间的触发时机:

static sync_callback* make_sync(p_cb_data data, bool readonly_flag) { sync_callback*obj = new sync_callback(data); struct sync_cb*cb = new sync_cb; cb->sync_flag = readonly_flag? true : false; cb->handle = obj; obj->cb_sync = cb; vvp_time64_t tv = 0; switch (obj->cb_time.type) { case vpiSuppressTime: break; case vpiSimTime: tv = vpip_timestruct_to_time(&obj->cb_time); break; default: fprintf(stderr, "Unsupported time type %d.\n", (int)obj->cb_time.type); assert(0); break; } schedule_generic(cb, tv, true, readonly_flag); return obj; } 

最后, 我们看一下dumpvars的calltf 函数sys_dumpvars_calltf, 它会获取$dumpvars(depth, module_or_var_list), 它会回去module输出的深度以及要输出的module列表, 并且将module下面的每一个signal遍历, 并且注册其valuechange callback:

讯享网static PLI_INT32 sys_dumpvars_calltf(ICARUS_VPI_CONST PLI_BYTE8*name) { vpiHandle callh = vpi_handle(vpiSysTfCall, 0); vpiHandle argv = vpi_iterate(vpiArgument, callh); vpiHandle item; s_vpi_value value; unsigned depth = 0; (void)name; /* Parameter is not used. */ if (dump_file == 0) { open_dumpfile(callh); if (dump_file == 0) { if (argv) vpi_free_object(argv); return 0; } } if (install_dumpvars_callback()) { if (argv) vpi_free_object(argv); return 0; } // $dumpvars的第一个参数是dump制定module的深度 /* Get the depth if it exists. */ if (argv) { value.format = vpiIntVal; vpi_get_value(vpi_scan(argv), &value); depth = value.value.integer; } if (!depth) depth = 10000; /* This dumps all the modules in the design if none are given. */ if (!argv || !(item = vpi_scan(argv))) { argv = vpi_iterate(vpiModule, 0x0); assert(argv); /* There must be at least one top level module. */ item = vpi_scan(argv); } // 遍历$dumpvar 的第二个参数 for ( ; item; item = vpi_scan(argv)) { char *scname; const char *fullname; int add_var = 0; int dep; PLI_INT32 item_type = vpi_get(vpiType, item); // dump $scope type name end dep = draw_scope(item, callh); // 遍历module下面的signal scan_item(depth, item, 0); /* The scope list must be sorted after we scan an item. */ vcd_names_sort(&vcd_tab); while (dep--) fprintf(dump_file, "$upscope $end\n"); /* Add this signal to the variable list so we can verify it * is not included twice. This must be done after it has * been added */ if (add_var) { vcd_names_add(&vcd_var, vpi_get_str(vpiFullName, item)); vcd_names_sort(&vcd_var); } } return 0; } 

scan_item是一个递归函数, 当传入的handle类型是var类型的, 产生一个var类型的对象, 并且注册valuechange 的callback函数:

static void scan_item(unsigned depth, vpiHandle item, int skip) { struct t_cb_data cb; struct vcd_info* info; const char *type; const char *name; const char *fullname; const char *prefix; const char *ident; int nexus_id; unsigned size; PLI_INT32 item_type; fullname = vpi_get_str(vpiFullName, item); /* Generate the $var or $scope commands. */ switch (item_type) { case vpiParameter: vpi_printf("VCD sorry: $dumpvars: can not dump parameters.\n"); break; case vpiNamedEvent: case vpiIntegerVar: case vpiBitVar: case vpiByteVar: case vpiShortIntVar: case vpiIntVar: case vpiLongIntVar: case vpiRealVar: case vpiMemoryWord: case vpiReg: case vpiTimeVar: case vpiNet: /* If we are skipping all signal or this is in an automatic * scope then just return. */ if (skip || vpi_get(vpiAutomatic, item)) return; //处理重复signal * This can only happen for implicitly given signals. */ if (vcd_names_search(&vcd_var, fullname)) return; /* Declare the variable in the VCD file. */ name = vpi_get_str(vpiName, item); prefix = is_escaped_id(name) ? "\\" : ""; /* Some signals can have an alias so handle that. */ nexus_id = vpi_get(_vpiNexusId, item); ident = 0; if (nexus_id) ident = find_nexus_ident(nexus_id); if (!ident) { ident = strdup(vcdid); gen_new_vcd_id(); if (nexus_id) set_nexus_ident(nexus_id, ident); /* Add a callback for the signal. */ info = malloc(sizeof(*info)); info->time.type = vpiSimTime; info->item = item; info->ident = ident; info->scheduled = 0; cb.time = &info->time; cb.user_data = (char*)info; cb.value = NULL; cb.obj = item; // callback 加入到item制定的signal的cb列表 cb.reason = cbValueChange; cb.cb_rtn = variable_cb_1; info->dmp_next = 0; info->next = vcd_list; vcd_list = info; info->cb = vpi_register_cb(&cb); } /* Named events do not have a size, but other tools use * a size of 1 and some viewers do not accept a width of * zero so we will also use a width of one for events. */ if (item_type == vpiNamedEvent) size = 1; else size = vpi_get(vpiSize, item); fprintf(dump_file, "$var %s %u %s %s%s", type, size, ident, prefix, name); /* Add a range for vectored values. */ if (size > 1 || vpi_get(vpiLeftRange, item) != 0) { fprintf(dump_file, " [%i:%i]", (int)vpi_get(vpiLeftRange, item), (int)vpi_get(vpiRightRange, item)); } fprintf(dump_file, " $end\n"); break; } ... } 

当传入的handle类型是scope, 做递归处理。

$dumpoff

When the $dumpoff task is executed, a checkpoint is made in which every selected variable is dumped as an x value. When the $dumpon task is later executed, each variable is dumped with its value at that time. In the interval between $dumpoff and $dumpon, no value changes are dumped.

在sys_dumpoff_calltf里面, 输出当前的时间点, 并且将每一个选定的signal的valuechange 输出为x:

讯享网/* Dump values for a $dumpoff. */ static void show_this_item_x(struct vcd_info*info) { PLI_INT32 type = vpi_get(vpiType, info->item); if (type == vpiRealVar) { /* Some tools dump nothing here...? */ fprintf(dump_file, "rNaN %s\n", info->ident); } else if (type == vpiNamedEvent) { /* Do nothing for named events. */ } else if (vpi_get(vpiSize, info->item) == 1) { fprintf(dump_file, "x%s\n", info->ident); } else { fprintf(dump_file, "bx %s\n", info->ident); } } static void vcd_checkpoint_x(void) { struct vcd_info*cur; for (cur = vcd_list ; cur ; cur = cur->next) show_this_item_x(cur); } 

$dumpon

static void show_this_item(struct vcd_info*info) { s_vpi_value value; PLI_INT32 type = vpi_get(vpiType, info->item); if (type == vpiRealVar) { value.format = vpiRealVal; vpi_get_value(info->item, &value); fprintf(dump_file, "r%.16g %s\n", value.value.real, info->ident); } else if (type == vpiNamedEvent) { fprintf(dump_file, "1%s\n", info->ident); } else if (vpi_get(vpiSize, info->item) == 1) { value.format = vpiBinStrVal; vpi_get_value(info->item, &value); fprintf(dump_file, "%s%s\n", value.value.str, info->ident); } else { value.format = vpiBinStrVal; vpi_get_value(info->item, &value); fprintf(dump_file, "b%s %s\n", truncate_bitvec(value.value.str), info->ident); } } /* * This function writes out all the traced variables, whether they * changed or not. */ static void vcd_checkpoint(void) { struct vcd_info*cur; for (cur = vcd_list ; cur ; cur = cur->next) show_this_item(cur); } 
小讯
上一篇 2025-01-18 18:30
下一篇 2025-01-25 19:49

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/20950.html