-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathREADME.html
More file actions
4228 lines (3989 loc) · 209 KB
/
README.html
File metadata and controls
4228 lines (3989 loc) · 209 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
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<h1>About HDLRuby</h1>
<p>HDLRuby is a library for describing and simulating digital electronic
systems.</p>
<p><strong>Note</strong>:</p>
<p>If you want to learn how to describe a circuit with HDLRuby, please jump to the following section:</p>
<ul>
<li><p><a href="#hdlruby-programming-guide">HDLRuby Programming Guide</a></p>
<ul>
<li><p><a href="#introduction">Introduction</a></p>
</li>
<li><p><a href="#how-hdlruby-works">How HDLRuby Works</a></p>
</li>
<li><p><a href="#naming-rules">Naming rules</a></p>
</li>
<li><p><a href="#systems-and-signals">Systems and Signals</a></p>
</li>
<li><p><a href="#events">Events</a></p>
</li>
<li><p><a href="#statements">Statements</a></p>
</li>
<li><p><a href="#types">Types</a></p>
</li>
<li><p><a href="#expressions">Expressions</a></p>
</li>
<li><p><a href="#functions">Functions</a></p>
</li>
<li><p><a href="#software-code">Software code</a></p>
</li>
<li><p><a href="#time">Time</a></p>
</li>
<li><p><a href="#high-level-programming-features">High-Level Programming Features</a></p>
</li>
<li><p><a href="#extending-hdlruby">Extending HDLRuby</a></p>
</li>
</ul>
</li>
</ul>
<p>Many of HDLRuby's features are available through its standard libraries.
We strongly recommend consulting the corresponding section:</p>
<ul>
<li><p><a href="#standard-libraries">Standard Libraries</a></p>
<ul>
<li><p><a href="#clocks">Clocks</a></p>
</li>
<li><p><a href="#decoder">Decoder</a></p>
</li>
<li><p><a href="#fsm">FSM</a></p>
</li>
<li><p><a href="#parallel-enumerators">Parallel Enumerators</a></p>
</li>
<li><p><a href="#sequencer-software-like-hardware-coding">Sequencer (Software-like Hardware Coding)</a></p>
</li>
<li><p><a href="#fixed-point">Fixed-Point</a></p>
</li>
</ul>
</li>
</ul>
<p>Samples are also available: <a href="#sample-hdlruby-descriptions">Sample HDLRuby descriptions</a></p>
<p>Finally, HDLRuby can also process Verilog HDL files: <a href="#converting-verilog-hdl-to-hdlruby">Converting Verilog HDL to HDLRuby</a>.</p>
<p>If you are new to HDLRuby, we recommend starting with the following
tutorial even if you have a hardware background:</p>
<ul>
<li><a href="https://github.com/civol/HDLRuby/blob/master/tuto/tutorial_sw.md">HDLRuby Tutorial for Software People</a> [md]</li>
</ul>
<p>If you would prefer an HTML version, you can generate it by running the
following command. This will create a <code>tuto</code> folder containing all the
necessary files. Then, simply open <code>tuto/tutorial_sw.html</code>:</p>
<pre><code>hdrcc --get-tuto
</code></pre>
<p><strong>What's New</strong></p>
<p>For HDKRuby version 3.9.2:</p>
<ul>
<li>Added the <code>hbreak</code> command for exiting parallel enumerator loops.</li>
</ul>
<p>For HDLRuby version 3.9.0/3.9.1:</p>
<ul>
<li><p>Added the parallel enumerators to the software sequencers.</p>
</li>
<li><p>Added experimental TensorFlow code generation from the software sequencers.</p>
</li>
<li><p>Added the possibility to declare vectors of instances.</p>
</li>
<li><p>Added the possibility to fix the data type for the accumulation with the hinject and sinject enumerators.</p>
</li>
<li><p>Fixed various bugs.</p>
</li>
<li><p>Made an overhaul of the documentation.</p>
</li>
</ul>
<p>For HDLRuby version 3.8.3:</p>
<ul>
<li><p>Fixed various bugs including some in interactive mode.</p>
</li>
<li><p>Updated the documentation:</p>
<ul>
<li><p>Rewrote the beginning of the <a href="#hdlruby-programming-guide">HDLRuby Programming Guide</a>.</p>
</li>
<li><p>Updated the documentation for interactive mode.</p>
</li>
<li><p>Updated the <a href="#high-level-programming-features">High-Level Programming Features</a> chapter.</p>
</li>
</ul>
</li>
</ul>
<p>For HDLRuby version 3.8.0:</p>
<ul>
<li><p>Added parallel enumerators (e.g., heach), allowing Ruby-like iteration for describing parallel hardware.</p>
</li>
<li><p>Added genererive programming using standard HDLRuby constructs (e.g., hif) -- there is no need to use Ruby code directly any more.</p>
</li>
<li><p>Fixed compile bugs for windows.</p>
</li>
</ul>
<p>For HDLRuby version 3.7.9:</p>
<ul>
<li><p>Added Python code generation from software sequencers.</p>
</li>
<li><p>Added <a href="#parallel-enumerators">Parallel Enumerators</a>.</p>
</li>
</ul>
<p>For HDLRuby versions 3.7.7/3.7.8:</p>
<ul>
<li>Various fixes related to software sequencers.</li>
</ul>
<p>For HDLRuby version 3.7.6:</p>
<ul>
<li><p>Added initial value support for signals in software sequencers.</p>
</li>
<li><p>Fixed <code>hprint</code> in software sequencers.</p>
</li>
</ul>
<p>For HDLRuby versions 3.7.4/3.7.5:</p>
<ul>
<li>Various bug fixes.</li>
</ul>
<p>For HDLRuby version 3.7.3:</p>
<ul>
<li>Enabled use of software sequencers within HDLRuby's <code>program</code> construct, including use of program ports as if they were input or output signals.</li>
</ul>
<p>For HDLRuby version 3.7.2:</p>
<ul>
<li><p>Added the <code>text</code> command for software sequencers.</p>
</li>
<li><p>Added the <code>value_text</code> method to software sequencers signal, generating Ruby/C code with correct typing.</p>
</li>
<li><p>Added the <code>alive?</code> and <code>reset!</code> commands for HDLRuby sequencers.</p>
</li>
<li><p>Added the <code>require_ruby</code> method for loading Ruby (i.e., non-HDLRuby) libraries.</p>
</li>
</ul>
<p>For HDLRuby version 3.7.x:</p>
<ul>
<li>Added the possibility to run <a href="#sequencers-as-software-code">Sequencers in Software</a>. (WIP)
This enables significantly faster simulation and allows reusing the same code for both hardware and software design.</li>
</ul>
<p>For HDLRuby version 3.6.x:</p>
<ul>
<li><p>Added a new GUI board element allowing assignment of expressions to signals during simulation.</p>
</li>
<li><p>Added a new slider element for the GUI board (from 3.6.1).</p>
</li>
</ul>
<p>For HDLRuby version 3.5.0:</p>
<ul>
<li><p>Added direct support for Verilog HDL files as input to 'hdrcc'.</p>
</li>
<li><p>Added the ability to generate a graphical representation of the RTL code in SVG format using the '--svg' option for 'hdrcc'.</p>
</li>
</ul>
<p>For HDLRuby version 3.4.0:</p>
<ul>
<li><p>Improved synchronization between the browser-based graphical interface and the HDLRuby simulator.</p>
</li>
<li><p>Added a Verilog HDL parsing library for Ruby (to be released separately once stabilized).</p>
</li>
<li><p>Added a library for generating HDLRuby code from a Verilog HDL AST (produced by the parsing library).</p>
</li>
<li><p>Added <a href="#converting-verilog-hdl-to-hdlruby">v2hdr</a>, a standalone tool for converting Verilog HDL files to HDLRuby (experimental).</p>
</li>
<li><p>Added a HDLRuby command for <a href="#converting-verilog-hdl-to-hdlruby">loading a Verilog HDL file from a HDLRuby description</a>.</p>
</li>
</ul>
<p>For HDLRuby version 3.3.0:</p>
<ul>
<li><p>Redesigned the description of software components using the program construct.
The <code>Code</code> objects are now deprecated.</p>
</li>
<li><p>Added HW/SW co-simulation capability for Ruby and compiled C-compatible
software programs.</p>
</li>
<li><p>Added a browser-based graphical interface simulating a development board that interacts with the HDLRuby simulator.</p>
</li>
<li><p>Updated the documentation and tutorial accordingly, and fixed several typos.</p>
</li>
</ul>
<p>For HDLRuby version 3.2.0:</p>
<ul>
<li><p>Added components for declaring BRAM and BRAM-based stacks to enable efficient memory allocation in FPGAs.</p>
</li>
<li><p>Performed internal code overhaul in preparation for version 4.0.0.</p>
</li>
<li><p>Multiple bug fixes.</p>
</li>
</ul>
<p>For HDLRuby version 3.1.0:</p>
<ul>
<li><p>Added <a href="#sequencer-specific-functions">functions for sequencers</a>, including support for recursion.</p>
</li>
<li><p>Replaced the <code>function</code> keyword with <code>hdef</code> for consistency with sequencer functions (<code>sdef</code>).</p>
</li>
<li><p>Added the <code>steps</code> command for waiting multiple steps in a sequencer.</p>
</li>
<li><p>Improved Verilog HDL code generation to better preserve original signal names.</p>
</li>
<li><p>Several bug fixes for the sequencers.</p>
</li>
</ul>
<p>For HDLRuby version 3.0.0:</p>
<ul>
<li><p>Intruduced this changelog section.</p>
</li>
<li><p>Added <a href="#sequencer-software-like-hardware-coding">Sequencers</a> for software-like hardware design.</p>
</li>
<li><p>Added a <a href="tuto/tutorial_sw.md">tutorial</a> for software developers.</p>
</li>
<li><p>The stable <a href="#standard-libraries">Standard Libraries</a> are now loaded by
default.</p>
</li>
</ul>
<p><strong>Install</strong>:</p>
<p>The recommended method of installation is via RubyGems:</p>
<pre><code>gem install HDLRuby
</code></pre>
<p>Developers who wish to contribute to HDLRuby can install it from source using GitHub:</p>
<pre><code>git clone https://github.com/civol/HDLRuby.git
</code></pre>
<p><strong>Warning</strong>:</p>
<ul>
<li><p>HDLRuby is still under active development, and the API may change before a stable release.</p>
</li>
<li><p>It is highly recommended that users have a basic understanding of both the Ruby programming language and hardware description languages before using HDLRuby.</p>
</li>
</ul>
<h1>Compiling HDLRuby Descriptions</h1>
<h2>Using the HDLRuby Compiler</h2>
<p>'hdrcc' is the HDLRuby compiler. It takes an HDLRuby file as input, checks it, and can generate one of several outputs: Verilog HDL, VHDL, or a YAML low-level hardware component description. It can also simulate the input design.</p>
<p><strong>Usage</strong>:</p>
<pre><code>hdrcc [options] <input file> <output/working directory>
</code></pre>
<p>Where:</p>
<ul>
<li><p><code>options</code> is a list of options (see below)</p>
</li>
<li><p><code><input file></code> is the input HDLRuby file to compile (mandatory)</p>
</li>
<li><p><code><output/working directory></code> is the directory where output and temporary files will be stored</p>
</li>
</ul>
<p>| Options | |
|:------------------|:-----------------------------------------------------|
| <code>-I, --interactive</code> | Run in interactive mode |
| <code>-y, --yaml</code> | Output in YAML format |
| <code>-v, --verilog</code> | Output in Verilog HDL format |
| <code>-V, --vhdl</code> | Output in VHDL format |
| <code>-s, --syntax</code> | Output the Ruby syntax tree |
| <code>-C, --clang</code> | Output the C code of the standalone simulator |
| <code>-S, --sim</code> | Perform the simulation with the default engine |
| <code>--csim</code> | Perform the simulation with the standalone engine |
| <code>--rsim</code> | Perform the simulation with the Ruby engine |
| <code>--rcsim</code> | Perform the simulation with the Hybrid engine |
| <code>--vcd</code> | Make the simulator generate a VCD (waveform) file |
| <code>--svg</code> | Output a graphical representation of the RTL (SVG format) |
| <code>-d, --directory</code> | Specify the base directory for loading the HDLRuby files |
| <code>-D, --debug</code> | Set the HDLRuby debug mode |
| <code>-t, --top system</code>| Specify the top system describing the circuit to compile |
| <code>-p, --param x,y,z</code> | Specify the generic parameters |
| <code>--get-samples</code> | Copy the <code>hdr_samples</code> directory to the current directory, then exit |
| <code>--version</code> | Show the version number, then exit |
| <code>-h, --help</code> | Show the help message |</p>
<p><strong>Notes</strong>:</p>
<ul>
<li><p>If no top system is specified, it will be automatically inferred from the input file.</p>
</li>
<li><p>If no options are provided, the compiler will only check the input file for correctness.</p>
</li>
<li><p>If you're new to HDLRuby, or want to see working examples of new features, we strongly recommend downloading the sample files:</p>
<pre><code class="language-bash">hdrcc --get-samples
</code></pre>
<p>This will create a <code>hdr_samples</code> subdirectory in your current folder, containing various HDLRuby example files. For more details, see the <a href="#sample-hdlruby-descriptions">samples</a>.</p>
</li>
</ul>
<p><strong>Examples</strong>:</p>
<ul>
<li>Compile <code>adder.rb</code> and generate a low-level Verilog HDL description in the <code>adder</code> directory:</li>
</ul>
<pre><code class="language-bash">hdrcc -v adder.rb adder
</code></pre>
<ul>
<li>Compile the Verilog HDL file <code>adder8.v</code>, using <code>adder8</code> as the top module, and generate a graphical RTL diagram in the <code>view</code> directory:</li>
</ul>
<pre><code class="language-bash">hdrcc adder8.v -t adder8 --svg view
</code></pre>
<ul>
<li>Compile a parameterized system <code>multer</code> from <code>multer_gen.rb</code>, generating a 16x16->32-bit YAML hardware description into the <code>multer</code> directory:</li>
</ul>
<pre><code class="language-bash">hdrcc -V -t adder --param 16 adder_gen.rb adder
</code></pre>
<ul>
<li>Compile system <code>multer</code> with inputs and output bit width is generic from <code>multer_gen.rb</code> input file to a 16x16->32-bit circuit whose low-level YAML description into directory <code>multer</code>:</li>
</ul>
<pre><code class="language-bash">hdrcc -y -t multer -p 16,16,32 multer_gen.rb multer
</code></pre>
<ul>
<li>Simulate the circuit described in <code>counter_bench.rb</code> using the default simulation engine, outputting files to the <code>counter</code> directory:</li>
</ul>
<pre><code class="language-bash">hdrcc -S counter_bench.rb counter
</code></pre>
<p>Note: The default simulation engine is set to the fastest available engine (currently, the hybrid engine).</p>
<ul>
<li>Run in interactive mode.</li>
</ul>
<pre><code class="language-bash">hdrcc -I
</code></pre>
<ul>
<li>Run in interactive mode using pry as UI.</li>
</ul>
<pre><code class="language-bash">hdrcc -I pry
</code></pre>
<h2>Using HDLRuby in Interactive Mode</h2>
<p>When run in interactive mode, the HDLRuby framework launches a REPL (Read-Eval-Print Loop) environment and creates a working directory named HDLRubyWorkspace. By default, the REPL is irb, but it can also be set to pry.</p>
<p>Within the interactive prompt, you can write HDLRuby code just as you would in a standard HDLRuby source file. In addition, a set of special commands is available to compile, inspect, and simulate your design interactively:</p>
<h4>Available Commands</h4>
<ul>
<li>Compile an HDLRuby module (with optional parameters):</li>
</ul>
<pre><code class="language-ruby">hdr_make(<module>[,<parameters])
</code></pre>
<ul>
<li>Display the internal representation (IR) of the compiled module in YAML format:</li>
</ul>
<pre><code class="language-ruby">hdr_yaml
</code></pre>
<ul>
<li>Reconstruct and display the HDLRuby source description of the compiled module:</li>
</ul>
<pre><code class="language-ruby">hdr_hdr
</code></pre>
<ul>
<li>Generate and save Verilog HDL output to the <code>HDLRubyWorkspace</code> directory:</li>
</ul>
<pre><code class="language-ruby">hdr_verilog
</code></pre>
<ul>
<li>Generate and save VHDL output to the HDLRubyWorkspace directory:</li>
</ul>
<pre><code class="language-ruby">hdr_vhdl
</code></pre>
<ul>
<li>Simulate the compiled module:</li>
</ul>
<pre><code class="language-ruby">hdr_sim
</code></pre>
<ul>
<li>Simulate the compiled module and save the VCD trace (waveform output) to the directory <code>HDLRubyWorkspace</code>:</li>
</ul>
<pre><code>hdr_sim_vcd
</code></pre>
<ul>
<li>Simulate the compiled module in mute mode:</li>
</ul>
<pre><code>hdr_sim_mute
</code></pre>
<h2>HDLRuby files.</h2>
<p>Since HDLRuby is built on top of the Ruby language, it is standard convention to name HDLRuby files with the <code>.rb</code> extension.</p>
<p>For the same reason, including external HDLRuby files is done using the Ruby <code>methods require</code> or <code>require_relative</code>, which behave the same way as in standard Ruby. However, these methods can only be used to include HDLRuby description files, not plain Ruby files.</p>
<p>To include standard Ruby code (e.g., helper libraries or tools), you must use the methods <code>require_ruby</code> or <code>require_relative_ruby</code>.</p>
<h1>HDLRuby programming guide</h1>
<p>HDLRuby is designed to bring the flexibility and expressiveness of the Ruby language to hardware description, while ensuring that the resulting designs remain synthesizable. The abstractions provided by HDLRuby are meant to aid in describing hardwareÑbut they do not alter the underlying execution model, which is RTL (Register Transfer Level) by construction.</p>
<p>Another key feature of HDLRuby is its native support for all features of the Ruby language.</p>
<p><strong>Notes</strong>:</p>
<ul>
<li>It is possible to extend HDLRuby to support hardware descriptions at a higher level of abstraction than RTL. See <a href="#extending-hdlruby">Extending HDLRuby</a> for more details.</li>
<li>Throughout this guide, HDLRuby constructs are often compared to their Verilog HDL or VHDL equivalents to aid understanding.</li>
</ul>
<h2>Introduction</h2>
<p>This introduction gives a glimpse of what HDLRuby makes possible.</p>
<p>At first glance, HDLRuby resembles other hardware description languages such as Verilog HDL or VHDL. For example, the following code describes a simple D flip-flop:</p>
<pre><code class="language-ruby">system :dff do
bit.input :clk, :rst, :d
bit.output :q
par(clk.posedge) do
q <= d & ~rst
end
end
</code></pre>
<p>In this example, <code>system</code> is the keyword used to define a hardware component, similar to the <code>module</code> construct in Verilog HDL. Signals are declared using a <code><type>.<direction></code> format, where <code>type</code> is the data type (e.g., <code>bit</code>) and direction indicates the signal's role (<code>input</code>, <code>output</code>, <code>inout</code>, or <code>inner</code>). Processes, like Verilog's <code>always</code> blocks, are described using the <code>par</code> keyword for non-blocking assignments and <code>seq</code> for blocking assignments.</p>
<p>Here is a second example: an 8-bit adder.</p>
<pre><code class="language-ruby">system :adder8 do
bit[7..0].input :x, :y
bit[7..0].output :z
bit.output :cout
[cout,z] <= x.as(bit[8..0]) + y
end
</code></pre>
<p>This example demonstrates how to declare vector types. The signals <code>x</code>, <code>y</code>, and <code>z</code> are 8-bit unsigned vectors. If signed values are needed, you would use <code>signed</code> instead of <code>bit</code>.</p>
<p>Line 6 illustrates a connection (similar to the <code>assign</code> statement in Verilog HDL), where <code>cout</code> and <code>z</code> are concatenated and connected to the result of an addition. Note that <code>x</code> is explicitly cast to a 9-bit value to preserve the carry-out. In HDLRuby, unlike Verilog HDL, operand types are strictly preserved. This means that adding two 8-bit values yields an 8-bit result unless explicitly extended. The goal is to avoid the type-related ambiguities found in Verilog, while keeping syntax lighter than VHDL.</p>
<p>Conditional statements, common in RTL languages, are also supported in HDLRuby. However, unlike in Verilog or VHDL, HDLRuby conditionals can appear anywhere in a system bodyânot just within processes.</p>
<p>These include:</p>
<ul>
<li><p><code>hif</code> / <code>helsif</code> / <code>helse</code> for <code>if</code>-like conditionals</p>
</li>
<li><p><code>hcase</code> / <code>hwhen</code> / <code>helse</code> for <code>case</code>-like conditionals</p>
</li>
<li><p><code>mux</code>, an expression-level construct for multiplexers, which supports multiple inputs, unlike the ?: ternary operator in Verilog, which only handles two</p>
</li>
</ul>
<p><strong>Note</strong>: These statements are also called "parallel conditionals" in HDLRuby, to contrast with the ones used in the <code>sequencer</code> constructs (see <a href="#sequencer-software-like-Hardware-coding">Sequencer</a>).</p>
<p>For example, we can upgrade the 8-bit adder to an adder-subtractor:</p>
<pre><code class="language-ruby">system :adder_suber8 do
bit.input :addbsub
bit[7..0].input :x, :y
bit[7..0].output :z
bit.output :cout
hif(addbsub) { [cout,z] <= x.as(bit[8..0]) + ~y + 1 }
helse { [cout,z] <= x.as(bit[8..0]) + y }
end
</code></pre>
<p>The conditional logic above can also be written more compactly using the <code>mux</code> expression:</p>
<pre><code class="language-ruby"> [cout,z] <= x.as(bit[8..0]) + mux(addbsub, y, ~y + 1)
</code></pre>
<hr>
<p>Once a module has been described, it can be instantiated. For example, a single instance of the <code>dff</code> module named <code>dff0</code> can be declared as follows:</p>
<pre><code class="language-ruby">dff :dff0
</code></pre>
<p>The ports of the instance can be accessed like regular signals. For example, <code>dff0.d</code> refers to the d input of the flip-flop.</p>
<p>You can also connect the ports of an instance at the time of declaration. The example above can be extended as follows:</p>
<pre><code class="language-ruby">system :counter2 do
bit.input :clk, :rst
bit.output :q
dff(:dff0).(clk: clk, rst: rst, d: ~dff0.q)
dff(:dff1).(~dff0.q, rst, ~dff1.q, q)
end
</code></pre>
<p>In this example:</p>
<ul>
<li><p><code>dff0</code> uses named connections for its ports (e.g., <code>clk: clk</code>).</p>
</li>
<li><p><code>dff1</code> uses positional connections, in the order the ports were declared in the module.</p>
</li>
</ul>
<p>It is also possible to connect only a subset of the ports at instantiation time, and to reconnect or override ports later in the code.</p>
<hr>
<p>To simulate a circuit, you must write a test bench using <code>timed</code> constructs, which describe how signals evolve over time.</p>
<p>Here is an example that simulates the D flip-flop <code>dff</code> using a 20 ns clock, and toggles the input <code>d</code> every two clock cycles for ten iterations:</p>
<pre><code class="language-ruby">system :dff_bench do
dff :dff0
timed do
dff0.clk <= 0
dff0.rst <= 1
!10.ns
dff0.clk <= 1
!10.ns
dff0.clk <= 0
dff0.rst <= 0
dff0.d <= 1
!10.ns
repeat(10) do
repeat(4) { !10.ns ; dff0.clk <= ~dff0.clk }
dff0.d <= ~dff0.d
end
end
end
</code></pre>
<p>In this code:</p>
<ul>
<li><p><code>!<time>.<unit></code> pauses execution for the specified physical time. Units can range from picoseconds (<code>ps</code>) to seconds (<code>s</code>).</p>
</li>
<li><p><code>repeat(n)</code> repeats the block <code>n</code> times.</p>
</li>
<li><p><code>~dff0.clk</code> inverts the clock value.</p>
</li>
</ul>
<p>This test bench models both the reset behavior and a clock-driven sequence, demonstrating how to simulate sequential logic in HDLRuby.</p>
<hr>
<p>The <code>dff</code> example shown earlier is quite similar to what you would write in other HDLs. However, HDLRuby offers several features to increase productivity and reduce boilerplate in hardware descriptions. Below are a few of these conveniences.</p>
<p>First, HDLRuby supports syntactic sugar that allows for more concise code. For example, the following version of the <code>dff</code> module is functionally identical to the earlier version:</p>
<pre><code class="language-ruby">system :dff do
input :clk, :rst, :d
output :q
(q <= d & ~rst).at(clk.posedge)
end
</code></pre>
<p>In this example:</p>
<ul>
<li><p>The <code>bit</code> type is omitted for signal declarations (it is the default type).</p>
</li>
<li><p>Since the process contains only a single statement, it is expressed more compactly using the <code>at</code> method.</p>
</li>
</ul>
<p>Similarly, the <code>adder8</code> module can be written more concisely:</p>
<pre><code class="language-ruby">system :adder8 do
[8].input :x, :y
[8].output :z
output :cout
[cout,z] <= x.as(bit[9]) + y
end
</code></pre>
<p>In this example:</p>
<ul>
<li><p>The vector range <code>[7..0]</code> is abbreviated to <code>[8]</code>, which implies an 8-bit width.</p>
</li>
<li><p>The <code>bit</code> type is omitted for signal declarations (again, because it is the default).</p>
</li>
<li><p><strong>Note</strong>: when casting a signal or using it in expressions where type precision matters, the bit type must still be explicitly specified, as seen in <code>bit[9]</code>.</p>
</li>
</ul>
<hr>
<p>Second, HDLRuby also provides high-level constructs that make it easier to describe complex structures and behaviors in hardware.</p>
<p>For example, the <code>sequencer</code> construct allows to describe finite state
machines using software code-like statements, including conditionals,
loops and function calls. For example the following module describes a
simple 8-bit serializing circuit that emmits bit every 10 clock cycle.</p>
<pre><code class="language-ruby">system :serial do
[8].input :din
input :clk, :rst, :req
output :ack, :bout
[8].inner :buf
bout <= buf[0]
sequencer(clk,rst) do
sloop do
ack <= 0 ; buf <= 0
swhile(~req)
buf <= din ; ack <= 1
8.stimes do
buf <= buf >> 1
9.stimes
end
end
end
end
</code></pre>
<p>In this example:</p>
<ul>
<li><p><code>sequencer(clk, rst)</code> creates a clocked finite-state machine initialized on reset <code>(rst = 1)</code>.</p>
</li>
<li><p><code>sloop</code> is an infinite loop.</p>
</li>
<li><p><code>swhile(condition)</code> loops until the condition becomes false; if it has no body, it waits passively.</p>
</li>
<li><p><code>stimes(n)</code> is a shorthand for looping a block <code>n</code> times.</p>
</li>
<li><p>Each control-flow step in a sequencer (even inside loops) corresponds to one clock cycle, making timing behavior explicit and predictable.</p>
</li>
</ul>
<p>This example uses <code>buf</code> to hold the 8-bit input and shift it right each cycle to serialize it bit by bit onto the <code>bout</code> output.</p>
<p>Other HDLRuby high-level contructs includes:</p>
<ul>
<li><p>Iterators (both parallel and sequential)</p>
</li>
<li><p>Decoders</p>
</li>
<li><p>Fixed-point arithmetic</p>
</li>
<li><p>And more...</p>
</li>
</ul>
<p>These high-level abstractions are built on synthesizable foundations, and help keep hardware descriptions clear, maintainable, and concise, especially for complex control logic.</p>
<hr>
<p>Third, HDLRuby supports generic parameters that can be used flexibly to define reusable hardware modules. These parameters can represent sizes, types, or any other construct needed to generalize a design.</p>
<p>For instance, the following example defines a simple, fixed-size 8-bit register:</p>
<pre><code class="language-ruby">system :reg8 do
input :clk, :rst
[8].input :d
[8].output :q
(q <= d & [~rst]*8).at(clk.posedge)
end
</code></pre>
<p>To make this register size configurable, you can introduce a parameter. In this version, <code>n</code> defines the bit width of the register:</p>
<pre><code class="language-ruby">system :regn do |n|
input :clk, :rst
[n].input :d
[n].output :q
(q <= d & [~rst]*n).at(clk.posedge)
end
</code></pre>
<p>Going further, you can define a fully generic register by parameterizing not just the size, but the data type itself (e.g., signed, fixed-point, structs, etc.):</p>
<pre><code class="language-ruby">system :reg do |typ|
input :clk, :rst
typ.input :d
typ.output :q
(q <= d & [~rst]*typ.width).at(clk.posedge)
end
</code></pre>
<p>In this example:</p>
<ul>
<li><p><code>typ</code> is used as a type object (e.g., <code>bit[8]</code>, <code>signed[16]</code>, etc.).</p>
</li>
<li><p><code>typ.width</code> returns the number of bits associated with the type, allowing the reset mask (<code>[~rst] * typ.width</code>) to scale automatically.</p>
</li>
</ul>
<hr>
<p>Fourth, HDLRuby allows you to extend modules and instances after their declaration. This makes it easy to add new features without duplicating code.</p>
<p>Let us say you want to extend an existing <code>dff</code> module to include an inverted output (<code>qb</code>). There are three ways to do this:</p>
<ol>
<li>Inheriting from a Module.</li>
</ol>
<p>You can define a new system that inherits from the existing <code>dff</code>:</p>
<pre><code class="language-ruby">system :dff_full, dff do
output :qb
qb <= ~q
end
</code></pre>
<p>This creates a new module <code>dff_full</code> that includes all the functionality of <code>dff</code>, with the additional inverted output.</p>
<ol start="2">
<li>Reopening a Module.</li>
</ol>
<p>You can modify the original <code>dff</code> module after its declaration using the open method:</p>
<pre><code class="language-ruby">dff.open do
output :qb
qb <= ~q
end
</code></pre>
<p>This approach modifies <code>dff</code> itself, and the added behavior (<code>qb <= ~q</code>) will apply to all future instances of <code>dff</code>.</p>
<ol start="3">
<li>Reopening a Specific Instance.</li>
</ol>
<p>You can also modify a single instance of a module without affecting the others:</p>
<pre><code class="language-ruby"># Declare dff0 as an instance of dff
dff :dff0
# Modify it
dff0.open do
output :qb
qb <= ~q
end
</code></pre>
<p>In this case, only <code>dff0</code> will have the qb inverted output. Other instances of <code>dff</code> remain unchanged.</p>
<p>In summary, HDLRuby supports:</p>
<ul>
<li><p>Inheritance: for creating extended modules from existing ones</p>
</li>
<li><p>Module reopening: to modify a module after declaration</p>
</li>
<li><p>Instance reopening: to customize individual instances</p>
</li>
</ul>
<hr>
<p>Fifth, HDLRuby allows you to instantiate components in groups, similar to how signals are grouped in arrays. This enables scalable and readable hardware descriptions using familiar Ruby-style iteration.</p>
<pre><code class="language-ruby">system :shifter do |n|
input :clk, :rst
input :i0
output :o0, :o0b
dff_full :dffr
dffr.clk <= clk
# Instantiating n D-FF
dff_full[n,:dffIs]
# Connect the clock and the reset.
dffIs.heach { |ff| ff.clk <= clk ; ff.rst <= rst }
# Interconnect them as a shift register
dffIs[0..-1].heach_cons(2) { |ff0,ff1| ff1.d <= ff0.q }
# Connects the input and output of the circuit
dffIs[0].d <= i0
o0 <= dffIs[-1].q
o0b <= dffIs[-1].qb
end
</code></pre>
<p>In this example:</p>
<ul>
<li><p><code>dff_full[n, :dffIs]</code> creates an array of <code>n</code> instances named <code>dffIs</code>.</p>
</li>
<li><p><code>heach</code> iterates over each instance in parallel.</p>
</li>
<li><p><code>heach_cons(2)</code> creates overlapping pairs (like a sliding window) to wire the flip-flops together.</p>
</li>
</ul>
<p>If you don¿t need a specific subcomponent like <code>dff_full</code>, you can describe the shift register more concisely using a bit vector:</p>
<pre><code class="language-ruby">system :shifter do |n|
input :clk, :rst
input :i0
output :o0
[n].inner :sh
par (clk.posedge) do
hif(rst) { sh <= 0 }
helse { sh <= ((sh << 1)|i0) }
end
o0 <= sh[n-1]
end
</code></pre>
<p>This version:</p>
<ul>
<li><p>Uses a single <code>n</code>-bit inner register (<code>sh</code>) to store the shift state.</p>
</li>
<li><p>Updates the register each clock cycle, inserting <code>i0</code> at the least significant bit.</p>
</li>
<li><p>Outputs the most significant bit (<code>sh[n-1]</code>).</p>
</li>
</ul>
<hr>
<p>HDLRuby supports many more advanced features that enable concise, flexible, and reusable hardware descriptions. The following examples showcase how you can use generic parameters, functional abstractions, and custom types in practice.</p>
<p>Suppose you want to build a circuit that computes a sum of products between several inputs and constant coefficients. For example, with four signed 16-bit inputs and coefficients 3, 4, 5, 6, a basic HDLRuby implementation looks like this:</p>
<pre><code class="language-ruby">system :sumprod_16_3456 do
signed[16].input :i0, :i1, :i2, :i3
signed[16].output :o
o <= i0*3 + i1*4 + i2*5 + i3*6
end
</code></pre>
<p>This works, but lacks flexibility. Changing the bit width or coefficients requires rewriting the entire module. It also becomes error-prone with large coefficient sets.</p>
<p>A better approach is to create a generic system:</p>
<pre><code class="language-ruby">system :sumprod do |typ,coefs|
typ[-coefs.size].input :ins
typ.output :o
o <= coefs.hzip(ins).hreduce(0) do |sum,(i,c)|
sum + i*c
end
end
</code></pre>
<p>In this version:</p>
<ul>
<li><p><code>typ</code> defines the data type (e.g., <code>signed[32]</code>)</p>
</li>
<li><p><code>coefs</code> is an array of constant coefficients</p>
</li>
<li><p><code>ins</code> is an array of inputs with size <code>coefs.size</code></p>
</li>
<li><p><code>[-coefs.size]</code> is shorthand for declaring an array indexed in the forward direction (<code>[0..coefs.size - 1]</code>)</p>
</li>
<li><p><code>hzip</code> pairs each input with its coefficient (like Ruby¿s <code>zip</code>)</p>
</li>
<li><p><code>hreduce</code> accumulates the products into a final sum (like Ruby¿s <code>reduce</code>)</p>
</li>
</ul>
<p>This version supports any number of coefficients and any data type.
Example instantiation (with 16 coefficients):</p>
<pre><code class="language-ruby">sumprod(signed[32],
[3,78,43,246, 3,67,1,8,
47,82,99,13, 5,77,2,4]).(:my_circuit)
</code></pre>
<p><strong>Note</strong>: when passing generic arguments, the instance name (:my_circuit) goes after the parameters, in parentheses.</p>
<p>While the description <code>sumprod</code> is already usable in a wide range of
cases you may want to use specialized operations (e.g., saturated arithmetic) instead of standard <code>+</code> and <code>*</code>. You can do this by replacing operators with functions:</p>
<pre><code class="language-ruby">system :sumprod_func do |typ,coefs|
typ[-coefs.size].input :ins
typ.output :o
o <= coefs.hzip(ins).hreduce(0) do |sum,(c,i)|
add(sum, mult(i,c))
end
end
</code></pre>
<p>Now you define your custom <code>add</code> and <code>mult</code> functions. For example, an addition with saturation at 1000:</p>
<pre><code class="language-ruby">hdef :add do |x,y|
inner :res
seq do
res <= x + y
hif(res > 1000) { res <= 1000 }
end
res
end
</code></pre>
<p>With HDLRuby functions, the value returned is the result of the last statement, here <code>res</code>.</p>
<p>To avoid hardcoding saturation values, functions can accept extra arguments:</p>
<pre><code class="language-ruby">hdef :add do |max, x, y|
inner :res
seq do
res <= x + y
hif(res > max) { res <= max }
end
res
end
</code></pre>
<p>You would then call it like:</p>
<pre><code class="language-ruby">add(1000,sum,mult(...))
</code></pre>
<p>However, this becomes cumbersome if your functions take inconsistent argument counts. A better approach is to pass code (lambdas or procs) as parameters:</p>
<pre><code class="language-ruby">system :sumprod_proc do |add,mult,typ,coefs|
typ[coefs.size].input :ins
typ.output :o
o <= coefs.hzip(ins).hreduce(0) do |sum,(c,i)|
add.(sum, mult.(i*c))
end
end
</code></pre>
<p><strong>Note</strong>: When calling a proc in HDLRuby, use <code>.()</code> instead of regular parentheses.</p>
<p>Example usage:</p>
<pre><code class="language-ruby">sumprod_proc(
proc { |x,y| add_sat(1000,x,y) },
proc { |x,y| mult_sat(1000,x,y) },
signed[64],
[3,78,43,246, 3,67,1,8,
47,82,99,13, 5,77,2,4]).(:my_circuit)
</code></pre>
<p>This lets you reconfigure the arithmetic logic without changing the core circuit.</p>
<p>As second possible approach, HDLRuby also allows you to define custom data types with redefined operators:</p>
<pre><code>signed[16].typedef(:sat16_1000)
sat16_1000.define_operator(:+) do |x,y|
tmp = x + y
mux(tmp > 1000,tmp,1000)
end
</code></pre>
<p>In the code above:</p>
<ul>
<li><p>The first line defines the new type <code>sat16_1000</code> to be
16-bit signed,</p>
</li>
<li><p>The <code>define_operator</code> method overloads (redefines) the <code>+</code> operator
for this type.</p>
</li>
</ul>
<p>Then use your original <code>sumprod</code> with this type:</p>
<pre><code class="language-ruby">sumprod(sat16_1000,
[3,78,43,246, 3,67,1,8,
47,82,99,13, 5,77,2,4]).(:my_circuit)
</code></pre>
<p>You can also define generic types with parameters:</p>
<pre><code class="language-ruby">typedef :sat do |width, max|
signed[width]
end
sat.define_operator(:+) do |width,max, x,y|
tmp = x + y
mux(tmp > max, tmp, max)
end
</code></pre>
<p>Now you can instantiate saturated arithmetic with custom precision and bounds:</p>
<pre><code class="language-ruby">sumprod(sat(16,1000),
[3,78,43,246, 3,67,1,8,
47,82,99,13, 5,77,2,4]).(:my_circuit)
</code></pre>
<p><strong>Note</strong>: Any parameters used in a type definition must also be listed when overloading operators.</p>
<h2>How HDLRuby works</h2>
<p>Unlike high-level HDLs such as SystemVerilog, VHDL, or SystemC, HDLRuby descriptions are not direct descriptions of hardware. Instead, they are Ruby programs that generate hardware descriptions.</p>
<p>In traditional HDLs, executing the code (e.g., in a simulator) simulates the behavior of the described circuit. In contrast, executing HDLRuby code produces a low-level hardware description, which can then be synthesized or simulated like any standard HDL.</p>
<p>This separation between:</p>
<ul>
<li><p>the user-facing description (written in HDLRuby), and</p>
</li>
<li><p>the internal hardware representation (handled by <code>HDLRuby::Low</code>)</p>
</li>
</ul>
<p>allows HDLRuby to incorporate advanced programming features¿such as iterators, generics, and metaprogramming -- without affecting the synthesizability of the resulting hardware description.</p>
<hr>
<p>In HDLRuby, each construct does not directly describe hardware. Instead, it generates a hardware description. For example, consider the following line:</p>
<pre><code class="language-ruby"> a <= b
</code></pre>
<p>This expression creates a connection from signal <code>b</code> to signal <code>a</code>. When this line is executed (remember, HDLRuby code runs as Ruby code), it generates an instance of <code>HDLRuby::Low::Connection</code> -- the internal object representing that hardware connection.</p>
<p>Its execution will produce the actual hardware description of this connection as an object of the <code>HDLRuby::Low library</code> in this case, an instance of the <code>HDLRuby::Low::Connection</code> class. Concretely, an HDLRuby system is described by a Ruby block, and the instantiation of this system is performed by executing this block. The actual synthesizable description of this hardware is the execution result of this instantiation.</p>
<p>More generally:</p>
<ul>
<li><p>an HDLRuby module (<code>system</code>) is defined using a Ruby block.</p>
</li>
<li><p>When the module is instantiated, the block is executed.</p>
</li>
<li><p>The result of that execution is a complete, synthesizable hardware description in the internal <code>HDLRuby::Low</code> format.</p>
</li>
</ul>
<p>This architecture -- where Ruby is used to dynamically generate HDL constructs -- makes HDLRuby extremely flexible and expressive, while still producing valid, low-level HDL for synthesis or simulation</p>
<p>From here, we will begin to explore HDLRuby’s core constructs in more detail.</p>
<h2>Naming Rules</h2>
<p>Several constructs in HDLRuby -- such as modules and signals -- are identified by names. These names must be specified using Ruby symbols that begin with a lowercase letter.</p>
<p>For example:</p>
<ul>
<li><p><code>:hello</code> -> valid</p>
</li>
<li><p><code>:Hello</code> -> invalid (starts with an uppercase letter)</p>
</li>
</ul>
<p>Once declared, the construct is referred to by the name without the colon (<code>:</code>). That is, a construct declared as <code>:hello</code> will later be referenced simply as <code>hello</code>.</p>
<h2>Systems and Signals</h2>
<p>In HDLRuby, a <em>system</em> represents a digital module, similar to a module in Verilog HDL. A system includes:</p>
<ul>
<li><p>An interface (comprising <code>input</code>, <code>output</code>, and <code>inout</code> signals),</p>
</li>
<li><p>as well as structural and behavioral descriptions of the circuit.</p>
</li>
</ul>
<p>A signal represents a piece of state within a system. Each signal has:</p>
<ul>
<li><p>a data type, and</p>
</li>
<li><p>a value that can change over time.</p>
</li>
</ul>
<p>HDLRuby signals abstract both wires and registers:</p>
<ul>
<li><p>If a signal's value is explicitly assigned at all times, it behaves like a wire.</p>
</li>
<li><p>If the value is updated conditionally or based on clocked logic, it behaves like a register.</p>
</li>
</ul>
<h3>Declaring an Empty System</h3>
<p>A system is declared using the <code>system</code> keyword. It must be given a name (as a Ruby symbol or string) and a block that defines its contents.</p>
<p>For example, the following code declares an empty system named <code>box</code>:</p>
<pre><code class="language-ruby">system(:box) {}
</code></pre>
<p><strong>Notes</strong>:</p>
<ul>
<li><p>Since this is Ruby code, the block can also be written using <code>do...end</code> syntax. In that case, parentheses around the name are optional:</p>
<pre><code class="language-ruby">system :box do
end
</code></pre>
</li>
<li><p>Although HDLRuby internally stores names as Ruby symbols, you can also use strings. For example, the following is equally valid:</p>
<pre><code class="language-ruby">system("box") {}
</code></pre>
</li>
</ul>
<h3>Declaring a system with an interface</h3>
<p>A system's interface defines how it communicates with the outside world. It consists of <code>input</code>, <code>output</code>, and <code>inout</code> signals, each of a specified data type.</p>
<p>While interface declarations can appear anywhere in the system body, it is recommended to place them at the beginning for clarity.</p>
<p>Interface signals are declared using the following pattern:</p>
<pre><code class="language-ruby"><data type>.<direction> :name1, :name2, ...
</code></pre>
<p>For example, to declare a 1-bit input signal named <code>clk</code>:</p>
<pre><code class="language-ruby">bit.input :clk
</code></pre>
<p>Since <code>bit</code> is the default data type in HDLRuby, it can be omitted:</p>
<pre><code class="language-ruby">input :clk
</code></pre>
<p>Here is a more complete example: the following defines a simple memory module. It has:</p>
<ul>
<li><p>a 1-bit clock input (<code>clk</code>)</p>
</li>
<li><p>a 1-bit read/write control input (<code>rwb</code>, where 1 = read, 0 = write)</p>
</li>
<li><p>a 16-bit address input (<code>addr</code>)</p>
</li>
<li><p>an 8-bit bidirectional data bus (<code>data</code>)</p>
</li>
</ul>
<pre><code class="language-ruby">system :mem8_16 do
input :clk, :rwb
[15..0].input :addr
[7..0].inout :data
bit[7..0][2**16].inner :content
par(clk.posedge) do
hif(rwb) { data <= content[addr] }
helse { content[addr] <= data }
end
end
</code></pre>
<p>In this example:</p>
<ul>
<li><p>The memory content is declared as an array of <code>2**16</code> 8-bit words.</p>
</li>
<li><p>On each rising edge of <code>clk</code>, the module either reads from or writes to memory depending on the value of <code>rwb</code>.</p>
</li>
</ul>
<h3>Structural description in a system</h3>
<p>In HDLRuby, structural descriptions define how subsystems (i.e., instances of other systems) are instantiated and interconnected.</p>
<p>To instantiate a system, use the following syntax:</p>
<pre><code class="language-ruby"><system name> :<instance name>
</code></pre>
<p>For example, to instantiate the <code>mem8_16</code> system:</p>
<pre><code class="language-ruby">mem8_16 :mem8_16I
</code></pre>
<p>You can also declare multiple instances at once:</p>
<pre><code class="language-ruby">mem8_16 [:mem8_16I0, :mem8_16I1]
</code></pre>
<p>Or create an array of instances:</p>
<pre><code class="language-ruby">mem8_16[5,:mem8_18Is] # Creates an array of 5 instances named mem8_16Is
</code></pre>
<p>To interconnect subsystems, you'll often need internal signals. These are declared using the inner direction:</p>
<pre><code class="language-ruby">inner :w1
[1..0].inner :w2
</code></pre>
<p>If a signal is constant (i.e., its value never changes), use constant instead of inner.</p>
<p>When signals are declared, use the assignment operator <= to define connections:</p>
<pre><code class="language-ruby"><destination> <= <source>
</code></pre>
<p>For example:</p>
<pre><code class="language-ruby">ready <= w1 # Connects internal w1 to ready
w2[0] <= clk # Assigns clk to the first bit of w2