From e92c4e6ccf14385acf541fb9bd13de3ebf18ee3b Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Mon, 24 Feb 2025 11:51:07 -0800 Subject: [PATCH] Allowing CSR Width to Exceed Frontdoor Interface Data Width (#177) --- doc/README.md | 1 + doc/components/csr.md | 8 ++ lib/src/memory/csr/csr_block.dart | 175 +++++++++++++++++++++++++----- lib/src/memory/csr/csr_top.dart | 36 ++++-- test/csr_test.dart | 40 ++++++- 5 files changed, 221 insertions(+), 39 deletions(-) diff --git a/doc/README.md b/doc/README.md index 002fe2345..986976b3c 100644 --- a/doc/README.md +++ b/doc/README.md @@ -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) diff --git a/doc/components/csr.md b/doc/components/csr.md index eddd9fdce..a28ea7da2 100644 --- a/doc/components/csr.md +++ b/doc/components/csr.md @@ -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. @@ -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. diff --git a/lib/src/memory/csr/csr_block.dart b/lib/src/memory/csr/csr_block.dart index b756e1fc6..b7bda97e2 100644 --- a/lib/src/memory/csr/csr_block.dart +++ b/lib/src/memory/csr/csr_block.dart @@ -93,6 +93,17 @@ class CsrBlock extends Module { /// CSRs in this block. final List 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 @@ -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 = []; for (final reg in config.registers) { csrs.add(Csr(reg)); @@ -139,6 +152,8 @@ class CsrBlock extends Module { reset: reset, fdw: fdw, fdr: fdr, + allowLargerRegisters: allowLargerRegisters, + logicalRegisterIncrement: logicalRegisterIncrement, ); } @@ -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(); @@ -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 = []; + 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()}.'); @@ -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()}.'); @@ -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 = {}; + 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, @@ -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([ @@ -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 = []; + 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, diff --git a/lib/src/memory/csr/csr_top.dart b/lib/src/memory/csr/csr_top.dart index 313f14200..802804857 100644 --- a/lib/src/memory/csr/csr_top.dart +++ b/lib/src/memory/csr/csr_top.dart @@ -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(); @@ -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._({ @@ -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(); @@ -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++) { @@ -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()}.'); @@ -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)}.'); diff --git a/test/csr_test.dart b/test/csr_test.dart index 0e59c368c..334bbb33a 100644 --- a/test/csr_test.dart +++ b/test/csr_test.dart @@ -95,6 +95,13 @@ class MyRegisterBlock extends CsrBlockConfig { addr: i + 1, width: csrWidth, name: 'csr2_$i')); } } + + // adding a register on the fly + // with a larger width for potential frontdoor excess + registers.add(CsrInstanceConfig( + arch: CsrConfig(name: 'csr3', access: CsrAccess.readWrite), + addr: numNoFieldCsrs + 2, + width: csrWidth * 2)); } } @@ -138,7 +145,7 @@ class DummyCsrTopModule extends Module { _reset = addInput('reset', reset); _fdr = DataPortInterface(32, 32); _fdw = DataPortInterface(32, 32); - _top = CsrTop(config, _clk, _reset, _fdw, _fdr); + _top = CsrTop(config, _clk, _reset, _fdw, _fdr, allowLargerRegisters: true); } } @@ -209,7 +216,8 @@ void main() { final reset = Logic()..put(0); final wIntf = DataPortInterface(csrWidth, 8); final rIntf = DataPortInterface(csrWidth, 8); - final csrBlock = CsrBlock(csrBlockCfg, clk, reset, wIntf, rIntf); + final csrBlock = CsrBlock(csrBlockCfg, clk, reset, wIntf, rIntf, + allowLargerRegisters: true); wIntf.en.put(0); wIntf.addr.put(0); @@ -238,6 +246,7 @@ void main() { // grab pointers to the CSRs final csr1 = csrBlock.getRegisterByName('csr1'); final csr2 = csrBlock.getRegisterByAddr(0x2); + final csr3 = csrBlock.getRegisterByName('csr3'); // perform a reset reset.inject(1); @@ -287,6 +296,30 @@ void main() { expect(rIntf.data.value, LogicValue.ofInt(0xad00f3, rIntf.dataWidth)); await clk.waitCycles(10); + // perform a write of the top half of csr3 + // then a read of the bottom half + // ensure that the bottom half is unchanged + await clk.nextNegedge; + wIntf.en.inject(1); + wIntf.addr.inject(csr3.addr + csrBlock.logicalRegisterIncrement); + wIntf.data.inject(0xdeadbeef); + await clk.nextNegedge; + wIntf.en.inject(0); + rIntf.en.inject(1); + rIntf.addr.inject(csr3.addr); + await clk.nextNegedge; + rIntf.en.inject(0); + expect( + rIntf.data.value, LogicValue.ofInt(csr3.resetValue, rIntf.dataWidth)); + await clk.nextNegedge; + wIntf.en.inject(0); + rIntf.en.inject(1); + rIntf.addr.inject(csr3.addr + csrBlock.logicalRegisterIncrement); + await clk.nextNegedge; + rIntf.en.inject(0); + expect(rIntf.data.value, LogicValue.ofInt(0xdeadbeef, rIntf.dataWidth)); + await clk.waitCycles(10); + // perform a read of nothing await clk.nextNegedge; rIntf.en.inject(1); @@ -325,7 +358,8 @@ void main() { final reset = Logic()..inject(0); final wIntf = DataPortInterface(csrWidth, 32); final rIntf = DataPortInterface(csrWidth, 32); - final csrTop = CsrTop(csrTopCfg, clk, reset, wIntf, rIntf); + final csrTop = + CsrTop(csrTopCfg, clk, reset, wIntf, rIntf, allowLargerRegisters: true); wIntf.en.inject(0); wIntf.addr.inject(0);