Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add chapter 8 tutorials #399

Merged
merged 35 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
cc2d0b2
Added chapter 8 materials
quekyj Aug 8, 2023
301d0b6
Fix dead link
quekyj Aug 8, 2023
e8dbab4
fix dead link
quekyj Aug 8, 2023
fa35229
Fix deadlink
quekyj Aug 8, 2023
428cdf6
update the counter interface
quekyj Aug 8, 2023
597bab2
Removed the word more verbose
quekyj Aug 9, 2023
d944cbb
Fix inconsistent use of singular and plural
quekyj Aug 9, 2023
e7147fe
Fix typo
quekyj Aug 9, 2023
a8f7193
Fix bug on the counter interface
quekyj Aug 9, 2023
da0233a
Modified the tutorials to match with latest verson of code
quekyj Aug 9, 2023
7d856fd
Fix typo to refractorable
quekyj Aug 9, 2023
2c5430a
Use counterInterface port
quekyj Aug 11, 2023
69caa59
Use counterinter instead of counter to access the value of the counter
quekyj Aug 11, 2023
bc21eca
Make the LED width 8 instead of borrowing Button width
quekyj Aug 11, 2023
7ac260a
Use Map instead of enum
quekyj Aug 11, 2023
4002c9a
Use Map for light as well
quekyj Aug 11, 2023
51e8ea6
Add FSM mermaid diagram
quekyj Aug 28, 2023
3d796b4
Added FSM mermaid diagram into tutorials
quekyj Aug 28, 2023
84e0179
Hardcode led width to 8
quekyj Aug 28, 2023
2845960
Fix indent on all code
quekyj Aug 28, 2023
f07d77a
Fix indent
quekyj Aug 28, 2023
aca808d
use simple map instead of enum
quekyj Aug 28, 2023
4ad22cc
Do not expose _oven fsm for security issue
quekyj Aug 28, 2023
7cb6327
Clean up interface example
quekyj Aug 28, 2023
c19ef4a
Fix broken link
quekyj Aug 28, 2023
4636c56
Delete Old FSM diagram
quekyj Aug 28, 2023
dea0c66
Fix broken image
quekyj Aug 28, 2023
123f4a3
Update file name
quekyj Aug 28, 2023
38ab517
Merge branch 'intel:main' into chapter-8-bootcamp-clean
quekyj Aug 28, 2023
5b60c9b
Fix dart 3.1.0 analysis_options
quekyj Aug 29, 2023
0dee717
Merge branch 'chapter-8-bootcamp-clean' of https://github.com/quekyj/…
quekyj Aug 29, 2023
0f1770f
Merge branch 'main' into chapter-8-bootcamp-clean
quekyj Sep 4, 2023
2f17932
Fix typo
quekyj Sep 4, 2023
56363c4
Removed license header in tutorial
quekyj Sep 4, 2023
c2a8f1c
Fix typo
quekyj Sep 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions doc/tutorials/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,10 @@

## Chapter 8: Abstractions

- Pipelines: Normally Delayed exists in between a big circuit, abstractions split the logic across multiple cycles and let us decide what logic do you want it to occur in each of the cycle.
- Finite state machines
- [ROHD Abstraction](./chapter_8/00_abstraction.md#rohd-abstraction)
- [Interface](./chapter_8/01_interface.md)
- [Finite State Machine](./chapter_8/02_finite_state_machine.md)
- [Pipeline](./chapter_8/03_pipeline.md)

## Chapter 9: ROHD-COSIM External SystemVerilog Modules (Coming Soon!)

Expand Down
28 changes: 28 additions & 0 deletions doc/tutorials/chapter_8/00_abstraction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Content

- [ROHD Abstraction](#rohd-abstraction)
- [Interface](#interface)
- [Finite State Machine](#finite-state-machine-fsm)
- [Pipeline](#pipeline)

## Learning Outcome

In this chapter:

- You will lern how to use ROHD abstraction to speed up the development process.
quekyj marked this conversation as resolved.
Show resolved Hide resolved

## ROHD Abstraction

ROHD provides several abstraction layer to fasten the development work. As of now, ROHD provides several abstraction catalog which listed as below:
quekyj marked this conversation as resolved.
Show resolved Hide resolved

### [Interface](01_interface.md)

Interface make it easier to define port connections of a module in a reusable way.

### [Finite State Machine (FSM)](02_finite_state_machine.md)

An easy and more verbose implementation of Finite State Machine with ROHD API.
quekyj marked this conversation as resolved.
Show resolved Hide resolved

### [Pipeline](03_pipeline.md)

Pipeline can be build with ROHD API in a simple and refractable way.
quekyj marked this conversation as resolved.
Show resolved Hide resolved
176 changes: 176 additions & 0 deletions doc/tutorials/chapter_8/01_interface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# Content

- [ROHD Interfaces](#rohd-interfaces)
- [Counter Module](#counter-module)
- [Counter Module Interface](#counter-module-interface)
- [Exercise](#exercise)

## Learning Outcome

In this chapter:

- You will learn how to use ROHD interface abstraction API to group and reuse port easily.

## ROHD Interfaces

Interfaces make it easier to define port connections of a module in a reusable way. An example of the counter re-implemented using interfaces is shown below.

`Interface` takes a generic parameter for direction type. This enables you to group signals so make adding them as inputs/outputs easier for different modules sharing this interface.

The `Port` class extends `Logic`, but has a constructor that takes width as a positional argument to make interface port definitions a little cleaner.

When connecting an `Interface` to a `Module`, you should always create a new instance of the `Interface` so you don't modify the one being passed in through the constructor. Modifying the same `Interface` as was passed would have negative consequences if multiple `Modules` were consuming the same `Interface`, and also breaks the rules for `Module` input and output connectivity.

The `connectIO` function under the hood calls `addInput` and `addOutput` directly on the `Module` and connects those `Module` ports to the correct ports on the `Interfaces`. Connection is based on signal names. You can use the `uniquify` Function argument in `connectIO` to uniquify inputs and outputs in case you have multiple instances of the same `Interface` connected to your module. You can also use the `setPort` function to directly set individual ports on the `Interface` instead of via tagged set of ports.

## Counter Module

In ROHD, the [`Counter` module](../../../example/example.dart) reside in the example is one of the most basic example. Let us try to understand the counter module and see how we can modified it with ROHD interface instead.

In the `Counter` module, its take in inputs `enable`, `reset`, `clk` and output `val`.

On every positive edge of the clock, the value of `val` will be increment by 1 if enable `en` is true.

```dart
// Define a class Counter that extends ROHD's abstract Module class.
class Counter extends Module {
// For convenience, map interesting outputs to short variable names for
// consumers of this module.
Logic get val => output('val');

// This counter supports any width, determined at run-time.
final int width;

Counter(Logic en, Logic reset, Logic clk,
{this.width = 8, super.name = 'counter'}) {
// Register inputs and outputs of the module in the constructor.
// Module logic must consume registered inputs and output to registered
// outputs.
en = addInput('en', en);
reset = addInput('reset', reset);
clk = addInput('clk', clk);

final val = addOutput('val', width: width);

// A local signal named 'nextVal'.
final nextVal = Logic(name: 'nextVal', width: width);

// Assignment statement of nextVal to be val+1
// ('<=' is the assignment operator).
nextVal <= val + 1;

// `Sequential` is like SystemVerilog's always_ff, in this case trigger on
// the positive edge of clk.
Sequential(clk, [
// `If` is a conditional if statement, like `if` in SystemVerilog
// always blocks.
If(reset, then: [
// The '<' operator is a conditional assignment.
val < 0
], orElse: [
If(en, then: [val < nextVal])
])
]);
}
}
```

## Counter Module Interface

Let us see how we can change the `ROHD` module to `Counter` interface. First, we can create a enum `CounterDirection` that have tags of `inward`, `outwards` and `misc`. You can think of this as what is the category you want to group your ports. This category can be reuse between modules. `inwards` port group all inputs port, `outwards` group all outputs port and `misc` group all miscellanous port such as `clk`.
quekyj marked this conversation as resolved.
Show resolved Hide resolved

Then, we can create our interface `CounterInterface` that extends from parents `Interface<TagType>`. The `TagType` is the enum that we create earlier. Let create the getters to all ports for `Counter` to allows us to send signals to the interface.

As normal, we will need to create the constructor `CounterInterface`. Inside the constuctor, add `setPorts()` function to group our common port. `setPorts` takes have function sinature of `void setPorts(List<Logic> ports, [List<CounterDirection>? tags])` which received a List of `Logic` and tags.
quekyj marked this conversation as resolved.
Show resolved Hide resolved

Hence, the `CounterInterface` will look something like this:

```dart
enum CounterDirection { inward, outward, misc }

/// A simple [Interface] for [Counter].
class CounterInterface extends Interface<CounterDirection> {
Logic get en => port('en');
Logic get reset => port('reset');
Logic get val => port('val');
Logic get clk => port('clk');

final int width;
CounterInterface({this.width = 8}) {
setPorts([Port('en'), Port('reset')], [CounterDirection.inward]);

setPorts([
Port('val', width),
], [
CounterDirection.outward
]);

setPorts([Port('clk')], [CounterDirection.misc]);
}
}
```

Next, we want to modify the `Counter` module constructor to receive the interface. Then, we **MUST** create a new instance of the interface to avoid modify the interface inside the constructor.

```dart
// create a new interface instance.
late final CounterInterface intf;
quekyj marked this conversation as resolved.
Show resolved Hide resolved
Counter(CounterInterface intf): super('counter') {}
```

Now, let use the `connectIO()` function. As mentioned [previously](#rohd-interfaces), this function called `addInput` and `addOutput` that help us register the port. Therefore, we can pass the `module`, `interface`, `inputTags`, and `outputTags` as the arguments of the `connectIO` function.

```dart
Counter(CounterInterface intf) : super(name: 'counter') {
this.intf = CounterInterface(width: intf.width)
..connectIO(this, intf,
inputTags: {CounterDirection.inward, CounterDirection.misc},
outputTags: {CounterDirection.outward});
}
```

Yup, that all you need to use the ROHD interface. Now, let see how to do simulation or pass value to perform test with interface module.

The only different here is instead of passing the `Logic` value through constructor, we are going to instantiate the interface object and perform assignment directly through the getter function we create earlier.

```dart
Future<void> main() async {
// instantiate the counter interface
final counterInterface = CounterInterface();

// Assign SimpleClockGenerator to the clk through assesing the getter function
counterInterface.clk <= SimpleClockGenerator(10).clk;

final counter = Counter(counterInterface);
await counter.build();

// Inject value to en and reset through interface
counterInterface.en.inject(0);
counterInterface.reset.inject(1);

print(counter.generateSynth());

WaveDumper(counter,
outputPath: 'doc/tutorials/chapter_8/counter_interface.vcd');
Simulator.registerAction(25, () {
counterInterface.en.put(1);
counterInterface.reset.put(0);
});

Simulator.setMaxSimTime(100);

await Simulator.run();
}
```

That it for the ROHD interface. By using interface, you code can be a lot cleaner and readable. Hop you enjoy the tutorials. You can find the executable version of code at [counter_interface.dart](./counter_interface.dart).

## Exercise

1. Serial Peripheral Interface (SPI)

Serial Peripheral Interface (SPI) is an interface bus commonly used to send data between microcontrollers and small peripherals such as shift registers, sensors, and SD cards. It uses separate clock and data lines, along with a select line to choose the device you wish to talk to.

Build a SPI using ROHD interface. You can use the shift register as the peripheral, you can just build the unit test for peripheral.

Answer to this exercise can be found at [answers/exercise_1_spi.dart](./answers/exercise_1_spi.dart)
Loading