-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmain.lua
More file actions
749 lines (648 loc) · 31.4 KB
/
main.lua
File metadata and controls
749 lines (648 loc) · 31.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
--- You can use `fork` to create multiple tasks.
--- Each task is a function with no arguments.
fork {
main_task = function()
print("hello from first `fork`")
--- Dump the wave file
sim.dump_wave()
--- Dump the wave file with a custom file name
-- sim.dump_wave("wave.vcd")
--- Create a `CallableHDL`(chdl) using `ProxyTableHandle`
local clock = dut.clock:chdl()
--- `CallableHDL` has a `__type` field to indicate its type
assert(clock.__type == "CallableHDL")
--- Create a `CallableHDL` using string literal
--- `clock` and `clock_1` are the same
--- The top-level of the verilua testbench is `tb_top` by default, where `clock` is the internal signal of the `tb_top` not our DUT
local clock_1 = ("tb_top.clock"):chdl()
--- `reset` is also the internal signal of `tb_top`
local reset = dut.reset:chdl()
--- Get the time precision and time unit from the global configuration(cfg)
--- time_precision is the smallest time unit that the simulator can represent (e.g., "ps", "fs", "ns")
--- time_unit is the base time unit for the simulation (e.g., "ns", "us", "s")
print("Time precision: ", cfg.time_precision)
print("Time unit: ", cfg.time_unit)
assert(cfg.time_precision == -12)
assert(cfg.time_unit == "ps")
--- Get the initial simulation time
--- By default, `sim.get_sim_time()` returns the simulation time in the simulator's native time unit
--- You can also specify a time unit as an argument: `sim.get_sim_time("ns")` returns time in nanoseconds
print("Initial simulation time: ", sim.get_sim_time())
assert(sim.get_sim_time() == 0)
assert(sim.get_sim_time("ns") == 0)
--- Set the value of the `reset` signal using `set` method
reset:set(1)
--- Waiting for the `posedge` event of the `clock` signal
--- `clock` signal is managed by the `tb_top` so you dont have to generate `clock` pulses by yourself
clock:posedge()
do
--- Wait for the first clock posedge and verify the simulation time
--- The testbench clock signal is generated by `always #10.0 clock = ~clock;` in tb_top.sv
--- This means the clock period is 20ns (10ns high, 10ns low), so each edge occurs every 10ns
--- Verilua's default timescale is 1ns/1ps (time unit: 1ns, time precision: 1ps)
local sim_time = sim.get_sim_time("ns")
print("Simulation time after first posedge clock: ", sim_time)
assert(sim_time == 10)
--- Verify time unit conversions
--- 10ns = 10000ps = 1e-8s
--- You can specify different time units to `sim.get_sim_time()`: "ps" (picoseconds), "ns" (nanoseconds), "us" (microseconds), "ms" (milliseconds), "s" (seconds)
assert(sim.get_sim_time("ps") == 10000)
assert(sim.get_sim_time("s") == 1e-08)
end
reset:set(0)
clock:posedge()
--- Verify the total simulation time is 30ns
---
--- 0ns 10ns 20ns 30ns 40ns
--- | | | | |
--- clock _____/^^^^^\_____/^^^^^\___
--- ↑ ↑ ↑
--- | | └─ posedge
--- | └─ negedge
--- └─ posedge
---
--- reset ______/^^^^^^^^^^^^^^^^\_____
--- 1 | | 0
---
assert(sim.get_sim_time("ns") == 30)
--- The first argument of `posedge` is the number of events to wait
clock:posedge(10)
--- The second argument of `posedge` is a callback, which will be called when the event happens
clock:posedge(5, function(count)
--- Expect to call this callback 5 times
print("repeat posedge clock 5 times, now is " .. count)
end)
--- Also support `negedge`
clock:negedge()
--- `cfg` is a pretty useful global variable, which saves some configuration information
assert(cfg.simulator == "verilator" or cfg.simulator == "vcs" or cfg.simulator == "iverilog" or
cfg.simulator == "xcelium")
print("Current simulator: ", cfg.simulator)
assert(cfg.top == "tb_top")
print("Current project directory: ", cfg.prj_dir)
--- `cfg` also merged the configuration from the configuration settings in `xmake.lua`: `set_values("cfg.user_cfg", "./cfg.lua")`
assert(cfg.var1 == 1)
assert(cfg.var2 == "hello")
--- `cfg:get_or_else` can be used to get the value of the configuration, if the configuration is not found, it will return the default value
assert(cfg:get_or_else("var1", 4) == 1)
assert(cfg:get_or_else("var3", "default") == "default")
--- `cycles` is also the internal signal of `tb_top` which is a 64-bit signal containing the number of clock cycles in posedge.
--- The value of the `cycles` signal will never be reset even if the `reset` signal is set.
local cycles = dut.cycles:get()
print("current cycle:", cycles)
--- To create a `CallaleHDL` pointing to a internal signal, you must at the `u_<dut_name>` level since the `u_<dut_name>` is auto instantiated by `testbench_gen`.
local internal_reg = dut.u_top.internal_reg:chdl()
--- Dump the current value of the `CallableHDL` object, the displayed value is in hex string format
internal_reg:dump() --- prints "[tb_top.u_top.internal_reg] => 0x11" in the console
--- `dump` also supports `ProxyTableHandle`
dut.u_top.internal_reg:dump()
--- Dump the current value of the `CallableHDL` object, the returned value is a string
--- `print(internal_reg:dump_str())` is equivalent to `internal_reg:dump()`
local str = internal_reg:dump_str()
--- `dump_str` also supports `ProxyTableHandle`
local str1 = dut.u_top.internal_reg:dump_str()
--- Read signal value from `CallableHDL` using `get` method
local internal_reg_v = internal_reg:get()
--- The return value of `get` method for a signal with width less than 32 is a lua number
assert(type(internal_reg_v) == "number")
--- Read signal value from `CallableHDL` using `get_hex_str` method
local internal_reg_v_hex_str = internal_reg:get_hex_str()
--- The return value of `get_hex_str` method for any signal(width of the signal don't matter) is a hex string, e.g. "123", "deadbeef"
assert(type(internal_reg_v_hex_str) == "string")
--- `get_bin_str` and `get_dec_str` are also supported which return a binary string and a decimal string separately
local str2 = internal_reg:get_bin_str()
local str3 = internal_reg:get_dec_str()
--- Cache the `ProxyTableHandle` object for later use
local u_top = dut.u_top
--- `u_top.internal_reg:chdl()` is equivalent to `dut.u_top.internal_reg:chdl()`
--- This is pretty useful when you have a deep hierarchy of signal needed to be accessed using `dut`
--- <chdl>:get() and <chdl>:set() has different behavior in different signal width
--- 1. width <= 32
do
local reg32 = u_top.reg32:chdl()
--- The return value of `get` method for a signal with width less than or equal to 32 is a lua number
local reg32_v = reg32:get()
assert(type(reg32_v) == "number")
assert(reg32_v == 32)
--- `get_hex_str` workds on any signal width
assert(reg32:get_hex_str() == "00000020")
--- `<chdl>:expect(<lua number value>/<uint64_t>)` can be used to check the value of the signal,
--- if the value is not equal to the expected value, an error will be thrown
reg32:expect(32)
--- `<chdl>:is(<lua number value>/<uint64_t>)` will return a boolean value indicating whether
--- the value of the signal is equal to the expected value
assert(reg32:is(32))
--- `set` method accepts a lua number when the width of the signal is less than or equal to 32
--- Notice: Typically you should not `set` a internal signal, `set` should be used on the top level IO signals.
reg32:set(123)
end
--- 2. width > 32 and width <= 64
do
local reg64 = u_top.reg64:chdl()
--- The return value of `get` method for a signal with width greater than 32 and less than or equal to 64 is a `uint64_t`
--- which is a `cdata` object in lua
local reg64_v = reg64:get()
assert(type(reg64_v) == "cdata")
assert(reg64_v == 0xFFFFFFFFFFFFFFFFULL)
--- `get_hex_str` workds on any signal width
assert(reg64:get_hex_str() == "ffffffffffffffff")
--- `get64` method always returns a `uint64_t`
local reg64_v2 = reg64:get64()
assert(type(reg64_v2) == "cdata")
assert(reg64_v2 == 0xFFFFFFFFFFFFFFFFULL)
assert(reg64_v2 == reg64_v)
assert(ffi.istype(ffi.new("uint64_t"), reg64_v2))
reg64:expect(0xFFFFFFFFFFFFFFFFULL)
assert(reg64:is(0xFFFFFFFFFFFFFFFFULL))
--- `get` method now accepts a `force_multi_beat` argument which return a `MultiBeatData` ffi object
--- A breif description of the `MultiBeatData` object:
--- - `MultiBeatData` is a `uint32_t[]` ffi object in lua
--- - `MultiBeatData`[0] is the beat number of the signal, each beat represents 32 bits of signal value
--- e.g. For signal with width 64, `MultiBeatData`[0] is 2, for signal with width 128, `MultiBeatData`[0] is 4, etc.
--- - In Lua, table index starts from 1, so `MultiBeatData`[1] is the first beat of the signal value,
--- and `MultiBeatData`[2] is the second beat of the signal value, etc.
--- - So you can always consider ``MultiBeatData`` as lua table of signal value, each element of the table is a 32bit lua number
local reg64_v_tbl = reg64:get(true)
assert(type(reg64_v_tbl) == "cdata")
--- 0xFFFFFFFFFFFFFFFFULL can be represented in `MultiBeatData` => { 2, 0xFFFFFFFF, 0xFFFFFFFF } with index starts from 0
assert(reg64_v_tbl[0] == 2)
assert(reg64_v_tbl[1] == 0xFFFFFFFF)
assert(reg64_v_tbl[2] == 0xFFFFFFFF)
assert(ffi.istype(ffi.new("uint32_t[?]", 3) --[[@as ffi.ct*]], reg64_v_tbl))
--- `set` method needs a lua table where each element is a 32bit lua number
--- Because the width of the signal is 64, so we need 2 elements in the table
reg64:set({ 1, 2 }) -- <LSB .. MSB>
--- Value will be updated after the next simulation cycle
clock:posedge()
reg64_v = reg64:get()
assert(reg64_v == 0x0000000200000001ULL)
--- `set` has another overload which accepts a lua number/uint64_t and a `force_single_beat` argument
reg64:set(1234, true)
clock:posedge()
reg64:expect(1234)
reg64:set(0xFFFF0000FFFF0000ULL, true)
clock:posedge()
reg64:expect(0xFFFF0000FFFF0000ULL)
end
--- 3. width > 64
do
local reg128 = u_top.reg128:chdl()
--- The return value of `get` method for a signal with width greater than 64 is a `MultiBeatData` ffi object
local reg128_v = reg128:get()
assert(type(reg128_v) == "cdata")
assert(reg128_v[0] == 4)
assert(reg128_v[1] == 128)
assert(reg128_v[2] == 0)
assert(reg128_v[3] == 0)
--- `get64` method always returns a `uint64_t`
local reg128_v2 = reg128:get64()
assert(reg128_v2 == 0x0000000000000080ULL)
assert(reg128_v2 == 128ULL)
assert(reg128_v2 == 128)
--- Like `reg64`, `set` method accepts a lua table where each element is a 32bit lua number
reg128:set({ 1, 2, 3, 4 })
clock:posedge()
reg128_v = reg128:get()
assert(reg128_v[0] == 4)
assert(reg128_v[1] == 1)
assert(reg128_v[2] == 2)
assert(reg128_v[3] == 3)
assert(reg128_v[4] == 4)
--- `set` with `force_single_beat` argument accepts a lua number/uint64_t, where the uncovered bits will be set to 0
reg128:set(0xFFFF0000FFFF0000ULL, true)
clock:posedge()
reg128_v = reg128:get()
assert(reg128_v[0] == 4)
assert(reg128_v[1] == 0xFFFF0000)
assert(reg128_v[2] == 0xFFFF0000)
assert(reg128_v[3] == 0x0000)
assert(reg128_v[4] == 0x0000)
--- `get_hex_str` workds on any signal width
assert(reg128:get_hex_str() == "0000000000000000ffff0000ffff0000")
end
--- There are few ways to get bitfield from a signal value
--- 1. using methods in `LuaUtils`
do
local utils = require "verilua.LuaUtils"
local v = tonumber(0b00001010) --[[@as integer]]
assert(utils.bitfield32(1, 1, v) == 1)
assert(utils.bitfield32(1, 3, v) == 5)
local v2 = 0xFFFFFFFF00000000ULL
assert(utils.bitfield64(32, 63, v2) == 0xFFFFFFFFULL)
end
--- 2. using `BitVec`
do
local BitVec = require "verilua.utils.BitVec"
local v = 0xFF01
local bv = BitVec(v, 16)
assert(bv:get_bitfield(0, 0) == 1)
assert(bv:get_bitfield(8, 15) == 0xff)
local reg128 = dut.u_top.reg128:chdl()
reg128:set(0x123, true)
clock:posedge()
--- `<chdl>:get_bitvec()` will return a `BitVec` object
local bv128 = reg128:get_bitvec()
assert(bv128:get_bitfield(0, 3) == 3)
assert(reg128:get_bitvec():get_bitfield(0, 3) == 3)
end
--- If you dont care about the performance, a auto-type-based value assignment is also supported
do
--- `<chdl>.value` is a special field for auto-type-based value assignment
--- Any value can be assigned to `<chdl>.value` and the value will be assigned to the signal correctly without concerning the signal width
--- This approach is very handy when you dont care about the performance
local reg128 = dut.u_top.reg128:chdl()
reg128.value = 1
clock:posedge()
reg128:expect_hex_str("1")
reg128.value = "0x123"
clock:posedge()
reg128:expect_hex_str("123")
reg128.value = 0xFFFFFFFFFFFFFFFFULL
clock:posedge()
reg128:expect_hex_str("ffffffffffffffff")
reg128.value = { 0x12345, 0, 0, 0 }
clock:posedge()
reg128:expect_hex_str("12345")
end
--- Also there has a auto-type-based comparison support
do
local reg128 = dut.u_top.reg128:chdl()
reg128.value = 0x12345
clock:posedge()
--- A global function `v` is provided for auto-type-based value assignment
assert(reg128 == v(0x12345))
assert(reg128 ~= v(0x12346))
assert(reg128 == v(0x12345ULL))
assert(reg128 == v({ 0x12345, 0, 0, 0 }))
assert(reg128 ~= v({ 0x12346, 0, 0, 0 }))
assert(reg128 == v("0x12345"))
assert(reg128 ~= v("0x12346"))
end
--- `<chdl>:set_shuffled()` allows you to randomly assign values to the signal based on the signal width
do
--- For example, for a 4-bit signal, it will randomly assign a value from `0x0` to `0xf`
local reg4 = dut.u_top.reg4:chdl()
local values = {}
for _ = 1, 100 do
reg4:set_shuffled()
clock:posedge()
local v = reg4:get()
values[v] = true
end
--- metamethod `#` can be used to get the bitwidth of a signal
local bitwidth_of_reg4 = #reg4
assert(bitwidth_of_reg4 == 4)
--- Or you can use `<chdl>:get_width()`
assert(reg4:get_width() == 4)
--- `table.nkeys` can be used to get the number of keys in a lua table
assert(table.nkeys(values) == math.pow(2, bitwidth_of_reg4))
table.clear(values)
assert(table.nkeys(values) == 0)
--- `<chdl>:shuffled_range_u32(<...>)`/`<chdl>:shuffled_range_u64(<...>)`/`<chdl>:shuffled_range_hex_str(<...>)` allows you
--- to assign values to the signal based on the specified range.
--- The suffix `_u32`/`_u64`/`_hex_str` means the range is for `number`/`uint64_t`/`hex_str` type values.
--- And the only argument is a lua table that contains the range values.
reg4:shuffled_range_u32({ 1, 3 }) --- Only need to specify the range values one time
for _ = 1, 100 do
reg4:set_shuffled()
clock:posedge()
local v = reg4:get()
values[v] = true
end
assert(table.nkeys(values) == 2)
assert(values[1] == true)
assert(values[3] == true)
table.clear(values)
assert(table.nkeys(values) == 0)
reg4:shuffled_range_hex_str({ "2", "0", "1" })
for _ = 1, 100 do
reg4:set_shuffled()
clock:posedge()
local v = reg4:get()
values[v] = true
end
assert(table.nkeys(values) == 3)
assert(values[2] == true)
assert(values[0] == true)
assert(values[1] == true)
end
local u_sub = dut.u_top.u_sub
--- `<ProxyTableHandle>:tostring()` will return the full path(hierarchical path) of the signal
assert(u_sub:tostring() == "tb_top.u_top.u_sub")
assert(dut:tostring() == "tb_top")
--- Also support `tostring`
assert(tostring(u_sub) == "tb_top.u_top.u_sub")
assert(tostring(dut) == "tb_top")
--- Create a bundle of signals by using `Bundle`(bdl) data structure
--- `bdl` will contains the following signals:
--- - tb_top.u_top.u_sub.some_prefix_valid
--- - tb_top.u_top.u_sub.some_prefix_ready
--- - tb_top.u_top.u_sub.some_prefix_bits_data_0
--- - tb_top.u_top.u_sub.some_prefix_bits_data_1
--- - tb_top.u_top.u_sub.some_prefix_bits_data_2
--- The signal matching rule is:
--- 1. is_decoupled = true
--- <hier>.<prefix>_bits_<signal_name>
--- 2. is_decoupled = false
--- <hier>.<prefix>_<signal_name>
local bdl = ([[
| valid
| ready
| data_0
| data_1
| data_2
]]):bdl {
hier = u_sub:tostring(),
prefix = "some_prefix_",
is_decoupled = true, -- default is true, which means the bundle is decoupled(valid/ready handshake), a `valid` signal is required
}
assert(bdl.__type == "Bundle")
assert(bdl.valid.__type == "CallableHDL")
assert(bdl.bits.data_0.__type == "CallableHDL")
assert(bdl.bits.data_1.__type == "CallableHDL")
assert(bdl.bits.data_2.__type == "CallableHDL")
local bdl_decl_str = [[
| valid
| ready
| bits_data_0
| bits_data_1
| bits_data_2
]]
local bdl2 = bdl_decl_str:bdl {
hier = u_sub:tostring(),
prefix = "some_prefix_",
is_decoupled = false,
}
assert(bdl2.__type == "Bundle")
assert(bdl2.valid.__type == "CallableHDL")
assert(bdl2.bits_data_0.__type == "CallableHDL")
assert(bdl2.bits_data_1.__type == "CallableHDL")
assert(bdl2.bits_data_2.__type == "CallableHDL")
--- Create an `AliasBundle`(abdl)
--- Signals in the declaration string follow by `=>` are alias names
local abdl = ([[
| valid => vld
| ready
| bits_data_0 => d0
| bits_data_1 => d1
| bits_data_2
]]):abdl {
hier = u_sub:tostring(),
prefix = "some_prefix_"
}
assert(abdl.__type == "AliasBundle")
assert(abdl.vld.__type == "CallableHDL")
assert(abdl.ready.__type == "CallableHDL")
assert(abdl.d0.__type == "CallableHDL")
assert(abdl.d1.__type == "CallableHDL")
assert(abdl.bits_data_2.__type == "CallableHDL")
--- `<ProxyTableHandle>:with_prefix(<prefix_name>)` allows you to create a new `ProxyTableHandle` object with a prefix string
--- The prefix string will be appended to the hierarchical path of the signal
local u_sub_with_prefix = dut.u_top.u_sub:with_prefix("some_prefix_")
assert(u_sub_with_prefix.valid:tostring() == "tb_top.u_top.u_sub.some_prefix_valid")
assert(u_sub_with_prefix.ready:tostring() == "tb_top.u_top.u_sub.some_prefix_ready")
assert(u_sub_with_prefix.bits_data_0:tostring() == "tb_top.u_top.u_sub.some_prefix_bits_data_0")
assert(u_sub_with_prefix.bits_data_1:tostring() == "tb_top.u_top.u_sub.some_prefix_bits_data_1")
assert(u_sub_with_prefix.bits_data_2:tostring() == "tb_top.u_top.u_sub.some_prefix_bits_data_2")
--- If you don't want to specify the signal string, you can use `<ProxyTableHandle>:auto_bundle(...)` to create a bundle of signals
--- which matches the provided rules
local auto_bdl = u_sub:auto_bundle {
prefix = "some_prefix_"
}
assert(auto_bdl.__type == "Bundle")
assert(auto_bdl.valid.__type == "CallableHDL")
assert(auto_bdl.ready.__type == "CallableHDL")
assert(auto_bdl.bits_data_0.__type == "CallableHDL")
assert(auto_bdl.bits_data_1.__type == "CallableHDL")
assert(auto_bdl.bits_data_2.__type == "CallableHDL")
local auto_bdl2 = u_sub:auto_bundle {
startswith = "some_prefix",
}
assert(auto_bdl2.some_prefix_valid.__type == "CallableHDL")
assert(auto_bdl2.some_prefix_ready.__type == "CallableHDL")
assert(auto_bdl2.some_prefix_bits_data_0.__type == "CallableHDL")
assert(auto_bdl2.some_prefix_bits_data_1.__type == "CallableHDL")
assert(auto_bdl2.some_prefix_bits_data_2.__type == "CallableHDL")
--- Combine with other rules
local auto_bdl2 = u_sub:auto_bundle {
startswith = "some_prefix",
wildmatch = "*_data_*"
}
assert(auto_bdl2.some_prefix_valid == nil)
assert(auto_bdl2.some_prefix_bits_data_0.__type == "CallableHDL")
assert(auto_bdl2.some_prefix_bits_data_1.__type == "CallableHDL")
assert(auto_bdl2.some_prefix_bits_data_2.__type == "CallableHDL")
--- Sometimes you may want to detect if a specific signal exists, you can use the `vpiml` module
local vpiml = require "vpiml"
--- `vpiml_handle_by_name_safe` will return -1 if the signal doesn't exist
local ret = vpiml.vpiml_handle_by_name_safe(dut.u_top.some_non_existent_signal:tostring())
assert(ret == -1)
ret = vpiml.vpiml_handle_by_name("tb_top.u_top.u_sub.some_prefix_valid")
assert(ret ~= -1)
--- You can use the following methods to iterate some regular signals because `ProxyHandleTable` is also a lua table
for i = 0, 2 do
local chdl = dut.u_top.u_sub["some_prefix_bits_data_" .. i]:chdl()
assert(chdl.__type == "CallableHDL")
end
--- `verilator` does not support force/release operation for now
if cfg.simulator ~= "verilator" then
local counter = dut.u_top.internal_reg:chdl()
dut.reset:set(1)
dut.clock:posedge()
dut.reset:set(0)
dut.clock:posedge()
counter:expect(0)
dut.clock:posedge()
counter:expect(1)
dut.clock:posedge(2)
counter:expect(3)
--- `<chdl>:set_force(<...>)` is similar to `<chdl>:set(...)` but with force operation, the assigned value will be kept until we call `<chdl>:set_release()`
counter:set_force(1)
dut.clock:posedge()
dut.clock:posedge(100, function(c)
--- Keep the value of `counter` as 1
counter:expect(1)
end)
counter:set_release()
dut.clock:posedge()
--- After release, the value of `counter` will be updated as expected
counter:expect(2)
end
local a = 123
--- `fork` can be used anywhere
fork {
function()
print("hello from inner `fork`")
clock:posedge()
a = a + 1
end
}
--- A `fork` created task is running in the background
assert(a == 123)
clock:posedge()
if cfg.simulator == "xcelium" then
-- Xcelium schedule posedge callback in random order, when the callback is called,
-- the value of `a` may not be updated yet, so we need to wait for one more cycle.
clock:posedge()
assert(a == 124)
else
assert(a == 124)
end
--- To wait for a spcific task to finish, you can use `jfork` combined with `join`
local e = jfork {
--- Notice: `jfork` only accept a single function as its argument
function()
clock:posedge()
a = a + 1
end
}
join(e)
assert(a == 125)
--- `join` can wait for multiple tasks to finish
local e1 = jfork {
function()
clock:posedge()
a = a + 1
end
}
local e2 = jfork {
function()
clock:posedge(20)
a = a + 1
end
}
join({ e1, e2 })
assert(a == 127)
--- A running task can be removed even if it is not finished
--- To do so, you need to get the task id from `jfork` and pass it to `scheduler:remove_task(<task_id>)` when you want to remove it
local e3, task_id = jfork { --- The second return value is the task id
function()
clock:posedge(100, function(c)
a = a + 1
end)
end
}
clock:posedge(10)
scheduler:remove_task(task_id)
assert(a == 138)
clock:posedge(10)
assert(a == 138) --- `a` is not changed since the task is removed
--- EventHandle allows tasks to wait for and send events, providing a simple
--- synchronization mechanism between tasks.
do
local ev = ("demo_event"):ehdl() -- Create a named event handle
local s_cycles = 0
local w_cycles = 0
fork {
event_sender = function()
print("[Event] Sender: waiting 5 clock cycles before sending...")
clock:posedge(5)
ev:send()
s_cycles = dut.cycles:get()
print("[Event] Sender: event sent.")
end,
event_waiter = function()
print("[Event] Waiter: waiting for event...")
ev:wait()
w_cycles = dut.cycles:get()
print("[Event] Waiter: event received, continuing. cycles: " .. tostring(w_cycles))
assert(w_cycles == s_cycles) -- Verify that the waiter resumes immediately after the sender sends the event
end
}
--- Allow enough time for the sender/waiter to complete
clock:posedge(10)
end
--- Verilua provides functions to wait for a specified amount of simulation time,
--- independent of clock edges. This is useful for generating delays that are
--- not tied to a clock.
do
local t0 = sim.get_sim_time("ns")
print("[Time] Waiting 50 ns..., cycles: " .. dut.cycles:get())
await_time_ns(50)
local t1 = sim.get_sim_time("ns")
assert(t1 - t0 == 50)
print(string.format("[Time] Waited %d ns, cycles: %d", t1 - t0, dut.cycles:get()))
-- Also demonstrate other units:
await_time_ps(100) -- 100 picoseconds
print(string.format("[Time] Waited 100 ps, cycles: %d", dut.cycles:get()))
await_time_us(1) -- 1 microsecond
print(string.format("[Time] Waited 1 us, cycles: %d", dut.cycles:get()))
end
--- The `scheduler` provides two methods to obtain information about the
--- currently executing task: `get_curr_task_id()` returns the task ID,
--- and `get_curr_task_name()` returns the task name.
do
assert(
scheduler:get_curr_task_id() ~= scheduler.NULL_TASK_ID,
"get_curr_task_id() should return a valid task ID inside a task"
)
assert(
scheduler:get_curr_task_name() == "main_task",
"get_curr_task_name() should return 'main_task' for the main task"
)
fork {
named_task = function()
local id = scheduler:get_curr_task_id()
local name = scheduler:get_curr_task_name()
assert(name == "named_task")
print(string.format("[TaskInfo] Current task ID: %d, name: %s", id, name))
end
}
dut.clock:posedge(5) -- allow the task to run
end
-- TODO: Fake CHDL
--- Finish the simulation, you must call this function manually otherwise the simulation will be stuck and never finish
sim.finish()
end,
--- You can also create tasks with name.
task_with_name = function()
--- Task will be removed from scheduler if it runs to the end.
end,
--- Other tasks...
}
--- `fork` can be used for multiple times.
fork {
function()
print("hello from another `fork`")
end
}
--- Demonstrate that outside of a task context:
--- - `scheduler:get_curr_task_id()` returns `scheduler.NULL_TASK_ID` (0).
--- - `scheduler:get_curr_task_name()` raises an error because there is no running task.
do
local tid = scheduler:get_curr_task_id()
assert(tid == scheduler.NULL_TASK_ID, "get_curr_task_id() should return NULL_TASK_ID outside a task")
local success, err = pcall(function()
local _name = scheduler:get_curr_task_name()
end)
assert(not success, "get_curr_task_name() should fail outside a task")
---@cast err string
assert(
err:contains("you are not in a task context!"),
"Error message should indicate missing task context"
)
end
--- You can use `initial` to create tasks which will be executed at the start of simulation.
initial {
function()
print("hello from initial task")
end,
--- `initial` can include multiple tasks.
function()
local a = 1
print(a + 1)
end
}
--- `initial` can be used for multiple times.
initial {
function()
print("hello from another initial task")
end
}
--- You can use `final` to create tasks which will be executed at the end of simulation.
--- `final` can be used for multiple times and can include multiple tasks in one `final` block just like `initial`.
final {
function()
print("hello from final task")
end
}