• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nickg / nvc / 13639324349

03 Mar 2025 07:58PM UTC coverage: 92.277% (+0.2%) from 92.103%
13639324349

push

github

nickg
Add a new mid-level IR to replace vcode

4917 of 5118 new or added lines in 14 files covered. (96.07%)

130 existing lines in 6 files now uncovered.

68023 of 73716 relevant lines covered (92.28%)

481213.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.55
/src/rt/shell.c
1
//
2
//  Copyright (C) 2011-2024  Nick Gasson
3
//
4
//  This program is free software: you can redistribute it and/or modify
5
//  it under the terms of the GNU General Public License as published by
6
//  the Free Software Foundation, either version 3 of the License, or
7
//  (at your option) any later version.
8
//
9
//  This program is distributed in the hope that it will be useful,
10
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
//  GNU General Public License for more details.
13
//
14
//  You should have received a copy of the GNU General Public License
15
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
//
17

18
#include "util.h"
19
#include "common.h"
20
#include "diag.h"
21
#include "eval.h"
22
#include "hash.h"
23
#include "lib.h"
24
#include "lower.h"
25
#include "phase.h"
26
#include "printer.h"
27
#include "rt/assert.h"
28
#include "rt/model.h"
29
#include "rt/structs.h"
30
#include "scan.h"
31
#include "shell.h"
32
#include "tree.h"
33
#include "type.h"
34

35
#include <assert.h>
36
#include <errno.h>
37
#include <inttypes.h>
38
#include <stdarg.h>
39
#include <stdio.h>
40
#include <stdlib.h>
41
#include <string.h>
42
#include <unistd.h>
43

44
#include <readline/readline.h>
45
#include <readline/history.h>
46

47
#undef DLLEXPORT
48
#include <tcl.h>
49

50
#define TIME_BUFSZ 32
51

52
typedef struct {
53
   const char     *name;
54
   Tcl_ObjCmdProc *fn;
55
   const char     *help;
56
} shell_cmd_t;
57

58
typedef enum {
59
   SHELL_SIGNAL,
60
   SHELL_REGION,
61
} object_kind_t;
62

63
typedef struct {
64
   object_kind_t kind;
65
   ident_t       name;
66
   ident_t       path;
67
} shell_object_t;
68

69
typedef struct {
70
   shell_object_t  obj;
71
   rt_signal_t    *signal;
72
   print_func_t   *printer;
73
   rt_watch_t     *watch;
74
   tcl_shell_t    *owner;
75
} shell_signal_t;
76

77
typedef struct {
78
   shell_object_t  obj;
79
   rt_scope_t     *scope;
80
} shell_region_t;
81

82
typedef char *(*get_line_fn_t)(tcl_shell_t *);
83

84
typedef struct _tcl_shell {
85
   char            *prompt;
86
   Tcl_Interp      *interp;
87
   shell_cmd_t     *cmds;
88
   size_t           ncmds;
89
   size_t           cmdalloc;
90
   rt_model_t      *model;
91
   tree_t           top;
92
   rt_scope_t      *root;
93
   shell_signal_t  *signals;
94
   unsigned         nsignals;
95
   shell_region_t  *regions;
96
   unsigned         nregions;
97
   hash_t          *namemap;
98
   jit_t           *jit;
99
   int64_t          now_var;
100
   unsigned         deltas_var;
101
   printer_t       *printer;
102
   get_line_fn_t    getline;
103
   jit_factory_t    make_jit;
104
   unit_registry_t *registry;
105
   shell_handler_t  handler;
106
   bool             quit;
107
   char            *datadir;
108
} tcl_shell_t;
109

110
static __thread tcl_shell_t *rl_shell = NULL;
111

112
__attribute__((format(printf, 2, 3)))
113
static int tcl_error(tcl_shell_t *sh, const char *fmt, ...)
4✔
114
{
115
   va_list ap;
4✔
116
   va_start(ap, fmt);
4✔
117
   char *buf LOCAL = color_vasprintf(fmt, ap);
4✔
118
   va_end(ap);
4✔
119

120
   Tcl_SetObjResult(sh->interp, Tcl_NewStringObj(buf, -1));
4✔
121
   return TCL_ERROR;
4✔
122
}
123

124
static void tcl_dict_put(tcl_shell_t *sh, Tcl_Obj *dict, const char *key,
14✔
125
                         Tcl_Obj *value)
126
{
127
   Tcl_DictObjPut(sh->interp, dict, Tcl_NewStringObj(key, -1), value);
14✔
128
}
14✔
129

130
static Tcl_Obj *tcl_ident_string(ident_t ident)
23✔
131
{
132
   return Tcl_NewStringObj(istr(ident), ident_len(ident));
23✔
133
}
134

135
static int syntax_error(tcl_shell_t *sh, Tcl_Obj *const objv[])
×
136
{
137
   return tcl_error(sh, "syntax error, enter $bold$help %s$$ for usage",
×
138
                    Tcl_GetString(objv[0]));
139
}
140

141
__attribute__((format(printf, 2, 3)))
142
static void shell_printf(tcl_shell_t *sh, const char *fmt, ...)
3✔
143
{
144
   va_list ap;
3✔
145
   va_start(ap, fmt);
3✔
146

147
   if (sh->handler.stdout_write != NULL) {
3✔
148
      char *buf LOCAL = color_vasprintf(fmt, ap);
6✔
149
      (*sh->handler.stdout_write)(buf, strlen(buf), sh->handler.context);
3✔
150
   }
151
   else
152
      wrapped_vprintf(fmt, ap);
×
153

154
   va_end(ap);
3✔
155
}
3✔
156

157
static bool shell_has_model(tcl_shell_t *sh)
73✔
158
{
159
   if (sh->model == NULL) {
73✔
160
      tcl_error(sh, "no simulation loaded, try the $bold$elaborate$$ "
×
161
                "command first");
162
      return false;
×
163
   }
164

165
   return true;
166
}
167

168
static void shell_clear_model(tcl_shell_t *sh)
29✔
169
{
170
   if (sh->model == NULL)
29✔
171
      return;
172

173
   model_free(sh->model);
8✔
174
   hash_free(sh->namemap);
8✔
175

176
   sh->model = NULL;
8✔
177
   sh->namemap = NULL;
8✔
178

179
   if (sh->handler.quit_sim != NULL)
8✔
180
      (*sh->handler.quit_sim)(sh->handler.context);
2✔
181
}
182

183
static void shell_next_time_step(rt_model_t *m, void *user)
5✔
184
{
185
   tcl_shell_t *sh = user;
5✔
186
   assert(sh->handler.next_time_step != NULL);
5✔
187

188
   uint64_t now = model_now(m, NULL);
5✔
189
   (*sh->handler.next_time_step)(now, sh->handler.context);
5✔
190

191
   model_set_global_cb(sh->model, RT_NEXT_TIME_STEP, shell_next_time_step, sh);
5✔
192
}
5✔
193

194
static void shell_create_model(tcl_shell_t *sh)
21✔
195
{
196
   assert(sh->model == NULL);
21✔
197

198
   sh->model = model_new(sh->jit, NULL);
21✔
199
   create_scope(sh->model, sh->top, NULL);
21✔
200

201
   if (sh->handler.next_time_step != NULL)
21✔
202
      model_set_global_cb(sh->model, RT_NEXT_TIME_STEP,
3✔
203
                          shell_next_time_step, sh);
204

205
   model_reset(sh->model);
21✔
206

207
   if ((sh->root = find_scope(sh->model, tree_stmt(sh->top, 0))) == NULL)
21✔
208
      fatal_trace("cannot find root scope");
209
}
21✔
210

211
static void shell_update_now(tcl_shell_t *sh)
37✔
212
{
213
   sh->now_var = model_now(sh->model, &sh->deltas_var);
37✔
214

215
   Tcl_UpdateLinkedVar(sh->interp, "now");
37✔
216
   Tcl_UpdateLinkedVar(sh->interp, "deltas");
37✔
217
}
37✔
218

219
static bool shell_get_printer(tcl_shell_t *sh, shell_signal_t *ss)
36✔
220
{
221
   if (ss->printer == NULL)
36✔
222
      ss->printer = printer_for(sh->printer, tree_type(ss->signal->where));
18✔
223

224
   if (ss->printer == NULL) {
36✔
225
      tcl_error(sh, "cannot display type %s",
×
226
                type_pp(tree_type(ss->signal->where)));
×
227
      return false;
×
228
   }
229

230
   return true;
231
}
232

233
static void shell_add_cmd(tcl_shell_t *sh, const char *name, Tcl_ObjCmdProc fn,
384✔
234
                          const char *help)
235
{
236
   shell_cmd_t cmd = { name, fn, help };
384✔
237

238
   if (sh->cmdalloc == sh->ncmds) {
384✔
239
      sh->cmdalloc = MAX(sh->cmdalloc * 2, 16);
24✔
240
      sh->cmds = xrealloc_array(sh->cmds, sh->cmdalloc, sizeof(shell_cmd_t));
24✔
241
   }
242

243
   sh->cmds[sh->ncmds++] = cmd;
384✔
244

245
   Tcl_CreateObjCommand(sh->interp, name, fn, sh, NULL);
384✔
246
}
384✔
247

248
static void shell_event_cb(uint64_t now, rt_signal_t *s, rt_watch_t *w,
5✔
249
                           void *user)
250
{
251
   shell_signal_t *ss = user;
5✔
252
   shell_handler_t *h = &(ss->owner->handler);
5✔
253

254
   if (h->signal_update != NULL) {
5✔
255
      const char *enc = print_signal(ss->printer, ss->signal, PRINT_F_ENCODE);
5✔
256
      (*h->signal_update)(ss->obj.path, now, s, enc, h->context);
5✔
257
   }
258
}
5✔
259

260
static void watch_signal(shell_signal_t *ss)
5✔
261
{
262
   ss->watch = watch_new(ss->owner->model, shell_event_cb, ss,
5✔
263
                         WATCH_POSTPONED, 1);
264
   model_set_event_cb(ss->owner->model, ss->signal, ss->watch);
5✔
265
}
5✔
266

267
static void recreate_objects(tcl_shell_t *sh, rt_scope_t *scope,
8✔
268
                             shell_signal_t **sptr, shell_region_t **rptr)
269
{
270
   shell_region_t *r = (*rptr)++;
8✔
271
   assert(r->obj.name == ident_downcase(tree_ident(scope->where)));
8✔
272
   r->scope = scope;
8✔
273

274
   for (int i = 0; i < scope->signals.count; i++) {
19✔
275
      shell_signal_t *ss = (*sptr)++;
11✔
276
      ss->signal = scope->signals.items[i];
11✔
277
      assert(ss->obj.name == ident_downcase(tree_ident(ss->signal->where)));
11✔
278

279
      if (ss->watch != NULL)
11✔
280
         watch_signal(ss);
1✔
281
   }
282

283
   for (int i = 0; i < scope->aliases.count; i++) {
12✔
284
      rt_alias_t *a = scope->aliases.items[i];
4✔
285

286
      shell_signal_t *ss = (*sptr)++;
4✔
287
      assert(ss->obj.name == ident_downcase(tree_ident(a->where)));
4✔
288
      ss->signal = a->signal;
4✔
289

290
      if (ss->watch != NULL)
4✔
291
         watch_signal(ss);
×
292
   }
293

294
   for (int i = 0; i < scope->children.count; i++)
12✔
295
      recreate_objects(sh, scope->children.items[i], sptr, rptr);
4✔
296
}
8✔
297

298
const char *next_option(int *pos, int objc, Tcl_Obj *const objv[])
52✔
299
{
300
   if (*pos >= objc)
52✔
301
      return NULL;
302

303
   const char *opt = Tcl_GetString(objv[*pos]);
49✔
304
   if (opt[0] != '-')
49✔
305
      return NULL;
306

307
   (*pos)++;
7✔
308
   return opt;
7✔
309
}
310

311
static shell_object_t *get_object(tcl_shell_t *sh, const char *name)
40✔
312
{
313
   shell_object_t *obj = hash_get(sh->namemap, ident_new(name));
40✔
314
   if (obj == NULL)
40✔
315
      tcl_error(sh, "cannot find name '%s'", name);
1✔
316

317
   return obj;
40✔
318
}
319

320
static shell_signal_t *get_signal(tcl_shell_t *sh, const char *name)
35✔
321
{
322
   shell_object_t *obj = get_object(sh, name);
35✔
323
   if (obj == NULL)
35✔
324
      return NULL;
325
   else if (obj->kind != SHELL_SIGNAL) {
35✔
326
      tcl_error(sh, "'%s' is not a signal", name);
×
327
      return NULL;
×
328
   }
329

330
   return container_of(obj, shell_signal_t, obj);
331
}
332

333
static const char restart_help[] =
334
   "Restart the simulation";
335

336
static int shell_cmd_restart(ClientData cd, Tcl_Interp *interp,
4✔
337
                             int objc, Tcl_Obj *const objv[])
338
{
339
   tcl_shell_t *sh = cd;
4✔
340

341
   if (!shell_has_model(sh))
4✔
342
      return TCL_ERROR;
343

344
   model_free(sh->model);
4✔
345
   sh->model = NULL;
4✔
346

347
   jit_reset(sh->jit);
4✔
348

349
   clear_vhdl_assert();
4✔
350
   for (vhdl_severity_t s = SEVERITY_NOTE; s <= SEVERITY_FAILURE; s++)
20✔
351
      set_vhdl_assert_enable(s, true);
16✔
352

353
   shell_create_model(sh);
4✔
354

355
   shell_signal_t *wptr = sh->signals;
4✔
356
   shell_region_t *rptr = sh->regions;
4✔
357
   recreate_objects(sh, sh->root, &wptr, &rptr);
4✔
358
   assert(wptr == sh->signals + sh->nsignals);
4✔
359
   assert(rptr == sh->regions + sh->nregions);
4✔
360

361
   shell_update_now(sh);
4✔
362

363
   if (sh->handler.restart_sim != NULL)
4✔
364
      (*sh->handler.restart_sim)(sh->handler.context);
1✔
365

366
   return TCL_OK;
367
}
368

369
static const char run_help[] =
370
   "Start or resume the simulation";
371

372
static int shell_cmd_run(ClientData cd, Tcl_Interp *interp,
16✔
373
                         int objc, Tcl_Obj *const objv[])
374
{
375
   tcl_shell_t *sh = cd;
16✔
376
   static bool sim_running = false;
16✔
377

378
   if (!shell_has_model(sh))
16✔
379
      return TCL_ERROR;
380
   else if (sim_running)
16✔
381
      return tcl_error(sh, "simulation already running");
×
382

383
   uint64_t stop_time = UINT64_MAX;
16✔
384
   if (objc == 3) {
16✔
385
      Tcl_WideInt base;
5✔
386
      int error = Tcl_GetWideIntFromObj(interp, objv[1], &base);
5✔
387
      if (error != TCL_OK || base <= 0)
5✔
388
         return tcl_error(sh, "invalid time");
×
389

390
      const char *unit = Tcl_GetString(objv[2]);
5✔
391

392
      uint64_t mult;
5✔
393
      if      (strcmp(unit, "fs") == 0) mult = 1;
5✔
394
      else if (strcmp(unit, "ps") == 0) mult = 1000;
5✔
395
      else if (strcmp(unit, "ns") == 0) mult = 1000000;
5✔
396
      else if (strcmp(unit, "us") == 0) mult = 1000000000;
×
397
      else if (strcmp(unit, "ms") == 0) mult = 1000000000000;
×
398
      else
399
         return tcl_error(sh, "invalid time unit %s", unit);
×
400

401
      stop_time = model_now(sh->model, NULL) + (base * mult);
5✔
402
   }
403
   else if (objc != 1)
11✔
404
      return tcl_error(sh, "usage: $bold$run [time units]$$");
×
405

406

407
   sim_running = true;
16✔
408
   model_run(sh->model, stop_time);
16✔
409
   sim_running = false;
16✔
410

411
   shell_update_now(sh);
16✔
412

413
   return TCL_OK;
16✔
414
}
415

416
static const char find_help[] =
417
   "Find signals and other objects in the design\n"
418
   "\n"
419
   "Syntax:\n"
420
   "  find signals [options] <pattern>\n"
421
   "  find regions [options] <pattern>\n"
422
   "\n"
423
   "Options:\n"
424
   "  -r, -recursive\tInclude subregions in wildcard search.\n"
425
   "\n"
426
   "Examples:\n"
427
   "  find signals -r /*\tList all signals in the design\n"
428
   "  find signals /uut/x*\tAll signals in instance UUT that start with X\n"
429
   "  find regions -r *\tList all regions in the design\n";
430

431
static int shell_cmd_find(ClientData cd, Tcl_Interp *interp,
6✔
432
                          int objc, Tcl_Obj *const objv[])
433
{
434
   tcl_shell_t *sh = cd;
6✔
435

436
   enum { SIGNALS, REGIONS } what;
6✔
437
   if (!shell_has_model(sh))
6✔
438
      return TCL_ERROR;
439
   else if (objc < 3)
6✔
440
      goto usage;
×
441
   else if (strcmp(Tcl_GetString(objv[1]), "signals") == 0)
6✔
442
      what = SIGNALS;
443
   else if (strcmp(Tcl_GetString(objv[1]), "regions") == 0)
×
444
      what = REGIONS;
445
   else
446
      goto usage;
×
447

448
   int pos = 2;
6✔
449
   for (const char *opt; (opt = next_option(&pos, objc, objv)); ) {
6✔
450
      if (strcmp(opt, "-recursive") == 0 || strcmp(opt, "-r") == 0) {
×
451
         // Always recursive for now...
452
      }
453
      else
454
         goto usage;
×
455
   }
456

457
   const char *glob = Tcl_GetString(objv[pos]);
6✔
458
   Tcl_Obj *result = Tcl_NewListObj(0, NULL);
6✔
459

460
   switch (what) {
6✔
461
   case SIGNALS:
462
      {
463
         for (int i = 0; i < sh->nsignals; i++) {
21✔
464
            if (!ident_glob(sh->signals[i].obj.path, glob, -1))
15✔
465
               continue;
×
466

467
            Tcl_Obj *obj = tcl_ident_string(sh->signals[i].obj.path);
15✔
468
            Tcl_ListObjAppendElement(interp, result, obj);
15✔
469
         }
470
      }
471
      break;
472

473
   case REGIONS:
474
      {
475
         for (int i = 0; i < sh->nregions; i++) {
×
476
            if (!ident_glob(sh->regions[i].obj.path, glob, -1))
×
477
               continue;
×
478

479
            Tcl_Obj *obj = tcl_ident_string(sh->regions[i].obj.path);
×
480
            Tcl_ListObjAppendElement(interp, result, obj);
×
481
         }
482
         break;
483
      }
484
      break;
485
   }
486

487
   Tcl_SetObjResult(interp, result);
6✔
488
   return TCL_OK;
6✔
489

490
 usage:
×
491
   return syntax_error(sh, objv);
×
492
}
493

494
static const char elaborate_help[] =
495
   "Elaborate a design hierarchy\n"
496
   "\n"
497
   "Syntax:\n"
498
   "  elaborate [options] <toplevel>\n"
499
   "\n"
500
   "Note \"vsim\" is an alias of this command.\n"
501
   "\n"
502
   "Options:\n"
503
   "\n"
504
   "Examples:\n"
505
   "  elaborate toplevel\n"
506
   "  vsim toplevel\n";
507

508
static int shell_cmd_elaborate(ClientData cd, Tcl_Interp *interp,
10✔
509
                               int objc, Tcl_Obj *const objv[])
510
{
511
   tcl_shell_t *sh = cd;
10✔
512
   LOCAL_TEXT_BUF tb = tb_new();
20✔
513

514
   int pos = 1;
10✔
515
   for (const char *opt; (opt = Tcl_GetString(objv[pos]))[0] == '-'; pos++)
10✔
516
      goto usage;
×
517

518
   if (pos + 1 != objc)
10✔
UNCOV
519
      goto usage;
×
520

521
   lib_t work = lib_work();
10✔
522

523
   tb_istr(tb, lib_name(work));
10✔
524
   tb_append(tb, '.');
10✔
525
   tb_cat(tb, Tcl_GetString(objv[pos]));
10✔
526
   tb_upcase(tb);
10✔
527

528
   tree_t unit = lib_get(lib_work(), ident_new(tb_get(tb)));
10✔
529
   if (unit == NULL)
10✔
UNCOV
530
      return tcl_error(sh, "cannot find unit %s in library %s",
×
531
                       Tcl_GetString(objv[pos]), istr(lib_name(work)));
532

533
   shell_clear_model(sh);
10✔
534

535
   reset_error_count();
10✔
536

537
   // Recreate the JIT instance and unit registry as it may have
538
   // references to stale code
539
   jit_free(sh->jit);
10✔
540
   unit_registry_free(sh->registry);
10✔
541
   sh->registry = unit_registry_new();
10✔
542
   sh->jit = (*sh->make_jit)(sh->registry);
10✔
543

544
   rt_model_t *m = model_new(sh->jit, NULL);
10✔
545
   tree_t top = elab(tree_to_object(unit), sh->jit, sh->registry,
10✔
546
                     NULL, NULL, m);
547
   model_free(m);   // XXX: reuse
10✔
548
   if (top == NULL)
10✔
549
      return TCL_ERROR;
550

551
   shell_reset(sh, top);
10✔
552
   return TCL_OK;
10✔
553

UNCOV
554
 usage:
×
UNCOV
555
   return syntax_error(sh, objv);
×
556
}
557

558
static const char examine_help[] =
559
   "Display current value of one of more signals\n"
560
   "\n"
561
   "Syntax:\n"
562
   "  examine [options] <name>...\n"
563
   "\n"
564
   "Note \"exa\" is an alias of this command.\n"
565
   "\n"
566
   "Options:\n"
567
   "  -radix <type>\tFormat as hexadecimal, decimal, or binary.\n"
568
   "  -<radix>\tAlias of \"-radix <radix>\".\n"
569
   "\n"
570
   "Examples:\n"
571
   "  examine /uut/foo\n"
572
   "  exa -hex sig\n";
573

574
static bool parse_radix(const char *str, print_flags_t *flags)
5✔
575
{
576
   if (strcmp(str, "binary") == 0 || strcmp(str, "bin") == 0
5✔
577
       || strcmp(str, "b") == 0) {
4✔
578
      *flags &= ~PRINT_F_RADIX;
1✔
579
      *flags |= PRINT_F_BIN;
1✔
580
      return true;
1✔
581
   }
582
   else if (strcmp(str, "-hexadecimal") == 0 || strcmp(str, "hex") == 0
4✔
583
            || strcmp(str, "h") == 0) {
1✔
584
      *flags &= ~PRINT_F_RADIX;
3✔
585
      *flags |= PRINT_F_HEX;
3✔
586
      return true;
3✔
587
   }
588
   else
589
      return false;
590
}
591

592
static int shell_cmd_examine(ClientData cd, Tcl_Interp *interp,
27✔
593
                             int objc, Tcl_Obj *const objv[])
594
{
595
   tcl_shell_t *sh = cd;
27✔
596

597
   if (!shell_has_model(sh))
27✔
598
      return TCL_ERROR;
599

600
   print_flags_t flags = 0;
27✔
601
   int pos = 1;
27✔
602
   for (const char *opt; (opt = next_option(&pos, objc, objv)); ) {
31✔
603
      if (parse_radix(opt + 1, &flags))
4✔
604
         continue;
3✔
605
      else if (strcmp(opt, "-radix") == 0 && pos + 1 < objc) {
1✔
606
         const char *arg = Tcl_GetString(objv[pos++]);
1✔
607
         if (!parse_radix(arg, &flags))
1✔
UNCOV
608
            goto usage;
×
609
      }
610
      else
UNCOV
611
         goto usage;
×
612
   }
613

614
   if (pos == objc)
27✔
UNCOV
615
      goto usage;
×
616

617
   const int count = objc - pos;
27✔
618
   Tcl_Obj *single[1], **result = single;
27✔
619

620
   if (count > 1)
27✔
621
      result = xmalloc_array(count, sizeof(Tcl_Obj *));
1✔
622

623
   for (int i = 0; pos < objc; pos++, i++) {
55✔
624
      const char *name = Tcl_GetString(objv[pos]);
28✔
625
      shell_signal_t *ss = get_signal(sh, name);
28✔
626
      if (ss == NULL)
28✔
627
         return TCL_ERROR;
628

629
      if (!shell_get_printer(sh, ss))
28✔
630
         return TCL_ERROR;
631

632
      const char *str = print_signal(ss->printer, ss->signal, flags);
28✔
633
      result[i] = Tcl_NewStringObj(str, -1);
28✔
634
   }
635

636
   if (count > 1) {
27✔
637
      Tcl_Obj *list = Tcl_NewListObj(count, result);
1✔
638
      Tcl_SetObjResult(interp, list);
1✔
639
      free(result);
1✔
640
   }
641
   else
642
      Tcl_SetObjResult(interp, result[0]);
26✔
643

644
   return TCL_OK;
645

UNCOV
646
 usage:
×
UNCOV
647
   return syntax_error(sh, objv);
×
648
}
649

650
static const char describe_help[] =
651
   "Return information about an object or region\n"
652
   "\n"
653
   "Syntax:\n"
654
   "  describe <name>...\n"
655
   "\n"
656
   "Examples:\n"
657
   "  describe /uut/foo\n"
658
   "  describe /uut\n";
659

660
static int shell_cmd_describe(ClientData cd, Tcl_Interp *interp,
5✔
661
                              int objc, Tcl_Obj *const objv[])
662
{
663
   tcl_shell_t *sh = cd;
5✔
664

665
   if (!shell_has_model(sh))
5✔
666
      return TCL_ERROR;
667

668
   int pos = 1;
5✔
669
   for (const char *opt; (opt = next_option(&pos, objc, objv)); ) {
5✔
UNCOV
670
      goto usage;
×
671
   }
672

673
   if (pos == objc)
5✔
UNCOV
674
      goto usage;
×
675

676
   const int count = objc - pos;
5✔
677
   Tcl_Obj *single[1], **result = single;
5✔
678

679
   if (count > 1)
5✔
UNCOV
680
      result = xmalloc_array(count, sizeof(Tcl_Obj *));
×
681

682
   for (int i = 0; pos < objc; pos++, i++) {
9✔
683
      const char *name = Tcl_GetString(objv[pos]);
5✔
684
      shell_object_t *obj = get_object(sh, name);
5✔
685
      if (obj == NULL)
5✔
686
         return TCL_ERROR;
687

688
      Tcl_Obj *d = result[i] = Tcl_NewDictObj();
4✔
689

690
      tcl_dict_put(sh, d, "name", tcl_ident_string(obj->name));
4✔
691
      tcl_dict_put(sh, d, "path", tcl_ident_string(obj->path));
4✔
692

693
      switch (obj->kind) {
4✔
694
      case SHELL_SIGNAL:
2✔
695
         {
696
            tcl_dict_put(sh, d, "kind", Tcl_NewStringObj("signal", -1));
2✔
697

698
            shell_signal_t *ss = container_of(obj, shell_signal_t, obj);
2✔
699
            type_t type = tree_type(ss->signal->where);
2✔
700
            tcl_dict_put(sh, d, "type", Tcl_NewStringObj(type_pp(type), -1));
2✔
701
         }
702
         break;
2✔
703
      case SHELL_REGION:
2✔
704
         tcl_dict_put(sh, d, "kind", Tcl_NewStringObj("region", -1));
2✔
705
         break;
2✔
706
      }
707
   }
708

709
   if (count > 1) {
4✔
UNCOV
710
      Tcl_Obj *list = Tcl_NewListObj(count, result);
×
UNCOV
711
      Tcl_SetObjResult(interp, list);
×
UNCOV
712
      free(result);
×
713
   }
714
   else
715
      Tcl_SetObjResult(interp, result[0]);
4✔
716

717
   return TCL_OK;
718

UNCOV
719
 usage:
×
720
   return syntax_error(sh, objv);
×
721
}
722

723
static const char force_help[] =
724
   "Force the value of a signal\n"
725
   "\n"
726
   "Syntax:\n"
727
   "  force [<signal> <value>]\n"
728
   "\n"
729
   "Value can be either an enumeration literal ('1', true), an integer "
730
   "(42, 0), or a bit string literal (\"10111\") and must be appropriate "
731
   "for the signal type. Without arguments lists all currently forced "
732
   "signals.\n"
733
   "\n"
734
   "Examples:\n"
735
   "  force /uut/foo '1'\n"
736
   "  force /bitvec \"10011\"\n";
737

738
static int shell_cmd_force(ClientData cd, Tcl_Interp *interp,
6✔
739
                           int objc, Tcl_Obj *const objv[])
740
{
741
   tcl_shell_t *sh = cd;
6✔
742

743
   if (!shell_has_model(sh))
6✔
744
      return TCL_ERROR;
745
   else if (objc != 3 && objc != 1)
6✔
UNCOV
746
      return syntax_error(sh, objv);
×
747

748
   if (objc == 1) {
6✔
749
      for (int i = 0; i < sh->nsignals; i++) {
4✔
750
         shell_signal_t *ss = &(sh->signals[i]);
3✔
751
         if (!(ss->signal->nexus.flags & NET_F_FORCED))
3✔
UNCOV
752
            continue;
×
753

754
         if (!shell_get_printer(sh, ss))
3✔
UNCOV
755
            return TCL_ERROR;
×
756

757
         const size_t nbytes = ss->signal->shared.size;
3✔
758
         uint8_t *value LOCAL = xmalloc(nbytes);
6✔
759
         get_forcing_value(ss->signal, value);
3✔
760

761
         shell_printf(sh, "force %s %s\n", istr(ss->obj.path),
3✔
762
                      print_raw(ss->printer, value, nbytes, 0));
763
      }
764

765
      return TCL_OK;
766
   }
767

768
   const char *signame = Tcl_GetString(objv[1]);
5✔
769
   const char *valstr = Tcl_GetString(objv[2]);
5✔
770

771
   shell_signal_t *ss = get_signal(sh, signame);
5✔
772
   if (ss == NULL)
5✔
773
      return TCL_ERROR;
774

775
   type_t type = tree_type(ss->signal->where);
5✔
776

777
   parsed_value_t value;
5✔
778
   if (!parse_value(type, valstr, &value))
5✔
779
      return tcl_error(sh, "value '%s' is not valid for type %s",
1✔
780
                       valstr, type_pp(type));
781

782
   if (type_is_scalar(type))
4✔
783
      force_signal(sh->model, ss->signal, &value.integer, 0, 1);
2✔
784
   else if (type_is_character_array(type)) {
2✔
785
      const int width = signal_width(ss->signal);
2✔
786
      if (value.enums->count != width) {
2✔
787
         tcl_error(sh, "expected %d elements for signal %s but have %d", width,
1✔
788
                   signame, value.enums->count);
789
         free(value.enums);
1✔
790
         return TCL_ERROR;
1✔
791
      }
792

793
      force_signal(sh->model, ss->signal, value.enums->values, 0, width);
1✔
794
      free(value.enums);
1✔
795
   }
796
   else
UNCOV
797
      return tcl_error(sh, "cannot force signals of type %s", type_pp(type));
×
798

799
   return TCL_OK;
800
}
801

802
static const char noforce_help[] =
803
   "Stop forcing the value of signals\n"
804
   "\n"
805
   "Syntax:\n"
806
   "  noforce <signal>...\n"
807
   "  noforce *\n"
808
   "\n"
809
   "The second form stops forcing all currently forced signals.\n"
810
   "\n"
811
   "Examples:\n"
812
   "  noforce /uut/foo /baz\n";
813

814
static int shell_cmd_noforce(ClientData cd, Tcl_Interp *interp,
3✔
815
                             int objc, Tcl_Obj *const objv[])
816
{
817
   tcl_shell_t *sh = cd;
3✔
818

819
   if (!shell_has_model(sh))
3✔
820
      return TCL_ERROR;
821
   else if (objc == 1)
3✔
UNCOV
822
      return syntax_error(sh, objv);
×
823

824
   for (int i = 1; i < objc; i++) {
5✔
825
      const char *signame = Tcl_GetString(objv[i]);
3✔
826
      if (strcmp(signame, "*") == 0) {
3✔
827
         for (int i = 0; i < sh->nsignals; i++) {
4✔
828
            shell_signal_t *ss = &(sh->signals[i]);
3✔
829
            if (ss->signal->nexus.flags & NET_F_FORCED)
3✔
830
               release_signal(sh->model, ss->signal, 0,
2✔
831
                              signal_width(ss->signal));
2✔
832
         }
833
      }
834
      else {
835
         shell_signal_t *ss = get_signal(sh, signame);
2✔
836
         if (ss == NULL)
2✔
837
            return TCL_ERROR;
838

839
         if (!(ss->signal->nexus.flags & NET_F_FORCED))
2✔
840
            return tcl_error(sh, "signal %s is not forced", signame);
1✔
841

842
         release_signal(sh->model, ss->signal, 0, signal_width(ss->signal));
1✔
843
      }
844
   }
845

846
   return TCL_OK;
847
}
848

849
static const char add_help[] =
850
   "Add signals and other objects to the display\n"
851
   "\n"
852
   "Syntax:\n"
853
   "  add wave [options] <name>...\n"
854
   "\n"
855
   "Options:\n"
856
   "  -r, -recursive\tInclude subregions in wildcard search.\n"
857
   "\n"
858
   "Examples:\n"
859
   "  add wave /*\tAdd all signals to waveform\n";
860

861
static int shell_cmd_add(ClientData cd, Tcl_Interp *interp,
4✔
862
                         int objc, Tcl_Obj *const objv[])
863
{
864
   tcl_shell_t *sh = cd;
4✔
865
   char **globs LOCAL = NULL;
8✔
866

867
   if (objc < 3 || strcmp(Tcl_GetString(objv[1]), "wave") != 0)
4✔
868
      goto usage;
×
869
   else if (!shell_has_model(sh))
4✔
870
      return TCL_ERROR;
871

872
   int pos = 2;
4✔
873
   for (const char *opt; (opt = next_option(&pos, objc, objv)); ) {
4✔
UNCOV
874
      if (strcmp(opt, "-recursive") == 0 || strcmp(opt, "-r") == 0) {
×
875
         // Always recursive for now...
876
      }
877
      else
UNCOV
878
         goto usage;
×
879
   }
880

881
   const int nglobs = objc - pos;
4✔
882
   globs = xmalloc_array(nglobs, sizeof(char *));
4✔
883
   for (int i = 0; i < nglobs; i++)
9✔
884
      globs[i] = Tcl_GetString(objv[pos++]);
5✔
885

886
   for (int i = 0; i < sh->nsignals; i++) {
16✔
887
      shell_signal_t *ss = &(sh->signals[i]);
12✔
888

889
      bool match = false;
12✔
890
      for (int j = 0; j < nglobs; j++)
27✔
891
         match |= ident_glob(ss->obj.path, globs[j], -1);
15✔
892

893
      if (!match || !shell_get_printer(sh, ss))
12✔
894
         continue;
7✔
895

896
      if (sh->handler.add_wave != NULL) {
5✔
897
         const char *enc =
5✔
898
            print_signal(ss->printer, ss->signal, PRINT_F_ENCODE);
5✔
899
         (*sh->handler.add_wave)(ss->obj.path, enc, sh->handler.context);
5✔
900
      }
901

902
      if (ss->watch == NULL)
5✔
903
         watch_signal(ss);
4✔
904
   }
905

906
   return TCL_OK;
907

UNCOV
908
 usage:
×
UNCOV
909
   return syntax_error(sh, objv);
×
910
}
911

912
static const char quit_help[] =
913
   "Exit the simulator or unload the current design\n"
914
   "\n"
915
   "Syntax:\n"
916
   "  quit [-sim]\n"
917
   "\n"
918
   "Options:\n"
919
   "  -sim\t\tUnload the current simulation but do not exit the program.\n";
920

921
static int shell_cmd_quit(ClientData cd, Tcl_Interp *interp,
2✔
922
                          int objc, Tcl_Obj *const objv[])
923
{
924
   tcl_shell_t *sh = cd;
2✔
925

926
   bool quit_sim = false;
2✔
927
   int pos = 1;
2✔
928
   for (const char *opt; (opt = next_option(&pos, objc, objv)); ) {
4✔
929
      if (strcmp(opt, "-sim") == 0)
2✔
930
         quit_sim = true;
931
      else
UNCOV
932
         goto usage;
×
933
   }
934

935
   if (pos != objc)
2✔
UNCOV
936
      goto usage;
×
937

938
   if (quit_sim) {
2✔
939
      if (!shell_has_model(sh))
2✔
940
         return TCL_ERROR;
941
      else
942
         shell_clear_model(sh);
2✔
943
   }
944
   else {
UNCOV
945
      sh->quit = true;
×
946

UNCOV
947
      if (sh->handler.exit != NULL)
×
UNCOV
948
         (*sh->handler.exit)(0, sh->handler.context);
×
949
   }
950

951
   return TCL_OK;
952

UNCOV
953
 usage:
×
954
   return syntax_error(sh, objv);
×
955
}
956

957
static const char exit_help[] =
958
   "Exit the simulator and return a status code\n"
959
   "\n"
960
   "Syntax:\n"
961
   "  exit [-code <integer>]\n"
962
   "\n"
963
   "Options:\n"
964
   "  -code <integer>\tStatus code to return to shell.\n";
965

966
static int shell_cmd_exit(ClientData cd, Tcl_Interp *interp,
1✔
967
                          int objc, Tcl_Obj *const objv[])
968
{
969
   tcl_shell_t *sh = cd;
1✔
970

971
   int pos = 1, status = EXIT_SUCCESS;
1✔
972
   for (const char *opt; (opt = next_option(&pos, objc, objv)); ) {
2✔
973
      if (strcmp(opt, "-code") == 0 && pos < objc)
1✔
974
         status = atoi(Tcl_GetString(objv[pos++]));
1✔
975
      else
UNCOV
976
         goto usage;
×
977
   }
978

979
   if (pos != objc)
1✔
UNCOV
980
      goto usage;
×
981

982
   if (sh->handler.exit != NULL)
1✔
983
      (*sh->handler.exit)(status, sh->handler.context);
1✔
984

985
   Tcl_Exit(status);
1✔
986

UNCOV
987
 usage:
×
UNCOV
988
   return syntax_error(sh, objv);
×
989
}
990

991
static const char help_help[] =
992
   "Display list of commands or detailed help\n"
993
   "\n"
994
   "Use $bold$help <command>$$ to display detailed usage of a particular\n"
995
   "command.\n";
996

UNCOV
997
static int shell_cmd_help(ClientData cd, Tcl_Interp *interp,
×
998
                          int objc, Tcl_Obj *const objv[])
999
{
1000
   tcl_shell_t *sh = cd;
×
1001

UNCOV
1002
   if (objc == 2) {
×
UNCOV
1003
      const char *which = Tcl_GetString(objv[1]);
×
UNCOV
1004
      for (int i = 0; i < sh->ncmds; i++) {
×
UNCOV
1005
         if (strcmp(sh->cmds[i].name, which) == 0) {
×
UNCOV
1006
            shell_printf(sh, "%s", sh->cmds[i].help);
×
UNCOV
1007
            return TCL_OK;
×
1008
         }
1009
      }
1010

UNCOV
1011
      return tcl_error(sh, "invalid command '%s'", which);
×
1012
   }
UNCOV
1013
   else if (objc != 1)
×
UNCOV
1014
      return tcl_error(sh, "syntax error, try $bold$help$$");
×
1015

UNCOV
1016
   shell_printf(sh, "List of supported commands:\n");
×
1017

UNCOV
1018
   for (shell_cmd_t *c = sh->cmds; c < sh->cmds + sh->ncmds; c++) {
×
UNCOV
1019
      const int linelen = strchrnul(c->help, '\n') - c->help;
×
UNCOV
1020
      shell_printf(sh, "  $bold$%-16s$$%.*s\n", c->name, linelen, c->help);
×
1021
   }
1022

UNCOV
1023
   shell_printf(sh, "\n");
×
UNCOV
1024
   shell_printf(sh, "Use $bold$help <command>$$ for detailed usage "
×
1025
                "of a particular command. Standard TCL commands are "
1026
                "also accepted.\n");
1027

UNCOV
1028
   return TCL_OK;
×
1029
}
1030

1031
static const char copyright_help[] = "Display copyright information";
1032

1033
static int shell_cmd_copyright(ClientData cd, Tcl_Interp *interp,
×
1034
                               int objc, Tcl_Obj *const objv[])
1035
{
UNCOV
1036
   Tcl_Channel channel = Tcl_GetStdChannel(TCL_STDOUT);
×
1037

UNCOV
1038
   extern char copy_string[];
×
UNCOV
1039
   Tcl_WriteChars(channel, copy_string, -1);
×
UNCOV
1040
   Tcl_WriteChars(channel, "\n", 1);
×
UNCOV
1041
   Tcl_Flush(channel);
×
1042

1043
   return TCL_OK;
×
1044
}
1045

1046
static const char echo_help[] = "Display value of arguments";
1047

1048
static int shell_cmd_echo(ClientData cd, Tcl_Interp *interp,
1✔
1049
                          int objc, Tcl_Obj *const objv[])
1050
{
1051
   Tcl_Channel channel = Tcl_GetStdChannel(TCL_STDOUT);
1✔
1052

1053
   for (int i = 1; i < objc; i++) {
3✔
1054
      if (i > 1) Tcl_WriteChars(channel, " ", 1);
2✔
1055
      Tcl_WriteObj(channel, objv[i]);
2✔
1056
   }
1057

1058
   Tcl_WriteChars(channel, "\n", 1);
1✔
1059
   Tcl_Flush(channel);
1✔
1060

1061
   return TCL_OK;
1✔
1062
}
1063

1064
static char *shell_list_generator(const char *script, const char *text,
×
1065
                                  int state, int prefix)
1066
{
UNCOV
1067
   static Tcl_Obj *list = NULL;
×
UNCOV
1068
   static int index, len, max;
×
1069

1070
   if (!state) {
×
UNCOV
1071
      if (Tcl_Eval(rl_shell->interp, script) != TCL_OK)
×
1072
         return NULL;
1073

1074
      list = Tcl_GetObjResult(rl_shell->interp);
×
1075

UNCOV
1076
      if (Tcl_ListObjLength(rl_shell->interp, list, &max) != TCL_OK)
×
1077
         return NULL;
1078

1079
      index = 0;
×
UNCOV
1080
      len = strlen(text);
×
1081
   }
1082

UNCOV
1083
   while (index < max) {
×
1084
      Tcl_Obj *obj;
×
1085
      if (Tcl_ListObjIndex(rl_shell->interp, list, index++, &obj) != TCL_OK)
×
1086
         return NULL;
×
1087

UNCOV
1088
      const char *str = Tcl_GetString(obj);
×
1089
      if (strncmp(str, text + prefix, len - prefix) == 0) {
×
UNCOV
1090
         if (prefix == 0)
×
UNCOV
1091
            return xstrdup(str);
×
1092
         else {
UNCOV
1093
            assert(len >= prefix);
×
UNCOV
1094
            const size_t complen = strlen(str);
×
UNCOV
1095
            char *buf = xmalloc(prefix + complen + 1);
×
UNCOV
1096
            memcpy(buf, text, prefix);
×
UNCOV
1097
            memcpy(buf + prefix, str, complen + 1);
×
UNCOV
1098
            return buf;
×
1099
         }
1100
      }
1101
   }
1102

1103
   return NULL;
1104
}
1105

UNCOV
1106
static char *shell_command_generator(const char *text, int state)
×
1107
{
UNCOV
1108
   return shell_list_generator("info commands", text, state, 0);
×
1109
}
1110

UNCOV
1111
static char *shell_variable_generator(const char *text, int state)
×
1112
{
1113
   return shell_list_generator("info vars", text, state, 1);
×
1114
}
1115

1116
static char **shell_tab_completion(const char *text, int start, int end)
×
1117
{
UNCOV
1118
   rl_attempted_completion_over = 0;
×
1119

1120
   if (text[0] == '$')
×
UNCOV
1121
      return rl_completion_matches(text, shell_variable_generator);
×
1122

1123
   // Determine if we are completing a TCL command or not
UNCOV
1124
   int pos = start - 1;
×
1125
   for (; pos >= 0 && isspace_iso88591(rl_line_buffer[pos]); pos--);
×
1126

UNCOV
1127
   if (pos == -1 || rl_line_buffer[pos] == '[')
×
UNCOV
1128
      return rl_completion_matches(text, shell_command_generator);
×
1129

1130
   return NULL;
1131
}
1132

UNCOV
1133
static char *shell_completing_get_line(tcl_shell_t *sh)
×
1134
{
1135
   rl_attempted_completion_function = shell_tab_completion;
×
1136
   rl_completer_quote_characters = "\"'";
×
1137
   rl_completer_word_break_characters = " \t\r\n[]{}";
×
UNCOV
1138
   rl_special_prefixes = "$";
×
1139
   rl_shell = sh;
×
1140

1141
   char *buf = readline(sh->prompt);
×
1142
   if ((buf != NULL) && (*buf != '\0'))
×
1143
      add_history(buf);
×
1144

UNCOV
1145
   rl_shell = NULL;
×
UNCOV
1146
   return buf;
×
1147
}
1148

1149

UNCOV
1150
static char *shell_raw_get_line(tcl_shell_t *sh)
×
1151
{
1152
   fputs(sh->prompt, stdout);
×
UNCOV
1153
   fflush(stdout);
×
1154

UNCOV
1155
   LOCAL_TEXT_BUF tb = tb_new();
×
1156

1157
   size_t off = 0;
×
UNCOV
1158
   for (;;) {
×
1159
      int ch = fgetc(stdin);
×
UNCOV
1160
      fputc(ch, stdout);
×
UNCOV
1161
      switch (ch) {
×
1162
      case EOF:
1163
         return (off > 0) ? tb_claim(tb) : NULL;
1164
      case '\n':
×
UNCOV
1165
         return tb_claim(tb);
×
1166
      default:
×
1167
         tb_append(tb, ch);
×
1168
      }
1169
   }
1170
}
1171

UNCOV
1172
void shell_print_banner(tcl_shell_t *sh)
×
1173
{
1174
   extern const char version_string[];
×
UNCOV
1175
   shell_printf(sh, "\n");
×
1176

UNCOV
1177
   if (sh->handler.stdout_write == NULL)
×
UNCOV
1178
      print_centred(version_string);
×
1179
   else
UNCOV
1180
      shell_printf(sh, "\t%s", version_string);
×
1181

1182
   static const char blurb[] =
×
1183
      "\n\nThis program comes with ABSOLUTELY NO WARRANTY. This is free "
1184
      "software, and you are welcome to redistribute it under certain "
1185
      "conditions; type $bold$copyright$$ for details.\n\n"
1186
      "Type $bold$help$$ for a list of supported commands.\n\n";
1187

1188
   shell_printf(sh, blurb);
×
1189
}
×
1190

1191
static int compare_shell_cmd(const void *a, const void *b)
1,416✔
1192
{
1193
   return strcmp(((shell_cmd_t *)a)->name, ((shell_cmd_t *)b)->name);
1,416✔
1194
}
1195

1196
tcl_shell_t *shell_new(jit_factory_t make_jit, unit_registry_t *registry)
24✔
1197
{
1198
   tcl_shell_t *sh = xcalloc(sizeof(tcl_shell_t));
24✔
1199
#ifdef RL_VERSION_MAJOR
1200
   sh->prompt   = color_asprintf("\001$+cyan$\002%%\001$$\002 ");
24✔
1201
#else
1202
   sh->prompt   = color_asprintf("$+cyan$%%$$ ");
1203
#endif
1204
   sh->interp   = Tcl_CreateInterp();
24✔
1205
   sh->make_jit = make_jit;
24✔
1206
   sh->registry = registry ?: unit_registry_new();
24✔
1207
   sh->jit      = make_jit ? (*make_jit)(sh->registry) : NULL;
24✔
1208
   sh->printer  = printer_new();
24✔
1209

1210
   if (isatty(fileno(stdin)))
24✔
1211
      sh->getline = shell_completing_get_line;
×
1212
   else
1213
      sh->getline = shell_raw_get_line;
24✔
1214

1215
   if (Tcl_Init(sh->interp) != 0)
24✔
UNCOV
1216
      fatal("%s", Tcl_GetStringResult(sh->interp));
×
1217

1218
   Tcl_LinkVar(sh->interp, "now", (char *)&sh->now_var,
24✔
1219
               TCL_LINK_WIDE_INT | TCL_LINK_READ_ONLY);
1220
   Tcl_LinkVar(sh->interp, "deltas", (char *)&sh->deltas_var,
24✔
1221
               TCL_LINK_UINT | TCL_LINK_READ_ONLY);
1222

1223
   {
1224
      LOCAL_TEXT_BUF tb = tb_new();
24✔
1225
      get_data_dir(tb);
24✔
1226
      sh->datadir = tb_claim(tb);
24✔
1227
   }
1228
   Tcl_LinkVar(sh->interp, "nvc_dataDir", (char *)&sh->datadir,
24✔
1229
               TCL_LINK_READ_ONLY | TCL_LINK_STRING);
1230

1231
   atexit(Tcl_Finalize);
24✔
1232

1233
   Tcl_DeleteCommand(sh->interp, "exit");
24✔
1234

1235
   shell_add_cmd(sh, "help", shell_cmd_help, help_help);
24✔
1236
   shell_add_cmd(sh, "exit", shell_cmd_exit, exit_help);
24✔
1237
   shell_add_cmd(sh, "copyright", shell_cmd_copyright, copyright_help);
24✔
1238
   shell_add_cmd(sh, "find", shell_cmd_find, find_help);
24✔
1239
   shell_add_cmd(sh, "run", shell_cmd_run, run_help);
24✔
1240
   shell_add_cmd(sh, "restart", shell_cmd_restart, restart_help);
24✔
1241
   shell_add_cmd(sh, "elaborate", shell_cmd_elaborate, elaborate_help);
24✔
1242
   shell_add_cmd(sh, "vsim", shell_cmd_elaborate, elaborate_help);
24✔
1243
   shell_add_cmd(sh, "examine", shell_cmd_examine, examine_help);
24✔
1244
   shell_add_cmd(sh, "exa", shell_cmd_examine, examine_help);
24✔
1245
   shell_add_cmd(sh, "add", shell_cmd_add, add_help);
24✔
1246
   shell_add_cmd(sh, "quit", shell_cmd_quit, quit_help);
24✔
1247
   shell_add_cmd(sh, "force", shell_cmd_force, force_help);
24✔
1248
   shell_add_cmd(sh, "noforce", shell_cmd_noforce, noforce_help);
24✔
1249
   shell_add_cmd(sh, "echo", shell_cmd_echo, echo_help);
24✔
1250
   shell_add_cmd(sh, "describe", shell_cmd_describe, describe_help);
24✔
1251

1252
   qsort(sh->cmds, sh->ncmds, sizeof(shell_cmd_t), compare_shell_cmd);
24✔
1253

1254
   return sh;
24✔
1255
}
1256

1257
void shell_free(tcl_shell_t *sh)
13✔
1258
{
1259
   if (sh->model != NULL) {
13✔
1260
      model_free(sh->model);
9✔
1261
      hash_free(sh->namemap);
9✔
1262
      free(sh->signals);
9✔
1263
      free(sh->regions);
9✔
1264
   }
1265

1266
   if (sh->jit != NULL)
13✔
1267
      jit_free(sh->jit);
10✔
1268

1269
   unit_registry_free(sh->registry);
13✔
1270
   printer_free(sh->printer);
13✔
1271
   Tcl_DeleteInterp(sh->interp);
13✔
1272

1273
   free(sh->datadir);
13✔
1274
   free(sh->prompt);
13✔
1275
   free(sh->cmds);
13✔
1276
   free(sh);
13✔
1277
}
13✔
1278

1279
bool shell_eval(tcl_shell_t *sh, const char *script, const char **result)
54✔
1280
{
1281
   const int code = Tcl_Eval(sh->interp, script);
54✔
1282

1283
   switch (code) {
53✔
1284
   case TCL_OK:
49✔
1285
      if (result != NULL)
49✔
1286
         *result = Tcl_GetStringResult(sh->interp);
49✔
1287
      return true;
1288
   case TCL_ERROR:
4✔
1289
      {
1290
         const char *info = Tcl_GetVar(sh->interp, "::errorInfo", 0);
4✔
1291
         if (info != NULL && *info != '\n')
4✔
1292
            errorf("%s", info);
4✔
1293

1294
         *result = Tcl_GetStringResult(sh->interp);
4✔
1295
         if (info == NULL && *result != NULL && **result != '\0')
4✔
UNCOV
1296
            errorf("%s", *result);
×
1297
      }
1298
      return false;
UNCOV
1299
   default:
×
UNCOV
1300
      warnf("Tcl_Eval returned unknown code %d", code);
×
UNCOV
1301
      return false;
×
1302
   }
1303
}
1304

1305
static void count_objects(rt_scope_t *scope, unsigned *nsignals,
26✔
1306
                          unsigned *nregions)
1307
{
1308
   *nsignals += scope->signals.count + scope->aliases.count;
26✔
1309
   *nregions += 1;
26✔
1310

1311
   for (int i = 0; i < scope->children.count;i ++)
35✔
1312
      count_objects(scope->children.items[i], nsignals, nregions);
9✔
1313
}
26✔
1314

1315
static void recurse_objects(tcl_shell_t *sh, rt_scope_t *scope,
26✔
1316
                            text_buf_t *path, shell_signal_t **sptr,
1317
                            shell_region_t **rptr)
1318
{
1319
   const int base = tb_len(path);
26✔
1320

1321
   shell_region_t *r = (*rptr)++;
26✔
1322
   r->scope = scope;
26✔
1323
   r->obj.kind = SHELL_REGION;
26✔
1324
   r->obj.name = ident_downcase(tree_ident(scope->where));
26✔
1325
   r->obj.path = ident_new(tb_get(path));
26✔
1326

1327
   hash_put(sh->namemap, r->obj.path, &(r->obj));
26✔
1328

1329
   for (int i = 0; i < scope->signals.count; i++) {
67✔
1330
      shell_signal_t *ss = (*sptr)++;
41✔
1331
      ss->signal = scope->signals.items[i];
41✔
1332
      ss->obj.kind = SHELL_SIGNAL;
41✔
1333
      ss->obj.name = ident_downcase(tree_ident(ss->signal->where));
41✔
1334
      ss->owner = sh;
41✔
1335

1336
      tb_istr(path, ss->obj.name);
41✔
1337
      ss->obj.path = ident_new(tb_get(path));
41✔
1338
      tb_trim(path, base);
41✔
1339

1340
      hash_put(sh->namemap, ss->obj.path, &(ss->obj));
41✔
1341
   }
1342

1343
   for (int i = 0; i < scope->aliases.count; i++) {
34✔
1344
      rt_alias_t *a = scope->aliases.items[i];
8✔
1345

1346
      shell_signal_t *ss = (*sptr)++;
8✔
1347
      ss->signal = a->signal;
8✔
1348
      ss->obj.kind = SHELL_SIGNAL;
8✔
1349
      ss->obj.name = ident_downcase(tree_ident(a->where));
8✔
1350
      ss->owner = sh;
8✔
1351

1352
      tb_istr(path, ss->obj.name);
8✔
1353
      ss->obj.path = ident_new(tb_get(path));
8✔
1354
      tb_trim(path, base);
8✔
1355

1356
      hash_put(sh->namemap, ss->obj.path, &(ss->obj));
8✔
1357
   }
1358

1359
   for (int i = 0; i < scope->children.count; i++) {
35✔
1360
      rt_scope_t *child = scope->children.items[i];
9✔
1361
      ident_t name = ident_downcase(tree_ident(child->where));
9✔
1362
      tb_istr(path, name);
9✔
1363
      tb_append(path, '/');
9✔
1364
      recurse_objects(sh, child, path, sptr, rptr);
9✔
1365
      tb_trim(path, base);
9✔
1366
   }
1367
}
26✔
1368

1369
void shell_reset(tcl_shell_t *sh, tree_t top)
17✔
1370
{
1371
   shell_clear_model(sh);
17✔
1372

1373
   jit_reset(sh->jit);
17✔
1374

1375
   sh->top = top;
17✔
1376

1377
   shell_create_model(sh);
17✔
1378

1379
   sh->nsignals = sh->nregions = 0;
17✔
1380
   count_objects(sh->root, &sh->nsignals, &sh->nregions);
17✔
1381

1382
   sh->signals = xcalloc_array(sh->nsignals, sizeof(shell_signal_t));
17✔
1383
   sh->regions = xcalloc_array(sh->nregions, sizeof(shell_region_t));
17✔
1384
   sh->namemap = hash_new(1 + sh->nsignals * 2);
17✔
1385

1386
   text_buf_t *path = tb_new();
17✔
1387
   shell_signal_t *sptr = sh->signals;
17✔
1388
   shell_region_t *rptr = sh->regions;
17✔
1389
   tb_cat(path, "/");
17✔
1390
   recurse_objects(sh, sh->root, path, &sptr, &rptr);
17✔
1391
   assert(sptr == sh->signals + sh->nsignals);
17✔
1392
   assert(rptr == sh->regions + sh->nregions);
17✔
1393
   tb_free(path);
17✔
1394

1395
   shell_update_now(sh);
17✔
1396

1397
   if (sh->handler.start_sim != NULL)
17✔
1398
      (*sh->handler.start_sim)(tree_ident(top), sh->handler.context);
2✔
1399
}
17✔
1400

UNCOV
1401
void shell_interact(tcl_shell_t *sh)
×
1402
{
UNCOV
1403
   shell_print_banner(sh);
×
1404

UNCOV
1405
   char *line;
×
UNCOV
1406
   while (!sh->quit && (line = (*sh->getline)(sh))) {
×
UNCOV
1407
      const char *result = NULL;
×
UNCOV
1408
      if (shell_eval(sh, line, &result) && *result != '\0')
×
UNCOV
1409
         color_printf("$+black$%s$$\n", result);
×
1410

UNCOV
1411
      free(line);
×
1412
   }
UNCOV
1413
}
×
1414

1415
bool shell_do(tcl_shell_t *sh, const char *file)
9✔
1416
{
1417
   const int code = Tcl_EvalFile(sh->interp, file);
9✔
1418

1419
   switch (code) {
9✔
1420
   case TCL_OK:
1421
      return true;
1422
   case TCL_ERROR:
3✔
1423
      {
1424
         const char *info = Tcl_GetVar(sh->interp, "::errorInfo", 0);
3✔
1425
         if (info != NULL && *info != '\n')
3✔
1426
            errorf("%s", info);
3✔
1427
         else {
UNCOV
1428
            const char *str = Tcl_GetStringResult(sh->interp);
×
UNCOV
1429
            if (str != NULL && *str != '\0')
×
UNCOV
1430
               errorf("%s", str);
×
1431
         }
1432

1433
         return false;
1434
      }
UNCOV
1435
   default:
×
UNCOV
1436
      warnf("Tcl_Eval returned unknown code %d", code);
×
UNCOV
1437
      return false;
×
1438
   }
1439
}
1440

1441
static int shell_redirect_close(ClientData cd, Tcl_Interp *interp)
26✔
1442
{
1443
   return EINVAL;
26✔
1444
}
1445

1446
static void shell_redirect_watch(ClientData cd, int mask)
26✔
1447
{
1448
}
26✔
1449

1450
static int shell_redirect_output(ClientData cd, const char *buf, int nchars,
5✔
1451
                                 int *error)
1452
{
1453
   tcl_shell_t *sh = untag_pointer(cd, tcl_shell_t);
5✔
1454
   switch (pointer_tag(cd)) {
5✔
1455
   case 0:
2✔
1456
      (*sh->handler.stdout_write)(buf, nchars, sh->handler.context);
2✔
1457
      break;
2✔
1458
   case 1:
2✔
1459
      (*sh->handler.stderr_write)(buf, nchars, sh->handler.context);
2✔
1460
      break;
2✔
1461
   case 2:
1✔
1462
      (*sh->handler.backchannel_write)(buf, nchars, sh->handler.context);
1✔
1463
      break;
1✔
UNCOV
1464
   default:
×
1465
      fatal_trace("invalid channel number %"PRIiPTR, pointer_tag(cd));
1466
   }
1467

1468
   return nchars;
5✔
1469
}
1470

1471
static const Tcl_ChannelType redirect_funcs = {
1472
   .typeName = "redirect",
1473
   .version = TCL_CHANNEL_VERSION_4,
1474
   .closeProc = shell_redirect_close,
1475
   .watchProc = shell_redirect_watch,
1476
   .outputProc = shell_redirect_output,
1477
};
1478

1479
void shell_set_handler(tcl_shell_t *sh, const shell_handler_t *h)
12✔
1480
{
1481
   sh->handler = *h;
12✔
1482

1483
   if (h->stdout_write != NULL) {
12✔
1484
      Tcl_Channel chan = Tcl_CreateChannel(&redirect_funcs, "redirect0",
30✔
1485
                                           tag_pointer(sh, 0), TCL_WRITABLE);
10✔
1486
      Tcl_SetChannelOption(NULL, chan, "-translation", "lf");
10✔
1487
      Tcl_SetChannelOption(NULL, chan, "-buffering", "line");
10✔
1488
      Tcl_SetChannelOption(NULL, chan, "-encoding", "utf-8");
10✔
1489

1490
      Tcl_RegisterChannel(sh->interp, chan);
10✔
1491
      Tcl_SetStdChannel(chan, TCL_STDOUT);
10✔
1492
   }
1493

1494
   if (h->stderr_write != NULL) {
12✔
1495
      Tcl_Channel chan = Tcl_CreateChannel(&redirect_funcs, "redirect1",
24✔
1496
                                           tag_pointer(sh, 1), TCL_WRITABLE);
8✔
1497
      Tcl_SetChannelOption(NULL, chan, "-translation", "lf");
8✔
1498
      Tcl_SetChannelOption(NULL, chan, "-buffering", "none");
8✔
1499
      Tcl_SetChannelOption(NULL, chan, "-encoding", "utf-8");
8✔
1500

1501
      Tcl_RegisterChannel(sh->interp, chan);
8✔
1502
      Tcl_SetStdChannel(chan, TCL_STDERR);
8✔
1503
   }
1504

1505
   if (h->backchannel_write != NULL) {
12✔
1506
      Tcl_Channel chan = Tcl_CreateChannel(&redirect_funcs, "backchannel",
24✔
1507
                                           tag_pointer(sh, 2), TCL_WRITABLE);
8✔
1508
      Tcl_SetChannelOption(NULL, chan, "-translation", "lf");
8✔
1509
      Tcl_SetChannelOption(NULL, chan, "-buffering", "full");
8✔
1510
      Tcl_SetChannelOption(NULL, chan, "-encoding", "utf-8");
8✔
1511

1512
      Tcl_RegisterChannel(sh->interp, chan);
8✔
1513
   }
1514
}
12✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc