From 151da010e9ca3f10bceaf32d75c1d70e3460685c Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Fri, 20 Dec 2024 17:04:14 -0800 Subject: [PATCH 01/14] WIP: starting generic CSR implementation. Still some work to do... --- lib/src/memory/csr.dart | 467 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 467 insertions(+) create mode 100644 lib/src/memory/csr.dart diff --git a/lib/src/memory/csr.dart b/lib/src/memory/csr.dart new file mode 100644 index 000000000..a0662089a --- /dev/null +++ b/lib/src/memory/csr.dart @@ -0,0 +1,467 @@ +// Copyright (C) 2023-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// csr.dart +// A flexible definition of CSRs. +// +// 2024 December +// Author: Josh Kimmel + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; + +/// definitions for various register field access patterns +enum CsrFieldAccess { + /// register field is read only + FIELD_READ_ONLY, + + /// register field can be read and written + FIELD_READ_WRITE, + + /// writing 1's to the field triggers some other action + /// but the field itself is read only + FIELD_W1C, +} + +/// definitions for various register access patterns +enum CsrAccess { + /// register field is read only + REGISTER_READ_ONLY, + + /// register field can be read and written + REGISTER_READ_WRITE, +} + +/// configuration for a register field +class CsrFieldConfig { + /// starting bit position of the field in the register + final int start; + + /// number of bits in the field + final int width; + + /// name for the field + final String name; + + /// access rules for the field + final CsrFieldAccess access; + + /// construct a new field configuration + CsrFieldConfig(this.start, this.width, this.name, this.access); +} + +/// configuration for a register +class CsrConfig { + /// register's address within its block + final int addr; + + /// number of bits in the register + final int width; + + /// name for the register + final String name; + + /// access rules for the register + final CsrAccess access; + + /// reset value for the register + final int resetValue; + + /// fields in this register + final List fields = []; + + /// construct a new register configuration + CsrConfig(this.addr, this.width, this.name, this.access, this.resetValue); + + /// add a register field to this register + void addField(CsrFieldConfig field) { + fields.add(field); + } +} + +/// definition for a coherent block of registers +class CsrBlockConfig { + /// name for the block + final String name; + + /// address of the first register in the block + final int baseAddr; + + /// registers in this block + final List registers = []; + + /// construct a new block configuration + CsrBlockConfig( + this.name, + this.baseAddr, + ); + + /// add a register to this block + void addRegister(CsrConfig register) { + registers.add(register); + } +} + +/// definition for a top level module containing CSR blocks +class CsrTopConfig { + /// name for the top module + final String name; + + /// blocks in this module + final List blocks = []; + + /// construct a new top level configuration + CsrTopConfig( + this.name, + ); + + /// add a block to this module + void addBlock(CsrBlockConfig block) { + blocks.add(block); + } +} + +/// Logic representation of a CSR +class Csr extends LogicStructure { + /// bit width of the CSR + @override + final int csrWidth; + + /// address for the CSR + final int addr; + + /// reset value for the CSR + final int resetValue; + + /// access control for the CSR + final CsrAccess access; + + /// CSR fields + final List fields; + + /// access control for each field + final List fieldAccess; + + Csr._({ + required super.name, + required this.csrWidth, + required this.addr, + required this.resetValue, + required this.access, + required this.fields, + required this.fieldAccess, + }) : super(fields); + + factory Csr( + CsrConfig config, + ) { + final fields = []; + var currIdx = 0; + var rsvdCount = 0; + final acc = []; + + // semantically a register with no fields means that + // there is one read/write field that is the entire register + if (config.fields.isEmpty) { + fields.add(Logic(name: '${config.name}_data', width: config.width)); + acc.add(CsrFieldAccess.FIELD_READ_WRITE); + } + // there is at least one field explicitly defined so + // process them individually + else { + for (final field in config.fields) { + if (field.start > currIdx) { + fields.add(Logic( + name: '${config.name}_rsvd_$rsvdCount', + width: field.start - currIdx)); + acc.add(CsrFieldAccess.FIELD_READ_ONLY); + } + fields.add( + Logic(name: '${config.name}_${field.name}', width: field.width)); + acc.add(field.access); + currIdx = field.start + field.width; + } + if (currIdx < config.width) { + fields.add(Logic( + name: '${config.name}_rsvd_$rsvdCount', + width: config.width - currIdx)); + acc.add(CsrFieldAccess.FIELD_READ_ONLY); + } + } + return Csr._( + name: config.name, + csrWidth: config.width, + addr: config.addr, + resetValue: config.resetValue, + access: config.access, + fields: fields, + fieldAccess: acc, + ); + } + + /// extract the address as a Const + Logic getAddr(int addrWidth) => Const(LogicValue.ofInt(addr, addrWidth)); + + /// extract the reset value as a Const + Logic getResetValue() => Const(LogicValue.ofInt(resetValue, width)); + + /// method to return an individual by name + Logic getField(String nm) => + fields.firstWhere((element) => element.name == '${name}_$nm'); + + /// perform mapping of original write data based on access config + Logic getWriteData(Logic wd) { + // if the whole register is ready only, return the current value + if (access == CsrAccess.REGISTER_READ_ONLY) { + return this; + } + // register can be written, but still need to look at the fields... + else { + var finalWd = wd; + var currIdx = 0; + for (var i = 0; i < fields.length; i++) { + // if the given field is read only, take the current value + // instead of the new value + final chk = fieldAccess[i] == CsrFieldAccess.FIELD_READ_ONLY || + fieldAccess[i] == CsrFieldAccess.FIELD_W1C; + if (chk) { + finalWd = finalWd.withSet(currIdx, fields[i]); + } + currIdx += fields[i].width; + } + return finalWd; + } + } +} + +/// a submodule representing a block of CSRs +class CsrBlock extends Module { + /// address for this block + final int addr; + + /// width of address in bits + final int addrWidth; + + /// CSRs in this block + final List csrs; + + /// clk for the module + late final Logic clk; + + /// reset for the module + late final Logic reset; + + /// interface for frontdoor writes to CSRs + late final DataPortInterface frontWrite; + + /// interface for frontdoor reads to CSRs + late final DataPortInterface frontRead; + + // TODO: how to deal with backdoor writes?? + + CsrBlock._({ + required super.name, + required this.addr, + required this.csrs, + required Logic clk, + required Logic reset, + required DataPortInterface fdw, + required DataPortInterface fdr, + }) : addrWidth = fdw.addrWidth { + clk = addInput('clk', clk); + reset = addInput('reset', reset); + + frontWrite = fdw.clone() + ..connectIO(this, fdw, + inputTags: {DataPortGroup.control}, + outputTags: {DataPortGroup.data}, + uniquify: (original) => 'frontWrite_$original'); + frontRead = fdr.clone() + ..connectIO(this, fdr, + inputTags: {DataPortGroup.control}, + outputTags: {DataPortGroup.data}, + uniquify: (original) => 'frontRead_$original'); + + _buildLogic(); + } + + /// create the CsrBlock from a configuration + factory CsrBlock( + CsrBlockConfig config, + Logic clk, + Logic reset, + DataPortInterface fdw, + DataPortInterface fdr, + ) { + final csrs = []; + for (final reg in config.registers) { + csrs.add(Csr(reg)); + } + return CsrBlock._( + name: config.name, + addr: config.baseAddr, + csrs: csrs, + clk: clk, + reset: reset, + fdw: fdw, + fdr: fdr, + ); + } + + /// extract the address as a Const + Logic getAddr(int addrWidth) => Const(LogicValue.ofInt(addr, addrWidth)); + + /// method to return an individual register by name + Csr getRegister(String nm) => + csrs.firstWhere((element) => element.name == nm); + + /// method to return an individual by address + Csr getRegisterByAddr(int addr) => + csrs.firstWhere((element) => element.addr == addr); + + /// API method to extract read data from block + Logic rdData() => frontRead.data; + + void _buildLogic() { + // individual CSR write logic + for (final csr in csrs) { + Sequential(clk, reset: reset, resetValues: { + csr: csr.getResetValue(), + }, [ + If(frontWrite.en & frontWrite.addr.eq(csr.getAddr(addrWidth)), then: [ + csr < csr.getWriteData(frontWrite.data), + ], orElse: [ + csr < csr, + ]), + ]); + } + + // individual CSR read logic + final rdData = Logic(name: 'rdData', width: frontRead.dataWidth); + final rdCases = csrs + .map((csr) => CaseItem(csr.getAddr(addrWidth), [ + rdData < csr, + ])) + .toList(); + Combinational([ + Case(frontRead.addr, rdCases, defaultItem: [ + rdData < Const(0, width: frontRead.dataWidth), + ]), + ]); + frontRead.data <= rdData; + } +} + +class CsrTop extends Module { + /// width of address in bits + final int addrWidth; + + /// CSRs in this block + final List blocks; + + /// clk for the module + late final Logic clk; + + /// reset for the module + late final Logic reset; + + /// interface for frontdoor writes to CSRs + late final DataPortInterface frontWrite; + + /// interface for frontdoor reads to CSRs + late final DataPortInterface frontRead; + + // individual sub interfaces to blocks + final List _fdWrites = []; + final List _fdReads = []; + + CsrTop._({ + required super.name, + required this.blocks, + required Logic clk, + required Logic reset, + required DataPortInterface fdw, + required DataPortInterface fdr, + }) : addrWidth = fdw.addrWidth { + clk = addInput('clk', clk); + reset = addInput('reset', reset); + + frontWrite = fdw.clone() + ..connectIO(this, fdw, + inputTags: {DataPortGroup.control}, + outputTags: {DataPortGroup.data}, + uniquify: (original) => 'frontWrite_$original'); + frontRead = fdr.clone() + ..connectIO(this, fdr, + inputTags: {DataPortGroup.control}, + outputTags: {DataPortGroup.data}, + uniquify: (original) => 'frontRead_$original'); + + _buildLogic(); + } + + /// create the CsrBlock from a configuration + factory CsrTop( + CsrTopConfig config, + Logic clk, + Logic reset, + DataPortInterface fdw, + DataPortInterface fdr, + ) { + final blks = []; + for (final block in config.blocks) { + // TODO: how to derive the address width + // TODO: how to connect properly top level + // IDEA: need to remove the block portion of the address + _fdWrites.add(DataPortInterface(fdw.dataWidth, TODO)); + _fdReads.add(DataPortInterface(fdr.dataWidth, TODO)); + blks.add(CsrBlock(block, clk, reset, _fdWrites.last, _fdReads.last)); + } + return CsrTop._( + name: config.name, + blocks: blks, + clk: clk, + reset: reset, + fdw: fdw, + fdr: fdr, + ); + } + + // TODO: how to map the address to the correct block + void _buildLogic() { + // drive frontdoor write and read inputs + for (var i = 0; i < blocks.length; i++) { + _fdWrites[i].en <= + frontWrite.en & frontWrite.addr.eq(blocks[i].getAddr(addrWidth)); + _fdReads[i].en <= + frontWrite.en & frontWrite.addr.eq(blocks[i].getAddr(addrWidth)); + + _fdWrites[i].addr <= frontWrite.addr; + _fdReads[i].addr <= frontWrite.addr; + + _fdWrites[i].data <= frontWrite.data; + } + + // capture frontdoor read output + final rdData = Logic(name: 'rdData', width: frontRead.dataWidth); + final rdCases = blocks + .map((block) => CaseItem(block.getAddr(addrWidth), [ + rdData < block.rdData(), + ])) + .toList(); + Combinational([ + Case(frontRead.addr, rdCases, defaultItem: [ + rdData < Const(0, width: frontRead.dataWidth), + ]), + ]); + frontRead.data <= rdData; + } + + /// method to return an individual by name + CsrBlock getBlock(String nm) => + blocks.firstWhere((element) => element.name == nm); + + /// method to return an individual by address + CsrBlock getBlockByAddr(int addr) => + blocks.firstWhere((element) => element.addr == addr); +} From 1ad1347f77a6b97c4aef939b98fb1d8b2fe8ccb7 Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Fri, 27 Dec 2024 16:16:58 -0800 Subject: [PATCH 02/14] WIP: progress on csrs. --- lib/src/memory/csr.dart | 173 ++++++++---- lib/src/memory/memories.dart | 1 + test/csr_test.dart | 526 +++++++++++++++++++++++++++++++++++ 3 files changed, 649 insertions(+), 51 deletions(-) create mode 100644 test/csr_test.dart diff --git a/lib/src/memory/csr.dart b/lib/src/memory/csr.dart index a0662089a..34bf48ff5 100644 --- a/lib/src/memory/csr.dart +++ b/lib/src/memory/csr.dart @@ -33,6 +33,13 @@ enum CsrAccess { } /// configuration for a register field +/// if starting bit position and/or width is symbolic based on +/// the parent register width and/or other fields +/// it is assumed that those symbolic values are resolved +/// before construction of the field config +/// if a field should be duplicated n times within a register, +/// it is assumed that this object is instantiated n times +/// within the parent register config accordingly class CsrFieldConfig { /// starting bit position of the field in the register final int start; @@ -47,31 +54,32 @@ class CsrFieldConfig { final CsrFieldAccess access; /// construct a new field configuration - CsrFieldConfig(this.start, this.width, this.name, this.access); + CsrFieldConfig({ + required this.start, + required this.width, + required this.name, + required this.access, + }); } -/// configuration for a register +/// configuration for an architectural register +/// any architecturally fixed fields can be added directly to this class +/// but any fields with implementation specific config should be left out class CsrConfig { - /// register's address within its block - final int addr; - - /// number of bits in the register - final int width; - /// name for the register final String name; /// access rules for the register final CsrAccess access; - /// reset value for the register - final int resetValue; - /// fields in this register final List fields = []; /// construct a new register configuration - CsrConfig(this.addr, this.width, this.name, this.access, this.resetValue); + CsrConfig({ + required this.name, + required this.access, + }); /// add a register field to this register void addField(CsrFieldConfig field) { @@ -79,7 +87,52 @@ class CsrConfig { } } +/// configuration for a register instance +/// apply implementation specific information to an architectural register +/// this includes instantiation of fields that require runtime configuration +/// such runtime configuration might also apply for conditional +/// instantiation of fields within the register +class CsrInstanceConfig { + /// underlying architectural config + final CsrConfig arch; + + /// register's address within its block + final int addr; + + /// number of bits in the register + final int width; + + /// reset value for the register + final int resetValue; + + /// accessor to the name of the arch register + String get name => arch.name; + + /// accessor to the privilege of the register + CsrAccess get access => arch.access; + + /// accessor to the fields of the register + List get fields => arch.fields; + + /// construct a new register configuration + CsrInstanceConfig({ + required this.arch, + required this.addr, + required this.width, + required this.resetValue, + }); + + /// add a register field to this register instance + void addField(CsrFieldConfig field) { + arch.addField(field); + } +} + /// definition for a coherent block of registers +/// blocks by definition are instantiations of registers and +/// hence require CsrInstanceConfig objects +/// this class is also where the choice to instantiate +/// any conditional registers should take place class CsrBlockConfig { /// name for the block final String name; @@ -88,31 +141,38 @@ class CsrBlockConfig { final int baseAddr; /// registers in this block - final List registers = []; + final List registers = []; /// construct a new block configuration - CsrBlockConfig( - this.name, - this.baseAddr, - ); + CsrBlockConfig({ + required this.name, + required this.baseAddr, + }); /// add a register to this block - void addRegister(CsrConfig register) { + void addRegister(CsrInstanceConfig register) { registers.add(register); } } /// definition for a top level module containing CSR blocks +/// this class is also where the choice to instantiate +/// any conditional blocks should take place class CsrTopConfig { /// name for the top module final String name; + /// number of LSBs in an incoming address to ignore + /// when assessing the address of a block + final int blockOffsetWidth; + /// blocks in this module final List blocks = []; /// construct a new top level configuration CsrTopConfig( this.name, + this.blockOffsetWidth, ); /// add a block to this module @@ -124,7 +184,6 @@ class CsrTopConfig { /// Logic representation of a CSR class Csr extends LogicStructure { /// bit width of the CSR - @override final int csrWidth; /// address for the CSR @@ -153,7 +212,7 @@ class Csr extends LogicStructure { }) : super(fields); factory Csr( - CsrConfig config, + CsrInstanceConfig config, ) { final fields = []; var currIdx = 0; @@ -235,6 +294,8 @@ class Csr extends LogicStructure { } /// a submodule representing a block of CSRs +// TODO: +// backdoor reads and writes class CsrBlock extends Module { /// address for this block final int addr; @@ -257,8 +318,6 @@ class CsrBlock extends Module { /// interface for frontdoor reads to CSRs late final DataPortInterface frontRead; - // TODO: how to deal with backdoor writes?? - CsrBlock._({ required super.name, required this.addr, @@ -278,8 +337,8 @@ class CsrBlock extends Module { uniquify: (original) => 'frontWrite_$original'); frontRead = fdr.clone() ..connectIO(this, fdr, - inputTags: {DataPortGroup.control}, - outputTags: {DataPortGroup.data}, + inputTags: {DataPortGroup.control, DataPortGroup.data}, + outputTags: {}, uniquify: (original) => 'frontRead_$original'); _buildLogic(); @@ -352,12 +411,17 @@ class CsrBlock extends Module { } } +/// top level CSR module class CsrTop extends Module { /// width of address in bits final int addrWidth; + /// width of the LSBs of the address + /// to ignore when mapping to blocks + final int blockOffsetWidth; + /// CSRs in this block - final List blocks; + final List blocks = []; /// clk for the module late final Logic clk; @@ -377,11 +441,12 @@ class CsrTop extends Module { CsrTop._({ required super.name, - required this.blocks, + required this.blockOffsetWidth, required Logic clk, required Logic reset, required DataPortInterface fdw, required DataPortInterface fdr, + required List bCfgs, }) : addrWidth = fdw.addrWidth { clk = addInput('clk', clk); reset = addInput('reset', reset); @@ -397,6 +462,12 @@ class CsrTop extends Module { outputTags: {DataPortGroup.data}, uniquify: (original) => 'frontRead_$original'); + for (final block in bCfgs) { + _fdWrites.add(DataPortInterface(fdw.dataWidth, blockOffsetWidth)); + _fdReads.add(DataPortInterface(fdr.dataWidth, blockOffsetWidth)); + blocks.add(CsrBlock(block, clk, reset, _fdWrites.last, _fdReads.last)); + } + _buildLogic(); } @@ -407,37 +478,37 @@ class CsrTop extends Module { Logic reset, DataPortInterface fdw, DataPortInterface fdr, - ) { - final blks = []; - for (final block in config.blocks) { - // TODO: how to derive the address width - // TODO: how to connect properly top level - // IDEA: need to remove the block portion of the address - _fdWrites.add(DataPortInterface(fdw.dataWidth, TODO)); - _fdReads.add(DataPortInterface(fdr.dataWidth, TODO)); - blks.add(CsrBlock(block, clk, reset, _fdWrites.last, _fdReads.last)); - } - return CsrTop._( - name: config.name, - blocks: blks, - clk: clk, - reset: reset, - fdw: fdw, - fdr: fdr, - ); - } + ) => + CsrTop._( + name: config.name, + blockOffsetWidth: config.blockOffsetWidth, + clk: clk, + reset: reset, + fdw: fdw, + fdr: fdr, + bCfgs: config.blocks, + ); - // TODO: how to map the address to the correct block void _buildLogic() { + // mask out LSBs to perform a match on block + final maskedFrontWrAddr = + frontWrite.addr & ~Const((1 << blockOffsetWidth) - 1, width: addrWidth); + final maskedFrontRdAddr = + frontRead.addr & ~Const((1 << blockOffsetWidth) - 1, width: addrWidth); + + // shift out MSBs to pass the appropriate address into the blocks + final shiftedFrontWrAddr = frontWrite.addr.getRange(0, blockOffsetWidth); + final shiftedFrontRdAddr = frontRead.addr.getRange(0, blockOffsetWidth); + // drive frontdoor write and read inputs for (var i = 0; i < blocks.length; i++) { _fdWrites[i].en <= - frontWrite.en & frontWrite.addr.eq(blocks[i].getAddr(addrWidth)); + frontWrite.en & maskedFrontWrAddr.eq(blocks[i].getAddr(addrWidth)); _fdReads[i].en <= - frontWrite.en & frontWrite.addr.eq(blocks[i].getAddr(addrWidth)); + frontWrite.en & maskedFrontRdAddr.eq(blocks[i].getAddr(addrWidth)); - _fdWrites[i].addr <= frontWrite.addr; - _fdReads[i].addr <= frontWrite.addr; + _fdWrites[i].addr <= shiftedFrontWrAddr; + _fdReads[i].addr <= shiftedFrontRdAddr; _fdWrites[i].data <= frontWrite.data; } @@ -450,7 +521,7 @@ class CsrTop extends Module { ])) .toList(); Combinational([ - Case(frontRead.addr, rdCases, defaultItem: [ + Case(maskedFrontRdAddr, rdCases, defaultItem: [ rdData < Const(0, width: frontRead.dataWidth), ]), ]); diff --git a/lib/src/memory/memories.dart b/lib/src/memory/memories.dart index dec8f2107..639281ab7 100644 --- a/lib/src/memory/memories.dart +++ b/lib/src/memory/memories.dart @@ -1,5 +1,6 @@ // Copyright (C) 2023-2024 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause +export 'csr.dart'; export 'memory.dart'; export 'register_file.dart'; diff --git a/test/csr_test.dart b/test/csr_test.dart new file mode 100644 index 000000000..d816dd154 --- /dev/null +++ b/test/csr_test.dart @@ -0,0 +1,526 @@ +// Copyright (C) 2023-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// csr_test.dart +// Tests for CSRs +// +// 2024 December +// Author: Josh Kimmel + +import 'dart:async'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; +import 'package:test/test.dart'; + +///// DEFINE SOME HELPER CLASSES FOR TESTING ///// + +class MyNoFieldCsr extends CsrConfig { + MyNoFieldCsr({super.name = 'myNoFieldCsr'}) + : super(access: CsrAccess.REGISTER_READ_ONLY); +} + +class MyNoFieldCsrInstance extends CsrInstanceConfig { + MyNoFieldCsrInstance({ + required super.addr, + required super.width, + super.resetValue = 0x0, + String name = 'myNoFieldCsrInstance', + }) : super(arch: MyNoFieldCsr(name: name)); +} + +class MyFieldCsr extends CsrConfig { + MyFieldCsr({super.name = 'myFieldCsr'}) + : super(access: CsrAccess.REGISTER_READ_WRITE); +} + +class MyFieldCsrInstance extends CsrInstanceConfig { + MyFieldCsrInstance({ + required super.addr, + required super.width, + super.resetValue = 0xff, + String name = 'myFieldCsrInstance', + }) : super(arch: MyFieldCsr(name: name)) { + // example of a static field + addField(CsrFieldConfig( + start: 0, + width: 2, + name: 'field1', + access: CsrFieldAccess.FIELD_READ_ONLY)); + // example of a field with dynamic start and width + addField(CsrFieldConfig( + start: width ~/ 2, + width: width ~/ 4, + name: 'field2', + access: CsrFieldAccess.FIELD_READ_WRITE)); + // example of field duplication + for (var i = 0; i < (width ~/ 4) - 1; i++) { + addField(CsrFieldConfig( + start: (3 * width ~/ 4) + i, + width: 1, + name: 'field3_$i', + access: CsrFieldAccess.FIELD_W1C)); + } + } +} + +class MyRegisterBlock extends CsrBlockConfig { + final int csrWidth; + final int numNoFieldCsrs; + final bool evensOnly; + + MyRegisterBlock({ + required super.baseAddr, + super.name = 'myRegisterBlock', + this.csrWidth = 32, + this.numNoFieldCsrs = 1, + this.evensOnly = false, + }) { + // static register instance + addRegister(MyFieldCsrInstance(addr: 0x0, width: csrWidth, name: 'csr1')); + + // dynamic register instances + for (var i = 0; i < numNoFieldCsrs; i++) { + final chk = i.isEven || !evensOnly; + if (chk) { + addRegister(MyNoFieldCsrInstance( + addr: i + 1, width: csrWidth, name: 'csr2_$i')); + } + } + } +} + +///// END DEFINE SOME HELPER CLASSES FOR TESTING ///// + +void main() { + tearDown(() async { + await Simulator.reset(); + }); + + test('simple individual CSRs', () async { + // register with no explicit fields, read only + final csr1Cfg = MyNoFieldCsrInstance(addr: 0x0, width: 32, name: 'csr1'); + final csr1 = Csr(csr1Cfg); + + // check address and reset value + expect(csr1.getAddr(8).value, LogicValue.ofInt(0x0, 8)); + expect(csr1.getResetValue().value, LogicValue.ofInt(0x0, 32)); + csr1.inject(csr1.resetValue); + + // check write data + // should return the reset value, since it's read only + final wd1 = csr1.getWriteData(Const(0x12345678, width: 32)); + expect(wd1.value, LogicValue.ofInt(0x0, 32)); + + // register with 3 explicit fields, read/write + // fields don't cover full width of register + final csr2Cfg = MyFieldCsrInstance(addr: 0x1, width: 8, name: 'csr2'); + final csr2 = Csr(csr2Cfg); + + // check address and reset value + expect(csr2.getAddr(8).value, LogicValue.ofInt(0x1, 8)); + expect(csr2.getResetValue().value, LogicValue.ofInt(0xff, 8)); + + // check the write data + // only some of what we're trying to write should + // given the field access rules + final wd2 = csr2.getWriteData(Const(0xab, width: 8)); + expect(wd2.value, LogicValue.ofInt(0xef, 8)); + + // check grabbing individual fields + final f1 = csr2.getField('field1'); + expect(f1.value, LogicValue.ofInt(0x3, 2)); + final f2 = csr2.getField('field2'); + expect(f2.value, LogicValue.ofInt(0x1, 1)); + final f3 = csr2.getField('field3'); + expect(f3.value, LogicValue.ofInt(0x5, 3)); + }); + + test('simple CSR block', () async { + const csrWidth = 32; + + final csrBlockCfg = MyRegisterBlock( + baseAddr: 0x0, + csrWidth: csrWidth, + numNoFieldCsrs: 2, + ); + + final clk = SimpleClockGenerator(10).clk; + final reset = Logic()..inject(0); + final wIntf = DataPortInterface(csrWidth, 8); + final rIntf = DataPortInterface(csrWidth, 8); + final csrBlock = CsrBlock(csrBlockCfg, clk, reset, wIntf, rIntf); + + wIntf.en.inject(0); + wIntf.addr.inject(0); + wIntf.data.inject(0); + rIntf.en.inject(0); + rIntf.addr.inject(0); + + // retrieve block address + final blkAddr = csrBlock.getAddr(16); + expect(blkAddr.value, LogicValue.ofInt(0x100, 16)); + + // grab pointers to the CSRs + final csr1 = csrBlock.getRegister('csr1'); + final csr2 = csrBlock.getRegisterByAddr(0x1); + + // perform a reset + reset.inject(1); + await clk.waitCycles(10); + reset.inject(0); + await clk.waitCycles(10); + + // perform a read of csr2 + // ensure that the read data is the reset value + await clk.nextNegedge; + rIntf.en.inject(1); + rIntf.addr.inject(csr2.getAddr(rIntf.addrWidth).value); + await clk.nextNegedge; + rIntf.en.inject(0); + expect(csrBlock.rdData().value, csr2.getResetValue().value); + await clk.waitCycles(10); + + // perform a write of csr1 and then a read + // ensure that the write takes no effect b/c readonly + await clk.nextNegedge; + wIntf.en.inject(1); + wIntf.addr.inject(csr1.getAddr(wIntf.addrWidth).value); + wIntf.data.inject(0xdeadbeef); + await clk.nextNegedge; + wIntf.en.inject(0); + rIntf.en.inject(1); + rIntf.addr.inject(csr1.getAddr(rIntf.addrWidth).value); + await clk.nextNegedge; + rIntf.en.inject(0); + expect(csrBlock.rdData().value, csr1.getResetValue().value); + await clk.waitCycles(10); + + // perform a write of csr2 + // ensure that the write data is modified appropriately + await clk.nextNegedge; + wIntf.en.inject(1); + wIntf.addr.inject(csr2.getAddr(wIntf.addrWidth).value); + wIntf.data.inject(0xdeadbeef); + await clk.nextNegedge; + wIntf.en.inject(0); + rIntf.en.inject(1); + rIntf.addr.inject(csr2.getAddr(rIntf.addrWidth).value); + await clk.nextNegedge; + rIntf.en.inject(0); + expect(csrBlock.rdData().value, LogicValue.ofInt(0xef, rIntf.dataWidth)); + await clk.waitCycles(10); + + // perform a read of nothing + await clk.nextNegedge; + rIntf.en.inject(1); + rIntf.addr.inject(0xff); + await clk.nextNegedge; + rIntf.en.inject(0); + expect(csrBlock.rdData().value, LogicValue.ofInt(0, rIntf.dataWidth)); + await clk.waitCycles(10); + + // TODO: end simulation + }); + + // test('simple shift register', () async { + // final dataIn = Logic(width: 8); + // final clk = SimpleClockGenerator(10).clk; + // const latency = 5; + // final sr = ShiftRegister(dataIn, clk: clk, depth: latency); + // final dataOut = sr.dataOut; + // final data3 = sr.stages[2]; + + // final data = [for (var i = 0; i < 20; i++) i * 3]; + + // unawaited(Simulator.run()); + + // for (final dataI in data) { + // dataIn.put(dataI); + // unawaited(clk + // .waitCycles(latency) + // .then((value) => expect(dataOut.value.toInt(), dataI))); + + // unawaited(clk + // .waitCycles(3) + // .then((value) => expect(data3.value.toInt(), dataI))); + + // await clk.nextPosedge; + // } + + // await clk.waitCycles(latency); + + // expect(dataOut.value.toInt(), data.last); + + // await Simulator.endSimulation(); + // }); + + // test('shift register naming', () { + // final sr = + // ShiftRegister(Logic(), clk: Logic(), depth: 3, dataName: 'fancy'); + // expect(sr.name, contains('fancy')); + // expect(sr.dataOut.name, contains('fancy')); + // expect( + // // ignore: invalid_use_of_protected_member + // sr.inputs.keys.where((element) => element.contains('fancy')).length, + // 1); + // }); + + // test('depth 0 shift register is pass-through', () { + // final dataIn = Logic(width: 8); + // final clk = Logic(); + // const latency = 0; + // final dataOut = ShiftRegister(dataIn, clk: clk, depth: latency).dataOut; + + // dataIn.put(0x23); + // expect(dataOut.value.toInt(), 0x23); + // }); + + // test('width 0 constructs properly', () { + // expect(ShiftRegister(Logic(width: 0), clk: Logic(), depth: 9).dataOut.width, + // 0); + // }); + + // test('enabled shift register', () async { + // final dataIn = Logic(width: 8); + // final clk = SimpleClockGenerator(10).clk; + // const latency = 5; + // final enable = Logic(); + // final dataOut = + // ShiftRegister(dataIn, clk: clk, depth: latency, enable: enable).dataOut; + + // unawaited(Simulator.run()); + + // enable.put(true); + // dataIn.put(0x45); + + // await clk.nextPosedge; + // dataIn.put(0); + + // await clk.waitCycles(2); + + // enable.put(false); + + // await clk.waitCycles(20); + + // enable.put(true); + + // expect(dataOut.value.isValid, isFalse); + + // await clk.waitCycles(2); + + // expect(dataOut.value.toInt(), 0x45); + + // await clk.nextPosedge; + + // expect(dataOut.value.toInt(), 0); + + // await Simulator.endSimulation(); + // }); + + // group('reset shift register', () { + // Future resetTest( + // dynamic resetVal, void Function(Logic dataOut) check) async { + // final dataIn = Logic(width: 8); + // final clk = SimpleClockGenerator(10).clk; + // const latency = 5; + // final reset = Logic(); + // final dataOut = ShiftRegister(dataIn, + // clk: clk, depth: latency, reset: reset, resetValue: resetVal) + // .dataOut; + + // unawaited(Simulator.run()); + + // dataIn.put(0x45); + // reset.put(true); + + // await clk.nextPosedge; + + // check(dataOut); + + // reset.put(false); + + // await clk.waitCycles(2); + + // check(dataOut); + + // await clk.waitCycles(3); + + // expect(dataOut.value.toInt(), 0x45); + + // await Simulator.endSimulation(); + // } + + // test('null reset value', () async { + // await resetTest(null, (dataOut) { + // expect(dataOut.value.toInt(), 0); + // }); + // }); + + // test('constant reset value', () async { + // await resetTest(0x56, (dataOut) { + // expect(dataOut.value.toInt(), 0x56); + // }); + // }); + + // test('logic reset value', () async { + // await resetTest(Const(0x78, width: 8), (dataOut) { + // expect(dataOut.value.toInt(), 0x78); + // }); + // }); + // }); + + // test('enabled and reset shift register', () async { + // final dataIn = Logic(width: 8); + // final clk = SimpleClockGenerator(10).clk; + // const latency = 5; + // final enable = Logic(); + // final reset = Logic(); + // final dataOut = ShiftRegister(dataIn, + // clk: clk, depth: latency, enable: enable, reset: reset) + // .dataOut; + + // unawaited(Simulator.run()); + + // enable.put(true); + // dataIn.put(0x45); + // reset.put(true); + + // await clk.nextPosedge; + // reset.put(false); + + // await clk.nextPosedge; + + // dataIn.put(0); + + // await clk.waitCycles(2); + + // enable.put(false); + + // await clk.waitCycles(20); + + // enable.put(true); + + // expect(dataOut.value.toInt(), 0); + + // await clk.waitCycles(2); + + // expect(dataOut.value.toInt(), 0x45); + + // await clk.nextPosedge; + + // expect(dataOut.value.toInt(), 0); + + // await Simulator.endSimulation(); + // }); + + // group('list reset value shift register', () { + // Future listResetTest( + // dynamic resetVal, void Function(Logic dataOut) check) async { + // final dataIn = Logic(width: 8); + // final clk = SimpleClockGenerator(10).clk; + // const depth = 5; + // final reset = Logic(); + // final dataOut = ShiftRegister(dataIn, + // clk: clk, depth: depth, reset: reset, resetValue: resetVal) + // .dataOut; + + // unawaited(Simulator.run()); + + // dataIn.put(0x45); + // reset.put(true); + + // await clk.nextPosedge; + + // reset.put(false); + + // await clk.waitCycles(3); + + // check(dataOut); + + // await Simulator.endSimulation(); + // } + + // test('list of logics reset value', () async { + // await listResetTest([ + // Logic(width: 8)..put(0x2), + // Logic(width: 8)..put(0x10), + // Logic(width: 8)..put(0x22), + // Logic(width: 8)..put(0x33), + // Logic(width: 8)..put(0x42), + // ], (dataOut) { + // expect(dataOut.value.toInt(), 0x10); + // }); + // }); + + // test('list of mixed reset value', () async { + // await listResetTest([ + // Logic(width: 8)..put(0x2), + // 26, + // Logic(width: 8)..put(0x22), + // true, + // Logic(width: 8)..put(0x42), + // ], (dataOut) { + // expect(dataOut.value.toInt(), 0x1A); + // }); + // }); + // }); + + // group('async reset shift register', () { + // Future asyncResetTest( + // dynamic resetVal, void Function(Logic dataOut) check) async { + // final dataIn = Logic(width: 8); + // final clk = SimpleClockGenerator(10).clk; + // const depth = 5; + // final reset = Logic(); + // final dataOut = ShiftRegister(dataIn, + // clk: Const(0), + // depth: depth, + // reset: reset, + // resetValue: resetVal, + // asyncReset: true) + // .dataOut; + + // unawaited(Simulator.run()); + + // dataIn.put(0x42); + + // reset.inject(false); + + // await clk.waitCycles(1); + + // reset.inject(true); + + // await clk.waitCycles(1); + + // check(dataOut); + + // await Simulator.endSimulation(); + // } + + // test('async reset value', () async { + // await asyncResetTest(Const(0x78, width: 8), (dataOut) { + // expect(dataOut.value.toInt(), 0x78); + // }); + // }); + + // test('async null reset value', () async { + // await asyncResetTest(null, (dataOut) { + // expect(dataOut.value.toInt(), 0); + // }); + // }); + + // test('async reset with list mixed type', () async { + // await asyncResetTest([ + // Logic(width: 8)..put(0x2), + // 59, + // Const(0x78, width: 8), + // Logic(width: 8)..put(0x33), + // true, + // ], (dataOut) { + // expect(dataOut.value.toInt(), 0x1); + // }); + // }); + // }); +} From 95f7ceb9fea6cb419b6af50994ee2fcc2749a7f4 Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Thu, 2 Jan 2025 15:00:57 -0800 Subject: [PATCH 03/14] Progress on CSR stuff. Simple tests now exist too. --- lib/src/memory/csr.dart | 102 +++++----- test/csr_test.dart | 426 +++++++++++----------------------------- 2 files changed, 168 insertions(+), 360 deletions(-) diff --git a/lib/src/memory/csr.dart b/lib/src/memory/csr.dart index 34bf48ff5..070e2b933 100644 --- a/lib/src/memory/csr.dart +++ b/lib/src/memory/csr.dart @@ -170,10 +170,10 @@ class CsrTopConfig { final List blocks = []; /// construct a new top level configuration - CsrTopConfig( - this.name, - this.blockOffsetWidth, - ); + CsrTopConfig({ + required this.name, + required this.blockOffsetWidth, + }); /// add a block to this module void addBlock(CsrBlockConfig block) { @@ -307,16 +307,16 @@ class CsrBlock extends Module { final List csrs; /// clk for the module - late final Logic clk; + late final Logic _clk; /// reset for the module - late final Logic reset; + late final Logic _reset; /// interface for frontdoor writes to CSRs - late final DataPortInterface frontWrite; + late final DataPortInterface _frontWrite; /// interface for frontdoor reads to CSRs - late final DataPortInterface frontRead; + late final DataPortInterface _frontRead; CsrBlock._({ required super.name, @@ -327,18 +327,18 @@ class CsrBlock extends Module { required DataPortInterface fdw, required DataPortInterface fdr, }) : addrWidth = fdw.addrWidth { - clk = addInput('clk', clk); - reset = addInput('reset', reset); + _clk = addInput('clk', clk); + _reset = addInput('reset', reset); - frontWrite = fdw.clone() + _frontWrite = fdw.clone() ..connectIO(this, fdw, - inputTags: {DataPortGroup.control}, - outputTags: {DataPortGroup.data}, - uniquify: (original) => 'frontWrite_$original'); - frontRead = fdr.clone() - ..connectIO(this, fdr, inputTags: {DataPortGroup.control, DataPortGroup.data}, outputTags: {}, + uniquify: (original) => 'frontWrite_$original'); + _frontRead = fdr.clone() + ..connectIO(this, fdr, + inputTags: {DataPortGroup.control}, + outputTags: {DataPortGroup.data}, uniquify: (original) => 'frontRead_$original'); _buildLogic(); @@ -379,16 +379,16 @@ class CsrBlock extends Module { csrs.firstWhere((element) => element.addr == addr); /// API method to extract read data from block - Logic rdData() => frontRead.data; + Logic rdData() => _frontRead.data; void _buildLogic() { // individual CSR write logic for (final csr in csrs) { - Sequential(clk, reset: reset, resetValues: { + Sequential(_clk, reset: _reset, resetValues: { csr: csr.getResetValue(), }, [ - If(frontWrite.en & frontWrite.addr.eq(csr.getAddr(addrWidth)), then: [ - csr < csr.getWriteData(frontWrite.data), + If(_frontWrite.en & _frontWrite.addr.eq(csr.getAddr(addrWidth)), then: [ + csr < csr.getWriteData(_frontWrite.data), ], orElse: [ csr < csr, ]), @@ -396,18 +396,18 @@ class CsrBlock extends Module { } // individual CSR read logic - final rdData = Logic(name: 'rdData', width: frontRead.dataWidth); + final rdData = Logic(name: 'rdData', width: _frontRead.dataWidth); final rdCases = csrs .map((csr) => CaseItem(csr.getAddr(addrWidth), [ rdData < csr, ])) .toList(); Combinational([ - Case(frontRead.addr, rdCases, defaultItem: [ - rdData < Const(0, width: frontRead.dataWidth), + Case(_frontRead.addr, rdCases, defaultItem: [ + rdData < Const(0, width: _frontRead.dataWidth), ]), ]); - frontRead.data <= rdData; + _frontRead.data <= rdData; } } @@ -421,19 +421,19 @@ class CsrTop extends Module { final int blockOffsetWidth; /// CSRs in this block - final List blocks = []; + final List _blocks = []; /// clk for the module - late final Logic clk; + late final Logic _clk; /// reset for the module - late final Logic reset; + late final Logic _reset; /// interface for frontdoor writes to CSRs - late final DataPortInterface frontWrite; + late final DataPortInterface _frontWrite; /// interface for frontdoor reads to CSRs - late final DataPortInterface frontRead; + late final DataPortInterface _frontRead; // individual sub interfaces to blocks final List _fdWrites = []; @@ -448,15 +448,15 @@ class CsrTop extends Module { required DataPortInterface fdr, required List bCfgs, }) : addrWidth = fdw.addrWidth { - clk = addInput('clk', clk); - reset = addInput('reset', reset); + _clk = addInput('clk', clk); + _reset = addInput('reset', reset); - frontWrite = fdw.clone() + _frontWrite = fdw.clone() ..connectIO(this, fdw, - inputTags: {DataPortGroup.control}, - outputTags: {DataPortGroup.data}, + inputTags: {DataPortGroup.control, DataPortGroup.data}, + outputTags: {}, uniquify: (original) => 'frontWrite_$original'); - frontRead = fdr.clone() + _frontRead = fdr.clone() ..connectIO(this, fdr, inputTags: {DataPortGroup.control}, outputTags: {DataPortGroup.data}, @@ -465,7 +465,7 @@ class CsrTop extends Module { for (final block in bCfgs) { _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)); } _buildLogic(); @@ -491,48 +491,48 @@ class CsrTop extends Module { void _buildLogic() { // mask out LSBs to perform a match on block - final maskedFrontWrAddr = - frontWrite.addr & ~Const((1 << blockOffsetWidth) - 1, width: addrWidth); + final maskedFrontWrAddr = _frontWrite.addr & + ~Const((1 << blockOffsetWidth) - 1, width: addrWidth); final maskedFrontRdAddr = - frontRead.addr & ~Const((1 << blockOffsetWidth) - 1, width: addrWidth); + _frontRead.addr & ~Const((1 << blockOffsetWidth) - 1, width: addrWidth); // shift out MSBs to pass the appropriate address into the blocks - final shiftedFrontWrAddr = frontWrite.addr.getRange(0, blockOffsetWidth); - final shiftedFrontRdAddr = frontRead.addr.getRange(0, blockOffsetWidth); + final shiftedFrontWrAddr = _frontWrite.addr.getRange(0, blockOffsetWidth); + final shiftedFrontRdAddr = _frontRead.addr.getRange(0, blockOffsetWidth); // drive frontdoor write and read inputs - for (var i = 0; i < blocks.length; i++) { + for (var i = 0; i < _blocks.length; i++) { _fdWrites[i].en <= - frontWrite.en & maskedFrontWrAddr.eq(blocks[i].getAddr(addrWidth)); + _frontWrite.en & maskedFrontWrAddr.eq(_blocks[i].getAddr(addrWidth)); _fdReads[i].en <= - frontWrite.en & maskedFrontRdAddr.eq(blocks[i].getAddr(addrWidth)); + _frontWrite.en & maskedFrontRdAddr.eq(_blocks[i].getAddr(addrWidth)); _fdWrites[i].addr <= shiftedFrontWrAddr; _fdReads[i].addr <= shiftedFrontRdAddr; - _fdWrites[i].data <= frontWrite.data; + _fdWrites[i].data <= _frontWrite.data; } // capture frontdoor read output - final rdData = Logic(name: 'rdData', width: frontRead.dataWidth); - final rdCases = blocks + final rdData = Logic(name: 'rdData', width: _frontRead.dataWidth); + final rdCases = _blocks .map((block) => CaseItem(block.getAddr(addrWidth), [ rdData < block.rdData(), ])) .toList(); Combinational([ Case(maskedFrontRdAddr, rdCases, defaultItem: [ - rdData < Const(0, width: frontRead.dataWidth), + rdData < Const(0, width: _frontRead.dataWidth), ]), ]); - frontRead.data <= rdData; + _frontRead.data <= rdData; } /// method to return an individual by name CsrBlock getBlock(String nm) => - blocks.firstWhere((element) => element.name == nm); + _blocks.firstWhere((element) => element.name == nm); /// method to return an individual by address CsrBlock getBlockByAddr(int addr) => - blocks.firstWhere((element) => element.addr == addr); + _blocks.firstWhere((element) => element.addr == addr); } diff --git a/test/csr_test.dart b/test/csr_test.dart index d816dd154..9fe417adb 100644 --- a/test/csr_test.dart +++ b/test/csr_test.dart @@ -55,7 +55,7 @@ class MyFieldCsrInstance extends CsrInstanceConfig { name: 'field2', access: CsrFieldAccess.FIELD_READ_WRITE)); // example of field duplication - for (var i = 0; i < (width ~/ 4) - 1; i++) { + for (var i = 0; i < width ~/ 4; i++) { addField(CsrFieldConfig( start: (3 * width ~/ 4) + i, width: 1, @@ -91,6 +91,26 @@ class MyRegisterBlock extends CsrBlockConfig { } } +class MyCsrModule extends CsrTopConfig { + final int numBlocks; + + MyCsrModule({ + this.numBlocks = 1, + super.name = 'myCsrModule', + super.blockOffsetWidth = 16, + }) { + // example of dynamic block instantiation + const baseAddr = 0x0; + for (var i = 0; i < numBlocks; i++) { + addBlock(MyRegisterBlock( + baseAddr: baseAddr + (i * 0x100), + numNoFieldCsrs: i + 1, + evensOnly: i.isEven, + name: 'block_$i')); + } + } +} + ///// END DEFINE SOME HELPER CLASSES FOR TESTING ///// void main() { @@ -106,7 +126,7 @@ void main() { // check address and reset value expect(csr1.getAddr(8).value, LogicValue.ofInt(0x0, 8)); expect(csr1.getResetValue().value, LogicValue.ofInt(0x0, 32)); - csr1.inject(csr1.resetValue); + csr1.put(csr1.resetValue); // check write data // should return the reset value, since it's read only @@ -121,6 +141,7 @@ void main() { // check address and reset value expect(csr2.getAddr(8).value, LogicValue.ofInt(0x1, 8)); expect(csr2.getResetValue().value, LogicValue.ofInt(0xff, 8)); + csr2.put(csr2.resetValue); // check the write data // only some of what we're trying to write should @@ -132,9 +153,11 @@ void main() { final f1 = csr2.getField('field1'); expect(f1.value, LogicValue.ofInt(0x3, 2)); final f2 = csr2.getField('field2'); - expect(f2.value, LogicValue.ofInt(0x1, 1)); - final f3 = csr2.getField('field3'); - expect(f3.value, LogicValue.ofInt(0x5, 3)); + expect(f2.value, LogicValue.ofInt(0x3, 2)); + final f3a = csr2.getField('field3_0'); + expect(f3a.value, LogicValue.ofInt(0x1, 1)); + final f3b = csr2.getField('field3_1'); + expect(f3b.value, LogicValue.ofInt(0x1, 1)); }); test('simple CSR block', () async { @@ -147,24 +170,31 @@ void main() { ); final clk = SimpleClockGenerator(10).clk; - final reset = Logic()..inject(0); + final reset = Logic()..put(0); final wIntf = DataPortInterface(csrWidth, 8); final rIntf = DataPortInterface(csrWidth, 8); final csrBlock = CsrBlock(csrBlockCfg, clk, reset, wIntf, rIntf); - wIntf.en.inject(0); - wIntf.addr.inject(0); - wIntf.data.inject(0); - rIntf.en.inject(0); - rIntf.addr.inject(0); + wIntf.en.put(0); + wIntf.addr.put(0); + wIntf.data.put(0); + rIntf.en.put(0); + rIntf.addr.put(0); + + await csrBlock.build(); + + // WaveDumper(csrBlock); + + Simulator.setMaxSimTime(10000); + unawaited(Simulator.run()); // retrieve block address final blkAddr = csrBlock.getAddr(16); - expect(blkAddr.value, LogicValue.ofInt(0x100, 16)); + expect(blkAddr.value, LogicValue.ofInt(0x0, 16)); // grab pointers to the CSRs final csr1 = csrBlock.getRegister('csr1'); - final csr2 = csrBlock.getRegisterByAddr(0x1); + final csr2 = csrBlock.getRegisterByAddr(0x2); // perform a reset reset.inject(1); @@ -182,34 +212,35 @@ void main() { expect(csrBlock.rdData().value, csr2.getResetValue().value); await clk.waitCycles(10); - // perform a write of csr1 and then a read + // perform a write of csr2 and then a read // ensure that the write takes no effect b/c readonly await clk.nextNegedge; wIntf.en.inject(1); - wIntf.addr.inject(csr1.getAddr(wIntf.addrWidth).value); + wIntf.addr.inject(csr2.getAddr(wIntf.addrWidth).value); wIntf.data.inject(0xdeadbeef); await clk.nextNegedge; wIntf.en.inject(0); rIntf.en.inject(1); - rIntf.addr.inject(csr1.getAddr(rIntf.addrWidth).value); + rIntf.addr.inject(csr2.getAddr(rIntf.addrWidth).value); await clk.nextNegedge; rIntf.en.inject(0); - expect(csrBlock.rdData().value, csr1.getResetValue().value); + expect(csrBlock.rdData().value, csr2.getResetValue().value); await clk.waitCycles(10); - // perform a write of csr2 + // perform a write of csr1 // ensure that the write data is modified appropriately await clk.nextNegedge; wIntf.en.inject(1); - wIntf.addr.inject(csr2.getAddr(wIntf.addrWidth).value); + wIntf.addr.inject(csr1.getAddr(wIntf.addrWidth).value); wIntf.data.inject(0xdeadbeef); await clk.nextNegedge; wIntf.en.inject(0); rIntf.en.inject(1); - rIntf.addr.inject(csr2.getAddr(rIntf.addrWidth).value); + rIntf.addr.inject(csr1.getAddr(rIntf.addrWidth).value); await clk.nextNegedge; rIntf.en.inject(0); - expect(csrBlock.rdData().value, LogicValue.ofInt(0xef, rIntf.dataWidth)); + expect( + csrBlock.rdData().value, LogicValue.ofInt(0xad00ff, rIntf.dataWidth)); await clk.waitCycles(10); // perform a read of nothing @@ -221,306 +252,83 @@ void main() { expect(csrBlock.rdData().value, LogicValue.ofInt(0, rIntf.dataWidth)); await clk.waitCycles(10); - // TODO: end simulation + await Simulator.endSimulation(); + await Simulator.simulationEnded; }); - // test('simple shift register', () async { - // final dataIn = Logic(width: 8); - // final clk = SimpleClockGenerator(10).clk; - // const latency = 5; - // final sr = ShiftRegister(dataIn, clk: clk, depth: latency); - // final dataOut = sr.dataOut; - // final data3 = sr.stages[2]; - - // final data = [for (var i = 0; i < 20; i++) i * 3]; - - // unawaited(Simulator.run()); - - // for (final dataI in data) { - // dataIn.put(dataI); - // unawaited(clk - // .waitCycles(latency) - // .then((value) => expect(dataOut.value.toInt(), dataI))); - - // unawaited(clk - // .waitCycles(3) - // .then((value) => expect(data3.value.toInt(), dataI))); - - // await clk.nextPosedge; - // } - - // await clk.waitCycles(latency); - - // expect(dataOut.value.toInt(), data.last); - - // await Simulator.endSimulation(); - // }); - - // test('shift register naming', () { - // final sr = - // ShiftRegister(Logic(), clk: Logic(), depth: 3, dataName: 'fancy'); - // expect(sr.name, contains('fancy')); - // expect(sr.dataOut.name, contains('fancy')); - // expect( - // // ignore: invalid_use_of_protected_member - // sr.inputs.keys.where((element) => element.contains('fancy')).length, - // 1); - // }); - - // test('depth 0 shift register is pass-through', () { - // final dataIn = Logic(width: 8); - // final clk = Logic(); - // const latency = 0; - // final dataOut = ShiftRegister(dataIn, clk: clk, depth: latency).dataOut; - - // dataIn.put(0x23); - // expect(dataOut.value.toInt(), 0x23); - // }); - - // test('width 0 constructs properly', () { - // expect(ShiftRegister(Logic(width: 0), clk: Logic(), depth: 9).dataOut.width, - // 0); - // }); - - // test('enabled shift register', () async { - // final dataIn = Logic(width: 8); - // final clk = SimpleClockGenerator(10).clk; - // const latency = 5; - // final enable = Logic(); - // final dataOut = - // ShiftRegister(dataIn, clk: clk, depth: latency, enable: enable).dataOut; - - // unawaited(Simulator.run()); - - // enable.put(true); - // dataIn.put(0x45); - - // await clk.nextPosedge; - // dataIn.put(0); - - // await clk.waitCycles(2); - - // enable.put(false); - - // await clk.waitCycles(20); - - // enable.put(true); - - // expect(dataOut.value.isValid, isFalse); - - // await clk.waitCycles(2); - - // expect(dataOut.value.toInt(), 0x45); - - // await clk.nextPosedge; - - // expect(dataOut.value.toInt(), 0); - - // await Simulator.endSimulation(); - // }); - - // group('reset shift register', () { - // Future resetTest( - // dynamic resetVal, void Function(Logic dataOut) check) async { - // final dataIn = Logic(width: 8); - // final clk = SimpleClockGenerator(10).clk; - // const latency = 5; - // final reset = Logic(); - // final dataOut = ShiftRegister(dataIn, - // clk: clk, depth: latency, reset: reset, resetValue: resetVal) - // .dataOut; - - // unawaited(Simulator.run()); - - // dataIn.put(0x45); - // reset.put(true); - - // await clk.nextPosedge; - - // check(dataOut); - - // reset.put(false); - - // await clk.waitCycles(2); - - // check(dataOut); - - // await clk.waitCycles(3); - - // expect(dataOut.value.toInt(), 0x45); - - // await Simulator.endSimulation(); - // } - - // test('null reset value', () async { - // await resetTest(null, (dataOut) { - // expect(dataOut.value.toInt(), 0); - // }); - // }); - - // test('constant reset value', () async { - // await resetTest(0x56, (dataOut) { - // expect(dataOut.value.toInt(), 0x56); - // }); - // }); - - // test('logic reset value', () async { - // await resetTest(Const(0x78, width: 8), (dataOut) { - // expect(dataOut.value.toInt(), 0x78); - // }); - // }); - // }); - - // test('enabled and reset shift register', () async { - // final dataIn = Logic(width: 8); - // final clk = SimpleClockGenerator(10).clk; - // const latency = 5; - // final enable = Logic(); - // final reset = Logic(); - // final dataOut = ShiftRegister(dataIn, - // clk: clk, depth: latency, enable: enable, reset: reset) - // .dataOut; - - // unawaited(Simulator.run()); - - // enable.put(true); - // dataIn.put(0x45); - // reset.put(true); - - // await clk.nextPosedge; - // reset.put(false); - - // await clk.nextPosedge; - - // dataIn.put(0); - - // await clk.waitCycles(2); - - // enable.put(false); - - // await clk.waitCycles(20); - - // enable.put(true); - - // expect(dataOut.value.toInt(), 0); - - // await clk.waitCycles(2); - - // expect(dataOut.value.toInt(), 0x45); - - // await clk.nextPosedge; - - // expect(dataOut.value.toInt(), 0); - - // await Simulator.endSimulation(); - // }); - - // group('list reset value shift register', () { - // Future listResetTest( - // dynamic resetVal, void Function(Logic dataOut) check) async { - // final dataIn = Logic(width: 8); - // final clk = SimpleClockGenerator(10).clk; - // const depth = 5; - // final reset = Logic(); - // final dataOut = ShiftRegister(dataIn, - // clk: clk, depth: depth, reset: reset, resetValue: resetVal) - // .dataOut; - - // unawaited(Simulator.run()); - - // dataIn.put(0x45); - // reset.put(true); - - // await clk.nextPosedge; + test('simple CSR top', () async { + const csrWidth = 32; - // reset.put(false); + final csrTopCfg = MyCsrModule(numBlocks: 4); - // await clk.waitCycles(3); + final clk = SimpleClockGenerator(10).clk; + final reset = Logic()..inject(0); + final wIntf = DataPortInterface(csrWidth, 32); + final rIntf = DataPortInterface(csrWidth, 32); + final csrTop = CsrTop(csrTopCfg, clk, reset, wIntf, rIntf); - // check(dataOut); + wIntf.en.inject(0); + wIntf.addr.inject(0); + wIntf.data.inject(0); + rIntf.en.inject(0); + rIntf.addr.inject(0); - // await Simulator.endSimulation(); - // } + await csrTop.build(); - // test('list of logics reset value', () async { - // await listResetTest([ - // Logic(width: 8)..put(0x2), - // Logic(width: 8)..put(0x10), - // Logic(width: 8)..put(0x22), - // Logic(width: 8)..put(0x33), - // Logic(width: 8)..put(0x42), - // ], (dataOut) { - // expect(dataOut.value.toInt(), 0x10); - // }); - // }); - - // test('list of mixed reset value', () async { - // await listResetTest([ - // Logic(width: 8)..put(0x2), - // 26, - // Logic(width: 8)..put(0x22), - // true, - // Logic(width: 8)..put(0x42), - // ], (dataOut) { - // expect(dataOut.value.toInt(), 0x1A); - // }); - // }); - // }); - - // group('async reset shift register', () { - // Future asyncResetTest( - // dynamic resetVal, void Function(Logic dataOut) check) async { - // final dataIn = Logic(width: 8); - // final clk = SimpleClockGenerator(10).clk; - // const depth = 5; - // final reset = Logic(); - // final dataOut = ShiftRegister(dataIn, - // clk: Const(0), - // depth: depth, - // reset: reset, - // resetValue: resetVal, - // asyncReset: true) - // .dataOut; + WaveDumper(csrTop); - // unawaited(Simulator.run()); + Simulator.setMaxSimTime(10000); + unawaited(Simulator.run()); - // dataIn.put(0x42); + // grab pointers to certain blocks and CSRs + final block1 = csrTop.getBlock('block_0'); + final block2 = csrTop.getBlockByAddr(0x100); + final csr1 = block1.getRegister('csr1'); + final csr2 = block2.getRegisterByAddr(0x2); - // reset.inject(false); + // perform a reset + reset.inject(1); + await clk.waitCycles(10); + reset.inject(0); + await clk.waitCycles(10); - // await clk.waitCycles(1); + // perform a read to a particular register in a particular block + final addr1 = + block2.getAddr(rIntf.addrWidth) + csr2.getAddr(rIntf.addrWidth); + await clk.nextNegedge; + rIntf.en.inject(1); + rIntf.addr.inject(addr1.value); + await clk.nextNegedge; + rIntf.en.inject(0); + expect(rIntf.data.value, csr2.getResetValue().value); + await clk.waitCycles(10); - // reset.inject(true); + // perform a write to a particular register in a particular block + final addr2 = + block1.getAddr(rIntf.addrWidth) + csr1.getAddr(rIntf.addrWidth); + await clk.nextNegedge; + wIntf.en.inject(1); + wIntf.addr.inject(addr2.value); + wIntf.data.inject(0xdeadbeef); + await clk.nextNegedge; + wIntf.en.inject(0); + rIntf.en.inject(1); + rIntf.addr.inject(addr2.value); + await clk.nextNegedge; + rIntf.en.inject(0); + expect(rIntf.data.value, LogicValue.ofInt(0xad00ff, rIntf.dataWidth)); + await clk.waitCycles(10); - // await clk.waitCycles(1); + // perform a read to an invalid block + await clk.nextNegedge; + rIntf.en.inject(1); + rIntf.addr.inject(0xffffffff); + await clk.nextNegedge; + rIntf.en.inject(0); + expect(rIntf.data.value, LogicValue.ofInt(0, rIntf.dataWidth)); + await clk.waitCycles(10); - // check(dataOut); - - // await Simulator.endSimulation(); - // } - - // test('async reset value', () async { - // await asyncResetTest(Const(0x78, width: 8), (dataOut) { - // expect(dataOut.value.toInt(), 0x78); - // }); - // }); - - // test('async null reset value', () async { - // await asyncResetTest(null, (dataOut) { - // expect(dataOut.value.toInt(), 0); - // }); - // }); - - // test('async reset with list mixed type', () async { - // await asyncResetTest([ - // Logic(width: 8)..put(0x2), - // 59, - // Const(0x78, width: 8), - // Logic(width: 8)..put(0x33), - // true, - // ], (dataOut) { - // expect(dataOut.value.toInt(), 0x1); - // }); - // }); - // }); + await Simulator.endSimulation(); + await Simulator.simulationEnded; + }); } From b976055913a1845a282bae8c85a8de6521047417 Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Tue, 7 Jan 2025 15:07:40 -0800 Subject: [PATCH 04/14] Addressing various feedback items. --- lib/src/memory/csr.dart | 488 +++++++++++++-------------------- lib/src/memory/csr_config.dart | 236 ++++++++++++++++ lib/src/memory/memories.dart | 1 + test/csr_test.dart | 101 +++---- 4 files changed, 486 insertions(+), 340 deletions(-) create mode 100644 lib/src/memory/csr_config.dart diff --git a/lib/src/memory/csr.dart b/lib/src/memory/csr.dart index 070e2b933..1e4c144cb 100644 --- a/lib/src/memory/csr.dart +++ b/lib/src/memory/csr.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2023-2024 Intel Corporation +// Copyright (C) 2024-2025 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // csr.dart @@ -10,220 +10,61 @@ import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; -/// definitions for various register field access patterns -enum CsrFieldAccess { - /// register field is read only - FIELD_READ_ONLY, - - /// register field can be read and written - FIELD_READ_WRITE, - - /// writing 1's to the field triggers some other action - /// but the field itself is read only - FIELD_W1C, -} - -/// definitions for various register access patterns -enum CsrAccess { - /// register field is read only - REGISTER_READ_ONLY, - - /// register field can be read and written - REGISTER_READ_WRITE, -} - -/// configuration for a register field -/// if starting bit position and/or width is symbolic based on -/// the parent register width and/or other fields -/// it is assumed that those symbolic values are resolved -/// before construction of the field config -/// if a field should be duplicated n times within a register, -/// it is assumed that this object is instantiated n times -/// within the parent register config accordingly -class CsrFieldConfig { - /// starting bit position of the field in the register - final int start; - - /// number of bits in the field - final int width; - - /// name for the field - final String name; - - /// access rules for the field - final CsrFieldAccess access; - - /// construct a new field configuration - CsrFieldConfig({ - required this.start, - required this.width, - required this.name, - required this.access, - }); -} - -/// configuration for an architectural register -/// any architecturally fixed fields can be added directly to this class -/// but any fields with implementation specific config should be left out -class CsrConfig { - /// name for the register - final String name; - - /// access rules for the register - final CsrAccess access; - - /// fields in this register - final List fields = []; - - /// construct a new register configuration - CsrConfig({ - required this.name, - required this.access, - }); - - /// add a register field to this register - void addField(CsrFieldConfig field) { - fields.add(field); - } -} - -/// configuration for a register instance -/// apply implementation specific information to an architectural register -/// this includes instantiation of fields that require runtime configuration -/// such runtime configuration might also apply for conditional -/// instantiation of fields within the register -class CsrInstanceConfig { - /// underlying architectural config - final CsrConfig arch; - - /// register's address within its block - final int addr; - - /// number of bits in the register - final int width; - - /// reset value for the register - final int resetValue; - - /// accessor to the name of the arch register - String get name => arch.name; - - /// accessor to the privilege of the register - CsrAccess get access => arch.access; - - /// accessor to the fields of the register - List get fields => arch.fields; - - /// construct a new register configuration - CsrInstanceConfig({ - required this.arch, - required this.addr, - required this.width, - required this.resetValue, - }); - - /// add a register field to this register instance - void addField(CsrFieldConfig field) { - arch.addField(field); - } -} - -/// definition for a coherent block of registers -/// blocks by definition are instantiations of registers and -/// hence require CsrInstanceConfig objects -/// this class is also where the choice to instantiate -/// any conditional registers should take place -class CsrBlockConfig { - /// name for the block - final String name; - - /// address of the first register in the block - final int baseAddr; - - /// registers in this block - final List registers = []; - - /// construct a new block configuration - CsrBlockConfig({ - required this.name, - required this.baseAddr, - }); - - /// add a register to this block - void addRegister(CsrInstanceConfig register) { - registers.add(register); - } -} - -/// definition for a top level module containing CSR blocks -/// this class is also where the choice to instantiate -/// any conditional blocks should take place -class CsrTopConfig { - /// name for the top module - final String name; - - /// number of LSBs in an incoming address to ignore - /// when assessing the address of a block - final int blockOffsetWidth; - - /// blocks in this module - final List blocks = []; - - /// construct a new top level configuration - CsrTopConfig({ - required this.name, - required this.blockOffsetWidth, - }); - - /// add a block to this module - void addBlock(CsrBlockConfig block) { - blocks.add(block); - } -} - -/// Logic representation of a CSR +/// Logic representation of a CSR. +/// +/// Semantically, a register can be created with no fields. +/// In this case, a single implicit field is created that is +/// read/write and the entire width of the register. class Csr extends LogicStructure { - /// bit width of the CSR - final int csrWidth; + /// Configuration for the CSR. + final CsrInstanceConfig config; - /// address for the CSR - final int addr; + /// A list of indices of all of the reserved fields in the CSR. + /// This is necessary because the configuration does not explicitly + /// define reserved fields, but they must be accounted for + /// in certain logic involving the CSR. + final List rsvdIndices; - /// reset value for the CSR - final int resetValue; + /// Getter for the address of the CSR. + int get addr => config.addr; - /// access control for the CSR - final CsrAccess access; + /// Getter for the reset value of the CSR. + int get resetValue => config.resetValue; - /// CSR fields - final List fields; + /// Getter for the access control of the CSR. + CsrAccess get access => config.access; - /// access control for each field - final List fieldAccess; + /// Getter for the field configuration of the CSR + List get fields => config.fields; + /// Explicit constructor. + /// + /// This constructor is private and should not be used directly. + /// Instead, the factory constructor [Csr] should be used. + /// This facilitates proper calling of the super constructor. Csr._({ - required super.name, - required this.csrWidth, - required this.addr, - required this.resetValue, - required this.access, - required this.fields, - required this.fieldAccess, - }) : super(fields); - + required this.config, + required this.rsvdIndices, + required List fields, + }) : super(fields, name: config.name); + + /// Factory constructor for [Csr]. + /// + /// Because LogicStructure requires a List upon construction, + /// the factory method assists in creating the List upfront before + /// the LogicStructure constructor is called. factory Csr( CsrInstanceConfig config, ) { final fields = []; + final rsvds = []; var currIdx = 0; var rsvdCount = 0; - final acc = []; // semantically a register with no fields means that // there is one read/write field that is the entire register if (config.fields.isEmpty) { fields.add(Logic(name: '${config.name}_data', width: config.width)); - acc.add(CsrFieldAccess.FIELD_READ_WRITE); } // there is at least one field explicitly defined so // process them individually @@ -233,100 +74,149 @@ class Csr extends LogicStructure { fields.add(Logic( name: '${config.name}_rsvd_$rsvdCount', width: field.start - currIdx)); - acc.add(CsrFieldAccess.FIELD_READ_ONLY); + rsvds.add(fields.length - 1); + rsvdCount++; } fields.add( Logic(name: '${config.name}_${field.name}', width: field.width)); - acc.add(field.access); currIdx = field.start + field.width; } if (currIdx < config.width) { fields.add(Logic( name: '${config.name}_rsvd_$rsvdCount', width: config.width - currIdx)); - acc.add(CsrFieldAccess.FIELD_READ_ONLY); + rsvds.add(fields.length - 1); } } return Csr._( - name: config.name, - csrWidth: config.width, - addr: config.addr, - resetValue: config.resetValue, - access: config.access, + config: config, + rsvdIndices: rsvds, fields: fields, - fieldAccess: acc, ); } - /// extract the address as a Const - Logic getAddr(int addrWidth) => Const(LogicValue.ofInt(addr, addrWidth)); - - /// extract the reset value as a Const - Logic getResetValue() => Const(LogicValue.ofInt(resetValue, width)); - - /// method to return an individual by name + /// Accessor to the bits of a particular field within the CSR by name [nm]. Logic getField(String nm) => - fields.firstWhere((element) => element.name == '${name}_$nm'); + elements.firstWhere((element) => element.name == '${name}_$nm'); - /// perform mapping of original write data based on access config + /// Accessor to the config of a particular field within the CSR by name [nm]. + CsrFieldConfig getFieldConfigByName(String nm) => config.getFieldByName(nm); + + /// Given some arbitrary data [wd] to write to this CSR, + /// return the data that should actually be written based + /// on the access control of the CSR and its fields. Logic getWriteData(Logic wd) { // if the whole register is ready only, return the current value - if (access == CsrAccess.REGISTER_READ_ONLY) { + if (access == CsrAccess.READ_ONLY) { return this; } // register can be written, but still need to look at the fields... else { + // special case of no explicit fields defined + // in this case, we have an implicit read/write field + // so there is nothing special to do + if (fields.isEmpty) { + return wd; + } + + // otherwise, we need to look at the fields var finalWd = wd; var currIdx = 0; - for (var i = 0; i < fields.length; i++) { - // if the given field is read only, take the current value - // instead of the new value - final chk = fieldAccess[i] == CsrFieldAccess.FIELD_READ_ONLY || - fieldAccess[i] == CsrFieldAccess.FIELD_W1C; - if (chk) { - finalWd = finalWd.withSet(currIdx, fields[i]); + var currField = 0; + for (var i = 0; i < elements.length; i++) { + // if the given field is reserved or read only + // take the current value instead of the new value + final chk1 = rsvdIndices.contains(i); + if (chk1) { + finalWd = finalWd.withSet(currIdx, elements[i]); + currIdx += elements[i].width; + continue; + } + + // if the given field is read only + // take the current value instead of the new value + final chk2 = fields[currField].access == CsrFieldAccess.READ_ONLY || + fields[currField].access == CsrFieldAccess.WRITE_ONES_CLEAR; + if (chk2) { + finalWd = finalWd.withSet(currIdx, elements[i]); + currField++; + currIdx += elements[i].width; + continue; + } + + if (fields[currField].access == CsrFieldAccess.READ_WRITE_LEGAL) { + // if the given field is write legal + // make sure the value is in fact legal + // and transform it if not + final origVal = + wd.getRange(currIdx, currIdx + fields[currField].width); + final legalCases = {}; + for (var i = 0; i < fields[currField].legalValues.length; i++) { + legalCases[Const(fields[currField].legalValues[i], + width: fields[currField].width)] = origVal; + } + final newVal = cases( + origVal, + conditionalType: ConditionalType.unique, + legalCases, + defaultValue: Const(fields[currField].transformIllegalValue(), + width: fields[currField].width)); + + finalWd = finalWd.withSet(currIdx, newVal); + currField++; + currIdx += elements[i].width; + } else { + // normal read/write field + currField++; + currIdx += elements[i].width; } - currIdx += fields[i].width; } return finalWd; } } } -/// a submodule representing a block of CSRs +/// Logic representation of a block of registers. +/// +/// A block is just a collection of registers that are +/// readable and writable through an addressing scheme +/// that is relative (offset from) the base address of the block. // TODO: // backdoor reads and writes class CsrBlock extends Module { - /// address for this block - final int addr; - - /// width of address in bits - final int addrWidth; + /// Configuration for the CSR block. + final CsrBlockConfig config; - /// CSRs in this block + /// CSRs in this block. final List csrs; - /// clk for the module + /// Clock for the module. late final Logic _clk; - /// reset for the module + /// Reset for the module. late final Logic _reset; - /// interface for frontdoor writes to CSRs + /// Interface for frontdoor writes to CSRs. late final DataPortInterface _frontWrite; - /// interface for frontdoor reads to CSRs + /// Interface for frontdoor reads to CSRs. late final DataPortInterface _frontRead; + /// Getter for block's base address + int get baseAddr => config.baseAddr; + + /// Getter for the CSR configurations. + List get registers => config.registers; + + /// Constructor for a CSR block. CsrBlock._({ - required super.name, - required this.addr, + required this.config, required this.csrs, required Logic clk, required Logic reset, required DataPortInterface fdw, required DataPortInterface fdr, - }) : addrWidth = fdw.addrWidth { + }) : super(name: config.name) { _clk = addInput('clk', clk); _reset = addInput('reset', reset); @@ -344,7 +234,7 @@ class CsrBlock extends Module { _buildLogic(); } - /// create the CsrBlock from a configuration + /// Create the CsrBlock from a configuration. factory CsrBlock( CsrBlockConfig config, Logic clk, @@ -357,8 +247,7 @@ class CsrBlock extends Module { csrs.add(Csr(reg)); } return CsrBlock._( - name: config.name, - addr: config.baseAddr, + config: config, csrs: csrs, clk: clk, reset: reset, @@ -367,38 +256,40 @@ class CsrBlock extends Module { ); } - /// extract the address as a Const - Logic getAddr(int addrWidth) => Const(LogicValue.ofInt(addr, addrWidth)); - - /// method to return an individual register by name - Csr getRegister(String nm) => - csrs.firstWhere((element) => element.name == nm); - - /// method to return an individual by address - Csr getRegisterByAddr(int addr) => - csrs.firstWhere((element) => element.addr == addr); + /// Accessor to the config of a particular register + /// within the block by name [nm]. + CsrInstanceConfig getRegisterByName(String nm) => + config.getRegisterByName(nm); - /// API method to extract read data from block - Logic rdData() => _frontRead.data; + /// Accessor to the config of a particular register + /// within the block by relative address [addr]. + CsrInstanceConfig getRegisterByAddr(int addr) => + config.getRegisterByAddr(addr); void _buildLogic() { + final addrWidth = _frontWrite.addrWidth; + // individual CSR write logic for (final csr in csrs) { Sequential(_clk, reset: _reset, resetValues: { - csr: csr.getResetValue(), + csr: csr.resetValue, }, [ - If(_frontWrite.en & _frontWrite.addr.eq(csr.getAddr(addrWidth)), then: [ - csr < csr.getWriteData(_frontWrite.data), - ], orElse: [ - csr < csr, - ]), + If( + _frontWrite.en & + _frontWrite.addr.eq(Const(csr.addr, width: addrWidth)), + then: [ + csr < csr.getWriteData(_frontWrite.data), + ], + orElse: [ + csr < csr, + ]), ]); } // individual CSR read logic final rdData = Logic(name: 'rdData', width: _frontRead.dataWidth); final rdCases = csrs - .map((csr) => CaseItem(csr.getAddr(addrWidth), [ + .map((csr) => CaseItem(Const(csr.addr, width: addrWidth), [ rdData < csr, ])) .toList(); @@ -411,43 +302,52 @@ class CsrBlock extends Module { } } -/// top level CSR module +/// Top level module encapsulating groups of CSRs. +/// +/// This top module can include arbitrarily many CSR blocks. +/// Individual blocks are addressable using some number of +/// MSBs of the incoming address and registers within the given block +/// are addressable using the remaining LSBs of the incoming address. +// TODO: +// backdoor reads and writes class CsrTop extends Module { - /// width of address in bits - final int addrWidth; - /// width of the LSBs of the address /// to ignore when mapping to blocks final int blockOffsetWidth; - /// CSRs in this block + /// Configuration for the CSR Top module. + final CsrTopConfig config; + + /// List of CSR blocks in this module. final List _blocks = []; - /// clk for the module + /// Clock for the module. late final Logic _clk; - /// reset for the module + /// Reset for the module. late final Logic _reset; - /// interface for frontdoor writes to CSRs + /// Interface for frontdoor writes to CSRs. late final DataPortInterface _frontWrite; - /// interface for frontdoor reads to CSRs + /// Interface for frontdoor reads to CSRs. late final DataPortInterface _frontRead; // individual sub interfaces to blocks final List _fdWrites = []; final List _fdReads = []; + /// Getter for the block configurations of the CSR. + List get blocks => config.blocks; + CsrTop._({ - required super.name, - required this.blockOffsetWidth, + required this.config, + required this.blockOffsetWidth, // TODO: make this part of the config?? required Logic clk, required Logic reset, required DataPortInterface fdw, required DataPortInterface fdr, - required List bCfgs, - }) : addrWidth = fdw.addrWidth { + }) : super(name: config.name) { _clk = addInput('clk', clk); _reset = addInput('reset', reset); @@ -455,14 +355,14 @@ class CsrTop extends Module { ..connectIO(this, fdw, inputTags: {DataPortGroup.control, DataPortGroup.data}, outputTags: {}, - uniquify: (original) => 'frontWrite_$original'); + uniquify: (original) => '${name}_frontWrite_$original'); _frontRead = fdr.clone() ..connectIO(this, fdr, inputTags: {DataPortGroup.control}, outputTags: {DataPortGroup.data}, - uniquify: (original) => 'frontRead_$original'); + uniquify: (original) => '${name}_frontRead_$original'); - for (final block in bCfgs) { + 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)); @@ -480,16 +380,25 @@ class CsrTop extends Module { DataPortInterface fdr, ) => CsrTop._( - name: config.name, + config: config, blockOffsetWidth: config.blockOffsetWidth, clk: clk, reset: reset, fdw: fdw, fdr: fdr, - bCfgs: config.blocks, ); + /// Accessor to the config of a particular register block + /// within the module by name [nm]. + CsrBlockConfig getBlockByName(String nm) => config.getBlockByName(nm); + + /// Accessor to the config of a particular register block + /// within the module by relative address [addr]. + CsrBlockConfig getBlockByAddr(int addr) => config.getBlockByAddr(addr); + void _buildLogic() { + final addrWidth = _frontWrite.addrWidth; + // mask out LSBs to perform a match on block final maskedFrontWrAddr = _frontWrite.addr & ~Const((1 << blockOffsetWidth) - 1, width: addrWidth); @@ -503,9 +412,13 @@ class CsrTop extends Module { // drive frontdoor write and read inputs for (var i = 0; i < _blocks.length; i++) { _fdWrites[i].en <= - _frontWrite.en & maskedFrontWrAddr.eq(_blocks[i].getAddr(addrWidth)); + _frontWrite.en & + maskedFrontWrAddr + .eq(Const(_blocks[i].baseAddr, width: addrWidth)); _fdReads[i].en <= - _frontWrite.en & maskedFrontRdAddr.eq(_blocks[i].getAddr(addrWidth)); + _frontWrite.en & + maskedFrontRdAddr + .eq(Const(_blocks[i].baseAddr, width: addrWidth)); _fdWrites[i].addr <= shiftedFrontWrAddr; _fdReads[i].addr <= shiftedFrontRdAddr; @@ -516,9 +429,12 @@ class CsrTop extends Module { // capture frontdoor read output final rdData = Logic(name: 'rdData', width: _frontRead.dataWidth); final rdCases = _blocks - .map((block) => CaseItem(block.getAddr(addrWidth), [ - rdData < block.rdData(), - ])) + .asMap() + .entries + .map( + (block) => CaseItem(Const(block.value.baseAddr, width: addrWidth), [ + rdData < _fdReads[block.key].data, + ])) .toList(); Combinational([ Case(maskedFrontRdAddr, rdCases, defaultItem: [ @@ -527,12 +443,4 @@ class CsrTop extends Module { ]); _frontRead.data <= rdData; } - - /// method to return an individual by name - CsrBlock getBlock(String nm) => - _blocks.firstWhere((element) => element.name == nm); - - /// method to return an individual by address - CsrBlock getBlockByAddr(int addr) => - _blocks.firstWhere((element) => element.addr == addr); } diff --git a/lib/src/memory/csr_config.dart b/lib/src/memory/csr_config.dart new file mode 100644 index 000000000..0b6df4186 --- /dev/null +++ b/lib/src/memory/csr_config.dart @@ -0,0 +1,236 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// csr_config.dart +// Configuration objects for defining CSRs. +// +// 2024 December +// Author: Josh Kimmel + +/// Definitions for various register field access patterns. +enum CsrFieldAccess { + /// Register field is read only. + // ignore: constant_identifier_names + READ_ONLY, + + /// Register field can be read and written. + // ignore: constant_identifier_names + READ_WRITE, + + /// Writing 1's to the field triggers some other action, + /// but the field itself is read only. + // ignore: constant_identifier_names + WRITE_ONES_CLEAR, + + /// Only legal values can be written + // ignore: constant_identifier_names + READ_WRITE_LEGAL, +} + +/// Definitions for various register access patterns. +enum CsrAccess { + /// Register is read only. + // ignore: constant_identifier_names + READ_ONLY, + + /// Register can be read and written. + // ignore: constant_identifier_names + READ_WRITE, +} + +/// Configuration for a register field. +/// +/// If [start] and/or [width] is symbolic based on +/// the parent register width and/or other fields, +/// it is assumed that those symbolic values are resolved +/// before construction of the field config. +/// If a field should be duplicated n times within a register, +/// it is assumed that this object is instantiated n times +/// within the parent register config accordingly. +class CsrFieldConfig { + /// Starting bit position of the field in the register. + final int start; + + /// Number of bits in the field. + final int width; + + /// Name for the field. + final String name; + + /// Access rule for the field. + final CsrFieldAccess access; + + /// A list of legal values for the field. + /// + /// This list can be empty and in general is only + /// applicable for fields with FIELD_WRITE_ANY_READ_LEGAL access. + final List legalValues = []; + + /// Construct a new field configuration. + CsrFieldConfig({ + required this.start, + required this.width, + required this.name, + required this.access, + }); + + /// Add a legal value for the field. + /// + /// Only applicable for fields with FIELD_WRITE_ANY_READ_LEGAL access. + void addLegalValue(int val) => legalValues.add(val); + + /// Method to return a legal value from an illegal one. + /// + /// Only applicable for fields with FIELD_WRITE_ANY_READ_LEGAL access. + /// This is a default implementation that simply takes the first + /// legal value but can be overridden in a derived class. + int transformIllegalValue() => legalValues[0]; +} + +/// Configuration for an architectural register. +/// +/// Any architecturally fixed fields can be added directly to this class, +/// but any fields with implementation specific config should be +/// left until the instantiation of the register. +class CsrConfig { + /// Name for the register. + final String name; + + /// Access rule for the register. + final CsrAccess access; + + /// Architectural reset value for the register. + /// + /// Note that this can be overridden in the instantiation of the register. + int resetValue; + + /// Fields in this register. + final List fields = []; + + /// Construct a new register configuration. + CsrConfig({ + required this.name, + required this.access, + this.resetValue = 0, + }); + + /// Accessor to the config of a particular field + /// within the register by name [nm]. + CsrFieldConfig getFieldByName(String nm) => + fields.firstWhere((element) => element.name == nm); +} + +/// Configuration for a register instance. +/// +/// Apply implementation specific information to an architectural register. +/// This includes instantiation of fields that require runtime configuration. +/// Such runtime configuration might also apply for conditional +/// instantiation of fields within the register +class CsrInstanceConfig { + /// Underlying architectural configuration. + final CsrConfig arch; + + /// Register's address within its block + /// + /// This can be thought of as an offset relative to the block address. + /// This can also be thought of as a unique ID for this register. + final int addr; + + /// Number of bits in the register. + final int width; + + /// Accessor to the name of the architectural register. + String get name => arch.name; + + /// Accessor to the architectural access rule of the register. + CsrAccess get access => arch.access; + + /// Accessor to the architectural reset value of the register. + int get resetValue => arch.resetValue; + + /// Accessor to the fields of the register. + List get fields => arch.fields; + + /// Construct a new register configuration. + CsrInstanceConfig({ + required this.arch, + required this.addr, + required this.width, + int? resetValue, + }) { + if (resetValue != null) { + arch.resetValue = resetValue; + } + } + + /// Accessor to the config of a particular field + /// within the register by name [nm]. + CsrFieldConfig getFieldByName(String nm) => arch.getFieldByName(nm); +} + +/// Definition for a coherent block of registers. +/// +/// Blocks by definition are instantiations of registers and +/// hence require CsrInstanceConfig objects. +/// This class is also where the choice to instantiate +/// any conditional registers should take place. +class CsrBlockConfig { + /// Name for the block. + final String name; + + /// Address off of which all register addresses are offset. + final int baseAddr; + + /// Registers in this block. + final List registers = []; + + /// Construct a new block configuration. + CsrBlockConfig({ + required this.name, + required this.baseAddr, + }); + + /// Accessor to the config of a particular register + /// within the block by name [nm]. + CsrInstanceConfig getRegisterByName(String nm) => + registers.firstWhere((element) => element.name == nm); + + /// Accessor to the config of a particular register + /// within the block by relative address [addr]. + CsrInstanceConfig getRegisterByAddr(int addr) => + registers.firstWhere((element) => element.addr == addr); +} + +/// Definition for a top level module containing CSR blocks. +/// +/// This class is also where the choice to instantiate +/// any conditional blocks should take place. +class CsrTopConfig { + /// Name for the top module. + final String name; + + /// Address bits dedicated to the individual registers. + /// + /// This is effectively the number of LSBs in an incoming address + /// to ignore when assessing the address of a block. + final int blockOffsetWidth; + + /// Blocks in this module. + final List blocks = []; + + /// Construct a new top level configuration. + CsrTopConfig({ + required this.name, + required this.blockOffsetWidth, + }); + + /// Accessor to the config of a particular register block + /// within the module by name [nm]. + CsrBlockConfig getBlockByName(String nm) => + blocks.firstWhere((element) => element.name == nm); + + /// Accessor to the config of a particular register block + /// within the module by relative address [addr]. + CsrBlockConfig getBlockByAddr(int addr) => + blocks.firstWhere((element) => element.baseAddr == addr); +} diff --git a/lib/src/memory/memories.dart b/lib/src/memory/memories.dart index 639281ab7..17e79f02e 100644 --- a/lib/src/memory/memories.dart +++ b/lib/src/memory/memories.dart @@ -2,5 +2,6 @@ // SPDX-License-Identifier: BSD-3-Clause export 'csr.dart'; +export 'csr_config.dart'; export 'memory.dart'; export 'register_file.dart'; diff --git a/test/csr_test.dart b/test/csr_test.dart index 9fe417adb..8a37920d6 100644 --- a/test/csr_test.dart +++ b/test/csr_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2023-2024 Intel Corporation +// Copyright (C) 2024-2025 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // csr_test.dart @@ -18,7 +18,7 @@ import 'package:test/test.dart'; class MyNoFieldCsr extends CsrConfig { MyNoFieldCsr({super.name = 'myNoFieldCsr'}) - : super(access: CsrAccess.REGISTER_READ_ONLY); + : super(access: CsrAccess.READ_ONLY); } class MyNoFieldCsrInstance extends CsrInstanceConfig { @@ -31,8 +31,7 @@ class MyNoFieldCsrInstance extends CsrInstanceConfig { } class MyFieldCsr extends CsrConfig { - MyFieldCsr({super.name = 'myFieldCsr'}) - : super(access: CsrAccess.REGISTER_READ_WRITE); + MyFieldCsr({super.name = 'myFieldCsr'}) : super(access: CsrAccess.READ_WRITE); } class MyFieldCsrInstance extends CsrInstanceConfig { @@ -43,24 +42,22 @@ class MyFieldCsrInstance extends CsrInstanceConfig { String name = 'myFieldCsrInstance', }) : super(arch: MyFieldCsr(name: name)) { // example of a static field - addField(CsrFieldConfig( - start: 0, - width: 2, - name: 'field1', - access: CsrFieldAccess.FIELD_READ_ONLY)); - // example of a field with dynamic start and width - addField(CsrFieldConfig( - start: width ~/ 2, - width: width ~/ 4, - name: 'field2', - access: CsrFieldAccess.FIELD_READ_WRITE)); + fields + ..add(CsrFieldConfig( + start: 0, width: 2, name: 'field1', access: CsrFieldAccess.READ_ONLY)) + // example of a field with dynamic start and width + ..add(CsrFieldConfig( + start: width ~/ 2, + width: width ~/ 4, + name: 'field2', + access: CsrFieldAccess.READ_WRITE)); // example of field duplication for (var i = 0; i < width ~/ 4; i++) { - addField(CsrFieldConfig( + fields.add(CsrFieldConfig( start: (3 * width ~/ 4) + i, width: 1, name: 'field3_$i', - access: CsrFieldAccess.FIELD_W1C)); + access: CsrFieldAccess.WRITE_ONES_CLEAR)); } } } @@ -78,13 +75,13 @@ class MyRegisterBlock extends CsrBlockConfig { this.evensOnly = false, }) { // static register instance - addRegister(MyFieldCsrInstance(addr: 0x0, width: csrWidth, name: 'csr1')); + registers.add(MyFieldCsrInstance(addr: 0x0, width: csrWidth, name: 'csr1')); // dynamic register instances for (var i = 0; i < numNoFieldCsrs; i++) { final chk = i.isEven || !evensOnly; if (chk) { - addRegister(MyNoFieldCsrInstance( + registers.add(MyNoFieldCsrInstance( addr: i + 1, width: csrWidth, name: 'csr2_$i')); } } @@ -102,7 +99,7 @@ class MyCsrModule extends CsrTopConfig { // example of dynamic block instantiation const baseAddr = 0x0; for (var i = 0; i < numBlocks; i++) { - addBlock(MyRegisterBlock( + blocks.add(MyRegisterBlock( baseAddr: baseAddr + (i * 0x100), numNoFieldCsrs: i + 1, evensOnly: i.isEven, @@ -119,19 +116,23 @@ void main() { }); test('simple individual CSRs', () async { + const dataWidth1 = 32; + const dataWidth2 = 8; + // register with no explicit fields, read only - final csr1Cfg = MyNoFieldCsrInstance(addr: 0x0, width: 32, name: 'csr1'); + final csr1Cfg = + MyNoFieldCsrInstance(addr: 0x0, width: dataWidth1, name: 'csr1'); final csr1 = Csr(csr1Cfg); // check address and reset value - expect(csr1.getAddr(8).value, LogicValue.ofInt(0x0, 8)); - expect(csr1.getResetValue().value, LogicValue.ofInt(0x0, 32)); + expect(csr1.addr, 0x0); + expect(csr1.resetValue, 0x0); csr1.put(csr1.resetValue); // check write data // should return the reset value, since it's read only - final wd1 = csr1.getWriteData(Const(0x12345678, width: 32)); - expect(wd1.value, LogicValue.ofInt(0x0, 32)); + final wd1 = csr1.getWriteData(Const(0x12345678, width: dataWidth1)); + expect(wd1.value, LogicValue.ofInt(0x0, dataWidth1)); // register with 3 explicit fields, read/write // fields don't cover full width of register @@ -139,15 +140,15 @@ void main() { final csr2 = Csr(csr2Cfg); // check address and reset value - expect(csr2.getAddr(8).value, LogicValue.ofInt(0x1, 8)); - expect(csr2.getResetValue().value, LogicValue.ofInt(0xff, 8)); + expect(csr2.addr, 0x1); + expect(csr2.resetValue, 0xff); csr2.put(csr2.resetValue); // check the write data // only some of what we're trying to write should // given the field access rules - final wd2 = csr2.getWriteData(Const(0xab, width: 8)); - expect(wd2.value, LogicValue.ofInt(0xef, 8)); + final wd2 = csr2.getWriteData(Const(0xab, width: dataWidth2)); + expect(wd2.value, LogicValue.ofInt(0xef, dataWidth2)); // check grabbing individual fields final f1 = csr2.getField('field1'); @@ -189,11 +190,11 @@ void main() { unawaited(Simulator.run()); // retrieve block address - final blkAddr = csrBlock.getAddr(16); - expect(blkAddr.value, LogicValue.ofInt(0x0, 16)); + final blkAddr = csrBlock.baseAddr; + expect(blkAddr, 0x0); // grab pointers to the CSRs - final csr1 = csrBlock.getRegister('csr1'); + final csr1 = csrBlock.getRegisterByName('csr1'); final csr2 = csrBlock.getRegisterByAddr(0x2); // perform a reset @@ -206,41 +207,42 @@ void main() { // ensure that the read data is the reset value await clk.nextNegedge; rIntf.en.inject(1); - rIntf.addr.inject(csr2.getAddr(rIntf.addrWidth).value); + rIntf.addr.inject(csr2.addr); await clk.nextNegedge; rIntf.en.inject(0); - expect(csrBlock.rdData().value, csr2.getResetValue().value); + expect( + rIntf.data.value, LogicValue.ofInt(csr2.resetValue, rIntf.dataWidth)); await clk.waitCycles(10); // perform a write of csr2 and then a read // ensure that the write takes no effect b/c readonly await clk.nextNegedge; wIntf.en.inject(1); - wIntf.addr.inject(csr2.getAddr(wIntf.addrWidth).value); + wIntf.addr.inject(csr2.addr); wIntf.data.inject(0xdeadbeef); await clk.nextNegedge; wIntf.en.inject(0); rIntf.en.inject(1); - rIntf.addr.inject(csr2.getAddr(rIntf.addrWidth).value); + rIntf.addr.inject(csr2.addr); await clk.nextNegedge; rIntf.en.inject(0); - expect(csrBlock.rdData().value, csr2.getResetValue().value); + expect( + rIntf.data.value, LogicValue.ofInt(csr2.resetValue, rIntf.dataWidth)); await clk.waitCycles(10); // perform a write of csr1 // ensure that the write data is modified appropriately await clk.nextNegedge; wIntf.en.inject(1); - wIntf.addr.inject(csr1.getAddr(wIntf.addrWidth).value); + wIntf.addr.inject(csr1.addr); wIntf.data.inject(0xdeadbeef); await clk.nextNegedge; wIntf.en.inject(0); rIntf.en.inject(1); - rIntf.addr.inject(csr1.getAddr(rIntf.addrWidth).value); + rIntf.addr.inject(csr1.addr); await clk.nextNegedge; rIntf.en.inject(0); - expect( - csrBlock.rdData().value, LogicValue.ofInt(0xad00ff, rIntf.dataWidth)); + expect(rIntf.data.value, LogicValue.ofInt(0xad00ff, rIntf.dataWidth)); await clk.waitCycles(10); // perform a read of nothing @@ -249,7 +251,7 @@ void main() { rIntf.addr.inject(0xff); await clk.nextNegedge; rIntf.en.inject(0); - expect(csrBlock.rdData().value, LogicValue.ofInt(0, rIntf.dataWidth)); + expect(rIntf.data.value, LogicValue.ofInt(0, rIntf.dataWidth)); await clk.waitCycles(10); await Simulator.endSimulation(); @@ -275,15 +277,15 @@ void main() { await csrTop.build(); - WaveDumper(csrTop); + // WaveDumper(csrTop); Simulator.setMaxSimTime(10000); unawaited(Simulator.run()); // grab pointers to certain blocks and CSRs - final block1 = csrTop.getBlock('block_0'); + final block1 = csrTop.getBlockByName('block_0'); final block2 = csrTop.getBlockByAddr(0x100); - final csr1 = block1.getRegister('csr1'); + final csr1 = block1.getRegisterByName('csr1'); final csr2 = block2.getRegisterByAddr(0x2); // perform a reset @@ -293,19 +295,18 @@ void main() { await clk.waitCycles(10); // perform a read to a particular register in a particular block - final addr1 = - block2.getAddr(rIntf.addrWidth) + csr2.getAddr(rIntf.addrWidth); + final addr1 = Const(block2.baseAddr + csr2.addr, width: rIntf.addrWidth); await clk.nextNegedge; rIntf.en.inject(1); rIntf.addr.inject(addr1.value); await clk.nextNegedge; rIntf.en.inject(0); - expect(rIntf.data.value, csr2.getResetValue().value); + expect( + rIntf.data.value, LogicValue.ofInt(csr2.resetValue, rIntf.dataWidth)); await clk.waitCycles(10); // perform a write to a particular register in a particular block - final addr2 = - block1.getAddr(rIntf.addrWidth) + csr1.getAddr(rIntf.addrWidth); + final addr2 = Const(block1.baseAddr + csr1.addr, width: rIntf.addrWidth); await clk.nextNegedge; wIntf.en.inject(1); wIntf.addr.inject(addr2.value); From bfc192bbcd01ce19b75b7d537274007a437a7265 Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Thu, 9 Jan 2025 10:56:44 -0800 Subject: [PATCH 05/14] Small tweaks. --- lib/src/memory/csr.dart | 20 ++++++++++++++------ pubspec.yaml | 4 ++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/src/memory/csr.dart b/lib/src/memory/csr.dart index 1e4c144cb..895ad7de6 100644 --- a/lib/src/memory/csr.dart +++ b/lib/src/memory/csr.dart @@ -294,9 +294,13 @@ class CsrBlock extends Module { ])) .toList(); Combinational([ - Case(_frontRead.addr, rdCases, defaultItem: [ - rdData < Const(0, width: _frontRead.dataWidth), - ]), + Case( + _frontRead.addr, + conditionalType: ConditionalType.unique, + rdCases, + defaultItem: [ + rdData < Const(0, width: _frontRead.dataWidth), + ]), ]); _frontRead.data <= rdData; } @@ -437,9 +441,13 @@ class CsrTop extends Module { ])) .toList(); Combinational([ - Case(maskedFrontRdAddr, rdCases, defaultItem: [ - rdData < Const(0, width: _frontRead.dataWidth), - ]), + Case( + maskedFrontRdAddr, + conditionalType: ConditionalType.unique, + rdCases, + defaultItem: [ + rdData < Const(0, width: _frontRead.dataWidth), + ]), ]); _frontRead.data <= rdData; } diff --git a/pubspec.yaml b/pubspec.yaml index c7241e144..8367e615b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,8 +12,8 @@ environment: dependencies: collection: ^1.18.0 meta: ^1.9.1 - rohd: ^0.5.2 - rohd_vf: ^0.5.0 + rohd: ^0.6.0 + rohd_vf: ^0.6.0 dev_dependencies: logging: ^1.0.1 From 1add2c5140f6593f5219402da51c7648f9333c84 Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Fri, 10 Jan 2025 13:22:07 -0800 Subject: [PATCH 06/14] Adding backdoor read and write functionality. --- lib/src/memory/csr.dart | 234 ++++++++++++++++++++++++++++++++++++---- test/csr_test.dart | 50 ++++++++- 2 files changed, 264 insertions(+), 20 deletions(-) diff --git a/lib/src/memory/csr.dart b/lib/src/memory/csr.dart index 895ad7de6..d2a6eab31 100644 --- a/lib/src/memory/csr.dart +++ b/lib/src/memory/csr.dart @@ -10,6 +10,74 @@ import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; +/// A grouping for interface signals of [CsrBackdoorInterface]s. +enum CsrBackdoorPortGroup { + /// For HW reads of CSRs. + read, + + /// For HW writes to CSRs. + write +} + +/// An interface to interact very simply with a CSR. +/// +/// Can be used for either read, write or both directions. +class CsrBackdoorInterface extends Interface { + /// Configuration for the associated CSR. + final CsrInstanceConfig config; + + /// Should this CSR be readable by the HW. + final bool hasRead; + + /// Should this CSR be writable by the HW. + final bool hasWrite; + + /// The width of data in the CSR. + final int dataWidth; + + /// The read data from the CSR. + Csr? get rdData => tryPort(config.name) as Csr?; + + /// Write the CSR in this cycle. + Logic? get wrEn => tryPort('${config.name}_wrEn'); + + /// Data to write to the CSR in this cycle. + Logic? get wrData => tryPort('${config.name}_wrData'); + + /// Constructs a new interface of specified [dataWidth] + /// and conditionally instantiates read and writes ports based on + /// [hasRead] and [hasWrite]. + CsrBackdoorInterface( + {required this.config, + this.dataWidth = 0, + this.hasRead = true, + this.hasWrite = true}) { + if (hasRead) { + setPorts([ + Csr(config), + ], [ + CsrBackdoorPortGroup.read, + ]); + } + + if (hasWrite) { + setPorts([ + Port('${config.name}_wrEn'), + Port('${config.name}_wrData', dataWidth), + ], [ + CsrBackdoorPortGroup.write, + ]); + } + } + + /// Makes a copy of this [Interface] with matching configuration. + CsrBackdoorInterface clone() => CsrBackdoorInterface( + config: config, + dataWidth: dataWidth, + hasRead: hasRead, + hasWrite: hasWrite); +} + /// Logic representation of a CSR. /// /// Semantically, a register can be created with no fields. @@ -181,8 +249,6 @@ class Csr extends LogicStructure { /// A block is just a collection of registers that are /// readable and writable through an addressing scheme /// that is relative (offset from) the base address of the block. -// TODO: -// backdoor reads and writes class CsrBlock extends Module { /// Configuration for the CSR block. final CsrBlockConfig config; @@ -190,6 +256,14 @@ class CsrBlock extends Module { /// CSRs in this block. final List csrs; + /// Direct access ports for reading and writing individual registers. + /// + /// There is a public copy that is exported out of the module + /// for consumption at higher levels in the hierarchy. + /// The private copies are used for internal logic. + final List backdoorInterfaces = []; + final List _backdoorInterfaces = []; + /// Clock for the module. late final Logic _clk; @@ -231,6 +305,19 @@ class CsrBlock extends Module { outputTags: {DataPortGroup.data}, uniquify: (original) => 'frontRead_$original'); + for (var i = 0; i < csrs.length; i++) { + // TODO: pull hasRead and hasWrite from config?? + _backdoorInterfaces.add(CsrBackdoorInterface( + config: csrs[i].config, dataWidth: fdr.dataWidth)); + backdoorInterfaces.add(CsrBackdoorInterface( + config: csrs[i].config, dataWidth: fdr.dataWidth)); + _backdoorInterfaces.last.connectIO(this, backdoorInterfaces.last, + outputTags: {CsrBackdoorPortGroup.read}, + inputTags: {CsrBackdoorPortGroup.write}, + uniquify: (original) => + '${name}_${csrs[i].config.name}_backdoor_$original'); + } + _buildLogic(); } @@ -266,28 +353,65 @@ class CsrBlock extends Module { CsrInstanceConfig getRegisterByAddr(int addr) => config.getRegisterByAddr(addr); + /// Accessor to the backdoor ports of a particular register + /// within the block by name [nm]. + CsrBackdoorInterface getBackdoorPortsByName(String nm) { + final idx = config.registers.indexOf(config.getRegisterByName(nm)); + if (idx >= 0 && idx < backdoorInterfaces.length) { + return backdoorInterfaces[idx]; + } else { + throw Exception('Register $nm not found in block ${config.name}'); + } + } + + /// Accessor to the backdoor ports of a particular register + /// within the block by relative address [addr]. + CsrBackdoorInterface getBackdoorPortsByAddr(int addr) { + final idx = config.registers.indexOf(config.getRegisterByAddr(addr)); + if (idx >= 0 && idx < backdoorInterfaces.length) { + return backdoorInterfaces[idx]; + } else { + throw Exception( + 'Register address $addr not found in block ${config.name}'); + } + } + void _buildLogic() { final addrWidth = _frontWrite.addrWidth; // individual CSR write logic - for (final csr in csrs) { - Sequential(_clk, reset: _reset, resetValues: { - csr: csr.resetValue, - }, [ - If( - _frontWrite.en & - _frontWrite.addr.eq(Const(csr.addr, width: addrWidth)), - then: [ - csr < csr.getWriteData(_frontWrite.data), - ], - orElse: [ - csr < csr, + for (var i = 0; i < csrs.length; i++) { + Sequential( + _clk, + reset: _reset, + resetValues: { + csrs[i]: csrs[i].resetValue, + }, + [ + If.block([ + // frontdoor write takes highest priority + Iff( + _frontWrite.en & + _frontWrite.addr.eq(Const(csrs[i].addr, width: addrWidth)), + [ + csrs[i] < csrs[i].getWriteData(_frontWrite.data), + ]), + // backdoor write takes next priority + if (_backdoorInterfaces[i].hasWrite) + ElseIf(_backdoorInterfaces[i].wrEn!, [ + csrs[i] < csrs[i].getWriteData(_backdoorInterfaces[i].wrData!), + ]), + // nothing to write this cycle + Else([ + csrs[i] < csrs[i], ]), - ]); + ]) + ], + ); } // individual CSR read logic - final rdData = Logic(name: 'rdData', width: _frontRead.dataWidth); + final rdData = Logic(name: 'internalRdData', width: _frontRead.dataWidth); final rdCases = csrs .map((csr) => CaseItem(Const(csr.addr, width: addrWidth), [ rdData < csr, @@ -303,6 +427,13 @@ class CsrBlock extends Module { ]), ]); _frontRead.data <= rdData; + + // driving of backdoor read outputs + for (var i = 0; i < csrs.length; i++) { + if (_backdoorInterfaces[i].hasRead) { + _backdoorInterfaces[i].rdData! <= csrs[i]; + } + } } } @@ -312,8 +443,6 @@ class CsrBlock extends Module { /// Individual blocks are addressable using some number of /// MSBs of the incoming address and registers within the given block /// are addressable using the remaining LSBs of the incoming address. -// TODO: -// backdoor reads and writes class CsrTop extends Module { /// width of the LSBs of the address /// to ignore when mapping to blocks @@ -341,9 +470,39 @@ class CsrTop extends Module { final List _fdWrites = []; final List _fdReads = []; + /// Direct access ports for reading and writing individual registers. + /// + /// There is a public copy that is exported out of the module + /// for consumption at higher levels in the hierarchy. + /// The private copies are used for internal logic. + final List> backdoorInterfaces = []; + final List> _backdoorInterfaces = []; + /// Getter for the block configurations of the CSR. List get blocks => config.blocks; + /// Accessor to the backdoor ports of a particular register [reg] + /// within the block [block]. + CsrBackdoorInterface getBackdoorPortsByName(String block, String reg) { + final idx = config.blocks.indexOf(config.getBlockByName(block)); + if (idx >= 0 && idx < backdoorInterfaces.length) { + return _blocks[idx].getBackdoorPortsByName(reg); + } else { + throw Exception('Block $block could not be found.'); + } + } + + /// Accessor to the backdoor ports of a particular register + /// using its address [regAddr] within the block with address [blockAddr]. + CsrBackdoorInterface getBackdoorPortsByAddr(int blockAddr, int regAddr) { + final idx = config.blocks.indexOf(config.getBlockByAddr(blockAddr)); + if (idx >= 0 && idx < backdoorInterfaces.length) { + return _blocks[idx].getBackdoorPortsByAddr(regAddr); + } else { + throw Exception('Block with address $blockAddr could not be found.'); + } + } + CsrTop._({ required this.config, required this.blockOffsetWidth, // TODO: make this part of the config?? @@ -372,6 +531,23 @@ class CsrTop extends Module { _blocks.add(CsrBlock(block, _clk, _reset, _fdWrites.last, _fdReads.last)); } + for (var i = 0; i < blocks.length; i++) { + _backdoorInterfaces.add([]); + backdoorInterfaces.add([]); + for (var j = 0; j < blocks[i].registers.length; j++) { + // TODO: pull hasRead and hasWrite from config?? + _backdoorInterfaces[i].add(CsrBackdoorInterface( + config: blocks[i].registers[j], dataWidth: fdr.dataWidth)); + backdoorInterfaces[i].add(CsrBackdoorInterface( + config: blocks[i].registers[j], dataWidth: fdr.dataWidth)); + _backdoorInterfaces[i].last.connectIO(this, backdoorInterfaces[i].last, + outputTags: {CsrBackdoorPortGroup.read}, + inputTags: {CsrBackdoorPortGroup.write}, + uniquify: (original) => + '${name}_${blocks[i].name}_${blocks[i].registers[j].name}_backdoor_$original'); + } + } + _buildLogic(); } @@ -428,10 +604,20 @@ class CsrTop extends Module { _fdReads[i].addr <= shiftedFrontRdAddr; _fdWrites[i].data <= _frontWrite.data; + + // drive backdoor write ports + for (var j = 0; j < blocks[i].registers.length; j++) { + if (_backdoorInterfaces[i][j].hasWrite) { + _blocks[i].backdoorInterfaces[j].wrEn! <= + _backdoorInterfaces[i][j].wrEn!; + _blocks[i].backdoorInterfaces[j].wrData! <= + _backdoorInterfaces[i][j].wrData!; + } + } } // capture frontdoor read output - final rdData = Logic(name: 'rdData', width: _frontRead.dataWidth); + final rdData = Logic(name: 'internalRdData', width: _frontRead.dataWidth); final rdCases = _blocks .asMap() .entries @@ -450,5 +636,15 @@ class CsrTop extends Module { ]), ]); _frontRead.data <= rdData; + + // driving of backdoor read outputs + for (var i = 0; i < blocks.length; i++) { + for (var j = 0; j < blocks[i].registers.length; j++) { + if (_backdoorInterfaces[i][j].hasRead) { + _backdoorInterfaces[i][j].rdData! <= + _blocks[i].backdoorInterfaces[j].rdData!; + } + } + } } } diff --git a/test/csr_test.dart b/test/csr_test.dart index 8a37920d6..912d63f5a 100644 --- a/test/csr_test.dart +++ b/test/csr_test.dart @@ -184,7 +184,14 @@ void main() { await csrBlock.build(); - // WaveDumper(csrBlock); + for (var i = 0; i < csrBlock.backdoorInterfaces.length; i++) { + if (csrBlock.backdoorInterfaces[i].hasWrite) { + csrBlock.backdoorInterfaces[i].wrEn!.put(0); + csrBlock.backdoorInterfaces[i].wrData!.put(0); + } + } + + WaveDumper(csrBlock); Simulator.setMaxSimTime(10000); unawaited(Simulator.run()); @@ -254,6 +261,22 @@ void main() { expect(rIntf.data.value, LogicValue.ofInt(0, rIntf.dataWidth)); await clk.waitCycles(10); + // grab backdoor interfaces + final back1 = csrBlock.getBackdoorPortsByName('csr1'); + final back2 = csrBlock.getBackdoorPortsByAddr(0x2); + + // perform backdoor read of csr2 + expect(back2.rdData!.value, + LogicValue.ofInt(csr2.resetValue, rIntf.dataWidth)); + + // perform a backdoor write and then a backdoor read of csr1 + await clk.nextNegedge; + back1.wrData!.inject(1); + back1.wrData!.inject(0xdeadbeef); + await clk.nextNegedge; + back1.wrData!.inject(0); + expect(back1.rdData!.value, LogicValue.ofInt(0xad00ff, rIntf.dataWidth)); + await Simulator.endSimulation(); await Simulator.simulationEnded; }); @@ -277,6 +300,15 @@ void main() { await csrTop.build(); + for (var i = 0; i < csrTop.backdoorInterfaces.length; i++) { + for (var j = 0; j < csrTop.backdoorInterfaces[i].length; j++) { + if (csrTop.backdoorInterfaces[i][j].hasWrite) { + csrTop.backdoorInterfaces[i][j].wrEn!.put(0); + csrTop.backdoorInterfaces[i][j].wrData!.put(0); + } + } + } + // WaveDumper(csrTop); Simulator.setMaxSimTime(10000); @@ -329,6 +361,22 @@ void main() { expect(rIntf.data.value, LogicValue.ofInt(0, rIntf.dataWidth)); await clk.waitCycles(10); + // grab backdoor interfaces + final back1 = csrTop.getBackdoorPortsByName('block_0', 'csr1'); + final back2 = csrTop.getBackdoorPortsByAddr(0x100, 0x2); + + // perform backdoor read of csr2 + expect(back2.rdData!.value, + LogicValue.ofInt(csr2.resetValue, rIntf.dataWidth)); + + // perform a backdoor write and then a backdoor read of csr1 + await clk.nextNegedge; + back1.wrData!.inject(1); + back1.wrData!.inject(0xdeadbeef); + await clk.nextNegedge; + back1.wrData!.inject(0); + expect(back1.rdData!.value, LogicValue.ofInt(0xad00ff, rIntf.dataWidth)); + await Simulator.endSimulation(); await Simulator.simulationEnded; }); From f4dacb53b857990183b4f9fc7481c2faad4c8712 Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Mon, 13 Jan 2025 15:24:16 -0800 Subject: [PATCH 07/14] More testing and bug fix with backdoor ports. --- lib/src/memory/csr.dart | 25 ++++++++++++---- test/csr_test.dart | 66 +++++++++++++++++++++++++++++++++-------- 2 files changed, 72 insertions(+), 19 deletions(-) diff --git a/lib/src/memory/csr.dart b/lib/src/memory/csr.dart index d2a6eab31..598a9ba62 100644 --- a/lib/src/memory/csr.dart +++ b/lib/src/memory/csr.dart @@ -291,8 +291,8 @@ class CsrBlock extends Module { required DataPortInterface fdw, required DataPortInterface fdr, }) : super(name: config.name) { - _clk = addInput('clk', clk); - _reset = addInput('reset', reset); + _clk = addInput('${name}_clk', clk); + _reset = addInput('${name}_reset', reset); _frontWrite = fdw.clone() ..connectIO(this, fdw, @@ -486,7 +486,13 @@ class CsrTop extends Module { CsrBackdoorInterface getBackdoorPortsByName(String block, String reg) { final idx = config.blocks.indexOf(config.getBlockByName(block)); if (idx >= 0 && idx < backdoorInterfaces.length) { - return _blocks[idx].getBackdoorPortsByName(reg); + final idx1 = config.blocks[idx].registers + .indexOf(config.blocks[idx].getRegisterByName(reg)); + if (idx1 >= 0 && idx1 < backdoorInterfaces[idx].length) { + return backdoorInterfaces[idx][idx1]; + } else { + throw Exception('Register $reg in block $block could not be found.'); + } } else { throw Exception('Block $block could not be found.'); } @@ -497,7 +503,14 @@ class CsrTop extends Module { CsrBackdoorInterface getBackdoorPortsByAddr(int blockAddr, int regAddr) { final idx = config.blocks.indexOf(config.getBlockByAddr(blockAddr)); if (idx >= 0 && idx < backdoorInterfaces.length) { - return _blocks[idx].getBackdoorPortsByAddr(regAddr); + final idx1 = config.blocks[idx].registers + .indexOf(config.blocks[idx].getRegisterByAddr(regAddr)); + if (idx1 >= 0 && idx1 < backdoorInterfaces[idx].length) { + return backdoorInterfaces[idx][idx1]; + } else { + throw Exception( + 'Register with address $regAddr in block with address $blockAddr could not be found.'); + } } else { throw Exception('Block with address $blockAddr could not be found.'); } @@ -511,8 +524,8 @@ class CsrTop extends Module { required DataPortInterface fdw, required DataPortInterface fdr, }) : super(name: config.name) { - _clk = addInput('clk', clk); - _reset = addInput('reset', reset); + _clk = addInput('${name}_clk', clk); + _reset = addInput('${name}_reset', reset); _frontWrite = fdw.clone() ..connectIO(this, fdw, diff --git a/test/csr_test.dart b/test/csr_test.dart index 912d63f5a..06a8c85db 100644 --- a/test/csr_test.dart +++ b/test/csr_test.dart @@ -45,18 +45,25 @@ class MyFieldCsrInstance extends CsrInstanceConfig { fields ..add(CsrFieldConfig( start: 0, width: 2, name: 'field1', access: CsrFieldAccess.READ_ONLY)) + ..add(CsrFieldConfig( + start: 2, + width: 2, + name: 'field2', + access: CsrFieldAccess.READ_WRITE_LEGAL) + ..addLegalValue(0x0) + ..addLegalValue(0x1)) // example of a field with dynamic start and width ..add(CsrFieldConfig( start: width ~/ 2, width: width ~/ 4, - name: 'field2', + name: 'field3', access: CsrFieldAccess.READ_WRITE)); // example of field duplication for (var i = 0; i < width ~/ 4; i++) { fields.add(CsrFieldConfig( start: (3 * width ~/ 4) + i, width: 1, - name: 'field3_$i', + name: 'field4_$i', access: CsrFieldAccess.WRITE_ONES_CLEAR)); } } @@ -108,6 +115,30 @@ class MyCsrModule extends CsrTopConfig { } } +// to test potentially issues with CsrTop port propagation +class DummyCsrTopModule extends Module { + // ignore: unused_field + late final Logic _clk; + // ignore: unused_field + late final Logic _reset; + + // ignore: unused_field + late final CsrTop _top; + late final DataPortInterface _fdr; + late final DataPortInterface _fdw; + + DummyCsrTopModule( + {required Logic clk, + required Logic reset, + required CsrTopConfig config}) { + _clk = addInput('clk', clk); + _reset = addInput('reset', reset); + _fdr = DataPortInterface(32, 32); + _fdw = DataPortInterface(32, 32); + _top = CsrTop(config, _clk, _reset, _fdw, _fdr); + } +} + ///// END DEFINE SOME HELPER CLASSES FOR TESTING ///// void main() { @@ -148,17 +179,19 @@ void main() { // only some of what we're trying to write should // given the field access rules final wd2 = csr2.getWriteData(Const(0xab, width: dataWidth2)); - expect(wd2.value, LogicValue.ofInt(0xef, dataWidth2)); + expect(wd2.value, LogicValue.ofInt(0xe3, dataWidth2)); // check grabbing individual fields final f1 = csr2.getField('field1'); expect(f1.value, LogicValue.ofInt(0x3, 2)); final f2 = csr2.getField('field2'); - expect(f2.value, LogicValue.ofInt(0x3, 2)); - final f3a = csr2.getField('field3_0'); - expect(f3a.value, LogicValue.ofInt(0x1, 1)); - final f3b = csr2.getField('field3_1'); - expect(f3b.value, LogicValue.ofInt(0x1, 1)); + expect(f2.value, LogicValue.ofInt(0x3, 2)); // never wrote the value... + final f3 = csr2.getField('field3'); + expect(f3.value, LogicValue.ofInt(0x3, 2)); + final f4a = csr2.getField('field4_0'); + expect(f4a.value, LogicValue.ofInt(0x1, 1)); + final f4b = csr2.getField('field4_1'); + expect(f4b.value, LogicValue.ofInt(0x1, 1)); }); test('simple CSR block', () async { @@ -191,7 +224,7 @@ void main() { } } - WaveDumper(csrBlock); + // WaveDumper(csrBlock); Simulator.setMaxSimTime(10000); unawaited(Simulator.run()); @@ -249,7 +282,7 @@ void main() { rIntf.addr.inject(csr1.addr); await clk.nextNegedge; rIntf.en.inject(0); - expect(rIntf.data.value, LogicValue.ofInt(0xad00ff, rIntf.dataWidth)); + expect(rIntf.data.value, LogicValue.ofInt(0xad00f3, rIntf.dataWidth)); await clk.waitCycles(10); // perform a read of nothing @@ -275,7 +308,7 @@ void main() { back1.wrData!.inject(0xdeadbeef); await clk.nextNegedge; back1.wrData!.inject(0); - expect(back1.rdData!.value, LogicValue.ofInt(0xad00ff, rIntf.dataWidth)); + expect(back1.rdData!.value, LogicValue.ofInt(0xad00f3, rIntf.dataWidth)); await Simulator.endSimulation(); await Simulator.simulationEnded; @@ -349,7 +382,7 @@ void main() { rIntf.addr.inject(addr2.value); await clk.nextNegedge; rIntf.en.inject(0); - expect(rIntf.data.value, LogicValue.ofInt(0xad00ff, rIntf.dataWidth)); + expect(rIntf.data.value, LogicValue.ofInt(0xad00f3, rIntf.dataWidth)); await clk.waitCycles(10); // perform a read to an invalid block @@ -375,9 +408,16 @@ void main() { back1.wrData!.inject(0xdeadbeef); await clk.nextNegedge; back1.wrData!.inject(0); - expect(back1.rdData!.value, LogicValue.ofInt(0xad00ff, rIntf.dataWidth)); + expect(back1.rdData!.value, LogicValue.ofInt(0xad00f3, rIntf.dataWidth)); await Simulator.endSimulation(); await Simulator.simulationEnded; }); + + test('simple CSR top sub-instantiation', () async { + final csrTopCfg = MyCsrModule(numBlocks: 4); + final mod = + DummyCsrTopModule(clk: Logic(), reset: Logic(), config: csrTopCfg); + await mod.build(); + }); } From 6a41c62c7ad20e8ecb965609b830ddd49160bacd Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Tue, 14 Jan 2025 11:15:36 -0800 Subject: [PATCH 08/14] Adding frontdoor and backdoor visibility as a config option. --- lib/src/memory/csr.dart | 111 ++++++++++++++++++--------------- lib/src/memory/csr_config.dart | 58 +++++++++++++++++ test/csr_test.dart | 11 ++-- 3 files changed, 125 insertions(+), 55 deletions(-) diff --git a/lib/src/memory/csr.dart b/lib/src/memory/csr.dart index 598a9ba62..8aa656eeb 100644 --- a/lib/src/memory/csr.dart +++ b/lib/src/memory/csr.dart @@ -47,11 +47,9 @@ class CsrBackdoorInterface extends Interface { /// Constructs a new interface of specified [dataWidth] /// and conditionally instantiates read and writes ports based on /// [hasRead] and [hasWrite]. - CsrBackdoorInterface( - {required this.config, - this.dataWidth = 0, - this.hasRead = true, - this.hasWrite = true}) { + CsrBackdoorInterface({required this.config, this.dataWidth = 0}) + : hasRead = config.isBackdoorReadable, + hasWrite = config.isBackdoorWritable { if (hasRead) { setPorts([ Csr(config), @@ -71,11 +69,8 @@ class CsrBackdoorInterface extends Interface { } /// Makes a copy of this [Interface] with matching configuration. - CsrBackdoorInterface clone() => CsrBackdoorInterface( - config: config, - dataWidth: dataWidth, - hasRead: hasRead, - hasWrite: hasWrite); + CsrBackdoorInterface clone() => + CsrBackdoorInterface(config: config, dataWidth: dataWidth); } /// Logic representation of a CSR. @@ -263,6 +258,7 @@ class CsrBlock extends Module { /// The private copies are used for internal logic. final List backdoorInterfaces = []; final List _backdoorInterfaces = []; + final Map _backdoorIndexMap = {}; /// Clock for the module. late final Logic _clk; @@ -306,16 +302,18 @@ class CsrBlock extends Module { uniquify: (original) => 'frontRead_$original'); for (var i = 0; i < csrs.length; i++) { - // TODO: pull hasRead and hasWrite from config?? - _backdoorInterfaces.add(CsrBackdoorInterface( - config: csrs[i].config, dataWidth: fdr.dataWidth)); - backdoorInterfaces.add(CsrBackdoorInterface( - config: csrs[i].config, dataWidth: fdr.dataWidth)); - _backdoorInterfaces.last.connectIO(this, backdoorInterfaces.last, - outputTags: {CsrBackdoorPortGroup.read}, - inputTags: {CsrBackdoorPortGroup.write}, - uniquify: (original) => - '${name}_${csrs[i].config.name}_backdoor_$original'); + if (csrs[i].config.backdoorAccessible) { + _backdoorInterfaces.add(CsrBackdoorInterface( + config: csrs[i].config, dataWidth: fdr.dataWidth)); + backdoorInterfaces.add(CsrBackdoorInterface( + config: csrs[i].config, dataWidth: fdr.dataWidth)); + _backdoorInterfaces.last.connectIO(this, backdoorInterfaces.last, + outputTags: {CsrBackdoorPortGroup.read}, + inputTags: {CsrBackdoorPortGroup.write}, + uniquify: (original) => + '${name}_${csrs[i].config.name}_backdoor_$original'); + _backdoorIndexMap[i] = _backdoorInterfaces.length - 1; + } } _buildLogic(); @@ -357,8 +355,8 @@ class CsrBlock extends Module { /// within the block by name [nm]. CsrBackdoorInterface getBackdoorPortsByName(String nm) { final idx = config.registers.indexOf(config.getRegisterByName(nm)); - if (idx >= 0 && idx < backdoorInterfaces.length) { - return backdoorInterfaces[idx]; + if (_backdoorIndexMap.containsKey(idx)) { + return backdoorInterfaces[_backdoorIndexMap[idx]!]; } else { throw Exception('Register $nm not found in block ${config.name}'); } @@ -368,8 +366,8 @@ class CsrBlock extends Module { /// within the block by relative address [addr]. CsrBackdoorInterface getBackdoorPortsByAddr(int addr) { final idx = config.registers.indexOf(config.getRegisterByAddr(addr)); - if (idx >= 0 && idx < backdoorInterfaces.length) { - return backdoorInterfaces[idx]; + if (_backdoorIndexMap.containsKey(idx)) { + return backdoorInterfaces[_backdoorIndexMap[idx]!]; } else { throw Exception( 'Register address $addr not found in block ${config.name}'); @@ -397,9 +395,12 @@ class CsrBlock extends Module { csrs[i] < csrs[i].getWriteData(_frontWrite.data), ]), // backdoor write takes next priority - if (_backdoorInterfaces[i].hasWrite) - ElseIf(_backdoorInterfaces[i].wrEn!, [ - csrs[i] < csrs[i].getWriteData(_backdoorInterfaces[i].wrData!), + if (_backdoorIndexMap.containsKey(i) && + _backdoorInterfaces[_backdoorIndexMap[i]!].hasWrite) + ElseIf(_backdoorInterfaces[_backdoorIndexMap[i]!].wrEn!, [ + csrs[i] < + csrs[i].getWriteData( + _backdoorInterfaces[_backdoorIndexMap[i]!].wrData!), ]), // nothing to write this cycle Else([ @@ -430,8 +431,9 @@ class CsrBlock extends Module { // driving of backdoor read outputs for (var i = 0; i < csrs.length; i++) { - if (_backdoorInterfaces[i].hasRead) { - _backdoorInterfaces[i].rdData! <= csrs[i]; + if (_backdoorIndexMap.containsKey(i) && + _backdoorInterfaces[_backdoorIndexMap[i]!].hasRead) { + _backdoorInterfaces[_backdoorIndexMap[i]!].rdData! <= csrs[i]; } } } @@ -477,6 +479,7 @@ class CsrTop extends Module { /// The private copies are used for internal logic. final List> backdoorInterfaces = []; final List> _backdoorInterfaces = []; + final List> _backdoorIndexMaps = []; /// Getter for the block configurations of the CSR. List get blocks => config.blocks; @@ -488,8 +491,8 @@ class CsrTop extends Module { if (idx >= 0 && idx < backdoorInterfaces.length) { final idx1 = config.blocks[idx].registers .indexOf(config.blocks[idx].getRegisterByName(reg)); - if (idx1 >= 0 && idx1 < backdoorInterfaces[idx].length) { - return backdoorInterfaces[idx][idx1]; + if (_backdoorIndexMaps[idx].containsKey(idx1)) { + return backdoorInterfaces[idx][_backdoorIndexMaps[idx][idx1]!]; } else { throw Exception('Register $reg in block $block could not be found.'); } @@ -505,8 +508,8 @@ class CsrTop extends Module { if (idx >= 0 && idx < backdoorInterfaces.length) { final idx1 = config.blocks[idx].registers .indexOf(config.blocks[idx].getRegisterByAddr(regAddr)); - if (idx1 >= 0 && idx1 < backdoorInterfaces[idx].length) { - return backdoorInterfaces[idx][idx1]; + if (_backdoorIndexMaps[idx].containsKey(idx1)) { + return backdoorInterfaces[idx][_backdoorIndexMaps[idx][idx1]!]; } else { throw Exception( 'Register with address $regAddr in block with address $blockAddr could not be found.'); @@ -547,17 +550,21 @@ class CsrTop extends Module { for (var i = 0; i < blocks.length; i++) { _backdoorInterfaces.add([]); backdoorInterfaces.add([]); + _backdoorIndexMaps.add({}); for (var j = 0; j < blocks[i].registers.length; j++) { - // TODO: pull hasRead and hasWrite from config?? - _backdoorInterfaces[i].add(CsrBackdoorInterface( - config: blocks[i].registers[j], dataWidth: fdr.dataWidth)); - backdoorInterfaces[i].add(CsrBackdoorInterface( - config: blocks[i].registers[j], dataWidth: fdr.dataWidth)); - _backdoorInterfaces[i].last.connectIO(this, backdoorInterfaces[i].last, - outputTags: {CsrBackdoorPortGroup.read}, - inputTags: {CsrBackdoorPortGroup.write}, - uniquify: (original) => - '${name}_${blocks[i].name}_${blocks[i].registers[j].name}_backdoor_$original'); + if (blocks[i].registers[j].backdoorAccessible) { + _backdoorInterfaces[i].add(CsrBackdoorInterface( + config: blocks[i].registers[j], dataWidth: fdr.dataWidth)); + backdoorInterfaces[i].add(CsrBackdoorInterface( + config: blocks[i].registers[j], dataWidth: fdr.dataWidth)); + _backdoorInterfaces[i].last.connectIO( + this, backdoorInterfaces[i].last, + outputTags: {CsrBackdoorPortGroup.read}, + inputTags: {CsrBackdoorPortGroup.write}, + uniquify: (original) => + '${name}_${blocks[i].name}_${blocks[i].registers[j].name}_backdoor_$original'); + _backdoorIndexMaps[i][j] = _backdoorInterfaces[i].length - 1; + } } } @@ -620,11 +627,12 @@ class CsrTop extends Module { // drive backdoor write ports for (var j = 0; j < blocks[i].registers.length; j++) { - if (_backdoorInterfaces[i][j].hasWrite) { - _blocks[i].backdoorInterfaces[j].wrEn! <= - _backdoorInterfaces[i][j].wrEn!; - _blocks[i].backdoorInterfaces[j].wrData! <= - _backdoorInterfaces[i][j].wrData!; + if (_backdoorIndexMaps[i].containsKey(j) && + _backdoorInterfaces[i][_backdoorIndexMaps[i][j]!].hasWrite) { + _blocks[i].backdoorInterfaces[_backdoorIndexMaps[i][j]!].wrEn! <= + _backdoorInterfaces[i][_backdoorIndexMaps[i][j]!].wrEn!; + _blocks[i].backdoorInterfaces[_backdoorIndexMaps[i][j]!].wrData! <= + _backdoorInterfaces[i][_backdoorIndexMaps[i][j]!].wrData!; } } } @@ -653,9 +661,10 @@ class CsrTop extends Module { // driving of backdoor read outputs for (var i = 0; i < blocks.length; i++) { for (var j = 0; j < blocks[i].registers.length; j++) { - if (_backdoorInterfaces[i][j].hasRead) { - _backdoorInterfaces[i][j].rdData! <= - _blocks[i].backdoorInterfaces[j].rdData!; + if (_backdoorIndexMaps[i].containsKey(j) && + _backdoorInterfaces[i][_backdoorIndexMaps[i][j]!].hasRead) { + _backdoorInterfaces[i][_backdoorIndexMaps[i][j]!].rdData! <= + _blocks[i].backdoorInterfaces[_backdoorIndexMaps[i][j]!].rdData!; } } } diff --git a/lib/src/memory/csr_config.dart b/lib/src/memory/csr_config.dart index 0b6df4186..0fa47a096 100644 --- a/lib/src/memory/csr_config.dart +++ b/lib/src/memory/csr_config.dart @@ -104,6 +104,26 @@ class CsrConfig { /// Note that this can be overridden in the instantiation of the register. int resetValue; + /// Architectural property in which the register can be frontdoor read. + /// + /// A frontdoor read occurs explicitly using the register's address. + bool isFrontdoorReadable; + + /// Architectural property in which the register can be frontdoor written. + /// + /// A frontdoor write occurs explicitly using the register's address. + bool isFrontdoorWritable; + + /// Architectural property in which the register can be backdoor read. + /// + /// A backdoor read exposes the register's value combinationally to the HW. + bool isBackdoorReadable; + + /// Architectural property in which the register can be backdoor written. + /// + /// A backdoor write exposes direct write access to the HW through an enable. + bool isBackdoorWritable; + /// Fields in this register. final List fields = []; @@ -112,6 +132,10 @@ class CsrConfig { required this.name, required this.access, this.resetValue = 0, + this.isFrontdoorReadable = true, + this.isFrontdoorWritable = true, + this.isBackdoorReadable = true, + this.isBackdoorWritable = true, }); /// Accessor to the config of a particular field @@ -148,6 +172,24 @@ class CsrInstanceConfig { /// Accessor to the architectural reset value of the register. int get resetValue => arch.resetValue; + /// Accessor to the architectural frontdoor readability of the register. + bool get isFrontdoorReadable => arch.isFrontdoorReadable; + + /// Accessor to the architectural frontdoor writability of the register. + bool get isFrontdoorWritable => arch.isFrontdoorWritable; + + /// Accessor to the architectural backdoor readability of the register. + bool get isBackdoorReadable => arch.isBackdoorReadable; + + /// Accessor to the architectural backdoor writability of the register. + bool get isBackdoorWritable => arch.isBackdoorWritable; + + /// Helper for determining if the register is frontdoor accessible. + bool get frontdoorAccessible => isFrontdoorReadable || isFrontdoorWritable; + + /// Helper for determining if the register is frontdoor accessible. + bool get backdoorAccessible => isBackdoorReadable || isBackdoorWritable; + /// Accessor to the fields of the register. List get fields => arch.fields; @@ -157,10 +199,26 @@ class CsrInstanceConfig { required this.addr, required this.width, int? resetValue, + bool? isFrontdoorReadable, + bool? isFrontdoorWritable, + bool? isBackdoorReadable, + bool? isBackdoorWritable, }) { if (resetValue != null) { arch.resetValue = resetValue; } + if (isFrontdoorReadable != null) { + arch.isFrontdoorReadable = isFrontdoorReadable; + } + if (isFrontdoorWritable != null) { + arch.isFrontdoorWritable = isFrontdoorWritable; + } + if (isBackdoorReadable != null) { + arch.isBackdoorReadable = isBackdoorReadable; + } + if (isBackdoorWritable != null) { + arch.isBackdoorWritable = isBackdoorWritable; + } } /// Accessor to the config of a particular field diff --git a/test/csr_test.dart b/test/csr_test.dart index 06a8c85db..fbc45e754 100644 --- a/test/csr_test.dart +++ b/test/csr_test.dart @@ -27,7 +27,10 @@ class MyNoFieldCsrInstance extends CsrInstanceConfig { required super.width, super.resetValue = 0x0, String name = 'myNoFieldCsrInstance', - }) : super(arch: MyNoFieldCsr(name: name)); + }) : super( + arch: MyNoFieldCsr(name: name), + isBackdoorReadable: addr != 0x1, + isBackdoorWritable: false); } class MyFieldCsr extends CsrConfig { @@ -40,7 +43,7 @@ class MyFieldCsrInstance extends CsrInstanceConfig { required super.width, super.resetValue = 0xff, String name = 'myFieldCsrInstance', - }) : super(arch: MyFieldCsr(name: name)) { + }) : super(arch: MyFieldCsr(name: name), isBackdoorWritable: true) { // example of a static field fields ..add(CsrFieldConfig( @@ -304,7 +307,7 @@ void main() { // perform a backdoor write and then a backdoor read of csr1 await clk.nextNegedge; - back1.wrData!.inject(1); + back1.wrEn!.inject(1); back1.wrData!.inject(0xdeadbeef); await clk.nextNegedge; back1.wrData!.inject(0); @@ -404,7 +407,7 @@ void main() { // perform a backdoor write and then a backdoor read of csr1 await clk.nextNegedge; - back1.wrData!.inject(1); + back1.wrEn!.inject(1); back1.wrData!.inject(0xdeadbeef); await clk.nextNegedge; back1.wrData!.inject(0); From a3285c81b24de426c77a2e7ddab8435969040dd8 Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Tue, 14 Jan 2025 14:22:01 -0800 Subject: [PATCH 09/14] Adding dynamic behavior for frontdoor read and writability. --- lib/src/memory/csr.dart | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/src/memory/csr.dart b/lib/src/memory/csr.dart index 8aa656eeb..e78a6cbe7 100644 --- a/lib/src/memory/csr.dart +++ b/lib/src/memory/csr.dart @@ -97,6 +97,18 @@ class Csr extends LogicStructure { /// Getter for the access control of the CSR. CsrAccess get access => config.access; + /// Accessor to the architectural frontdoor readability of the register. + bool get isFrontdoorReadable => config.isFrontdoorReadable; + + /// Accessor to the architectural frontdoor writability of the register. + bool get isFrontdoorWritable => config.isFrontdoorWritable; + + /// Accessor to the architectural backdoor readability of the register. + bool get isBackdoorReadable => config.isBackdoorReadable; + + /// Accessor to the architectural backdoor writability of the register. + bool get isBackdoorWritable => config.isBackdoorWritable; + /// Getter for the field configuration of the CSR List get fields => config.fields; @@ -388,12 +400,14 @@ class CsrBlock extends Module { [ If.block([ // frontdoor write takes highest priority - Iff( - _frontWrite.en & - _frontWrite.addr.eq(Const(csrs[i].addr, width: addrWidth)), - [ - csrs[i] < csrs[i].getWriteData(_frontWrite.data), - ]), + if (config.registers[i].isFrontdoorWritable) + ElseIf( + _frontWrite.en & + _frontWrite.addr + .eq(Const(csrs[i].addr, width: addrWidth)), + [ + csrs[i] < csrs[i].getWriteData(_frontWrite.data), + ]), // backdoor write takes next priority if (_backdoorIndexMap.containsKey(i) && _backdoorInterfaces[_backdoorIndexMap[i]!].hasWrite) @@ -414,6 +428,7 @@ 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, ])) From bba27cd2ed0aaf39312efe5394dc19e9da1fee98 Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Wed, 15 Jan 2025 11:28:45 -0800 Subject: [PATCH 10/14] Adding validation to all Csr config objects. --- lib/src/memory/csr.dart | 17 ++-- lib/src/memory/csr_config.dart | 179 +++++++++++++++++++++++++++++++++ test/csr_test.dart | 99 +++++++++++++++++- 3 files changed, 287 insertions(+), 8 deletions(-) diff --git a/lib/src/memory/csr.dart b/lib/src/memory/csr.dart index e78a6cbe7..94a3cb330 100644 --- a/lib/src/memory/csr.dart +++ b/lib/src/memory/csr.dart @@ -121,7 +121,9 @@ class Csr extends LogicStructure { required this.config, required this.rsvdIndices, required List fields, - }) : super(fields, name: config.name); + }) : super(fields, name: config.name) { + config.validate(); + } /// Factory constructor for [Csr]. /// @@ -299,6 +301,8 @@ class CsrBlock extends Module { required DataPortInterface fdw, required DataPortInterface fdr, }) : super(name: config.name) { + config.validate(); + _clk = addInput('${name}_clk', clk); _reset = addInput('${name}_reset', reset); @@ -461,10 +465,6 @@ class CsrBlock extends Module { /// MSBs of the incoming address and registers within the given block /// are addressable using the remaining LSBs of the incoming address. class CsrTop extends Module { - /// width of the LSBs of the address - /// to ignore when mapping to blocks - final int blockOffsetWidth; - /// Configuration for the CSR Top module. final CsrTopConfig config; @@ -496,6 +496,9 @@ class CsrTop extends Module { final List> _backdoorInterfaces = []; final List> _backdoorIndexMaps = []; + /// Getter for the block offset width. + int get blockOffsetWidth => config.blockOffsetWidth; + /// Getter for the block configurations of the CSR. List get blocks => config.blocks; @@ -536,12 +539,13 @@ class CsrTop extends Module { CsrTop._({ required this.config, - required this.blockOffsetWidth, // TODO: make this part of the config?? required Logic clk, required Logic reset, required DataPortInterface fdw, required DataPortInterface fdr, }) : super(name: config.name) { + config.validate(); + _clk = addInput('${name}_clk', clk); _reset = addInput('${name}_reset', reset); @@ -596,7 +600,6 @@ class CsrTop extends Module { ) => CsrTop._( config: config, - blockOffsetWidth: config.blockOffsetWidth, clk: clk, reset: reset, fdw: fdw, diff --git a/lib/src/memory/csr_config.dart b/lib/src/memory/csr_config.dart index 0fa47a096..3dea253e6 100644 --- a/lib/src/memory/csr_config.dart +++ b/lib/src/memory/csr_config.dart @@ -7,6 +7,18 @@ // 2024 December // Author: Josh Kimmel +/// Targeted Exception type for Csr valiation. +class CsrValidationException implements Exception { + /// Message associated with the Exception. + final String message; + + /// Public constructor. + CsrValidationException(this.message); + + @override + String toString() => message; +} + /// Definitions for various register field access patterns. enum CsrFieldAccess { /// Register field is read only. @@ -85,6 +97,17 @@ class CsrFieldConfig { /// This is a default implementation that simply takes the first /// legal value but can be overridden in a derived class. int transformIllegalValue() => legalValues[0]; + + /// Method to validate the configuration of a single field. + void validate() { + // there must be at least 1 legal value for a READ_WRITE_LEGAL field + if (access == CsrFieldAccess.READ_WRITE_LEGAL) { + if (legalValues.isEmpty) { + throw CsrValidationException( + 'Field $name has no legal values but has access READ_WRITE_LEGAL.'); + } + } + } } /// Configuration for an architectural register. @@ -142,6 +165,46 @@ class CsrConfig { /// within the register by name [nm]. CsrFieldConfig getFieldByName(String nm) => fields.firstWhere((element) => element.name == nm); + + /// Method to validate the configuration of a single register. + /// + /// Must check that its fields are mutually valid. + void validate() { + final ranges = >[]; + final issues = []; + for (final field in fields) { + // check the field on its own for issues + try { + field.validate(); + } on Exception catch (e) { + issues.add(e.toString()); + } + + // check to ensure that the field doesn't overlap with any other field + // overlap can occur on name or on bit placement + for (var i = 0; i < ranges.length; i++) { + // check against all other names + if (field.name == fields[i].name) { + issues.add('Field ${field.name} is duplicated.'); + } + // check field start to see if it falls within another field + else if (field.start >= ranges[i][0] && field.start <= ranges[i][1]) { + issues.add( + 'Field ${field.name} overlaps with field ${fields[i].name}.'); + } + // check field end to see if it falls within another field + else if (field.start + field.width - 1 >= ranges[i][0] && + field.start + field.width - 1 <= ranges[i][1]) { + issues.add( + 'Field ${field.name} overlaps with field ${fields[i].name}.'); + } + } + ranges.add([field.start, field.start + field.width - 1]); + } + if (issues.isNotEmpty) { + throw CsrValidationException(issues.join('\n')); + } + } } /// Configuration for a register instance. @@ -224,6 +287,27 @@ class CsrInstanceConfig { /// Accessor to the config of a particular field /// within the register by name [nm]. CsrFieldConfig getFieldByName(String nm) => arch.getFieldByName(nm); + + /// Method to validate the configuration of a single register. + /// + /// Must check that its fields are mutually valid. + void validate() { + // start by running architectural register validation + arch.validate(); + + // check that the field widths don't exceed the register width + var impliedEnd = 0; + for (final field in fields) { + final currEnd = field.start + field.width - 1; + if (currEnd > impliedEnd) { + impliedEnd = currEnd; + } + } + if (impliedEnd > width - 1) { + throw CsrValidationException( + 'Register width implied by its fields exceeds true register width.'); + } + } } /// Definition for a coherent block of registers. @@ -257,6 +341,49 @@ class CsrBlockConfig { /// within the block by relative address [addr]. CsrInstanceConfig getRegisterByAddr(int addr) => registers.firstWhere((element) => element.addr == addr); + + /// Method to validate the configuration of a single register block. + /// + /// Must check that its registers are mutually valid. + /// Note that this method does not call the validate method of + /// the individual registers in the block. It is assumed that + /// register validation is called separately (i.e., in Csr HW construction). + void validate() { + // at least 1 register + if (registers.isEmpty) { + throw CsrValidationException('Block $name has no registers.'); + } + + // no two registers with the same name + // no two registers with the same address + final issues = []; + for (var i = 0; i < registers.length; i++) { + for (var j = i + 1; j < registers.length; j++) { + if (registers[i].name == registers[j].name) { + issues.add('Register ${registers[i].name} is duplicated.'); + } + if (registers[i].addr == registers[j].addr) { + issues.add('Register ${registers[i].name} has a duplicate address.'); + } + } + } + if (issues.isNotEmpty) { + throw CsrValidationException(issues.join('\n')); + } + } + + /// 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. + int minAddrBits() { + var maxAddr = 0; + for (final reg in registers) { + if (reg.addr > maxAddr) { + maxAddr = reg.addr; + } + } + return maxAddr.bitLength; + } } /// Definition for a top level module containing CSR blocks. @@ -291,4 +418,56 @@ class CsrTopConfig { /// within the module by relative address [addr]. CsrBlockConfig getBlockByAddr(int addr) => blocks.firstWhere((element) => element.baseAddr == addr); + + /// Method to validate the configuration of register top module. + /// + /// Must check that its blocks are mutually valid. + /// Note that this method does not call the validate method of + /// the individual blocks. It is assumed that + /// block validation is called separately (i.e., in CsrBlock HW construction). + void validate() { + // at least 1 block + if (blocks.isEmpty) { + throw CsrValidationException( + 'Csr top module $name has no register blocks.'); + } + + // 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 + final issues = []; + var maxMinAddrBits = 0; + for (var i = 0; i < blocks.length; i++) { + final currMaxMin = blocks[i].minAddrBits(); + if (currMaxMin > maxMinAddrBits) { + maxMinAddrBits = currMaxMin; + } + + for (var j = i + 1; j < blocks.length; j++) { + if (blocks[i].name == blocks[j].name) { + issues.add('Register block ${blocks[i].name} is duplicated.'); + } + + 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.'); + } + } + } + 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.'); + } + } } diff --git a/test/csr_test.dart b/test/csr_test.dart index fbc45e754..56beebeb1 100644 --- a/test/csr_test.dart +++ b/test/csr_test.dart @@ -104,7 +104,7 @@ class MyCsrModule extends CsrTopConfig { MyCsrModule({ this.numBlocks = 1, super.name = 'myCsrModule', - super.blockOffsetWidth = 16, + super.blockOffsetWidth = 8, }) { // example of dynamic block instantiation const baseAddr = 0x0; @@ -423,4 +423,101 @@ void main() { DummyCsrTopModule(clk: Logic(), reset: Logic(), config: csrTopCfg); await mod.build(); }); + + test('CSR validation failures', () async { + // illegal individual field + final badFieldCfg = CsrFieldConfig( + start: 0, + width: 1, + name: 'badFieldCfg', + access: CsrFieldAccess.READ_WRITE_LEGAL); + expect(badFieldCfg.validate, + throwsA(predicate((f) => f is CsrValidationException))); + + // illegal architectural register + final badArchRegCfg = + CsrConfig(access: CsrAccess.READ_WRITE, name: 'badArchRegCfg') + ..fields.add(CsrFieldConfig( + start: 0, + width: 8, + name: 'field', + access: CsrFieldAccess.READ_WRITE)) + ..fields.add(CsrFieldConfig( + start: 3, + width: 4, + name: 'field', + access: CsrFieldAccess.READ_WRITE)) + ..fields.add(CsrFieldConfig( + start: 3, + width: 10, + name: 'field1', + access: CsrFieldAccess.READ_WRITE)) + ..fields.add(CsrFieldConfig( + start: 9, + width: 11, + name: 'field2', + access: CsrFieldAccess.READ_WRITE)); + expect(badArchRegCfg.validate, + throwsA(predicate((f) => f is CsrValidationException))); + + // illegal register instance + final badRegInstCfg = CsrInstanceConfig( + arch: CsrConfig(access: CsrAccess.READ_WRITE, name: 'reg') + ..fields.add(CsrFieldConfig( + start: 0, + width: 32, + name: 'field', + access: CsrFieldAccess.READ_WRITE)), + addr: 0x0, + width: 4); + expect(badRegInstCfg.validate, + throwsA(predicate((f) => f is CsrValidationException))); + }); + + test('CSR block and top validation failures', () async { + // illegal block - empty + final badBlockCfg1 = CsrBlockConfig(name: 'block', baseAddr: 0x0); + expect(badBlockCfg1.validate, + throwsA(predicate((f) => f is CsrValidationException))); + + // illegal block - duplication + final badBlockCfg2 = CsrBlockConfig(name: 'block', baseAddr: 0x0) + ..registers.add(CsrInstanceConfig( + arch: CsrConfig(access: CsrAccess.READ_WRITE, name: 'reg'), + addr: 0x0, + width: 4)) + ..registers.add(CsrInstanceConfig( + arch: CsrConfig(access: CsrAccess.READ_WRITE, name: 'reg'), + addr: 0x1, + width: 4)) + ..registers.add(CsrInstanceConfig( + arch: CsrConfig(access: CsrAccess.READ_WRITE, name: 'reg1'), + addr: 0x1, + width: 4)); + expect(badBlockCfg2.validate, + throwsA(predicate((f) => f is CsrValidationException))); + + // illegal top - empty + final badTopCfg1 = CsrTopConfig(name: 'top', blockOffsetWidth: 8); + expect(badTopCfg1.validate, + throwsA(predicate((f) => f is CsrValidationException))); + + // illegal top - duplication and closeness + final badTopCfg2 = CsrTopConfig(name: 'top', blockOffsetWidth: 8) + ..blocks.add(CsrBlockConfig(name: 'block', baseAddr: 0x0)) + ..blocks.add(CsrBlockConfig(name: 'block', baseAddr: 0x1)) + ..blocks.add(CsrBlockConfig(name: 'block1', baseAddr: 0x1)); + expect(badTopCfg2.validate, + throwsA(predicate((f) => f is CsrValidationException))); + + // illegal top - bad block offset width + final badTopCfg3 = CsrTopConfig(name: 'top', blockOffsetWidth: 1) + ..blocks.add(CsrBlockConfig(name: 'block', baseAddr: 0x0) + ..registers.add(CsrInstanceConfig( + arch: CsrConfig(access: CsrAccess.READ_WRITE, name: 'reg'), + addr: 0x4, + width: 4))); + expect(badTopCfg3.validate, + throwsA(predicate((f) => f is CsrValidationException))); + }); } From 635197283efcf7f2cbe7afa085807d9e3375b863 Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Thu, 16 Jan 2025 09:41:10 -0800 Subject: [PATCH 11/14] Adding validation to frontdoor interface widths relative to configs. --- lib/src/memory/csr.dart | 52 ++++++++++++++++++++++++++++++++++ lib/src/memory/csr_config.dart | 39 +++++++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/lib/src/memory/csr.dart b/lib/src/memory/csr.dart index 94a3cb330..d6d76ef56 100644 --- a/lib/src/memory/csr.dart +++ b/lib/src/memory/csr.dart @@ -7,6 +7,8 @@ // 2024 December // Author: Josh Kimmel +import 'dart:math'; + import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; @@ -317,6 +319,8 @@ class CsrBlock extends Module { outputTags: {DataPortGroup.data}, uniquify: (original) => 'frontRead_$original'); + _validate(); + for (var i = 0; i < csrs.length; i++) { if (csrs[i].config.backdoorAccessible) { _backdoorInterfaces.add(CsrBackdoorInterface( @@ -390,6 +394,29 @@ class CsrBlock extends Module { } } + // validate the frontdoor interface widths to ensure that they are wide enough + void _validate() { + // check frontdoor interfaces + // data width must be at least as wide as the biggest register in the block + // 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 (_frontWrite.dataWidth < config.maxRegWidth()) { + throw CsrValidationException( + 'Frontdoor write interface data width must be at least ${config.maxRegWidth()}.'); + } + if (_frontRead.addrWidth < config.minAddrBits()) { + throw CsrValidationException( + 'Frontdoor read interface address width must be at least ${config.minAddrBits()}.'); + } + if (_frontWrite.dataWidth < config.minAddrBits()) { + throw CsrValidationException( + 'Frontdoor write interface address width must be at least ${config.minAddrBits()}.'); + } + } + void _buildLogic() { final addrWidth = _frontWrite.addrWidth; @@ -560,6 +587,8 @@ class CsrTop extends Module { outputTags: {DataPortGroup.data}, uniquify: (original) => '${name}_frontRead_$original'); + _validate(); + for (final block in config.blocks) { _fdWrites.add(DataPortInterface(fdw.dataWidth, blockOffsetWidth)); _fdReads.add(DataPortInterface(fdr.dataWidth, blockOffsetWidth)); @@ -614,6 +643,29 @@ class CsrTop extends Module { /// within the module by relative address [addr]. CsrBlockConfig getBlockByAddr(int addr) => config.getBlockByAddr(addr); + // validate the frontdoor interface widths to ensure that they are wide enough + void _validate() { + // data width must be at least as wide as 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()) { + throw CsrValidationException( + 'Frontdoor read interface data width must be at least ${config.maxRegWidth()}.'); + } + if (_frontWrite.dataWidth < config.maxRegWidth()) { + throw CsrValidationException( + 'Frontdoor write interface data width must be at least ${config.maxRegWidth()}.'); + } + if (_frontRead.addrWidth < config.minAddrBits() || + _frontRead.addrWidth < blockOffsetWidth) { + throw CsrValidationException( + 'Frontdoor read interface address width must be at least ${max(config.minAddrBits(), blockOffsetWidth)}.'); + } + if (_frontWrite.dataWidth < config.minAddrBits()) { + throw CsrValidationException( + 'Frontdoor write interface address width must be at least ${max(config.minAddrBits(), blockOffsetWidth)}.'); + } + } + void _buildLogic() { final addrWidth = _frontWrite.addrWidth; diff --git a/lib/src/memory/csr_config.dart b/lib/src/memory/csr_config.dart index 3dea253e6..1a9059dd8 100644 --- a/lib/src/memory/csr_config.dart +++ b/lib/src/memory/csr_config.dart @@ -384,6 +384,18 @@ class CsrBlockConfig { } return maxAddr.bitLength; } + + /// Method to determine the maximum register size. + /// This is important for interface data width validation. + int maxRegWidth() { + var maxWidth = 0; + for (final reg in registers) { + if (reg.width > maxWidth) { + maxWidth = reg.width; + } + } + return maxWidth; + } } /// Definition for a top level module containing CSR blocks. @@ -470,4 +482,31 @@ class CsrTopConfig { '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 + /// so we can trust the simpler analysis here. + int minAddrBits() { + var maxAddr = 0; + for (final block in blocks) { + if (block.baseAddr > maxAddr) { + maxAddr = block.baseAddr; + } + } + return maxAddr.bitLength; + } + + /// Method to determine the maximum register size. + /// This is important for interface data width validation. + int maxRegWidth() { + var maxWidth = 0; + for (final block in blocks) { + if (block.maxRegWidth() > maxWidth) { + maxWidth = block.maxRegWidth(); + } + } + return maxWidth; + } } From b89cdcdb2d0c8df29e1553b42ef9b747814846ed Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Thu, 16 Jan 2025 09:47:29 -0800 Subject: [PATCH 12/14] Adding support for heterogeneous register widths both across and within blocks. --- lib/src/memory/csr.dart | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/src/memory/csr.dart b/lib/src/memory/csr.dart index d6d76ef56..a33f85401 100644 --- a/lib/src/memory/csr.dart +++ b/lib/src/memory/csr.dart @@ -324,9 +324,9 @@ class CsrBlock extends Module { for (var i = 0; i < csrs.length; i++) { if (csrs[i].config.backdoorAccessible) { _backdoorInterfaces.add(CsrBackdoorInterface( - config: csrs[i].config, dataWidth: fdr.dataWidth)); + config: csrs[i].config, dataWidth: csrs[i].config.width)); backdoorInterfaces.add(CsrBackdoorInterface( - config: csrs[i].config, dataWidth: fdr.dataWidth)); + config: csrs[i].config, dataWidth: csrs[i].config.width)); _backdoorInterfaces.last.connectIO(this, backdoorInterfaces.last, outputTags: {CsrBackdoorPortGroup.read}, inputTags: {CsrBackdoorPortGroup.write}, @@ -437,7 +437,9 @@ class CsrBlock extends Module { _frontWrite.addr .eq(Const(csrs[i].addr, width: addrWidth)), [ - csrs[i] < csrs[i].getWriteData(_frontWrite.data), + csrs[i] < + csrs[i].getWriteData( + _frontWrite.data.getRange(0, csrs[i].config.width)), ]), // backdoor write takes next priority if (_backdoorIndexMap.containsKey(i) && @@ -461,7 +463,7 @@ class CsrBlock extends Module { final rdCases = csrs .where((csr) => csr.isFrontdoorReadable) .map((csr) => CaseItem(Const(csr.addr, width: addrWidth), [ - rdData < csr, + rdData < csr.zeroExtend(_frontRead.dataWidth), ])) .toList(); Combinational([ @@ -602,9 +604,11 @@ class CsrTop extends Module { for (var j = 0; j < blocks[i].registers.length; j++) { if (blocks[i].registers[j].backdoorAccessible) { _backdoorInterfaces[i].add(CsrBackdoorInterface( - config: blocks[i].registers[j], dataWidth: fdr.dataWidth)); + config: blocks[i].registers[j], + dataWidth: blocks[i].registers[j].width)); backdoorInterfaces[i].add(CsrBackdoorInterface( - config: blocks[i].registers[j], dataWidth: fdr.dataWidth)); + config: blocks[i].registers[j], + dataWidth: blocks[i].registers[j].width)); _backdoorInterfaces[i].last.connectIO( this, backdoorInterfaces[i].last, outputTags: {CsrBackdoorPortGroup.read}, From 6a98c5e739fa3a31f6d172c9b113a45be83abf3d Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Fri, 17 Jan 2025 09:55:00 -0800 Subject: [PATCH 13/14] Adding the ability to set reset values on individual fields instead of just on a full register. Adding validation on reset and legal values to ensure that they fit within their allotted bits. --- lib/src/memory/csr_config.dart | 47 +++++++++++++++++++++++++++++++--- test/csr_test.dart | 47 +++++++++++++++++++++++++++++----- 2 files changed, 85 insertions(+), 9 deletions(-) diff --git a/lib/src/memory/csr_config.dart b/lib/src/memory/csr_config.dart index 1a9059dd8..e829c5f8e 100644 --- a/lib/src/memory/csr_config.dart +++ b/lib/src/memory/csr_config.dart @@ -72,6 +72,11 @@ class CsrFieldConfig { /// Access rule for the field. final CsrFieldAccess access; + /// Reset value for the field. + /// + /// Can be unspecified in which case takes 0. + final int resetValue; + /// A list of legal values for the field. /// /// This list can be empty and in general is only @@ -84,6 +89,7 @@ class CsrFieldConfig { required this.width, required this.name, required this.access, + this.resetValue = 0, }); /// Add a legal value for the field. @@ -100,11 +106,29 @@ class CsrFieldConfig { /// Method to validate the configuration of a single field. void validate() { + // reset value must fit within the field's width + if (resetValue.bitLength > width) { + throw CsrValidationException( + 'Field $name reset value does not fit within the field.'); + } + // there must be at least 1 legal value for a READ_WRITE_LEGAL field + // and the reset value must be legal + // and every legal value must fit within the field's width if (access == CsrFieldAccess.READ_WRITE_LEGAL) { if (legalValues.isEmpty) { throw CsrValidationException( 'Field $name has no legal values but has access READ_WRITE_LEGAL.'); + } else if (!legalValues.contains(resetValue)) { + throw CsrValidationException( + 'Field $name reset value is not a legal value.'); + } + + for (final lv in legalValues) { + if (lv.bitLength > width) { + throw CsrValidationException( + 'Field $name legal value $lv does not fit within the field.'); + } } } } @@ -125,7 +149,7 @@ class CsrConfig { /// Architectural reset value for the register. /// /// Note that this can be overridden in the instantiation of the register. - int resetValue; + int? resetValue; /// Architectural property in which the register can be frontdoor read. /// @@ -154,7 +178,7 @@ class CsrConfig { CsrConfig({ required this.name, required this.access, - this.resetValue = 0, + this.resetValue, this.isFrontdoorReadable = true, this.isFrontdoorWritable = true, this.isBackdoorReadable = true, @@ -166,6 +190,17 @@ class CsrConfig { CsrFieldConfig getFieldByName(String nm) => fields.firstWhere((element) => element.name == nm); + /// Helper to derive a reset value for the register from its fields. + /// + /// Only should be used if a reset value isn't explicitly provided. + int resetValueFromFields() { + var rv = 0; + for (final field in fields) { + rv |= field.resetValue << field.start; + } + return rv; + } + /// Method to validate the configuration of a single register. /// /// Must check that its fields are mutually valid. @@ -233,7 +268,7 @@ class CsrInstanceConfig { CsrAccess get access => arch.access; /// Accessor to the architectural reset value of the register. - int get resetValue => arch.resetValue; + int get resetValue => arch.resetValue ?? arch.resetValueFromFields(); /// Accessor to the architectural frontdoor readability of the register. bool get isFrontdoorReadable => arch.isFrontdoorReadable; @@ -295,6 +330,12 @@ class CsrInstanceConfig { // start by running architectural register validation arch.validate(); + // reset value must fit within the register's width + if (resetValue.bitLength > width) { + throw CsrValidationException( + 'Register $name reset value does not fit within its width.'); + } + // check that the field widths don't exceed the register width var impliedEnd = 0; for (final field in fields) { diff --git a/test/csr_test.dart b/test/csr_test.dart index 56beebeb1..2696becfc 100644 --- a/test/csr_test.dart +++ b/test/csr_test.dart @@ -425,13 +425,34 @@ void main() { }); test('CSR validation failures', () async { - // illegal individual field - final badFieldCfg = CsrFieldConfig( + // illegal individual field - no legal values + final badFieldCfg1 = CsrFieldConfig( start: 0, width: 1, name: 'badFieldCfg', access: CsrFieldAccess.READ_WRITE_LEGAL); - expect(badFieldCfg.validate, + expect(badFieldCfg1.validate, + throwsA(predicate((f) => f is CsrValidationException))); + + // illegal individual field - reset value doesn't fit + final badFieldCfg2 = CsrFieldConfig( + start: 0, + width: 1, + name: 'badFieldCfg', + access: CsrFieldAccess.READ_WRITE, + resetValue: 0xfff); + expect(badFieldCfg2.validate, + throwsA(predicate((f) => f is CsrValidationException))); + + // illegal individual field - legal value doesn't fit + final badFieldCfg3 = CsrFieldConfig( + start: 0, + width: 1, + name: 'badFieldCfg', + access: CsrFieldAccess.READ_WRITE_LEGAL) + ..addLegalValue(0x0) + ..addLegalValue(0xfff); + expect(badFieldCfg3.validate, throwsA(predicate((f) => f is CsrValidationException))); // illegal architectural register @@ -460,8 +481,8 @@ void main() { expect(badArchRegCfg.validate, throwsA(predicate((f) => f is CsrValidationException))); - // illegal register instance - final badRegInstCfg = CsrInstanceConfig( + // illegal register instance - field surpasses reg width + final badRegInstCfg1 = CsrInstanceConfig( arch: CsrConfig(access: CsrAccess.READ_WRITE, name: 'reg') ..fields.add(CsrFieldConfig( start: 0, @@ -470,7 +491,21 @@ void main() { access: CsrFieldAccess.READ_WRITE)), addr: 0x0, width: 4); - expect(badRegInstCfg.validate, + expect(badRegInstCfg1.validate, + throwsA(predicate((f) => f is CsrValidationException))); + + // illegal register instance - reset value surpasses reg width + final badRegInstCfg2 = CsrInstanceConfig( + arch: CsrConfig(access: CsrAccess.READ_WRITE, name: 'reg') + ..fields.add(CsrFieldConfig( + start: 0, + width: 4, + name: 'field', + access: CsrFieldAccess.READ_WRITE)), + addr: 0x0, + width: 4, + resetValue: 0xfff); + expect(badRegInstCfg2.validate, throwsA(predicate((f) => f is CsrValidationException))); }); From f29b46fbcf7845ca99bc547c6e4a7202e5974fef Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Fri, 24 Jan 2025 14:50:38 -0800 Subject: [PATCH 14/14] Addressing various feedback items and fixing lint warnings. --- doc/components/csr.md | 324 ++++++++++ lib/src/memory/csr.dart | 746 ----------------------- lib/src/memory/csr/csr.dart | 192 ++++++ lib/src/memory/csr/csr_block.dart | 310 ++++++++++ lib/src/memory/{ => csr}/csr_config.dart | 48 +- lib/src/memory/csr/csr_top.dart | 276 +++++++++ lib/src/memory/memories.dart | 6 +- test/csr_test.dart | 45 +- 8 files changed, 1150 insertions(+), 797 deletions(-) create mode 100644 doc/components/csr.md delete mode 100644 lib/src/memory/csr.dart create mode 100644 lib/src/memory/csr/csr.dart create mode 100644 lib/src/memory/csr/csr_block.dart rename lib/src/memory/{ => csr}/csr_config.dart (94%) create mode 100644 lib/src/memory/csr/csr_top.dart diff --git a/doc/components/csr.md b/doc/components/csr.md new file mode 100644 index 000000000..647ce4f0a --- /dev/null +++ b/doc/components/csr.md @@ -0,0 +1,324 @@ +# Control/Status Registers (CSRs) + +ROHD HCL provides an infrastructure for creating CSRs at a few different granularities. These granularities are: + +- Individual register (as an extension of ROHD's `LogicStructure`). +- A block of registers in which each register is uniquely addressable. +- A top-level module that contains arbitrarily many register blocks, each being uniquely addressable. + +Each granularity will be discussed herein. + +## Individual CSR Definition + +An individual CSR is an extension of a `LogicStructure` and its members are the fields of the register. The class in HW to create a CSR is called `Csr`. It is constructed by passing a configuration object of type `CsrInstanceConfig`. + +### CsrInstanceConfig + +The `CsrInstanceConfig` is associated with a particular instantiation of an architecturally defined CSR. As such, it is constructed by passing a `CsrConfig` which defines the architectural CSR being instantiated. In addition to the properties of the architectural configuration, the instance configuration offers the following functionality: + +- An address or unique ID for this CSR instance (called `addr`). +- A bit width for this CSR instance (called `width`). +- The ability to override the architectural reset value for this CSR upon construction. +- The ability to override the architectural readability and writability of this CSR (frontdoor, backdoor). +- A `validate()` method to check for configuration correctness and consistency. + +#### Validation of CsrInstanceConfig + +The following checks are run: + +- Call the architectural configuration's `validate()` method. +- The `resetValue` for this CSR must fit within its `width`. +- Every field of this CSR must fit within its `width`. + +### CsrConfig + +The `CsrConfig` contains the architectural definition of a CSR (i.e., applies to every instance). An architectural CSR is logically comprised of 1 more fields. As such, it is constructed by providing a list of `CsrFieldConfig`s which defines its fields. The `CsrConfig` exposes the following functionality: + +- A name for the CSR (called `name`). +- An access mode for the CSR (called `access`). +- An optional architectural reset value for the CSR (called `resetValue`). +- Configuration for the readability and writeability of the CSR (Booleans called `isFrontdoorReadable`, `isFrontdoorWriteable`, `isBackdoorReadable`, `isBackdoorWriteable`). All of these values default to `true` if not provided. +- A `validate()` method to check for configuration correctness and consistency. +- A method `resetValueFromFields()` that computes an implicit reset value for the CSR based on the reset values for its individual fields. + +#### CSR Access Modes + +At the full CSR granularity, the access modes, defined in the Enum `CsrAccess`, are: + +- Read only +- Read/write + +Note that access rules only apply to frontdoor accesses. I.e., a read only register is still backdoor writeable if the configuration indicates as such. + +#### CSR Readability and Writeability Configuration + +We define a "frontdoor" access as one performed by an external agent (i.e., software or some external hardware). For frontdoor accesses, the access (read or write) occurs using the address of the register and only one register is typically accessed via the frontdoor in a given cycle. A status register typically must be frontdoor readable while a control register typically must be frontdoor writeable. + +We define a "backdoor" access as one performed by the encompassing hardware module (i.e., parent module reads or writes the register directly). For backdoor accesses, each register is individually and simultaneously accessible. A status register typically must be backdoor writeable while a control register typically must be backdoor readable. + +#### Register Field Implications + +There are certain special cases that are worth addressing regarding the fields of a CSR. + +- It is a legal to have a `CsrConfig` with an empty field list. In this case, a single implicit field is added at hardware generation time that spans the entirety of the register. This field's access rules match the access rules of the register. +- If there are any gap bits between fields within a register, an implicit reserved field is added at hardware generation time to fill the gap. These reserved fields are always read only. + +#### Validation of CsrConfig + +The following checks are run: + +- Call every field configuration's `validate()` method. +- No two fields in the register have the same `name`. +- No field overlaps with another field in the register. + +### CsrFieldConfig + +The `CsrFieldConfig` contains the architectural definition of an individual field within a CSR. The `CsrFieldConfig` exposes the following functionality: + +- A name for the field (called `name`). +- A starting bit position for the field within the register (called `start`). +- A bit width for the field (called `width`). +- An access mode for the field (called `access`). +- An optional reset value for the field (called `resetValue`). If not provided, the reset value defaults to 0. +- An optional list of legal values for the field (called `legalValues`). +- A `validate()` method to check for configuration correctness and consistency. +- A method `transformIllegalValue()` to map illegal field values to some legal one. The default implementation statically maps to the first legal value in the `legalValues` list. + +#### CSR Field Access Modes + +At the CSR field granularity, the access modes, defined in the Enum `CsrFieldAccess`, are: + +- Read only +- Read/write +- Write ones clear (read only, but writing it has a side effect) +- Read/write legal (can only write a legal value) + +Note that field access rules apply to both frontdoor and backdoor accesses of the register. + +To support the read/write legal mode, the configuration must provide a non-empty list of legal values to check against. In the hardware's logic construction, if a write is attempting to place an illegal value in the field, this write data is remapped to a legal value per the `transformIllegalValue()` method. This method can be custom defined in a derived class of `CsrFieldConfig` but has a default implementation that can be used as is. + +#### Validation of CsrFieldConfig + +The following checks are run: + +- The `resetValue` must fit within the field's `width`. +- If the field has read/write legal access, the `legalValues` must not be empty. +- If the field has read/write legal access, the `resetValue` must appear in the `legalValues`. +- If the field has read/write legal access, every value in `legalValues` must fit within the field's `width`. + +### API for Csr + +As the `Csr` class is an extension of `LogicStructure`, it inherits all of the functionality of `LogicStructure`. This includes the ability to directly assign and/or consume it like any ROHD `Logic`. + +In addition, the following attributes and methods are exposed: + +- Accessors to all of the member attributes of the underlying `CsrInstanceConfig`. +- `Logic getField(String name)` which returns a `Logic` for the field within the CSR with the name `name`. This enables easy read/write access to the fields of the register if needed for logic in the parent hardware module. +- `Logic getWriteData(Logic wd) ` which returns a `Logic` tranforming the input `Logic` in such a way as to be a legal value to write to the given register. For example, if a field is read only, the current data for that field is grafted into the input data in the appropriate bit position. + +## CSR Block Definition + +A CSR block is a `Module` that wraps a collection of `Csr` objects, making them accessible to reads and writes. The class in HW to create a CSR block is called `CsrBlock`. It is constructed by passing a configuration object of type `CsrBlockConfig`. + +### CsrBlockConfig + +The `CsrBlockConfig` defines the contents of a register block. As such, it is constructed by passing a list of `CsrInstanceConfig`s which defines the registers contained within the block. In addition to the register instance configurations, the block configuration offers the following functionality: + +- A name for the block (called `name`). +- A base address or unique ID for the block (called `baseAddr`). +- Methods to retrieve a given register instance's configuration by name or address (`getRegisterByName` and `getRegisterByAddr`). +- A `validate()` method 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 the given block. The return value is based on the largest `addr` across all register instances. +- A method `maxRegWidth()` that returns the number of bits in the largest register instance within the block. + +#### Heterogeneity in CSR Widths + +The `CsrBlock` hardware supports having registers of different widths if desired. As such, the hardware must ensure that the frontdoor data signals are wide enough to cover all registers within the block. + +#### Validation of CsrBlockConfig + +The following checks are run: + +- There must be at least 1 register instance in the block. +- No two register instances in the block have the same `name`. +- No two register instances in the block have the same `addr`. + +### Frontdoor CSR Access + +The `CsrBlock` module provides frontdoor read/write access to its registers through a read `DataPortInterface` and a write `DataPortInterface`. These are passed to the module in its constructor. + +To access a given register, in addition to asserting the enable signal, the encapsulating module must drive the address of the `DataPortInterface` to the address of one of the registers in the block (must be an exact match). If the address does not match any register, for writes, this is treated as a NOP and for reads the data returned is 0x0. + +When performing a write, the write data driven on the write `DataPortInterface` is transformed using the target register's `getWriteData` function so as to enforce writing only valid values. If the target register's width is less than the width of the write `DataPortInterface`'s input data signal, the LSBs of the write data up to the width of the register are used. + +When performing a read, if the target register's width is less than the width of the read `DataPortInterface`'s output data signal, the register's contents are zero-extended to match the target width. Read data from any register is always correct/legal by construction. + +If a given register is configured as not frontdoor writeable, there is no hardware instantiated to write the register through the write `DataPortInterface`. In this case, even on an address match, the operation becomes a NOP. + +If a given register is configured as not frontdoor readable, there is no hardware instantiated to read the register through the read `DataPortInterface`. In this case, even on an address match, the data returned will always be 0x0. + +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`. + +### Backdoor CSR Access + +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. + +If a given register is configured as not backdoor writeable, the write related signals on its `CsrBackdoorInterface` will not be present. + +If a given register is configured as not backdoor readable, the read related signals on its `CsrBackdoorInterface` will not be present. + +The width of the data signals on the `CsrBackdoorInterface` will exactly match the associated register's `width` by construction. + +#### CsrBackdoorInterface + +The `CsrBackdoorInterface` has the following ports: + +- `rdData` (width = `dataWidth`) => the data currently in the associated CSR +- `wrEn` (width = 1) => perform a backdoor write of the associated CSR in this cycle +- `wrData` (width = `dataWidth`) => the data to write to the associated CSR for a backdoor write + +The `CsrBackdoorInterface` is constructed with a `CsrInstanceConfig`. This config enables the following: + +- Populate the parameter `dataWidth` using the associated register config's `width`. +- Conditionally instantiate the `rdData` signal only if the associated register is backdoor readable. +- Conditionally instantiate the `wrEn` and `wrData` signals only if the associated register is backdoor writeable. + +Note that `rdData` actually returns a `Csr` (i.e., a `LogicStructure`). This can be useful for subsequently retrieving fields. + +### API for CsrBlock + +The following attributes and methods of `CsrBlock` are exposed: + +- Accessors to all of the member attributes of the underlying `CsrBlockConfig`. +- `CsrBackdoorInterface getBackdoorPortsByName(String name)` which returns the `CsrBackdoorInterface` for the register within the block with the name `name`. This enables encapsulating module's to drive/consume the backdoor read/write outputs. +- `CsrBackdoorInterface getBackdoorPortsByAddr(int addr)` which returns the `CsrBackdoorInterface` for the register within the block with the address `addr`. This enables encapsulating module's to drive/consume the backdoor read/write outputs. + +## CSR Top Definition + +A CSR top is a `Module` that wraps a collection of `CsrBlock` objects, making them and their underlying registers accessible to reads and writes. The class in HW to create a CSR top is called `CsrTop`. It is constructed by passing a configuration object of type `CsrTopConfig`. + +### CsrTopConfig + +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`). +- Methods to retrieve a given register block's configuration by name or base address (`getBlockByName` and `getBlockByAddr`). +- A `validate()` method 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`. +- A method `maxRegWidth()` that returns the number of bits in the largest register instance across all blocks. + +#### Validation of CsrTopConfig + +The following checks are run: + +- There must be at least 1 register block in the module. +- 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. + +### Frontdoor CSR Access + +Similar to `CsrBlock`, the `CsrTop` module provides frontdoor read/write access to its blocks/registers through a read `DataPortInterface` and a write `DataPortInterface`. 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. + +If an access drives an address that doesn't map to any block, writes are NOPs and reads return 0x0. + +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`. + +### Backdoor CSR Access + +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. + +### API for CsrTop + +The following attributes and methods of `CsrTop` are exposed: + +- Accessors to all of the member attributes of the underlying `CsrTopConfig`. +- `CsrBackdoorInterface getBackdoorPortsByName(String block, String reg)` which returns the `CsrBackdoorInterface` for the register with name `reg` within the block with name `block`. This enables encapsulating modules to drive/consume the backdoor read/write outputs. +- `CsrBackdoorInterface getBackdoorPortsByAddr(int blockAddr, int regAddr)` which returns the `CsrBackdoorInterface` for the register with address `regAddr` within the block with base address `blockAddr`. This enables encapsulating modules to drive/consume the backdoor read/write outputs. + + + + + + + + + + + + + + +## Interface + +The inputs to the divider module are: + +* `clock` => clock for synchronous logic +* `reset` => reset for synchronous logic (active high, synchronous to `clock`) +* `dividend` => the numerator operand +* `divisor` => the denominator operand +* `isSigned` => should the operands of the division be treated as signed integers +* `validIn` => indication that a new division operation is being requested +* `readyOut` => indication that the result of the current division can be consumed + +The outputs of the divider module are: + +* `quotient` => the result of the division +* `remainder` => the remainder of the division +* `divZero` => divide by zero error indication +* `validOut` => the result of the current division operation is ready +* `readyIn` => the divider is ready to accept a new operation + +The numerical inputs (`dividend`, `divisor`, `quotient`, `remainder`) are parametrized by a constructor parameter called `dataWidth`. All other signals have a width of 1. + +## Protocol Description + +To initiate a new request, it is expected that the requestor drive `validIn` to high along with the numerical values for `dividend`, `divisor` and the `isSigned` indicator. The first cycle in which `readyIn` is high where the above occurs is the cycle in which the operation is accepted by the divider. + +When the division is complete, the module will assert the `validOut` signal along with the numerical values of `quotient` and `remainder` representing the division result and the signal `divZero` to indicate whether or not a division by zero occurred. The module will hold these signal values until `readyOut` is driven high by the integrating environment. The integrating environment must assume that `quotient` and `remainder` are meaningless if `divZero` is asserted. + +## Mathematical Properties + +For the division, implicit rounding towards 0 is always performed. I.e., a negative quotient will always be rounded up if the dividend is not evenly divisible by the divisor. Note that this behavior is not uniform across all programming languages (for example, Python rounds towards negative infinity). + +For the remainder, the following equation will always precisely hold true: `dividend = divisor * quotient + remainder`. Note that this differs from the Euclidean modulo operator where the sign of the remainder is always positive. + +Overflow can only occur when `dividend=`, `divisor=-1` and `isSigned=1`. In this case, the hardware will return `quotient=` and `remainder=0`. This is by design as the mathematically correct quotient cannot be represented in the fixed number of bits available. + +## Code Example + +```dart + +final width = 32; // width of operands and result +final divIntf = MultiCycleDividerInterface(dataWidth: width); +final MultiCycleDivider divider = MultiCycleDivider(divIntf); + +// ... assume some clock generator and reset flow occur ... // + +if (divIntf.readyIn.value.toBool()) { + divIntf.validIn.put(1); + divIntf.dividend.put(2); + divIntf.divisor.put(1); + divIntf.isSigned.put(1); +} + +// ... wait some time for result ... // + +if (divIntf.validOut.value.toBool()) { + expect(divIntf.quotient.value.toInt(), 2); + expect(divIntf.remainder.value.toInt(), 0); + expect(divIntf.divZero.value.toBool(), false); + divIntf.readyOut.put(1); +} + +``` + +## Future Considerations + +In the future, an optimization might be added in which the `remainder` output is optional and controlled by a build time constructor parameter. If the remainder does not need to be computed, the implementation's upper bound latency can be significantly improved (`O(WIDTH**2)` => `O(WIDTH)`). diff --git a/lib/src/memory/csr.dart b/lib/src/memory/csr.dart deleted file mode 100644 index a33f85401..000000000 --- a/lib/src/memory/csr.dart +++ /dev/null @@ -1,746 +0,0 @@ -// Copyright (C) 2024-2025 Intel Corporation -// SPDX-License-Identifier: BSD-3-Clause -// -// csr.dart -// A flexible definition of CSRs. -// -// 2024 December -// Author: Josh Kimmel - -import 'dart:math'; - -import 'package:rohd/rohd.dart'; -import 'package:rohd_hcl/rohd_hcl.dart'; - -/// A grouping for interface signals of [CsrBackdoorInterface]s. -enum CsrBackdoorPortGroup { - /// For HW reads of CSRs. - read, - - /// For HW writes to CSRs. - write -} - -/// An interface to interact very simply with a CSR. -/// -/// Can be used for either read, write or both directions. -class CsrBackdoorInterface extends Interface { - /// Configuration for the associated CSR. - final CsrInstanceConfig config; - - /// Should this CSR be readable by the HW. - final bool hasRead; - - /// Should this CSR be writable by the HW. - final bool hasWrite; - - /// The width of data in the CSR. - final int dataWidth; - - /// The read data from the CSR. - Csr? get rdData => tryPort(config.name) as Csr?; - - /// Write the CSR in this cycle. - Logic? get wrEn => tryPort('${config.name}_wrEn'); - - /// Data to write to the CSR in this cycle. - Logic? get wrData => tryPort('${config.name}_wrData'); - - /// Constructs a new interface of specified [dataWidth] - /// and conditionally instantiates read and writes ports based on - /// [hasRead] and [hasWrite]. - CsrBackdoorInterface({required this.config, this.dataWidth = 0}) - : hasRead = config.isBackdoorReadable, - hasWrite = config.isBackdoorWritable { - if (hasRead) { - setPorts([ - Csr(config), - ], [ - CsrBackdoorPortGroup.read, - ]); - } - - if (hasWrite) { - setPorts([ - Port('${config.name}_wrEn'), - Port('${config.name}_wrData', dataWidth), - ], [ - CsrBackdoorPortGroup.write, - ]); - } - } - - /// Makes a copy of this [Interface] with matching configuration. - CsrBackdoorInterface clone() => - CsrBackdoorInterface(config: config, dataWidth: dataWidth); -} - -/// Logic representation of a CSR. -/// -/// Semantically, a register can be created with no fields. -/// In this case, a single implicit field is created that is -/// read/write and the entire width of the register. -class Csr extends LogicStructure { - /// Configuration for the CSR. - final CsrInstanceConfig config; - - /// A list of indices of all of the reserved fields in the CSR. - /// This is necessary because the configuration does not explicitly - /// define reserved fields, but they must be accounted for - /// in certain logic involving the CSR. - final List rsvdIndices; - - /// Getter for the address of the CSR. - int get addr => config.addr; - - /// Getter for the reset value of the CSR. - int get resetValue => config.resetValue; - - /// Getter for the access control of the CSR. - CsrAccess get access => config.access; - - /// Accessor to the architectural frontdoor readability of the register. - bool get isFrontdoorReadable => config.isFrontdoorReadable; - - /// Accessor to the architectural frontdoor writability of the register. - bool get isFrontdoorWritable => config.isFrontdoorWritable; - - /// Accessor to the architectural backdoor readability of the register. - bool get isBackdoorReadable => config.isBackdoorReadable; - - /// Accessor to the architectural backdoor writability of the register. - bool get isBackdoorWritable => config.isBackdoorWritable; - - /// Getter for the field configuration of the CSR - List get fields => config.fields; - - /// Explicit constructor. - /// - /// This constructor is private and should not be used directly. - /// Instead, the factory constructor [Csr] should be used. - /// This facilitates proper calling of the super constructor. - Csr._({ - required this.config, - required this.rsvdIndices, - required List fields, - }) : super(fields, name: config.name) { - config.validate(); - } - - /// Factory constructor for [Csr]. - /// - /// Because LogicStructure requires a List upon construction, - /// the factory method assists in creating the List upfront before - /// the LogicStructure constructor is called. - factory Csr( - CsrInstanceConfig config, - ) { - final fields = []; - final rsvds = []; - var currIdx = 0; - var rsvdCount = 0; - - // semantically a register with no fields means that - // there is one read/write field that is the entire register - if (config.fields.isEmpty) { - fields.add(Logic(name: '${config.name}_data', width: config.width)); - } - // there is at least one field explicitly defined so - // process them individually - else { - for (final field in config.fields) { - if (field.start > currIdx) { - fields.add(Logic( - name: '${config.name}_rsvd_$rsvdCount', - width: field.start - currIdx)); - rsvds.add(fields.length - 1); - rsvdCount++; - } - fields.add( - Logic(name: '${config.name}_${field.name}', width: field.width)); - currIdx = field.start + field.width; - } - if (currIdx < config.width) { - fields.add(Logic( - name: '${config.name}_rsvd_$rsvdCount', - width: config.width - currIdx)); - rsvds.add(fields.length - 1); - } - } - return Csr._( - config: config, - rsvdIndices: rsvds, - fields: fields, - ); - } - - /// Accessor to the bits of a particular field within the CSR by name [nm]. - Logic getField(String nm) => - elements.firstWhere((element) => element.name == '${name}_$nm'); - - /// Accessor to the config of a particular field within the CSR by name [nm]. - CsrFieldConfig getFieldConfigByName(String nm) => config.getFieldByName(nm); - - /// Given some arbitrary data [wd] to write to this CSR, - /// return the data that should actually be written based - /// on the access control of the CSR and its fields. - Logic getWriteData(Logic wd) { - // if the whole register is ready only, return the current value - if (access == CsrAccess.READ_ONLY) { - return this; - } - // register can be written, but still need to look at the fields... - else { - // special case of no explicit fields defined - // in this case, we have an implicit read/write field - // so there is nothing special to do - if (fields.isEmpty) { - return wd; - } - - // otherwise, we need to look at the fields - var finalWd = wd; - var currIdx = 0; - var currField = 0; - for (var i = 0; i < elements.length; i++) { - // if the given field is reserved or read only - // take the current value instead of the new value - final chk1 = rsvdIndices.contains(i); - if (chk1) { - finalWd = finalWd.withSet(currIdx, elements[i]); - currIdx += elements[i].width; - continue; - } - - // if the given field is read only - // take the current value instead of the new value - final chk2 = fields[currField].access == CsrFieldAccess.READ_ONLY || - fields[currField].access == CsrFieldAccess.WRITE_ONES_CLEAR; - if (chk2) { - finalWd = finalWd.withSet(currIdx, elements[i]); - currField++; - currIdx += elements[i].width; - continue; - } - - if (fields[currField].access == CsrFieldAccess.READ_WRITE_LEGAL) { - // if the given field is write legal - // make sure the value is in fact legal - // and transform it if not - final origVal = - wd.getRange(currIdx, currIdx + fields[currField].width); - final legalCases = {}; - for (var i = 0; i < fields[currField].legalValues.length; i++) { - legalCases[Const(fields[currField].legalValues[i], - width: fields[currField].width)] = origVal; - } - final newVal = cases( - origVal, - conditionalType: ConditionalType.unique, - legalCases, - defaultValue: Const(fields[currField].transformIllegalValue(), - width: fields[currField].width)); - - finalWd = finalWd.withSet(currIdx, newVal); - currField++; - currIdx += elements[i].width; - } else { - // normal read/write field - currField++; - currIdx += elements[i].width; - } - } - return finalWd; - } - } -} - -/// Logic representation of a block of registers. -/// -/// A block is just a collection of registers that are -/// readable and writable through an addressing scheme -/// that is relative (offset from) the base address of the block. -class CsrBlock extends Module { - /// Configuration for the CSR block. - final CsrBlockConfig config; - - /// CSRs in this block. - final List csrs; - - /// Direct access ports for reading and writing individual registers. - /// - /// There is a public copy that is exported out of the module - /// for consumption at higher levels in the hierarchy. - /// The private copies are used for internal logic. - final List backdoorInterfaces = []; - final List _backdoorInterfaces = []; - final Map _backdoorIndexMap = {}; - - /// Clock for the module. - late final Logic _clk; - - /// Reset for the module. - late final Logic _reset; - - /// Interface for frontdoor writes to CSRs. - late final DataPortInterface _frontWrite; - - /// Interface for frontdoor reads to CSRs. - late final DataPortInterface _frontRead; - - /// Getter for block's base address - int get baseAddr => config.baseAddr; - - /// Getter for the CSR configurations. - List get registers => config.registers; - - /// Constructor for a CSR block. - CsrBlock._({ - required this.config, - required this.csrs, - required Logic clk, - required Logic reset, - required DataPortInterface fdw, - required DataPortInterface fdr, - }) : super(name: config.name) { - config.validate(); - - _clk = addInput('${name}_clk', clk); - _reset = addInput('${name}_reset', reset); - - _frontWrite = fdw.clone() - ..connectIO(this, fdw, - inputTags: {DataPortGroup.control, DataPortGroup.data}, - outputTags: {}, - uniquify: (original) => 'frontWrite_$original'); - _frontRead = fdr.clone() - ..connectIO(this, fdr, - inputTags: {DataPortGroup.control}, - outputTags: {DataPortGroup.data}, - uniquify: (original) => 'frontRead_$original'); - - _validate(); - - for (var i = 0; i < csrs.length; i++) { - if (csrs[i].config.backdoorAccessible) { - _backdoorInterfaces.add(CsrBackdoorInterface( - config: csrs[i].config, dataWidth: csrs[i].config.width)); - backdoorInterfaces.add(CsrBackdoorInterface( - config: csrs[i].config, dataWidth: csrs[i].config.width)); - _backdoorInterfaces.last.connectIO(this, backdoorInterfaces.last, - outputTags: {CsrBackdoorPortGroup.read}, - inputTags: {CsrBackdoorPortGroup.write}, - uniquify: (original) => - '${name}_${csrs[i].config.name}_backdoor_$original'); - _backdoorIndexMap[i] = _backdoorInterfaces.length - 1; - } - } - - _buildLogic(); - } - - /// Create the CsrBlock from a configuration. - factory CsrBlock( - CsrBlockConfig config, - Logic clk, - Logic reset, - DataPortInterface fdw, - DataPortInterface fdr, - ) { - final csrs = []; - for (final reg in config.registers) { - csrs.add(Csr(reg)); - } - return CsrBlock._( - config: config, - csrs: csrs, - clk: clk, - reset: reset, - fdw: fdw, - fdr: fdr, - ); - } - - /// Accessor to the config of a particular register - /// within the block by name [nm]. - CsrInstanceConfig getRegisterByName(String nm) => - config.getRegisterByName(nm); - - /// Accessor to the config of a particular register - /// within the block by relative address [addr]. - CsrInstanceConfig getRegisterByAddr(int addr) => - config.getRegisterByAddr(addr); - - /// Accessor to the backdoor ports of a particular register - /// within the block by name [nm]. - CsrBackdoorInterface getBackdoorPortsByName(String nm) { - final idx = config.registers.indexOf(config.getRegisterByName(nm)); - if (_backdoorIndexMap.containsKey(idx)) { - return backdoorInterfaces[_backdoorIndexMap[idx]!]; - } else { - throw Exception('Register $nm not found in block ${config.name}'); - } - } - - /// Accessor to the backdoor ports of a particular register - /// within the block by relative address [addr]. - CsrBackdoorInterface getBackdoorPortsByAddr(int addr) { - final idx = config.registers.indexOf(config.getRegisterByAddr(addr)); - if (_backdoorIndexMap.containsKey(idx)) { - return backdoorInterfaces[_backdoorIndexMap[idx]!]; - } else { - throw Exception( - 'Register address $addr not found in block ${config.name}'); - } - } - - // validate the frontdoor interface widths to ensure that they are wide enough - void _validate() { - // check frontdoor interfaces - // data width must be at least as wide as the biggest register in the block - // 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 (_frontWrite.dataWidth < config.maxRegWidth()) { - throw CsrValidationException( - 'Frontdoor write interface data width must be at least ${config.maxRegWidth()}.'); - } - if (_frontRead.addrWidth < config.minAddrBits()) { - throw CsrValidationException( - 'Frontdoor read interface address width must be at least ${config.minAddrBits()}.'); - } - if (_frontWrite.dataWidth < config.minAddrBits()) { - throw CsrValidationException( - 'Frontdoor write interface address width must be at least ${config.minAddrBits()}.'); - } - } - - void _buildLogic() { - final addrWidth = _frontWrite.addrWidth; - - // individual CSR write logic - for (var i = 0; i < csrs.length; i++) { - Sequential( - _clk, - reset: _reset, - resetValues: { - csrs[i]: csrs[i].resetValue, - }, - [ - 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)), - ]), - // 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!), - ]), - // nothing to write this cycle - Else([ - csrs[i] < csrs[i], - ]), - ]) - ], - ); - } - - // 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(); - Combinational([ - Case( - _frontRead.addr, - conditionalType: ConditionalType.unique, - rdCases, - defaultItem: [ - rdData < Const(0, width: _frontRead.dataWidth), - ]), - ]); - _frontRead.data <= rdData; - - // driving of backdoor read outputs - for (var i = 0; i < csrs.length; i++) { - if (_backdoorIndexMap.containsKey(i) && - _backdoorInterfaces[_backdoorIndexMap[i]!].hasRead) { - _backdoorInterfaces[_backdoorIndexMap[i]!].rdData! <= csrs[i]; - } - } - } -} - -/// Top level module encapsulating groups of CSRs. -/// -/// This top module can include arbitrarily many CSR blocks. -/// Individual blocks are addressable using some number of -/// MSBs of the incoming address and registers within the given block -/// are addressable using the remaining LSBs of the incoming address. -class CsrTop extends Module { - /// Configuration for the CSR Top module. - final CsrTopConfig config; - - /// List of CSR blocks in this module. - final List _blocks = []; - - /// Clock for the module. - late final Logic _clk; - - /// Reset for the module. - late final Logic _reset; - - /// Interface for frontdoor writes to CSRs. - late final DataPortInterface _frontWrite; - - /// Interface for frontdoor reads to CSRs. - late final DataPortInterface _frontRead; - - // individual sub interfaces to blocks - final List _fdWrites = []; - final List _fdReads = []; - - /// Direct access ports for reading and writing individual registers. - /// - /// There is a public copy that is exported out of the module - /// for consumption at higher levels in the hierarchy. - /// The private copies are used for internal logic. - final List> backdoorInterfaces = []; - final List> _backdoorInterfaces = []; - final List> _backdoorIndexMaps = []; - - /// Getter for the block offset width. - int get blockOffsetWidth => config.blockOffsetWidth; - - /// Getter for the block configurations of the CSR. - List get blocks => config.blocks; - - /// Accessor to the backdoor ports of a particular register [reg] - /// within the block [block]. - CsrBackdoorInterface getBackdoorPortsByName(String block, String reg) { - final idx = config.blocks.indexOf(config.getBlockByName(block)); - if (idx >= 0 && idx < backdoorInterfaces.length) { - final idx1 = config.blocks[idx].registers - .indexOf(config.blocks[idx].getRegisterByName(reg)); - if (_backdoorIndexMaps[idx].containsKey(idx1)) { - return backdoorInterfaces[idx][_backdoorIndexMaps[idx][idx1]!]; - } else { - throw Exception('Register $reg in block $block could not be found.'); - } - } else { - throw Exception('Block $block could not be found.'); - } - } - - /// Accessor to the backdoor ports of a particular register - /// using its address [regAddr] within the block with address [blockAddr]. - CsrBackdoorInterface getBackdoorPortsByAddr(int blockAddr, int regAddr) { - final idx = config.blocks.indexOf(config.getBlockByAddr(blockAddr)); - if (idx >= 0 && idx < backdoorInterfaces.length) { - final idx1 = config.blocks[idx].registers - .indexOf(config.blocks[idx].getRegisterByAddr(regAddr)); - if (_backdoorIndexMaps[idx].containsKey(idx1)) { - return backdoorInterfaces[idx][_backdoorIndexMaps[idx][idx1]!]; - } else { - throw Exception( - 'Register with address $regAddr in block with address $blockAddr could not be found.'); - } - } else { - throw Exception('Block with address $blockAddr could not be found.'); - } - } - - CsrTop._({ - required this.config, - required Logic clk, - required Logic reset, - required DataPortInterface fdw, - required DataPortInterface fdr, - }) : super(name: config.name) { - config.validate(); - - _clk = addInput('${name}_clk', clk); - _reset = addInput('${name}_reset', reset); - - _frontWrite = fdw.clone() - ..connectIO(this, fdw, - inputTags: {DataPortGroup.control, DataPortGroup.data}, - outputTags: {}, - uniquify: (original) => '${name}_frontWrite_$original'); - _frontRead = fdr.clone() - ..connectIO(this, fdr, - inputTags: {DataPortGroup.control}, - outputTags: {DataPortGroup.data}, - uniquify: (original) => '${name}_frontRead_$original'); - - _validate(); - - 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)); - } - - for (var i = 0; i < blocks.length; i++) { - _backdoorInterfaces.add([]); - backdoorInterfaces.add([]); - _backdoorIndexMaps.add({}); - for (var j = 0; j < blocks[i].registers.length; j++) { - if (blocks[i].registers[j].backdoorAccessible) { - _backdoorInterfaces[i].add(CsrBackdoorInterface( - config: blocks[i].registers[j], - dataWidth: blocks[i].registers[j].width)); - backdoorInterfaces[i].add(CsrBackdoorInterface( - config: blocks[i].registers[j], - dataWidth: blocks[i].registers[j].width)); - _backdoorInterfaces[i].last.connectIO( - this, backdoorInterfaces[i].last, - outputTags: {CsrBackdoorPortGroup.read}, - inputTags: {CsrBackdoorPortGroup.write}, - uniquify: (original) => - '${name}_${blocks[i].name}_${blocks[i].registers[j].name}_backdoor_$original'); - _backdoorIndexMaps[i][j] = _backdoorInterfaces[i].length - 1; - } - } - } - - _buildLogic(); - } - - /// create the CsrBlock from a configuration - factory CsrTop( - CsrTopConfig config, - Logic clk, - Logic reset, - DataPortInterface fdw, - DataPortInterface fdr, - ) => - CsrTop._( - config: config, - clk: clk, - reset: reset, - fdw: fdw, - fdr: fdr, - ); - - /// Accessor to the config of a particular register block - /// within the module by name [nm]. - CsrBlockConfig getBlockByName(String nm) => config.getBlockByName(nm); - - /// Accessor to the config of a particular register block - /// within the module by relative address [addr]. - CsrBlockConfig getBlockByAddr(int addr) => config.getBlockByAddr(addr); - - // validate the frontdoor interface widths to ensure that they are wide enough - void _validate() { - // data width must be at least as wide as 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()) { - throw CsrValidationException( - 'Frontdoor read interface data width must be at least ${config.maxRegWidth()}.'); - } - if (_frontWrite.dataWidth < config.maxRegWidth()) { - throw CsrValidationException( - 'Frontdoor write interface data width must be at least ${config.maxRegWidth()}.'); - } - if (_frontRead.addrWidth < config.minAddrBits() || - _frontRead.addrWidth < blockOffsetWidth) { - throw CsrValidationException( - 'Frontdoor read interface address width must be at least ${max(config.minAddrBits(), blockOffsetWidth)}.'); - } - if (_frontWrite.dataWidth < config.minAddrBits()) { - throw CsrValidationException( - 'Frontdoor write interface address width must be at least ${max(config.minAddrBits(), blockOffsetWidth)}.'); - } - } - - void _buildLogic() { - final addrWidth = _frontWrite.addrWidth; - - // mask out LSBs to perform a match on block - final maskedFrontWrAddr = _frontWrite.addr & - ~Const((1 << blockOffsetWidth) - 1, width: addrWidth); - final maskedFrontRdAddr = - _frontRead.addr & ~Const((1 << blockOffsetWidth) - 1, width: addrWidth); - - // shift out MSBs to pass the appropriate address into the blocks - final shiftedFrontWrAddr = _frontWrite.addr.getRange(0, blockOffsetWidth); - final shiftedFrontRdAddr = _frontRead.addr.getRange(0, blockOffsetWidth); - - // drive frontdoor write and read inputs - for (var i = 0; i < _blocks.length; i++) { - _fdWrites[i].en <= - _frontWrite.en & - maskedFrontWrAddr - .eq(Const(_blocks[i].baseAddr, width: addrWidth)); - _fdReads[i].en <= - _frontWrite.en & - maskedFrontRdAddr - .eq(Const(_blocks[i].baseAddr, width: addrWidth)); - - _fdWrites[i].addr <= shiftedFrontWrAddr; - _fdReads[i].addr <= shiftedFrontRdAddr; - - _fdWrites[i].data <= _frontWrite.data; - - // drive backdoor write ports - for (var j = 0; j < blocks[i].registers.length; j++) { - if (_backdoorIndexMaps[i].containsKey(j) && - _backdoorInterfaces[i][_backdoorIndexMaps[i][j]!].hasWrite) { - _blocks[i].backdoorInterfaces[_backdoorIndexMaps[i][j]!].wrEn! <= - _backdoorInterfaces[i][_backdoorIndexMaps[i][j]!].wrEn!; - _blocks[i].backdoorInterfaces[_backdoorIndexMaps[i][j]!].wrData! <= - _backdoorInterfaces[i][_backdoorIndexMaps[i][j]!].wrData!; - } - } - } - - // capture frontdoor read output - final rdData = Logic(name: 'internalRdData', width: _frontRead.dataWidth); - final rdCases = _blocks - .asMap() - .entries - .map( - (block) => CaseItem(Const(block.value.baseAddr, width: addrWidth), [ - rdData < _fdReads[block.key].data, - ])) - .toList(); - Combinational([ - Case( - maskedFrontRdAddr, - conditionalType: ConditionalType.unique, - rdCases, - defaultItem: [ - rdData < Const(0, width: _frontRead.dataWidth), - ]), - ]); - _frontRead.data <= rdData; - - // driving of backdoor read outputs - for (var i = 0; i < blocks.length; i++) { - for (var j = 0; j < blocks[i].registers.length; j++) { - if (_backdoorIndexMaps[i].containsKey(j) && - _backdoorInterfaces[i][_backdoorIndexMaps[i][j]!].hasRead) { - _backdoorInterfaces[i][_backdoorIndexMaps[i][j]!].rdData! <= - _blocks[i].backdoorInterfaces[_backdoorIndexMaps[i][j]!].rdData!; - } - } - } - } -} diff --git a/lib/src/memory/csr/csr.dart b/lib/src/memory/csr/csr.dart new file mode 100644 index 000000000..e44909867 --- /dev/null +++ b/lib/src/memory/csr/csr.dart @@ -0,0 +1,192 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// csr.dart +// A flexible definition of CSRs. +// +// 2024 December +// Author: Josh Kimmel + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; + +/// Logic representation of a CSR. +/// +/// Semantically, a register can be created with no fields. +/// In this case, a single implicit field is created that is +/// read/write and the entire width of the register. +class Csr extends LogicStructure { + /// Configuration for the CSR. + final CsrInstanceConfig config; + + /// A list of indices of all of the reserved fields in the CSR. + /// This is necessary because the configuration does not explicitly + /// define reserved fields, but they must be accounted for + /// in certain logic involving the CSR. + final List rsvdIndices; + + /// Getter for the address of the CSR. + int get addr => config.addr; + + /// Getter for the reset value of the CSR. + int get resetValue => config.resetValue; + + /// Getter for the access control of the CSR. + CsrAccess get access => config.access; + + /// Accessor to the architectural frontdoor readability of the register. + bool get isFrontdoorReadable => config.isFrontdoorReadable; + + /// Accessor to the architectural frontdoor writability of the register. + bool get isFrontdoorWritable => config.isFrontdoorWritable; + + /// Accessor to the architectural backdoor readability of the register. + bool get isBackdoorReadable => config.isBackdoorReadable; + + /// Accessor to the architectural backdoor writability of the register. + bool get isBackdoorWritable => config.isBackdoorWritable; + + /// Getter for the field configuration of the CSR + List get fields => config.fields; + + /// Factory constructor for [Csr]. + /// + /// Because LogicStructure requires a List upon construction, + /// the factory method assists in creating the List upfront before + /// the LogicStructure constructor is called. + factory Csr( + CsrInstanceConfig config, + ) { + final fields = []; + final rsvds = []; + var currIdx = 0; + var rsvdCount = 0; + + // semantically a register with no fields means that + // there is one read/write field that is the entire register + if (config.fields.isEmpty) { + fields.add(Logic(name: '${config.name}_data', width: config.width)); + } + // there is at least one field explicitly defined so + // process them individually + else { + for (final field in config.fields) { + if (field.start > currIdx) { + fields.add(Logic( + name: '${config.name}_rsvd_$rsvdCount', + width: field.start - currIdx)); + rsvds.add(fields.length - 1); + rsvdCount++; + } + fields.add( + Logic(name: '${config.name}_${field.name}', width: field.width)); + currIdx = field.start + field.width; + } + if (currIdx < config.width) { + fields.add(Logic( + name: '${config.name}_rsvd_$rsvdCount', + width: config.width - currIdx)); + rsvds.add(fields.length - 1); + } + } + return Csr._( + config: config, + rsvdIndices: rsvds, + fields: fields, + ); + } + + /// Explicit constructor. + /// + /// This constructor is private and should not be used directly. + /// Instead, the factory constructor [Csr] should be used. + /// This facilitates proper calling of the super constructor. + Csr._({ + required this.config, + required this.rsvdIndices, + required List fields, + }) : super(fields, name: config.name) { + config.validate(); + } + + /// Accessor to the bits of a particular field within the CSR by name [name]. + Logic getField(String name) => + elements.firstWhere((element) => element.name == '${this.name}_$name'); + + /// Accessor to the config of a particular field within the CSR by name [name]. + CsrFieldConfig getFieldConfigByName(String name) => + config.getFieldByName(name); + + /// Given some arbitrary data [wd] to write to this CSR, + /// return the data that should actually be written based + /// on the access control of the CSR and its fields. + Logic getWriteData(Logic wd) { + // if the whole register is ready only, return the current value + if (access == CsrAccess.readOnly) { + return this; + } + // register can be written, but still need to look at the fields... + else { + // special case of no explicit fields defined + // in this case, we have an implicit read/write field + // so there is nothing special to do + if (fields.isEmpty) { + return wd; + } + + // otherwise, we need to look at the fields + var finalWd = wd; + var currIdx = 0; + var currField = 0; + for (var i = 0; i < elements.length; i++) { + // if the given field is reserved or read only + // take the current value instead of the new value + final chk1 = rsvdIndices.contains(i); + if (chk1) { + finalWd = finalWd.withSet(currIdx, elements[i]); + currIdx += elements[i].width; + continue; + } + + // if the given field is read only + // take the current value instead of the new value + final chk2 = fields[currField].access == CsrFieldAccess.readOnly || + fields[currField].access == CsrFieldAccess.writeOnesClear; + if (chk2) { + finalWd = finalWd.withSet(currIdx, elements[i]); + currField++; + currIdx += elements[i].width; + continue; + } + + if (fields[currField].access == CsrFieldAccess.readWriteLegal) { + // if the given field is write legal + // make sure the value is in fact legal + // and transform it if not + final origVal = + wd.getRange(currIdx, currIdx + fields[currField].width); + final legalCases = {}; + for (var i = 0; i < fields[currField].legalValues.length; i++) { + legalCases[Const(fields[currField].legalValues[i], + width: fields[currField].width)] = origVal; + } + final newVal = cases( + origVal, + conditionalType: ConditionalType.unique, + legalCases, + defaultValue: Const(fields[currField].transformIllegalValue(), + width: fields[currField].width)); + + finalWd = finalWd.withSet(currIdx, newVal); + currField++; + currIdx += elements[i].width; + } else { + // normal read/write field + currField++; + currIdx += elements[i].width; + } + } + return finalWd; + } + } +} diff --git a/lib/src/memory/csr/csr_block.dart b/lib/src/memory/csr/csr_block.dart new file mode 100644 index 000000000..2ac14770f --- /dev/null +++ b/lib/src/memory/csr/csr_block.dart @@ -0,0 +1,310 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// csr_block.dart +// A flexible definition of CSRs. +// +// 2024 December +// Author: Josh Kimmel + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; + +/// A grouping for interface signals of [CsrBackdoorInterface]s. +enum CsrBackdoorPortGroup { + /// For HW reads of CSRs. + read, + + /// For HW writes to CSRs. + write +} + +/// An interface to interact very simply with a CSR. +/// +/// Can be used for either read, write or both directions. +class CsrBackdoorInterface extends Interface { + /// Configuration for the associated CSR. + final CsrInstanceConfig config; + + /// Should this CSR be readable by the HW. + final bool hasRead; + + /// Should this CSR be writable by the HW. + final bool hasWrite; + + /// The width of data in the CSR. + final int dataWidth; + + /// The read data from the CSR. + Csr? get rdData => tryPort(config.name) as Csr?; + + /// Write the CSR in this cycle. + Logic? get wrEn => tryPort('${config.name}_wrEn'); + + /// Data to write to the CSR in this cycle. + Logic? get wrData => tryPort('${config.name}_wrData'); + + /// Constructs a new interface of specified [dataWidth] + /// and conditionally instantiates read and writes ports based on + /// [hasRead] and [hasWrite]. + CsrBackdoorInterface({ + required this.config, + }) : dataWidth = config.width, + hasRead = config.isBackdoorReadable, + hasWrite = config.isBackdoorWritable { + if (hasRead) { + setPorts([ + Csr(config), + ], [ + CsrBackdoorPortGroup.read, + ]); + } + + if (hasWrite) { + setPorts([ + Port('${config.name}_wrEn'), + Port('${config.name}_wrData', dataWidth), + ], [ + CsrBackdoorPortGroup.write, + ]); + } + } + + /// Makes a copy of this [Interface] with matching configuration. + CsrBackdoorInterface clone() => CsrBackdoorInterface(config: config); +} + +/// Logic representation of a block of registers. +/// +/// A block is just a collection of registers that are +/// readable and writable through an addressing scheme +/// that is relative (offset from) the base address of the block. +class CsrBlock extends Module { + /// Configuration for the CSR block. + final CsrBlockConfig config; + + /// CSRs in this block. + final List csrs; + + /// Direct access ports for reading and writing individual registers. + /// + /// There is a public copy that is exported out of the module + /// for consumption at higher levels in the hierarchy. + /// The private copies are used for internal logic. + final List backdoorInterfaces = []; + final List _backdoorInterfaces = []; + final Map _backdoorIndexMap = {}; + + /// Clock for the module. + late final Logic _clk; + + /// Reset for the module. + late final Logic _reset; + + /// Interface for frontdoor writes to CSRs. + late final DataPortInterface _frontWrite; + + /// Interface for frontdoor reads to CSRs. + late final DataPortInterface _frontRead; + + /// Getter for block's base address + int get baseAddr => config.baseAddr; + + /// Getter for the CSR configurations. + List get registers => config.registers; + + /// Create the CsrBlock from a configuration. + factory CsrBlock( + CsrBlockConfig config, + Logic clk, + Logic reset, + DataPortInterface fdw, + DataPortInterface fdr, + ) { + final csrs = []; + for (final reg in config.registers) { + csrs.add(Csr(reg)); + } + return CsrBlock._( + config: config, + csrs: csrs, + clk: clk, + reset: reset, + fdw: fdw, + fdr: fdr, + ); + } + + /// Constructor for a CSR block. + CsrBlock._({ + required this.config, + required this.csrs, + required Logic clk, + required Logic reset, + required DataPortInterface fdw, + required DataPortInterface fdr, + }) : super(name: config.name) { + config.validate(); + + _clk = addInput('${name}_clk', clk); + _reset = addInput('${name}_reset', reset); + + _frontWrite = fdw.clone() + ..connectIO(this, fdw, + inputTags: {DataPortGroup.control, DataPortGroup.data}, + outputTags: {}, + uniquify: (original) => 'frontWrite_$original'); + _frontRead = fdr.clone() + ..connectIO(this, fdr, + inputTags: {DataPortGroup.control}, + outputTags: {DataPortGroup.data}, + uniquify: (original) => 'frontRead_$original'); + + _validate(); + + for (var i = 0; i < csrs.length; i++) { + if (csrs[i].config.backdoorAccessible) { + _backdoorInterfaces.add(CsrBackdoorInterface(config: csrs[i].config)); + backdoorInterfaces.add(CsrBackdoorInterface(config: csrs[i].config)); + _backdoorInterfaces.last.connectIO(this, backdoorInterfaces.last, + outputTags: {CsrBackdoorPortGroup.read}, + inputTags: {CsrBackdoorPortGroup.write}, + uniquify: (original) => + '${name}_${csrs[i].config.name}_backdoor_$original'); + _backdoorIndexMap[i] = _backdoorInterfaces.length - 1; + } + } + + _buildLogic(); + } + + /// Accessor to the config of a particular register + /// within the block by name [name]. + CsrInstanceConfig getRegisterByName(String name) => + config.getRegisterByName(name); + + /// Accessor to the config of a particular register + /// within the block by relative address [addr]. + CsrInstanceConfig getRegisterByAddr(int addr) => + config.getRegisterByAddr(addr); + + /// Accessor to the backdoor ports of a particular register + /// within the block by name [name]. + CsrBackdoorInterface getBackdoorPortsByName(String name) { + final idx = config.registers.indexOf(config.getRegisterByName(name)); + if (_backdoorIndexMap.containsKey(idx)) { + return backdoorInterfaces[_backdoorIndexMap[idx]!]; + } else { + throw Exception('Register $name not found in block ${config.name}'); + } + } + + /// Accessor to the backdoor ports of a particular register + /// within the block by relative address [addr]. + CsrBackdoorInterface getBackdoorPortsByAddr(int addr) { + final idx = config.registers.indexOf(config.getRegisterByAddr(addr)); + if (_backdoorIndexMap.containsKey(idx)) { + return backdoorInterfaces[_backdoorIndexMap[idx]!]; + } else { + throw Exception( + 'Register address $addr not found in block ${config.name}'); + } + } + + // validate the frontdoor interface widths to ensure that they are wide enough + void _validate() { + // check frontdoor interfaces + // data width must be at least as wide as the biggest register in the block + // 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 (_frontWrite.dataWidth < config.maxRegWidth()) { + throw CsrValidationException( + 'Frontdoor write interface data width must be ' + 'at least ${config.maxRegWidth()}.'); + } + if (_frontRead.addrWidth < config.minAddrBits()) { + throw CsrValidationException( + 'Frontdoor read interface address width must be ' + 'at least ${config.minAddrBits()}.'); + } + if (_frontWrite.dataWidth < config.minAddrBits()) { + throw CsrValidationException( + 'Frontdoor write interface address width must be ' + 'at least ${config.minAddrBits()}.'); + } + } + + void _buildLogic() { + final addrWidth = _frontWrite.addrWidth; + + // individual CSR write logic + for (var i = 0; i < csrs.length; i++) { + Sequential( + _clk, + reset: _reset, + resetValues: { + csrs[i]: csrs[i].resetValue, + }, + [ + 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)), + ]), + // 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!), + ]), + // nothing to write this cycle + Else([ + csrs[i] < csrs[i], + ]), + ]) + ], + ); + } + + // 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(); + Combinational([ + Case( + _frontRead.addr, + conditionalType: ConditionalType.unique, + rdCases, + defaultItem: [ + rdData < Const(0, width: _frontRead.dataWidth), + ]), + ]); + _frontRead.data <= rdData; + + // driving of backdoor read outputs + for (var i = 0; i < csrs.length; i++) { + if (_backdoorIndexMap.containsKey(i) && + _backdoorInterfaces[_backdoorIndexMap[i]!].hasRead) { + _backdoorInterfaces[_backdoorIndexMap[i]!].rdData! <= csrs[i]; + } + } + } +} diff --git a/lib/src/memory/csr_config.dart b/lib/src/memory/csr/csr_config.dart similarity index 94% rename from lib/src/memory/csr_config.dart rename to lib/src/memory/csr/csr_config.dart index e829c5f8e..6329ca2b4 100644 --- a/lib/src/memory/csr_config.dart +++ b/lib/src/memory/csr/csr_config.dart @@ -22,32 +22,26 @@ class CsrValidationException implements Exception { /// Definitions for various register field access patterns. enum CsrFieldAccess { /// Register field is read only. - // ignore: constant_identifier_names - READ_ONLY, + readOnly, /// Register field can be read and written. - // ignore: constant_identifier_names - READ_WRITE, + readWrite, /// Writing 1's to the field triggers some other action, /// but the field itself is read only. - // ignore: constant_identifier_names - WRITE_ONES_CLEAR, + writeOnesClear, /// Only legal values can be written - // ignore: constant_identifier_names - READ_WRITE_LEGAL, + readWriteLegal, } /// Definitions for various register access patterns. enum CsrAccess { /// Register is read only. - // ignore: constant_identifier_names - READ_ONLY, + readOnly, /// Register can be read and written. - // ignore: constant_identifier_names - READ_WRITE, + readWrite, } /// Configuration for a register field. @@ -115,7 +109,7 @@ class CsrFieldConfig { // there must be at least 1 legal value for a READ_WRITE_LEGAL field // and the reset value must be legal // and every legal value must fit within the field's width - if (access == CsrFieldAccess.READ_WRITE_LEGAL) { + if (access == CsrFieldAccess.readWriteLegal) { if (legalValues.isEmpty) { throw CsrValidationException( 'Field $name has no legal values but has access READ_WRITE_LEGAL.'); @@ -186,9 +180,9 @@ class CsrConfig { }); /// Accessor to the config of a particular field - /// within the register by name [nm]. - CsrFieldConfig getFieldByName(String nm) => - fields.firstWhere((element) => element.name == nm); + /// within the register by name [name]. + CsrFieldConfig getFieldByName(String name) => + fields.firstWhere((element) => element.name == name); /// Helper to derive a reset value for the register from its fields. /// @@ -320,8 +314,8 @@ class CsrInstanceConfig { } /// Accessor to the config of a particular field - /// within the register by name [nm]. - CsrFieldConfig getFieldByName(String nm) => arch.getFieldByName(nm); + /// within the register by name [name]. + CsrFieldConfig getFieldByName(String name) => arch.getFieldByName(name); /// Method to validate the configuration of a single register. /// @@ -374,9 +368,9 @@ class CsrBlockConfig { }); /// Accessor to the config of a particular register - /// within the block by name [nm]. - CsrInstanceConfig getRegisterByName(String nm) => - registers.firstWhere((element) => element.name == nm); + /// within the block by name [name]. + CsrInstanceConfig getRegisterByName(String name) => + registers.firstWhere((element) => element.name == name); /// Accessor to the config of a particular register /// within the block by relative address [addr]. @@ -463,9 +457,9 @@ class CsrTopConfig { }); /// Accessor to the config of a particular register block - /// within the module by name [nm]. - CsrBlockConfig getBlockByName(String nm) => - blocks.firstWhere((element) => element.name == nm); + /// within the module by name [name]. + CsrBlockConfig getBlockByName(String name) => + blocks.firstWhere((element) => element.name == name); /// Accessor to the config of a particular register block /// within the module by relative address [addr]. @@ -508,7 +502,8 @@ class CsrTopConfig { } 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.'); + 'Register blocks ${blocks[i].name} and ${blocks[j].name} are ' + 'too close together per the block offset width.'); } } } @@ -520,7 +515,8 @@ class CsrTopConfig { // 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.'); + 'Block offset width is too small to address all register in all ' + 'blocks in the module. The minimum offset width is $maxMinAddrBits.'); } } diff --git a/lib/src/memory/csr/csr_top.dart b/lib/src/memory/csr/csr_top.dart new file mode 100644 index 000000000..7bcaeb004 --- /dev/null +++ b/lib/src/memory/csr/csr_top.dart @@ -0,0 +1,276 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// csr_top.dart +// A flexible definition of CSRs. +// +// 2024 December +// Author: Josh Kimmel + +import 'dart:math'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; + +/// Top level module encapsulating groups of CSRs. +/// +/// This top module can include arbitrarily many CSR blocks. +/// Individual blocks are addressable using some number of +/// MSBs of the incoming address and registers within the given block +/// are addressable using the remaining LSBs of the incoming address. +class CsrTop extends Module { + /// Configuration for the CSR Top module. + final CsrTopConfig config; + + /// List of CSR blocks in this module. + final List _blocks = []; + + /// Clock for the module. + late final Logic _clk; + + /// Reset for the module. + late final Logic _reset; + + /// Interface for frontdoor writes to CSRs. + late final DataPortInterface _frontWrite; + + /// Interface for frontdoor reads to CSRs. + late final DataPortInterface _frontRead; + + // individual sub interfaces to blocks + final List _fdWrites = []; + final List _fdReads = []; + + /// Direct access ports for reading and writing individual registers. + /// + /// There is a public copy that is exported out of the module + /// for consumption at higher levels in the hierarchy. + /// The private copies are used for internal logic. + final List> backdoorInterfaces = []; + final List> _backdoorInterfaces = []; + final List> _backdoorIndexMaps = []; + + /// Getter for the block offset width. + int get blockOffsetWidth => config.blockOffsetWidth; + + /// Getter for the block configurations of the CSR. + List get blocks => config.blocks; + + /// Accessor to the backdoor ports of a particular register [reg] + /// within the block [block]. + CsrBackdoorInterface getBackdoorPortsByName(String block, String reg) { + final idx = config.blocks.indexOf(config.getBlockByName(block)); + if (idx >= 0 && idx < backdoorInterfaces.length) { + final idx1 = config.blocks[idx].registers + .indexOf(config.blocks[idx].getRegisterByName(reg)); + if (_backdoorIndexMaps[idx].containsKey(idx1)) { + return backdoorInterfaces[idx][_backdoorIndexMaps[idx][idx1]!]; + } else { + throw Exception('Register $reg in block $block could not be found.'); + } + } else { + throw Exception('Block $block could not be found.'); + } + } + + /// Accessor to the backdoor ports of a particular register + /// using its address [regAddr] within the block with address [blockAddr]. + CsrBackdoorInterface getBackdoorPortsByAddr(int blockAddr, int regAddr) { + final idx = config.blocks.indexOf(config.getBlockByAddr(blockAddr)); + if (idx >= 0 && idx < backdoorInterfaces.length) { + final idx1 = config.blocks[idx].registers + .indexOf(config.blocks[idx].getRegisterByAddr(regAddr)); + if (_backdoorIndexMaps[idx].containsKey(idx1)) { + return backdoorInterfaces[idx][_backdoorIndexMaps[idx][idx1]!]; + } else { + throw Exception('Register with address $regAddr in block with ' + 'address $blockAddr could not be found.'); + } + } else { + throw Exception('Block with address $blockAddr could not be found.'); + } + } + + /// create the CsrBlock from a configuration + factory CsrTop( + CsrTopConfig config, + Logic clk, + Logic reset, + DataPortInterface fdw, + DataPortInterface fdr, + ) => + CsrTop._( + config: config, + clk: clk, + reset: reset, + fdw: fdw, + fdr: fdr, + ); + + CsrTop._({ + required this.config, + required Logic clk, + required Logic reset, + required DataPortInterface fdw, + required DataPortInterface fdr, + }) : super(name: config.name) { + config.validate(); + + _clk = addInput('${name}_clk', clk); + _reset = addInput('${name}_reset', reset); + + _frontWrite = fdw.clone() + ..connectIO(this, fdw, + inputTags: {DataPortGroup.control, DataPortGroup.data}, + outputTags: {}, + uniquify: (original) => '${name}_frontWrite_$original'); + _frontRead = fdr.clone() + ..connectIO(this, fdr, + inputTags: {DataPortGroup.control}, + outputTags: {DataPortGroup.data}, + uniquify: (original) => '${name}_frontRead_$original'); + + _validate(); + + 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)); + } + + for (var i = 0; i < blocks.length; i++) { + _backdoorInterfaces.add([]); + backdoorInterfaces.add([]); + _backdoorIndexMaps.add({}); + for (var j = 0; j < blocks[i].registers.length; j++) { + if (blocks[i].registers[j].backdoorAccessible) { + _backdoorInterfaces[i] + .add(CsrBackdoorInterface(config: blocks[i].registers[j])); + backdoorInterfaces[i] + .add(CsrBackdoorInterface(config: blocks[i].registers[j])); + _backdoorInterfaces[i].last.connectIO( + this, backdoorInterfaces[i].last, + outputTags: {CsrBackdoorPortGroup.read}, + inputTags: {CsrBackdoorPortGroup.write}, + uniquify: (original) => + '${name}_${blocks[i].name}_${blocks[i].registers[j].name}' + '_backdoor_$original'); + _backdoorIndexMaps[i][j] = _backdoorInterfaces[i].length - 1; + } + } + } + + _buildLogic(); + } + + /// Accessor to the config of a particular register block + /// within the module by name [name]. + CsrBlockConfig getBlockByName(String name) => config.getBlockByName(name); + + /// Accessor to the config of a particular register block + /// within the module by relative address [addr]. + CsrBlockConfig getBlockByAddr(int addr) => config.getBlockByAddr(addr); + + // validate the frontdoor interface widths to ensure that they are wide enough + void _validate() { + // data width must be at least as wide as + // 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()) { + throw CsrValidationException( + 'Frontdoor read interface data width must be ' + 'at least ${config.maxRegWidth()}.'); + } + if (_frontWrite.dataWidth < config.maxRegWidth()) { + throw CsrValidationException( + 'Frontdoor write interface data width must be ' + 'at least ${config.maxRegWidth()}.'); + } + if (_frontRead.addrWidth < config.minAddrBits() || + _frontRead.addrWidth < blockOffsetWidth) { + throw CsrValidationException( + 'Frontdoor read interface address width must be ' + 'at least ${max(config.minAddrBits(), blockOffsetWidth)}.'); + } + if (_frontWrite.dataWidth < config.minAddrBits()) { + throw CsrValidationException( + 'Frontdoor write interface address width must be ' + 'at least ${max(config.minAddrBits(), blockOffsetWidth)}.'); + } + } + + void _buildLogic() { + final addrWidth = _frontWrite.addrWidth; + + // mask out LSBs to perform a match on block + final maskedFrontWrAddr = _frontWrite.addr & + ~Const((1 << blockOffsetWidth) - 1, width: addrWidth); + final maskedFrontRdAddr = + _frontRead.addr & ~Const((1 << blockOffsetWidth) - 1, width: addrWidth); + + // shift out MSBs to pass the appropriate address into the blocks + final shiftedFrontWrAddr = _frontWrite.addr.getRange(0, blockOffsetWidth); + final shiftedFrontRdAddr = _frontRead.addr.getRange(0, blockOffsetWidth); + + // drive frontdoor write and read inputs + for (var i = 0; i < _blocks.length; i++) { + _fdWrites[i].en <= + _frontWrite.en & + maskedFrontWrAddr + .eq(Const(_blocks[i].baseAddr, width: addrWidth)); + _fdReads[i].en <= + _frontWrite.en & + maskedFrontRdAddr + .eq(Const(_blocks[i].baseAddr, width: addrWidth)); + + _fdWrites[i].addr <= shiftedFrontWrAddr; + _fdReads[i].addr <= shiftedFrontRdAddr; + + _fdWrites[i].data <= _frontWrite.data; + + // drive backdoor write ports + for (var j = 0; j < blocks[i].registers.length; j++) { + if (_backdoorIndexMaps[i].containsKey(j) && + _backdoorInterfaces[i][_backdoorIndexMaps[i][j]!].hasWrite) { + _blocks[i].backdoorInterfaces[_backdoorIndexMaps[i][j]!].wrEn! <= + _backdoorInterfaces[i][_backdoorIndexMaps[i][j]!].wrEn!; + _blocks[i].backdoorInterfaces[_backdoorIndexMaps[i][j]!].wrData! <= + _backdoorInterfaces[i][_backdoorIndexMaps[i][j]!].wrData!; + } + } + } + + // capture frontdoor read output + final rdData = Logic(name: 'internalRdData', width: _frontRead.dataWidth); + final rdCases = _blocks + .asMap() + .entries + .map( + (block) => CaseItem(Const(block.value.baseAddr, width: addrWidth), [ + rdData < _fdReads[block.key].data, + ])) + .toList(); + Combinational([ + Case( + maskedFrontRdAddr, + conditionalType: ConditionalType.unique, + rdCases, + defaultItem: [ + rdData < Const(0, width: _frontRead.dataWidth), + ]), + ]); + _frontRead.data <= rdData; + + // driving of backdoor read outputs + for (var i = 0; i < blocks.length; i++) { + for (var j = 0; j < blocks[i].registers.length; j++) { + if (_backdoorIndexMaps[i].containsKey(j) && + _backdoorInterfaces[i][_backdoorIndexMaps[i][j]!].hasRead) { + _backdoorInterfaces[i][_backdoorIndexMaps[i][j]!].rdData! <= + _blocks[i].backdoorInterfaces[_backdoorIndexMaps[i][j]!].rdData!; + } + } + } + } +} diff --git a/lib/src/memory/memories.dart b/lib/src/memory/memories.dart index 17e79f02e..540ac30e7 100644 --- a/lib/src/memory/memories.dart +++ b/lib/src/memory/memories.dart @@ -1,7 +1,9 @@ // Copyright (C) 2023-2024 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause -export 'csr.dart'; -export 'csr_config.dart'; +export 'csr/csr.dart'; +export 'csr/csr_block.dart'; +export 'csr/csr_config.dart'; +export 'csr/csr_top.dart'; export 'memory.dart'; export 'register_file.dart'; diff --git a/test/csr_test.dart b/test/csr_test.dart index 2696becfc..0e59c368c 100644 --- a/test/csr_test.dart +++ b/test/csr_test.dart @@ -18,7 +18,7 @@ import 'package:test/test.dart'; class MyNoFieldCsr extends CsrConfig { MyNoFieldCsr({super.name = 'myNoFieldCsr'}) - : super(access: CsrAccess.READ_ONLY); + : super(access: CsrAccess.readOnly); } class MyNoFieldCsrInstance extends CsrInstanceConfig { @@ -34,7 +34,7 @@ class MyNoFieldCsrInstance extends CsrInstanceConfig { } class MyFieldCsr extends CsrConfig { - MyFieldCsr({super.name = 'myFieldCsr'}) : super(access: CsrAccess.READ_WRITE); + MyFieldCsr({super.name = 'myFieldCsr'}) : super(access: CsrAccess.readWrite); } class MyFieldCsrInstance extends CsrInstanceConfig { @@ -47,12 +47,12 @@ class MyFieldCsrInstance extends CsrInstanceConfig { // example of a static field fields ..add(CsrFieldConfig( - start: 0, width: 2, name: 'field1', access: CsrFieldAccess.READ_ONLY)) + start: 0, width: 2, name: 'field1', access: CsrFieldAccess.readOnly)) ..add(CsrFieldConfig( start: 2, width: 2, name: 'field2', - access: CsrFieldAccess.READ_WRITE_LEGAL) + access: CsrFieldAccess.readWriteLegal) ..addLegalValue(0x0) ..addLegalValue(0x1)) // example of a field with dynamic start and width @@ -60,14 +60,14 @@ class MyFieldCsrInstance extends CsrInstanceConfig { start: width ~/ 2, width: width ~/ 4, name: 'field3', - access: CsrFieldAccess.READ_WRITE)); + access: CsrFieldAccess.readWrite)); // example of field duplication for (var i = 0; i < width ~/ 4; i++) { fields.add(CsrFieldConfig( start: (3 * width ~/ 4) + i, width: 1, name: 'field4_$i', - access: CsrFieldAccess.WRITE_ONES_CLEAR)); + access: CsrFieldAccess.writeOnesClear)); } } } @@ -202,7 +202,6 @@ void main() { final csrBlockCfg = MyRegisterBlock( baseAddr: 0x0, - csrWidth: csrWidth, numNoFieldCsrs: 2, ); @@ -430,7 +429,7 @@ void main() { start: 0, width: 1, name: 'badFieldCfg', - access: CsrFieldAccess.READ_WRITE_LEGAL); + access: CsrFieldAccess.readWriteLegal); expect(badFieldCfg1.validate, throwsA(predicate((f) => f is CsrValidationException))); @@ -439,7 +438,7 @@ void main() { start: 0, width: 1, name: 'badFieldCfg', - access: CsrFieldAccess.READ_WRITE, + access: CsrFieldAccess.readWrite, resetValue: 0xfff); expect(badFieldCfg2.validate, throwsA(predicate((f) => f is CsrValidationException))); @@ -449,7 +448,7 @@ void main() { start: 0, width: 1, name: 'badFieldCfg', - access: CsrFieldAccess.READ_WRITE_LEGAL) + access: CsrFieldAccess.readWriteLegal) ..addLegalValue(0x0) ..addLegalValue(0xfff); expect(badFieldCfg3.validate, @@ -457,38 +456,38 @@ void main() { // illegal architectural register final badArchRegCfg = - CsrConfig(access: CsrAccess.READ_WRITE, name: 'badArchRegCfg') + CsrConfig(access: CsrAccess.readWrite, name: 'badArchRegCfg') ..fields.add(CsrFieldConfig( start: 0, width: 8, name: 'field', - access: CsrFieldAccess.READ_WRITE)) + access: CsrFieldAccess.readWrite)) ..fields.add(CsrFieldConfig( start: 3, width: 4, name: 'field', - access: CsrFieldAccess.READ_WRITE)) + access: CsrFieldAccess.readWrite)) ..fields.add(CsrFieldConfig( start: 3, width: 10, name: 'field1', - access: CsrFieldAccess.READ_WRITE)) + access: CsrFieldAccess.readWrite)) ..fields.add(CsrFieldConfig( start: 9, width: 11, name: 'field2', - access: CsrFieldAccess.READ_WRITE)); + access: CsrFieldAccess.readWrite)); expect(badArchRegCfg.validate, throwsA(predicate((f) => f is CsrValidationException))); // illegal register instance - field surpasses reg width final badRegInstCfg1 = CsrInstanceConfig( - arch: CsrConfig(access: CsrAccess.READ_WRITE, name: 'reg') + arch: CsrConfig(access: CsrAccess.readWrite, name: 'reg') ..fields.add(CsrFieldConfig( start: 0, width: 32, name: 'field', - access: CsrFieldAccess.READ_WRITE)), + access: CsrFieldAccess.readWrite)), addr: 0x0, width: 4); expect(badRegInstCfg1.validate, @@ -496,12 +495,12 @@ void main() { // illegal register instance - reset value surpasses reg width final badRegInstCfg2 = CsrInstanceConfig( - arch: CsrConfig(access: CsrAccess.READ_WRITE, name: 'reg') + arch: CsrConfig(access: CsrAccess.readWrite, name: 'reg') ..fields.add(CsrFieldConfig( start: 0, width: 4, name: 'field', - access: CsrFieldAccess.READ_WRITE)), + access: CsrFieldAccess.readWrite)), addr: 0x0, width: 4, resetValue: 0xfff); @@ -518,15 +517,15 @@ void main() { // illegal block - duplication final badBlockCfg2 = CsrBlockConfig(name: 'block', baseAddr: 0x0) ..registers.add(CsrInstanceConfig( - arch: CsrConfig(access: CsrAccess.READ_WRITE, name: 'reg'), + arch: CsrConfig(access: CsrAccess.readWrite, name: 'reg'), addr: 0x0, width: 4)) ..registers.add(CsrInstanceConfig( - arch: CsrConfig(access: CsrAccess.READ_WRITE, name: 'reg'), + arch: CsrConfig(access: CsrAccess.readWrite, name: 'reg'), addr: 0x1, width: 4)) ..registers.add(CsrInstanceConfig( - arch: CsrConfig(access: CsrAccess.READ_WRITE, name: 'reg1'), + arch: CsrConfig(access: CsrAccess.readWrite, name: 'reg1'), addr: 0x1, width: 4)); expect(badBlockCfg2.validate, @@ -549,7 +548,7 @@ void main() { final badTopCfg3 = CsrTopConfig(name: 'top', blockOffsetWidth: 1) ..blocks.add(CsrBlockConfig(name: 'block', baseAddr: 0x0) ..registers.add(CsrInstanceConfig( - arch: CsrConfig(access: CsrAccess.READ_WRITE, name: 'reg'), + arch: CsrConfig(access: CsrAccess.readWrite, name: 'reg'), addr: 0x4, width: 4))); expect(badTopCfg3.validate,