Skip to content

Commit

Permalink
Allowing CSR Width to Exceed Frontdoor Interface Data Width (#177)
Browse files Browse the repository at this point in the history
  • Loading branch information
kimmeljo authored Feb 24, 2025
1 parent b18521d commit e92c4e6
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 39 deletions.
1 change: 1 addition & 0 deletions doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ Some in-development items will have opened issues, as well. Feel free to create
- Replacement Policies
- LRU
- [Memory Model](./components/memory.md#memory-models)
- [Control/Status Registers (CSRs)](./components/csr.md)
- Standard interfaces
- AXI
- [APB](./components/standard_interfaces.md#apb)
Expand Down
8 changes: 8 additions & 0 deletions doc/components/csr.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,12 @@ If a given register is configured as not frontdoor readable, there is no hardwar

On module build, the width of the address signal on both `DataPortInterface`s is checked to ensure that it is at least as wide as the block's `minAddrBits`. On module build, the width of the input and output data signals on the `DataPortInterface`s are checked to ensure that they are at least as wide as the block's `maxRegWidth`.

#### Larger Width Registers in a Block

There is a special mode called `allowLargerRegisters` where any register in the block can have a width that exceeds the width of the frontdoor interface data. In this case, HW generation will assign multiple logical addresses to the given register. Each logical address points to a contiguous chunk of bits in the register. Each chunk is at most the width of the frontdoor data, but can be less (if the register width is not evenly divisible by the frontdoor data width, the last chunk will be smaller). The number of logical addresses (chunks) is `ceil(register.width / frontdoor.data.width)`. The 0th chunk contains the LSBs of the register and has a logical address of `register.address` as defined in its config object. Each subsequent chunk contains the next LSBs of the register and has a logical address of `prior_chunk_address + logicalRegisterIncrement` where `logicalRegisterIncrement` is another construction parameter (with a default value of 1).

Note that there is additional validation to ensure that the implicit addresses "added" by this mechanism do not overlap with any other existing address in the block.

### Backdoor CSR Access - Block

The `CsrBlock` module provides backdoor read/write access to its registers through a `CsrBackdoorInterface`. One interface is instantiated per register that is backdoor accessible in the block and ported out of the module on build.
Expand Down Expand Up @@ -230,6 +236,8 @@ If an access drives an address that doesn't map to any block, writes are NOPs an

On module build, the width of the address signal on both `DataPortInterface`s is checked to ensure that it is at least as wide as the module's `minAddrBits`. On module build, the width of the input and output data signals on the `DataPortInterface`s are checked to ensure that they are at least as wide as the module's `maxRegWidth`.

Note that the same parameters `allowLargerRegisters` and `logicalRegisterIncrement` that are found in `CsrBlock` can be passed at the top and propagated down to all blocks within the module.

### Backdoor CSR Access - Top

The `CsrTop` module provides backdoor read/write access to its blocks' registers through a `CsrBackdoorInterface`. One interface is instantiated per register in every block that is backdoor accessible and ported out of the module on build.
Expand Down
175 changes: 150 additions & 25 deletions lib/src/memory/csr/csr_block.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@ class CsrBlock extends Module {
/// CSRs in this block.
final List<Csr> csrs;

/// Is it legal for the largest register width to be
/// greater than the data width of the frontdoor interfaces.
///
/// If this is true, HW generation must assign multiple addresses
/// to any register that exceeds the data width of the frontdoor.
final bool allowLargerRegisters;

/// What increment value to use when deriving logical addresses
/// for registers that are wider than the frontdoor data width.
final int logicalRegisterIncrement;

/// Direct access ports for reading and writing individual registers.
///
/// There is a public copy that is exported out of the module
Expand Down Expand Up @@ -126,8 +137,10 @@ class CsrBlock extends Module {
Logic clk,
Logic reset,
DataPortInterface fdw,
DataPortInterface fdr,
) {
DataPortInterface fdr, {
bool allowLargerRegisters = false,
int logicalRegisterIncrement = 1,
}) {
final csrs = <Csr>[];
for (final reg in config.registers) {
csrs.add(Csr(reg));
Expand All @@ -139,6 +152,8 @@ class CsrBlock extends Module {
reset: reset,
fdw: fdw,
fdr: fdr,
allowLargerRegisters: allowLargerRegisters,
logicalRegisterIncrement: logicalRegisterIncrement,
);
}

Expand All @@ -150,6 +165,8 @@ class CsrBlock extends Module {
required Logic reset,
required DataPortInterface fdw,
required DataPortInterface fdr,
this.allowLargerRegisters = false,
this.logicalRegisterIncrement = 1,
}) : _config = config.clone(),
super(name: config.name) {
_config.validate();
Expand Down Expand Up @@ -226,11 +243,36 @@ class CsrBlock extends Module {
// address width must be at least wide enough
// to address all registers in the block
if (_frontRead.dataWidth < _config.maxRegWidth()) {
throw CsrValidationException(
'Frontdoor read interface data width must be '
'at least ${_config.maxRegWidth()}.');
if (allowLargerRegisters) {
// must check for collisions in logical register addresses
final regCheck = <int>[];
for (final csr in csrs) {
if (csr.config.width > _frontRead.dataWidth) {
final targ = (csr.width / _frontRead.dataWidth).ceil();

for (var j = 0; j < targ; j++) {
regCheck.add(csr.addr + j * logicalRegisterIncrement);
}
} else {
regCheck.add(csr.addr);
}
}
if (regCheck.length != regCheck.toSet().length) {
throw CsrValidationException(
'There is at least one collision across logical register '
'addresses due to some registers being wider than the '
'frontdoor data width. Note that each logical address '
'has a +$logicalRegisterIncrement increment to '
'the original address.');
}
} else {
throw CsrValidationException(
'Frontdoor read interface data width must be '
'at least ${_config.maxRegWidth()}.');
}
}
if (_frontWrite.dataWidth < _config.maxRegWidth()) {
if (_frontWrite.dataWidth < _config.maxRegWidth() &&
!allowLargerRegisters) {
throw CsrValidationException(
'Frontdoor write interface data width must be '
'at least ${_config.maxRegWidth()}.');
Expand All @@ -240,7 +282,7 @@ class CsrBlock extends Module {
'Frontdoor read interface address width must be '
'at least ${_config.minAddrBits()}.');
}
if (_frontWrite.dataWidth < _config.minAddrBits()) {
if (_frontWrite.addrWidth < _config.minAddrBits()) {
throw CsrValidationException(
'Frontdoor write interface address width must be '
'at least ${_config.minAddrBits()}.');
Expand All @@ -249,9 +291,71 @@ class CsrBlock extends Module {

void _buildLogic() {
final addrWidth = _frontWrite.addrWidth;
final dataWidth = _frontWrite.dataWidth;

// individual CSR write logic
for (var i = 0; i < csrs.length; i++) {
// this block of code mostly handles the case where
// the register is wider than the data width of the frontdoor
// which is only permissible if [allowLargerRegisters] is true.
Logic addrCheck;
Logic dataToWrite;
if (dataWidth < csrs[i].config.width) {
final rem = csrs[i].config.width % dataWidth;
final targ = (csrs[i].config.width / dataWidth).ceil();

// must logically separate the register out across multiple addresses
final addrs = List.generate(
targ,
(j) => Const(csrs[i].addr + j * logicalRegisterIncrement,
width: addrWidth));
addrCheck = _frontWrite.addr.isIn(addrs);

// we write the portion of the register that
// corresponds to this logical address
final wrCases = <Logic, Logic>{};
for (var j = 0; j < targ; j++) {
final key = Const(csrs[i].addr + j * logicalRegisterIncrement,
width: addrWidth);
if (j == targ - 1) {
// might need to truncate the data on the interface
// for the last chunk depending on divisibility
wrCases[key] = csrs[i].withSet(
j * dataWidth,
csrs[i]
.getWriteData([
if (j * dataWidth > 0) csrs[i].getRange(0, j * dataWidth),
_frontWrite.data.getRange(0, rem == 0 ? dataWidth : rem)
].rswizzle())
.getRange(j * dataWidth,
rem == 0 ? (j + 1) * dataWidth : j * dataWidth + rem));
} else {
// no truncation needed
wrCases[key] = csrs[i].withSet(
j * dataWidth,
csrs[i]
.getWriteData([
if (j * dataWidth > 0) csrs[i].getRange(0, j * dataWidth),
_frontWrite.data,
if ((j + 1) * dataWidth < csrs[i].config.width)
csrs[i].getRange((j + 1) * dataWidth),
].rswizzle())
.getRange(j * dataWidth, (j + 1) * dataWidth));
}
}
dataToWrite = cases(
_frontWrite.addr,
conditionalType: ConditionalType.unique,
wrCases,
defaultValue: csrs[i]);
} else {
// direct address check
// direct application of write data
addrCheck = _frontWrite.addr.eq(Const(csrs[i].addr, width: addrWidth));
dataToWrite = csrs[i]
.getWriteData(_frontWrite.data.getRange(0, csrs[i].config.width));
}

Sequential(
_clk,
reset: _reset,
Expand All @@ -262,22 +366,14 @@ class CsrBlock extends Module {
If.block([
// frontdoor write takes highest priority
if (_config.registers[i].isFrontdoorWritable)
ElseIf(
_frontWrite.en &
_frontWrite.addr
.eq(Const(csrs[i].addr, width: addrWidth)),
[
csrs[i] <
csrs[i].getWriteData(
_frontWrite.data.getRange(0, csrs[i].config.width)),
]),
ElseIf(_frontWrite.en & addrCheck, [
csrs[i] < dataToWrite,
]),
// backdoor write takes next priority
if (_backdoorIndexMap.containsKey(i) &&
_backdoorInterfaces[_backdoorIndexMap[i]!].hasWrite)
ElseIf(_backdoorInterfaces[_backdoorIndexMap[i]!].wrEn!, [
csrs[i] <
csrs[i].getWriteData(
_backdoorInterfaces[_backdoorIndexMap[i]!].wrData!),
csrs[i] < dataToWrite,
]),
// nothing to write this cycle
Else([
Expand All @@ -290,12 +386,41 @@ class CsrBlock extends Module {

// individual CSR read logic
final rdData = Logic(name: 'internalRdData', width: _frontRead.dataWidth);
final rdCases = csrs
.where((csr) => csr.isFrontdoorReadable)
.map((csr) => CaseItem(Const(csr.addr, width: addrWidth), [
rdData < csr.zeroExtend(_frontRead.dataWidth),
]))
.toList();
final rdCases = <CaseItem>[];
for (final csr in csrs) {
if (csr.isFrontdoorReadable) {
if (dataWidth < csr.config.width) {
final rem = csr.width % dataWidth;
final targ = (csr.width / dataWidth).ceil();

// must further examine logical registers
// and capture the correct logical chunk
for (var j = 0; j < targ; j++) {
final rngEnd = j == targ - 1
? rem == 0
? _frontRead.dataWidth
: rem
: _frontRead.dataWidth;
rdCases.add(CaseItem(
Const(csr.addr + j * logicalRegisterIncrement,
width: addrWidth),
[
rdData <
csr
.getRange(_frontRead.dataWidth * j,
_frontRead.dataWidth * j + rngEnd)
.zeroExtend(_frontRead.dataWidth),
]));
}
} else {
// normal capture of the register data
rdCases.add(CaseItem(Const(csr.addr, width: addrWidth), [
rdData < csr.zeroExtend(_frontRead.dataWidth),
]));
}
}
}

Combinational([
Case(
_frontRead.addr,
Expand Down
36 changes: 25 additions & 11 deletions lib/src/memory/csr/csr_top.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ class CsrTop extends Module {
// private config object
final CsrTopConfig _config;

/// Is it legal for the largest register width to be
/// greater than the data width of the frontdoor interfaces.
///
/// If this is true, HW generation must assign multiple addresses
/// to any register that exceeds the data width of the frontdoor.
final bool allowLargerRegisters;

/// What increment value to use when deriving logical addresses
/// for registers that are wider than the frontdoor data width.
final int logicalRegisterIncrement;

/// Configuration for the CSR Top module.
CsrTopConfig get config => _config.clone();

Expand Down Expand Up @@ -95,19 +106,18 @@ class CsrTop extends Module {
}

/// create the CsrBlock from a configuration
factory CsrTop(
CsrTopConfig config,
Logic clk,
Logic reset,
DataPortInterface fdw,
DataPortInterface fdr,
) =>
factory CsrTop(CsrTopConfig config, Logic clk, Logic reset,
DataPortInterface fdw, DataPortInterface fdr,
{bool allowLargerRegisters = false,
int logicalRegisterIncrement = 1}) =>
CsrTop._(
config: config,
clk: clk,
reset: reset,
fdw: fdw,
fdr: fdr,
allowLargerRegisters: allowLargerRegisters,
logicalRegisterIncrement: logicalRegisterIncrement,
);

CsrTop._({
Expand All @@ -116,6 +126,8 @@ class CsrTop extends Module {
required Logic reset,
required DataPortInterface fdw,
required DataPortInterface fdr,
this.allowLargerRegisters = false,
this.logicalRegisterIncrement = 1,
}) : _config = config.clone(),
super(name: config.name) {
_config.validate();
Expand All @@ -139,7 +151,8 @@ class CsrTop extends Module {
for (final block in _config.blocks) {
_fdWrites.add(DataPortInterface(fdw.dataWidth, blockOffsetWidth));
_fdReads.add(DataPortInterface(fdr.dataWidth, blockOffsetWidth));
_blocks.add(CsrBlock(block, _clk, _reset, _fdWrites.last, _fdReads.last));
_blocks.add(CsrBlock(block, _clk, _reset, _fdWrites.last, _fdReads.last,
allowLargerRegisters: allowLargerRegisters));
}

for (var i = 0; i < blocks.length; i++) {
Expand Down Expand Up @@ -181,12 +194,13 @@ class CsrTop extends Module {
// the biggest register across all blocks
// address width must be at least wide enough
//to address all registers in all blocks
if (_frontRead.dataWidth < _config.maxRegWidth()) {
if (_frontRead.dataWidth < _config.maxRegWidth() && !allowLargerRegisters) {
throw CsrValidationException(
'Frontdoor read interface data width must be '
'at least ${_config.maxRegWidth()}.');
}
if (_frontWrite.dataWidth < _config.maxRegWidth()) {
if (_frontWrite.dataWidth < _config.maxRegWidth() &&
!allowLargerRegisters) {
throw CsrValidationException(
'Frontdoor write interface data width must be '
'at least ${_config.maxRegWidth()}.');
Expand All @@ -197,7 +211,7 @@ class CsrTop extends Module {
'Frontdoor read interface address width must be '
'at least ${max(_config.minAddrBits(), blockOffsetWidth)}.');
}
if (_frontWrite.dataWidth < _config.minAddrBits()) {
if (_frontWrite.addrWidth < _config.minAddrBits()) {
throw CsrValidationException(
'Frontdoor write interface address width must be '
'at least ${max(_config.minAddrBits(), blockOffsetWidth)}.');
Expand Down
Loading

0 comments on commit e92c4e6

Please sign in to comment.