From a6575e9bd9c1bdb077f2ccc8ffa6360da91d8799 Mon Sep 17 00:00:00 2001 From: Nick Gasson Date: Sat, 22 Jul 2023 11:04:39 +0100 Subject: [PATCH] Add "force" and "noforce" commands to TCL shell --- src/rt/model.c | 88 ++++++++++++-------- src/rt/model.h | 8 +- src/rt/printer.c | 40 ++++++++- src/rt/printer.h | 2 + src/rt/shell.c | 188 +++++++++++++++++++++++++++++++++++------- src/vhpi/vhpi-model.c | 4 +- test/shell/force1.vhd | 33 ++++++++ test/test_shell.c | 84 +++++++++++++++++++ 8 files changed, 375 insertions(+), 72 deletions(-) create mode 100644 test/shell/force1.vhd diff --git a/src/rt/model.c b/src/rt/model.c index d5b9a9f9a..b64898490 100644 --- a/src/rt/model.c +++ b/src/rt/model.c @@ -3143,14 +3143,14 @@ static inline void check_delay(int64_t delay) } } -void force_signal(rt_signal_t *s, const void *values, int offset, size_t count) +void force_signal(rt_model_t *m, rt_signal_t *s, const void *values, + int offset, size_t count) { RT_LOCK(s->lock); - TRACE("force signal %s (offset %d) to %s", istr(tree_ident(s->where)), offset, + TRACE("force signal %s+%d to %s", istr(tree_ident(s->where)), offset, fmt_values(values, count)); - rt_model_t *m = get_model(); assert(m->can_create_delta); rt_nexus_t *n = split_nexus(m, s, offset, count); @@ -3169,14 +3169,37 @@ void force_signal(rt_signal_t *s, const void *values, int offset, size_t count) } } -void deposit_signal(rt_signal_t *s, const void *values, int offset, size_t count) +void release_signal(rt_model_t *m, rt_signal_t *s, int offset, size_t count) { RT_LOCK(s->lock); - TRACE("deposit signal %s (offset %d) to %s", istr(tree_ident(s->where)), + TRACE("release signal %s+%d", istr(tree_ident(s->where)), offset); + + assert(m->can_create_delta); + + rt_nexus_t *n = split_nexus(m, s, offset, count); + for (; count > 0; n = n->chain) { + count -= n->width; + assert(count >= 0); + + if (n->flags & NET_F_FORCED) + n->flags &= ~NET_F_FORCED; + + rt_source_t *src = get_forcing_source(m, n); + src->disconnected = 1; + + deltaq_insert_force_release(m, n); + } +} + +void deposit_signal(rt_model_t *m, rt_signal_t *s, const void *values, + int offset, size_t count) +{ + RT_LOCK(s->lock); + + TRACE("deposit signal %s+%d to %s", istr(tree_ident(s->where)), offset, fmt_values(values, count)); - rt_model_t *m = get_model(); assert(m->can_create_delta); rt_nexus_t *n = split_nexus(m, s, offset, count); @@ -3445,6 +3468,25 @@ int64_t get_static_expr(rt_model_t *m, tree_t expr) return eval_static_expr(m->jit, expr); } +void get_forcing_value(rt_signal_t *s, uint8_t *value) +{ + uint8_t *p = value; + rt_nexus_t *n = &(s->nexus); + for (int i = 0; i < s->n_nexus; i++) { + assert(n->n_sources > 0); + rt_source_t *s = NULL; + for (s = &(n->sources); s; s = s->chain_input) { + if (s->tag == SOURCE_FORCING) + break; + } + assert(s != NULL); + + memcpy(p, s->u.forcing.bytes, n->width * n->size); + p += n->width * n->size; + } + assert(p == value + s->shared.size); +} + //////////////////////////////////////////////////////////////////////////////// // Entry points from compiled code @@ -4081,30 +4123,16 @@ void x_disconnect(sig_shared_t *ss, uint32_t offset, int32_t count, void x_force(sig_shared_t *ss, uint32_t offset, int32_t count, void *values) { rt_signal_t *s = container_of(ss, rt_signal_t, shared); - RT_LOCK(s->lock); TRACE("force signal %s+%d value=%s count=%d", istr(tree_ident(s->where)), offset, fmt_values(values, count), count); rt_proc_t *proc = get_active_proc(); - - check_postponed(0, proc); - rt_model_t *m = get_model(); - rt_nexus_t *n = split_nexus(m, s, offset, count); - char *vptr = values; - for (; count > 0; n = n->chain) { - count -= n->width; - assert(count >= 0); - - n->flags |= NET_F_FORCED; - rt_source_t *src = get_forcing_source(m, n); - copy_value_ptr(n, &(src->u.forcing), vptr); - vptr += n->width * n->size; + check_postponed(0, proc); - deltaq_insert_force_release(m, n); - } + force_signal(m, s, values, offset, count); } void x_release(sig_shared_t *ss, uint32_t offset, int32_t count) @@ -4115,23 +4143,11 @@ void x_release(sig_shared_t *ss, uint32_t offset, int32_t count) offset, count); rt_proc_t *proc = get_active_proc(); - - check_postponed(0, proc); - rt_model_t *m = get_model(); - rt_nexus_t *n = split_nexus(m, s, offset, count); - for (; count > 0; n = n->chain) { - count -= n->width; - assert(count >= 0); - if (n->flags & NET_F_FORCED) - n->flags &= ~NET_F_FORCED; - - rt_source_t *src = get_forcing_source(m, n); - src->disconnected = 1; + check_postponed(0, proc); - deltaq_insert_force_release(m, n); - } + release_signal(m, s, offset, count); } void x_resolve_signal(sig_shared_t *ss, jit_handle_t handle, void *context, diff --git a/src/rt/model.h b/src/rt/model.h index 9652eefcc..2158dca89 100644 --- a/src/rt/model.h +++ b/src/rt/model.h @@ -60,9 +60,13 @@ const void *signal_last_value(rt_signal_t *s); uint8_t signal_size(rt_signal_t *s); uint32_t signal_width(rt_signal_t *s); size_t signal_expand(rt_signal_t *s, uint64_t *buf, size_t max); -void force_signal(rt_signal_t *s, const void *values, int offset, size_t count); -void deposit_signal(rt_signal_t *s, const void *values, int offset, size_t count); +void force_signal(rt_model_t *m, rt_signal_t *s, const void *values, + int offset, size_t count); +void release_signal(rt_model_t *m, rt_signal_t *s, int offset, size_t count); +void deposit_signal(rt_model_t *m, rt_signal_t *s, const void *values, + int offset, size_t count); rt_watch_t *find_watch(rt_nexus_t *n, sig_event_fn_t fn); +void get_forcing_value(rt_signal_t *s, uint8_t *value); int64_t get_static_expr(rt_model_t *m, tree_t expr); diff --git a/src/rt/printer.c b/src/rt/printer.c index 90525a6fc..a99f6c17d 100644 --- a/src/rt/printer.c +++ b/src/rt/printer.c @@ -98,6 +98,24 @@ static void std_logic_vector_printer(print_func_t *f, const void *data, tb_append(f->printer->buf, '"'); } +static void bit_printer(print_func_t *f, const void *data, size_t size, + print_flags_t flags) +{ + assert(size == 1); + tb_cat(f->printer->buf, *(uint8_t *)data ? "'1'" : "'0'"); +} + +static void bit_vector_printer(print_func_t *f, const void *data, + size_t size, print_flags_t flags) +{ + tb_append(f->printer->buf, '"'); + + for (int i = 0; i < size; i++) + tb_append(f->printer->buf, *((uint8_t *)data + i) ? '1' : '0'); + + tb_append(f->printer->buf, '"'); +} + printer_t *printer_new(void) { printer_t *p = xcalloc(sizeof(printer_t)); @@ -145,6 +163,9 @@ print_func_t *printer_for(printer_t *p, type_t type) case W_IEEE_ULOGIC: f->typefn = std_logic_printer; break; + case W_STD_BIT: + f->typefn = bit_printer; + break; default: goto invalid; } @@ -155,10 +176,13 @@ print_func_t *printer_for(printer_t *p, type_t type) case W_IEEE_ULOGIC_VECTOR: f->typefn = std_logic_vector_printer; break; - default: - goto invalid; - } - break; + case W_STD_BIT_VECTOR: + f->typefn = bit_vector_printer; + break; + default: + goto invalid; + } + break; default: goto invalid; } @@ -177,3 +201,11 @@ const char *print_signal(print_func_t *fn, rt_signal_t *s, print_flags_t flags) (*fn->typefn)(fn, s->shared.data, s->shared.size, flags); return tb_get(fn->printer->buf); } + +const char *print_raw(print_func_t *fn, const void *data, size_t size, + print_flags_t flags) +{ + tb_rewind(fn->printer->buf); + (*fn->typefn)(fn, data, size, flags); + return tb_get(fn->printer->buf); +} diff --git a/src/rt/printer.h b/src/rt/printer.h index aa63e2184..8f2ce40a5 100644 --- a/src/rt/printer.h +++ b/src/rt/printer.h @@ -33,5 +33,7 @@ void printer_free(printer_t *p); print_func_t *printer_for(printer_t *p, type_t type); const char *print_signal(print_func_t *fn, rt_signal_t *s, print_flags_t flags); +const char *print_raw(print_func_t *fn, const void *data, size_t size, + print_flags_t flags); #endif // _RT_PRINTER_H diff --git a/src/rt/shell.c b/src/rt/shell.c index 4c25365c9..29d670656 100644 --- a/src/rt/shell.c +++ b/src/rt/shell.c @@ -102,6 +102,12 @@ static int tcl_error(tcl_shell_t *sh, const char *fmt, ...) return TCL_ERROR; } +static int syntax_error(tcl_shell_t *sh, Tcl_Obj *const objv[]) +{ + return tcl_error(sh, "syntax error, enter $bold$help %s$$ for usage", + Tcl_GetString(objv[0])); +} + __attribute__((format(printf, 2, 3))) static void shell_printf(tcl_shell_t *sh, const char *fmt, ...) { @@ -179,6 +185,20 @@ static void shell_update_now(tcl_shell_t *sh) Tcl_UpdateLinkedVar(sh->interp, "deltas"); } +static bool shell_get_printer(tcl_shell_t *sh, shell_signal_t *ss) +{ + if (ss->printer == NULL) + ss->printer = printer_for(sh->printer, tree_type(ss->signal->where)); + + if (ss->printer == NULL) { + tcl_error(sh, "cannot display type %s", + type_pp(tree_type(ss->signal->where))); + return false; + } + + return true; +} + static void shell_add_cmd(tcl_shell_t *sh, const char *name, Tcl_ObjCmdProc fn, const char *help) { @@ -343,7 +363,7 @@ static int shell_cmd_find(ClientData cd, Tcl_Interp *interp, return TCL_OK; usage: - return tcl_error(sh, "syntax error, enter $bold$help find$$ for usage"); + return syntax_error(sh, objv); } static const char analyse_help[] = @@ -389,8 +409,7 @@ static int shell_cmd_analyse(ClientData cd, Tcl_Interp *interp, return error_count() > 0 ? TCL_ERROR : TCL_OK; usage: - return tcl_error(sh, "syntax error, enter $bold$help %s$$ for usage", - Tcl_GetString(objv[0])); + return syntax_error(sh, objv); } static const char elaborate_help[] = @@ -453,8 +472,7 @@ static int shell_cmd_elaborate(ClientData cd, Tcl_Interp *interp, return TCL_OK; usage: - return tcl_error(sh, "syntax error, enter $bold$help %s$$ for usage", - Tcl_GetString(objv[0])); + return syntax_error(sh, objv); } static const char examine_help[] = @@ -520,14 +538,14 @@ static int shell_cmd_examine(ClientData cd, Tcl_Interp *interp, else if (strcmp(opt, "-radix") == 0 && pos + 1 < objc) { const char *arg = Tcl_GetString(objv[pos++]); if (!parse_radix(arg, &flags)) - goto usage; + return syntax_error(sh, objv); } else - goto usage; + return syntax_error(sh, objv); } if (pos == objc) - goto usage; + return syntax_error(sh, objv); const int count = objc - pos; Tcl_Obj *single[1], **result = single; @@ -541,12 +559,8 @@ static int shell_cmd_examine(ClientData cd, Tcl_Interp *interp, if (ss == NULL) return tcl_error(sh, "cannot find name '%s'", name); - if (ss->printer == NULL) - ss->printer = printer_for(sh->printer, tree_type(ss->signal->where)); - - if (ss->printer == NULL) - return tcl_error(sh, "cannot display type %s", - type_pp(tree_type(ss->signal->where))); + if (!shell_get_printer(sh, ss)) + return TCL_ERROR; const char *str = print_signal(ss->printer, ss->signal, flags); result[i] = Tcl_NewStringObj(str, -1); @@ -561,10 +575,132 @@ static int shell_cmd_examine(ClientData cd, Tcl_Interp *interp, Tcl_SetObjResult(interp, result[0]); return TCL_OK; +} - usage: - return tcl_error(sh, "syntax error, enter $bold$help %s$$ for usage", - Tcl_GetString(objv[0])); +static const char force_help[] = + "Force the value of a signal\n" + "\n" + "Syntax:\n" + " force [ ]\n" + "\n" + "Value can be either an enumeration literal ('1', true), an integer " + "(42, 0), or a bit string literal (\"10111\") and must be appropriate " + "for the signal type. Without arguments lists all currently forced " + "signals.\n" + "\n" + "Examples:\n" + " force /uut/foo '1'\n" + " force /bitvec \"10011\"\n"; + +static int shell_cmd_force(ClientData cd, Tcl_Interp *interp, + int objc, Tcl_Obj *const objv[]) +{ + tcl_shell_t *sh = cd; + + if (!shell_has_model(sh)) + return TCL_ERROR; + else if (objc != 3 && objc != 1) + return syntax_error(sh, objv); + + if (objc == 1) { + for (int i = 0; i < sh->nsignals; i++) { + shell_signal_t *ss = &(sh->signals[i]); + if (!(ss->signal->nexus.flags & NET_F_FORCED)) + continue; + + if (!shell_get_printer(sh, ss)) + return TCL_ERROR; + + const size_t nbytes = ss->signal->shared.size; + uint8_t *value LOCAL = xmalloc(nbytes); + get_forcing_value(ss->signal, value); + + shell_printf(sh, "force %s %s\n", istr(ss->path), + print_raw(ss->printer, value, nbytes, 0)); + } + + return TCL_OK; + } + + const char *signame = Tcl_GetString(objv[1]); + const char *valstr = Tcl_GetString(objv[2]); + + shell_signal_t *ss = hash_get(sh->namemap, ident_new(signame)); + if (ss == NULL) + return tcl_error(sh, "cannot find signal '%s'", signame); + + type_t type = tree_type(ss->signal->where); + + parsed_value_t value; + if (!parse_value(type, valstr, &value)) + return tcl_error(sh, "value '%s' is not valid for type %s", + valstr, type_pp(type)); + + if (type_is_scalar(type)) + force_signal(sh->model, ss->signal, &value.integer, 0, 1); + else if (type_is_character_array(type)) { + const int width = signal_width(ss->signal); + if (value.enums->count != width) { + tcl_error(sh, "expected %d elements for signal %s but have %d", width, + signame, value.enums->count); + free(value.enums); + return TCL_ERROR; + } + + force_signal(sh->model, ss->signal, value.enums->values, 0, width); + free(value.enums); + } + else + return tcl_error(sh, "cannot force signals of type %s", type_pp(type)); + + return TCL_OK; +} + +static const char noforce_help[] = + "Stop forcing the value of signals\n" + "\n" + "Syntax:\n" + " noforce ...\n" + " noforce *\n" + "\n" + "The second form stops forcing all currently forced signals.\n" + "\n" + "Examples:\n" + " noforce /uut/foo /baz\n"; + +static int shell_cmd_noforce(ClientData cd, Tcl_Interp *interp, + int objc, Tcl_Obj *const objv[]) +{ + tcl_shell_t *sh = cd; + + if (!shell_has_model(sh)) + return TCL_ERROR; + else if (objc == 1) + return syntax_error(sh, objv); + + for (int i = 1; i < objc; i++) { + const char *signame = Tcl_GetString(objv[i]); + if (strcmp(signame, "*") == 0) { + for (int i = 0; i < sh->nsignals; i++) { + shell_signal_t *ss = &(sh->signals[i]); + if (ss->signal->nexus.flags & NET_F_FORCED) + release_signal(sh->model, ss->signal, 0, + signal_width(ss->signal)); + } + } + else { + shell_signal_t *ss = hash_get(sh->namemap, ident_new(signame)); + if (ss == NULL) + return tcl_error(sh, "cannot find signal '%s'", signame); + + if (!(ss->signal->nexus.flags & NET_F_FORCED)) + return tcl_error(sh, "signal %s is not forced", signame); + + release_signal(sh->model, ss->signal, 0, signal_width(ss->signal)); + } + } + + return TCL_OK; } static const char add_help[] = @@ -582,7 +718,7 @@ static int shell_cmd_add(ClientData cd, Tcl_Interp *interp, tcl_shell_t *sh = cd; if (objc != 3 || strcmp(Tcl_GetString(objv[1]), "wave") != 0) - goto usage; + return syntax_error(sh, objv); else if (!shell_has_model(sh)) return TCL_ERROR; @@ -601,9 +737,6 @@ static int shell_cmd_add(ClientData cd, Tcl_Interp *interp, } return TCL_OK; - - usage: - return tcl_error(sh, "syntax error, enter $bold$help add$$ for usage"); } static const char quit_help[] = @@ -626,11 +759,11 @@ static int shell_cmd_quit(ClientData cd, Tcl_Interp *interp, if (strcmp(opt, "-sim") == 0) quit_sim = true; else - goto usage; + return syntax_error(sh, objv); } if (pos != objc) - goto usage; + return syntax_error(sh, objv); if (quit_sim) { if (!shell_has_model(sh)) @@ -646,9 +779,6 @@ static int shell_cmd_quit(ClientData cd, Tcl_Interp *interp, } return TCL_OK; - - usage: - return tcl_error(sh, "syntax error, enter $bold$help quit$$ for usage"); } static const char exit_help[] = @@ -682,7 +812,7 @@ static int shell_cmd_exit(ClientData cd, Tcl_Interp *interp, Tcl_Exit(status); usage: - return tcl_error(sh, "syntax error, enter $bold$help exit$$ for usage"); + return syntax_error(sh, objv); } static const char help_help[] = @@ -700,7 +830,7 @@ static int shell_cmd_help(ClientData cd, Tcl_Interp *interp, const char *which = Tcl_GetString(objv[1]); for (int i = 0; i < sh->ncmds; i++) { if (strcmp(sh->cmds[i].name, which) == 0) { - fputs(sh->cmds[i].help, stdout); + shell_printf(sh, "%s", sh->cmds[i].help); return TCL_OK; } } @@ -905,6 +1035,8 @@ tcl_shell_t *shell_new(jit_factory_t make_jit) shell_add_cmd(sh, "add", shell_cmd_add, add_help); shell_add_cmd(sh, "quit", shell_cmd_quit, quit_help); shell_add_cmd(sh, "exit", shell_cmd_exit, exit_help); + shell_add_cmd(sh, "force", shell_cmd_force, force_help); + shell_add_cmd(sh, "noforce", shell_cmd_noforce, noforce_help); qsort(sh->cmds, sh->ncmds, sizeof(shell_cmd_t), compare_shell_cmd); diff --git a/src/vhpi/vhpi-model.c b/src/vhpi/vhpi-model.c index afaf473f9..3f9174eaf 100644 --- a/src/vhpi/vhpi-model.c +++ b/src/vhpi/vhpi-model.c @@ -2399,9 +2399,9 @@ int vhpi_put_value(vhpiHandleT handle, } if (mode == vhpiForcePropagate) - force_signal(signal, ptr, offset, num_elems); + force_signal(model, signal, ptr, offset, num_elems); else - deposit_signal(signal, ptr, offset, num_elems); + deposit_signal(model, signal, ptr, offset, num_elems); return 0; } diff --git a/test/shell/force1.vhd b/test/shell/force1.vhd new file mode 100644 index 000000000..305d8994c --- /dev/null +++ b/test/shell/force1.vhd @@ -0,0 +1,33 @@ +entity force1 is +end entity; + +architecture test of force1 is + signal x : bit; + signal y : integer; + signal z : bit_vector(1 to 3); +begin + + x <= '0'; + y <= 55; + z <= "001"; + + tb: process is + begin + wait for 1 ns; + assert x = '0'; + assert y = 55; + assert z = "001"; + wait for 1 ns; + assert x = '1'; + assert y = 42; + assert z = "110"; + wait for 1 ns; + assert x = '0'; + assert y = 42; + wait for 1 ns; + assert y = 55; + assert z = "001"; + wait; + end process; + +end architecture; diff --git a/test/test_shell.c b/test/test_shell.c index 474139160..2851ec969 100644 --- a/test/test_shell.c +++ b/test/test_shell.c @@ -315,6 +315,89 @@ START_TEST(test_exit) } END_TEST +static void force1_stdout_handler(const char *buf, size_t nchars, void *user) +{ + static const char *expect[] = { + "force /x '1'\n", "force /y 42\n", "force /z \"110\"\n" + }; + + int *state = user; + ck_assert_int_lt(*state, ARRAY_LEN(expect)); + ck_assert_int_eq(nchars, strlen(expect[*state])); + ck_assert_mem_eq(buf, expect[*state], nchars); + + (*state)++; +} + +START_TEST(test_force1) +{ + const error_t expect[] = { + { LINE_INVALID, "expected 3 elements for signal /z but have 5" }, + { LINE_INVALID, "value '11' is not valid for type BIT" }, + { LINE_INVALID, "signal /x is not forced" }, + { -1, NULL } + }; + expect_errors(expect); + + tcl_shell_t *sh = shell_new(jit_new); + + int state = 0; + shell_handler_t handler = { + .stdout_write = force1_stdout_handler, + .context = &state + }; + shell_set_handler(sh, &handler); + + const char *result = NULL; + + shell_eval(sh, "analyse " TESTDIR "/shell/force1.vhd", &result); + ck_assert_str_eq(result, ""); + + shell_eval(sh, "elaborate force1", &result); + ck_assert_str_eq(result, ""); + + shell_eval(sh, "run 1 ns", &result); + ck_assert_str_eq(result, ""); + + shell_eval(sh, "force /x '1'", &result); + ck_assert_str_eq(result, ""); + + shell_eval(sh, "force /y 42", &result); + ck_assert_str_eq(result, ""); + + shell_eval(sh, "force /z \"110\"", &result); + ck_assert_str_eq(result, ""); + + shell_eval(sh, "force", &result); + ck_assert_str_eq(result, ""); + ck_assert_int_eq(state, 3); + + shell_eval(sh, "run 1 ns", &result); + ck_assert_str_eq(result, ""); + + fail_if(shell_eval(sh, "force /z \"11011\"", &result)); + fail_if(shell_eval(sh, "force /x {11}", &result)); + + shell_eval(sh, "noforce /x", &result); + ck_assert_str_eq(result, ""); + + shell_eval(sh, "run 1 ns", &result); + ck_assert_str_eq(result, ""); + + shell_eval(sh, "noforce *", &result); + ck_assert_str_eq(result, ""); + + shell_eval(sh, "noforce /x", &result); + + shell_eval(sh, "run", &result); + ck_assert_str_eq(result, ""); + + shell_free(sh); + + check_expected_errors(); +} +END_TEST + Suite *get_shell_tests(void) { Suite *s = suite_create("shell"); @@ -325,6 +408,7 @@ Suite *get_shell_tests(void) tcase_add_test(tc, test_examine1); tcase_add_test(tc, test_wave1); tcase_add_test(tc, test_redirect); + tcase_add_test(tc, test_force1); tcase_add_exit_test(tc, test_exit, 5); suite_add_tcase(s, tc);