From bb1e48785de78a6e7747bdd2d80ec839cea9ceef Mon Sep 17 00:00:00 2001 From: Blebowski <34539154+Blebowski@users.noreply.github.com> Date: Wed, 9 Nov 2022 19:04:39 +0100 Subject: [PATCH] Code coverage improvements (#535) --- nvc.1 | 61 ++ src/cgen.c | 86 ++- src/dump.c | 23 +- src/jit/jit-exits.c | 6 + src/jit/jit-exits.h | 1 + src/lower.c | 336 ++++++-- src/nvc.c | 150 +++- src/opt.c | 1 - src/opt.h | 1 - src/parse.c | 15 +- src/rt/cover.c | 1390 +++++++++++++++++++++++----------- src/rt/cover.h | 125 ++- src/rt/model.c | 38 +- src/rt/model.h | 1 + src/symbols.txt | 1 + src/vcode.c | 39 +- src/vcode.h | 6 +- test/lower/cover.vhd | 18 + test/regress/cover1.sh | 17 + test/regress/gold/cover1.txt | 7 +- test/regress/testlist.txt | 2 +- test/test_lower.c | 27 +- test/test_util.c | 2 - 23 files changed, 1762 insertions(+), 591 deletions(-) create mode 100644 test/regress/cover1.sh diff --git a/nvc.1 b/nvc.1 index aa45bb8c..4a68065d 100644 --- a/nvc.1 +++ b/nvc.1 @@ -16,6 +16,8 @@ .Nm .Fl r .Fa unit +.Nm +.Fl c Ar .\" ------------------------------------------------------------ .\" Description .\" ------------------------------------------------------------ @@ -48,6 +50,11 @@ Elaborate a previously analysed top level design unit. .\" -r .It Fl r Ar unit Execute a previously elaborated top level design unit. +.\" -c +.It Fl c Ar +Process code coverage data from +.Ar file +and generate coverage report. .\" --dump .It Fl -dump Ar unit Print out a pseudo-VHDL representation of an analysed unit. This is @@ -351,6 +358,21 @@ option. By default all signals in the design will be dumped: see the section below for how to control this. .El .\" ------------------------------------------------------------ +.\" Coverage processing options +.\" ------------------------------------------------------------ +.Ss Coverage processing options +.Bl -tag -width Ds +.It Fl -merge= Ar OUTPUT +Merge multiple +.Ar file +code coverage databases into +.Ar OUTPUT +code coverage database. +.It Fl -report= Ar DIR +Generate HTML code coverage report to +.Ar DIR +directory. +.\" ------------------------------------------------------------ .\" Make options .\" ------------------------------------------------------------ .Ss Make options @@ -441,6 +463,45 @@ completely safe and protected by a lock in the filesystem using .Xr flock 2 that allows multiple concurrent readers but only a single writer. .\" ------------------------------------------------------------ +.\" CODE COVERAGE +.\" ------------------------------------------------------------ +.Sh CODE COVERAGE +NVC supports collecting of code coverage. Following coverage types +are supported: +.Bl -bullet +.It +Statement - +.Cm s +- For each statement, NVC creates coverage bin. When statement is +executed, it is covered. +.It +Branch - +.Cm b +- For each point where code diverges (if/else, case, +when/else, with/select statements), NVC creates coverage bin. If +branch can be evaluated to both true and false, NVC creates two +coverage bins for such branch (one for each of true/false) +.It +Toggle - +.Cm t +- Each signal of type derived from std_logic +(including nested arrays) creates two coverage bins (to track 0->1 and +1->0 transitions). +.El +Collecting each coverage type can be enabled separately at elaboration time: +.Bd -literal -offset indent +$ nvc -e --cover=s,b,t +.Ed +If no coverage type is specified as argument of +.Fl -cover +,all coverage types are collected. After +simulation is executed, NVC dumps coverage data into coverage database file +(*.covdb). To merge coverage databases from multiple simulations, and generate +hierarchy coverage report in HTML format, run: +.Bd -literal -offset indent +$nvc -c --merge=merged.covdb --report= first.covdb second.covdb third.covdb ... +.Ed +.\" ------------------------------------------------------------ .\" Relaxed rules .\" ------------------------------------------------------------ .Sh RELAXED RULES diff --git a/src/cgen.c b/src/cgen.c index e80442d1..d2fe29d4 100644 --- a/src/cgen.c +++ b/src/cgen.c @@ -3058,15 +3058,21 @@ static void cgen_op_unreachable(int op, cgen_ctx_t *ctx) LLVMBuildUnreachable(builder); } -static void cgen_op_cover_stmt(int op, cgen_ctx_t *ctx) +static LLVMValueRef cgen_get_cover_cnt(int op, const char *cnt_name) { const uint32_t cover_tag = vcode_get_tag(op); - LLVMValueRef cover_counts = LLVMGetNamedGlobal(module, "cover_stmts"); + LLVMValueRef cover_counts = LLVMGetNamedGlobal(module, cnt_name); LLVMValueRef indexes[] = { llvm_int32(0), llvm_int32(cover_tag) }; - LLVMValueRef count_ptr = LLVMBuildGEP(builder, cover_counts, - indexes, ARRAY_LEN(indexes), ""); + LLVMValueRef ptr = LLVMBuildGEP(builder, cover_counts, + indexes, ARRAY_LEN(indexes), ""); + return ptr; +} + +static void cgen_op_cover_stmt(int op, cgen_ctx_t *ctx) +{ + LLVMValueRef count_ptr = cgen_get_cover_cnt(op, "cover_stmts"); LLVMValueRef count = LLVMBuildLoad(builder, count_ptr, "cover_count"); LLVMValueRef count1 = LLVMBuildAdd(builder, count, llvm_int32(1), ""); @@ -3074,25 +3080,17 @@ static void cgen_op_cover_stmt(int op, cgen_ctx_t *ctx) LLVMBuildStore(builder, count1, count_ptr); } -static void cgen_op_cover_cond(int op, cgen_ctx_t *ctx) +static void cgen_op_cover_branch(int op, cgen_ctx_t *ctx) { - const uint32_t cover_tag = vcode_get_tag(op); - const int sub_cond = vcode_get_subkind(op); - - LLVMValueRef cover_conds = LLVMGetNamedGlobal(module, "cover_conds"); - - LLVMValueRef indexes[] = { llvm_int32(0), llvm_int32(cover_tag) }; - LLVMValueRef mask_ptr = LLVMBuildGEP(builder, cover_conds, - indexes, ARRAY_LEN(indexes), ""); + LLVMValueRef mask_ptr = cgen_get_cover_cnt(op, "cover_branches"); - LLVMValueRef mask = LLVMBuildLoad(builder, mask_ptr, "cover_conds"); + LLVMValueRef mask = LLVMBuildLoad(builder, mask_ptr, "cover_branches"); // Bit zero means evaluated false, bit one means evaluated true - // Other bits may be used in the future for sub-conditions LLVMValueRef or = LLVMBuildSelect(builder, cgen_get_arg(op, 0, ctx), - llvm_int32(1 << ((sub_cond * 2) + 1)), - llvm_int32(1 << (sub_cond * 2)), + llvm_int32(1 << 0), + llvm_int32(1 << 1), "cond_mask_or"); LLVMValueRef mask1 = LLVMBuildOr(builder, mask, or, ""); @@ -3100,6 +3098,22 @@ static void cgen_op_cover_cond(int op, cgen_ctx_t *ctx) LLVMBuildStore(builder, mask1, mask_ptr); } +static void cgen_op_cover_toggle(int op, cgen_ctx_t *ctx) +{ + LLVMValueRef mask_ptr = cgen_get_cover_cnt(op, "cover_toggles"); + + LLVMValueRef sigptr = cgen_get_arg(op, 0, ctx); + LLVMValueRef shared = LLVMBuildExtractValue(builder, sigptr, 0, ""); + + LLVMValueRef args[] = { + shared, + mask_ptr + }; + + LLVMBuildCall(builder, llvm_fn("__nvc_setup_toggle_cb"), args, + ARRAY_LEN(args), ""); +} + static void cgen_op_range_null(int op, cgen_ctx_t *ctx) { vcode_reg_t result = vcode_get_result(op); @@ -3744,8 +3758,11 @@ static void cgen_op(int i, cgen_ctx_t *ctx) case VCODE_OP_COVER_STMT: cgen_op_cover_stmt(i, ctx); break; - case VCODE_OP_COVER_COND: - cgen_op_cover_cond(i, ctx); + case VCODE_OP_COVER_BRANCH: + cgen_op_cover_branch(i, ctx); + break; + case VCODE_OP_COVER_TOGGLE: + cgen_op_cover_toggle(i, ctx); break; case VCODE_OP_UARRAY_LEN: cgen_op_uarray_len(i, ctx); @@ -4381,8 +4398,8 @@ static void cgen_reset_function(void) static void cgen_coverage_state(tree_t t, cover_tagging_t *tagging, bool external) { - int32_t stmt_tags, cond_tags; - cover_count_tags(tagging, &stmt_tags, &cond_tags); + int32_t stmt_tags, branch_tags, toggle_tags; + cover_count_tags(tagging, &stmt_tags, &branch_tags, &toggle_tags); if (stmt_tags > 0) { LLVMTypeRef type = LLVMArrayType(llvm_int32_type(), stmt_tags); @@ -4395,9 +4412,20 @@ static void cgen_coverage_state(tree_t t, cover_tagging_t *tagging, } } - if (cond_tags > 0) { - LLVMTypeRef type = LLVMArrayType(llvm_int32_type(), stmt_tags); - LLVMValueRef var = LLVMAddGlobal(module, type, "cover_conds"); + if (branch_tags > 0) { + LLVMTypeRef type = LLVMArrayType(llvm_int32_type(), branch_tags); + LLVMValueRef var = LLVMAddGlobal(module, type, "cover_branches"); + if (external) + LLVMSetLinkage(var, LLVMExternalLinkage); + else { + LLVMSetInitializer(var, LLVMGetUndef(type)); + cgen_add_func_attr(var, FUNC_ATTR_DLLEXPORT, -1); + } + } + + if (toggle_tags > 0) { + LLVMTypeRef type = LLVMArrayType(llvm_int32_type(), toggle_tags); + LLVMValueRef var = LLVMAddGlobal(module, type, "cover_toggles"); if (external) LLVMSetLinkage(var, LLVMExternalLinkage); else { @@ -5118,6 +5146,16 @@ static LLVMValueRef cgen_support_fn(const char *name) LLVMFunctionType(llvm_void_type(), args, ARRAY_LEN(args), false)); } + else if (strcmp(name, "__nvc_setup_toggle_cb") == 0) { + LLVMTypeRef args[] = { + LLVMPointerType(llvm_signal_shared_struct(), 0), + LLVMPointerType(llvm_int32_type(), 0) + }; + + fn = LLVMAddFunction(module, "__nvc_setup_toggle_cb", + LLVMFunctionType(llvm_void_type(), + args, ARRAY_LEN(args), false)); + } else if (strcmp(name, "_implicit_signal") == 0) { LLVMTypeRef args[] = { llvm_int32_type(), diff --git a/src/dump.c b/src/dump.c index 7c973d61..a94cba91 100644 --- a/src/dump.c +++ b/src/dump.c @@ -31,6 +31,7 @@ #define DUMP_TYPE_HINT 0 #define DUMP_ADDRESS 0 #define DUMP_STATICNESS 0 +#define DUMP_GEN_NAMES 0 LCOV_EXCL_START @@ -937,8 +938,11 @@ static void dump_stmt(tree_t t, int indent) if (tree_has_ident(t)) { const char *label = istr(tree_ident(t)); +#ifndef DUMP_GEN_NAMES if (label[0] != '_') // Skip generated labels +#endif printf("%s: ", label); + } switch (tree_kind(t)) { @@ -1065,13 +1069,21 @@ static void dump_stmt(tree_t t, int indent) break; case T_IF: - syntax("#if "); for (unsigned i = 0; i < tree_conds(t); i++) { tree_t c = tree_cond(t, i); if (tree_has_value(c)) { - if (i > 0) { + if (i > 0) tab(indent); + +#ifdef DUMP_GEN_NAMES + // T_CONDS can only have generate name + const char *label = istr(tree_ident(c)); + printf("%s: ", label); +#endif + if (i > 0) { syntax("#elsif "); + } else { + syntax("#if "); } dump_expr(tree_value(c)); syntax(" #then\n"); @@ -1101,7 +1113,12 @@ static void dump_stmt(tree_t t, int indent) syntax(" #is\n"); for (unsigned i = 0; i < tree_assocs(t); i++) { tab(indent + 2); - tree_t a = tree_assoc(t, i); + tree_t a = tree_assoc(t, i); +#ifdef DUMP_GEN_NAMES + // T_ASSOC can only have generate name + const char *label = istr(tree_ident(a)); + printf("%s: ", label); +#endif switch (tree_subkind(a)) { case A_NAMED: syntax("#when "); diff --git a/src/jit/jit-exits.c b/src/jit/jit-exits.c index 3725f031..083de6f8 100644 --- a/src/jit/jit-exits.c +++ b/src/jit/jit-exits.c @@ -1466,3 +1466,9 @@ void __nvc_putpriv(jit_handle_t handle, void *data) jit_put_privdata(j, f, data); } + +DLLEXPORT +void __nvc_setup_toggle_cb(sig_shared_t *ss, int32_t* toggle_mask) +{ + x_cover_setup_toggle_cb(ss, toggle_mask); +} diff --git a/src/jit/jit-exits.h b/src/jit/jit-exits.h index 92593ed9..d7de562e 100644 --- a/src/jit/jit-exits.h +++ b/src/jit/jit-exits.h @@ -93,5 +93,6 @@ void x_resolve_signal2(sig_shared_t *ss, jit_handle_t handle, void *context, void x_elab_order_fail(tree_t where); void x_unreachable(tree_t where); void *x_mspace_alloc(uint32_t size, uint32_t nelems); +void x_cover_setup_toggle_cb(sig_shared_t *ss, int32_t *toggle_mask); #endif // _JIT_EXITS_H diff --git a/src/lower.c b/src/lower.c index 27961cc6..dd1cc4e4 100644 --- a/src/lower.c +++ b/src/lower.c @@ -111,7 +111,7 @@ typedef struct { bool is_const; } map_signal_param_t; -typedef void (*lower_field_fn_t)(type_t, vcode_reg_t, vcode_reg_t, void *); +typedef void (*lower_field_fn_t)(tree_t, vcode_reg_t, vcode_reg_t, void *); typedef A(concat_param_t) concat_list_t; @@ -937,12 +937,12 @@ static void lower_for_each_field(type_t type, vcode_reg_t rec1_ptr, const int nfields = type_fields(type); for (int i = 0; i < nfields; i++) { - type_t ftype = tree_type(type_field(type, i)); + tree_t f = type_field(type, i); vcode_reg_t f1_reg = emit_record_ref(rec1_ptr, i); vcode_reg_t f2_reg = VCODE_INVALID_REG; if (rec2_ptr != VCODE_INVALID_REG) f2_reg = emit_record_ref(rec2_ptr, i); - (*fn)(ftype, f1_reg, f2_reg, context); + (*fn)(f, f1_reg, f2_reg, context); } } } @@ -989,9 +989,10 @@ static vcode_reg_t lower_coerce_arrays(type_t from, type_t to, vcode_reg_t reg) return reg; } -static void lower_resolved_field_cb(type_t ftype, vcode_reg_t field_ptr, +static void lower_resolved_field_cb(tree_t field, vcode_reg_t field_ptr, vcode_reg_t dst_ptr, void *__ctx) { + type_t ftype = tree_type(field); if (!type_is_homogeneous(ftype)) { if (lower_have_uarray_ptr(dst_ptr)) { // Need to allocate memory for the array @@ -1167,9 +1168,10 @@ static vcode_reg_t lower_subprogram_arg(tree_t fcall, unsigned nth) return preg; } -static void lower_signal_flag_field_cb(type_t ftype, vcode_reg_t field_ptr, +static void lower_signal_flag_field_cb(tree_t field, vcode_reg_t field_ptr, vcode_reg_t unused, void *__ctx) { + type_t ftype = tree_type(field); if (!type_is_homogeneous(ftype)) lower_for_each_field(ftype, field_ptr, VCODE_INVALID_REG, lower_signal_flag_field_cb, __ctx); @@ -1493,21 +1495,133 @@ static vcode_reg_t lower_arith(tree_t fcall, subprogram_kind_t kind, return lower_narrow(tree_type(fcall), result); } -static void lower_cond_coverage(tree_t test, vcode_reg_t value) +static void lower_branch_coverage(tree_t b, unsigned int flags, + vcode_reg_t hit_reg) { - int32_t cover_tag, sub_cond; - if (cover_is_tagged(cover_tags, test, &cover_tag, &sub_cond)) - emit_cover_cond(value, cover_tag, sub_cond); + assert(cover_enabled(cover_tags, COVER_MASK_BRANCH)); + + int32_t tag = cover_add_tag(b, NULL, cover_tags, TAG_BRANCH, flags)->tag; + emit_cover_branch(hit_reg, tag); } -static vcode_reg_t lower_logical(tree_t fcall, vcode_reg_t result) +static int32_t lower_toggle_tag_for(type_t type, tree_t where, ident_t prefix, int dims) { - int32_t cover_tag, sub_cond; - if (!cover_is_tagged(cover_tags, fcall, &cover_tag, &sub_cond)) - return result; + type_t root = type; + + // Gets well known type for scalar and vectorized version of + // standard types (std_[u]logic[_vector], signed, unsigned) + if (type_base_kind(type) == T_ARRAY) + root = type_elem(type); + root = type_base_recur(root); + + well_known_t known = is_well_known(type_ident(root)); + if (known != W_IEEE_ULOGIC && known != W_IEEE_ULOGIC_VECTOR) + return -1; + + unsigned int flags = 0; + if (tree_kind(where) == T_SIGNAL_DECL) + flags = COV_FLAG_TOGGLE_SIGNAL; + else + flags = COV_FLAG_TOGGLE_PORT; + + if (type_is_array(type)) { + tree_t r = range_of(type, dims - 1); + int32_t first_tag = 0; + int64_t low, high; + + if (folded_bounds(r, &low, &high)) { + assert(low <= high); + for (int64_t i = low; i <= high; i++) { + char arr_index[16] = {0}; + int32_t tmp; + checked_sprintf(arr_index, sizeof(arr_index), "(%lu)", i); + ident_t arr_suffix = ident_prefix(prefix, ident_new(arr_index), '\0'); + + if (dims == 1) { + tmp = cover_add_tag(where, arr_suffix, cover_tags, TAG_TOGGLE, flags)->tag; + if (i == low) + first_tag = tmp; + } + else { + tmp = lower_toggle_tag_for(type, where, arr_suffix, dims - 1); + if (i == low) + first_tag = tmp; + } + } + } + + return first_tag; + } + else + return cover_add_tag(where, NULL, cover_tags, TAG_TOGGLE, flags)->tag; +} + +static void lower_toggle_coverage_cb(tree_t field, vcode_reg_t field_ptr, + vcode_reg_t unused, void *ctx) +{ + type_t ftype = tree_type(field); + + cover_push_scope(cover_tags, field); + + if (!type_is_homogeneous(ftype)) + lower_for_each_field(ftype, field_ptr, VCODE_INVALID_REG, + lower_toggle_coverage_cb, NULL); + else { + const int ndims = dimension_of(ftype); + int32_t tag = lower_toggle_tag_for(ftype, field, NULL, ndims); + if (tag == -1) { + cover_pop_scope(cover_tags); + return; + } + + vcode_reg_t nets_reg = emit_load_indirect(field_ptr); + emit_cover_toggle(nets_reg, tag); + } + + cover_pop_scope(cover_tags); +} + +static void lower_toggle_coverage(tree_t decl) +{ + assert(cover_enabled(cover_tags, COVER_MASK_TOGGLE)); + + int hops = 0; + vcode_var_t var = lower_search_vcode_obj(decl, top_scope, &hops); + assert(var != VCODE_INVALID_VAR); + assert(hops == 0); + + cover_push_scope(cover_tags, decl); + + type_t type = tree_type(decl); + if (!type_is_homogeneous(type)) { + vcode_reg_t rec_ptr = emit_index(var, VCODE_INVALID_REG); + lower_for_each_field(type, rec_ptr, VCODE_INVALID_REG, + lower_toggle_coverage_cb, NULL); + } + else { + int32_t tag = lower_toggle_tag_for(type, decl, NULL, dimension_of(type)); + if (tag == -1) { + cover_pop_scope(cover_tags); + return; + } - if (sub_cond > 0) - emit_cover_cond(result, cover_tag, sub_cond); + vcode_reg_t nets_reg = emit_load(var); + emit_cover_toggle(nets_reg, tag); + } + + cover_pop_scope(cover_tags); +} + +static vcode_reg_t lower_logical(tree_t fcall, vcode_reg_t result) +{ + // TODO: Change to expression coverage, not branch! + // + //int32_t cover_tag, sub_cond; + //if (!cover_is_tagged(cover_tags, fcall, &cover_tag, &sub_cond)) + // return result; + // + //if (sub_cond > 0) + // emit_cover_cond(result, cover_tag, sub_cond); return result; } @@ -3667,9 +3781,10 @@ static vcode_reg_t lower_record_ref(tree_t expr, expr_ctx_t ctx) return f_reg; } -static void lower_new_field_cb(type_t ftype, vcode_reg_t dst_ptr, +static void lower_new_field_cb(tree_t field, vcode_reg_t dst_ptr, vcode_reg_t src_ptr, void *ctx) { + type_t ftype = tree_type(field); if (!type_is_composite(ftype) || lower_const_bounds(ftype)) return; // Already allocated else if (type_is_array(ftype)) { @@ -4579,9 +4694,10 @@ static void lower_assert(tree_t stmt) } } -static void lower_sched_event_field_cb(type_t type, vcode_reg_t ptr, +static void lower_sched_event_field_cb(tree_t field, vcode_reg_t ptr, vcode_reg_t unused, void *__ctx) { + type_t type = tree_type(field); if (!type_is_homogeneous(type)) lower_for_each_field(type, ptr, VCODE_INVALID_REG, lower_sched_event_field_cb, __ctx); @@ -4858,10 +4974,11 @@ static void lower_fill_target_parts(tree_t target, part_kind_t kind, } } -static void lower_copy_record_cb(type_t ftype, vcode_reg_t dst_ptr, +static void lower_copy_record_cb(tree_t field, vcode_reg_t dst_ptr, vcode_reg_t src_ptr, void *ctx) { tree_t where = ctx; + type_t ftype = tree_type(field); if (type_is_scalar(ftype) || type_is_access(ftype)) { vcode_reg_t src_reg = emit_load_indirect(src_ptr); @@ -5066,9 +5183,11 @@ static void lower_var_assign(tree_t stmt) } } -static void lower_signal_target_field_cb(type_t type, vcode_reg_t dst_ptr, +static void lower_signal_target_field_cb(tree_t field, vcode_reg_t dst_ptr, vcode_reg_t src_ptr, void *__ctx) { + type_t type = tree_type(field); + struct { vcode_reg_t reject; vcode_reg_t after; @@ -5310,9 +5429,10 @@ static void lower_signal_assign(tree_t stmt) } } -static void lower_force_field_cb(type_t type, vcode_reg_t ptr, +static void lower_force_field_cb(tree_t field, vcode_reg_t ptr, vcode_reg_t value, void *__ctx) { + type_t type = tree_type(field); if (type_is_homogeneous(type)) { vcode_reg_t nets_reg = emit_load_indirect(ptr); vcode_reg_t count_reg = lower_type_width(type, nets_reg); @@ -5361,24 +5481,25 @@ static void lower_release(tree_t stmt) emit_release(nets, emit_const(vtype_offset(), 1)); } -static vcode_reg_t lower_test_expr(tree_t value) -{ - vcode_reg_t test = lower_rvalue(value); - lower_cond_coverage(value, test); - return test; -} - static void lower_if(tree_t stmt, loop_stack_t *loops) { vcode_block_t exit_bb = VCODE_INVALID_BLOCK; const int nconds = tree_conds(stmt); for (int i = 0; i < nconds; i++) { - tree_t c = tree_cond(stmt, i); + tree_t c = tree_cond(stmt, i); vcode_block_t next_bb = VCODE_INVALID_BLOCK; + cover_push_scope(cover_tags, c); + if (tree_has_value(c)) { - vcode_reg_t test = lower_test_expr(tree_value(c)); + tree_t v = tree_value(c); + vcode_reg_t test = lower_rvalue(v); + + if (cover_enabled(cover_tags, COVER_MASK_BRANCH)) + lower_branch_coverage(v, COV_FLAG_HAS_FALSE | COV_FLAG_HAS_TRUE, + test); + vcode_block_t btrue = emit_block(); if (i == nconds - 1) { @@ -5403,6 +5524,8 @@ static void lower_if(tree_t stmt, loop_stack_t *loops) emit_jump(exit_bb); } + cover_pop_scope(cover_tags); + if (next_bb == VCODE_INVALID_BLOCK) break; else @@ -5704,6 +5827,7 @@ static void lower_for(tree_t stmt, loop_stack_t *loops) static void lower_while(tree_t stmt, loop_stack_t *loops) { vcode_block_t test_bb, body_bb, exit_bb; + if (tree_has_value(stmt)) { test_bb = emit_block(); body_bb = emit_block(); @@ -5712,8 +5836,13 @@ static void lower_while(tree_t stmt, loop_stack_t *loops) emit_jump(test_bb); vcode_select_block(test_bb); + tree_t v = tree_value(stmt); + vcode_reg_t test = lower_rvalue(v); + + if (cover_enabled(cover_tags, COVER_MASK_BRANCH)) + lower_branch_coverage(v, COV_FLAG_HAS_FALSE | COV_FLAG_HAS_TRUE, + test); - vcode_reg_t test = lower_test_expr(tree_value(stmt)); emit_cond(test, body_bb, exit_bb); } else { @@ -5725,6 +5854,7 @@ static void lower_while(tree_t stmt, loop_stack_t *loops) } vcode_select_block(body_bb); + cover_push_scope(cover_tags, stmt); loop_stack_t this = { .up = loops, @@ -5741,6 +5871,7 @@ static void lower_while(tree_t stmt, loop_stack_t *loops) emit_jump(test_bb); vcode_select_block(exit_bb); + cover_pop_scope(cover_tags); } static void lower_sequence(tree_t block, loop_stack_t *loops) @@ -5756,8 +5887,13 @@ static void lower_loop_control(tree_t stmt, loop_stack_t *loops) if (tree_has_value(stmt)) { vcode_block_t true_bb = emit_block(); + tree_t v = tree_value(stmt); + vcode_reg_t result = lower_rvalue(v); + + if (cover_enabled(cover_tags, COVER_MASK_BRANCH)) + lower_branch_coverage(v, COV_FLAG_HAS_FALSE | COV_FLAG_HAS_TRUE, + result); - vcode_reg_t result = lower_test_expr(tree_value(stmt)); emit_cond(result, true_bb, false_bb); vcode_select_block(true_bb); @@ -5780,6 +5916,35 @@ static void lower_loop_control(tree_t stmt, loop_stack_t *loops) vcode_select_block(false_bb); } +static void lower_case_others_branch_coverage(tree_t stmt, vcode_reg_t *hit_regs) +{ + assert (tree_kind(stmt) == T_CASE); + + tree_t a_others = NULL; + vcode_reg_t merged = emit_const(vtype_bool(), 1); + int nassocs = tree_assocs(stmt); + + for (int i = 0; i < nassocs; i++) { + tree_t a = tree_assoc(stmt, i); + const assoc_kind_t kind = tree_subkind(a); + + if (kind == A_OTHERS) { + a_others = a; + continue; + } + + // Satisfied by caller to precompute comparisons with case alternatives + assert (hit_regs[i]); + + vcode_reg_t inverted = emit_not(hit_regs[i]); + merged = emit_and(merged, inverted); + } + + cover_push_scope(cover_tags, a_others); + lower_branch_coverage(a_others, COV_FLAG_HAS_TRUE, merged); + cover_pop_scope(cover_tags); +} + static void lower_case_scalar(tree_t stmt, loop_stack_t *loops) { const int nassocs = tree_assocs(stmt); @@ -5789,14 +5954,18 @@ static void lower_case_scalar(tree_t stmt, loop_stack_t *loops) vcode_block_t hit_bb = VCODE_INVALID_BLOCK; vcode_reg_t value_reg = lower_rvalue(tree_value(stmt)); + vcode_reg_t hit_regs[nassocs]; tree_t last = NULL; for (int i = 0; i < nassocs; i++) { tree_t a = tree_assoc(stmt, i); + // Pre-filter range choices in case the number of elements is large if (tree_subkind(a) == A_RANGE) { - // Pre-filter range choices in case the number of elements is large + + cover_push_scope(cover_tags, a); + tree_t r = tree_range(a, 0); vcode_reg_t left_reg = lower_range_left(r); vcode_reg_t right_reg = lower_range_right(r); @@ -5807,14 +5976,17 @@ static void lower_case_scalar(tree_t stmt, loop_stack_t *loops) vcode_reg_t lcmp_reg = emit_cmp(VCODE_CMP_GEQ, value_reg, low_reg); vcode_reg_t hcmp_reg = emit_cmp(VCODE_CMP_LEQ, value_reg, high_reg); - vcode_reg_t hit_reg = emit_and(lcmp_reg, hcmp_reg); + hit_regs[i] = emit_and(lcmp_reg, hcmp_reg); + + if (cover_enabled(cover_tags, COVER_MASK_BRANCH)) + lower_branch_coverage(a, COV_FLAG_HAS_TRUE, hit_regs[i]); vcode_block_t skip_bb = emit_block(); tree_t block = tree_value(a); if (block != last) hit_bb = emit_block(); - emit_cond(hit_reg, hit_bb, skip_bb); + emit_cond(hit_regs[i], hit_bb, skip_bb); if (stmt != last) { vcode_select_block(hit_bb); @@ -5825,6 +5997,8 @@ static void lower_case_scalar(tree_t stmt, loop_stack_t *loops) last = block; vcode_select_block(skip_bb); + + cover_pop_scope(cover_tags); } } @@ -5837,6 +6011,7 @@ static void lower_case_scalar(tree_t stmt, loop_stack_t *loops) hit_bb = VCODE_INVALID_BLOCK; int cptr = 0; + bool assoc_others = false; for (int i = 0; i < nassocs; i++) { tree_t a = tree_assoc(stmt, i); const assoc_kind_t kind = tree_subkind(a); @@ -5844,14 +6019,23 @@ static void lower_case_scalar(tree_t stmt, loop_stack_t *loops) if (kind == A_RANGE) continue; // Handled separately above + cover_push_scope(cover_tags, a); + tree_t block = tree_value(a); if (block != last) hit_bb = emit_block(); - if (kind == A_OTHERS) + if (kind == A_OTHERS) { def_bb = hit_bb; - else { + assoc_others = true; + } else { vcode_select_block(start_bb); cases[cptr] = lower_rvalue(tree_name(a)); + + if (cover_enabled(cover_tags, COVER_MASK_BRANCH)) { + hit_regs[i] = emit_cmp(VCODE_CMP_EQ, cases[cptr], value_reg); + lower_branch_coverage(a, COV_FLAG_HAS_TRUE, hit_regs[i]); + } + blocks[cptr] = hit_bb; cptr++; } @@ -5863,6 +6047,8 @@ static void lower_case_scalar(tree_t stmt, loop_stack_t *loops) emit_jump(exit_bb); } + cover_pop_scope(cover_tags); + last = block; } @@ -5870,8 +6056,11 @@ static void lower_case_scalar(tree_t stmt, loop_stack_t *loops) def_bb = exit_bb; vcode_select_block(start_bb); - emit_case(value_reg, def_bb, cases, blocks, cptr); + if (cover_enabled(cover_tags, COVER_MASK_BRANCH) && assoc_others) + lower_case_others_branch_coverage(stmt, hit_regs); + + emit_case(value_reg, def_bb, cases, blocks, cptr); vcode_select_block(exit_bb); } @@ -5997,6 +6186,7 @@ static void lower_case_array(tree_t stmt, loop_stack_t *loops) vcode_reg_t *cases LOCAL = xcalloc_array(nassocs, sizeof(vcode_reg_t)); vcode_block_t *blocks LOCAL = xcalloc_array(nassocs, sizeof(vcode_block_t)); int64_t *encoding LOCAL = xcalloc_array(nassocs, sizeof(int64_t)); + vcode_reg_t hit_regs[nassocs]; tree_t last = NULL; ident_t cmp_func = NULL; @@ -6010,6 +6200,7 @@ static void lower_case_array(tree_t stmt, loop_stack_t *loops) int ndups = 0; int cptr = 0; + bool has_others = true; for (int i = 0; i < nassocs; i++) { tree_t a = tree_assoc(stmt, i); const assoc_kind_t kind = tree_subkind(a); @@ -6018,8 +6209,12 @@ static void lower_case_array(tree_t stmt, loop_stack_t *loops) tree_t block = tree_value(a); if (block != last) hit_bb = emit_block(); - if (kind == A_OTHERS) + cover_push_scope(cover_tags, a); + + if (kind == A_OTHERS) { def_bb = hit_bb; + has_others = true; + } else { tree_t name = tree_name(a); int64_t enc = encode_case_choice(name, length, exact_map ? nbits : 0); @@ -6057,6 +6252,13 @@ static void lower_case_array(tree_t stmt, loop_stack_t *loops) if (!have_dup) { vcode_select_block(start_bb); cases[cptr] = emit_const(enc_type, enc); + + // TODO: How to handle have_dup == true ? + if (cover_enabled(cover_tags, COVER_MASK_BRANCH)) { + hit_regs[i] = emit_cmp(VCODE_CMP_EQ, cases[cptr], enc_reg); + lower_branch_coverage(a, COV_FLAG_HAS_TRUE, hit_regs[i]); + } + blocks[cptr] = entry_bb; encoding[cptr] = enc; cptr++; @@ -6071,6 +6273,8 @@ static void lower_case_array(tree_t stmt, loop_stack_t *loops) } last = block; + + cover_pop_scope(cover_tags); } if (def_bb == VCODE_INVALID_BLOCK) @@ -6082,6 +6286,10 @@ static void lower_case_array(tree_t stmt, loop_stack_t *loops) } vcode_select_block(start_bb); + + if (cover_enabled(cover_tags, COVER_MASK_BRANCH) && has_others) + lower_case_others_branch_coverage(stmt, hit_regs); + emit_case(enc_reg, def_bb, cases, blocks, cptr); vcode_select_block(exit_bb); @@ -6240,9 +6448,11 @@ static void lower_stmt(tree_t stmt, loop_stack_t *loops) if (vcode_block_finished()) return; // Unreachable - int32_t stmt_tag; - if (cover_is_tagged(cover_tags, stmt, &stmt_tag, NULL)) - emit_cover_stmt(stmt_tag); + cover_push_scope(cover_tags, stmt); + if (cover_enabled(cover_tags, COVER_MASK_STMT) && cover_is_stmt(stmt)) { + int32_t tag = cover_add_tag(stmt, NULL, cover_tags, TAG_STMT, 0)->tag; + emit_cover_stmt(tag); + } emit_debug_info(tree_loc(stmt)); @@ -6298,6 +6508,8 @@ static void lower_stmt(tree_t stmt, loop_stack_t *loops) fatal_at(tree_loc(stmt), "cannot lower statement kind %s", tree_kind_str(tree_kind(stmt))); } + + cover_pop_scope(cover_tags); } static void lower_check_indexes(type_t type, vcode_reg_t array) @@ -6749,6 +6961,9 @@ static void lower_signal_decl(tree_t decl) lower_sub_signals(type, decl, cons, ncons, value_type, var, VCODE_INVALID_REG, init_reg, VCODE_INVALID_REG, VCODE_INVALID_REG, flags_reg); + + if (cover_enabled(cover_tags, COVER_MASK_TOGGLE)) + lower_toggle_coverage(decl); } static ident_t lower_guard_func(ident_t prefix, tree_t expr) @@ -7474,12 +7689,14 @@ static void lower_protected_body(tree_t body, vcode_unit_t context) vcode_unit_t vu = emit_protected(type_ident(type), tree_loc(body), context); lower_push_scope(body); + cover_push_scope(cover_tags, body); lower_decls(body, vu); emit_return(VCODE_INVALID_REG); lower_finished(); lower_pop_scope(); + cover_pop_scope(cover_tags); } static void lower_decls(tree_t scope, vcode_unit_t context) @@ -8705,6 +8922,7 @@ static void lower_proc_body(tree_t body, vcode_unit_t context) vu = emit_procedure(name, tree_loc(body), context); lower_push_scope(body); + cover_push_scope(cover_tags, body); vcode_type_t vcontext = vtype_context(context_id); emit_param(vcontext, vcontext, ident_new("context")); @@ -8728,6 +8946,7 @@ static void lower_proc_body(tree_t body, vcode_unit_t context) lower_finished(); lower_pop_scope(); + cover_pop_scope(cover_tags); if (vcode_unit_has_undefined()) vcode_unit_unref(vu); @@ -8755,6 +8974,7 @@ static void lower_func_body(tree_t body, vcode_unit_t context) emit_param(vcontext, vcontext, ident_new("context")); lower_push_scope(body); + cover_push_scope(cover_tags, body); if (tree_kind(body) == T_FUNC_INST) lower_generics(body, NULL); @@ -8773,13 +8993,15 @@ static void lower_func_body(tree_t body, vcode_unit_t context) lower_finished(); lower_pop_scope(); + cover_pop_scope(cover_tags); return; } -static void lower_driver_field_cb(type_t type, vcode_reg_t ptr, +static void lower_driver_field_cb(tree_t field, vcode_reg_t ptr, vcode_reg_t unused, void *__ctx) { + type_t type = tree_type(field); if (type_is_homogeneous(type)) { vcode_reg_t nets_reg = emit_load_indirect(ptr); vcode_reg_t count_reg = lower_type_width(type, nets_reg); @@ -8857,7 +9079,8 @@ static void lower_driver_cb(tree_t t, void *__ctx) static void lower_process(tree_t proc, vcode_unit_t context) { vcode_select_unit(context); - ident_t name = ident_prefix(vcode_unit_name(), tree_ident(proc), '.'); + ident_t label = tree_ident(proc); + ident_t name = ident_prefix(vcode_unit_name(), label, '.'); vcode_unit_t vu = emit_process(name, tree_loc(proc), context); emit_debug_info(tree_loc(proc)); @@ -8867,6 +9090,7 @@ static void lower_process(tree_t proc, vcode_unit_t context) vcode_block_t start_bb = emit_block(); assert(start_bb == 1); + cover_push_scope(cover_tags, proc); lower_push_scope(proc); lower_decls(proc, vu); @@ -8899,6 +9123,7 @@ static void lower_process(tree_t proc, vcode_unit_t context) lower_finished(); lower_pop_scope(); + cover_pop_scope(cover_tags); } static bool lower_is_signal_ref(tree_t expr) @@ -9036,9 +9261,10 @@ static ident_t lower_converter(tree_t expr, type_t atype, type_t rtype, return name; } -static void lower_map_signal_field_cb(type_t ftype, vcode_reg_t src_ptr, +static void lower_map_signal_field_cb(tree_t field, vcode_reg_t src_ptr, vcode_reg_t dst_ptr, void *__ctx) { + type_t ftype = tree_type(field); map_signal_param_t *args = __ctx; if (type_is_homogeneous(ftype)) { @@ -9444,6 +9670,11 @@ static void lower_ports(tree_t block) } } + if (cover_enabled(cover_tags, COVER_MASK_TOGGLE)) { + for (int i = 0; i < nports; i++) + lower_toggle_coverage(tree_port(block, i)); + } + hset_free(direct); if (poison != NULL) hset_free(poison); @@ -9591,12 +9822,21 @@ static vcode_unit_t lower_concurrent_block(tree_t block, vcode_unit_t context) vcode_select_unit(context); ident_t prefix = context ? vcode_unit_name() : lib_name(lib_work()); - ident_t name = ident_prefix(prefix, tree_ident(block), '.'); + ident_t label = tree_ident(block); + ident_t name = ident_prefix(prefix, label, '.'); const loc_t *loc = tree_loc(block); vcode_unit_t vu = emit_instance(name, loc, context); emit_debug_info(loc); + if (cover_enabled(cover_tags, COVER_MASK_ALL)) { + if (!context) + cover_reset_scope(cover_tags, prefix); + + cover_push_scope(cover_tags, block); + cover_add_tag(block, NULL, cover_tags, TAG_HIER, COV_FLAG_HIER_DOWN); + } + lower_push_scope(block); lower_dependencies(block, NULL); lower_generics(block, NULL); @@ -9623,6 +9863,12 @@ static vcode_unit_t lower_concurrent_block(tree_t block, vcode_unit_t context) } lower_pop_scope(); + + if (cover_enabled(cover_tags, COVER_MASK_ALL)) { + cover_add_tag(block, NULL, cover_tags, TAG_HIER, COV_FLAG_HIER_UP); + cover_pop_scope(cover_tags); + } + return vu; } diff --git a/src/nvc.c b/src/nvc.c index e224cc8d..4043f1c4 100644 --- a/src/nvc.c +++ b/src/nvc.c @@ -81,7 +81,7 @@ static ident_t to_unit_name(const char *str) static int scan_cmd(int start, int argc, char **argv) { const char *commands[] = { - "-a", "-e", "-r", "--dump", "--make", "--syntax", "--list", "--init", + "-a", "-e", "-r", "-c", "--dump", "--make", "--syntax", "--list", "--init", "--install", }; @@ -219,17 +219,57 @@ static void set_top_level(char **argv, int next_cmd) } } +static cover_mask_t parse_cover_mask(const char *str) +{ + char prev = 0; + int n_chars = 0; + const char *full_cov_opt = str; + cover_mask_t rv = 0; + while (1) { + if (*str == ',' || *str == '\0') { + if (prev == 's') + rv |= COVER_MASK_STMT; + else if (prev == 't') + rv |= COVER_MASK_TOGGLE; + else if (prev == 'b') + rv |= COVER_MASK_BRANCH; + else { + diag_t *d = diag_new(DIAG_FATAL, NULL); + diag_printf(d, "unknown coverage type '%c'", *str); + diag_hint(d, NULL, "valid coverage types are: \n" + " s (statement)\n" + " t (toggle)\n" + " b (branch)"); + diag_hint(d, NULL, "selected coverage types shall be " + "comma separated e.g $bold$--cover=s,t,b$$"); + diag_emit(d); + fatal_exit(EXIT_FAILURE); + } + n_chars = 0; + if (*str == '\0') + break; + } + n_chars++; + if (n_chars >= 3) + fatal("Invalid coverage type: '%s'.", full_cov_opt); + prev = *str; + str++; + } + return rv; +} + static int elaborate(int argc, char **argv) { static struct option long_options[] = { { "dump-llvm", no_argument, 0, 'd' }, { "dump-vcode", optional_argument, 0, 'v' }, - { "cover", no_argument, 0, 'c' }, + { "cover", optional_argument, 0, 'c' }, { "verbose", no_argument, 0, 'V' }, { "no-save", no_argument, 0, 'N' }, { 0, 0, 0, 0 } }; + cover_mask_t cover_mask = 0; const int next_cmd = scan_cmd(2, argc, argv); int c, index = 0; const char *spec = "Vg:O:"; @@ -251,7 +291,10 @@ static int elaborate(int argc, char **argv) opt_set_str(OPT_DUMP_VCODE, optarg ?: ""); break; case 'c': - opt_set_int(OPT_COVER, 1); + if (optarg) + cover_mask = parse_cover_mask(optarg); + else + cover_mask = COVER_MASK_ALL; break; case 'V': opt_set_int(OPT_VERBOSE, 1); @@ -290,14 +333,19 @@ static int elaborate(int argc, char **argv) progress("elaborating design"); cover_tagging_t *cover = NULL; - if (opt_get_int(OPT_COVER)) { - cover = cover_tag(top); - progress("generating coverage information"); - } + if (cover_mask != 0) + cover = cover_tags_init(cover_mask); vcode_unit_t vu = lower_unit(top, cover); progress("generating intermediate code"); + if (cover != NULL) { + fbuf_t *covdb = cover_open_lib_file(top, FBUF_OUT, true); + cover_dump_tags(cover, covdb, COV_DUMP_ELAB, NULL, NULL, NULL); + fbuf_close(covdb, NULL); + progress("dumping coverage data"); + } + if (error_count() > 0) return EXIT_FAILURE; @@ -866,6 +914,71 @@ static int dump_cmd(int argc, char **argv) return argc > 1 ? process_command(argc, argv) : EXIT_SUCCESS; } +static int coverage(int argc, char **argv) +{ + static struct option long_options[] = { + { "report", required_argument, 0, 'r' }, + { "merge", required_argument, 0, 'm' }, + { 0, 0, 0, 0 } + }; + + const char *out_db = NULL, *rpt_file = NULL; + int c, index; + const char *spec = "V"; + + while ((c = getopt_long(argc, argv, spec, long_options, &index)) != -1) { + switch (c) { + case 'r': + rpt_file = optarg; + break; + case 'm': + out_db = optarg; + break; + case 'V': + opt_set_int(OPT_VERBOSE, 1); + break; + case '?': + bad_option("coverage", argv); + default: + abort(); + } + } + + progress("initialising"); + + if (optind == argc) + fatal("No input coverage database FILE specified"); + + cover_tagging_t *cover = NULL; + + // Rest of inputs are coverage input files + for (int i = optind; i < argc; i++) { + progress("Loading input coverage database: %s", argv[i]); + fbuf_t *f = fbuf_open(argv[i], FBUF_IN, FBUF_CS_NONE); + + if (i == optind) + cover = cover_read_tags(f); + else + cover_merge_tags(f, cover); + + fbuf_close(f, NULL); + } + + if (out_db) { + progress("Saving merged coverage database to: %s", out_db); + fbuf_t *f = fbuf_open(out_db, FBUF_OUT, FBUF_CS_NONE); + cover_dump_tags(cover, f, COV_DUMP_PROCESSING, NULL, NULL, NULL); + fbuf_close(f, NULL); + } + + if (rpt_file && cover) { + progress("Generating coverage report to folder: %s.", rpt_file); + cover_report(rpt_file, cover); + } + + return 0; +} + static void usage(void) { printf("Usage: %s [OPTION]... COMMAND [OPTION]...\n" @@ -874,6 +987,8 @@ static void usage(void) " -a [OPTION]... FILE...\t\tAnalyse FILEs into work library\n" " -e [OPTION]... UNIT\t\tElaborate and generate code for UNIT\n" " -r [OPTION]... UNIT\t\tExecute previously elaborated UNIT\n" + " -c [OPTION]... FILE...\t\tProcess code coverage from FILEs\n" + " \t\t'covdb' coverage databases.\n" " --dump [OPTION]... UNIT\tPrint out previously analysed UNIT\n" " --init\t\t\t\tInitialise work library directory\n" " --install PKG\t\t\tInstall third-party packages\n" @@ -901,7 +1016,14 @@ static void usage(void) " --relaxed\t\tDisable certain pedantic rule checks\n" "\n" "Elaborate options:\n" - " --cover\t\tEnable code coverage reporting\n" + " --cover=\tEnable code coverage collection.\n" + " \t is comma separated list\n" + " \tof coverage types to collect:\n" + " \t s - Statement coverage\n" + " \t t - Toggle coverage\n" + " \t b - Branch coverage\n" + " \t Ommiting '=' collects all\n" + " \t coverage types.\n" " --dump-llvm\tDump generated LLVM IR\n" " --dump-vcode\tPrint generated intermediate code\n" " -g NAME=VALUE\t\tSet top level generic NAME to VALUE\n" @@ -927,6 +1049,12 @@ static void usage(void) " --vhpi-trace\tTrace VHPI calls and events\n" " -w, --wave=FILE\tWrite waveform data; file name is optional\n" "\n" + "Coverage processing options:\n" + " --merge=OUTPUT\tMerge all input coverage databases from FILEs\n" + " to OUTPUT coverage database.\n" + " --report=DIR\tGenerate HTML report with code coverage results\n" + " \tto DIR folder.\n" + "\n" "Dump options:\n" " -e, --elab\t\tDump an elaborated unit\n" " -b, --body\t\tDump package body\n" @@ -1065,7 +1193,7 @@ static int process_command(int argc, char **argv) optind = 1; int index = 0; - const char *spec = "aer"; + const char *spec = "aerc"; switch (getopt_long(MIN(argc, 2), argv, spec, long_options, &index)) { case 'a': return analyse(argc, argv); @@ -1073,6 +1201,8 @@ static int process_command(int argc, char **argv) return elaborate(argc, argv); case 'r': return run(argc, argv); + case 'c': + return coverage(argc, argv); case 'd': return dump_cmd(argc, argv); case 'm': @@ -1124,7 +1254,7 @@ int main(int argc, char **argv) const int next_cmd = scan_cmd(1, argc, argv); int c, index = 0; - const char *spec = "aehrvL:M:P:G:H:"; + const char *spec = "aehrcvL:M:P:G:H:"; while ((c = getopt_long(next_cmd, argv, spec, long_options, &index)) != -1) { switch (c) { case 0: diff --git a/src/opt.c b/src/opt.c index fc3582ba..2d6ea4fe 100644 --- a/src/opt.c +++ b/src/opt.c @@ -111,7 +111,6 @@ void set_default_options(void) opt_set_int(OPT_DUMP_LLVM, 0); opt_set_int(OPT_OPTIMISE, 2); opt_set_int(OPT_BOOTSTRAP, 0); - opt_set_int(OPT_COVER, 0); opt_set_int(OPT_STOP_DELTA, 10000); opt_set_int(OPT_UNIT_TEST, 0); opt_set_int(OPT_MAKE_DEPS_ONLY, 0); diff --git a/src/opt.h b/src/opt.h index c382f955..7fa0cf2c 100644 --- a/src/opt.h +++ b/src/opt.h @@ -29,7 +29,6 @@ typedef enum { OPT_DUMP_LLVM, OPT_OPTIMISE, OPT_BOOTSTRAP, - OPT_COVER, OPT_STOP_DELTA, OPT_UNIT_TEST, OPT_MAKE_DEPS_ONLY, diff --git a/src/parse.c b/src/parse.c index 8879aafd..6e0c55cc 100644 --- a/src/parse.c +++ b/src/parse.c @@ -8424,6 +8424,8 @@ static void p_waveform(tree_t stmt, type_t constraint) while (optional(tCOMMA)) tree_add_waveform(stmt, p_waveform_element(constraint)); + + tree_set_loc(stmt, CURRENT_LOC); } static tree_t p_delay_mechanism(void) @@ -9260,7 +9262,7 @@ static tree_t p_component_instantiation_statement(ident_t label, tree_t name) tree_set_loc(t, CURRENT_LOC); - if (label == NULL) { + if (label == NULL){ parse_error(CURRENT_LOC, "component instantiation statement must " "have a label"); tree_set_ident(t, error_marker()); @@ -9311,21 +9313,19 @@ static void p_conditional_waveforms(tree_t stmt, tree_t target, tree_t s0) if (a == NULL) { a = tree_new(T_SIGNAL_ASSIGN); tree_set_target(a, target); - p_waveform(a, constraint); } - else + else { s0 = NULL; - - tree_set_loc(a, CURRENT_LOC); - tree_set_loc(c, CURRENT_LOC); - + tree_set_loc(a, CURRENT_LOC); + } tree_add_stmt(c, a); tree_add_cond(stmt, c); if (optional(tWHEN)) { tree_t when = p_condition(); tree_set_value(c, when); + tree_set_loc(c, tree_loc(when)); solve_types(nametab, when, std_type(NULL, STD_BOOLEAN)); if (!optional(tELSE)) @@ -9409,7 +9409,6 @@ static void p_selected_waveforms(tree_t stmt, tree_t target, tree_t reject) for (int i = nstart; i < nassocs; i++) tree_set_value(tree_assoc(stmt, i), a); - tree_set_loc(a, CURRENT_LOC); sem_check(a, nametab); } while (optional(tCOMMA)); } diff --git a/src/rt/cover.c b/src/rt/cover.c index b9775446..69501cda 100644 --- a/src/rt/cover.c +++ b/src/rt/cover.c @@ -19,48 +19,80 @@ #include "array.h" #include "common.h" #include "cover.h" -#include "diag.h" -#include "fbuf.h" -#include "hash.h" #include "lib.h" #include "opt.h" #include "type.h" +#include "rt.h" +#include "rt/structs.h" +#include "model.h" #include #include #include #include +#include +#include +#include +#include -#define SUB_COND_BITS 5 -#define MAX_SUB_CONDS (1 << SUB_COND_BITS) -#define SUB_COND_MASK (MAX_SUB_CONDS - 1) +//#define COVER_DEBUG -#define PERCENT_RED 50.0f -#define PERCENT_ORANGE 90.0f +#define MARGIN_LEFT "20%%" +#define SIDEBAR_WIDTH "15%%" -typedef struct _cover_hl cover_hl_t; -typedef struct _cover_file cover_file_t; +typedef A(cover_tag_t) tag_array_t; + +typedef struct _cover_report_ctx cover_report_ctx_t; +typedef struct _cover_file cover_file_t; +typedef struct _cover_scope cover_scope_t; -typedef enum { - HL_HIT, - HL_MISS -} hl_kind_t; +typedef struct _cover_scope { + ident_t name; + int branch_label; + int stmt_label; + cover_scope_t *parent; +} cover_scope_t; -struct _cover_hl { - cover_hl_t *next; - int start; - int end; - hl_kind_t kind; - const char *help; +struct _cover_tagging { + int next_stmt_tag; + int next_branch_tag; + int next_toggle_tag; + int next_hier_tag; + ident_t hier; + tag_array_t tags; + cover_mask_t mask; + cover_scope_t *top_scope; }; +typedef struct { + unsigned total_stmts; + unsigned hit_stmts; + unsigned total_branches; + unsigned hit_branches; + unsigned total_toggles; + unsigned hit_toggles; +} cover_stats_t; + typedef struct { char *text; size_t len; - int hits; - cover_hl_t *hl; } cover_line_t; +typedef struct { + cover_line_t *line; + cover_tag_t *tag; + int flags; +} cover_pair_t; + +typedef struct { + cover_pair_t *hits; + cover_pair_t *miss; + int n_hits; + int n_miss; + int alloc_hits; + int alloc_miss; +} cover_chain_t; + struct _cover_file { const char *name; cover_line_t *lines; @@ -70,75 +102,31 @@ struct _cover_file { cover_file_t *next; }; -typedef enum { TAG_STMT, TAG_COND, TAG_LAST } tag_kind_t; - -typedef struct { - tag_kind_t kind; - int32_t tag; - int32_t sub_cond; - loc_t loc; -} cover_tag_t; - -typedef A(cover_tag_t) tag_array_t; - -struct _cover_tagging { - int next_stmt_tag; - int next_cond_tag; - int next_sub_cond; - hash_t *tree_hash; - tag_array_t tags; +struct _cover_report_ctx { + cover_stats_t flat_stats; + cover_stats_t nested_stats; + cover_report_ctx_t *parent; + cover_tag_t *start_tag; + cover_chain_t ch_stmt; + cover_chain_t ch_branch; + cover_chain_t ch_toggle; }; -typedef struct { - unsigned total_branches; - unsigned hit_branches; - unsigned total_conds; - unsigned hit_conds; - unsigned total_stmts; - unsigned hit_stmts; -} cover_stats_t; - -typedef struct { - const int32_t *stmts; - const int32_t *conds; - cover_tagging_t *tagging; -} report_ctx_t; - static cover_file_t *files; -static cover_stats_t stats; - -static void cover_tag_conditions(tree_t t, cover_tagging_t *ctx, int branch) -{ - const int32_t tag = (branch == -1) ? (ctx->next_cond_tag)++ : branch; - - if (ctx->next_sub_cond == MAX_SUB_CONDS) - return; - - intptr_t enc = 0; - enc |= tag << SUB_COND_BITS; - enc |= ((ctx->next_sub_cond)++ & SUB_COND_MASK); - enc <<= 2; - enc |= 3; - - hash_put(ctx->tree_hash, t, (void *)enc); - - if (tree_kind(t) != T_FCALL) - return; - // Tag Boolean sub-expressions - - if (!is_builtin(tree_subkind(tree_ref(t)))) - return; - - const int nparams = tree_params(t); - for (int i = 0; i < nparams; i++) { - tree_t value = tree_value(tree_param(t, i)); - if (type_ident(tree_type(value)) == well_known(W_STD_BOOL)) - cover_tag_conditions(value, ctx, tag); - } -} +enum std_ulogic { + _U = 0x0, + _X = 0x1, + _0 = 0x2, + _1 = 0x3, + _Z = 0x4, + _W = 0x5, + _L = 0x6, + _H = 0x7, + _DC = 0x8 +}; -static bool cover_is_stmt(tree_t t) +bool cover_is_stmt(tree_t t) { switch (tree_kind(t)) { case T_IF: @@ -148,162 +136,418 @@ static bool cover_is_stmt(tree_t t) case T_SIGNAL_ASSIGN: case T_ASSERT: case T_VAR_ASSIGN: - case T_WAIT: case T_RETURN: + case T_FOR: + case T_PCALL: + case T_FCALL: case T_CASE: return true; + // Static waits are introduced during simp pass. These are hidden + // for user, no need to cover them. + case T_WAIT: + if (tree_flags(t) & TREE_F_STATIC_WAIT) + return false; + return true; + default: return false; } } -static void cover_tag_visit_fn(tree_t t, void *context) +fbuf_t *cover_open_lib_file(tree_t top, fbuf_mode_t mode, bool check_null) { - cover_tagging_t *ctx = context; - - if (cover_is_stmt(t)) { - intptr_t enc = 0; - enc |= (ctx->next_stmt_tag)++ << SUB_COND_BITS; - enc <<= 2; - enc |= 1; - hash_put(ctx->tree_hash, t, (void *)enc); - - ctx->next_sub_cond = 0; - switch (tree_kind(t)) { - case T_IF: - { - const int nconds = tree_conds(t); - for (int i = 0; i < nconds; i++) { - tree_t c = tree_cond(t, i); - if (tree_has_value(c)) - cover_tag_conditions(tree_value(c), ctx, -1); - } - } - break; + char *dbname LOCAL = xasprintf("_%s.covdb", istr(tree_ident(top))); + fbuf_t *f = lib_fbuf_open(lib_work(), dbname, mode, FBUF_CS_NONE); - case T_WHILE: - case T_NEXT: - case T_EXIT: - cover_tag_conditions(tree_value(t), ctx, -1); - break; + if (check_null && (f == NULL)) + fatal_errno("failed to open coverage db file: %s", dbname); + return f; +} + +cover_tag_t *cover_add_tag(tree_t t, ident_t suffix, cover_tagging_t *ctx, + tag_kind_t kind, uint32_t flags) +{ + int *cnt; + assert (ctx != NULL); + + switch (kind) { + case TAG_STMT: cnt = &(ctx->next_stmt_tag); break; + case TAG_BRANCH: cnt = &(ctx->next_branch_tag); break; + case TAG_TOGGLE: cnt = &(ctx->next_toggle_tag); break; + case TAG_HIER: cnt = &(ctx->next_hier_tag); break; default: - break; - } + fatal("Unknown coverage type: %d", kind); } + assert (cnt != NULL); + + // Everything creates scope, so name of current tag is already given + // by scope in hierarchy. + ident_t hier = ctx->hier; + if (suffix) + hier = ident_prefix(hier, suffix, '\0'); + +#ifdef COVER_DEBUG + printf("Tag: %s\n", istr(hier)); + printf(" First line: %d\n", tree_loc(t)->first_line); + printf(" First column: %d\n", tree_loc(t)->first_column); + printf(" Line delta: %d\n", tree_loc(t)->line_delta); + printf(" Column delta: %d\n", tree_loc(t)->column_delta); + printf("\n\n"); +#endif + + cover_tag_t new = { + .kind = kind, + .tag = *cnt, + .data = 0, + .flags = flags, + .loc = *tree_loc(t), + .hier = hier + }; + + APUSH(ctx->tags, new); + (*cnt)++; + + return AREF(ctx->tags, ctx->tags.count - 1); } -cover_tagging_t *cover_tag(tree_t top) +void cover_dump_tags(cover_tagging_t *ctx, fbuf_t *f, cover_dump_t dt, + const int32_t *stmts, const int32_t *branches, + const int32_t *toggles) { - cover_tagging_t *ctx = xcalloc(sizeof(cover_tagging_t)); - ctx->tree_hash = hash_new(1024); - - tree_visit(top, cover_tag_visit_fn, ctx); - if (opt_get_int(OPT_UNIT_TEST)) - return ctx; - - char *dbname LOCAL = xasprintf("_%s.covdb", istr(tree_ident(top))); - fbuf_t *f = lib_fbuf_open(lib_work(), dbname, FBUF_OUT, FBUF_CS_NONE); - if (f == NULL) - fatal_errno("failed to create coverage db file: %s", dbname); +#ifdef COVER_DEBUG + printf("Dumping coverage entries:\n"); + printf("Number of statement tags: %d\n", ctx->next_stmt_tag); + printf("Number of branch tags: %d\n", ctx->next_branch_tag); + printf("Number of toggle tags: %d\n", ctx->next_toggle_tag); + printf("Number of hierarchy tags: %d\n", ctx->next_hier_tag); + printf("Total tag count: %d\n", ctx->tags.count); +#endif write_u32(ctx->next_stmt_tag, f); - write_u32(ctx->next_cond_tag, f); + write_u32(ctx->next_branch_tag, f); + write_u32(ctx->next_toggle_tag, f); + write_u32(ctx->next_hier_tag, f); loc_wr_ctx_t *loc_wr = loc_write_begin(f); + ident_wr_ctx_t ident_ctx = ident_write_begin(f); + + for (int i = 0; i < ctx->tags.count; i++) { + cover_tag_t *tag = &(ctx->tags.items[i]); + + write_u8(tag->kind, f); + write_u32(tag->tag, f); + + if (dt == COV_DUMP_RUNTIME) { + const int32_t *cnts = NULL; + if (tag->kind == TAG_STMT) + cnts = stmts; + else if (tag->kind == TAG_BRANCH) + cnts = branches; + else if (tag->kind == TAG_TOGGLE) + cnts = toggles; + + int32_t data = (cnts) ? cnts[tag->tag] : 0; + write_u32(data, f); - hash_iter_t it = HASH_BEGIN; - tree_t tree; - intptr_t enc; - while (hash_iter(ctx->tree_hash, &it, (const void **)&tree, (void **)&enc)) { - const tag_kind_t kind = (enc & 3) == 1 ? TAG_STMT : TAG_COND; - enc >>= 2; - - write_u8(kind, f); - write_u32(enc >> SUB_COND_BITS, f); - if (kind == TAG_COND) write_u8(enc & SUB_COND_MASK, f); - loc_write(tree_loc(tree), loc_wr); +#ifdef COVER_DEBUG + printf("Index: %4d Tag: %s Kind: %d Data: %d\n", tag->tag, + istr(tag->hier), tag->kind, data); +#endif + + } + else { + write_u32(tag->data, f); +#ifdef COVER_DEBUG + printf("Index: %4d Tag: %s Kind: %d Data: %d\n", tag->tag, + istr(tag->hier), tag->kind, tag->data); +#endif + } + write_u32(tag->flags, f); + loc_write(&(tag->loc), loc_wr); + ident_write(tag->hier, ident_ctx); } write_u8(TAG_LAST, f); loc_write_end(loc_wr); - fbuf_close(f, NULL); + ident_write_end(ident_ctx); +} + +cover_tagging_t *cover_tags_init(cover_mask_t mask) +{ + cover_tagging_t *ctx = xcalloc(sizeof(cover_tagging_t)); + ctx->mask = mask; return ctx; } -cover_tagging_t *cover_read_tags(tree_t top) +bool cover_enabled(cover_tagging_t *tagging, cover_mask_t mask) { - char *dbname LOCAL = xasprintf("_%s.covdb", istr(tree_ident(top))); - fbuf_t *f = lib_fbuf_open(lib_work(), dbname, FBUF_IN, FBUF_CS_NONE); - if (f == NULL) - return NULL; + return tagging != NULL && (tagging->mask & mask); +} - cover_tagging_t *tagging = xcalloc(sizeof(cover_tagging_t)); +void cover_reset_scope(cover_tagging_t *tagging, ident_t hier) +{ + if (tagging == NULL) + return; + + assert(tagging->top_scope == NULL); + + cover_scope_t *s = xcalloc(sizeof(cover_scope_t)); + s->name = hier; + + tagging->top_scope = s; + tagging->hier = hier; +} + +void cover_push_scope(cover_tagging_t *tagging, tree_t t) +{ + if (tagging == NULL) + return; + + cover_scope_t *s = xcalloc(sizeof(cover_scope_t)); + ident_t name = NULL; + tree_kind_t kind = tree_kind(t); + + // Named tree elements give hierarchy + if (kind != T_ASSOC && kind != T_COND && tree_has_ident(t)) + name = tree_ident(t); + + // Branches / Statements are implicitly labelled like so: + // - By counter relevant for each scope + // - others choice separately + else { + assert(tagging->top_scope); + + char prefix[16] = {0}; + int *cnt; + char c; + + // when others choice labelled explicitly + if (kind == T_ASSOC && tree_subkind(t) == A_OTHERS) + checked_sprintf(prefix, sizeof(prefix), "_B_OTHERS"); + else { + if (kind == T_ASSOC || kind == T_COND) { + cnt = &tagging->top_scope->branch_label; + c = 'B'; + } + else { + cnt = &tagging->top_scope->stmt_label; + c = 'S'; + } + checked_sprintf(prefix, sizeof(prefix), "_%c%u", c, *cnt); + (*cnt)++; + } + name = ident_new(prefix); + } + + s->name = name; + s->parent = tagging->top_scope; + + tagging->top_scope = s; + tagging->hier = ident_prefix(tagging->hier, name, '.'); + +#ifdef COVER_DEBUG + printf("Pushing cover scope: %s\n", istr(tagging->hier)); +#endif +} + +void cover_pop_scope(cover_tagging_t *tagging) +{ + if (tagging == NULL) + return; + + assert(tagging->top_scope != NULL); + + cover_scope_t *tmp = tagging->top_scope->parent; + free(tagging->top_scope); + tagging->top_scope = tmp; + +#ifdef COVER_DEBUG + printf("Popping cover scope: %s\n", istr(tagging->hier)); +#endif + + tagging->hier = ident_runtil(tagging->hier, '.'); + assert(tagging->hier != NULL); +} + +void cover_read_header(fbuf_t *f, cover_tagging_t *tagging) +{ + assert(tagging != NULL); tagging->next_stmt_tag = read_u32(f); - tagging->next_cond_tag = read_u32(f); + tagging->next_branch_tag = read_u32(f); + tagging->next_toggle_tag = read_u32(f); + tagging->next_hier_tag = read_u32(f); +} + +void cover_read_one_tag(fbuf_t *f, loc_rd_ctx_t *loc_rd, + ident_rd_ctx_t ident_ctx, cover_tag_t *tag) +{ + tag->kind = read_u8(f); + if (tag->kind == TAG_LAST) + return; + + tag->tag = read_u32(f); + tag->data = read_u32(f); + tag->flags = read_u32(f); + + loc_read(&(tag->loc), loc_rd); + tag->hier = ident_read(ident_ctx); +} + +cover_tagging_t *cover_read_tags(fbuf_t *f) +{ +#ifdef COVER_DEBUG + printf("Reading coverage database.\n"); +#endif + + cover_tagging_t *tagging = xcalloc(sizeof(cover_tagging_t)); + cover_read_header(f, tagging); loc_rd_ctx_t *loc_rd = loc_read_begin(f); + ident_rd_ctx_t ident_ctx = ident_read_begin(f); for (;;) { - const tag_kind_t kind = read_u8(f); - if (kind == TAG_LAST) - break; - - const int32_t tag = read_u32(f); - const int32_t sub_cond = kind == TAG_COND ? read_u8(f) : 0; + cover_tag_t new; + cover_read_one_tag(f, loc_rd, ident_ctx, &new); - loc_t loc; - loc_read(&loc, loc_rd); + if (new.kind == TAG_LAST) + break; - cover_tag_t new = { - .kind = kind, - .loc = loc, - .tag = tag, - .sub_cond = sub_cond, - }; APUSH(tagging->tags, new); } loc_read_end(loc_rd); - fbuf_close(f, NULL); return tagging; } +void cover_merge_tags(fbuf_t *f, cover_tagging_t *tagging) +{ + assert (tagging != NULL); + + cover_read_header(f, tagging); + + loc_rd_ctx_t *loc_rd = loc_read_begin(f); + ident_rd_ctx_t ident_ctx = ident_read_begin(f); + + for (;;) { + cover_tag_t new; + cover_read_one_tag(f, loc_rd, ident_ctx, &new); + + if (new.kind == TAG_LAST) + break; + + // TODO: Could merging be done more efficiently? + bool found = false; + for (int i = 0; i < tagging->tags.count; i++) { + cover_tag_t *old = AREF(tagging->tags, i); + + // Compare based on hierarchical path, each + // statement / branch / signal has unique hierarchical name + if (new.hier == old->hier) { + assert(new.kind == old->kind); +#ifdef COVER_DEBUG + printf("Merging coverage tag: %s\n", istr(old->hier)); +#endif + switch (new.kind) { + case TAG_STMT: + old->data += new.data; + break; + case TAG_TOGGLE: + case TAG_BRANCH: + old->data |= new.data; + break; + default: + break; + } + + found = true; + break; + } + } + + // TODO: Append the new tag just before popping hierarchy tag + // with longest common prefix of new tag. That will allow to + // merge coverage of IPs from different configurations of + // generics which form hierarchy differently! + if (!found) + warnf("Dropping coverage tag: %s\n", istr(new.hier)); + } +} + void cover_count_tags(cover_tagging_t *tagging, int32_t *n_stmts, - int32_t *n_conds) + int32_t *n_branches, int32_t *n_toggles) { if (tagging == NULL) { *n_stmts = 0; - *n_conds = 0; + *n_branches = 0; + *n_toggles = 0; } else { *n_stmts = tagging->next_stmt_tag; - *n_conds = tagging->next_cond_tag; + *n_branches = tagging->next_branch_tag; + *n_toggles = tagging->next_toggle_tag; } } -bool cover_is_tagged(cover_tagging_t *tagging, tree_t t, - int32_t *tag, int32_t *sub_cond) +void cover_toggle_event_cb(uint64_t now, rt_signal_t *s, rt_watch_t *w, + void *user) { - if (tagging == NULL) - return false; - intptr_t enc = (intptr_t)hash_get(tagging->tree_hash, t); - if (enc == 0) - return false; +#ifdef COVER_DEBUG + printf("Time: %lu Callback on signal: %s\n", + now, istr(tree_ident(s->where))); +#endif + + uint32_t sig_size = s->shared.size; + int32_t *toggle_mask = ((int32_t *)user) + sig_size - 1; + + for (int i = 0; i < sig_size; i++) { + uint8_t new = ((uint8_t*)signal_value(s))[i]; + uint8_t old = ((uint8_t*)signal_last_value(s))[i]; + + // 0->1 + if (old == _0 && new == _1) + *toggle_mask |= 0x1; + + // 1->0 + if (old == _1 && new == _0) + *toggle_mask |= 0x2; + + toggle_mask--; + } + +#ifdef COVER_DEBUG + printf("New signal value:\n"); + for (int i = 0; i < sig_size; i++) + printf("0x%x ", ((uint8_t*)signal_value(s))[i]); + printf("\n"); - enc >>= 2; - if (sub_cond) *sub_cond = enc & 0x1f; - if (tag) *tag = enc >> 5; + printf("Old signal value:\n"); + for (int i = 0; i < sig_size; i++) { + printf("0x%x ", ((const uint8_t *)signal_last_value(s))[i]); + } + printf("\n\n"); +#endif + +} - return true; +void x_cover_setup_toggle_cb(sig_shared_t *ss, int32_t *toggle_mask) +{ + rt_signal_t *s = container_of(ss, rt_signal_t, shared); + rt_model_t *m = get_model(); + model_set_event_cb(m, s, cover_toggle_event_cb, toggle_mask, false); } + +/////////////////////////////////////////////////////////////////////////////// +// Report generation +/////////////////////////////////////////////////////////////////////////////// + static void cover_append_line(cover_file_t *f, const char *buf) { if (f->n_lines == f->alloc_lines) { @@ -314,8 +558,6 @@ static void cover_append_line(cover_file_t *f, const char *buf) cover_line_t *l = &(f->lines[(f->n_lines)++]); l->text = xstrdup(buf); l->len = strlen(buf); - l->hits = -1; - l->hl = NULL; } static cover_file_t *cover_file(const loc_t *loc) @@ -368,323 +610,577 @@ static cover_file_t *cover_file(const loc_t *loc) return (files = f); } -static void cover_process_tag(cover_tag_t *tag, const int32_t *stmts, - const int32_t *conds) -{ - cover_file_t *file = cover_file(&(tag->loc)); - if (file == NULL || !file->valid || tag->loc.first_line == 0) - return; - assert(tag->loc.first_line < file->n_lines); - - cover_line_t *l = &(file->lines[tag->loc.first_line - 1]); +static void cover_print_html_header(FILE *f, cover_report_ctx_t *ctx, bool top, + const char *title, ...) +{ + fprintf(f, "\n" + "\n" + " \n" + " \n"); + + va_list ap; + va_start(ap, title); + vfprintf(f, title, ap); + va_end(ap); + + fprintf(f, " \n" + " \n" + " \n" + "
\n"); + + if (!top) { + fprintf(f, ""); + } - if (tag->kind == TAG_STMT) { - l->hits = MAX(stmts[tag->tag], l->hits); + fprintf(f, "
\n"); + va_start(ap, title); + vfprintf(f, title, ap); + va_end(ap); + fprintf(f, "
\n"); + + fprintf(f, "

\n"); + if (!top) + fprintf(f, " Instance: %s\n", istr(ctx->start_tag->hier)); + else + fprintf(f, " Instance:"); + fprintf(f, "

\n"); + + // start_tag has still loc corresponding to a file where hierarchy + // is instantiated. + cover_file_t *src = cover_file(&((ctx->start_tag + 1)->loc)); + fprintf(f, "

\n"); + if (!top) + fprintf(f, " File:  ../../%s\n", + src->name, src->name); + else + fprintf(f, " File:"); + fprintf(f, "

\n"); - if (stmts[tag->tag] > 0) - stats.hit_stmts++; +} - stats.total_stmts++; +static void cover_print_percents_cell(FILE *f, unsigned hit, unsigned total) +{ + if (total > 0) { + float perc = ((float) hit / (float) total) * 100; + char color[8]; + if (hit == total) + checked_sprintf(color, sizeof(color), "#00cc00"); + else if (perc > 90) + checked_sprintf(color, sizeof(color), "#e6e600"); + else if (perc > 80) + checked_sprintf(color, sizeof(color), "#ff9900"); + else + checked_sprintf(color, sizeof(color), "#ff0000"); + + fprintf(f, " %.1f %% (%d/%d)\n", + color, perc, hit, total); + return; } - else { - const int start = tag->loc.first_column; - const int end = (tag->loc.line_delta == 0) - ? tag->loc.first_column + tag->loc.column_delta : l->len; - - const int mask = (conds[tag->tag] >> (tag->sub_cond * 2)) & 3; - - if (tag->sub_cond == 0) { - stats.total_branches++; - if (mask == 3) - stats.hit_branches++; - } - stats.total_conds++; - if (mask == 3) - stats.hit_conds++; + fprintf(f, " N.A.\n"); +} - cover_hl_t *hl; - for (hl = l->hl; hl != NULL; hl = hl->next) { - if ((hl->start == start) && (hl->end == end)) - break; - } +static void cover_print_hierarchy_header(FILE *f) +{ + fprintf(f, " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n"); +} - if (hl == NULL) { - hl = xmalloc(sizeof(cover_hl_t)); - hl->start = start; - hl->end = end; - hl->next = l->hl; +static void cover_print_hierarchy_footer(FILE *f) +{ + fprintf(f, "
InstanceStatementBranchToggleAverage
\n"); +} - l->hl = hl; - } - else if (hl->kind == HL_HIT) - return; +static void cover_print_timestamp(FILE *f) +{ + time_t t; + time(&t); - hl->kind = (mask == 3) ? HL_HIT : HL_MISS; - hl->help = NULL; + fprintf(f, "
\n"); - if (mask == 1) - hl->help = "Condition never evaluated to TRUE"; - else if (mask == 2) - hl->help = "Condition never evaluated to FALSE"; - } + fprintf(f, "
"); + fprintf(f, "

NVC version: %s

\n", PACKAGE_VERSION); + fprintf(f, "

Generated on: %s

\n", ctime(&t)); + fprintf(f, "
"); } -static void cover_report_line(FILE *fp, cover_line_t *l) +static void cover_print_hierarchy_summary(FILE *f, cover_stats_t *stats, ident_t hier, + bool top) { - fprintf(fp, ""); - - if (l->hits != -1) { - fprintf(fp, "%d", l->hits); - fprintf(fp, "", (l->hits > 0) ? "hit" : "miss"); - } - else { - fprintf(fp, ""); - fprintf(fp, ""); + fprintf(f, " \n" + " %s\n", + top ? "hier/" : "", istr(hier), istr(hier)); + + cover_print_percents_cell(f, stats->hit_stmts, stats->total_stmts); + cover_print_percents_cell(f, stats->hit_branches, stats->total_branches); + cover_print_percents_cell(f, stats->hit_toggles, stats->total_toggles); + + int avg_total = stats->total_stmts + stats->total_branches + stats->total_toggles; + int avg_hit = stats->hit_stmts + stats->hit_branches + stats->hit_toggles; + cover_print_percents_cell(f, avg_hit, avg_total); + + fprintf(f, " \n"); + + if (top) { + notef("code coverage results for: %s", istr(hier)); + + if (stats->total_stmts > 0) + notef(" statement: %.1f %%", + 100.0 * ((double)stats->hit_stmts) / stats->total_stmts); + else + notef(" statement: N.A."); + + if (stats->total_branches > 0) + notef(" branch: %.1f %%", + 100.0 * ((double)stats->hit_branches) / stats->total_branches); + else + notef(" branch: N.A."); + + if (stats->total_toggles > 0) + notef(" toggle: %.1f %%", + 100.0 * ((double)stats->hit_toggles) / stats->total_toggles); + else + notef(" toggle: N.A."); } +} - int col = 0; - for (const char *p = l->text; *p != '\0'; p++, col++) { - for (cover_hl_t *it = l->hl; it != NULL; it = it->next) { - if (it->start == col) { - fprintf(fp, "kind == HL_HIT) ? "hit" : "miss"); - if (it->help != NULL) - fprintf(fp, " title=\"%s\"", it->help); - fprintf(fp, ">"); - } + +static void cover_print_chain(FILE *f, cover_chain_t *chn, tag_kind_t kind) +{ + // HTML TAB + fprintf(f, "
\n"); + + for (int i = 0; i < 2; i++) { + int n; + cover_pair_t *pair; + + if (i == 0) { + pair = chn->miss; + n = chn->n_miss; + } + else { + pair = chn->hits; + n = chn->n_hits; } - switch (*p) { - case ' ': - fprintf(fp, " "); - break; - case '\t': - { - int col = (p - l->text); - while (++col % 8) - fprintf(fp, " "); + fprintf(f, "

"); + if (i == 0) + fprintf(f, "Uncovered "); + else + fprintf(f, "Covered "); + + if (kind == TAG_STMT) + fprintf(f, "statements:"); + else if (kind == TAG_BRANCH) + fprintf(f, "branches:"); + else if (kind == TAG_TOGGLE) + fprintf(f, "toggles:"); + fprintf(f, "

"); + + for (int j = 0; j < n; j++) { + loc_t loc = pair->tag->loc; + + if (kind == TAG_BRANCH || kind == TAG_STMT) + fprintf(f, "

Line %d", loc.first_line); + + if (kind == TAG_BRANCH) { + // True / False flags set for T_IF on tag + if ((pair->tag->flags & COV_FLAG_HAS_TRUE) && + (pair->tag->flags & COV_FLAG_HAS_FALSE)) + { + fprintf(f, " (Evaluated to "); + if (pair->flags & COV_FLAG_HAS_TRUE) + fprintf(f, "True)"); + else + fprintf(f, "False)"); + } + else + fprintf(f, " (Choice)"); + } + if (kind == TAG_BRANCH || kind == TAG_STMT) + fprintf(f, ":
"); + + // Print line on with the tag, and highlight its location + if (kind == TAG_BRANCH || kind == TAG_STMT) { + fprintf(f, ""); + int last = strlen(pair->line->text); + int curr = 0; + while (curr <= last) { + if (curr == loc.first_column) + fprintf(f, ""); + fprintf(f, "%c", pair->line->text[curr]); + if (curr == (loc.first_column + loc.column_delta) && + loc.line_delta == 0) + fprintf(f, ""); + curr++; + } + if (loc.line_delta > 0) + fprintf(f, ""); + } + fprintf(f, ""); + + // Hier contains also indices of sub-signals + if (kind == TAG_TOGGLE) { + fprintf(f, ""); + if (pair->flags & COV_FLAG_TOGGLE_TO_1) + fprintf(f, "Toggle to 1  "); + else if (pair->flags & COV_FLAG_TOGGLE_TO_0) + fprintf(f, "Toggle to 0  "); + + fprintf(f, "on "); + if (pair->tag->flags & COV_FLAG_TOGGLE_SIGNAL) + fprintf(f, "signal:"); + else if (pair->tag->flags & COV_FLAG_TOGGLE_PORT) + fprintf(f, "port:   "); + fprintf(f, "
"); + fprintf(f, "%s", istr(ident_rfrom(pair->tag->hier, '.'))); } - break; - case '<': - fprintf(fp, "<"); - break; - case '>': - fprintf(fp, ">"); - break; - default: - fputc(*p, fp); - break; - } - for (cover_hl_t *it = l->hl; it != NULL; it = it->next) { - if (it->end == col) - fprintf(fp, ""); + fprintf(f, "

\n"); + pair++; } } - switch (*(l->text)) { - case '\n': - case '\r': - case '\0': - fprintf(fp, " "); // Equal height for empty lines - break; - } - - fprintf(fp, "\n"); + fprintf(f, "
\n"); } -static const char *cover_file_url(cover_file_t *f) +static void cover_print_hierarchy_guts(FILE *f, cover_report_ctx_t *ctx) { - static char buf[256]; - checked_sprintf(buf, sizeof(buf) - 6, "report_%s.html", f->name); - for (char *p = buf; *(p + 5) != '\0'; p++) { - if (*p == DIR_SEP[0] || *p == '.') - *p = '_'; - } - return buf; + fprintf(f, "
" + " \n" + " \n" + " \n" + "
\n"); + + cover_print_chain(f, &(ctx->ch_stmt), TAG_STMT); + cover_print_chain(f, &(ctx->ch_branch), TAG_BRANCH); + cover_print_chain(f, &(ctx->ch_toggle), TAG_TOGGLE); + + fprintf(f, "\n"); } -static void cover_html_header(FILE *fp, const char *title, ...) +static void cover_append_to_chain(cover_chain_t *chain, bool hits, + cover_tag_t *tag, cover_line_t *line, + unsigned flags) { - fprintf(fp, - "\n" - "\n" - "\n" - " \n" - " "); - - va_list ap; - va_start(ap, title); - vfprintf(fp, title, ap); - va_end(ap); - - fprintf(fp, " \n \n\n\n"); - - fprintf(fp, "
\n"); - fprintf(fp, "

Reports

\n"); - fprintf(fp, "
    \n"); - fprintf(fp, "
  • Index
  • \n"); - fprintf(fp, "
\n"); - fprintf(fp, "

Files

\n"); - fprintf(fp, "
    \n"); - for (cover_file_t *f = files; f != NULL; f = f->next) - fprintf(fp, "
  • %s
  • \n", - cover_file_url(f), f->name); - fprintf(fp, "
\n"); - fprintf(fp, "
\n"); - - - fprintf(fp, "
\n"); + if (hits) { + if (chain->n_hits == chain->alloc_hits) { + chain->alloc_hits *= 2; + chain->hits = xrealloc_array(chain->hits, chain->alloc_hits, sizeof(cover_pair_t)); + } + chain->hits[chain->n_hits].tag = tag; + chain->hits[chain->n_hits].line = line; + chain->hits[chain->n_hits].flags = flags; + chain->n_hits++; + } + else { + if (chain->n_miss == chain->alloc_miss) { + chain->alloc_miss *= 2; + chain->miss = xrealloc_array(chain->miss, chain->alloc_miss, sizeof(cover_pair_t)); + } + chain->miss[chain->n_miss].tag = tag; + chain->miss[chain->n_miss].line = line; + chain->miss[chain->n_miss].flags = flags; + chain->n_miss++; + } } -static void cover_html_footer(FILE *fp) +static cover_tag_t* cover_report_hierarchy(cover_report_ctx_t *ctx, + const char *dir) { - fprintf(fp, "
\n\n"); -} + char *hier LOCAL = xasprintf("%s/%s.html", dir, istr(ctx->start_tag->hier)); + cover_tag_t *tag = ctx->start_tag; -static void cover_report_file(cover_file_t *f, const char *dir) -{ - char *buf LOCAL = xasprintf("%s" DIR_SEP "%s", dir, cover_file_url(f)); - FILE *fp = lib_fopen(lib_work(), buf, "w"); - if (fp == NULL) - fatal("failed to create %s", buf); + // TODO: Handle escaped identifiers in hierarchy path! + FILE *f = fopen(hier, "w"); + if (f == NULL) + fatal("Failed to open file: %s\n", hier); + + ctx->ch_stmt.hits = xcalloc_array(1024, sizeof(cover_pair_t)); + ctx->ch_stmt.miss = xcalloc_array(1024, sizeof(cover_pair_t)); + ctx->ch_stmt.alloc_hits = 1024; + ctx->ch_stmt.alloc_miss = 1024; + + ctx->ch_branch.hits = xcalloc_array(1024, sizeof(cover_pair_t)); + ctx->ch_branch.miss = xcalloc_array(1024, sizeof(cover_pair_t)); + ctx->ch_branch.alloc_hits = 1024; + ctx->ch_branch.alloc_miss = 1024; + + ctx->ch_toggle.hits = xcalloc_array(1024, sizeof(cover_pair_t)); + ctx->ch_toggle.miss = xcalloc_array(1024, sizeof(cover_pair_t)); + ctx->ch_toggle.alloc_hits = 1024; + ctx->ch_toggle.alloc_miss = 1024; + + cover_print_html_header(f, ctx, false, "NVC code coverage report"); + + fprintf(f, "

Sub-instances:

\n"); + cover_print_hierarchy_header(f); + + for(;;) { + tag++; + + if (tag->kind == TAG_HIER) { + if (tag->flags & COV_FLAG_HIER_DOWN) { + + // Collect coverage of sub-block + cover_report_ctx_t sub_ctx = {0}; + sub_ctx.start_tag = tag; + sub_ctx.parent = ctx; + tag = cover_report_hierarchy(&sub_ctx, dir); + cover_print_hierarchy_summary(f, &(sub_ctx.nested_stats), + tag->hier, false); + + // Add coverage from sub-hierarchies + ctx->nested_stats.hit_stmts += sub_ctx.nested_stats.hit_stmts; + ctx->nested_stats.total_stmts += sub_ctx.nested_stats.total_stmts; + ctx->nested_stats.hit_branches += sub_ctx.nested_stats.hit_branches; + ctx->nested_stats.total_branches += sub_ctx.nested_stats.total_branches; + ctx->nested_stats.hit_toggles += sub_ctx.nested_stats.hit_toggles; + ctx->nested_stats.total_toggles += sub_ctx.nested_stats.total_toggles; - cover_html_header(fp, "Coverage report for %s", f->name); + } + else if (tag->flags & COV_FLAG_HIER_UP) + break; - fprintf(fp, "\n"); - for (int i = 0; i < f->n_lines; i++) { - cover_line_t *l = &(f->lines[i]); - cover_report_line(fp, l); - } - fprintf(fp, "
\n"); + } + else { + + cover_file_t *f_src = cover_file(&(tag->loc)); + if (f_src == NULL) + continue; + cover_line_t *line = &(f_src->lines[tag->loc.first_line-1]); + + switch (tag->kind){ + case TAG_STMT: + (ctx->flat_stats.total_stmts)++; + (ctx->nested_stats.total_stmts)++; + + if (tag->data > 0) { + (ctx->flat_stats.hit_stmts)++; + (ctx->nested_stats.hit_stmts)++; + cover_append_to_chain(&(ctx->ch_stmt), true, tag, line, 0); + } + else + cover_append_to_chain(&(ctx->ch_stmt), false, tag, line, 0); - cover_html_footer(fp); + break; - fclose(fp); -} + case TAG_BRANCH: + if (tag->flags & COV_FLAG_HAS_TRUE) { + (ctx->flat_stats.total_branches)++; + (ctx->nested_stats.total_branches)++; + + if (tag->data & 0x1) { + (ctx->flat_stats.hit_branches)++; + (ctx->nested_stats.hit_branches)++; + cover_append_to_chain(&(ctx->ch_branch), true, tag, + line, COV_FLAG_HAS_TRUE); + } + else + cover_append_to_chain(&(ctx->ch_branch), false, tag, + line, COV_FLAG_HAS_TRUE); + } + if (tag->flags & COV_FLAG_HAS_FALSE) { + (ctx->flat_stats.total_branches)++; + (ctx->nested_stats.total_branches)++; + + if (tag->data & 0x2) { + (ctx->flat_stats.hit_branches)++; + (ctx->nested_stats.hit_branches)++; + + cover_append_to_chain(&(ctx->ch_branch), true, tag, + line, COV_FLAG_HAS_FALSE); + } + else + cover_append_to_chain(&(ctx->ch_branch), false, tag, + line, COV_FLAG_HAS_FALSE); + } + break; -static const char *cover_percent(unsigned x, unsigned y) -{ - const float pct = ((float)x / (float)y) * 100.0f; + case TAG_TOGGLE: + (ctx->flat_stats.total_toggles) += 2; + (ctx->nested_stats.total_toggles) += 2; - static char buf[256]; - snprintf(buf, sizeof(buf), "%.0f%%", - (pct < PERCENT_RED) ? "red" - : ((pct < PERCENT_ORANGE) ? "orange" : "green"), pct); + if (tag->data & 0x1) { + (ctx->flat_stats.hit_toggles)++; + (ctx->nested_stats.hit_toggles)++; + cover_append_to_chain(&(ctx->ch_toggle), true, tag, + line, COV_FLAG_TOGGLE_TO_1); + } + else + cover_append_to_chain(&(ctx->ch_toggle), false, tag, + line, COV_FLAG_TOGGLE_TO_1); + + if (tag->data & 0x2) { + (ctx->flat_stats.hit_toggles)++; + (ctx->nested_stats.hit_toggles)++; + cover_append_to_chain(&(ctx->ch_toggle), true, tag, + line, COV_FLAG_TOGGLE_TO_0); + } + else + cover_append_to_chain(&(ctx->ch_toggle), false, tag, + line, COV_FLAG_TOGGLE_TO_0); - return buf; -} + break; -static void cover_stat_line(FILE *fp, const char *text, - unsigned hit, unsigned total) -{ - if (total > 0) { - fprintf(fp, "%s%u" - "%u%s\n", - text, hit, total, cover_percent(hit, total)); + default: + fatal("Unsupported type of code coverage:%d !", tag->kind); + } + } } -} -static void cover_index(ident_t name, const char *dir) -{ - char *buf = xasprintf("%s/index.html", dir); - FILE *fp = lib_fopen(lib_work(), buf, "w"); - if (fp == NULL) - fatal("failed to create %s", buf); - free(buf); - - cover_html_header(fp, "Coverage report for %s", istr(name)); - - fprintf(fp, "

Coverage report for %s

\n", istr(name)); - fprintf(fp, "

Select a file from the sidebar to see " - "annotated statement and condition coverage.

\n"); - - fprintf(fp, "

Overall Statistics

\n"); - fprintf(fp, "\n"); - fprintf(fp, "" - "\n"); - cover_stat_line(fp, "Statements executed", - stats.hit_stmts, stats.total_stmts); - cover_stat_line(fp, "Branches observed taken and not taken", - stats.hit_branches, stats.total_branches); - cover_stat_line(fp, "Conditions evaluated to both TRUE and FALSE", - stats.hit_conds, stats.total_conds); - fprintf(fp, "
MetricCoveredTotalPercentage
\n"); - - fprintf(fp, "

Generated by %s\n" - "
" - PACKAGE_URL"

\n", - PACKAGE_STRING); - - cover_html_footer(fp); - - fclose(fp); -} + cover_print_hierarchy_footer(f); -void cover_report(tree_t top, cover_tagging_t *tagging, - const int32_t *stmts, const int32_t *conds) -{ - for (unsigned i = 0; i < tagging->tags.count; i++) - cover_process_tag(&(tagging->tags.items[i]), stmts, conds); + fprintf(f, "

Current Instance:

\n"); + cover_print_hierarchy_header(f); + cover_print_hierarchy_summary(f, &(ctx->flat_stats), tag->hier, false); + cover_print_hierarchy_footer(f); - ident_t name = ident_strip(tree_ident(top), ident_new(".elab")); + fprintf(f, "

Details:

\n"); + cover_print_hierarchy_guts(f, ctx); + cover_print_timestamp(f); - char *dir LOCAL = xasprintf("%s.cover", istr(name)); + fclose(f); + return tag; +} + + +void cover_report(const char *path, cover_tagging_t *tagging) +{ + char *subdir LOCAL = xasprintf("%s/hier", path); + make_dir(path); + make_dir(subdir); - lib_t work = lib_work(); - lib_mkdir(work, dir); + assert(tagging->tags.items[0].kind == TAG_HIER); - for (cover_file_t *f = files; f != NULL; f = f->next) - cover_report_file(f, dir); + cover_report_ctx_t top_ctx = {0}; + top_ctx.start_tag = AREF(tagging->tags, 0); + cover_report_hierarchy(&top_ctx, subdir); - cover_index(name, dir); + char *top LOCAL = xasprintf("%s/index.html", path); + FILE *f = fopen(top, "w"); - char output[PATH_MAX]; - lib_realpath(work, dir, output, sizeof(output)); + cover_print_html_header(f, &top_ctx, true, "NVC code coverage report"); + cover_print_hierarchy_header(f); + cover_print_hierarchy_summary(f, &(top_ctx.nested_stats), + top_ctx.start_tag->hier, true); + cover_print_hierarchy_footer(f); + cover_print_timestamp(f); - char *buf LOCAL = xasprintf( - "coverage report generated in %s/\n" - " %u/%u statements covered\n" - " %u/%u branches covered\n" - " %u/%u conditions covered", - output, - stats.hit_stmts, stats.total_stmts, - stats.hit_branches, stats.total_branches, - stats.hit_conds, stats.total_conds); - notef("%s", buf); + fclose(f); } diff --git a/src/rt/cover.h b/src/rt/cover.h index 45924e19..ea65d947 100644 --- a/src/rt/cover.h +++ b/src/rt/cover.h @@ -20,16 +20,127 @@ #include "util.h" #include "tree.h" +#include "fbuf.h" +#include "diag.h" +#include "rt.h" typedef struct _cover_tagging cover_tagging_t; -cover_tagging_t *cover_tag(tree_t top); -void cover_report(tree_t top, cover_tagging_t *tagging, - const int32_t *stmts, const int32_t *conds); -bool cover_is_tagged(cover_tagging_t *tagging, tree_t t, - int32_t *tag, int32_t *sub_cond); +typedef enum { + // Statement/Line coverage + // + // Each execution of a statement increments counter. If counter is bigger + // than 0, statement is covered. + TAG_STMT, + + // Branch / Decision coverage + // + // Each statement in which code flow diverges (if, case, while, when/else, + // with/select) contains two flags. If each diverging path of the code + // flow is taken/executed, a flag is set. If both flags are set, decision / + // branch is 100% covered. If only one is taken it is 50% covered. If none, + // decision / branch is not covered. + TAG_BRANCH, + + // Toggle coverage + // + // Each logic signal contains two flags. Upon 0->1 transition, one flag is + // set. Upon 1 -> 0 transition, the other flag is set. If both flags are set, + // signal is 100% covered. Ports and internal signals are flagged. + TAG_TOGGLE, + + // TODO: Expression coverage + + // Tag used to represent hierarchy break in the linear sequence of tags. + // Does not hold any coverage information + TAG_HIER, + + // Last tag out of all tags, used to indicate no more tags are present in + // coverage database. + TAG_LAST +} tag_kind_t; + +typedef struct _cover_tag { + // Type of coverage + tag_kind_t kind; + + // Index of the tag of given kind. + // - 0 ... 2^31 - 1 - Valid tag entry, Index to run-time arrays with + // coverage data + // - -1 - Invalid tag + int32_t tag; + + // Coverage data: + // TAG_STMT - Counter of hits + // TAG_BRANCH - Bit 0 - Evaluated to True + // - Bit 1 - Evaluated to False + // TAG_TOOGLE - Bit 0 - 0 -> 1 transition + // - Bit 1 - 1 -> 0 transition + int32_t data; + + // Flags for coverage tag + int32_t flags; + + // Location in the source file + loc_t loc; + + // Hierarchy path of the covered object + ident_t hier; +} cover_tag_t; + +typedef enum { + COV_FLAG_HAS_TRUE = (1 << 0), + COV_FLAG_HAS_FALSE = (1 << 1), + COV_FLAG_HIER_UP = (1 << 8), + COV_FLAG_HIER_DOWN = (1 << 9), + COV_FLAG_TOGGLE_TO_0 = (1 << 15), + COV_FLAG_TOGGLE_TO_1 = (1 << 16), + COV_FLAG_TOGGLE_SIGNAL = (1 << 17), + COV_FLAG_TOGGLE_PORT = (1 << 18) +} cover_flags_t; + +typedef enum { + COV_DUMP_ELAB, + COV_DUMP_RUNTIME, + COV_DUMP_PROCESSING +} cover_dump_t; + +typedef enum { + COVER_MASK_STMT = (1 << 0), + COVER_MASK_BRANCH = (1 << 1), + COVER_MASK_TOGGLE = (1 << 2), +} cover_mask_t; + +#define COVER_MASK_ALL (COVER_MASK_STMT | COVER_MASK_BRANCH | COVER_MASK_TOGGLE) + +cover_tagging_t *cover_tags_init(cover_mask_t mask); +bool cover_enabled(cover_tagging_t *tagging, cover_mask_t mask); + +void cover_reset_scope(cover_tagging_t *tagging, ident_t hier); +void cover_push_scope(cover_tagging_t *tagging, tree_t t); +void cover_pop_scope(cover_tagging_t *tagging); + +bool cover_is_stmt(tree_t t); + +fbuf_t *cover_open_lib_file(tree_t top, fbuf_mode_t mode, bool check_null); + +void cover_toggle_event_cb(uint64_t now, rt_signal_t *s, rt_watch_t *w, + void *user); + +cover_tag_t *cover_add_tag(tree_t t, ident_t suffix, cover_tagging_t *ctx, + tag_kind_t kind, uint32_t flags); + +void cover_report(const char *path, cover_tagging_t *tagging); + void cover_count_tags(cover_tagging_t *tagging, int32_t *n_stmts, - int32_t *n_conds); -cover_tagging_t *cover_read_tags(tree_t top); + int32_t *n_branches, int32_t *n_toggles); + +void cover_dump_tags(cover_tagging_t *ctx, fbuf_t *f, cover_dump_t dt, + const int32_t *stmts, const int32_t *branches, + const int32_t *toggles); + +cover_tagging_t *cover_read_tags(fbuf_t *f); + +void cover_merge_tags(fbuf_t *f, cover_tagging_t *tagging); #endif // _COVER_H diff --git a/src/rt/model.c b/src/rt/model.c index 881617b8..3b2d369b 100644 --- a/src/rt/model.c +++ b/src/rt/model.c @@ -622,6 +622,11 @@ const void *signal_value(rt_signal_t *s) return s->shared.data; } +const void *signal_last_value(rt_signal_t *s) +{ + return s->shared.data + s->shared.size; +} + size_t signal_expand(rt_signal_t *s, int offset, uint64_t *buf, size_t max) { rt_nexus_t *n = &(s->nexus); @@ -1759,28 +1764,41 @@ static void reset_coverage(rt_model_t *m) { assert(m->cover == NULL); - if ((m->cover = cover_read_tags(m->top)) == NULL) + fbuf_t *f = cover_open_lib_file(m->top, FBUF_IN, false); + if (f == NULL) return; - int32_t n_stmts, n_conds; - cover_count_tags(m->cover, &n_stmts, &n_conds); + m->cover = cover_read_tags(f); + + int32_t n_stmts, n_branches, n_toggles; + cover_count_tags(m->cover, &n_stmts, &n_branches, &n_toggles); int32_t *cover_stmts = ffi_find_symbol(NULL, "cover_stmts"); if (cover_stmts != NULL) memset(cover_stmts, '\0', sizeof(int32_t) * n_stmts); - int32_t *cover_conds = ffi_find_symbol(NULL, "cover_conds"); - if (cover_conds != NULL) - memset(cover_conds, '\0', sizeof(int32_t) * n_conds); + int32_t *cover_branches = ffi_find_symbol(NULL, "cover_branches"); + if (cover_branches != NULL) + memset(cover_branches, '\0', sizeof(int32_t) * n_branches); + + int32_t *cover_toggles = ffi_find_symbol(NULL, "cover_toggles"); + if (cover_toggles != NULL) + memset(cover_toggles, '\0', sizeof(int32_t) * n_toggles); + + fbuf_close(f, NULL); } static void emit_coverage(rt_model_t *m) { if (m->cover != NULL) { - int32_t *cover_stmts = ffi_find_symbol(NULL, "cover_stmts"); - int32_t *cover_conds = ffi_find_symbol(NULL, "cover_conds"); - if (cover_stmts != NULL) - cover_report(m->top, m->cover, cover_stmts, cover_conds); + const int32_t *cover_stmts = ffi_find_symbol(NULL, "cover_stmts"); + const int32_t *cover_branches = ffi_find_symbol(NULL, "cover_branches"); + const int32_t *cover_toggles = ffi_find_symbol(NULL, "cover_toggles"); + + fbuf_t *covdb = cover_open_lib_file(m->top, FBUF_OUT, true); + cover_dump_tags(m->cover, covdb, COV_DUMP_RUNTIME, cover_stmts, + cover_branches, cover_toggles); + fbuf_close(covdb, NULL); } } diff --git a/src/rt/model.h b/src/rt/model.h index 7627c2c8..cdb2678e 100644 --- a/src/rt/model.h +++ b/src/rt/model.h @@ -46,6 +46,7 @@ rt_scope_t *child_scope(rt_scope_t *scope, tree_t decl); rt_signal_t *find_signal(rt_scope_t *scope, tree_t decl); const void *signal_value(rt_signal_t *s); +const void *signal_last_value(rt_signal_t *s); size_t signal_expand(rt_signal_t *s, int offset, uint64_t *buf, size_t max); size_t signal_string(rt_signal_t *s, const char *map, char *buf, size_t max); bool force_signal(rt_signal_t *s, const uint64_t *buf, size_t count); diff --git a/src/symbols.txt b/src/symbols.txt index 124dd48b..0b5b1850 100644 --- a/src/symbols.txt +++ b/src/symbols.txt @@ -58,6 +58,7 @@ __nvc_release; __nvc_report; __nvc_resolve_signal; + __nvc_setup_toggle_cb; __nvc_tlab; __nvc_unreachable; _assert_fail; diff --git a/src/vcode.c b/src/vcode.c index b4b9350b..571d8a6a 100644 --- a/src/vcode.c +++ b/src/vcode.c @@ -43,7 +43,7 @@ DECLARE_AND_DEFINE_ARRAY(vcode_type); (x == VCODE_OP_LOAD || x == VCODE_OP_STORE || x == VCODE_OP_INDEX \ || x == VCODE_OP_VAR_UPREF) #define OP_HAS_SUBKIND(x) \ - (x == VCODE_OP_COVER_COND || x == VCODE_OP_PCALL \ + (x == VCODE_OP_PCALL \ || x == VCODE_OP_FCALL || x == VCODE_OP_RESOLUTION_WRAPPER \ || x == VCODE_OP_CLOSURE || x == VCODE_OP_PROTECTED_INIT \ || x == VCODE_OP_PACKAGE_INIT) @@ -69,7 +69,8 @@ DECLARE_AND_DEFINE_ARRAY(vcode_type); #define OP_HAS_CMP(x) \ (x == VCODE_OP_CMP) #define OP_HAS_TAG(x) \ - (x == VCODE_OP_COVER_STMT || x == VCODE_OP_COVER_COND) + (x == VCODE_OP_COVER_STMT || x == VCODE_OP_COVER_BRANCH \ + || x == VCODE_OP_COVER_TOGGLE) #define OP_HAS_COMMENT(x) \ (x == VCODE_OP_COMMENT) #define OP_HAS_TARGET(x) \ @@ -203,7 +204,7 @@ struct vcode_unit { #define VCODE_FOR_EACH_MATCHING_OP(name, k) \ VCODE_FOR_EACH_OP(name) if (name->kind == k) -#define VCODE_VERSION 25 +#define VCODE_VERSION 26 #define VCODE_CHECK_UNIONS 0 static __thread vcode_unit_t active_unit = NULL; @@ -921,8 +922,8 @@ const char *vcode_op_string(vcode_op_t op) "pcall", "resume", "xor", "xnor", "nand", "nor", "memset", "case", "endfile", "file open", "file write", "file close", "file read", "null", "new", "null check", "deallocate", "all", - "const real", "last event", "debug out", "cover stmt", "cover cond", - "uarray len", "undefined", "range null", "var upref", + "const real", "last event", "debug out", "cover stmt", "cover branch", + "cover toggle", "uarray len", "undefined", "range null", "var upref", "resolved", "last value", "init signal", "map signal", "drive signal", "link var", "resolution wrapper", "last active", "driving", "driving value", "address of", "closure", "protected init", @@ -2113,14 +2114,20 @@ void vcode_dump_with_mark(int mark_op, vcode_dump_fn_t callback, void *arg) case VCODE_OP_COVER_STMT: { - printf("%s %u", vcode_op_string(op->kind), op->tag); + printf("%s %u ", vcode_op_string(op->kind), op->tag); } break; - case VCODE_OP_COVER_COND: + case VCODE_OP_COVER_BRANCH: { - printf("%s %u sub %u ", vcode_op_string(op->kind), - op->tag, op->subkind); + printf("%s %u ", vcode_op_string(op->kind), op->tag); + vcode_dump_reg(op->args.items[0]); + } + break; + + case VCODE_OP_COVER_TOGGLE: + { + printf("%s %u ", vcode_op_string(op->kind), op->tag); vcode_dump_reg(op->args.items[0]); } break; @@ -5472,12 +5479,18 @@ void emit_cover_stmt(uint32_t tag) op->tag = tag; } -void emit_cover_cond(vcode_reg_t test, uint32_t tag, unsigned sub) +void emit_cover_branch(vcode_reg_t test, uint32_t tag) { - op_t *op = vcode_add_op(VCODE_OP_COVER_COND); + op_t *op = vcode_add_op(VCODE_OP_COVER_BRANCH); vcode_add_arg(op, test); - op->tag = tag; - op->subkind = sub; + op->tag = tag; +} + +void emit_cover_toggle(vcode_reg_t signal, uint32_t tag) +{ + op_t *op = vcode_add_op(VCODE_OP_COVER_TOGGLE); + vcode_add_arg(op, signal); + op->tag = tag; } void emit_unreachable(vcode_reg_t locus) diff --git a/src/vcode.h b/src/vcode.h index c4814217..d4ac6167 100644 --- a/src/vcode.h +++ b/src/vcode.h @@ -103,7 +103,8 @@ typedef enum { VCODE_OP_LAST_EVENT, VCODE_OP_DEBUG_OUT, VCODE_OP_COVER_STMT, - VCODE_OP_COVER_COND, + VCODE_OP_COVER_BRANCH, + VCODE_OP_COVER_TOGGLE, VCODE_OP_UARRAY_LEN, VCODE_OP_UNDEFINED, VCODE_OP_RANGE_NULL, @@ -473,7 +474,8 @@ void emit_exponent_check(vcode_reg_t exp, vcode_reg_t locus); void emit_zero_check(vcode_reg_t denom, vcode_reg_t locus); void emit_debug_out(vcode_reg_t reg); void emit_cover_stmt(uint32_t tag); -void emit_cover_cond(vcode_reg_t test, uint32_t tag, unsigned sub); +void emit_cover_branch(vcode_reg_t test, uint32_t tag); +void emit_cover_toggle(vcode_reg_t signal, uint32_t tag); vcode_reg_t emit_undefined(vcode_type_t type); void emit_debug_info(const loc_t *loc); vcode_reg_t emit_range_null(vcode_reg_t left, vcode_reg_t right, diff --git a/test/lower/cover.vhd b/test/lower/cover.vhd index 72efa793..7c01d507 100644 --- a/test/lower/cover.vhd +++ b/test/lower/cover.vhd @@ -1,8 +1,13 @@ +Library ieee; +use ieee.std_logic_1164.all; + entity cover is end entity; architecture test of cover is signal s : integer; + signal l : std_logic; + signal l_vect : std_logic_vector(7 downto 0); begin p1: process is @@ -15,4 +20,17 @@ begin wait; end process; + p2: process + begin + l <= '0'; + l_vect <= (others => '0'); + wait for 1 ns; + l <= '1'; + l_vect <= (others => '1'); + wait for 1 ns; + l <= '0'; + l_vect <= (others => '0'); + wait; + end process; + end architecture; diff --git a/test/regress/cover1.sh b/test/regress/cover1.sh new file mode 100644 index 00000000..baab7e0c --- /dev/null +++ b/test/regress/cover1.sh @@ -0,0 +1,17 @@ +set -xe + +pwd +which nvc +which fstdump + +nvc -a $TESTDIR/regress/cover1.vhd -e --cover cover1 -r + +nvc -c --report html work/_WORK.COVER1.elab.covdb 2>&1 | tee out.txt + +# TODO: perhaps should be index.html? +if [ ! -f html/index.html ]; then + echo "missing coverage report" + exit 1 +fi + +diff -u $TESTDIR/regress/gold/cover1.txt out.txt diff --git a/test/regress/gold/cover1.txt b/test/regress/gold/cover1.txt index ac493994..839313f6 100644 --- a/test/regress/gold/cover1.txt +++ b/test/regress/gold/cover1.txt @@ -1,3 +1,4 @@ - 10/11 statements covered - 2/3 branches covered - 2/5 conditions covered +** Note: code coverage results for: WORK.COVER1 +** Note: statement: 90.9 % +** Note: branch: 83.3 % +** Note: toggle: N.A. diff --git a/test/regress/testlist.txt b/test/regress/testlist.txt index 3bf5aa9f..c4f961af 100644 --- a/test/regress/testlist.txt +++ b/test/regress/testlist.txt @@ -260,7 +260,7 @@ issue111 normal issue115 normal issue116 normal issue112 normal -cover1 cover,gold +cover1 cover,shell issue121 normal issue122 normal issue95 normal diff --git a/test/test_lower.c b/test/test_lower.c index f8daf8f1..a496410d 100644 --- a/test/test_lower.c +++ b/test/test_lower.c @@ -313,14 +313,8 @@ static void check_bb(int bb, const check_bb_t *expect, int len) } break; - case VCODE_OP_COVER_COND: - if (vcode_get_subkind(i) != e->subkind) { - vcode_dump_with_mark(i, NULL, NULL); - fail("expected op %d in block %d to have sub cond %x but " - "has %x", i, bb, e->subkind, vcode_get_subkind(i)); - } - // Fall-through - + case VCODE_OP_COVER_BRANCH: + case VCODE_OP_COVER_TOGGLE: case VCODE_OP_COVER_STMT: if (e->tag != vcode_get_tag(i)) { vcode_dump_with_mark(i, NULL, NULL); @@ -2014,7 +2008,7 @@ START_TEST(test_cover) tree_t e = run_elab(); - cover_tagging_t *tagging = cover_tag(e); + cover_tagging_t *tagging = cover_tags_init(COVER_MASK_ALL); lower_unit(e, tagging); vcode_unit_t v0 = find_unit("WORK.COVER.P1"); @@ -2024,34 +2018,39 @@ START_TEST(test_cover) { VCODE_OP_COVER_STMT, .tag = 0 }, { VCODE_OP_CONST, .value = 1 }, { VCODE_OP_STORE, .name = "V" }, - { VCODE_OP_COVER_STMT, .tag = 2 }, + { VCODE_OP_COVER_STMT, .tag = 1 }, { VCODE_OP_VAR_UPREF, .hops = 1, .name = "S" }, { VCODE_OP_LOAD_INDIRECT }, { VCODE_OP_RESOLVED }, { VCODE_OP_LOAD_INDIRECT }, { VCODE_OP_CMP, .cmp = VCODE_CMP_EQ }, - { VCODE_OP_COVER_COND, .tag = 0, .subkind = 1 }, { VCODE_OP_LOAD_INDIRECT }, { VCODE_OP_RESOLVED }, { VCODE_OP_LOAD_INDIRECT }, { VCODE_OP_CONST, .value = 10 }, { VCODE_OP_CMP, .cmp = VCODE_CMP_GT }, - { VCODE_OP_COVER_COND, .tag = 0, .subkind = 2 }, { VCODE_OP_OR }, - { VCODE_OP_COVER_COND, .tag = 0, .subkind = 0 }, + { VCODE_OP_COVER_BRANCH, .tag = 0 }, { VCODE_OP_COND, .target = 2, .target_else = 3 } }; CHECK_BB(1); EXPECT_BB(2) = { - { VCODE_OP_COVER_STMT, .tag = 1 }, + { VCODE_OP_COVER_STMT, .tag = 2 }, { VCODE_OP_CONST, .value = 2 }, { VCODE_OP_STORE, .name = "V" }, { VCODE_OP_JUMP, .target = 3 } }; CHECK_BB(2); + + EXPECT_BB(3) = { + { VCODE_OP_COVER_STMT, .tag = 3 }, + { VCODE_OP_WAIT, .target = 4 } + }; + + CHECK_BB(3); } END_TEST diff --git a/test/test_util.c b/test/test_util.c index 5023975f..cfde3735 100644 --- a/test/test_util.c +++ b/test/test_util.c @@ -65,7 +65,6 @@ static void setup(void) lib_add_search_path(lib_dir); opt_set_int(OPT_BOOTSTRAP, 0); - opt_set_int(OPT_COVER, 0); opt_set_int(OPT_UNIT_TEST, 1); opt_set_str(OPT_DUMP_VCODE, getenv("NVC_LOWER_VERBOSE")); opt_set_int(OPT_IGNORE_TIME, 0); @@ -95,7 +94,6 @@ static void setup_per_test(void) test_lib = lib_tmp("work"); lib_set_work(test_lib); - opt_set_int(OPT_COVER, 0); opt_set_int(OPT_MISSING_BODY, 0); reset_error_count(); -- 2.39.2