From 2256bc29f7659167225396e44af73f2f0d77412b Mon Sep 17 00:00:00 2001 From: marton bognar Date: Wed, 12 Mar 2025 13:34:09 +0100 Subject: [PATCH] Updates --- Exercises/build.sbt | 2 +- README.md | 396 ++----------------------------------------- SpinalTest/README.md | 10 +- SpinalTest/build.sbt | 2 +- Tutorial.md | 381 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 399 insertions(+), 392 deletions(-) create mode 100644 Tutorial.md diff --git a/Exercises/build.sbt b/Exercises/build.sbt index b9c6694..6d83460 100644 --- a/Exercises/build.sbt +++ b/Exercises/build.sbt @@ -2,7 +2,7 @@ name := "SpinalExercises" version := "0.1" scalaVersion := "2.11.12" -val spinalVersion = "1.7.3" +val spinalVersion = "1.10.2a" fork := true diff --git a/README.md b/README.md index 10f9c8d..da1355a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -[[_TOC_]] - # Introduction This exercise session introduces SpinalHDL, a Scala-based Hardware Description @@ -12,386 +10,17 @@ full power of Scala can be used to build abstractions on top of the basic RTL components but only by combining those basic components. Having a good understanding of RTL is therefore imperative before using SpinalHDL. -This text explains the basic concepts of SpinalHDL before introducing the -exercises. However, it is not meant to be a comprehensive description of all its -features. For that, we refer to the official -[documentation](https://spinalhdl.github.io/SpinalDoc-RTD/). +Before diving into this session, it is strongly recommended to read our +[tutorial](Tutorial.md). -# Setup +# Exercises -Read [this](SpinalTest) to learn how to install all prerequisites and test your -setup. +# 1. popcnt -# Basic logic - -We will introduce the basic concepts of RTL modelling in SpinalHDL by building -the same 16-bit timer module we built in Verilog during the lectures. As in -Verilog, we will create an 8-bit timer module first and use that as the building -blocks for a 16-bit timer. - -While designing the combinational path in Verilog, we ended-up with the -following code (ignoring the declaration of all signals that are read): - -```verilog -reg [7:0] t2; - -always @* begin - if (rst) t2 = 0; - else if (enable) t2 = counter + 1; - else t2 = counter ; -end -``` - -In SpinalHDL, the same logic could be modelled as follows: - -```scala -val t2 = UInt(8 bits) - -when (rst) { - t2 := 0 -} elsewhen (enable) { - t2 := counter + 1 -} otherwise { - t2 := counter -} -``` - -Structurally, this looks very similar to the Verilog code. The most important -difference is the lack of an equivalent of Verilog's `always` block. When a -value is assigned to a variable, SpinalHDL uses its type annotations to figure -out if it should create combinational or synchronous logic. In this case, since -`t2` is not declared as a register, combinational logic will automatically be -created that is equivalent to the Verilog code above. - -While in Verilog we only specify the bit width of signals, SpinalHDL has -stricter -[types](https://spinalhdl.github.io/SpinalDoc-RTD/master/SpinalHDL/Data%20types). The -most important simple types are `Bool` (single-bit signal), `Bits` (bit-vector), -`UInt`/`SInt` (multi-bit signal supporting unsigned/signed arithmetic -operations). The multi-bit types take their bit width as a constructor argument, -which is specified as a positive integer followed by the keyword `bits`. - -Signals can freely be "cast" between types using the methods `asBits`, `asUInt`, -`asSInt`, and `asBools`. The latter method converts a multi-bit signal to a -`Vec[Bool]` ([see here](#aggregate-data-types)). - -SpinalHDL uses the `:=` operator to connect signals. For most arithmetic -operations, the same operators as in Verilog or Scala are used (e.g., `+`, -`&&`,...). The two exceptions are tests for equality (`===`) and inequality -(`=/=`) because using the standard operators (`==` and `!=`) would clash with -the use of those operators in Scala's class hierarchy. - -Because Scala already has keywords named `if` and `else`, SpinalHDL introduces -the keywords `when` (`if` in Verilog), `elsewhen` (`else if`), and `otherwise` -(`else`). Besides their names, they are syntactically similar as normal -conditional execution in Scala. - -For storing the value in a register, we initially had the following Verilog code: - -```verilog -reg [7:0] counter; - -always @(posedge clk) begin - counter <= t2; -end -``` - -which can be coded like this in SpinalHDL: - -```scala -val counter = Reg(UInt(8 bits)) -counter := t2 -``` - -Next to its type (`UInt`), the `counter` signal is explicitly annotated to be a -register -([`Reg`](https://spinalhdl.github.io/SpinalDoc-RTD/master/SpinalHDL/Sequential%20logic/registers.html)). -Since SpinalHDL knows that `counter` is a register, we do not have specify any -clock edge sensitivity like we have to in Verilog. Note that there is no mention -of any clock signal at all. By default, SpinalHDL uses a global "clock domain" -(which contains, among others, a clock signal) to drive all registers in the -design. This means that in most designs it is not necessary to ever explicitly -refer to a clock. - -The 8-bit counter design with all logic combined looked like this in Verilog: - -```verilog -reg [7:0] counter; - -always @(posedge clk) begin - if (rst) counter <= 0; - else if (enable) counter <= counter + 1; -end -``` - -which is concisely specified as follows in SpinalHDL: - -```scala -val counter = Reg(UInt(8 bits)).init(0) - -when (enable) { - counter := counter + 1 -} -``` - -The most important difference with Verilog is that we don't have to explicitly -deal with the reset signal. Like the clock signal, an implicit reset signal is -contained within the global clock domain. A reset value can be specified for a -register by calling its `init` method. - -# Advanced muxing - -We ran into trouble in Verilog when trying to model a 3-input mux like this: - -```verilog -reg o; - -always @* begin - if (s == 0) o = i0; - else if (s == 1) o = i1; - else if (s == 2) o = i2; -end -``` - -Here, Verilog would silently create a latch for `o`. - -If we model the same logic in SpinalHDL: - -```scala -val o = Bool - -when (s === 0) { - o := i0 -} elsewhen (s === 1) { - o := i1 -} elsewhen (s === 2) { - o := i2 -} -``` - -we get a very useful error (SpinalHDL checks for many [common design -errors](https://spinalhdl.github.io/SpinalDoc-RTD/master/SpinalHDL/Design%20errors): - -``` -LATCH DETECTED from the combinatorial signal (toplevel/o : Bool), defined at ... -``` - -The solution is the same as in Verilog: make sure a value is assigned to `o` under all conditions. -Like in Verilog, later assignments override earlier ones so we could do something like this: - -```scala -val o = Bool -o := False // or o.assignDontCare() -... -``` - -SpinalHDL also supports a [switch -construct](https://spinalhdl.github.io/SpinalDoc-RTD/master/SpinalHDL/Semantic/when_switch.html): - -```scala -switch (s) { - is (0) { o := i0 } - is (1) { o := i1 } - is (2) { o := i0 } - default { o := False } -} -``` - -and a more explicit `mux` method: - -```scala -o := s.mux( - 0 -> i1, - 1 -> i1, - 2 -> i2, - default -> False -) -``` - -# Modules - -The full 8-bit counter module looked as follows in Verilog: - -```verilog -module counter8( - input wire clk, - input wire rst, - input wire enable, - output reg[7:0] counter -); -always @(posedge clk) begin - if (rst) counter <= 0; - else if (enable) counter <= counter + 1; -end -endmodule -``` - -In SpinalHDL, modules are created by wrapping logic in the constructor of a -class that inherits from `Component`: - -```scala -class Counter8 extends Component { - val enable = in(Bool) - val counter = out(Reg(UInt(8 bits)).init(0)) - - when (enable) { - counter := counter + 1 - } -} -``` - -I/O ports are created by wrapping signal types with the `in` or `out` -annotations. Signals that are not annotated as I/O are local signals which -cannot be accessed from outside the surrounding module. Note again that the -`clk` and `rst` signals are not visible since they are part of the implicit -global clock domain. - -Modules can be instantiated by creating objects of their classes. -I/O ports are then connected by simply assigning to or reading from them: - -```scala -class Counter16 extends Component { - val enable = in(Bool) - val counter = out(UInt(16 bits)) - - val lsb = new Counter8 - lsb.enable := enable - - val msb = new Counter8 - msb.enable := enable && (lsb.counter === 0xff); - - counter := msb.counter @@ lsb.counter -} -``` - -# Workflow - -SpinalHDL is implemented as a Scala library and all "keywords" discussed so for -are, in fact, not keywords but simply methods defined by this library. When -running a Scala program using SpinalHDL, an internal RTL representation is -created that corresponds to the logic defined by the library calls. This RTL can -then be converted to Verilog or VHDL: - -```scala -object Generate { - def main(args: Array[String]): Unit = { - SpinalVerilog(new Counter16) - } -} -``` - -Here, the instantiation of the `Counter16` module builds an internal -representation of all `RTL` for this module which is then converted to Verilog -by passing the instance to the `SpinalVerilog` method. When running this main -method, a file called `Counter16.v` will be created that contains the generated -Verilog code. - -SpinalHDL also supports running [Verilog -simulations](https://spinalhdl.github.io/SpinalDoc-RTD/master/SpinalHDL/Simulation/) -directly from Scala. This is implemented by first generating Verilog and then -simulating this code using [Verilator](https://www.veripool.org/wiki/verilator). -While the simulation is running, it is possible to access Verilog signals from -Scala which allows, for example, writing test cases in Scala. For this exercise -session, all necessary simulation code is provided. - -The temporary files and results of the simulation are stored in the -`simWorkspace` directory. When a top-level component called `Foo` is simulated, -the following files are created (among others): - -- `Foo/test.vcd`: VCD file resulting from the simulation; -- `Foo/rtl/Foo.v`: Verilog code used for the simulation. - -# Naming - -Since SpinalHDL generates Verilog or runs simulations using a Verilog simulator, -it is important that modules and signals defined in Scala are properly named in -Verilog. For the most common cases, SpinalHDL is able to automatically figure -out good names to use in Verilog using the following rules: - -- Modules get the same name as the `Component` subclass used to generate them; -- Top-level signals defined in the constructor of a `Component` subclass get the - same name as the Scala instance variable used to store them. - -In both cases, SpinalHDL will resolve naming conflicts with Verilog keywords by -appending a number to the name. It is recommended to avoid using Verilog -keywords for modules or signal names. - -In more advanced use cases where signals are not necessarily stored in top-level -Scala instance variables, SpinalHDL will not be able to automatically select a -good name. Instead, it will generate a name of the form `_zz_n_` where `n` is -a natural number. Such signals are not dumped to theVCD file during simulation. -In these cases, the `setName` method can be used to manually select a name. - -# Aggregate data types - -SpinalHDL offers an array-like data structure called -[`Vec`](https://spinalhdl.github.io/SpinalDoc-RTD/master/SpinalHDL/Data%20types/Vec.html) -that holds a fixed number of signals of the same type. It implements Scala's -`IndexedSeq` trait. The main advantage of using `Vec` over one of Scala's -built-in collections is that SpinalHDL's naming algorithm recognizes it. It -creates a signal for each element that is named by appending its index to the -name of the `Vec`. - -Multiple signals of different types can be bundled in a struct-like data -structure using the -[`Bundle`](https://spinalhdl.github.io/SpinalDoc-RTD/master/SpinalHDL/Data%20types/bundle.html) -base class. This is mainly useful to define complex buses in which multiple -signals are always used together. For example, an [SPI -bus](https://en.wikipedia.org/wiki/Serial_Peripheral_Interface) could be defined -as follows: - -```scala -class SpiBus(numSlaves: Int) extends Bundle { - val sclk = Bool - val mosi = Bool - val miso = Bool - val ss = Bits(numSlaves bits) -} -``` - -Buses often have an I/O direction and what is an output on one side of the bus, -is an input on the other side. The two sides of a bus are called master and -slave. To handle this, SpinalHDL defines the `IMasterSlave` trait. When -implementing this trait, the `asMaster` method must be implemented which should -set the I/O direction of all signals when the bus is used as a master (when used -as a slave, SpinalHDL automatically flips the direction of all signals): - -```scala -class SpiBus(numSlaves: Int) extends Bundle with IMasterSlave { - ... - override def asMaster(): Unit = { - out(sclk, mosi, ss) - in(ss) - } -} -``` - -SpinalHDL also supports an "auto-connect" operator (`<>`) that automatically -connects all signals of a bus to the corresponding signals of another bus -(taking their directions into account): - -```scala -class SpiMaster extends Component { - val bus = master(new SpiBus) - ... -} - -class SpiSlave extends Component { - val bus = slave(new SpiBus) - ... -} - -class Soc extends Component { - val spiMaster = new SpiMaster - val spiSlave = new SpiSlave - spiMaster.bus <> spiSlave.bus -} -``` - -# Exercise: popcnt - -For the first exercise, the `popcnt` module discussed during the Verilog lecture -should be implemented in SpinalHDL. The end goal is to implement a class that is +For the first exercise, the `popcnt` module mentioned during the Verilog lecture +should be implemented in SpinalHDL. This module takes as input a bitvector, and +returns the number of `1` bits in it. +The end goal is to implement a class that is configurable in the number of bits of the input and in whether the operation should be implemented in multiple cycles. The logic should be implemented in `Popcnt.scala` and the simulation can be run using the `PopcntSim` object in @@ -423,7 +52,11 @@ It is recommended to implement the logic incrementally: this is to use a fold operation on the bits of the input; 1. Add the logic for the multi-cycle implementation. -# Exercise: a configurable shift register +You can run the tests by executing `sbt 'runMain exercises.PopcntSim'` in the +Exercises directory. Look at the test cases and add more to thoroughly test your +implementation! + +# 2. Configurable shift register In this exercise, you will be implementing a component that manages [shift registers](https://en.wikipedia.org/wiki/Shift_register). This component @@ -474,4 +107,5 @@ simulation can be run using the `ShiftRegSim` object in `Top.scala`. The simulation adds two registers (see method `createShiftReg`) and runs for 10 cycles, setting the input of the first register to increasing values starting at 0, and the input of the second register to decreasing values starting at 10. - +Find the generated VCD file and examine it in GTKWave to confirm that your +implementation works as expected. diff --git a/SpinalTest/README.md b/SpinalTest/README.md index 115954f..1cf7135 100644 --- a/SpinalTest/README.md +++ b/SpinalTest/README.md @@ -1,20 +1,12 @@ # Prerequisites -- [sbt](https://www.scala-sbt.org/download.html) +- [sbt](https://www.scala-sbt.org/download.html) (which relies on a Java JDK) - [Verilator](https://verilator.org/guide/latest/install.html) - A Java JDK More info can be found in the SpinalHDL [documentation](https://spinalhdl.github.io/SpinalDoc-RTD/master/SpinalHDL/Getting%20Started/getting_started.html). -On Arch Linux, everything can be installed from the package repositories: - -``` -sudo pacman -S sbt verilator jdk-openjdk -``` - -The PCs in the CS department have everything installed. - # Test installation To test code generation (this should create a file called `Counter.v`): diff --git a/SpinalTest/build.sbt b/SpinalTest/build.sbt index 4a4fc21..d8991c0 100644 --- a/SpinalTest/build.sbt +++ b/SpinalTest/build.sbt @@ -2,7 +2,7 @@ name := "SpinalTest" version := "0.1" scalaVersion := "2.11.12" -val spinalVersion = "1.7.3" +val spinalVersion = "1.10.2a" fork := true diff --git a/Tutorial.md b/Tutorial.md new file mode 100644 index 0000000..624081b --- /dev/null +++ b/Tutorial.md @@ -0,0 +1,381 @@ +# SpinalHDL + +This text explains the basic concepts of SpinalHDL before introducing the +exercises. However, it is not meant to be a comprehensive description of all its +features. For that, we refer to the official +[documentation](https://spinalhdl.github.io/SpinalDoc-RTD/). + +# Table of Contents + +[[_TOC_]] + +# Setup + +Read [this](SpinalTest) to learn how to install all prerequisites and test your +setup. + +# Basic logic + +We will introduce the basic concepts of RTL modeling in SpinalHDL by building +the same 16-bit counter module we saw during the lectures. As in +Verilog, we will create an 8-bit counter module first and use that as the building +blocks for a 16-bit counter. + +While designing the combinational path in Verilog, we could write the +following code (ignoring the declaration of all signals that are read): + +```verilog +reg [7:0] t2; + +always @* begin + if (rst) t2 = 0; + else if (enable) t2 = counter + 1; + else t2 = counter ; +end +``` + +In SpinalHDL, the same logic could be modelled as follows: + +```scala +val t2 = UInt(8 bits) + +when (rst) { + t2 := 0 +} elsewhen (enable) { + t2 := counter + 1 +} otherwise { + t2 := counter +} +``` + +Structurally, this looks very similar to the Verilog code. The most important +difference is the lack of an equivalent of Verilog's `always` block. When a +value is assigned to a variable, SpinalHDL uses its type annotations to figure +out if it should create combinational or synchronous logic. In this case, since +`t2` is not declared as a register, combinational logic will automatically be +created that is equivalent to the Verilog code above. + +While in Verilog we only specify the bit width of signals, SpinalHDL has +stricter +[types](https://spinalhdl.github.io/SpinalDoc-RTD/master/SpinalHDL/Data%20types). The +most important simple types are `Bool` (single-bit signal), `Bits` (bit-vector), +`UInt`/`SInt` (multi-bit signal supporting unsigned/signed arithmetic +operations). The multi-bit types take their bit width as a constructor argument, +which is specified as a positive integer followed by the keyword `bits`. + +Signals can freely be "cast" between types using the methods `asBits`, `asUInt`, +`asSInt`, and `asBools`. The latter method converts a multi-bit signal to a +`Vec[Bool]` ([see here](#aggregate-data-types)). + +SpinalHDL uses the `:=` operator to connect signals. For most arithmetic +operations, the same operators as in Verilog or Scala are used (e.g., `+`, +`&&`,...). The two exceptions are tests for equality (`===`) and inequality +(`=/=`) because using the standard operators (`==` and `!=`) would clash with +the use of those operators in Scala's class hierarchy. + +Because Scala already has keywords named `if` and `else`, SpinalHDL introduces +the keywords `when` (`if` in Verilog), `elsewhen` (`else if`), and `otherwise` +(`else`). Besides their names, they are syntactically similar as normal +conditional execution in Scala. + +For storing the value in a register, we initially had the following Verilog code: + +```verilog +reg [7:0] counter; + +always @(posedge clk) begin + counter <= t2; +end +``` + +which can be coded like this in SpinalHDL: + +```scala +val counter = Reg(UInt(8 bits)) +counter := t2 +``` + +Next to its type (`UInt`), the `counter` signal is explicitly annotated to be a +register +([`Reg`](https://spinalhdl.github.io/SpinalDoc-RTD/master/SpinalHDL/Sequential%20logic/registers.html)). +Since SpinalHDL knows that `counter` is a register, we do not have specify any +clock edge sensitivity like we have to in Verilog. Note that there is no mention +of any clock signal at all. By default, SpinalHDL uses a global "clock domain" +(which contains, among others, a clock signal) to drive all registers in the +design. This means that in most designs it is not necessary to ever explicitly +refer to a clock. + +The 8-bit counter design with all logic combined looked like this in Verilog: + +```verilog +reg [7:0] counter; + +always @(posedge clk) begin + if (rst) counter <= 0; + else if (enable) counter <= counter + 1; +end +``` + +which is concisely specified as follows in SpinalHDL: + +```scala +val counter = Reg(UInt(8 bits)).init(0) + +when (enable) { + counter := counter + 1 +} +``` + +The most important difference with Verilog is that we don't have to explicitly +deal with the reset signal. Like the clock signal, an implicit reset signal is +contained within the global clock domain. A reset value can be specified for a +register by calling its `init` method. + +# Advanced muxing + +We ran into trouble in Verilog when trying to model a 3-input mux like this: + +```verilog +reg o; + +always @* begin + if (s == 0) o = i0; + else if (s == 1) o = i1; + else if (s == 2) o = i2; +end +``` + +Here, Verilog would silently create a latch for `o`. + +If we model the same logic in SpinalHDL: + +```scala +val o = Bool + +when (s === 0) { + o := i0 +} elsewhen (s === 1) { + o := i1 +} elsewhen (s === 2) { + o := i2 +} +``` + +we get a very useful error (SpinalHDL checks for many [common design +errors](https://spinalhdl.github.io/SpinalDoc-RTD/master/SpinalHDL/Design%20errors): + +``` +LATCH DETECTED from the combinatorial signal (toplevel/o : Bool), defined at ... +``` + +The solution is the same as in Verilog: make sure a value is assigned to `o` under all conditions. +Like in Verilog, later assignments override earlier ones so we could do something like this: + +```scala +val o = Bool +o := False // or o.assignDontCare() +... +``` + +SpinalHDL also supports a [switch +construct](https://spinalhdl.github.io/SpinalDoc-RTD/master/SpinalHDL/Semantic/when_switch.html): + +```scala +switch (s) { + is (0) { o := i0 } + is (1) { o := i1 } + is (2) { o := i0 } + default { o := False } +} +``` + +and a more explicit `mux` method: + +```scala +o := s.mux( + 0 -> i1, + 1 -> i1, + 2 -> i2, + default -> False +) +``` + +# Modules + +The full 8-bit counter module looked as follows in Verilog: + +```verilog +module counter8( + input wire clk, + input wire rst, + input wire enable, + output reg[7:0] counter +); +always @(posedge clk) begin + if (rst) counter <= 0; + else if (enable) counter <= counter + 1; +end +endmodule +``` + +In SpinalHDL, modules are created by wrapping logic in the constructor of a +class that inherits from `Component`: + +```scala +class Counter8 extends Component { + val enable = in(Bool) + val counter = out(Reg(UInt(8 bits)).init(0)) + + when (enable) { + counter := counter + 1 + } +} +``` + +I/O ports are created by wrapping signal types with the `in` or `out` +annotations. Signals that are not annotated as I/O are local signals which +cannot be accessed from outside the surrounding module. Note again that the +`clk` and `rst` signals are not visible since they are part of the implicit +global clock domain. + +Modules can be instantiated by creating objects of their classes. +I/O ports are then connected by simply assigning to or reading from them: + +```scala +class Counter16 extends Component { + val enable = in(Bool) + val counter = out(UInt(16 bits)) + + val lsb = new Counter8 + lsb.enable := enable + + val msb = new Counter8 + msb.enable := enable && (lsb.counter === 0xff); + + counter := msb.counter @@ lsb.counter +} +``` + +# Workflow + +SpinalHDL is implemented as a Scala library and all "keywords" discussed so for +are, in fact, not keywords but simply methods defined by this library. When +running a Scala program using SpinalHDL, an internal RTL representation is +created that corresponds to the logic defined by the library calls. This RTL can +then be converted to Verilog or VHDL: + +```scala +object Generate { + def main(args: Array[String]): Unit = { + SpinalVerilog(new Counter16) + } +} +``` + +Here, the instantiation of the `Counter16` module builds an internal +representation of all `RTL` for this module which is then converted to Verilog +by passing the instance to the `SpinalVerilog` method. When running this main +method, a file called `Counter16.v` will be created that contains the generated +Verilog code. + +SpinalHDL also supports running [Verilog +simulations](https://spinalhdl.github.io/SpinalDoc-RTD/master/SpinalHDL/Simulation/) +directly from Scala. This is implemented by first generating Verilog and then +simulating this code using [Verilator](https://www.veripool.org/wiki/verilator). +While the simulation is running, it is possible to access Verilog signals from +Scala which allows, for example, writing test cases in Scala. For this exercise +session, all necessary simulation code is provided. + +The temporary files and results of the simulation are stored in the +`simWorkspace` directory. When a top-level component called `Foo` is simulated, +the following files are created (among others): + +- `Foo/test.vcd`: VCD file resulting from the simulation; +- `Foo/rtl/Foo.v`: Verilog code used for the simulation. + +# Naming + +Since SpinalHDL generates Verilog or runs simulations using a Verilog simulator, +it is important that modules and signals defined in Scala are properly named in +Verilog. For the most common cases, SpinalHDL is able to automatically figure +out good names to use in Verilog using the following rules: + +- Modules get the same name as the `Component` subclass used to generate them; +- Top-level signals defined in the constructor of a `Component` subclass get the + same name as the Scala instance variable used to store them. + +In both cases, SpinalHDL will resolve naming conflicts with Verilog keywords by +appending a number to the name. It is recommended to avoid using Verilog +keywords for modules or signal names. + +In more advanced use cases where signals are not necessarily stored in top-level +Scala instance variables, SpinalHDL will not be able to automatically select a +good name. Instead, it will generate a name of the form `_zz_n_` where `n` is +a natural number. Such signals are not dumped to theVCD file during simulation. +In these cases, the `setName` method can be used to manually select a name. + +# Aggregate data types + +SpinalHDL offers an array-like data structure called +[`Vec`](https://spinalhdl.github.io/SpinalDoc-RTD/master/SpinalHDL/Data%20types/Vec.html) +that holds a fixed number of signals of the same type. It implements Scala's +`IndexedSeq` trait. The main advantage of using `Vec` over one of Scala's +built-in collections is that SpinalHDL's naming algorithm recognizes it. It +creates a signal for each element that is named by appending its index to the +name of the `Vec`. + +Multiple signals of different types can be bundled in a struct-like data +structure using the +[`Bundle`](https://spinalhdl.github.io/SpinalDoc-RTD/master/SpinalHDL/Data%20types/bundle.html) +base class. This is mainly useful to define complex buses in which multiple +signals are always used together. For example, an [SPI +bus](https://en.wikipedia.org/wiki/Serial_Peripheral_Interface) could be defined +as follows: + +```scala +class SpiBus(numSlaves: Int) extends Bundle { + val sclk = Bool + val mosi = Bool + val miso = Bool + val ss = Bits(numSlaves bits) +} +``` + +Buses often have an I/O direction and what is an output on one side of the bus, +is an input on the other side. The two sides of a bus are called master and +slave. To handle this, SpinalHDL defines the `IMasterSlave` trait. When +implementing this trait, the `asMaster` method must be implemented which should +set the I/O direction of all signals when the bus is used as a master (when used +as a slave, SpinalHDL automatically flips the direction of all signals): + +```scala +class SpiBus(numSlaves: Int) extends Bundle with IMasterSlave { + ... + override def asMaster(): Unit = { + out(sclk, mosi, ss) + in(ss) + } +} +``` + +SpinalHDL also supports an "auto-connect" operator (`<>`) that automatically +connects all signals of a bus to the corresponding signals of another bus +(taking their directions into account): + +```scala +class SpiMaster extends Component { + val bus = master(new SpiBus) + ... +} + +class SpiSlave extends Component { + val bus = slave(new SpiBus) + ... +} + +class Soc extends Component { + val spiMaster = new SpiMaster + val spiSlave = new SpiSlave + spiMaster.bus <> spiSlave.bus +} +```