Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 3 additions & 3 deletions doc/components/csr.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ A CSR top is a `Module` that wraps a collection of `CsrBlock` objects, making th
The `CsrTopConfig` defines the contents of the top module. As such, it is constructed by passing a list of `CsrBlockConfig`s which defines the blocks contained within the top module. In addition to the register block configurations, the top configuration offers the following functionality:

- A name for the module (called `name`).
- An offset width that is used to slice the main address signal to address registers within a given block (called `blockOffsetWidth`).
- A block size that defines how many addresses of space live in each block (called `blockSize`).
- Methods to retrieve a given register block's configuration by name or base address (`getBlockByName` and `getBlockByAddr`).
- Validation to check for configuration correctness and consistency.
- A method `minAddrBits()` that returns the minimum number of address bits required to uniquely address every register instance in every block. The return value is based on both the largest block `baseAddr` and its largest `minAddrBits`.
Expand All @@ -220,15 +220,15 @@ The following checks are run:
- No two register blocks in the module have the same `name`.
- No two register blocks in the module have the same `baseAddr`.
- No two register blocks in the module have `baseAddr`s that are too close together such there would be an address collision. This is based on the `minAddrBits` of each block to determine how much room that block needs before the next `baseAddr`.
- The `blockOffsetWidth` must be wide enough to cover the largest `minAddrBits` across all blocks.
- The `blockSize` must be large enough to cover the largest register address in each block.

### Frontdoor CSR Access - Top

Similar to `CsrBlock`, the `CsrTop` module provides frontdoor read/write access to its blocks/registers through a read `DataPortInterface` and a write `DataPortInterface`. Each of these arguments are optional. These are passed to the module in its constructor.

To access a particular register in a particular block, drive the address of the appropriate `DataPortInterface` to the block's `baseAddr` + the register's `addr`.

In the hardware's construction, each `CsrBlock`'s `DataPortInterface` is driven by the `CsrTop`'s associated `DataPortInterface`. For the address signal, the LSBs of the `CsrTop`'s `DataPortInterface` are used per the value of `blockOffsetWidth`. All other signals are direct pass-throughs.
In the hardware's construction, each `CsrBlock`'s `DataPortInterface` is driven by the `CsrTop`'s associated `DataPortInterface`. For the address signal, the LSBs of the `CsrTop`'s `DataPortInterface` are used per the offset width derived from `blockSize`. All other signals are direct pass-throughs.

If an access drives an address that doesn't map to any block, writes are NOPs and reads return 0x0.

Expand Down
37 changes: 31 additions & 6 deletions lib/src/memory/csr/config/csr_block_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,21 @@ class CsrBlockConfig extends CsrContainerConfig {
/// Registers in this block.
final List<CsrInstanceConfig> registers;

/// Optional override for the number of addresses in this block's
/// address space.
///
/// When set, this value takes precedence over the uniform
/// [CsrTopConfig.blockSize] for this specific block, enabling
/// heterogeneous block sizes within the same top-level module. When `null`
/// (the default), the top-level [CsrTopConfig.blockSize] is used.
final int? blockSize;

/// Construct a new block configuration.
CsrBlockConfig({
required super.name,
required this.baseAddr,
required List<CsrInstanceConfig> registers,
this.blockSize,
}) : registers = List.unmodifiable(registers) {
// validate the block
_validate();
Expand Down Expand Up @@ -74,22 +84,34 @@ class CsrBlockConfig extends CsrContainerConfig {
if (issues.isNotEmpty) {
throw CsrValidationException(issues.join('\n'));
}

// if a blockSize override is provided, validate it is large enough
if (blockSize != null && blockSize! < minBlockSize()) {
throw CsrValidationException(
'Block $name has a blockSize of $blockSize which is '
'too small to address all registers. '
'The minimum block size is ${minBlockSize()}.');
}
}

/// Method to determine the minimum number of address bits
/// needed to address all registers in the block. This is
/// based on the maximum register address offset.
@override
int minAddrBits() {
/// Returns the minimum block size (number of addresses) needed to
/// cover all registers in this block.
int minBlockSize() {
var maxAddr = 0;
for (final reg in registers) {
if (reg.addr > maxAddr) {
maxAddr = reg.addr;
}
}
return maxAddr.bitLength;
return maxAddr + 1;
}

/// Method to determine the minimum number of address bits
/// needed to address all registers in the block. This is
/// based on the maximum register address offset.
@override
int minAddrBits() => (minBlockSize() - 1).bitLength;

/// Method to determine the maximum register size.
/// This is important for interface data width validation.
@override
Expand All @@ -109,6 +131,7 @@ class CsrBlockConfig extends CsrContainerConfig {
name: name,
baseAddr: baseAddr,
registers: registers,
blockSize: blockSize,
);

@override
Expand All @@ -120,6 +143,7 @@ class CsrBlockConfig extends CsrContainerConfig {
return other is CsrBlockConfig &&
super == other &&
other.baseAddr == baseAddr &&
other.blockSize == blockSize &&
const ListEquality<CsrInstanceConfig>()
.equals(other.registers, registers);
}
Expand All @@ -128,5 +152,6 @@ class CsrBlockConfig extends CsrContainerConfig {
int get hashCode =>
super.hashCode ^
baseAddr.hashCode ^
blockSize.hashCode ^
const ListEquality<CsrInstanceConfig>().hash(registers);
}
81 changes: 53 additions & 28 deletions lib/src/memory/csr/config/csr_top_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,36 @@ import 'package:rohd_hcl/src/memory/csr/config/csr_container_config.dart';
/// any conditional blocks should take place.
@immutable
class CsrTopConfig extends CsrContainerConfig {
/// Address bits dedicated to the individual registers.
/// Default number of addresses in each block's address space.
///
/// This is effectively the number of LSBs in an incoming address
/// to ignore when assessing the address of a block.
final int blockOffsetWidth;
/// Individual blocks may override this value via
/// [CsrBlockConfig.blockSize] to support heterogeneous block sizes.
final int blockSize;

/// Blocks in this module.
final List<CsrBlockConfig> blocks;

/// Construct a new top level configuration.
CsrTopConfig({
required super.name,
required this.blockOffsetWidth,
required this.blockSize,
required List<CsrBlockConfig> blocks,
}) : blocks = List.unmodifiable(blocks) {
_validate();
}

/// Returns the effective block size for [block].
///
/// If the block has its own [CsrBlockConfig.blockSize] set, that
/// value is returned; otherwise the top-level [blockSize] default
/// is used.
int blockSizeForBlock(CsrBlockConfig block) => block.blockSize ?? blockSize;

/// Returns the number of address offset bits needed to index within
/// [block], derived from the effective block size.
int blockOffsetWidthForBlock(CsrBlockConfig block) =>
(blockSizeForBlock(block) - 1).bitLength;

/// Accessor to the config of a particular register block
/// within the module by name [name].
CsrBlockConfig getBlockByName(String name) =>
Expand Down Expand Up @@ -63,13 +75,20 @@ class CsrTopConfig extends CsrContainerConfig {
// no two blocks with the same name
// no two blocks with the same base address
// no two blocks with base addresses that are too close together
// also compute the max min address bits across the blocks
// also check that each block's effective size is large enough
final issues = <String>[];
var maxMinAddrBits = 0;
for (var i = 0; i < blocks.length; i++) {
final currMaxMin = blocks[i].minAddrBits();
if (currMaxMin > maxMinAddrBits) {
maxMinAddrBits = currMaxMin;
final effectiveSizeI = blockSizeForBlock(blocks[i]);

// verify that the effective block size can address all registers
// in this block (only needs to be checked here for blocks that use the
// top-level default; blocks with their own override are validated in
// CsrBlockConfig directly)
if (blocks[i].blockSize == null &&
effectiveSizeI < blocks[i].minBlockSize()) {
issues.add('Block size $effectiveSizeI is too small to address all '
'registers in block ${blocks[i].name}. The minimum block size '
'for this block is ${blocks[i].minBlockSize()}.');
}

for (var j = i + 1; j < blocks.length; j++) {
Expand All @@ -80,31 +99,37 @@ class CsrTopConfig extends CsrContainerConfig {
if (blocks[i].baseAddr == blocks[j].baseAddr) {
issues.add(
'Register block ${blocks[i].name} has a duplicate base address.');
} else if ((blocks[i].baseAddr - blocks[j].baseAddr).abs().bitLength <
blockOffsetWidth) {
issues.add(
'Register blocks ${blocks[i].name} and ${blocks[j].name} are '
'too close together per the block offset width.');
} else {
// the block whose base address comes first in the address space
// must not bleed into the block that comes second, based on
// the first block's effective size
final effectiveSizeJ = blockSizeForBlock(blocks[j]);
final int separation;
final int firstBlockSize;
if (blocks[i].baseAddr < blocks[j].baseAddr) {
separation = blocks[j].baseAddr - blocks[i].baseAddr;
firstBlockSize = effectiveSizeI;
} else {
separation = blocks[i].baseAddr - blocks[j].baseAddr;
firstBlockSize = effectiveSizeJ;
}
if (separation < firstBlockSize) {
issues.add(
'Register blocks ${blocks[i].name} and ${blocks[j].name} are '
'too close together per their block sizes.');
}
}
}
}
if (issues.isNotEmpty) {
throw CsrValidationException(issues.join('\n'));
}

// is the block offset width big enough to address
// every register in every block
if (blockOffsetWidth < maxMinAddrBits) {
throw CsrValidationException(
'Block offset width is too small to address all register in all '
'blocks in the module. The minimum offset width is $maxMinAddrBits.');
}
}

/// Method to determine the minimum number of address bits
/// needed to address all registers across all blocks. This is
/// based on the maximum block base address. Note that we independently
/// validate the block offset width relative to the base addresses
/// validate the block size relative to the base addresses
/// so we can trust the simpler analysis here.
@override
int minAddrBits() {
Expand Down Expand Up @@ -134,12 +159,12 @@ class CsrTopConfig extends CsrContainerConfig {
@override
CsrTopConfig clone({
String? name,
int? blockOffsetWidth,
int? blockSize,
List<CsrBlockConfig>? blocks,
}) =>
CsrTopConfig(
name: name ?? this.name,
blockOffsetWidth: blockOffsetWidth ?? this.blockOffsetWidth,
blockSize: blockSize ?? this.blockSize,
blocks: blocks ?? this.blocks,
);

Expand All @@ -151,14 +176,14 @@ class CsrTopConfig extends CsrContainerConfig {

return other is CsrTopConfig &&
super == other &&
blockOffsetWidth == other.blockOffsetWidth &&
blockSize == other.blockSize &&
blocks.length == other.blocks.length &&
const ListEquality<CsrBlockConfig>().equals(blocks, other.blocks);
}

@override
int get hashCode =>
super.hashCode ^
blockOffsetWidth.hashCode ^
blockSize.hashCode ^
const ListEquality<CsrBlockConfig>().hash(blocks);
}
Loading
Loading