spinalhdl-exercises/README.md
marton bognar 2256bc29f7 Updates
2025-03-12 13:34:09 +01:00

111 lines
4.7 KiB
Markdown

# Introduction
This exercise session introduces SpinalHDL, a Scala-based Hardware Description
Language (HDL). SpinalHDL is implemented as a Scala library that generates RTL
in the form of either Verilog or VHDL code. It is also capable of running, and
interacting with, a Verilog simulation from Scala.
SpinalHDL is a RTL code generator rather than a higher-level HDL. Of course, the
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.
Before diving into this session, it is strongly recommended to read our
[tutorial](Tutorial.md).
# Exercises
# 1. popcnt
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
`Top.scala`.
The `Popcnt` component has the following constructor parameters:
- `width`: the bit width of the input;
- `multiCycle`: boolean indicating whether the logic should be implemented in
multiple cycles or asynchronous.
The following I/O ports should be defined by the `Popcnt` component:
- `start` (input): asserted when the operation should start. Not needed for the
asynchronous logic but should still be defined;
- `value` (input): input to the operation;
- `count` (output): result of the operation;
- `ready` (output): asserted when the operation is ready. Not needed for the
asynchronous logic but should still be defined;
It is recommended to implement the logic incrementally:
1. Implement the logic for a 4-bit asynchronous circuit. Beware that even though
the operation should be implemented using purely combinational logic, the
result should still be stored in a register. Also, the simulation will fail
with an obscure error message if you try to run it before defining a register
in your implementation;
1. Generalize this logic to support arbitrary bit widths. Hint: one way to do
this is to use a fold operation on the bits of the input;
1. Add the logic for the multi-cycle implementation.
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
provides the following method to dynamically add a shift register:
```scala
def addReg(name: String, width: BitCount, delay: Int): (Bits, Bits)
```
This methods adds a new shift register to the component containing `delay`
`width`-bit registers in cascade. The `name` argument can be used to generate
readable names for all created I/O ports and registers. The return value of this
method is a 2-tuple where the first element is an input connected to the first
register in the cascade and the second element an output connected to the last.
The method `getReg` should return the same tuple.
As an example, the image below shows the configuration of the component after
calling
```scala
addReg("foo", 8 bits, 3)|
```
In this case, the tuple returned would be `(foo_in, foo_out)`.
![Example shift register configuration](Images/shift_reg.svg)
SpinalHDL globally keeps track of the "current component". Whenever a new
instance is created of a `Component` subclass, the current component is set to
this instance. When logic is created, it is added to the current component. This
means that if logic is created inside the `addReg` method, it will be added to
the component that called this method, which is not what we want. To solve this,
SpinalHDL offers the `rework` method which can be called on a `Component` to add
logic to it after its creation:
```scala
class ExtendableComponent extends Component {
def addLogic(): Unit = {
rework {
// Add logic here
}
}
}
```
The logic for this exercise should be implemented in `ShiftReg.scala` and the
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.