Skip to content

Commit

Permalink
APB BFM (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
mkorbel1 authored Sep 27, 2023
1 parent a062b37 commit 2fa69a1
Show file tree
Hide file tree
Showing 19 changed files with 1,087 additions and 34 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Some examples of component categories include:
- Data flow
- Memory
- Standard interfaces
- Models

----------------

Expand Down
2 changes: 2 additions & 0 deletions doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ Some in-development items will have opened issues, as well. Feel free to create
- UART
- DDR
- HBM
- Models
- [APB](./components/apb_bfm.md)

----------------

Expand Down
20 changes: 20 additions & 0 deletions doc/components/apb_bfm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# APB BFM

The APB BFM is a collection of [ROHD-VF](https://github.com/intel/rohd-vf) components and objects that are helpful for validating hardware that contains an APB interface. It includes all the basic APB interface features for sending and responding to reads and writes, including with strobes and errors.

The main two components are the `ApbRequesterAgent` and the `ApbCompleterAgent`, which behave like a "requester" and "completer" as described in the APB spec, respectively. The `ApbRequesterAgent` has a standard `Sequencer` that accepts `ApbPacket`s to be driven out to the completer. The `ApbCompleterAgent` has default behavior and accepts a `MemoryStorage` instance as a memory model. See the API docs for more details on how to use each of these components, which both have substantial configurability to control behavior.

A `ApbMonitor` is also included, which implements the standard `Monitor` and provides a stream of `ApbPacket`s monitored on positive edges of the clock. The `ApbTracker` can be used to log all items detected by the monitor by implementing the standard `Tracker` API (log file or JSON both supported).

Finally, a `ApbComplianceChecker` monitors an `ApbInterface` for a subset of the rules described in the APB specification. Errors are flagged using the `severe` log messages, as is standard for errors in ROHD-VF.

The unit tests in `apb_test.dart`, which have a completer and requester communicating with each other, are a good example for setting up the APB BFM.

## Unsupported features

The following features are not supported by or have no utilities within the BFM:

- **Wake-up signalling**: wake-up features are not considered.
- **Protection**: protection features are not considered.
- **User requests and responses**: these signals are un-driven and not monitored by the BFM.
- **Retry on error**: it is up to the user of the BFM to write any error handling logic.
1 change: 1 addition & 0 deletions lib/rohd_hcl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ export 'src/ripple_carry_adder.dart';
export 'src/rotate.dart';
export 'src/shift_register.dart';
export 'src/sort.dart';
export 'src/utils.dart';
14 changes: 4 additions & 10 deletions lib/src/fifo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

import 'package:rohd/rohd.dart';
import 'package:rohd_hcl/rohd_hcl.dart';
import 'package:rohd_hcl/src/utils.dart';
import 'package:rohd_vf/rohd_vf.dart';

/// A simple FIFO (First In, First Out).
Expand Down Expand Up @@ -327,24 +326,19 @@ class FifoTracker extends Tracker {
columnWidth: fifo.dataWidth ~/ 4 + log2Ceil(fifo.dataWidth) + 1),
TrackerField('Occupancy', columnWidth: fifo.depth ~/ 10 + 1),
]) {
LogicValue? prevReadValue;
Simulator.preTick.listen((event) {
prevReadValue = fifo.readData.value;
});

fifo._clk.posedge.listen((event) {
if (fifo._writeEnable.value.toBool()) {
if (fifo._writeEnable.previousValue!.toBool()) {
record(_FifoEvent(
_FifoCmd.wr,
fifo._writeData.value,
fifo._writeData.previousValue!,
++_occupancy,
));
}

if (fifo._readEnable.value.toBool()) {
if (fifo._readEnable.previousValue!.toBool()) {
record(_FifoEvent(
_FifoCmd.rd,
prevReadValue!,
fifo.readData.previousValue!,
--_occupancy,
));
}
Expand Down
53 changes: 42 additions & 11 deletions lib/src/interfaces/apb.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,31 @@
// 2023 May 19
// Author: Max Korbel <max.korbel@intel.com>

import 'dart:collection';

import 'package:rohd/rohd.dart';
import 'package:rohd_hcl/src/exceptions.dart';

/// A grouping of signals on the [Apb] interface based on direction.
/// A grouping of signals on the [ApbInterface] interface based on direction.
enum ApbDirection {
/// Miscellaneous system-level signals, common inputs to both sides.
misc,

/// Signals driven by the requester.
/// Signals driven by the requester (including select signals).
fromRequester,

/// Signals driven by the requester except for select signals.
///
/// This can be helpful if a completer needs to selectively listen to one
/// bit of the select signals.
fromRequesterExceptSelect,

/// Signals driven by the completer.
fromCompleter
}

/// A standard APB interface.
class Apb extends Interface<ApbDirection> {
class ApbInterface extends Interface<ApbDirection> {
/// The width of address port [addr].
///
/// Equvalent to the `ADDR_WIDTH` parameter in the APB specification.
Expand Down Expand Up @@ -95,7 +103,9 @@ class Apb extends Interface<ApbDirection> {
/// The Requester generates a select signal for each Completer. Select
/// indicates that the Completer is selected and that a data transfer is
/// required.
Logic get selX => port('PSELx');
late final List<Logic> sel = UnmodifiableListView(List.generate(
numSelects, (index) => port('PSEL$index'),
growable: false));

/// Enable.
///
Expand Down Expand Up @@ -149,7 +159,9 @@ class Apb extends Interface<ApbDirection> {
/// Wake-up.
///
/// Indicates any activity associated with an APB interface.
Logic get wakeup => port('PWAKEUP');
///
/// Only generated if [includeWakeup] is `true`.
Logic? get wakeup => includeWakeup ? port('PWAKEUP') : null;

/// User request attribute.
///
Expand All @@ -171,14 +183,27 @@ class Apb extends Interface<ApbDirection> {
/// Width equal to [userRespWidth]. Only exists if [userRespWidth] > 0.
Logic? get bUser => userRespWidth != 0 ? port('PBUSER') : null;

/// The number of select signals on this interface.
///
/// A completer should always set this to `1`, but a requester may have more
/// than `1`.
final int numSelects;

/// If `true`, indicates that this interface supports wake-up.
///
/// The interface will only include [wakeup] if this is `true`.
final bool includeWakeup;

/// Construct a new instance of an APB interface.
Apb({
ApbInterface({
this.addrWidth = 32,
this.dataWidth = 32,
this.userReqWidth = 0,
this.userDataWidth = 0,
this.userRespWidth = 0,
this.includeSlvErr = false,
this.includeWakeup = false,
this.numSelects = 1,
}) {
_validateParameters();

Expand All @@ -193,21 +218,27 @@ class Apb extends Interface<ApbDirection> {
Port('PADDR', addrWidth),
Port('PPROT', 3),
Port('PNSE'),
Port('PSELx'),
Port('PENABLE'),
Port('PWRITE'),
Port('PWDATA', dataWidth),
Port('PSTRB', dataWidth ~/ 8),
if (userReqWidth != 0) Port('PAUSER', userReqWidth),
if (userDataWidth != 0) Port('PWUSER', userDataWidth),
], [
ApbDirection.fromRequester
ApbDirection.fromRequester,
ApbDirection.fromRequesterExceptSelect,
]);

setPorts([
for (var i = 0; i < numSelects; i++) Port('PSEL$i'),
], [
ApbDirection.fromRequester,
]);

setPorts([
Port('PREADY'),
Port('PRDATA', dataWidth),
Port('PSLVERR'),
if (includeSlvErr) Port('PSLVERR'),
Port('PWAKEUP'),
if (userDataWidth != 0) Port('PRUSER', userDataWidth),
if (userRespWidth != 0) Port('PBUSER', userRespWidth),
Expand All @@ -216,8 +247,8 @@ class Apb extends Interface<ApbDirection> {
]);
}

/// Constructs a new [Apb] with identical parameters to [other].
Apb.clone(Apb other)
/// Constructs a new [ApbInterface] with identical parameters to [other].
ApbInterface.clone(ApbInterface other)
: this(
addrWidth: other.addrWidth,
dataWidth: other.dataWidth,
Expand Down
168 changes: 168 additions & 0 deletions lib/src/models/apb_bfm/abp_completer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Copyright (C) 2023 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause
//
// apb_completer.dart
// An agent for completing APB requests.
//
// 2023 June 12
// Author: Max Korbel <max.korbel@intel.com>

import 'dart:async';

import 'package:rohd/rohd.dart';
import 'package:rohd_hcl/rohd_hcl.dart';
import 'package:rohd_vf/rohd_vf.dart';

/// A model for the completer side of an [ApbInterface].
class ApbCompleterAgent extends Agent {
/// The interface to drive.
final ApbInterface intf;

/// The index that this is listening to on the [intf].
final int selectIndex;

/// A place where the completer should save and retrieve data.
///
/// The [ApbCompleterAgent] will reset [storage] whenever the `resetN` signal
/// is dropped.
final MemoryStorage storage;

/// A function which delays the response for the given `request`.
///
/// If none is provided, then the delay will always be `0`.
final int Function(ApbPacket request)? responseDelay;

/// A function that determines whether a response for a request should contain
/// an error (`slvErr`).
///
/// If none is provided, it will always respond with no error.
final bool Function(ApbPacket request)? respondWithError;

/// If true, then returned data on an error will be `x`.
final bool invalidReadDataOnError;

/// If true, then writes that respond with an error will not store into the
/// [storage].
final bool dropWriteDataOnError;

/// Creates a new model [ApbCompleterAgent].
ApbCompleterAgent(
{required this.intf,
required this.storage,
required Component parent,
this.selectIndex = 0,
this.responseDelay,
this.respondWithError,
this.invalidReadDataOnError = true,
this.dropWriteDataOnError = true,
String name = 'apbCompleter'})
: super(name, parent);

@override
Future<void> run(Phase phase) async {
unawaited(super.run(phase));

intf.resetN.negedge.listen((event) {
storage.reset();
});

_respond(ready: false);

// wait for reset to complete
await intf.resetN.nextPosedge;

while (!Simulator.simulationHasEnded) {
await _receive();
}
}

/// Calculates a strobed version of data.
LogicValue _strobeData(
LogicValue originalData, LogicValue newData, LogicValue strobe) =>
[
for (var i = 0; i < strobe.width; i++)
(strobe[i].toBool() ? newData : originalData)
.getRange(i * 8, i * 8 + 8)
].rswizzle();

/// Receives one packet (or returns if not selected).
Future<void> _receive() async {
await intf.enable.nextPosedge;

if (!intf.sel[selectIndex].value.toBool()) {
// we're not selected, wait for the next time
return;
}

ApbPacket packet;
if (intf.write.value.toBool()) {
packet = ApbWritePacket(
addr: intf.addr.value,
data: intf.wData.value,
strobe: intf.strb.value,
);
} else {
packet = ApbReadPacket(addr: intf.addr.value);
}

if (responseDelay != null) {
final delayCycles = responseDelay!(packet);
if (delayCycles > 0) {
await intf.clk.waitCycles(delayCycles);
}
}

if (packet is ApbWritePacket) {
final writeError = respondWithError != null && respondWithError!(packet);

// store the data
if (!(writeError && dropWriteDataOnError)) {
storage.writeData(
packet.addr,
packet.strobe.and().toBool() // don't `readData` if all 1's
? packet.data
: _strobeData(
storage.readData(packet.addr),
packet.data,
packet.strobe,
),
);
}

_respond(
ready: true,
error: writeError,
);
} else if (packet is ApbReadPacket) {
// capture the data
_respond(
ready: true,
data: storage.readData(packet.addr),
error: respondWithError != null && respondWithError!(packet),
);
}

// drop the ready when enable drops
await intf.enable.nextNegedge;
_respond(ready: false);
}

/// Sets up response signals for the completer (including using inject).
void _respond({required bool ready, bool? error, LogicValue? data}) {
Simulator.injectAction(() {
intf.ready.put(ready);

if (error == null) {
intf.slvErr?.put(LogicValue.x);
} else {
intf.slvErr?.put(error);
}

if (data == null || ((error ?? false) && invalidReadDataOnError)) {
intf.rData.put(LogicValue.x);
} else {
intf.rData.put(data);
}
});
}
}
10 changes: 10 additions & 0 deletions lib/src/models/apb_bfm/apb_bfm.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (C) 2023 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause

export 'abp_completer.dart';
export 'apb_compliance_checker.dart';
export 'apb_monitor.dart';
export 'apb_packet.dart';
export 'apb_requester.dart';
export 'apb_requester_driver.dart';
export 'apb_tracker.dart';
Loading

0 comments on commit 2fa69a1

Please sign in to comment.