From ab42597656d1817c00c660f74bf1877168e536db Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Tue, 14 Jan 2025 16:21:29 -0800 Subject: [PATCH 01/16] Definition for AXI4 interfaces. --- lib/src/interfaces/axi4.dart | 551 +++++++++++++++++++++++++++++++++++ 1 file changed, 551 insertions(+) create mode 100644 lib/src/interfaces/axi4.dart diff --git a/lib/src/interfaces/axi4.dart b/lib/src/interfaces/axi4.dart new file mode 100644 index 000000000..61e1f4417 --- /dev/null +++ b/lib/src/interfaces/axi4.dart @@ -0,0 +1,551 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// axi.dart +// Definitions for the AXI interface. +// +// 2025 January +// Author: Josh Kimmel + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/src/exceptions.dart'; + +/// A grouping of signals on the [Axi4ReadInterface] interface based on direction. +enum Axi4Direction { + /// Miscellaneous system-level signals, common inputs to both sides. + misc, + + /// Signals driven by the main. + fromMain, + + /// Signals driven by the subordinate. + fromSubordinate +} + +/// AXI4 clock and reset. +class Axi4SystemInterface extends Interface { + /// Clock for the interface. + /// + /// Global clock signals. Synchronous signals are sampled on the rising edge of the global clock. + Logic get clk => port('ACLK'); + + /// Reset signal (active LOW). + /// + /// Global reset signal. This signal is active-LOW, synchronous but can be asserted asynchronously. + Logic get resetN => port('ARESETn'); + + /// Construct a new instance of an AXI4 interface. + Axi4SystemInterface() { + setPorts([ + Port('ACLK'), + Port('ARESETn'), + ], [ + Axi4Direction.misc, + ]); + } +} + +/// A standard AXI4 read interface. +class Axi4ReadInterface extends Interface { + /// Number of channel ID bits. + final int idWidth; + + /// Width of the address bus. + final int addrWidth; + + /// Width of len field. + final int lenWidth; + + /// Width of the user AR sideband field. + final int aruserWidth; + + /// Width of the system data buses. + final int dataWidth; + + /// Width of the user R sideband field. + final int ruserWidth; + + /// Width of the size field is fixed for AXI4. + final int sizeWidth = 3; + + /// Width of the burst field is fixed for AXI4. + final int burstWidth = 2; + + /// Width of the cache field is fixed for AXI4. + final int cacheWidth = 4; + + /// Width of the prot field is fixed for AXI4. + final int protWidth = 3; + + /// Width of the QoS field is fixed for AXI4. + final int qosWidth = 4; + + /// Width of the region field is fixed for AXI4. + final int regionWidth = 4; + + /// Width of the RRESP field is fixed for AXI4. + final int rrespWidth = 2; + + /// Controls the presence of [arLock] which is an optional port. + final bool useLock; + + /// Controls the presence of [rLast] which is an optional port. + final bool useLast; + + /// Identification tag for a read transaction. + /// + /// Width is equal to [idWidth]. + Logic? get arId => tryPort('ARID'); + + /// The address of the first transfer in a read transaction. + /// + /// Width is equal to [addrWidth]. + Logic get arAddr => port('ARADDR'); + + /// Length, the exact number of data transfers in a read transaction. + /// + /// Width is equal to [lenWidth]. + Logic? get arLen => tryPort('ARLEN'); + + /// Size, the number of bytes in each data transfer in a read transaction. + /// + /// Width is equal to [sizeWidth]. + Logic? get arSize => tryPort('ARSIZE'); + + /// Burst type, indicates how address changes between each transfer in a read transaction. + /// + /// Width is equal to [burstWidth]. + Logic? get arBurst => tryPort('ARBURST'); + + /// Provides information about atomic characteristics of a read transaction. + /// + /// Width is always 1. + Logic? get arLock => tryPort('ARLOCK'); + + /// Indicates how a read transaction is required to progress through a system. + /// + /// Width is equal to [cacheWidth]. + Logic? get arCache => tryPort('ARCACHE'); + + /// Protection attributes of a read transaction: privilege, security level, and access type. + /// + /// Width is equal to [protWidth]. + Logic get arProt => port('ARPROT'); + + /// Quality of service identifier for a read transaction. + /// + /// Width is equal to [qosWidth]. + Logic? get arQos => tryPort('ARQOS'); + + /// Region indicator for a Read transaction. + /// + /// Width is equal to [regionWidth]. + Logic? get arRegion => tryPort('ARREGION'); + + /// User-defined extension for the read address channel. + /// + /// Width is equal to [aruserWidth]. + Logic? get arUser => tryPort('ARUSER'); + + /// Indicates that the read address channel signals are valid. + /// + /// Width is always 1. + Logic get arValid => port('ARVALID'); + + /// Indicates that a transfer on the read address channel can be accepted. + /// + /// Width is always 1. + Logic get arReady => port('ARREADY'); + + /// Identification tag for read data and response. + /// + /// Width is equal to [idWidth]. + Logic? get rId => tryPort('RID'); + + /// Read data. + /// + /// Width is equal to [dataWidth]. + Logic get rData => port('RDATA'); + + /// Read response, indicates the status of a read transfer. + /// + /// Width is equal to [rrespWidth]. + Logic? get rResp => tryPort('RRESP'); + + /// Indicates whether this is the last data transfer in a read transaction. + /// + /// Width is always 1. + Logic? get rLast => tryPort('RLAST'); + + /// User-defined extension for the read data channel. + /// + /// Width is equal to [ruserWidth]. + Logic? get rUser => tryPort('RUSER'); + + /// Indicates that the read data channel signals are valid. + /// + /// Width is always 1. + Logic get rValid => port('RVALID'); + + /// Indicates that a transfer on the read data channel can be accepted. + /// + /// Width is always 1. + Logic get rReady => port('RREADY'); + + /// Construct a new instance of an AXI4 interface. + /// + /// Default values in constructor are from official spec. + Axi4ReadInterface({ + this.idWidth = 4, + this.addrWidth = 32, + this.lenWidth = 8, + this.dataWidth = 64, + this.aruserWidth = 32, + this.ruserWidth = 32, + this.useLock = true, + this.useLast = true, + }) { + _validateParameters(); + + setPorts([ + if (idWidth > 0) Port('ARID', idWidth), + Port('ARADDR', addrWidth), + if (lenWidth > 0) Port('ARLEN', lenWidth), + if (sizeWidth > 0) Port('ARSIZE', sizeWidth), + if (burstWidth > 0) Port('ARBURST', burstWidth), + if (useLock) Port('ARLOCK'), + if (cacheWidth > 0) Port('ARCACHE', cacheWidth), + Port('ARPROT', protWidth), + if (qosWidth > 0) Port('ARQOS', qosWidth), + if (regionWidth > 0) Port('ARREGION', regionWidth), + if (aruserWidth > 0) Port('ARUSER', aruserWidth), + Port('ARVALID'), + Port('RREADY'), + ], [ + Axi4Direction.fromMain, + ]); + + setPorts([ + if (idWidth > 0) Port('RID', idWidth), + Port('RDATA', dataWidth), + if (rrespWidth > 0) Port('RRESP', rrespWidth), + if (useLast) Port('RLAST'), + if (ruserWidth > 0) Port('RUSER', ruserWidth), + Port('RVALID'), + Port('ARREADY'), + ], [ + Axi4Direction.fromSubordinate, + ]); + } + + /// Constructs a new [Axi4ReadInterface] with identical parameters to [other]. + Axi4ReadInterface.clone(Axi4ReadInterface other) + : this( + idWidth: other.idWidth, + addrWidth: other.addrWidth, + lenWidth: other.lenWidth, + dataWidth: other.dataWidth, + aruserWidth: other.aruserWidth, + ruserWidth: other.ruserWidth, + useLock: other.useLock, + useLast: other.useLast, + ); + + /// Checks that the values set for parameters follow the specification's + /// restrictions. + void _validateParameters() { + if (addrWidth > 64 || addrWidth < 1) { + throw RohdHclException('addrWidth must be >= 1 and <= 64.'); + } + + const legalDataWidths = [8, 16, 32, 64, 128, 256, 512, 1024]; + if (!legalDataWidths.contains(dataWidth)) { + throw RohdHclException('dataWidth must be one of $legalDataWidths'); + } + + if (lenWidth < 0 || lenWidth > 8) { + throw RohdHclException('lenWidth must be >= 0 and <= 8'); + } + + if (idWidth < 0 || idWidth > 32) { + throw RohdHclException('idWidth must be >= 0 and <= 32'); + } + + if (aruserWidth < 0 || aruserWidth > 128) { + throw RohdHclException('aruserWidth must be >= 0 and <= 128'); + } + + if (ruserWidth < 0 || ruserWidth > (dataWidth ~/ 2)) { + throw RohdHclException( + 'ruserWidth must be >= 0 and <= ${dataWidth ~/ 2}'); + } + } +} + +/// A standard AXI4 write interface. +class Axi4WriteInterface extends Interface { + /// Number of channel ID bits. + final int idWidth; + + /// Width of the address bus. + final int addrWidth; + + /// Width of len field. + final int lenWidth; + + /// Width of the user AW sideband field. + final int awuserWidth; + + /// Width of the system data buses. + final int dataWidth; + + /// Width of the write strobe. + final int strbWidth; + + /// Width of the user W sideband field. + final int wuserWidth; + + /// Width of the user B sideband field. + final int buserWidth; + + /// Width of the size field is fixed for AXI4. + final int sizeWidth = 3; + + /// Width of the burst field is fixed for AXI4. + final int burstWidth = 2; + + /// Width of the cache field is fixed for AXI4. + final int cacheWidth = 4; + + /// Width of the prot field is fixed for AXI4. + final int protWidth = 3; + + /// Width of the QoS field is fixed for AXI4. + final int qosWidth = 4; + + /// Width of the region field is fixed for AXI4. + final int regionWidth = 4; + + /// Width of the BRESP field is fixed for AXI4. + final int brespWidth = 2; + + /// Controls the presence of [awLock] which is an optional port. + final bool useLock; + + /// Identification tag for a write transaction. + /// + /// Width is equal to [idWidth]. + Logic? get awId => tryPort('AWID'); + + /// The address of the first transfer in a write transaction. + /// + /// Width is equal to [addrWidth]. + Logic get awAddr => port('AWADDR'); + + /// Length, the exact number of data transfers in a write transaction. + /// + /// Width is equal to [lenWidth]. + Logic? get awLen => tryPort('AWLEN'); + + /// Size, the number of bytes in each data transfer in a write transaction. + /// + /// Width is equal to [sizeWidth]. + Logic? get awSize => tryPort('AWSIZE'); + + /// Burst type, indicates how address changes between each transfer in a write transaction. + /// + /// Width is equal to [burstWidth]. + Logic? get awBurst => tryPort('AWBURST'); + + /// Provides information about atomic characteristics of a write transaction. + /// + /// Width is always 1. + Logic? get awLock => tryPort('AWLOCK'); + + /// Indicates how a write transaction is required to progress through a system. + /// + /// Width is equal to [cacheWidth]. + Logic? get awCache => tryPort('AWCACHE'); + + /// Protection attributes of a write transaction: privilege, security level, and access type. + /// + /// Width is equal to [protWidth]. + Logic get awProt => port('AWPROT'); + + /// Quality of service identifier for a write transaction. + /// + /// Width is equal to [qosWidth]. + Logic? get awQos => tryPort('AWQOS'); + + /// Region indicator for a write transaction. + /// + /// Width is equal to [regionWidth]. + Logic? get awRegion => tryPort('AWREGION'); + + /// User-defined extension for the write address channel. + /// + /// Width is equal to [awuserWidth]. + Logic? get awUser => tryPort('AWUSER'); + + /// Indicates that the write address channel signals are valid. + /// + /// Width is always 1. + Logic get awValid => port('AWVALID'); + + /// Indicates that a transfer on the write address channel can be accepted. + /// + /// Width is always 1. + Logic get awReady => port('AWREADY'); + + /// Write data. + /// + /// Width is equal to [dataWidth]. + Logic get wData => port('WDATA'); + + /// Write strobes, indicate which byte lanes hold valid data. + /// + /// Width is equal to [strbWidth]. + Logic get wStrb => port('WSTRB'); + + /// Indicates whether this is the last data transfer in a write transaction. + /// + /// Width is always 1. + Logic get wLast => port('WLAST'); + + /// User-defined extension for the write data channel. + /// + /// Width is equal to [wuserWidth]. + Logic? get wUser => tryPort('WUSER'); + + /// Indicates that the write data channel signals are valid. + /// + /// Width is always 1. + Logic get wValid => port('WVALID'); + + /// Indicates that a transfer on the write data channel can be accepted. + /// + /// Width is always 1. + Logic get wReady => port('WREADY'); + + /// Identification tag for a write response. + /// + /// Width is equal to [idWidth]. + Logic? get bId => tryPort('BID'); + + /// Write response, indicates the status of a write transaction. + /// + /// Width is equal to [brespWidth]. + Logic? get bResp => tryPort('BRESP'); + + /// User-defined extension for the write response channel. + /// + /// Width is equal to [buserWidth]. + Logic? get bUser => tryPort('BUSER'); + + /// Indicates that the write response channel signals are valid. + /// + /// Width is always 1. + Logic get bValid => port('BVALID'); + + /// Indicates that a transfer on the write response channel can be accepted. + /// + /// Width is always 1. + Logic get bReady => port('BREADY'); + + /// Construct a new instance of an AXI4 interface. + /// + /// Default values in constructor are from official spec. + Axi4WriteInterface({ + this.idWidth = 4, + this.addrWidth = 32, + this.lenWidth = 8, + this.dataWidth = 64, + this.awuserWidth = 32, + this.wuserWidth = 32, + this.buserWidth = 16, + this.useLock = true, + }) : strbWidth = dataWidth ~/ 8 { + _validateParameters(); + + setPorts([ + if (idWidth > 0) Port('AWID', idWidth), + Port('AWADDR', addrWidth), + if (lenWidth > 0) Port('AWLEN', lenWidth), + if (sizeWidth > 0) Port('AWSIZE', sizeWidth), + if (burstWidth > 0) Port('AWBURST', burstWidth), + if (useLock) Port('AWLOCK'), + if (cacheWidth > 0) Port('AWCACHE', cacheWidth), + Port('AWPROT', protWidth), + if (qosWidth > 0) Port('AWQOS', qosWidth), + if (regionWidth > 0) Port('AWREGION', regionWidth), + if (awuserWidth > 0) Port('AWUSER', awuserWidth), + Port('AWVALID'), + Port('WDATA', dataWidth), + if (strbWidth > 0) Port('WSTRB', strbWidth), + Port('WLAST'), + if (wuserWidth > 0) Port('WUSER', wuserWidth), + Port('WVALID'), + Port('BREADY'), + ], [ + Axi4Direction.fromMain, + ]); + + setPorts([ + if (idWidth > 0) Port('BID', idWidth), + if (brespWidth > 0) Port('BRESP', brespWidth), + if (buserWidth > 0) Port('BUSER', buserWidth), + Port('BVALID'), + Port('AWREADY'), + Port('WREADY'), + ], [ + Axi4Direction.fromSubordinate, + ]); + } + + /// Constructs a new [Axi4WriteInterface] with identical parameters to [other]. + Axi4WriteInterface.clone(Axi4WriteInterface other) + : this( + idWidth: other.idWidth, + addrWidth: other.addrWidth, + lenWidth: other.lenWidth, + dataWidth: other.dataWidth, + awuserWidth: other.awuserWidth, + wuserWidth: other.wuserWidth, + buserWidth: other.buserWidth, + useLock: other.useLock, + ); + + /// Checks that the values set for parameters follow the specification's + /// restrictions. + void _validateParameters() { + if (addrWidth > 64 || addrWidth < 1) { + throw RohdHclException('addrWidth must be >= 1 and <= 64.'); + } + + const legalDataWidths = [8, 16, 32, 64, 128, 256, 512, 1024]; + if (!legalDataWidths.contains(dataWidth)) { + throw RohdHclException('dataWidth must be one of $legalDataWidths'); + } + + if (lenWidth < 0 || lenWidth > 8) { + throw RohdHclException('lenWidth must be >= 0 and <= 8'); + } + + if (idWidth < 0 || idWidth > 32) { + throw RohdHclException('idWidth must be >= 0 and <= 32'); + } + + if (awuserWidth < 0 || awuserWidth > 128) { + throw RohdHclException('awuserWidth must be >= 0 and <= 128'); + } + + if (wuserWidth < 0 || wuserWidth > (dataWidth ~/ 2)) { + throw RohdHclException( + 'wuserWidth must be >= 0 and <= ${dataWidth ~/ 2}'); + } + + if (buserWidth < 0 || buserWidth > 128) { + throw RohdHclException('buserWidth must be >= 0 and <= 128'); + } + } +} From 2650df18cead279954b038f84fbb6686b98c2588 Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Wed, 15 Jan 2025 09:55:25 -0800 Subject: [PATCH 02/16] WIP: starting on BFM. --- lib/src/interfaces/interfaces.dart | 1 + lib/src/models/axi4_bfm/axi4_bfm.dart | 10 + .../axi4_bfm/axi4_compliance_checker.dart | 153 ++++++++++++++ lib/src/models/axi4_bfm/axi4_main.dart | 51 +++++ lib/src/models/axi4_bfm/axi4_main_driver.dart | 130 ++++++++++++ lib/src/models/axi4_bfm/axi4_monitor.dart | 64 ++++++ lib/src/models/axi4_bfm/axi4_packet.dart | 199 ++++++++++++++++++ lib/src/models/axi4_bfm/axi4_subordinate.dart | 175 +++++++++++++++ lib/src/models/axi4_bfm/axi4_tracker.dart | 89 ++++++++ 9 files changed, 872 insertions(+) create mode 100644 lib/src/models/axi4_bfm/axi4_bfm.dart create mode 100644 lib/src/models/axi4_bfm/axi4_compliance_checker.dart create mode 100644 lib/src/models/axi4_bfm/axi4_main.dart create mode 100644 lib/src/models/axi4_bfm/axi4_main_driver.dart create mode 100644 lib/src/models/axi4_bfm/axi4_monitor.dart create mode 100644 lib/src/models/axi4_bfm/axi4_packet.dart create mode 100644 lib/src/models/axi4_bfm/axi4_subordinate.dart create mode 100644 lib/src/models/axi4_bfm/axi4_tracker.dart diff --git a/lib/src/interfaces/interfaces.dart b/lib/src/interfaces/interfaces.dart index 45914619e..3d397f8f7 100644 --- a/lib/src/interfaces/interfaces.dart +++ b/lib/src/interfaces/interfaces.dart @@ -2,3 +2,4 @@ // SPDX-License-Identifier: BSD-3-Clause export 'apb.dart'; +export 'axi4.dart'; diff --git a/lib/src/models/axi4_bfm/axi4_bfm.dart b/lib/src/models/axi4_bfm/axi4_bfm.dart new file mode 100644 index 000000000..d0ef643b7 --- /dev/null +++ b/lib/src/models/axi4_bfm/axi4_bfm.dart @@ -0,0 +1,10 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +export 'axi4_compliance_checker.dart'; +export 'axi4_main.dart'; +export 'axi4_main_driver.dart'; +export 'axi4_monitor.dart'; +export 'axi4_packet.dart'; +export 'axi4_subordinate.dart'; +export 'axi4_tracker.dart'; diff --git a/lib/src/models/axi4_bfm/axi4_compliance_checker.dart b/lib/src/models/axi4_bfm/axi4_compliance_checker.dart new file mode 100644 index 000000000..a9aa311d1 --- /dev/null +++ b/lib/src/models/axi4_bfm/axi4_compliance_checker.dart @@ -0,0 +1,153 @@ +// Copyright (C) 2023-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// apb_completer.dart +// An agent for completing APB requests. +// +// 2023 June 14 +// Author: Max Korbel + +import 'dart:async'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A checker for some of the rules defined in the APB interface specification. +/// +/// This does not necessarily cover all rules defined in the spec. +class ApbComplianceChecker extends Component { + /// The interface being checked. + final ApbInterface intf; + + /// Creates a new compliance checker for [intf]. + ApbComplianceChecker( + this.intf, { + required Component parent, + String name = 'apbComplianceChecker', + }) : super(name, parent); + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + // wait for reset to complete + await intf.resetN.nextPosedge; + + var accessLastCycle = false; + + LogicValue? lastWrite; + LogicValue? lastAddr; + List? lastSel; + LogicValue? lastWriteData; + LogicValue? lastStrb; + LogicValue? lastProt; + LogicValue? lastAuser; + LogicValue? lastWuser; + + intf.clk.posedge.listen((event) { + final currSels = intf.sel.map((e) => e.value).toList(); + + if (currSels.map((e) => e.isValid).reduce((a, b) => a | b)) { + // if any select is high + + // valid checks + if (!intf.write.value.isValid) { + logger.severe('Write must be valid during select.'); + } + if (!intf.addr.value.isValid) { + logger.severe('Addr must be valid during select.'); + } + if (!intf.wData.value.isValid) { + logger.severe('WData must be valid during select.'); + } + if (!intf.strb.value.isValid) { + logger.severe('Strobe must be valid during select.'); + } + if (!intf.enable.value.isValid) { + logger.severe('Enable must be valid during select.'); + } + + // stability checks + if (intf.enable.value.isValid && intf.enable.value.toBool()) { + if (lastWrite != null && lastWrite != intf.write.value) { + logger.severe('Write must be stable until ready.'); + } + if (lastAddr != null && lastAddr != intf.addr.value) { + logger.severe('Addr must be stable until ready.'); + } + if (lastSel != null) { + for (var i = 0; i < intf.numSelects; i++) { + if (intf.sel[i].value != lastSel![i]) { + logger.severe('Sel must be stable until ready.'); + } + } + } + if (lastWriteData != null && lastWriteData != intf.wData.value) { + logger.severe('Write data must be stable until ready.'); + } + if (lastStrb != null && lastStrb != intf.strb.value) { + logger.severe('Strobe must be stable until ready.'); + } + if (lastProt != null && lastProt != intf.prot.value) { + logger.severe('Prot must be stable until ready.'); + } + if (lastAuser != null && lastAuser != intf.aUser?.value) { + logger.severe('AUser must be stable until ready.'); + } + if (lastWuser != null && lastWuser != intf.wUser?.value) { + logger.severe('WUser must be stable until ready.'); + } + + // collect "last" items for next check + lastWrite = intf.write.value; + lastAddr = intf.addr.value; + lastSel = currSels; + lastWriteData = intf.wData.value; + lastStrb = intf.strb.value; + lastProt = intf.prot.value; + lastAuser = intf.aUser?.value; + lastWuser = intf.wUser?.value; + } + } + + if (intf.ready.value.toBool()) { + lastWrite = null; + lastAddr = null; + lastSel = null; + lastWriteData = null; + lastStrb = null; + lastProt = null; + lastAuser = null; + lastWuser = null; + } + + if (intf.write.value.isValid && + !intf.write.value.toBool() && + intf.enable.value.isValid && + intf.enable.value.toBool() && + intf.strb.value.isValid && + intf.strb.value.toInt() > 0) { + // strobe must not be "active" during read xfer (all low during read) + logger.severe('Strobe must not be active during read transfer.'); + } + + if (intf.enable.value.isValid && + intf.enable.value.toBool() && + intf.ready.value.isValid && + intf.ready.value.toBool()) { + if (accessLastCycle) { + logger.severe('Cannot have back-to-back accesses.'); + } + + if (intf.includeSlvErr && !intf.slvErr!.value.isValid) { + logger.severe('SlvErr must be valid during transfer.'); + } + + accessLastCycle = true; + } else { + accessLastCycle = false; + } + }); + } +} diff --git a/lib/src/models/axi4_bfm/axi4_main.dart b/lib/src/models/axi4_bfm/axi4_main.dart new file mode 100644 index 000000000..428c534cf --- /dev/null +++ b/lib/src/models/axi4_bfm/axi4_main.dart @@ -0,0 +1,51 @@ +// Copyright (C) 2023-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// apb_requester.dart +// An agent sending for APB requests. +// +// 2023 June 12 +// Author: Max Korbel + +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// An agent for sending requests on an [ApbInterface]. +/// +/// Driven read packets will update the returned data into the same packet. +class ApbRequesterAgent extends Agent { + /// The interface to drive. + final ApbInterface intf; + + /// The sequencer where requests should be sent. + late final Sequencer sequencer; + + /// The driver that sends the requests over the interface. + late final ApbRequesterDriver driver; + + /// The number of cycles before timing out if no transactions can be sent. + final int timeoutCycles; + + /// The number of cycles before an objection will be dropped when there are + /// no pending packets to send. + final int dropDelayCycles; + + /// Constructs a new [ApbRequesterAgent]. + ApbRequesterAgent({ + required this.intf, + required Component parent, + String name = 'apbRequester', + this.timeoutCycles = 500, + this.dropDelayCycles = 30, + }) : super(name, parent) { + sequencer = Sequencer('sequencer', this); + + driver = ApbRequesterDriver( + parent: this, + intf: intf, + sequencer: sequencer, + timeoutCycles: timeoutCycles, + dropDelayCycles: dropDelayCycles, + ); + } +} diff --git a/lib/src/models/axi4_bfm/axi4_main_driver.dart b/lib/src/models/axi4_bfm/axi4_main_driver.dart new file mode 100644 index 000000000..42a3eda18 --- /dev/null +++ b/lib/src/models/axi4_bfm/axi4_main_driver.dart @@ -0,0 +1,130 @@ +// Copyright (C) 2023-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// apb_requester_driver.dart +// A driver for APB requests. +// +// 2023 June 12 +// Author: Max Korbel + +import 'dart:async'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/src/interfaces/interfaces.dart'; +import 'package:rohd_hcl/src/models/apb_bfm/apb_packet.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A driver for the [ApbInterface] from the requester side. +/// +/// Driven read packets will update the returned data into the same packet. +class ApbRequesterDriver extends PendingClockedDriver { + /// The interface to drive. + final ApbInterface intf; + + /// Creates a new [ApbRequesterDriver]. + ApbRequesterDriver({ + required Component parent, + required this.intf, + required super.sequencer, + super.timeoutCycles = 500, + super.dropDelayCycles = 30, + String name = 'apbRequesterDriver', + }) : super( + name, + parent, + clk: intf.clk, + ); + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + Simulator.injectAction(() { + _deselectAll(); + intf.enable.put(0); + intf.write.put(0); + intf.addr.put(0); + intf.wData.put(0); + intf.strb.put(0); + }); + + // wait for reset to complete before driving anything + await intf.resetN.nextPosedge; + + while (!Simulator.simulationHasEnded) { + if (pendingSeqItems.isNotEmpty) { + await _drivePacket(pendingSeqItems.removeFirst()); + } else { + await intf.clk.nextPosedge; + Simulator.injectAction(() { + _deselectAll(); + intf.enable.put(0); + }); + } + } + } + + /// Drives a packet onto the interface. + Future _drivePacket(ApbPacket packet) async { + // first, SETUP + + await intf.clk.nextPosedge; + + // if we're not selecting this interface, then we need to select it + if (!intf.sel[packet.selectIndex].value.toBool()) { + _select(packet.selectIndex); + } + + Simulator.injectAction(() { + intf.enable.put(0); + intf.addr.put(packet.addr); + + if (packet is ApbWritePacket) { + intf.write.put(1); + intf.wData.put(packet.data); + intf.strb.put(packet.strobe); + } else if (packet is ApbReadPacket) { + intf.write.put(0); + intf.wData.put(0); + intf.strb.put(0); + } + }); + + await intf.clk.nextPosedge; + + // now, ACCESS + intf.enable.inject(1); + + // wait for ready from completer, if not already asserted + if (!intf.ready.value.toBool()) { + await intf.ready.nextPosedge; + } + + if (packet is ApbWritePacket) { + packet.complete( + slvErr: intf.slvErr?.value, + ); + } else if (packet is ApbReadPacket) { + packet.complete( + data: intf.rData.value, + slvErr: intf.slvErr?.value, + ); + } + + // now we're done, since enable and ready are both high, move on + } + + /// Selects [index] and deselects the rest. + void _select(int index) { + _deselectAll(); + intf.sel[index].put(1); + } + + /// Clears all selects. + void _deselectAll() { + // zero out all the selects, which should mask everything else + for (var i = 0; i < intf.numSelects; i++) { + intf.sel[i].put(0); + } + } +} diff --git a/lib/src/models/axi4_bfm/axi4_monitor.dart b/lib/src/models/axi4_bfm/axi4_monitor.dart new file mode 100644 index 000000000..a1b679fbd --- /dev/null +++ b/lib/src/models/axi4_bfm/axi4_monitor.dart @@ -0,0 +1,64 @@ +// Copyright (C) 2023-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// apb_monitor.dart +// A monitor that watches the APB interface. +// +// 2023 June 12 +// Author: Max Korbel + +import 'dart:async'; + +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A monitor for [ApbInterface]s. +class ApbMonitor extends Monitor { + /// The interface to monitor. + final ApbInterface intf; + + /// Creates a new [ApbMonitor] on [intf]. + ApbMonitor( + {required this.intf, + required Component parent, + String name = 'apbMonitor'}) + : super(name, parent); + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + await intf.resetN.nextPosedge; + + intf.clk.posedge.listen((event) { + for (var i = 0; i < intf.numSelects; i++) { + if (intf.sel[i].previousValue!.toBool() && + intf.enable.previousValue!.toBool() && + intf.ready.previousValue!.toBool()) { + if (intf.write.previousValue!.toBool()) { + add( + ApbWritePacket( + addr: intf.addr.previousValue!, + data: intf.wData.previousValue!, + strobe: intf.strb.previousValue, + selectIndex: i, + )..complete( + slvErr: intf.slvErr?.previousValue, + ), + ); + } else { + add( + ApbReadPacket( + addr: intf.addr.previousValue!, + selectIndex: i, + )..complete( + data: intf.rData.previousValue, + slvErr: intf.slvErr?.previousValue, + ), + ); + } + } + } + }); + } +} diff --git a/lib/src/models/axi4_bfm/axi4_packet.dart b/lib/src/models/axi4_bfm/axi4_packet.dart new file mode 100644 index 000000000..66f1ee9bb --- /dev/null +++ b/lib/src/models/axi4_bfm/axi4_packet.dart @@ -0,0 +1,199 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// axi4_packet.dart +// Packet for AXI4 interface. +// +// 2025 January +// 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'; + +/// A packet on an AXI4 interface. +abstract class Axi4Packet extends SequenceItem implements Trackable { + /// Address. + final LogicValue addr; + + /// Protection + final LogicValue prot; + + /// ID (optional). + final LogicValue? id; + + /// Length (optional). + final LogicValue? len; + + /// Size (optional). + final LogicValue? size; + + /// Burst (optional). + final LogicValue? burst; + + /// Lock (optional). + final LogicValue? lock; + + /// Cache (optional). + final LogicValue? cache; + + /// QoS (optional). + final LogicValue? qos; + + /// Region (optional). + final LogicValue? region; + + /// User (optional). + final LogicValue? user; + + /// Creates a new packet. + Axi4Packet( + {required this.addr, + required this.prot, + this.id, + this.len, + this.size, + this.burst, + this.lock, + this.cache, + this.qos, + this.region, + this.user}); + + /// A [Future] that completes once the the read has been completed. + Future get completed => _completer.future; + final Completer _completer = Completer(); + + /// Response returned by the request. + LogicValue? get returnedResponse => _returnedResponse; + + /// User data returned by the request. + LogicValue? get returnedUserData => _returnedUserData; + + LogicValue? _returnedResponse; + LogicValue? _returnedUserData; + + /// Called by a completer when a transfer is completed. + void complete({LogicValue? resp, LogicValue? user}) { + if (_returnedResponse != null) { + throw RohdHclException('Packet is already completed.'); + } + + _returnedResponse = resp; + _returnedUserData = user; + _completer.complete(); + } + + @override + String? trackerString(TrackerField field) { + switch (field.title) { + case Axi4Tracker.timeField: + return Simulator.time.toString(); + case Axi4Tracker.addrField: + return addr.toString(); + case Axi4Tracker.selectField: + return selectIndex.toString(); + } + + return null; + } +} + +/// A read packet on an [Axi4ReadInterface]. +class Axi4ReadPacket extends Axi4Packet { + /// Data returned by the read. + LogicValue? get returnedData => _returnedData; + + LogicValue? _returnedData; + + /// Creates a read packet. + Axi4ReadPacket({ + required super.addr, + required super.prot, + super.id, + super.len, + super.size, + super.burst, + super.lock, + super.cache, + super.qos, + super.region, + super.user, + }); + + /// Called by a completer when a transfer is completed. + @override + void complete({ + LogicValue? data, + LogicValue? resp, + LogicValue? user, + }) { + if (_returnedData != null) { + throw RohdHclException('Packet is already completed.'); + } + _returnedData = data; + super.complete(resp: resp, user: user); + } + + @override + String? trackerString(TrackerField field) { + switch (field.title) { + case ApbTracker.typeField: + return 'R'; + case ApbTracker.dataField: + return returnedData?.toString(); + case ApbTracker.slverrField: + return returnedSlvErr?.toString(includeWidth: false); + } + + return super.trackerString(field); + } +} + +/// A write packet on an [Axi4WriteInterface]. +class Axi4WritePacket extends Axi4Packet { + /// The data for this packet. + final LogicValue data; + + /// The strobe associated with this write. + final LogicValue? strobe; + + /// The user metadata associated with this write. + final LogicValue? wUser; + + /// Creates a write packet. + /// + /// If no [strobe] is provided, it will default to all high. + Axi4WritePacket( + {required super.addr, + required super.prot, + super.id, + super.len, + super.size, + super.burst, + super.lock, + super.cache, + super.qos, + super.region, + super.user, + LogicValue? strobe, + required this.data, + this.wUser}) + : strobe = strobe ?? LogicValue.filled(data.width ~/ 8, LogicValue.one); + + @override + String? trackerString(TrackerField field) { + switch (field.title) { + case ApbTracker.typeField: + return 'W'; + case ApbTracker.dataField: + return data.toString(); + case ApbTracker.strobeField: + return strobe.toString(includeWidth: false); + } + + return super.trackerString(field); + } +} diff --git a/lib/src/models/axi4_bfm/axi4_subordinate.dart b/lib/src/models/axi4_bfm/axi4_subordinate.dart new file mode 100644 index 000000000..a0058c33a --- /dev/null +++ b/lib/src/models/axi4_bfm/axi4_subordinate.dart @@ -0,0 +1,175 @@ +// Copyright (C) 2023-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// apb_completer.dart +// An agent for completing APB requests. +// +// 2023 June 12 +// Author: Max Korbel + +import 'dart:async'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A model for the completer side of an [ApbInterface]. +class ApbCompleterAgent extends Agent { + /// The interface to drive. + final ApbInterface intf; + + /// The index that this is listening to on the [intf]. + final int selectIndex; + + /// A place where the completer should save and retrieve data. + /// + /// The [ApbCompleterAgent] will reset [storage] whenever the `resetN` signal + /// is dropped. + final MemoryStorage storage; + + /// A function which delays the response for the given `request`. + /// + /// If none is provided, then the delay will always be `0`. + final int Function(ApbPacket request)? responseDelay; + + /// A function that determines whether a response for a request should contain + /// an error (`slvErr`). + /// + /// If none is provided, it will always respond with no error. + final bool Function(ApbPacket request)? respondWithError; + + /// If true, then returned data on an error will be `x`. + final bool invalidReadDataOnError; + + /// If true, then writes that respond with an error will not store into the + /// [storage]. + final bool dropWriteDataOnError; + + /// Creates a new model [ApbCompleterAgent]. + /// + /// If no [storage] is provided, it will use a default [SparseMemoryStorage]. + ApbCompleterAgent( + {required this.intf, + required Component parent, + MemoryStorage? storage, + this.selectIndex = 0, + this.responseDelay, + this.respondWithError, + this.invalidReadDataOnError = true, + this.dropWriteDataOnError = true, + String name = 'apbCompleter'}) + : storage = storage ?? + SparseMemoryStorage( + addrWidth: intf.addrWidth, + dataWidth: intf.dataWidth, + ), + super(name, parent); + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + intf.resetN.negedge.listen((event) { + storage.reset(); + }); + + _respond(ready: false); + + // wait for reset to complete + await intf.resetN.nextPosedge; + + while (!Simulator.simulationHasEnded) { + await _receive(); + } + } + + /// Calculates a strobed version of data. + static LogicValue _strobeData( + LogicValue originalData, LogicValue newData, LogicValue strobe) => + [ + for (var i = 0; i < strobe.width; i++) + (strobe[i].toBool() ? newData : originalData) + .getRange(i * 8, i * 8 + 8) + ].rswizzle(); + + /// Receives one packet (or returns if not selected). + Future _receive() async { + await intf.enable.nextPosedge; + + if (!intf.sel[selectIndex].value.toBool()) { + // we're not selected, wait for the next time + return; + } + + ApbPacket packet; + if (intf.write.value.toBool()) { + packet = ApbWritePacket( + addr: intf.addr.value, + data: intf.wData.value, + strobe: intf.strb.value, + ); + } else { + packet = ApbReadPacket(addr: intf.addr.value); + } + + if (responseDelay != null) { + final delayCycles = responseDelay!(packet); + if (delayCycles > 0) { + await intf.clk.waitCycles(delayCycles); + } + } + + if (packet is ApbWritePacket) { + final writeError = respondWithError != null && respondWithError!(packet); + + // store the data + if (!(writeError && dropWriteDataOnError)) { + storage.writeData( + packet.addr, + packet.strobe.and().toBool() // don't `readData` if all 1's + ? packet.data + : _strobeData( + storage.readData(packet.addr), + packet.data, + packet.strobe, + ), + ); + } + + _respond( + ready: true, + error: writeError, + ); + } else if (packet is ApbReadPacket) { + // capture the data + _respond( + ready: true, + data: storage.readData(packet.addr), + error: respondWithError != null && respondWithError!(packet), + ); + } + + // drop the ready when enable drops + await intf.enable.nextNegedge; + _respond(ready: false); + } + + /// Sets up response signals for the completer (including using inject). + void _respond({required bool ready, bool? error, LogicValue? data}) { + Simulator.injectAction(() { + intf.ready.put(ready); + + if (error == null) { + intf.slvErr?.put(LogicValue.x); + } else { + intf.slvErr?.put(error); + } + + if (data == null || ((error ?? false) && invalidReadDataOnError)) { + intf.rData.put(LogicValue.x); + } else { + intf.rData.put(data); + } + }); + } +} diff --git a/lib/src/models/axi4_bfm/axi4_tracker.dart b/lib/src/models/axi4_bfm/axi4_tracker.dart new file mode 100644 index 000000000..f776c1a4a --- /dev/null +++ b/lib/src/models/axi4_bfm/axi4_tracker.dart @@ -0,0 +1,89 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// axi4_tracker.dart +// Monitors that watch the AXI4 interfaces. +// +// 2025 January +// Author: Josh Kimmel + +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A tracker for the [Axi4ReadInterface] or [Axi4WriteInterface]. +class Axi4Tracker extends Tracker { + /// Tracker field for simulation time. + static const timeField = 'time'; + + /// Tracker field for type (R/W). + static const typeField = 'type'; + + /// Tracker field for ID. + static const idField = 'ID'; + + /// Tracker field for ADDR. + static const addrField = 'ADDR'; + + /// Tracker field for LEN. + static const lenField = 'LEN'; + + /// Tracker field for SIZE. + static const sizeField = 'SIZE'; + + /// Tracker field for BURST. + static const burstField = 'BURST'; + + /// Tracker field for LOCK. + static const lockField = 'LOCK'; + + /// Tracker field for CACHE. + static const cacheField = 'CACHE'; + + /// Tracker field for QOS. + static const qosField = 'QOS'; + + /// Tracker field for REGION. + static const regionField = 'REGION'; + + /// Tracker field for USER. + static const userField = 'USER'; + + /// Tracker field for RESP. + static const respField = 'RESP'; + + /// Tracker field for RUSER. + static const rUserField = 'RUSER'; + + /// Tracker field for DATA. + static const dataField = 'DATA'; + + /// Tracker field for STRB. + static const strbField = 'STRB'; + + /// Creates a new tracker for [Axi4ReadInterface]. + /// + /// If the [selectColumnWidth] is set to 0, the field will be omitted. + Axi4Tracker({ + required Axi4ReadInterface intf, + String name = 'Axi4ReadTracker', + super.dumpJson, + super.dumpTable, + super.outputFolder, + int timeColumnWidth = 12, + // TODO: Add more fields + + int selectColumnWidth = 4, + int addrColumnWidth = 12, + int dataColumnWidth = 12, + }) : super(name, [ + TrackerField(timeField, columnWidth: timeColumnWidth), + if (selectColumnWidth > 0) + TrackerField(selectField, columnWidth: selectColumnWidth), + const TrackerField(typeField, columnWidth: 1), + TrackerField(addrField, columnWidth: addrColumnWidth), + TrackerField(dataField, columnWidth: dataColumnWidth), + const TrackerField(strobeField, columnWidth: 4), + if (intf.includeSlvErr) + const TrackerField(slverrField, columnWidth: 1), + ]); +} From e6db586766094c902cd557a93a1ca5f9827bc8fd Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Fri, 17 Jan 2025 09:38:57 -0800 Subject: [PATCH 03/16] WIP: more AXI progress. --- lib/src/interfaces/axi4.dart | 91 +++++ lib/src/models/axi4_bfm/axi4_packet.dart | 97 ++++-- lib/src/models/axi4_bfm/axi4_subordinate.dart | 315 ++++++++++++------ lib/src/models/axi4_bfm/axi4_tracker.dart | 61 +++- 4 files changed, 427 insertions(+), 137 deletions(-) diff --git a/lib/src/interfaces/axi4.dart b/lib/src/interfaces/axi4.dart index 61e1f4417..741630a7e 100644 --- a/lib/src/interfaces/axi4.dart +++ b/lib/src/interfaces/axi4.dart @@ -549,3 +549,94 @@ class Axi4WriteInterface extends Interface { } } } + +/// Helper to enumerate the encodings of the xRESP signal. +enum Axi4BurstField { + /// Address remains constants. + fixed(0x0), + + /// Address increments by the transfer size. + incr(0x1), + + /// Similar to incr, but wraps around to a lower boundary when a boundary is reached. + wrap(0x2); + + /// Underlying value. + final int value; + + const Axi4BurstField(this.value); +} + +/// Helper to enumerate the one hot encodings of the AxPROT signal. +enum Axi4ProtField { + /// Transaction to be performed in privileged mode (1) or non-privileged (0). + privileged(0x1), + + /// Transaction is accessing secure memory (0) or unsecure memory (1). + secure(0x2), + + /// Transaction is performing an instruction fetch (1) or data fetch (0). + instruction(0x4); + + /// Underlying value. + final int value; + + const Axi4ProtField(this.value); +} + +/// Helper to enumerate the encodings of the AxLOCK signal. +enum Axi4LockField { + /// Normal transaction. + normal(0x0), + + /// Part of a read-modify-write. + exclusive(0x2), + + /// Resource can't be accessed until transaction completes. + locked(0x3); + + /// Underlying value. + final int value; + + const Axi4LockField(this.value); +} + +/// Helper to enumerate the one hot encodings of the AxCACHE signal. +enum Axi4CacheField { + /// Transaction can be buffered. + bufferable(0x1), + + /// Transaction can be cached. + cacheable(0x2), + + /// Cache space can be allocated during a read. + readAllocate(0x4), + + /// Cache space can be allocated during a write + writeAllocate(0x8); + + /// Underlying one hot encoded value. + final int value; + + const Axi4CacheField(this.value); +} + +/// Helper to enumerate the encodings of the xRESP signal. +enum Axi4RespField { + /// Expected result. + okay(0x0), + + /// Okay, exclusive access granted. + exOkay(0x1), + + /// Subordinate recoverable error. + slvErr(0x2), + + /// Subordinate fatal error. + decErr(0x3); + + /// Underlying value. + final int value; + + const Axi4RespField(this.value); +} diff --git a/lib/src/models/axi4_bfm/axi4_packet.dart b/lib/src/models/axi4_bfm/axi4_packet.dart index 66f1ee9bb..8f59a411c 100644 --- a/lib/src/models/axi4_bfm/axi4_packet.dart +++ b/lib/src/models/axi4_bfm/axi4_packet.dart @@ -11,10 +11,11 @@ import 'dart:async'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; import 'package:rohd_vf/rohd_vf.dart'; -/// A packet on an AXI4 interface. -abstract class Axi4Packet extends SequenceItem implements Trackable { +/// A request packet on an AXI4 interface. +abstract class Axi4RequestPacket extends SequenceItem implements Trackable { /// Address. final LogicValue addr; @@ -49,7 +50,7 @@ abstract class Axi4Packet extends SequenceItem implements Trackable { final LogicValue? user; /// Creates a new packet. - Axi4Packet( + Axi4RequestPacket( {required this.addr, required this.prot, this.id, @@ -77,7 +78,7 @@ abstract class Axi4Packet extends SequenceItem implements Trackable { /// Called by a completer when a transfer is completed. void complete({LogicValue? resp, LogicValue? user}) { - if (_returnedResponse != null) { + if (_returnedResponse != null || _returnedUserData != null) { throw RohdHclException('Packet is already completed.'); } @@ -91,10 +92,32 @@ abstract class Axi4Packet extends SequenceItem implements Trackable { switch (field.title) { case Axi4Tracker.timeField: return Simulator.time.toString(); + case Axi4Tracker.idField: + return Simulator.time.toString(); case Axi4Tracker.addrField: return addr.toString(); - case Axi4Tracker.selectField: - return selectIndex.toString(); + case Axi4Tracker.lenField: + return len.toString(); + case Axi4Tracker.sizeField: + return size.toString(); + case Axi4Tracker.burstField: + return burst.toString(); + case Axi4Tracker.lockField: + return lock.toString(); + case Axi4Tracker.cacheField: + return cache.toString(); + case Axi4Tracker.protField: + return prot.toString(); + case Axi4Tracker.qosField: + return qos.toString(); + case Axi4Tracker.regionField: + return region.toString(); + case Axi4Tracker.userField: + return user.toString(); + case Axi4Tracker.respField: + return returnedResponse.toString(); + case Axi4Tracker.rUserField: + return returnedUserData.toString(); } return null; @@ -102,14 +125,14 @@ abstract class Axi4Packet extends SequenceItem implements Trackable { } /// A read packet on an [Axi4ReadInterface]. -class Axi4ReadPacket extends Axi4Packet { +class Axi4ReadRequestPacket extends Axi4RequestPacket { /// Data returned by the read. - LogicValue? get returnedData => _returnedData; + List get returnedData => _returnedData; - LogicValue? _returnedData; + List _returnedData = []; /// Creates a read packet. - Axi4ReadPacket({ + Axi4ReadRequestPacket({ required super.addr, required super.prot, super.id, @@ -126,11 +149,11 @@ class Axi4ReadPacket extends Axi4Packet { /// Called by a completer when a transfer is completed. @override void complete({ - LogicValue? data, + List data = const [], LogicValue? resp, LogicValue? user, }) { - if (_returnedData != null) { + if (_returnedData.isNotEmpty) { throw RohdHclException('Packet is already completed.'); } _returnedData = data; @@ -140,12 +163,14 @@ class Axi4ReadPacket extends Axi4Packet { @override String? trackerString(TrackerField field) { switch (field.title) { - case ApbTracker.typeField: + case Axi4Tracker.typeField: return 'R'; - case ApbTracker.dataField: - return returnedData?.toString(); - case ApbTracker.slverrField: - return returnedSlvErr?.toString(includeWidth: false); + case Axi4Tracker.dataField: + return returnedData + .map((d) => d.toRadixString(radix: 16)) + .toList() + .reversed + .join(); } return super.trackerString(field); @@ -153,22 +178,23 @@ class Axi4ReadPacket extends Axi4Packet { } /// A write packet on an [Axi4WriteInterface]. -class Axi4WritePacket extends Axi4Packet { +class Axi4WriteRequestPacket extends Axi4RequestPacket { /// The data for this packet. - final LogicValue data; + final List data; /// The strobe associated with this write. - final LogicValue? strobe; + final List strobe; /// The user metadata associated with this write. - final LogicValue? wUser; + final List wUser; /// Creates a write packet. /// /// If no [strobe] is provided, it will default to all high. - Axi4WritePacket( + Axi4WriteRequestPacket( {required super.addr, required super.prot, + required this.data, super.id, super.len, super.size, @@ -178,20 +204,29 @@ class Axi4WritePacket extends Axi4Packet { super.qos, super.region, super.user, - LogicValue? strobe, - required this.data, - this.wUser}) - : strobe = strobe ?? LogicValue.filled(data.width ~/ 8, LogicValue.one); + this.strobe = const [], + this.wUser = const []}); @override String? trackerString(TrackerField field) { switch (field.title) { - case ApbTracker.typeField: + case Axi4Tracker.typeField: return 'W'; - case ApbTracker.dataField: - return data.toString(); - case ApbTracker.strobeField: - return strobe.toString(includeWidth: false); + case Axi4Tracker.dataField: + return data + .map((d) => d.toRadixString(radix: 16)) + .toList() + .reversed + .join(); + case Axi4Tracker.strbField: + return strobe + .where( + (element) => element != null, + ) + .map((d) => d!.toRadixString(radix: 16)) + .toList() + .reversed + .join(); } return super.trackerString(field); diff --git a/lib/src/models/axi4_bfm/axi4_subordinate.dart b/lib/src/models/axi4_bfm/axi4_subordinate.dart index a0058c33a..82b92fe59 100644 --- a/lib/src/models/axi4_bfm/axi4_subordinate.dart +++ b/lib/src/models/axi4_bfm/axi4_subordinate.dart @@ -1,42 +1,53 @@ -// Copyright (C) 2023-2024 Intel Corporation +// Copyright (C) 2024-2025 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // -// apb_completer.dart -// An agent for completing APB requests. +// axi4_subordinate.dart +// A subordinate AXI4 agent. // -// 2023 June 12 -// Author: Max Korbel +// 2025 January +// Author: Josh Kimmel import 'dart:async'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; import 'package:rohd_vf/rohd_vf.dart'; -/// A model for the completer side of an [ApbInterface]. -class ApbCompleterAgent extends Agent { - /// The interface to drive. - final ApbInterface intf; +// TODO: FIGURE OUT IF DATA TRANSFERS CAN OCCUR OVER ARBITARY CYCLES - /// The index that this is listening to on the [intf]. - final int selectIndex; +/// A model for the subordinate side of an [Axi4ReadInterface] and [Axi4WriteInterface]. +class Axi4SubordinateAgent extends Agent { + /// The system interface. + final Axi4SystemInterface sIntf; - /// A place where the completer should save and retrieve data. + /// The read interface to drive. + final Axi4ReadInterface rIntf; + + /// The write interface to drive. + final Axi4WriteInterface wIntf; + + /// A place where the subordinate should save and retrieve data. /// - /// The [ApbCompleterAgent] will reset [storage] whenever the `resetN` signal + /// The [Axi4SubordinateAgent] will reset [storage] whenever the `resetN` signal /// is dropped. final MemoryStorage storage; /// A function which delays the response for the given `request`. /// /// If none is provided, then the delay will always be `0`. - final int Function(ApbPacket request)? responseDelay; + final int Function(Axi4ReadRequestPacket request)? readResponseDelay; + + /// A function which delays the response for the given `request`. + /// + /// If none is provided, then the delay will always be `0`. + final int Function(Axi4WriteRequestPacket request)? writeResponseDelay; /// A function that determines whether a response for a request should contain /// an error (`slvErr`). /// /// If none is provided, it will always respond with no error. - final bool Function(ApbPacket request)? respondWithError; + final bool Function(Axi4RequestPacket request)? respondWithError; /// If true, then returned data on an error will be `x`. final bool invalidReadDataOnError; @@ -45,23 +56,39 @@ class ApbCompleterAgent extends Agent { /// [storage]. final bool dropWriteDataOnError; - /// Creates a new model [ApbCompleterAgent]. + // to handle response read responses + final List _dataReadResponseMetadataQueue = []; + final List> _dataReadResponseDataQueue = []; + int _dataReadResponseIndex = 0; + + // to handle writes + final List _writeMetadataQueue = []; + final List> _writeDataQueue = []; + bool _writeReadyToOccur = false; + + /// Creates a new model [Axi4SubordinateAgent]. /// /// If no [storage] is provided, it will use a default [SparseMemoryStorage]. - ApbCompleterAgent( - {required this.intf, + Axi4SubordinateAgent( + {required this.sIntf, + required this.rIntf, + required this.wIntf, required Component parent, MemoryStorage? storage, - this.selectIndex = 0, - this.responseDelay, + this.readResponseDelay, + this.writeResponseDelay, this.respondWithError, this.invalidReadDataOnError = true, this.dropWriteDataOnError = true, - String name = 'apbCompleter'}) - : storage = storage ?? + String name = 'axi4SubordinateAgent'}) + : assert(rIntf.addrWidth == wIntf.addrWidth, + 'Read and write interfaces should have same address width.'), + assert(rIntf.dataWidth == wIntf.dataWidth, + 'Read and write interfaces should have same data width.'), + storage = storage ?? SparseMemoryStorage( - addrWidth: intf.addrWidth, - dataWidth: intf.dataWidth, + addrWidth: rIntf.addrWidth, + dataWidth: rIntf.dataWidth, ), super(name, parent); @@ -69,17 +96,23 @@ class ApbCompleterAgent extends Agent { Future run(Phase phase) async { unawaited(super.run(phase)); - intf.resetN.negedge.listen((event) { + sIntf.resetN.negedge.listen((event) { storage.reset(); + _dataReadResponseDataQueue.clear(); + _dataReadResponseMetadataQueue.clear(); + _dataReadResponseIndex = 0; }); - _respond(ready: false); - // wait for reset to complete - await intf.resetN.nextPosedge; + await sIntf.resetN.nextPosedge; while (!Simulator.simulationHasEnded) { - await _receive(); + await sIntf.clk.nextNegedge; + _driveReadys(); + _respondRead(); + _respondWrite(); + _receiveRead(); + _receiveWrite(); } } @@ -92,84 +125,180 @@ class ApbCompleterAgent extends Agent { .getRange(i * 8, i * 8 + 8) ].rswizzle(); + // assesses the input ready signals and drives them appropriately + void _driveReadys() { + // for now, assume we can always handle a new request + rIntf.arReady.put(true); + wIntf.awReady.put(true); + wIntf.wReady.put(true); + } + /// Receives one packet (or returns if not selected). - Future _receive() async { - await intf.enable.nextPosedge; + void _receiveRead() { + // work to if main is indicating a valid read that we are ready to handle + if (rIntf.arValid.value.toBool() && rIntf.arReady.value.toBool()) { + final packet = Axi4ReadRequestPacket( + addr: rIntf.arAddr.value, + prot: rIntf.arProt.value, + id: rIntf.arId?.value, + len: rIntf.arLen?.value, + size: rIntf.arSize?.value, + burst: rIntf.arBurst?.value, + lock: rIntf.arLock?.value, + cache: rIntf.arCache?.value, + qos: rIntf.arQos?.value, + region: rIntf.arRegion?.value, + user: rIntf.arUser?.value); - if (!intf.sel[selectIndex].value.toBool()) { - // we're not selected, wait for the next time - return; - } + // NOTE: generic model does not handle the following read request fields: + // cache + // qos + // region + // user + // These can be handled in a derived class of this model if need be. + // Because for the most part they require implementation specific handling. - ApbPacket packet; - if (intf.write.value.toBool()) { - packet = ApbWritePacket( - addr: intf.addr.value, - data: intf.wData.value, - strobe: intf.strb.value, - ); - } else { - packet = ApbReadPacket(addr: intf.addr.value); - } + // NOTE: generic model doesn't honor the prot field in read requests. + // It will be added as a feature request in the future. - if (responseDelay != null) { - final delayCycles = responseDelay!(packet); - if (delayCycles > 0) { - await intf.clk.waitCycles(delayCycles); - } - } + // NOTE: generic model doesn't honor the lock field in read requests. + // It will be added as a feature request in the future. - if (packet is ApbWritePacket) { - final writeError = respondWithError != null && respondWithError!(packet); - - // store the data - if (!(writeError && dropWriteDataOnError)) { - storage.writeData( - packet.addr, - packet.strobe.and().toBool() // don't `readData` if all 1's - ? packet.data - : _strobeData( - storage.readData(packet.addr), - packet.data, - packet.strobe, - ), - ); + // query storage to retrieve the data + final data = []; + var addrToRead = rIntf.arAddr.value; + final endCount = rIntf.arLen?.value.toInt() ?? 1; + final dSize = (rIntf.arSize?.value.toInt() ?? 0) * 8; + final increment = rIntf.arBurst?.value.toInt() ?? 1; + for (var i = 0; i < endCount; i++) { + var currData = storage.readData(addrToRead); + if (dSize > 0) { + if (currData.width < dSize) { + currData = currData.zeroExtend(dSize); + } else if (currData.width > dSize) { + currData = currData.getRange(0, dSize); + } + } + data.add(currData); + addrToRead = addrToRead + increment; } - _respond( - ready: true, - error: writeError, - ); - } else if (packet is ApbReadPacket) { - // capture the data - _respond( - ready: true, - data: storage.readData(packet.addr), - error: respondWithError != null && respondWithError!(packet), - ); + _dataReadResponseMetadataQueue.add(packet); + _dataReadResponseDataQueue.add(data); } - - // drop the ready when enable drops - await intf.enable.nextNegedge; - _respond(ready: false); } - /// Sets up response signals for the completer (including using inject). - void _respond({required bool ready, bool? error, LogicValue? data}) { - Simulator.injectAction(() { - intf.ready.put(ready); + // respond to a read request + void _respondRead() { + // only respond if there is something to respond to + // and the main side is indicating that it is ready to receive + if (_dataReadResponseMetadataQueue.isNotEmpty && + _dataReadResponseDataQueue.isNotEmpty && + rIntf.rReady.value.toBool()) { + final packet = _dataReadResponseMetadataQueue[0]; + final currData = _dataReadResponseDataQueue[0][_dataReadResponseIndex]; + final error = respondWithError != null && respondWithError!(packet); + final last = + _dataReadResponseIndex == _dataReadResponseDataQueue[0].length - 1; - if (error == null) { - intf.slvErr?.put(LogicValue.x); + // TODO: how to deal with delays?? + // if (readResponseDelay != null) { + // final delayCycles = readResponseDelay!(packet); + // if (delayCycles > 0) { + // await sIntf.clk.waitCycles(delayCycles); + // } + // } + + // for now, only support sending slvErr and okay as responses + rIntf.rValid.put(true); + rIntf.rId?.put(packet.id); + rIntf.rData.put(currData); + rIntf.rResp?.put(error + ? LogicValue.ofInt(Axi4RespField.slvErr.value, rIntf.rResp!.width) + : LogicValue.ofInt(Axi4RespField.okay.value, rIntf.rResp!.width)); + rIntf.rUser?.put(0); // don't support user field for now + rIntf.rLast?.put(last); + + if (last) { + // pop this read response off the queue + _dataReadResponseIndex = 0; + _dataReadResponseMetadataQueue.removeAt(0); + _dataReadResponseDataQueue.removeAt(0); } else { - intf.slvErr?.put(error); + // move to the next chunk of data + _dataReadResponseIndex++; } + } + } - if (data == null || ((error ?? false) && invalidReadDataOnError)) { - intf.rData.put(LogicValue.x); - } else { - intf.rData.put(data); + // handle an incoming write request + void _receiveWrite() { + // work to if main is indicating a valid read that we are ready to handle + if (wIntf.awValid.value.toBool() && wIntf.awReady.value.toBool()) { + var packet = Axi4WriteRequestPacket( + addr: wIntf.awAddr.value, + prot: wIntf.awProt.value, + id: wIntf.awId?.value, + len: wIntf.awLen?.value, + size: wIntf.awSize?.value, + burst: wIntf.awBurst?.value, + lock: wIntf.awLock?.value, + cache: wIntf.awCache?.value, + qos: wIntf.awQos?.value, + region: wIntf.awRegion?.value, + user: wIntf.awUser?.value, + data: [], + strobe: []); + + // might need to capture the first data and strobe simultaneously + // NOTE: we are dropping wUser on the floor for now... + final idMatch = wIntf.awId?.value == wIntf.wId?.value; + if (wIntf.wValid.value.toBool() && wIntf.wReady.value.toBool()) { + packet.data.add(wIntf.wData.value); + packet.strobe.add(wIntf.wStrb.value); + if (wIntf.wLast.value.toBool()) { + _writeReadyToOccur = true; + } } - }); + + // queue up the packet for further processing + _writeMetadataQueue.add(packet); + + // NOTE: generic model does not handle the following read request fields: + // cache + // qos + // region + // user + // These can be handled in a derived class of this model if need be. + // Because for the most part they require implementation specific handling. + + // NOTE: generic model doesn't honor the prot field in read requests. + // It will be added as a feature request in the future. + + // NOTE: generic model doesn't honor the lock field in read requests. + // It will be added as a feature request in the future. + + // write data to the storage + final data = []; + var addrToRead = rIntf.arAddr.value; + final endCount = rIntf.arLen?.value.toInt() ?? 1; + final dSize = (rIntf.arSize?.value.toInt() ?? 0) * 8; + final increment = rIntf.arBurst?.value.toInt() ?? 1; + for (var i = 0; i < endCount; i++) { + var currData = storage.readData(addrToRead); + if (dSize > 0) { + if (currData.width < dSize) { + currData = currData.zeroExtend(dSize); + } else if (currData.width > dSize) { + currData = currData.getRange(0, dSize); + } + } + data.add(currData); + addrToRead = addrToRead + increment; + } + + _dataReadResponseMetadataQueue.add(packet); + _dataReadResponseDataQueue.add(data); + } } } diff --git a/lib/src/models/axi4_bfm/axi4_tracker.dart b/lib/src/models/axi4_bfm/axi4_tracker.dart index f776c1a4a..ff9cd6c79 100644 --- a/lib/src/models/axi4_bfm/axi4_tracker.dart +++ b/lib/src/models/axi4_bfm/axi4_tracker.dart @@ -8,10 +8,11 @@ // Author: Josh Kimmel import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; import 'package:rohd_vf/rohd_vf.dart'; /// A tracker for the [Axi4ReadInterface] or [Axi4WriteInterface]. -class Axi4Tracker extends Tracker { +class Axi4Tracker extends Tracker { /// Tracker field for simulation time. static const timeField = 'time'; @@ -39,6 +40,9 @@ class Axi4Tracker extends Tracker { /// Tracker field for CACHE. static const cacheField = 'CACHE'; + /// Tracker field for PROT. + static const protField = 'PROT'; + /// Tracker field for QOS. static const qosField = 'QOS'; @@ -60,30 +64,61 @@ class Axi4Tracker extends Tracker { /// Tracker field for STRB. static const strbField = 'STRB'; - /// Creates a new tracker for [Axi4ReadInterface]. + /// Creates a new tracker for [Axi4ReadInterface] and [Axi4WriteInterface]. /// /// If the [selectColumnWidth] is set to 0, the field will be omitted. Axi4Tracker({ - required Axi4ReadInterface intf, - String name = 'Axi4ReadTracker', + required Axi4ReadInterface rIntf, + required Axi4ReadInterface wIntf, + String name = 'Axi4Tracker', super.dumpJson, super.dumpTable, super.outputFolder, int timeColumnWidth = 12, - // TODO: Add more fields - - int selectColumnWidth = 4, + int idColumnWidth = 0, int addrColumnWidth = 12, - int dataColumnWidth = 12, + int lenColumnWidth = 12, + int sizeColumnWidth = 0, + int burstColumnWidth = 0, + int lockColumnWidth = 0, + int cacheColumnWidth = 0, + int protColumnWidth = 4, + int qosColumnWidth = 0, + int regionColumnWidth = 0, + int userColumnWidth = 0, + int respColumnWidth = 12, + int ruserColumnWidth = 0, + int dataColumnWidth = 64, + int strbColumnWidth = 0, }) : super(name, [ TrackerField(timeField, columnWidth: timeColumnWidth), - if (selectColumnWidth > 0) - TrackerField(selectField, columnWidth: selectColumnWidth), const TrackerField(typeField, columnWidth: 1), + if (idColumnWidth > 0) + TrackerField(idField, columnWidth: idColumnWidth), TrackerField(addrField, columnWidth: addrColumnWidth), + if (lenColumnWidth > 0) + TrackerField(lenField, columnWidth: lenColumnWidth), + if (sizeColumnWidth > 0) + TrackerField(sizeField, columnWidth: sizeColumnWidth), + if (burstColumnWidth > 0) + TrackerField(burstField, columnWidth: burstColumnWidth), + if (lockColumnWidth > 0) + TrackerField(lockField, columnWidth: lockColumnWidth), + if (cacheColumnWidth > 0) + TrackerField(cacheField, columnWidth: cacheColumnWidth), + TrackerField(protField, columnWidth: protColumnWidth), + if (qosColumnWidth > 0) + TrackerField(qosField, columnWidth: qosColumnWidth), + if (regionColumnWidth > 0) + TrackerField(regionField, columnWidth: regionColumnWidth), + if (userColumnWidth > 0) + TrackerField(userField, columnWidth: userColumnWidth), + if (respColumnWidth > 0) + TrackerField(respField, columnWidth: respColumnWidth), + if (ruserColumnWidth > 0) + TrackerField(rUserField, columnWidth: ruserColumnWidth), TrackerField(dataField, columnWidth: dataColumnWidth), - const TrackerField(strobeField, columnWidth: 4), - if (intf.includeSlvErr) - const TrackerField(slverrField, columnWidth: 1), + if (strbColumnWidth > 0) + TrackerField(strbField, columnWidth: strbColumnWidth), ]); } From 2eca845756732e8201c2e724d74cb38eaaaa93fb Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Thu, 23 Jan 2025 10:08:21 -0800 Subject: [PATCH 04/16] More progress on AXI4 support. --- .../axi4_bfm/axi4_compliance_checker.dart | 206 ++++++-------- lib/src/models/axi4_bfm/axi4_main.dart | 47 ++-- lib/src/models/axi4_bfm/axi4_main_driver.dart | 215 +++++++++----- lib/src/models/axi4_bfm/axi4_monitor.dart | 163 ++++++++--- lib/src/models/axi4_bfm/axi4_packet.dart | 33 +-- lib/src/models/axi4_bfm/axi4_subordinate.dart | 129 ++++++--- lib/src/models/axi4_bfm/axi4_tracker.dart | 2 +- test/axi4_bfm_test.dart | 265 ++++++++++++++++++ 8 files changed, 751 insertions(+), 309 deletions(-) create mode 100644 test/axi4_bfm_test.dart diff --git a/lib/src/models/axi4_bfm/axi4_compliance_checker.dart b/lib/src/models/axi4_bfm/axi4_compliance_checker.dart index a9aa311d1..db5ca40ca 100644 --- a/lib/src/models/axi4_bfm/axi4_compliance_checker.dart +++ b/lib/src/models/axi4_bfm/axi4_compliance_checker.dart @@ -1,30 +1,37 @@ -// Copyright (C) 2023-2024 Intel Corporation +// Copyright (C) 2024-2025 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // -// apb_completer.dart -// An agent for completing APB requests. +// axi4_compliance_checker.dart +// Compliance checking for AXI4. // -// 2023 June 14 -// Author: Max Korbel +// 2025 January +// 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'; -/// A checker for some of the rules defined in the APB interface specification. +/// A checker for some of the rules defined in the AXI4 interface specification. /// /// This does not necessarily cover all rules defined in the spec. -class ApbComplianceChecker extends Component { - /// The interface being checked. - final ApbInterface intf; +class Axi4ComplianceChecker extends Component { + /// AXI4 System Interface. + final Axi4SystemInterface sIntf; - /// Creates a new compliance checker for [intf]. - ApbComplianceChecker( - this.intf, { + /// AXI4 Read Interface. + final Axi4ReadInterface rIntf; + + /// AXI4 Write Interface. + final Axi4WriteInterface wIntf; + + /// Creates a new compliance checker for AXI4. + Axi4ComplianceChecker( + this.sIntf, + this.rIntf, + this.wIntf, { required Component parent, - String name = 'apbComplianceChecker', + String name = 'axi4ComplianceChecker', }) : super(name, parent); @override @@ -32,122 +39,81 @@ class ApbComplianceChecker extends Component { unawaited(super.run(phase)); // wait for reset to complete - await intf.resetN.nextPosedge; - - var accessLastCycle = false; - - LogicValue? lastWrite; - LogicValue? lastAddr; - List? lastSel; - LogicValue? lastWriteData; - LogicValue? lastStrb; - LogicValue? lastProt; - LogicValue? lastAuser; - LogicValue? lastWuser; - - intf.clk.posedge.listen((event) { - final currSels = intf.sel.map((e) => e.value).toList(); - - if (currSels.map((e) => e.isValid).reduce((a, b) => a | b)) { - // if any select is high + await sIntf.resetN.nextPosedge; + + // checks to run + // READ REQUESTS + // number of flits returned matches ARLEN if no error + // if RLAST is present, asserted on the final flit only + // if RID is present, every read response should match a pending request ARID + // WRITE REQUESTS + // number of flits sent matches AWLEN + // WLAST is asserted on the final flit only + // if BID is present, every write response should match a pending request AWID + + final rLastPresent = rIntf.rLast != null; + + final readReqMap = >{}; + final writeReqMap = >{}; + var lastWriteReqId = -1; + + sIntf.clk.posedge.listen((event) { + // capture read requests for counting + if (rIntf.arValid.previousValue!.toBool()) { + final id = rIntf.arId?.previousValue?.toInt() ?? 0; + final len = rIntf.arLen?.previousValue?.toInt() ?? 1; + readReqMap[id] = [len, 0]; + } - // valid checks - if (!intf.write.value.isValid) { - logger.severe('Write must be valid during select.'); - } - if (!intf.addr.value.isValid) { - logger.severe('Addr must be valid during select.'); - } - if (!intf.wData.value.isValid) { - logger.severe('WData must be valid during select.'); - } - if (!intf.strb.value.isValid) { - logger.severe('Strobe must be valid during select.'); - } - if (!intf.enable.value.isValid) { - logger.severe('Enable must be valid during select.'); + // track write data flits + if (wIntf.wValid.previousValue!.toBool()) { + final id = rIntf.rId?.previousValue?.toInt() ?? 0; + if (!readReqMap.containsKey(id)) { + logger.severe( + 'Cannot match a read response to any pending read request. ID captured by the response was $id.'); } - // stability checks - if (intf.enable.value.isValid && intf.enable.value.toBool()) { - if (lastWrite != null && lastWrite != intf.write.value) { - logger.severe('Write must be stable until ready.'); - } - if (lastAddr != null && lastAddr != intf.addr.value) { - logger.severe('Addr must be stable until ready.'); - } - if (lastSel != null) { - for (var i = 0; i < intf.numSelects; i++) { - if (intf.sel[i].value != lastSel![i]) { - logger.severe('Sel must be stable until ready.'); - } - } - } - if (lastWriteData != null && lastWriteData != intf.wData.value) { - logger.severe('Write data must be stable until ready.'); - } - if (lastStrb != null && lastStrb != intf.strb.value) { - logger.severe('Strobe must be stable until ready.'); - } - if (lastProt != null && lastProt != intf.prot.value) { - logger.severe('Prot must be stable until ready.'); - } - if (lastAuser != null && lastAuser != intf.aUser?.value) { - logger.severe('AUser must be stable until ready.'); - } - if (lastWuser != null && lastWuser != intf.wUser?.value) { - logger.severe('WUser must be stable until ready.'); - } - - // collect "last" items for next check - lastWrite = intf.write.value; - lastAddr = intf.addr.value; - lastSel = currSels; - lastWriteData = intf.wData.value; - lastStrb = intf.strb.value; - lastProt = intf.prot.value; - lastAuser = intf.aUser?.value; - lastWuser = intf.wUser?.value; + readReqMap[id]![1] = readReqMap[id]![1] + 1; + final len = readReqMap[id]![0]; + final currCount = readReqMap[id]![1]; + if (currCount > len) { + logger.severe( + 'Received more read response data flits than indicated by the request with ID $id ARLEN. Expected $len but got $currCount'); + } else if (currCount == len && + rLastPresent && + !rIntf.rLast!.previousValue!.toBool()) { + logger.severe( + 'Received the final flit in the read response data per the request with ID $id ARLEN but RLAST is not asserted.'); } } - if (intf.ready.value.toBool()) { - lastWrite = null; - lastAddr = null; - lastSel = null; - lastWriteData = null; - lastStrb = null; - lastProt = null; - lastAuser = null; - lastWuser = null; + // track write requests + if (wIntf.awValid.previousValue!.toBool()) { + final id = wIntf.awId?.previousValue?.toInt() ?? 0; + final len = wIntf.awLen?.previousValue?.toInt() ?? 1; + writeReqMap[id] = [len, 0]; + lastWriteReqId = id; } + }); - if (intf.write.value.isValid && - !intf.write.value.toBool() && - intf.enable.value.isValid && - intf.enable.value.toBool() && - intf.strb.value.isValid && - intf.strb.value.toInt() > 0) { - // strobe must not be "active" during read xfer (all low during read) - logger.severe('Strobe must not be active during read transfer.'); + // track read response flits + if (rIntf.rValid.previousValue!.toBool()) { + final id = lastWriteReqId; + if (!writeReqMap.containsKey(id)) { + logger.severe( + 'There is no pending write request to associate with valid write data.'); } - if (intf.enable.value.isValid && - intf.enable.value.toBool() && - intf.ready.value.isValid && - intf.ready.value.toBool()) { - if (accessLastCycle) { - logger.severe('Cannot have back-to-back accesses.'); - } - - if (intf.includeSlvErr && !intf.slvErr!.value.isValid) { - logger.severe('SlvErr must be valid during transfer.'); - } - - accessLastCycle = true; - } else { - accessLastCycle = false; + writeReqMap[id]![1] = writeReqMap[id]![1] + 1; + final len = writeReqMap[id]![0]; + final currCount = writeReqMap[id]![1]; + if (currCount > len) { + logger.severe( + 'Sent more write data flits than indicated by the request with ID $id AWLEN. Expected $len but sent $currCount'); + } else if (currCount == len && !wIntf.wLast.previousValue!.toBool()) { + logger.severe( + 'Sent the final flit in the write data per the request with ID $id AWLEN but WLAST is not asserted.'); } - }); + } } } diff --git a/lib/src/models/axi4_bfm/axi4_main.dart b/lib/src/models/axi4_bfm/axi4_main.dart index 428c534cf..a99f00dcd 100644 --- a/lib/src/models/axi4_bfm/axi4_main.dart +++ b/lib/src/models/axi4_bfm/axi4_main.dart @@ -1,27 +1,34 @@ -// Copyright (C) 2023-2024 Intel Corporation +// Copyright (C) 2024-2025 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // -// apb_requester.dart -// An agent sending for APB requests. +// axi4_main.dart +// An agent sending for AXI4 requests. // -// 2023 June 12 -// Author: Max Korbel +// 2025 January +// Author: Josh Kimmel import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; import 'package:rohd_vf/rohd_vf.dart'; -/// An agent for sending requests on an [ApbInterface]. +/// An agent for sending requests on [Axi4ReadInterface]s and [Axi4WriteInterface]s. /// /// Driven read packets will update the returned data into the same packet. -class ApbRequesterAgent extends Agent { - /// The interface to drive. - final ApbInterface intf; +class Axi4MainAgent extends Agent { + /// AXI4 System Interface. + final Axi4SystemInterface sIntf; + + /// AXI4 Read Interface. + final Axi4ReadInterface rIntf; + + /// AXI4 Write Interface. + final Axi4WriteInterface wIntf; /// The sequencer where requests should be sent. - late final Sequencer sequencer; + late final Sequencer sequencer; /// The driver that sends the requests over the interface. - late final ApbRequesterDriver driver; + late final Axi4MainDriver driver; /// The number of cycles before timing out if no transactions can be sent. final int timeoutCycles; @@ -30,19 +37,23 @@ class ApbRequesterAgent extends Agent { /// no pending packets to send. final int dropDelayCycles; - /// Constructs a new [ApbRequesterAgent]. - ApbRequesterAgent({ - required this.intf, + /// Constructs a new [Axi4MainAgent]. + Axi4MainAgent({ + required this.sIntf, + required this.rIntf, + required this.wIntf, required Component parent, - String name = 'apbRequester', + String name = 'axiMainAgent', this.timeoutCycles = 500, this.dropDelayCycles = 30, }) : super(name, parent) { - sequencer = Sequencer('sequencer', this); + sequencer = Sequencer('sequencer', this); - driver = ApbRequesterDriver( + driver = Axi4MainDriver( parent: this, - intf: intf, + sIntf: sIntf, + rIntf: rIntf, + wIntf: wIntf, sequencer: sequencer, timeoutCycles: timeoutCycles, dropDelayCycles: dropDelayCycles, diff --git a/lib/src/models/axi4_bfm/axi4_main_driver.dart b/lib/src/models/axi4_bfm/axi4_main_driver.dart index 42a3eda18..b6799f73b 100644 --- a/lib/src/models/axi4_bfm/axi4_main_driver.dart +++ b/lib/src/models/axi4_bfm/axi4_main_driver.dart @@ -1,38 +1,46 @@ -// Copyright (C) 2023-2024 Intel Corporation +// Copyright (C) 2024-2025 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // -// apb_requester_driver.dart -// A driver for APB requests. +// axi4_main_driver.dart +// A driver for AXI4 requests. // -// 2023 June 12 -// Author: Max Korbel +// 2025 January +// Author: Josh Kimmel import 'dart:async'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/src/interfaces/interfaces.dart'; -import 'package:rohd_hcl/src/models/apb_bfm/apb_packet.dart'; +import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; import 'package:rohd_vf/rohd_vf.dart'; -/// A driver for the [ApbInterface] from the requester side. +/// A driver for the [Axi4ReadInterface] and [Axi4WriteInterface] interfaces on the main side. /// /// Driven read packets will update the returned data into the same packet. -class ApbRequesterDriver extends PendingClockedDriver { - /// The interface to drive. - final ApbInterface intf; +class Axi4MainDriver extends PendingClockedDriver { + /// AXI4 System Interface. + final Axi4SystemInterface sIntf; - /// Creates a new [ApbRequesterDriver]. - ApbRequesterDriver({ + /// AXI4 Read Interface. + final Axi4ReadInterface rIntf; + + /// AXI4 Write Interface. + final Axi4WriteInterface wIntf; + + /// Creates a new [Axi4MainDriver]. + Axi4MainDriver({ required Component parent, - required this.intf, + required this.sIntf, + required this.rIntf, + required this.wIntf, required super.sequencer, super.timeoutCycles = 500, super.dropDelayCycles = 30, - String name = 'apbRequesterDriver', + String name = 'axi4MainDriver', }) : super( name, parent, - clk: intf.clk, + clk: sIntf.clk, ); @override @@ -40,91 +48,146 @@ class ApbRequesterDriver extends PendingClockedDriver { unawaited(super.run(phase)); Simulator.injectAction(() { - _deselectAll(); - intf.enable.put(0); - intf.write.put(0); - intf.addr.put(0); - intf.wData.put(0); - intf.strb.put(0); + rIntf.arValid.put(0); + rIntf.arId?.put(0); + rIntf.arAddr.put(0); + rIntf.arLen?.put(0); + rIntf.arSize?.put(0); + rIntf.arBurst?.put(0); + rIntf.arLock?.put(0); + rIntf.arCache?.put(0); + rIntf.arProt.put(0); + rIntf.arQos?.put(0); + rIntf.arRegion?.put(0); + rIntf.arUser?.put(0); + rIntf.rReady.put(0); + wIntf.awValid.put(0); + wIntf.awId?.put(0); + wIntf.awAddr.put(0); + wIntf.awLen?.put(0); + wIntf.awSize?.put(0); + wIntf.awBurst?.put(0); + wIntf.awLock?.put(0); + wIntf.awCache?.put(0); + wIntf.awProt.put(0); + wIntf.awQos?.put(0); + wIntf.awRegion?.put(0); + wIntf.awUser?.put(0); + wIntf.wValid.put(0); + wIntf.wData.put(0); + wIntf.wStrb.put(0); + wIntf.wLast.put(0); + wIntf.bReady.put(0); }); // wait for reset to complete before driving anything - await intf.resetN.nextPosedge; + await sIntf.resetN.nextPosedge; while (!Simulator.simulationHasEnded) { if (pendingSeqItems.isNotEmpty) { await _drivePacket(pendingSeqItems.removeFirst()); - } else { - await intf.clk.nextPosedge; - Simulator.injectAction(() { - _deselectAll(); - intf.enable.put(0); - }); - } + } else {} } } /// Drives a packet onto the interface. - Future _drivePacket(ApbPacket packet) async { - // first, SETUP + Future _drivePacket(Axi4RequestPacket packet) async { + if (packet is Axi4ReadRequestPacket) { + await _driveReadPacket(packet); + } else if (packet is Axi4WriteRequestPacket) { + await _driveWritePacket(packet); + } else {} + } - await intf.clk.nextPosedge; + // TODO: need a more robust way of driving the "ready" signals... + // RREADY for read data responses + // BREADY for write responses + // specifically, when should they toggle on/off? + // ON => either always or when the associated request is driven? + // OFF => either never or when there are no more outstanding requests of the given type? + // should we enable the ability to backpressure?? - // if we're not selecting this interface, then we need to select it - if (!intf.sel[packet.selectIndex].value.toBool()) { - _select(packet.selectIndex); + Future _driveReadPacket(Axi4ReadRequestPacket packet) async { + await sIntf.clk.nextPosedge; + Simulator.injectAction(() { + rIntf.arValid.put(1); + rIntf.arId?.put(packet.id); + rIntf.arAddr.put(packet.addr); + rIntf.arLen?.put(packet.len); + rIntf.arSize?.put(packet.size); + rIntf.arBurst?.put(packet.burst); + rIntf.arLock?.put(packet.lock); + rIntf.arCache?.put(packet.cache); + rIntf.arProt.put(packet.prot); + rIntf.arQos?.put(packet.qos); + rIntf.arRegion?.put(packet.region); + rIntf.arUser?.put(packet.user); + rIntf.rReady.put(1); + }); + + // need to hold the request until receiver is ready + await sIntf.clk.nextPosedge; + if (!rIntf.arReady.value.toBool()) { + await rIntf.arReady.nextPosedge; } + // now we can release the request Simulator.injectAction(() { - intf.enable.put(0); - intf.addr.put(packet.addr); - - if (packet is ApbWritePacket) { - intf.write.put(1); - intf.wData.put(packet.data); - intf.strb.put(packet.strobe); - } else if (packet is ApbReadPacket) { - intf.write.put(0); - intf.wData.put(0); - intf.strb.put(0); - } + rIntf.arValid.put(0); }); - await intf.clk.nextPosedge; + // TODO: wait for the response to complete?? + } - // now, ACCESS - intf.enable.inject(1); + Future _driveWritePacket(Axi4WriteRequestPacket packet) async { + await sIntf.clk.nextPosedge; + Simulator.injectAction(() { + wIntf.awValid.put(1); + wIntf.awId?.put(packet.id); + wIntf.awAddr.put(packet.addr); + wIntf.awLen?.put(packet.len); + wIntf.awSize?.put(packet.size); + wIntf.awBurst?.put(packet.burst); + wIntf.awLock?.put(packet.lock); + wIntf.awCache?.put(packet.cache); + wIntf.awProt.put(packet.prot); + wIntf.awQos?.put(packet.qos); + wIntf.awRegion?.put(packet.region); + wIntf.awUser?.put(packet.user); + wIntf.bReady.put(1); + }); - // wait for ready from completer, if not already asserted - if (!intf.ready.value.toBool()) { - await intf.ready.nextPosedge; + // need to hold the request until receiver is ready + await sIntf.clk.nextPosedge; + if (!wIntf.awReady.value.toBool()) { + await wIntf.awReady.nextPosedge; } - if (packet is ApbWritePacket) { - packet.complete( - slvErr: intf.slvErr?.value, - ); - } else if (packet is ApbReadPacket) { - packet.complete( - data: intf.rData.value, - slvErr: intf.slvErr?.value, - ); - } + // now we can release the request + Simulator.injectAction(() { + wIntf.awValid.put(0); + }); - // now we're done, since enable and ready are both high, move on - } + // next send the data for the write + for (var i = 0; i < packet.data.length; i++) { + if (!wIntf.wReady.value.toBool()) { + await wIntf.wReady.nextPosedge; + } + Simulator.injectAction(() { + wIntf.wValid.put(1); + wIntf.wData.put(packet.data[i]); + wIntf.wStrb.put(packet.strobe[i]); + wIntf.wLast.put(i == packet.data.length - 1 ? 1 : 0); + wIntf.wUser?.put(packet.wUser); + }); + await sIntf.clk.nextPosedge; + } - /// Selects [index] and deselects the rest. - void _select(int index) { - _deselectAll(); - intf.sel[index].put(1); - } + // now we can stop the write data + Simulator.injectAction(() { + wIntf.wValid.put(0); + }); - /// Clears all selects. - void _deselectAll() { - // zero out all the selects, which should mask everything else - for (var i = 0; i < intf.numSelects; i++) { - intf.sel[i].put(0); - } + // TODO: wait for the response to complete?? } } diff --git a/lib/src/models/axi4_bfm/axi4_monitor.dart b/lib/src/models/axi4_bfm/axi4_monitor.dart index a1b679fbd..ad28a43da 100644 --- a/lib/src/models/axi4_bfm/axi4_monitor.dart +++ b/lib/src/models/axi4_bfm/axi4_monitor.dart @@ -1,64 +1,147 @@ -// Copyright (C) 2023-2024 Intel Corporation +// Copyright (C) 2024-2025 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // -// apb_monitor.dart -// A monitor that watches the APB interface. +// axi4_monitor.dart +// A monitor that watches the AXI4 interfaces. // -// 2023 June 12 -// Author: Max Korbel +// 2025 January +// Author: Josh Kimmel import 'dart:async'; +import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; import 'package:rohd_vf/rohd_vf.dart'; -/// A monitor for [ApbInterface]s. -class ApbMonitor extends Monitor { - /// The interface to monitor. - final ApbInterface intf; +/// A monitor for [Axi4ReadInterface]s and [Axi4WriteInterface]s. +class Axi4Monitor extends Monitor { + /// AXI4 System Interface. + final Axi4SystemInterface sIntf; - /// Creates a new [ApbMonitor] on [intf]. - ApbMonitor( - {required this.intf, + /// AXI4 Read Interface. + final Axi4ReadInterface rIntf; + + /// AXI4 Write Interface. + final Axi4WriteInterface wIntf; + + final List _pendingReadRequests = []; + final List> _pendingReadResponseData = []; + + final List _pendingWriteRequests = []; + + /// Creates a new [Axi4Monitor] on [rIntf] and [wIntf]. + Axi4Monitor( + {required this.sIntf, + required this.rIntf, + required this.wIntf, required Component parent, - String name = 'apbMonitor'}) + String name = 'axi4Monitor'}) : super(name, parent); @override Future run(Phase phase) async { unawaited(super.run(phase)); - await intf.resetN.nextPosedge; - - intf.clk.posedge.listen((event) { - for (var i = 0; i < intf.numSelects; i++) { - if (intf.sel[i].previousValue!.toBool() && - intf.enable.previousValue!.toBool() && - intf.ready.previousValue!.toBool()) { - if (intf.write.previousValue!.toBool()) { - add( - ApbWritePacket( - addr: intf.addr.previousValue!, - data: intf.wData.previousValue!, - strobe: intf.strb.previousValue, - selectIndex: i, - )..complete( - slvErr: intf.slvErr?.previousValue, - ), - ); - } else { - add( - ApbReadPacket( - addr: intf.addr.previousValue!, - selectIndex: i, - )..complete( - data: intf.rData.previousValue, - slvErr: intf.slvErr?.previousValue, - ), + await sIntf.resetN.nextPosedge; + + // handle reset + sIntf.resetN.negedge.listen((event) { + _pendingReadRequests.clear(); + _pendingReadResponseData.clear(); + _pendingWriteRequests.clear(); + }); + + sIntf.clk.posedge.listen((event) { + // read request monitoring + if (rIntf.arValid.previousValue!.toBool() && + rIntf.arReady.previousValue!.toBool()) { + _pendingReadRequests.add( + Axi4ReadRequestPacket( + addr: rIntf.arAddr.previousValue!, + prot: rIntf.arProt.previousValue!, + id: rIntf.arId?.previousValue, + len: rIntf.arLen?.previousValue, + size: rIntf.arSize?.previousValue, + burst: rIntf.arBurst?.previousValue, + lock: rIntf.arLock?.previousValue, + cache: rIntf.arCache?.previousValue, + qos: rIntf.arQos?.previousValue, + region: rIntf.arRegion?.previousValue, + user: rIntf.arUser?.previousValue, + ), + ); + _pendingReadResponseData.add([]); + } + + // read response data monitoring + if (rIntf.rValid.previousValue!.toBool() && + rIntf.rReady.previousValue!.toBool()) { + var targIdx = 0; + if (rIntf.rId != null) { + targIdx = _pendingReadRequests.indexWhere((element) => + element.id!.toBigInt() == rIntf.rId!.previousValue!.toBigInt()); + } + if (_pendingReadRequests.length > targIdx) { + _pendingReadResponseData[targIdx].add(rIntf.rData.previousValue!); + if (rIntf.rLast?.value.toBool() ?? true) { + _pendingReadRequests[targIdx].complete( + data: _pendingReadResponseData[targIdx], + resp: rIntf.rResp?.previousValue, + user: rIntf.rUser?.previousValue, ); + _pendingReadRequests.removeAt(targIdx); + _pendingReadResponseData.removeAt(targIdx); } } } + + // write request monitoring + if (wIntf.awValid.previousValue!.toBool() && + wIntf.awReady.previousValue!.toBool()) { + add( + Axi4WriteRequestPacket( + addr: wIntf.awAddr.previousValue!, + prot: wIntf.awProt.previousValue!, + id: wIntf.awId?.previousValue, + len: wIntf.awLen?.previousValue, + size: wIntf.awSize?.previousValue, + burst: wIntf.awBurst?.previousValue, + lock: wIntf.awLock?.previousValue, + cache: wIntf.awCache?.previousValue, + qos: wIntf.awQos?.previousValue, + region: wIntf.awRegion?.previousValue, + user: wIntf.awUser?.previousValue, + data: [], + strobe: []), + ); + } + + // write data monitoring + // NOTE: not dealing with WLAST here b/c it is implicit in how the interface behaves + if (wIntf.wValid.previousValue!.toBool() && + wIntf.wReady.previousValue!.toBool()) { + final targIdx = _pendingWriteRequests.length - 1; + _pendingWriteRequests[targIdx].data.add(wIntf.wData.previousValue!); + _pendingWriteRequests[targIdx].strobe.add(wIntf.wStrb.previousValue); + _pendingWriteRequests[targIdx].wUser = wIntf.wUser?.previousValue; + } + + // write response monitoring + if (wIntf.bValid.previousValue!.toBool() && + wIntf.bReady.previousValue!.toBool()) { + var targIdx = 0; + if (wIntf.bId != null) { + targIdx = _pendingWriteRequests.indexWhere((element) => + element.id!.toBigInt() == wIntf.bId!.previousValue!.toBigInt()); + } + if (_pendingWriteRequests.length > targIdx) { + _pendingWriteRequests[targIdx].complete( + resp: wIntf.bResp?.previousValue, + user: wIntf.bUser?.previousValue, + ); + } + } }); } } diff --git a/lib/src/models/axi4_bfm/axi4_packet.dart b/lib/src/models/axi4_bfm/axi4_packet.dart index 8f59a411c..d585cf0e4 100644 --- a/lib/src/models/axi4_bfm/axi4_packet.dart +++ b/lib/src/models/axi4_bfm/axi4_packet.dart @@ -186,26 +186,27 @@ class Axi4WriteRequestPacket extends Axi4RequestPacket { final List strobe; /// The user metadata associated with this write. - final List wUser; + LogicValue? wUser; /// Creates a write packet. /// /// If no [strobe] is provided, it will default to all high. - Axi4WriteRequestPacket( - {required super.addr, - required super.prot, - required this.data, - super.id, - super.len, - super.size, - super.burst, - super.lock, - super.cache, - super.qos, - super.region, - super.user, - this.strobe = const [], - this.wUser = const []}); + Axi4WriteRequestPacket({ + required super.addr, + required super.prot, + required this.data, + super.id, + super.len, + super.size, + super.burst, + super.lock, + super.cache, + super.qos, + super.region, + super.user, + this.strobe = const [], + this.wUser, + }); @override String? trackerString(TrackerField field) { diff --git a/lib/src/models/axi4_bfm/axi4_subordinate.dart b/lib/src/models/axi4_bfm/axi4_subordinate.dart index 82b92fe59..e81a0b454 100644 --- a/lib/src/models/axi4_bfm/axi4_subordinate.dart +++ b/lib/src/models/axi4_bfm/axi4_subordinate.dart @@ -14,8 +14,6 @@ import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; import 'package:rohd_vf/rohd_vf.dart'; -// TODO: FIGURE OUT IF DATA TRANSFERS CAN OCCUR OVER ARBITARY CYCLES - /// A model for the subordinate side of an [Axi4ReadInterface] and [Axi4WriteInterface]. class Axi4SubordinateAgent extends Agent { /// The system interface. @@ -63,7 +61,6 @@ class Axi4SubordinateAgent extends Agent { // to handle writes final List _writeMetadataQueue = []; - final List> _writeDataQueue = []; bool _writeReadyToOccur = false; /// Creates a new model [Axi4SubordinateAgent]. @@ -101,6 +98,8 @@ class Axi4SubordinateAgent extends Agent { _dataReadResponseDataQueue.clear(); _dataReadResponseMetadataQueue.clear(); _dataReadResponseIndex = 0; + _writeMetadataQueue.clear(); + _writeReadyToOccur = false; }); // wait for reset to complete @@ -112,6 +111,7 @@ class Axi4SubordinateAgent extends Agent { _respondRead(); _respondWrite(); _receiveRead(); + _captureWriteData(); _receiveWrite(); } } @@ -135,7 +135,7 @@ class Axi4SubordinateAgent extends Agent { /// Receives one packet (or returns if not selected). void _receiveRead() { - // work to if main is indicating a valid read that we are ready to handle + // work to do if main is indicating a valid read that we are ready to handle if (rIntf.arValid.value.toBool() && rIntf.arReady.value.toBool()) { final packet = Axi4ReadRequestPacket( addr: rIntf.arAddr.value, @@ -169,7 +169,13 @@ class Axi4SubordinateAgent extends Agent { var addrToRead = rIntf.arAddr.value; final endCount = rIntf.arLen?.value.toInt() ?? 1; final dSize = (rIntf.arSize?.value.toInt() ?? 0) * 8; - final increment = rIntf.arBurst?.value.toInt() ?? 1; + var increment = 0; + if (rIntf.arBurst == null || + rIntf.arBurst?.value.toInt() == Axi4BurstField.wrap.value || + rIntf.arBurst?.value.toInt() == Axi4BurstField.incr.value) { + increment = dSize ~/ 8; + } + for (var i = 0; i < endCount; i++) { var currData = storage.readData(addrToRead); if (dSize > 0) { @@ -233,9 +239,9 @@ class Axi4SubordinateAgent extends Agent { // handle an incoming write request void _receiveWrite() { - // work to if main is indicating a valid read that we are ready to handle + // work to do if main is indicating a valid write that we are ready to handle if (wIntf.awValid.value.toBool() && wIntf.awReady.value.toBool()) { - var packet = Axi4WriteRequestPacket( + final packet = Axi4WriteRequestPacket( addr: wIntf.awAddr.value, prot: wIntf.awProt.value, id: wIntf.awId?.value, @@ -252,7 +258,6 @@ class Axi4SubordinateAgent extends Agent { // might need to capture the first data and strobe simultaneously // NOTE: we are dropping wUser on the floor for now... - final idMatch = wIntf.awId?.value == wIntf.wId?.value; if (wIntf.wValid.value.toBool() && wIntf.wReady.value.toBool()) { packet.data.add(wIntf.wData.value); packet.strobe.add(wIntf.wStrb.value); @@ -263,42 +268,90 @@ class Axi4SubordinateAgent extends Agent { // queue up the packet for further processing _writeMetadataQueue.add(packet); + } + } - // NOTE: generic model does not handle the following read request fields: - // cache - // qos - // region - // user - // These can be handled in a derived class of this model if need be. - // Because for the most part they require implementation specific handling. - - // NOTE: generic model doesn't honor the prot field in read requests. - // It will be added as a feature request in the future. + // method to capture incoming write data after the initial request + // note that this method does not handle the first flit of write data + // if it is transmitted simultaneously with the write request + void _captureWriteData() { + // NOTE: we are dropping wUser on the floor for now... + if (_writeMetadataQueue.isNotEmpty && + wIntf.wValid.value.toBool() && + wIntf.wReady.value.toBool()) { + final packet = _writeMetadataQueue[0]; + packet.data.add(wIntf.wData.value); + packet.strobe.add(wIntf.wStrb.value); + if (wIntf.wLast.value.toBool()) { + _writeReadyToOccur = true; + } + } + } - // NOTE: generic model doesn't honor the lock field in read requests. - // It will be added as a feature request in the future. + void _respondWrite() { + // only work to do if we have received all of the data for our write request + if (_writeReadyToOccur) { + // only respond if the main is ready + if (wIntf.bReady.value.toBool()) { + final packet = _writeMetadataQueue[0]; + final error = respondWithError != null && respondWithError!(packet); + + // for now, only support sending slvErr and okay as responses + wIntf.bValid.put(true); + wIntf.bId?.put(packet.id); + wIntf.bResp?.put(error + ? LogicValue.ofInt(Axi4RespField.slvErr.value, wIntf.bResp!.width) + : LogicValue.ofInt(Axi4RespField.okay.value, wIntf.bResp!.width)); + wIntf.bUser?.put(0); // don't support user field for now + + // TODO: how to deal with delays?? + // if (readResponseDelay != null) { + // final delayCycles = readResponseDelay!(packet); + // if (delayCycles > 0) { + // await sIntf.clk.waitCycles(delayCycles); + // } + // } + + // NOTE: generic model does not handle the following write request fields: + // cache + // qos + // region + // user + // These can be handled in a derived class of this model if need be. + // Because for the most part they require implementation specific handling. + + // NOTE: generic model doesn't honor the prot field in write requests. + // It will be added as a feature request in the future. + + // NOTE: generic model doesn't honor the lock field in write requests. + // It will be added as a feature request in the future. + + if (!error || !dropWriteDataOnError) { + // write the data to the storage + var addrToWrite = packet.addr; + final dSize = (packet.size?.toInt() ?? 0) * 8; + var increment = 0; + if (packet.burst == null || + packet.burst!.toInt() == Axi4BurstField.wrap.value || + packet.burst!.toInt() == Axi4BurstField.incr.value) { + increment = dSize ~/ 8; + } - // write data to the storage - final data = []; - var addrToRead = rIntf.arAddr.value; - final endCount = rIntf.arLen?.value.toInt() ?? 1; - final dSize = (rIntf.arSize?.value.toInt() ?? 0) * 8; - final increment = rIntf.arBurst?.value.toInt() ?? 1; - for (var i = 0; i < endCount; i++) { - var currData = storage.readData(addrToRead); - if (dSize > 0) { - if (currData.width < dSize) { - currData = currData.zeroExtend(dSize); - } else if (currData.width > dSize) { - currData = currData.getRange(0, dSize); + for (var i = 0; i < packet.data.length; i++) { + final strobedData = _strobeData( + storage.readData(addrToWrite), + packet.data[i], + packet.strobe[i] ?? + LogicValue.filled(packet.data[i].width, LogicValue.one)); + storage.writeData(addrToWrite, strobedData.getRange(0, dSize)); + addrToWrite = addrToWrite + increment; } } - data.add(currData); - addrToRead = addrToRead + increment; - } - _dataReadResponseMetadataQueue.add(packet); - _dataReadResponseDataQueue.add(data); + // pop this write response off the queue + _writeMetadataQueue.removeAt(0); + _writeReadyToOccur = false; + } } } } diff --git a/lib/src/models/axi4_bfm/axi4_tracker.dart b/lib/src/models/axi4_bfm/axi4_tracker.dart index ff9cd6c79..72961744a 100644 --- a/lib/src/models/axi4_bfm/axi4_tracker.dart +++ b/lib/src/models/axi4_bfm/axi4_tracker.dart @@ -69,7 +69,7 @@ class Axi4Tracker extends Tracker { /// If the [selectColumnWidth] is set to 0, the field will be omitted. Axi4Tracker({ required Axi4ReadInterface rIntf, - required Axi4ReadInterface wIntf, + required Axi4WriteInterface wIntf, String name = 'Axi4Tracker', super.dumpJson, super.dumpTable, diff --git a/test/axi4_bfm_test.dart b/test/axi4_bfm_test.dart new file mode 100644 index 000000000..0841d9710 --- /dev/null +++ b/test/axi4_bfm_test.dart @@ -0,0 +1,265 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// axi4_bfm_test.dart +// Tests for the AXI4 BFM. +// +// 2025 January +// Author: Josh Kimmel + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; +import 'package:rohd_vf/rohd_vf.dart'; +import 'package:test/test.dart'; + +import 'apb_test.dart'; + +class Axi4BfmTest extends Test { + late final Axi4SystemInterface sIntf; + late final Axi4ReadInterface rIntf; + late final Axi4WriteInterface wIntf; + + late final Axi4MainAgent main; + + final storage = SparseMemoryStorage( + addrWidth: 32, + dataWidth: 32, + onInvalidRead: (addr, dataWidth) => + LogicValue.filled(dataWidth, LogicValue.zero), + ); + + final int numTransfers; + + final bool withStrobes; + + final int interTxnDelay; + + final bool withRandomRspDelays; + + final bool withErrors; + + String get outFolder => 'tmp_test/axi4bfm/$name/'; + + Axi4BfmTest( + super.name, { + this.numTransfers = 10, + this.withStrobes = false, + this.interTxnDelay = 0, + this.withRandomRspDelays = false, + this.withErrors = false, + }) : super(randomSeed: 123) { + // using default parameter values for all interfaces + sIntf = Axi4SystemInterface(); + rIntf = Axi4ReadInterface(); + wIntf = Axi4WriteInterface(); + + sIntf.clk <= SimpleClockGenerator(10).clk; + + main = + Axi4MainAgent(sIntf: sIntf, rIntf: rIntf, wIntf: wIntf, parent: this); + + Axi4SubordinateAgent( + sIntf: sIntf, + rIntf: rIntf, + wIntf: wIntf, + parent: this, + storage: storage, + readResponseDelay: + withRandomRspDelays ? (request) => Test.random!.nextInt(5) : null, + writeResponseDelay: + withRandomRspDelays ? (request) => Test.random!.nextInt(5) : null,, + respondWithError: withErrors ? (request) => true : null, + ); + + final monitor = Axi4Monitor(sIntf: sIntf, + rIntf: rIntf, + wIntf: wIntf, + parent: this,); + + Directory(outFolder).createSync(recursive: true); + + final tracker = Axi4Tracker( + rIntf: rIntf, + wIntf: wIntf, + dumpTable: false, + outputFolder: outFolder, + ); + + Axi4ComplianceChecker(sIntf, + rIntf, + wIntf, parent: this); + + Simulator.registerEndOfSimulationAction(() async { + await tracker.terminate(); + + final jsonStr = + File('$outFolder/axi4Tracker.tracker.json').readAsStringSync(); + final jsonContents = json.decode(jsonStr); + // ignore: avoid_dynamic_calls + // TODO: fix?? + expect(jsonContents['records'].length, 2 * numTransfers); + + Directory(outFolder).deleteSync(recursive: true); + }); + + monitor.stream.listen(tracker.record); + } + + int numTransfersCompleted = 0; + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + final obj = phase.raiseObjection('axi4BfmTestObj'); + + await _resetFlow(); + + final randomStrobes = List.generate( + numTransfers, (index) => LogicValue.ofInt(Test.random!.nextInt(16), 4)); + + final randomData = List.generate(numTransfers, + (index) => LogicValue.ofInt(Test.random!.nextInt(1 << 32), 32)); + + LogicValue strobedData(LogicValue originalData, LogicValue strobe) => [ + for (var i = 0; i < 4; i++) + strobe[i].toBool() + ? originalData.getRange(i * 8, i * 8 + 8) + : LogicValue.filled(8, LogicValue.zero) + ].rswizzle(); + + // normal writes + for (var i = 0; i < numTransfers; i++) { + final wrPkt = Axi4WriteRequestPacket( + addr: LogicValue.ofInt(i, 32), + prot: LogicValue.ofInt(0, wIntf.protWidth), + data: randomData, + id: LogicValue.ofInt(i, wIntf.idWidth), + len: LogicValue.ofInt(randomData.length, wIntf.lenWidth), + size: LogicValue.ofInt(2, wIntf.sizeWidth), // TODO + burst: LogicValue.ofInt(Axi4BurstField.incr.value, wIntf.burstWidth), + lock: LogicValue.ofInt(0, 1), + cache: LogicValue.ofInt(0, wIntf.cacheWidth), + qos: LogicValue.ofInt(0, wIntf.qosWidth), + region: LogicValue.ofInt(0, wIntf.regionWidth), + user: LogicValue.ofInt(0, wIntf.awuserWidth), + strobe: withStrobes ? randomStrobes : null, + wUser: LogicValue.ofInt(0, wIntf.wuserWidth), + ); + + main.sequencer.add(wrPkt); + numTransfersCompleted++; + + // TODO: should we be waiting?? + // Note that driver will already serialize the writes + + await sIntf.clk.waitCycles(interTxnDelay); + } + + // normal reads that check data + for (var i = 0; i < numTransfers; i++) { + final rdPkt = Axi4ReadRequestPacket( + addr: LogicValue.ofInt(i, 32), + prot: LogicValue.ofInt(0, rIntf.protWidth), + id: LogicValue.ofInt(i, rIntf.idWidth), + len: LogicValue.ofInt(randomData.length, rIntf.lenWidth), + size: LogicValue.ofInt(2, rIntf.sizeWidth), // TODO + burst: LogicValue.ofInt(Axi4BurstField.incr.value, rIntf.burstWidth), + lock: LogicValue.ofInt(0, 1), + cache: LogicValue.ofInt(0, rIntf.cacheWidth), + qos: LogicValue.ofInt(0, rIntf.qosWidth), + region: LogicValue.ofInt(0, rIntf.regionWidth), + user: LogicValue.ofInt(0, rIntf.aruserWidth), + ); + + main.sequencer.add(rdPkt); + + // TODO: should we be waiting?? + + await sIntf.clk.waitCycles(interTxnDelay); + } + + obj.drop(); + } + + Future _resetFlow() async { + await sIntf.clk.waitCycles(2); + sIntf.resetN.inject(0); + await sIntf.clk.waitCycles(3); + sIntf.resetN.inject(1); + } + + @override + void check() { + expect(numTransfersCompleted, numTransfers * 2); + + if (withErrors) { + expect(storage.isEmpty, true); + } + } +} + +void main() { + tearDown(() async { + await Simulator.reset(); + }); + + Future runTest(Axi4BfmTest axi4BfmTest, {bool dumpWaves = false}) async { + Simulator.setMaxSimTime(3000); + + // TODO: dump waves... + + await axi4BfmTest.start(); + } + + test('simple writes and reads', () async { + await runTest(Axi4BfmTest('simple')); + }); + + test('writes with strobes', () async { + await runTest(Axi4BfmTest('strobes', numTransfers: 20, withStrobes: true)); + }); + + test('writes and reads with 1 cycle delays', () async { + await runTest(Axi4BfmTest('delay1', interTxnDelay: 1)); + }); + + test('writes and reads with 2 cycle delays', () async { + await runTest(Axi4BfmTest('delay2', interTxnDelay: 2)); + }); + + test('writes and reads with 3 cycle delays', () async { + await runTest(Axi4BfmTest('delay3', interTxnDelay: 3)); + }); + + test('writes and reads with big delays', () async { + await runTest(Axi4BfmTest('delay5', interTxnDelay: 5)); + }); + + test('random response delays', () async { + await runTest(Axi4BfmTest( + 'randrsp', + numTransfers: 20, + withRandomRspDelays: true, + )); + }); + + test('random everything', () async { + await runTest(Axi4BfmTest( + 'randeverything', + numTransfers: 20, + withRandomRspDelays: true, + withStrobes: true, + interTxnDelay: 3, + )); + }); + + test('with errors', () async { + await runTest(Axi4BfmTest('werr', withErrors: true)); + }); +} From 441c5aeeb03207f2f327e65c623da78427cb9e7b Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Mon, 27 Jan 2025 17:24:56 -0800 Subject: [PATCH 05/16] Basic liveness in a test. --- .../axi4_bfm/axi4_compliance_checker.dart | 69 ++++---- lib/src/models/axi4_bfm/axi4_main_driver.dart | 13 +- lib/src/models/axi4_bfm/axi4_monitor.dart | 33 ++-- lib/src/models/axi4_bfm/axi4_packet.dart | 9 +- lib/src/models/axi4_bfm/axi4_subordinate.dart | 40 ++++- test/axi4_bfm_test.dart | 160 ++++++++++++------ test/axi4_test.dart | 103 +++++++++++ 7 files changed, 314 insertions(+), 113 deletions(-) create mode 100644 test/axi4_test.dart diff --git a/lib/src/models/axi4_bfm/axi4_compliance_checker.dart b/lib/src/models/axi4_bfm/axi4_compliance_checker.dart index db5ca40ca..5ce543015 100644 --- a/lib/src/models/axi4_bfm/axi4_compliance_checker.dart +++ b/lib/src/models/axi4_bfm/axi4_compliance_checker.dart @@ -45,11 +45,13 @@ class Axi4ComplianceChecker extends Component { // READ REQUESTS // number of flits returned matches ARLEN if no error // if RLAST is present, asserted on the final flit only - // if RID is present, every read response should match a pending request ARID + // if RID is present, every read response should match + // a pending request ARID // WRITE REQUESTS // number of flits sent matches AWLEN // WLAST is asserted on the final flit only - // if BID is present, every write response should match a pending request AWID + // if BID is present, every write response should match + // a pending request AWID final rLastPresent = rIntf.rLast != null; @@ -59,18 +61,21 @@ class Axi4ComplianceChecker extends Component { sIntf.clk.posedge.listen((event) { // capture read requests for counting - if (rIntf.arValid.previousValue!.toBool()) { + if (rIntf.arValid.previousValue!.isValid && + rIntf.arValid.previousValue!.toBool()) { final id = rIntf.arId?.previousValue?.toInt() ?? 0; - final len = rIntf.arLen?.previousValue?.toInt() ?? 1; + final len = (rIntf.arLen?.previousValue?.toInt() ?? 0) + 1; readReqMap[id] = [len, 0]; } - // track write data flits - if (wIntf.wValid.previousValue!.toBool()) { + // track read response flits + if (rIntf.rValid.previousValue!.isValid && + rIntf.rValid.previousValue!.toBool()) { final id = rIntf.rId?.previousValue?.toInt() ?? 0; if (!readReqMap.containsKey(id)) { logger.severe( - 'Cannot match a read response to any pending read request. ID captured by the response was $id.'); + 'Cannot match a read response to any pending read request. ' + 'ID captured by the response was $id.'); } readReqMap[id]![1] = readReqMap[id]![1] + 1; @@ -78,42 +83,46 @@ class Axi4ComplianceChecker extends Component { final currCount = readReqMap[id]![1]; if (currCount > len) { logger.severe( - 'Received more read response data flits than indicated by the request with ID $id ARLEN. Expected $len but got $currCount'); + 'Received more read response data flits than indicated by the ' + 'request with ID $id ARLEN. Expected $len but got $currCount'); } else if (currCount == len && rLastPresent && !rIntf.rLast!.previousValue!.toBool()) { - logger.severe( - 'Received the final flit in the read response data per the request with ID $id ARLEN but RLAST is not asserted.'); + logger.severe('Received the final flit in the read response data per ' + 'the request with ID $id ARLEN but RLAST is not asserted.'); } } // track write requests - if (wIntf.awValid.previousValue!.toBool()) { + if (wIntf.awValid.previousValue!.isValid && + wIntf.awValid.previousValue!.toBool()) { final id = wIntf.awId?.previousValue?.toInt() ?? 0; - final len = wIntf.awLen?.previousValue?.toInt() ?? 1; + final len = (wIntf.awLen?.previousValue?.toInt() ?? 0) + 1; writeReqMap[id] = [len, 0]; lastWriteReqId = id; } - }); - // track read response flits - if (rIntf.rValid.previousValue!.toBool()) { - final id = lastWriteReqId; - if (!writeReqMap.containsKey(id)) { - logger.severe( - 'There is no pending write request to associate with valid write data.'); - } + // track write data flits + if (wIntf.wValid.previousValue!.isValid && + wIntf.wValid.previousValue!.toBool()) { + final id = lastWriteReqId; + if (!writeReqMap.containsKey(id)) { + logger.severe('There is no pending write request ' + 'to associate with valid write data.'); + } - writeReqMap[id]![1] = writeReqMap[id]![1] + 1; - final len = writeReqMap[id]![0]; - final currCount = writeReqMap[id]![1]; - if (currCount > len) { - logger.severe( - 'Sent more write data flits than indicated by the request with ID $id AWLEN. Expected $len but sent $currCount'); - } else if (currCount == len && !wIntf.wLast.previousValue!.toBool()) { - logger.severe( - 'Sent the final flit in the write data per the request with ID $id AWLEN but WLAST is not asserted.'); + writeReqMap[id]![1] = writeReqMap[id]![1] + 1; + final len = writeReqMap[id]![0]; + final currCount = writeReqMap[id]![1]; + if (currCount > len) { + logger.severe( + 'Sent more write data flits than indicated by the request ' + 'with ID $id AWLEN. Expected $len but sent $currCount'); + } else if (currCount == len && !wIntf.wLast.previousValue!.toBool()) { + logger.severe('Sent the final flit in the write data per the request ' + 'with ID $id AWLEN but WLAST is not asserted.'); + } } - } + }); } } diff --git a/lib/src/models/axi4_bfm/axi4_main_driver.dart b/lib/src/models/axi4_bfm/axi4_main_driver.dart index b6799f73b..5f4300220 100644 --- a/lib/src/models/axi4_bfm/axi4_main_driver.dart +++ b/lib/src/models/axi4_bfm/axi4_main_driver.dart @@ -14,9 +14,9 @@ import 'package:rohd_hcl/src/interfaces/interfaces.dart'; import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; import 'package:rohd_vf/rohd_vf.dart'; -/// A driver for the [Axi4ReadInterface] and [Axi4WriteInterface] interfaces on the main side. +/// A driver for the [Axi4ReadInterface] and [Axi4WriteInterface] interfaces. /// -/// Driven read packets will update the returned data into the same packet. +/// Driving from the perspective of the Main agent. class Axi4MainDriver extends PendingClockedDriver { /// AXI4 System Interface. final Axi4SystemInterface sIntf; @@ -86,17 +86,22 @@ class Axi4MainDriver extends PendingClockedDriver { while (!Simulator.simulationHasEnded) { if (pendingSeqItems.isNotEmpty) { await _drivePacket(pendingSeqItems.removeFirst()); - } else {} + } else { + await sIntf.clk.nextPosedge; + } } } /// Drives a packet onto the interface. Future _drivePacket(Axi4RequestPacket packet) async { + print('Driving packet at time ${Simulator.time}'); if (packet is Axi4ReadRequestPacket) { await _driveReadPacket(packet); } else if (packet is Axi4WriteRequestPacket) { await _driveWritePacket(packet); - } else {} + } else { + await sIntf.clk.nextPosedge; + } } // TODO: need a more robust way of driving the "ready" signals... diff --git a/lib/src/models/axi4_bfm/axi4_monitor.dart b/lib/src/models/axi4_bfm/axi4_monitor.dart index ad28a43da..b8ad93b21 100644 --- a/lib/src/models/axi4_bfm/axi4_monitor.dart +++ b/lib/src/models/axi4_bfm/axi4_monitor.dart @@ -54,7 +54,9 @@ class Axi4Monitor extends Monitor { sIntf.clk.posedge.listen((event) { // read request monitoring - if (rIntf.arValid.previousValue!.toBool() && + if (rIntf.arValid.previousValue!.isValid && + rIntf.arReady.previousValue!.isValid && + rIntf.arValid.previousValue!.toBool() && rIntf.arReady.previousValue!.toBool()) { _pendingReadRequests.add( Axi4ReadRequestPacket( @@ -75,14 +77,16 @@ class Axi4Monitor extends Monitor { } // read response data monitoring - if (rIntf.rValid.previousValue!.toBool() && + if (rIntf.rValid.previousValue!.isValid && + rIntf.rReady.previousValue!.isValid && + rIntf.rValid.previousValue!.toBool() && rIntf.rReady.previousValue!.toBool()) { var targIdx = 0; if (rIntf.rId != null) { targIdx = _pendingReadRequests.indexWhere((element) => - element.id!.toBigInt() == rIntf.rId!.previousValue!.toBigInt()); + element.id!.toInt() == rIntf.rId!.previousValue!.toInt()); } - if (_pendingReadRequests.length > targIdx) { + if (targIdx >= 0 && _pendingReadRequests.length > targIdx) { _pendingReadResponseData[targIdx].add(rIntf.rData.previousValue!); if (rIntf.rLast?.value.toBool() ?? true) { _pendingReadRequests[targIdx].complete( @@ -97,9 +101,11 @@ class Axi4Monitor extends Monitor { } // write request monitoring - if (wIntf.awValid.previousValue!.toBool() && + if (wIntf.awValid.previousValue!.isValid && + wIntf.awReady.previousValue!.isValid && + wIntf.awValid.previousValue!.toBool() && wIntf.awReady.previousValue!.toBool()) { - add( + _pendingWriteRequests.add( Axi4WriteRequestPacket( addr: wIntf.awAddr.previousValue!, prot: wIntf.awProt.previousValue!, @@ -119,27 +125,32 @@ class Axi4Monitor extends Monitor { // write data monitoring // NOTE: not dealing with WLAST here b/c it is implicit in how the interface behaves - if (wIntf.wValid.previousValue!.toBool() && + if (wIntf.wValid.previousValue!.isValid && + wIntf.wReady.previousValue!.isValid && + wIntf.wValid.previousValue!.toBool() && wIntf.wReady.previousValue!.toBool()) { final targIdx = _pendingWriteRequests.length - 1; _pendingWriteRequests[targIdx].data.add(wIntf.wData.previousValue!); - _pendingWriteRequests[targIdx].strobe.add(wIntf.wStrb.previousValue); + _pendingWriteRequests[targIdx].strobe.add(wIntf.wStrb.previousValue!); _pendingWriteRequests[targIdx].wUser = wIntf.wUser?.previousValue; } // write response monitoring - if (wIntf.bValid.previousValue!.toBool() && + if (wIntf.bValid.previousValue!.isValid && + wIntf.bReady.previousValue!.isValid && + wIntf.bValid.previousValue!.toBool() && wIntf.bReady.previousValue!.toBool()) { var targIdx = 0; if (wIntf.bId != null) { targIdx = _pendingWriteRequests.indexWhere((element) => - element.id!.toBigInt() == wIntf.bId!.previousValue!.toBigInt()); + element.id!.toInt() == wIntf.bId!.previousValue!.toInt()); } - if (_pendingWriteRequests.length > targIdx) { + if (targIdx >= 0 && _pendingWriteRequests.length > targIdx) { _pendingWriteRequests[targIdx].complete( resp: wIntf.bResp?.previousValue, user: wIntf.bUser?.previousValue, ); + _pendingWriteRequests.removeAt(targIdx); } } }); diff --git a/lib/src/models/axi4_bfm/axi4_packet.dart b/lib/src/models/axi4_bfm/axi4_packet.dart index d585cf0e4..649bb133d 100644 --- a/lib/src/models/axi4_bfm/axi4_packet.dart +++ b/lib/src/models/axi4_bfm/axi4_packet.dart @@ -183,7 +183,7 @@ class Axi4WriteRequestPacket extends Axi4RequestPacket { final List data; /// The strobe associated with this write. - final List strobe; + final List strobe; /// The user metadata associated with this write. LogicValue? wUser; @@ -195,6 +195,7 @@ class Axi4WriteRequestPacket extends Axi4RequestPacket { required super.addr, required super.prot, required this.data, + required this.strobe, super.id, super.len, super.size, @@ -204,7 +205,6 @@ class Axi4WriteRequestPacket extends Axi4RequestPacket { super.qos, super.region, super.user, - this.strobe = const [], this.wUser, }); @@ -221,10 +221,7 @@ class Axi4WriteRequestPacket extends Axi4RequestPacket { .join(); case Axi4Tracker.strbField: return strobe - .where( - (element) => element != null, - ) - .map((d) => d!.toRadixString(radix: 16)) + .map((d) => d.toRadixString(radix: 16)) .toList() .reversed .join(); diff --git a/lib/src/models/axi4_bfm/axi4_subordinate.dart b/lib/src/models/axi4_bfm/axi4_subordinate.dart index e81a0b454..8ee4d9dd6 100644 --- a/lib/src/models/axi4_bfm/axi4_subordinate.dart +++ b/lib/src/models/axi4_bfm/axi4_subordinate.dart @@ -14,7 +14,8 @@ import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; import 'package:rohd_vf/rohd_vf.dart'; -/// A model for the subordinate side of an [Axi4ReadInterface] and [Axi4WriteInterface]. +/// A model for the subordinate side of +/// an [Axi4ReadInterface] and [Axi4WriteInterface]. class Axi4SubordinateAgent extends Agent { /// The system interface. final Axi4SystemInterface sIntf; @@ -27,8 +28,8 @@ class Axi4SubordinateAgent extends Agent { /// A place where the subordinate should save and retrieve data. /// - /// The [Axi4SubordinateAgent] will reset [storage] whenever the `resetN` signal - /// is dropped. + /// The [Axi4SubordinateAgent] will reset [storage] whenever + /// the `resetN` signal is dropped. final MemoryStorage storage; /// A function which delays the response for the given `request`. @@ -107,12 +108,14 @@ class Axi4SubordinateAgent extends Agent { while (!Simulator.simulationHasEnded) { await sIntf.clk.nextNegedge; + // WE EVENTUALLY GET STUCK WAITING FOR THIS NEGEDGE!!! _driveReadys(); _respondRead(); _respondWrite(); _receiveRead(); _captureWriteData(); _receiveWrite(); + print('BLAH2'); } } @@ -137,6 +140,8 @@ class Axi4SubordinateAgent extends Agent { void _receiveRead() { // work to do if main is indicating a valid read that we are ready to handle if (rIntf.arValid.value.toBool() && rIntf.arReady.value.toBool()) { + print('Received read request at time ${Simulator.time}'); + final packet = Axi4ReadRequestPacket( addr: rIntf.arAddr.value, prot: rIntf.arProt.value, @@ -150,13 +155,13 @@ class Axi4SubordinateAgent extends Agent { region: rIntf.arRegion?.value, user: rIntf.arUser?.value); - // NOTE: generic model does not handle the following read request fields: + // generic model does not handle the following read request fields: // cache // qos // region // user // These can be handled in a derived class of this model if need be. - // Because for the most part they require implementation specific handling. + // Because they require implementation specific handling. // NOTE: generic model doesn't honor the prot field in read requests. // It will be added as a feature request in the future. @@ -167,7 +172,7 @@ class Axi4SubordinateAgent extends Agent { // query storage to retrieve the data final data = []; var addrToRead = rIntf.arAddr.value; - final endCount = rIntf.arLen?.value.toInt() ?? 1; + final endCount = (rIntf.arLen?.value.toInt() ?? 0) + 1; final dSize = (rIntf.arSize?.value.toInt() ?? 0) * 8; var increment = 0; if (rIntf.arBurst == null || @@ -207,6 +212,10 @@ class Axi4SubordinateAgent extends Agent { final last = _dataReadResponseIndex == _dataReadResponseDataQueue[0].length - 1; + print('HELLO JOSH'); + print(_dataReadResponseIndex); + print(_dataReadResponseDataQueue[0].length); + // TODO: how to deal with delays?? // if (readResponseDelay != null) { // final delayCycles = readResponseDelay!(packet); @@ -230,10 +239,15 @@ class Axi4SubordinateAgent extends Agent { _dataReadResponseIndex = 0; _dataReadResponseMetadataQueue.removeAt(0); _dataReadResponseDataQueue.removeAt(0); + + print('Finished sending read response at time ${Simulator.time}'); } else { // move to the next chunk of data _dataReadResponseIndex++; + print('Still send the read response as of time ${Simulator.time}'); } + } else { + rIntf.rValid.put(false); } } @@ -241,6 +255,7 @@ class Axi4SubordinateAgent extends Agent { void _receiveWrite() { // work to do if main is indicating a valid write that we are ready to handle if (wIntf.awValid.value.toBool() && wIntf.awReady.value.toBool()) { + print('Received write request at time ${Simulator.time}'); final packet = Axi4WriteRequestPacket( addr: wIntf.awAddr.value, prot: wIntf.awProt.value, @@ -268,6 +283,7 @@ class Axi4SubordinateAgent extends Agent { // queue up the packet for further processing _writeMetadataQueue.add(packet); + print('JOSH1'); } } @@ -283,14 +299,18 @@ class Axi4SubordinateAgent extends Agent { packet.data.add(wIntf.wData.value); packet.strobe.add(wIntf.wStrb.value); if (wIntf.wLast.value.toBool()) { + print('JOSH3'); _writeReadyToOccur = true; } + print('JOSH2'); } } void _respondWrite() { // only work to do if we have received all of the data for our write request + print('JOSH5'); if (_writeReadyToOccur) { + print('JOSH4'); // only respond if the main is ready if (wIntf.bReady.value.toBool()) { final packet = _writeMetadataQueue[0]; @@ -312,13 +332,13 @@ class Axi4SubordinateAgent extends Agent { // } // } - // NOTE: generic model does not handle the following write request fields: + // generic model does not handle the following write request fields: // cache // qos // region // user // These can be handled in a derived class of this model if need be. - // Because for the most part they require implementation specific handling. + // Because they require implementation specific handling. // NOTE: generic model doesn't honor the prot field in write requests. // It will be added as a feature request in the future. @@ -351,7 +371,11 @@ class Axi4SubordinateAgent extends Agent { // pop this write response off the queue _writeMetadataQueue.removeAt(0); _writeReadyToOccur = false; + + print('Sent write response at time ${Simulator.time}'); } + } else { + wIntf.bValid.put(false); } } } diff --git a/test/axi4_bfm_test.dart b/test/axi4_bfm_test.dart index 0841d9710..526ba4132 100644 --- a/test/axi4_bfm_test.dart +++ b/test/axi4_bfm_test.dart @@ -17,7 +17,7 @@ import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; import 'package:rohd_vf/rohd_vf.dart'; import 'package:test/test.dart'; -import 'apb_test.dart'; +import 'axi4_test.dart'; class Axi4BfmTest extends Test { late final Axi4SystemInterface sIntf; @@ -26,12 +26,7 @@ class Axi4BfmTest extends Test { late final Axi4MainAgent main; - final storage = SparseMemoryStorage( - addrWidth: 32, - dataWidth: 32, - onInvalidRead: (addr, dataWidth) => - LogicValue.filled(dataWidth, LogicValue.zero), - ); + late SparseMemoryStorage storage; final int numTransfers; @@ -43,6 +38,13 @@ class Axi4BfmTest extends Test { final bool withErrors; + final int addrWidth; + + final int dataWidth; + + // large lens can make transactions really long... + final int lenWidth; + String get outFolder => 'tmp_test/axi4bfm/$name/'; Axi4BfmTest( @@ -52,11 +54,29 @@ class Axi4BfmTest extends Test { this.interTxnDelay = 0, this.withRandomRspDelays = false, this.withErrors = false, + this.addrWidth = 32, + this.dataWidth = 32, + this.lenWidth = 2, }) : super(randomSeed: 123) { // using default parameter values for all interfaces sIntf = Axi4SystemInterface(); - rIntf = Axi4ReadInterface(); - wIntf = Axi4WriteInterface(); + rIntf = Axi4ReadInterface( + addrWidth: addrWidth, + dataWidth: dataWidth, + lenWidth: lenWidth, + ruserWidth: dataWidth ~/ 2 - 1); + wIntf = Axi4WriteInterface( + addrWidth: addrWidth, + dataWidth: dataWidth, + lenWidth: lenWidth, + wuserWidth: dataWidth ~/ 2 - 1); + + storage = SparseMemoryStorage( + addrWidth: addrWidth, + dataWidth: dataWidth, + onInvalidRead: (addr, dataWidth) => + LogicValue.filled(dataWidth, LogicValue.zero), + ); sIntf.clk <= SimpleClockGenerator(10).clk; @@ -72,14 +92,16 @@ class Axi4BfmTest extends Test { readResponseDelay: withRandomRspDelays ? (request) => Test.random!.nextInt(5) : null, writeResponseDelay: - withRandomRspDelays ? (request) => Test.random!.nextInt(5) : null,, + withRandomRspDelays ? (request) => Test.random!.nextInt(5) : null, respondWithError: withErrors ? (request) => true : null, ); - final monitor = Axi4Monitor(sIntf: sIntf, + final monitor = Axi4Monitor( + sIntf: sIntf, rIntf: rIntf, wIntf: wIntf, - parent: this,); + parent: this, + ); Directory(outFolder).createSync(recursive: true); @@ -90,9 +112,7 @@ class Axi4BfmTest extends Test { outputFolder: outFolder, ); - Axi4ComplianceChecker(sIntf, - rIntf, - wIntf, parent: this); + Axi4ComplianceChecker(sIntf, rIntf, wIntf, parent: this); Simulator.registerEndOfSimulationAction(() async { await tracker.terminate(); @@ -100,9 +120,9 @@ class Axi4BfmTest extends Test { final jsonStr = File('$outFolder/axi4Tracker.tracker.json').readAsStringSync(); final jsonContents = json.decode(jsonStr); - // ignore: avoid_dynamic_calls - // TODO: fix?? - expect(jsonContents['records'].length, 2 * numTransfers); + + // TODO: check jsonContents... + // APB test checks the number of records based on the number of transactions Directory(outFolder).deleteSync(recursive: true); }); @@ -111,6 +131,7 @@ class Axi4BfmTest extends Test { } int numTransfersCompleted = 0; + final mandatoryTransWaitPeriod = 10; @override Future run(Phase phase) async { @@ -120,12 +141,6 @@ class Axi4BfmTest extends Test { await _resetFlow(); - final randomStrobes = List.generate( - numTransfers, (index) => LogicValue.ofInt(Test.random!.nextInt(16), 4)); - - final randomData = List.generate(numTransfers, - (index) => LogicValue.ofInt(Test.random!.nextInt(1 << 32), 32)); - LogicValue strobedData(LogicValue originalData, LogicValue strobe) => [ for (var i = 0; i < 4; i++) strobe[i].toBool() @@ -133,54 +148,80 @@ class Axi4BfmTest extends Test { : LogicValue.filled(8, LogicValue.zero) ].rswizzle(); + // to track what was written + final lens = []; + final sizes = []; + final data = >[]; + final strobes = >[]; + // normal writes for (var i = 0; i < numTransfers; i++) { + // generate a completely random access + final transLen = Test.random!.nextInt(1 << wIntf.lenWidth); + final transSize = + Test.random!.nextInt(1 << wIntf.sizeWidth) % (dataWidth ~/ 8); + final randomData = List.generate( + transLen + 1, + (index) => LogicValue.ofInt( + Test.random!.nextInt(1 << wIntf.dataWidth), wIntf.dataWidth)); + final randomStrobes = List.generate( + transLen + 1, + (index) => withStrobes + ? LogicValue.ofInt( + Test.random!.nextInt(1 << wIntf.strbWidth), wIntf.strbWidth) + : LogicValue.filled(wIntf.strbWidth, LogicValue.one)); + lens.add(transLen); + sizes.add(transSize); + data.add(randomData); + strobes.add(randomStrobes); + final wrPkt = Axi4WriteRequestPacket( - addr: LogicValue.ofInt(i, 32), - prot: LogicValue.ofInt(0, wIntf.protWidth), + addr: LogicValue.ofInt(i, 32), + prot: LogicValue.ofInt(0, wIntf.protWidth), // not supported data: randomData, id: LogicValue.ofInt(i, wIntf.idWidth), - len: LogicValue.ofInt(randomData.length, wIntf.lenWidth), - size: LogicValue.ofInt(2, wIntf.sizeWidth), // TODO - burst: LogicValue.ofInt(Axi4BurstField.incr.value, wIntf.burstWidth), - lock: LogicValue.ofInt(0, 1), - cache: LogicValue.ofInt(0, wIntf.cacheWidth), - qos: LogicValue.ofInt(0, wIntf.qosWidth), - region: LogicValue.ofInt(0, wIntf.regionWidth), - user: LogicValue.ofInt(0, wIntf.awuserWidth), - strobe: withStrobes ? randomStrobes : null, - wUser: LogicValue.ofInt(0, wIntf.wuserWidth), + len: LogicValue.ofInt(transLen, wIntf.lenWidth), + size: LogicValue.ofInt(transSize, wIntf.sizeWidth), + burst: LogicValue.ofInt( + Axi4BurstField.incr.value, wIntf.burstWidth), // fixed for now + lock: LogicValue.ofInt(0, 1), // not supported + cache: LogicValue.ofInt(0, wIntf.cacheWidth), // not supported + qos: LogicValue.ofInt(0, wIntf.qosWidth), // not supported + region: LogicValue.ofInt(0, wIntf.regionWidth), // not supported + user: LogicValue.ofInt(0, wIntf.awuserWidth), // not supported + strobe: randomStrobes, + wUser: LogicValue.ofInt(0, wIntf.wuserWidth), // not supported ); main.sequencer.add(wrPkt); numTransfersCompleted++; - // TODO: should we be waiting?? // Note that driver will already serialize the writes - + await sIntf.clk.waitCycles(mandatoryTransWaitPeriod); await sIntf.clk.waitCycles(interTxnDelay); } // normal reads that check data for (var i = 0; i < numTransfers; i++) { final rdPkt = Axi4ReadRequestPacket( - addr: LogicValue.ofInt(i, 32), - prot: LogicValue.ofInt(0, rIntf.protWidth), + addr: LogicValue.ofInt(i, 32), + prot: LogicValue.ofInt(0, rIntf.protWidth), // not supported id: LogicValue.ofInt(i, rIntf.idWidth), - len: LogicValue.ofInt(randomData.length, rIntf.lenWidth), - size: LogicValue.ofInt(2, rIntf.sizeWidth), // TODO - burst: LogicValue.ofInt(Axi4BurstField.incr.value, rIntf.burstWidth), - lock: LogicValue.ofInt(0, 1), - cache: LogicValue.ofInt(0, rIntf.cacheWidth), - qos: LogicValue.ofInt(0, rIntf.qosWidth), - region: LogicValue.ofInt(0, rIntf.regionWidth), - user: LogicValue.ofInt(0, rIntf.aruserWidth), + len: LogicValue.ofInt(lens[i], rIntf.lenWidth), + size: LogicValue.ofInt(sizes[i], rIntf.sizeWidth), + burst: LogicValue.ofInt( + Axi4BurstField.incr.value, rIntf.burstWidth), // fixed for now + lock: LogicValue.ofInt(0, 1), // not supported + cache: LogicValue.ofInt(0, rIntf.cacheWidth), // not supported + qos: LogicValue.ofInt(0, rIntf.qosWidth), // not supported + region: LogicValue.ofInt(0, rIntf.regionWidth), // not supported + user: LogicValue.ofInt(0, rIntf.aruserWidth), // not supported ); - - main.sequencer.add(rdPkt); - // TODO: should we be waiting?? + main.sequencer.add(rdPkt); + // Note that driver will already serialize the reads + await sIntf.clk.waitCycles(mandatoryTransWaitPeriod); await sIntf.clk.waitCycles(interTxnDelay); } @@ -209,14 +250,25 @@ void main() { await Simulator.reset(); }); - Future runTest(Axi4BfmTest axi4BfmTest, {bool dumpWaves = false}) async { - Simulator.setMaxSimTime(3000); + Future runTest(Axi4BfmTest axi4BfmTest, + {bool dumpWaves = false}) async { + Simulator.setMaxSimTime(30000); - // TODO: dump waves... + if (dumpWaves) { + final mod = Axi4Subordinate( + axi4BfmTest.sIntf, axi4BfmTest.rIntf, axi4BfmTest.wIntf); + await mod.build(); + WaveDumper(mod); + } await axi4BfmTest.start(); } + // FAILING + // we see 1st write request come in at subordinate + // we see all of the data come in including a "last" + // after that cycle, the test just hangs... (but we get to the end of the call loop in subordinate) + // SEEMS LIKE SOMETHING OTHER THAN SUBORDINATE IS BLOCKING OR INFINITE LOOPING test('simple writes and reads', () async { await runTest(Axi4BfmTest('simple')); }); diff --git a/test/axi4_test.dart b/test/axi4_test.dart new file mode 100644 index 000000000..0ca672644 --- /dev/null +++ b/test/axi4_test.dart @@ -0,0 +1,103 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// axi_test.dart +// Tests for the AXI4 interface. +// +// 2025 January +// Author: Josh Kimmel + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:test/test.dart'; + +class Axi4Subordinate extends Module { + Axi4Subordinate(Axi4SystemInterface sIntf, Axi4ReadInterface rIntf, + Axi4WriteInterface wIntf) { + sIntf = Axi4SystemInterface() + ..connectIO(this, sIntf, inputTags: {Axi4Direction.misc}); + + rIntf = Axi4ReadInterface.clone(rIntf) + ..connectIO(this, rIntf, + inputTags: {Axi4Direction.fromMain}, + outputTags: {Axi4Direction.fromSubordinate}); + + wIntf = Axi4WriteInterface.clone(wIntf) + ..connectIO(this, wIntf, + inputTags: {Axi4Direction.fromMain}, + outputTags: {Axi4Direction.fromSubordinate}); + } +} + +class Axi4Main extends Module { + Axi4Main(Axi4SystemInterface sIntf, Axi4ReadInterface rIntf, + Axi4WriteInterface wIntf) { + sIntf = Axi4SystemInterface() + ..connectIO(this, sIntf, inputTags: {Axi4Direction.misc}); + + rIntf = Axi4ReadInterface.clone(rIntf) + ..connectIO(this, rIntf, + inputTags: {Axi4Direction.fromSubordinate}, + outputTags: {Axi4Direction.fromMain}); + + wIntf = Axi4WriteInterface.clone(wIntf) + ..connectIO(this, wIntf, + inputTags: {Axi4Direction.fromSubordinate}, + outputTags: {Axi4Direction.fromMain}); + } +} + +class Axi4Pair extends Module { + Axi4Pair(Logic clk, Logic reset) { + clk = addInput('clk', clk); + reset = addInput('reset', reset); + + final sIntf = Axi4SystemInterface(); + sIntf.clk <= clk; + sIntf.resetN <= ~reset; + + final rIntf = Axi4ReadInterface(); + final wIntf = Axi4WriteInterface(); + + Axi4Main(sIntf, rIntf, wIntf); + Axi4Subordinate(sIntf, rIntf, wIntf); + } +} + +void main() { + test('connect axi4 modules', () async { + final axi4Pair = Axi4Pair(Logic(), Logic()); + await axi4Pair.build(); + }); + + test('axi4 optional ports null', () async { + final rIntf = Axi4ReadInterface( + idWidth: 0, + lenWidth: 0, + aruserWidth: 0, + ruserWidth: 0, + useLast: false, + useLock: false); + expect(rIntf.arId, isNull); + expect(rIntf.arLen, isNull); + expect(rIntf.arLock, isNull); + expect(rIntf.arUser, isNull); + expect(rIntf.rId, isNull); + expect(rIntf.rLast, isNull); + expect(rIntf.rUser, isNull); + + final wIntf = Axi4WriteInterface( + idWidth: 0, + lenWidth: 0, + awuserWidth: 0, + wuserWidth: 0, + buserWidth: 0, + useLock: false); + expect(wIntf.awId, isNull); + expect(wIntf.awLen, isNull); + expect(wIntf.awLock, isNull); + expect(wIntf.awUser, isNull); + expect(wIntf.wUser, isNull); + expect(wIntf.bUser, isNull); + }); +} From 83ca3a559d81b01c365692d8a471f51a360d9819 Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Tue, 28 Jan 2025 15:55:42 -0800 Subject: [PATCH 06/16] Initial tests are passing. --- lib/src/interfaces/axi4.dart | 7 ++-- lib/src/models/axi4_bfm/axi4_subordinate.dart | 39 ++++++++----------- test/axi4_bfm_test.dart | 30 +++++++------- 3 files changed, 33 insertions(+), 43 deletions(-) diff --git a/lib/src/interfaces/axi4.dart b/lib/src/interfaces/axi4.dart index 741630a7e..b04cdf43c 100644 --- a/lib/src/interfaces/axi4.dart +++ b/lib/src/interfaces/axi4.dart @@ -481,7 +481,7 @@ class Axi4WriteInterface extends Interface { if (awuserWidth > 0) Port('AWUSER', awuserWidth), Port('AWVALID'), Port('WDATA', dataWidth), - if (strbWidth > 0) Port('WSTRB', strbWidth), + Port('WSTRB', strbWidth), Port('WLAST'), if (wuserWidth > 0) Port('WUSER', wuserWidth), Port('WVALID'), @@ -550,7 +550,7 @@ class Axi4WriteInterface extends Interface { } } -/// Helper to enumerate the encodings of the xRESP signal. +/// Helper to enumerate the encodings of the xBURST signal. enum Axi4BurstField { /// Address remains constants. fixed(0x0), @@ -558,7 +558,8 @@ enum Axi4BurstField { /// Address increments by the transfer size. incr(0x1), - /// Similar to incr, but wraps around to a lower boundary when a boundary is reached. + /// Similar to incr, but wraps around to a lower boundary point + /// when an upper boundary point is reached. wrap(0x2); /// Underlying value. diff --git a/lib/src/models/axi4_bfm/axi4_subordinate.dart b/lib/src/models/axi4_bfm/axi4_subordinate.dart index 8ee4d9dd6..aaf4aad1c 100644 --- a/lib/src/models/axi4_bfm/axi4_subordinate.dart +++ b/lib/src/models/axi4_bfm/axi4_subordinate.dart @@ -108,14 +108,12 @@ class Axi4SubordinateAgent extends Agent { while (!Simulator.simulationHasEnded) { await sIntf.clk.nextNegedge; - // WE EVENTUALLY GET STUCK WAITING FOR THIS NEGEDGE!!! _driveReadys(); _respondRead(); _respondWrite(); _receiveRead(); _captureWriteData(); _receiveWrite(); - print('BLAH2'); } } @@ -140,7 +138,7 @@ class Axi4SubordinateAgent extends Agent { void _receiveRead() { // work to do if main is indicating a valid read that we are ready to handle if (rIntf.arValid.value.toBool() && rIntf.arReady.value.toBool()) { - print('Received read request at time ${Simulator.time}'); + logger.info('Received read request.'); final packet = Axi4ReadRequestPacket( addr: rIntf.arAddr.value, @@ -212,10 +210,6 @@ class Axi4SubordinateAgent extends Agent { final last = _dataReadResponseIndex == _dataReadResponseDataQueue[0].length - 1; - print('HELLO JOSH'); - print(_dataReadResponseIndex); - print(_dataReadResponseDataQueue[0].length); - // TODO: how to deal with delays?? // if (readResponseDelay != null) { // final delayCycles = readResponseDelay!(packet); @@ -240,11 +234,11 @@ class Axi4SubordinateAgent extends Agent { _dataReadResponseMetadataQueue.removeAt(0); _dataReadResponseDataQueue.removeAt(0); - print('Finished sending read response at time ${Simulator.time}'); + logger.info('Finished sending read response.'); } else { // move to the next chunk of data _dataReadResponseIndex++; - print('Still send the read response as of time ${Simulator.time}'); + logger.info('Still sending the read response.'); } } else { rIntf.rValid.put(false); @@ -253,9 +247,9 @@ class Axi4SubordinateAgent extends Agent { // handle an incoming write request void _receiveWrite() { - // work to do if main is indicating a valid write that we are ready to handle + // work to do if main is indicating a valid + ready write if (wIntf.awValid.value.toBool() && wIntf.awReady.value.toBool()) { - print('Received write request at time ${Simulator.time}'); + logger.info('Received write request.'); final packet = Axi4WriteRequestPacket( addr: wIntf.awAddr.value, prot: wIntf.awProt.value, @@ -283,7 +277,6 @@ class Axi4SubordinateAgent extends Agent { // queue up the packet for further processing _writeMetadataQueue.add(packet); - print('JOSH1'); } } @@ -298,19 +291,17 @@ class Axi4SubordinateAgent extends Agent { final packet = _writeMetadataQueue[0]; packet.data.add(wIntf.wData.value); packet.strobe.add(wIntf.wStrb.value); + logger.info('Captured write data.'); if (wIntf.wLast.value.toBool()) { - print('JOSH3'); + logger.info('Finished capturing write data.'); _writeReadyToOccur = true; } - print('JOSH2'); } } void _respondWrite() { // only work to do if we have received all of the data for our write request - print('JOSH5'); if (_writeReadyToOccur) { - print('JOSH4'); // only respond if the main is ready if (wIntf.bReady.value.toBool()) { final packet = _writeMetadataQueue[0]; @@ -358,12 +349,14 @@ class Axi4SubordinateAgent extends Agent { } for (var i = 0; i < packet.data.length; i++) { - final strobedData = _strobeData( - storage.readData(addrToWrite), - packet.data[i], - packet.strobe[i] ?? - LogicValue.filled(packet.data[i].width, LogicValue.one)); - storage.writeData(addrToWrite, strobedData.getRange(0, dSize)); + final rdData = storage.readData(addrToWrite); + final strobedData = + _strobeData(rdData, packet.data[i], packet.strobe[i]); + final wrData = (dSize < strobedData.width) + ? [strobedData.getRange(0, dSize), rdData.getRange(dSize)] + .rswizzle() + : strobedData; + storage.writeData(addrToWrite, wrData); addrToWrite = addrToWrite + increment; } } @@ -372,7 +365,7 @@ class Axi4SubordinateAgent extends Agent { _writeMetadataQueue.removeAt(0); _writeReadyToOccur = false; - print('Sent write response at time ${Simulator.time}'); + logger.info('Sent write response.'); } } else { wIntf.bValid.put(false); diff --git a/test/axi4_bfm_test.dart b/test/axi4_bfm_test.dart index 526ba4132..c0feefc04 100644 --- a/test/axi4_bfm_test.dart +++ b/test/axi4_bfm_test.dart @@ -117,14 +117,14 @@ class Axi4BfmTest extends Test { Simulator.registerEndOfSimulationAction(() async { await tracker.terminate(); - final jsonStr = - File('$outFolder/axi4Tracker.tracker.json').readAsStringSync(); - final jsonContents = json.decode(jsonStr); + // final jsonStr = + // File('$outFolder/axi4Tracker.tracker.json').readAsStringSync(); + // final jsonContents = json.decode(jsonStr); - // TODO: check jsonContents... - // APB test checks the number of records based on the number of transactions + // // TODO: check jsonContents... + // // APB test checks the number of records based on the number of transactions - Directory(outFolder).deleteSync(recursive: true); + // Directory(outFolder).deleteSync(recursive: true); }); monitor.stream.listen(tracker.record); @@ -141,12 +141,12 @@ class Axi4BfmTest extends Test { await _resetFlow(); - LogicValue strobedData(LogicValue originalData, LogicValue strobe) => [ - for (var i = 0; i < 4; i++) - strobe[i].toBool() - ? originalData.getRange(i * 8, i * 8 + 8) - : LogicValue.filled(8, LogicValue.zero) - ].rswizzle(); + // LogicValue strobedData(LogicValue originalData, LogicValue strobe) => [ + // for (var i = 0; i < 4; i++) + // strobe[i].toBool() + // ? originalData.getRange(i * 8, i * 8 + 8) + // : LogicValue.filled(8, LogicValue.zero) + // ].rswizzle(); // to track what was written final lens = []; @@ -219,6 +219,7 @@ class Axi4BfmTest extends Test { ); main.sequencer.add(rdPkt); + numTransfersCompleted++; // Note that driver will already serialize the reads await sIntf.clk.waitCycles(mandatoryTransWaitPeriod); @@ -264,11 +265,6 @@ void main() { await axi4BfmTest.start(); } - // FAILING - // we see 1st write request come in at subordinate - // we see all of the data come in including a "last" - // after that cycle, the test just hangs... (but we get to the end of the call loop in subordinate) - // SEEMS LIKE SOMETHING OTHER THAN SUBORDINATE IS BLOCKING OR INFINITE LOOPING test('simple writes and reads', () async { await runTest(Axi4BfmTest('simple')); }); From d4b5a695dfa668864e958def768f22f3e8612c21 Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Tue, 28 Jan 2025 15:59:58 -0800 Subject: [PATCH 07/16] Fixing monitor to tracker enablement. --- lib/src/models/axi4_bfm/axi4_monitor.dart | 20 +++++++++++--------- test/axi4_bfm_test.dart | 1 - 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/src/models/axi4_bfm/axi4_monitor.dart b/lib/src/models/axi4_bfm/axi4_monitor.dart index b8ad93b21..be3d91fab 100644 --- a/lib/src/models/axi4_bfm/axi4_monitor.dart +++ b/lib/src/models/axi4_bfm/axi4_monitor.dart @@ -89,11 +89,12 @@ class Axi4Monitor extends Monitor { if (targIdx >= 0 && _pendingReadRequests.length > targIdx) { _pendingReadResponseData[targIdx].add(rIntf.rData.previousValue!); if (rIntf.rLast?.value.toBool() ?? true) { - _pendingReadRequests[targIdx].complete( - data: _pendingReadResponseData[targIdx], - resp: rIntf.rResp?.previousValue, - user: rIntf.rUser?.previousValue, - ); + add(_pendingReadRequests[targIdx] + ..complete( + data: _pendingReadResponseData[targIdx], + resp: rIntf.rResp?.previousValue, + user: rIntf.rUser?.previousValue, + )); _pendingReadRequests.removeAt(targIdx); _pendingReadResponseData.removeAt(targIdx); } @@ -146,10 +147,11 @@ class Axi4Monitor extends Monitor { element.id!.toInt() == wIntf.bId!.previousValue!.toInt()); } if (targIdx >= 0 && _pendingWriteRequests.length > targIdx) { - _pendingWriteRequests[targIdx].complete( - resp: wIntf.bResp?.previousValue, - user: wIntf.bUser?.previousValue, - ); + add(_pendingWriteRequests[targIdx] + ..complete( + resp: wIntf.bResp?.previousValue, + user: wIntf.bUser?.previousValue, + )); _pendingWriteRequests.removeAt(targIdx); } } diff --git a/test/axi4_bfm_test.dart b/test/axi4_bfm_test.dart index c0feefc04..e3225f17a 100644 --- a/test/axi4_bfm_test.dart +++ b/test/axi4_bfm_test.dart @@ -8,7 +8,6 @@ // Author: Josh Kimmel import 'dart:async'; -import 'dart:convert'; import 'dart:io'; import 'package:rohd/rohd.dart'; From 2d9f21070bb95a1766567958247b2ecef64c894b Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Mon, 3 Feb 2025 14:42:21 -0800 Subject: [PATCH 08/16] small tweaks from feedback and linting. --- lib/src/interfaces/axi4.dart | 22 +++++++++++++--------- lib/src/interfaces/interfaces.dart | 2 +- lib/src/models/axi4_bfm/axi4_main.dart | 3 ++- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/src/interfaces/axi4.dart b/lib/src/interfaces/axi4.dart index b04cdf43c..06449c43c 100644 --- a/lib/src/interfaces/axi4.dart +++ b/lib/src/interfaces/axi4.dart @@ -1,7 +1,7 @@ // Copyright (C) 2024-2025 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // -// axi.dart +// axi4.dart // Definitions for the AXI interface. // // 2025 January @@ -10,7 +10,8 @@ import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/src/exceptions.dart'; -/// A grouping of signals on the [Axi4ReadInterface] interface based on direction. +/// A grouping of signals on the [Axi4ReadInterface] and [Axi4WriteInterface] +/// interfaces based on direction. enum Axi4Direction { /// Miscellaneous system-level signals, common inputs to both sides. misc, @@ -26,12 +27,14 @@ enum Axi4Direction { class Axi4SystemInterface extends Interface { /// Clock for the interface. /// - /// Global clock signals. Synchronous signals are sampled on the rising edge of the global clock. + /// Global clock signals. Synchronous signals are sampled + /// on the rising edge of the global clock. Logic get clk => port('ACLK'); /// Reset signal (active LOW). /// - /// Global reset signal. This signal is active-LOW, synchronous but can be asserted asynchronously. + /// Global reset signal. This signal is active-LOW, synchronous + /// but can be asserted asynchronously. Logic get resetN => port('ARESETn'); /// Construct a new instance of an AXI4 interface. @@ -112,7 +115,8 @@ class Axi4ReadInterface extends Interface { /// Width is equal to [sizeWidth]. Logic? get arSize => tryPort('ARSIZE'); - /// Burst type, indicates how address changes between each transfer in a read transaction. + /// Burst type, indicates how address changes between + /// each transfer in a read transaction. /// /// Width is equal to [burstWidth]. Logic? get arBurst => tryPort('ARBURST'); @@ -127,7 +131,7 @@ class Axi4ReadInterface extends Interface { /// Width is equal to [cacheWidth]. Logic? get arCache => tryPort('ARCACHE'); - /// Protection attributes of a read transaction: privilege, security level, and access type. + /// Protection attributes of a read transaction. /// /// Width is equal to [protWidth]. Logic get arProt => port('ARPROT'); @@ -352,7 +356,7 @@ class Axi4WriteInterface extends Interface { /// Width is equal to [sizeWidth]. Logic? get awSize => tryPort('AWSIZE'); - /// Burst type, indicates how address changes between each transfer in a write transaction. + /// Burst type, indicates how address changes between each transfer. /// /// Width is equal to [burstWidth]. Logic? get awBurst => tryPort('AWBURST'); @@ -362,12 +366,12 @@ class Axi4WriteInterface extends Interface { /// Width is always 1. Logic? get awLock => tryPort('AWLOCK'); - /// Indicates how a write transaction is required to progress through a system. + /// Indicates how a write transaction is required to progress in a system. /// /// Width is equal to [cacheWidth]. Logic? get awCache => tryPort('AWCACHE'); - /// Protection attributes of a write transaction: privilege, security level, and access type. + /// Protection attributes of a write transaction. /// /// Width is equal to [protWidth]. Logic get awProt => port('AWPROT'); diff --git a/lib/src/interfaces/interfaces.dart b/lib/src/interfaces/interfaces.dart index 3d397f8f7..112f07e19 100644 --- a/lib/src/interfaces/interfaces.dart +++ b/lib/src/interfaces/interfaces.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2023-2024 Intel Corporation +// Copyright (C) 2024-2025 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause export 'apb.dart'; diff --git a/lib/src/models/axi4_bfm/axi4_main.dart b/lib/src/models/axi4_bfm/axi4_main.dart index a99f00dcd..988535d29 100644 --- a/lib/src/models/axi4_bfm/axi4_main.dart +++ b/lib/src/models/axi4_bfm/axi4_main.dart @@ -11,7 +11,8 @@ import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; import 'package:rohd_vf/rohd_vf.dart'; -/// An agent for sending requests on [Axi4ReadInterface]s and [Axi4WriteInterface]s. +/// An agent for sending requests on +/// the [Axi4ReadInterface] and [Axi4WriteInterface]. /// /// Driven read packets will update the returned data into the same packet. class Axi4MainAgent extends Agent { From ca671b9492fb584f5899c5bdc436dfd460fc5b86 Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Tue, 4 Feb 2025 17:31:37 -0800 Subject: [PATCH 09/16] Adding export of axi4 bfm. --- lib/src/models/models.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart index 617bf5ab5..649c6444f 100644 --- a/lib/src/models/models.dart +++ b/lib/src/models/models.dart @@ -1,7 +1,8 @@ -// Copyright (C) 2023-2024 Intel Corporation +// Copyright (C) 2024-2025 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause export 'apb_bfm/apb_bfm.dart'; +export 'axi4_bfm/axi4_bfm.dart'; export 'memory_model.dart'; export 'ready_valid_bfm/ready_valid_bfm.dart'; export 'sparse_memory_storage.dart'; From f74a6584230849969f8963c0a522e6b086b1ca2d Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Wed, 5 Feb 2025 15:07:30 -0800 Subject: [PATCH 10/16] WIP: Enhancing to allow arbitrary numbers of read and write interfaces on agents. Tests are failing but build. --- .../axi4_bfm/axi4_compliance_checker.dart | 174 ++++++++++- lib/src/models/axi4_bfm/axi4_main.dart | 79 +++-- lib/src/models/axi4_bfm/axi4_main_driver.dart | 294 ++++++++++++++++-- lib/src/models/axi4_bfm/axi4_monitor.dart | 196 +++++++++++- lib/src/models/axi4_bfm/axi4_packet.dart | 1 - lib/src/models/axi4_bfm/axi4_subordinate.dart | 293 +++++++++-------- lib/src/models/axi4_bfm/axi4_tracker.dart | 5 - test/axi4_bfm_test.dart | 112 ++++--- test/axi4_test.dart | 61 ++-- 9 files changed, 934 insertions(+), 281 deletions(-) diff --git a/lib/src/models/axi4_bfm/axi4_compliance_checker.dart b/lib/src/models/axi4_bfm/axi4_compliance_checker.dart index 5ce543015..d6d819ecc 100644 --- a/lib/src/models/axi4_bfm/axi4_compliance_checker.dart +++ b/lib/src/models/axi4_bfm/axi4_compliance_checker.dart @@ -12,26 +12,137 @@ import 'dart:async'; import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:rohd_vf/rohd_vf.dart'; +// /// A checker for some of the rules defined in the AXI4 interface specification. +// /// +// /// This does not necessarily cover all rules defined in the spec. +// class Axi4ComplianceChecker extends Component { +// /// AXI4 System Interface. +// final Axi4SystemInterface sIntf; + +// /// AXI4 Read Interface. +// final Axi4ReadInterface rIntf; + +// /// AXI4 Write Interface. +// final Axi4WriteInterface wIntf; + +// /// Creates a new compliance checker for AXI4. +// Axi4ComplianceChecker( +// this.sIntf, +// this.rIntf, +// this.wIntf, { +// required Component parent, +// String name = 'axi4ComplianceChecker', +// }) : super(name, parent); + +// @override +// Future run(Phase phase) async { +// unawaited(super.run(phase)); + +// // wait for reset to complete +// await sIntf.resetN.nextPosedge; + +// // checks to run +// // READ REQUESTS +// // number of flits returned matches ARLEN if no error +// // if RLAST is present, asserted on the final flit only +// // if RID is present, every read response should match +// // a pending request ARID +// // WRITE REQUESTS +// // number of flits sent matches AWLEN +// // WLAST is asserted on the final flit only +// // if BID is present, every write response should match +// // a pending request AWID + +// final rLastPresent = rIntf.rLast != null; + +// final readReqMap = >{}; +// final writeReqMap = >{}; +// var lastWriteReqId = -1; + +// sIntf.clk.posedge.listen((event) { +// // capture read requests for counting +// if (rIntf.arValid.previousValue!.isValid && +// rIntf.arValid.previousValue!.toBool()) { +// final id = rIntf.arId?.previousValue?.toInt() ?? 0; +// final len = (rIntf.arLen?.previousValue?.toInt() ?? 0) + 1; +// readReqMap[id] = [len, 0]; +// } + +// // track read response flits +// if (rIntf.rValid.previousValue!.isValid && +// rIntf.rValid.previousValue!.toBool()) { +// final id = rIntf.rId?.previousValue?.toInt() ?? 0; +// if (!readReqMap.containsKey(id)) { +// logger.severe( +// 'Cannot match a read response to any pending read request. ' +// 'ID captured by the response was $id.'); +// } + +// readReqMap[id]![1] = readReqMap[id]![1] + 1; +// final len = readReqMap[id]![0]; +// final currCount = readReqMap[id]![1]; +// if (currCount > len) { +// logger.severe( +// 'Received more read response data flits than indicated by the ' +// 'request with ID $id ARLEN. Expected $len but got $currCount'); +// } else if (currCount == len && +// rLastPresent && +// !rIntf.rLast!.previousValue!.toBool()) { +// logger.severe('Received the final flit in the read response data per ' +// 'the request with ID $id ARLEN but RLAST is not asserted.'); +// } +// } + +// // track write requests +// if (wIntf.awValid.previousValue!.isValid && +// wIntf.awValid.previousValue!.toBool()) { +// final id = wIntf.awId?.previousValue?.toInt() ?? 0; +// final len = (wIntf.awLen?.previousValue?.toInt() ?? 0) + 1; +// writeReqMap[id] = [len, 0]; +// lastWriteReqId = id; +// } + +// // track write data flits +// if (wIntf.wValid.previousValue!.isValid && +// wIntf.wValid.previousValue!.toBool()) { +// final id = lastWriteReqId; +// if (!writeReqMap.containsKey(id)) { +// logger.severe('There is no pending write request ' +// 'to associate with valid write data.'); +// } + +// writeReqMap[id]![1] = writeReqMap[id]![1] + 1; +// final len = writeReqMap[id]![0]; +// final currCount = writeReqMap[id]![1]; +// if (currCount > len) { +// logger.severe( +// 'Sent more write data flits than indicated by the request ' +// 'with ID $id AWLEN. Expected $len but sent $currCount'); +// } else if (currCount == len && !wIntf.wLast.previousValue!.toBool()) { +// logger.severe('Sent the final flit in the write data per the request ' +// 'with ID $id AWLEN but WLAST is not asserted.'); +// } +// } +// }); +// } +// } + /// A checker for some of the rules defined in the AXI4 interface specification. /// /// This does not necessarily cover all rules defined in the spec. -class Axi4ComplianceChecker extends Component { +class Axi4ReadComplianceChecker extends Component { /// AXI4 System Interface. final Axi4SystemInterface sIntf; /// AXI4 Read Interface. final Axi4ReadInterface rIntf; - /// AXI4 Write Interface. - final Axi4WriteInterface wIntf; - /// Creates a new compliance checker for AXI4. - Axi4ComplianceChecker( + Axi4ReadComplianceChecker( this.sIntf, - this.rIntf, - this.wIntf, { + this.rIntf, { required Component parent, - String name = 'axi4ComplianceChecker', + String name = 'axi4ReadComplianceChecker', }) : super(name, parent); @override @@ -47,17 +158,9 @@ class Axi4ComplianceChecker extends Component { // if RLAST is present, asserted on the final flit only // if RID is present, every read response should match // a pending request ARID - // WRITE REQUESTS - // number of flits sent matches AWLEN - // WLAST is asserted on the final flit only - // if BID is present, every write response should match - // a pending request AWID final rLastPresent = rIntf.rLast != null; - final readReqMap = >{}; - final writeReqMap = >{}; - var lastWriteReqId = -1; sIntf.clk.posedge.listen((event) { // capture read requests for counting @@ -92,7 +195,46 @@ class Axi4ComplianceChecker extends Component { 'the request with ID $id ARLEN but RLAST is not asserted.'); } } + }); + } +} +/// A checker for some of the rules defined in the AXI4 interface specification. +/// +/// This does not necessarily cover all rules defined in the spec. +class Axi4WriteComplianceChecker extends Component { + /// AXI4 System Interface. + final Axi4SystemInterface sIntf; + + /// AXI4 Write Interface. + final Axi4WriteInterface wIntf; + + /// Creates a new compliance checker for AXI4. + Axi4WriteComplianceChecker( + this.sIntf, + this.wIntf, { + required Component parent, + String name = 'axi4WriteComplianceChecker', + }) : super(name, parent); + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + // wait for reset to complete + await sIntf.resetN.nextPosedge; + + // checks to run + // WRITE REQUESTS + // number of flits sent matches AWLEN + // WLAST is asserted on the final flit only + // if BID is present, every write response should match + // a pending request AWID + + final writeReqMap = >{}; + var lastWriteReqId = -1; + + sIntf.clk.posedge.listen((event) { // track write requests if (wIntf.awValid.previousValue!.isValid && wIntf.awValid.previousValue!.toBool()) { diff --git a/lib/src/models/axi4_bfm/axi4_main.dart b/lib/src/models/axi4_bfm/axi4_main.dart index 988535d29..b7db6ce9d 100644 --- a/lib/src/models/axi4_bfm/axi4_main.dart +++ b/lib/src/models/axi4_bfm/axi4_main.dart @@ -8,11 +8,10 @@ // Author: Josh Kimmel import 'package:rohd_hcl/rohd_hcl.dart'; -import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; import 'package:rohd_vf/rohd_vf.dart'; /// An agent for sending requests on -/// the [Axi4ReadInterface] and [Axi4WriteInterface]. +/// [Axi4ReadInterface]s and [Axi4WriteInterface]s. /// /// Driven read packets will update the returned data into the same packet. class Axi4MainAgent extends Agent { @@ -20,16 +19,28 @@ class Axi4MainAgent extends Agent { final Axi4SystemInterface sIntf; /// AXI4 Read Interface. - final Axi4ReadInterface rIntf; + final List rIntfs; /// AXI4 Write Interface. - final Axi4WriteInterface wIntf; + final List wIntfs; - /// The sequencer where requests should be sent. - late final Sequencer sequencer; + /// The sequencers where read requests should be sent. + late final List> rdSequencers; - /// The driver that sends the requests over the interface. - late final Axi4MainDriver driver; + /// The sequencers where write requests should be sent. + late final List> wrSequencers; + + /// The drivers that send read requests over the interface. + late final List rdDrivers; + + /// The drivers that send write requests over the interface. + late final List wrDrivers; + + /// Monitoring of read requests over the interface. + late final List rdMonitors; + + /// Monitoring of write requests over the interface. + late final List wrMonitors; /// The number of cycles before timing out if no transactions can be sent. final int timeoutCycles; @@ -41,23 +52,49 @@ class Axi4MainAgent extends Agent { /// Constructs a new [Axi4MainAgent]. Axi4MainAgent({ required this.sIntf, - required this.rIntf, - required this.wIntf, + required this.rIntfs, + required this.wIntfs, required Component parent, String name = 'axiMainAgent', this.timeoutCycles = 500, this.dropDelayCycles = 30, }) : super(name, parent) { - sequencer = Sequencer('sequencer', this); - - driver = Axi4MainDriver( - parent: this, - sIntf: sIntf, - rIntf: rIntf, - wIntf: wIntf, - sequencer: sequencer, - timeoutCycles: timeoutCycles, - dropDelayCycles: dropDelayCycles, - ); + for (var i = 0; i < rIntfs.length; i++) { + rdSequencers + .add(Sequencer('axiRdSequencer$i', this)); + rdDrivers.add(Axi4ReadMainDriver( + parent: this, + sIntf: sIntf, + rIntf: rIntfs[i], + sequencer: rdSequencers[i], + timeoutCycles: timeoutCycles, + dropDelayCycles: dropDelayCycles, + name: 'axiRdDriver$i', + )); + rdMonitors.add(Axi4ReadMonitor( + sIntf: sIntf, + rIntf: rIntfs[i], + parent: parent, + name: 'axiRdMonitor$i')); + } + + for (var i = 0; i < wIntfs.length; i++) { + wrSequencers + .add(Sequencer('axiWrSequencer$i', this)); + wrDrivers.add(Axi4WriteMainDriver( + parent: this, + sIntf: sIntf, + wIntf: wIntfs[i], + sequencer: wrSequencers[i], + timeoutCycles: timeoutCycles, + dropDelayCycles: dropDelayCycles, + name: 'axiWrDriver$i', + )); + wrMonitors.add(Axi4WriteMonitor( + sIntf: sIntf, + wIntf: wIntfs[i], + parent: parent, + name: 'axiWrMonitor$i')); + } } } diff --git a/lib/src/models/axi4_bfm/axi4_main_driver.dart b/lib/src/models/axi4_bfm/axi4_main_driver.dart index 5f4300220..6c413db0b 100644 --- a/lib/src/models/axi4_bfm/axi4_main_driver.dart +++ b/lib/src/models/axi4_bfm/axi4_main_driver.dart @@ -14,29 +14,208 @@ import 'package:rohd_hcl/src/interfaces/interfaces.dart'; import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; import 'package:rohd_vf/rohd_vf.dart'; -/// A driver for the [Axi4ReadInterface] and [Axi4WriteInterface] interfaces. +// /// A driver for the [Axi4ReadInterface] and [Axi4WriteInterface] interfaces. +// /// +// /// Driving from the perspective of the Main agent. +// class Axi4MainDriver extends PendingClockedDriver { +// /// AXI4 System Interface. +// final Axi4SystemInterface sIntf; + +// /// AXI4 Read Interface. +// final Axi4ReadInterface rIntf; + +// /// AXI4 Write Interface. +// final Axi4WriteInterface wIntf; + +// /// Creates a new [Axi4MainDriver]. +// Axi4MainDriver({ +// required Component parent, +// required this.sIntf, +// required this.rIntf, +// required this.wIntf, +// required super.sequencer, +// super.timeoutCycles = 500, +// super.dropDelayCycles = 30, +// String name = 'axi4MainDriver', +// }) : super( +// name, +// parent, +// clk: sIntf.clk, +// ); + +// @override +// Future run(Phase phase) async { +// unawaited(super.run(phase)); + +// Simulator.injectAction(() { +// rIntf.arValid.put(0); +// rIntf.arId?.put(0); +// rIntf.arAddr.put(0); +// rIntf.arLen?.put(0); +// rIntf.arSize?.put(0); +// rIntf.arBurst?.put(0); +// rIntf.arLock?.put(0); +// rIntf.arCache?.put(0); +// rIntf.arProt.put(0); +// rIntf.arQos?.put(0); +// rIntf.arRegion?.put(0); +// rIntf.arUser?.put(0); +// rIntf.rReady.put(0); +// wIntf.awValid.put(0); +// wIntf.awId?.put(0); +// wIntf.awAddr.put(0); +// wIntf.awLen?.put(0); +// wIntf.awSize?.put(0); +// wIntf.awBurst?.put(0); +// wIntf.awLock?.put(0); +// wIntf.awCache?.put(0); +// wIntf.awProt.put(0); +// wIntf.awQos?.put(0); +// wIntf.awRegion?.put(0); +// wIntf.awUser?.put(0); +// wIntf.wValid.put(0); +// wIntf.wData.put(0); +// wIntf.wStrb.put(0); +// wIntf.wLast.put(0); +// wIntf.bReady.put(0); +// }); + +// // wait for reset to complete before driving anything +// await sIntf.resetN.nextPosedge; + +// while (!Simulator.simulationHasEnded) { +// if (pendingSeqItems.isNotEmpty) { +// await _drivePacket(pendingSeqItems.removeFirst()); +// } else { +// await sIntf.clk.nextPosedge; +// } +// } +// } + +// /// Drives a packet onto the interface. +// Future _drivePacket(Axi4RequestPacket packet) async { +// print('Driving packet at time ${Simulator.time}'); +// if (packet is Axi4ReadRequestPacket) { +// await _driveReadPacket(packet); +// } else if (packet is Axi4WriteRequestPacket) { +// await _driveWritePacket(packet); +// } else { +// await sIntf.clk.nextPosedge; +// } +// } + +// // TODO: need a more robust way of driving the "ready" signals... +// // RREADY for read data responses +// // BREADY for write responses +// // specifically, when should they toggle on/off? +// // ON => either always or when the associated request is driven? +// // OFF => either never or when there are no more outstanding requests of the given type? +// // should we enable the ability to backpressure?? + +// Future _driveReadPacket(Axi4ReadRequestPacket packet) async { +// await sIntf.clk.nextPosedge; +// Simulator.injectAction(() { +// rIntf.arValid.put(1); +// rIntf.arId?.put(packet.id); +// rIntf.arAddr.put(packet.addr); +// rIntf.arLen?.put(packet.len); +// rIntf.arSize?.put(packet.size); +// rIntf.arBurst?.put(packet.burst); +// rIntf.arLock?.put(packet.lock); +// rIntf.arCache?.put(packet.cache); +// rIntf.arProt.put(packet.prot); +// rIntf.arQos?.put(packet.qos); +// rIntf.arRegion?.put(packet.region); +// rIntf.arUser?.put(packet.user); +// rIntf.rReady.put(1); +// }); + +// // need to hold the request until receiver is ready +// await sIntf.clk.nextPosedge; +// if (!rIntf.arReady.value.toBool()) { +// await rIntf.arReady.nextPosedge; +// } + +// // now we can release the request +// Simulator.injectAction(() { +// rIntf.arValid.put(0); +// }); + +// // TODO: wait for the response to complete?? +// } + +// Future _driveWritePacket(Axi4WriteRequestPacket packet) async { +// await sIntf.clk.nextPosedge; +// Simulator.injectAction(() { +// wIntf.awValid.put(1); +// wIntf.awId?.put(packet.id); +// wIntf.awAddr.put(packet.addr); +// wIntf.awLen?.put(packet.len); +// wIntf.awSize?.put(packet.size); +// wIntf.awBurst?.put(packet.burst); +// wIntf.awLock?.put(packet.lock); +// wIntf.awCache?.put(packet.cache); +// wIntf.awProt.put(packet.prot); +// wIntf.awQos?.put(packet.qos); +// wIntf.awRegion?.put(packet.region); +// wIntf.awUser?.put(packet.user); +// wIntf.bReady.put(1); +// }); + +// // need to hold the request until receiver is ready +// await sIntf.clk.nextPosedge; +// if (!wIntf.awReady.value.toBool()) { +// await wIntf.awReady.nextPosedge; +// } + +// // now we can release the request +// Simulator.injectAction(() { +// wIntf.awValid.put(0); +// }); + +// // next send the data for the write +// for (var i = 0; i < packet.data.length; i++) { +// if (!wIntf.wReady.value.toBool()) { +// await wIntf.wReady.nextPosedge; +// } +// Simulator.injectAction(() { +// wIntf.wValid.put(1); +// wIntf.wData.put(packet.data[i]); +// wIntf.wStrb.put(packet.strobe[i]); +// wIntf.wLast.put(i == packet.data.length - 1 ? 1 : 0); +// wIntf.wUser?.put(packet.wUser); +// }); +// await sIntf.clk.nextPosedge; +// } + +// // now we can stop the write data +// Simulator.injectAction(() { +// wIntf.wValid.put(0); +// }); + +// // TODO: wait for the response to complete?? +// } +// } + +/// A driver for the [Axi4ReadInterface] interface. /// /// Driving from the perspective of the Main agent. -class Axi4MainDriver extends PendingClockedDriver { +class Axi4ReadMainDriver extends PendingClockedDriver { /// AXI4 System Interface. final Axi4SystemInterface sIntf; /// AXI4 Read Interface. final Axi4ReadInterface rIntf; - /// AXI4 Write Interface. - final Axi4WriteInterface wIntf; - /// Creates a new [Axi4MainDriver]. - Axi4MainDriver({ + Axi4ReadMainDriver({ required Component parent, required this.sIntf, required this.rIntf, - required this.wIntf, required super.sequencer, super.timeoutCycles = 500, super.dropDelayCycles = 30, - String name = 'axi4MainDriver', + String name = 'axi4ReadMainDriver', }) : super( name, parent, @@ -61,23 +240,6 @@ class Axi4MainDriver extends PendingClockedDriver { rIntf.arRegion?.put(0); rIntf.arUser?.put(0); rIntf.rReady.put(0); - wIntf.awValid.put(0); - wIntf.awId?.put(0); - wIntf.awAddr.put(0); - wIntf.awLen?.put(0); - wIntf.awSize?.put(0); - wIntf.awBurst?.put(0); - wIntf.awLock?.put(0); - wIntf.awCache?.put(0); - wIntf.awProt.put(0); - wIntf.awQos?.put(0); - wIntf.awRegion?.put(0); - wIntf.awUser?.put(0); - wIntf.wValid.put(0); - wIntf.wData.put(0); - wIntf.wStrb.put(0); - wIntf.wLast.put(0); - wIntf.bReady.put(0); }); // wait for reset to complete before driving anything @@ -97,8 +259,6 @@ class Axi4MainDriver extends PendingClockedDriver { print('Driving packet at time ${Simulator.time}'); if (packet is Axi4ReadRequestPacket) { await _driveReadPacket(packet); - } else if (packet is Axi4WriteRequestPacket) { - await _driveWritePacket(packet); } else { await sIntf.clk.nextPosedge; } @@ -106,7 +266,6 @@ class Axi4MainDriver extends PendingClockedDriver { // TODO: need a more robust way of driving the "ready" signals... // RREADY for read data responses - // BREADY for write responses // specifically, when should they toggle on/off? // ON => either always or when the associated request is driven? // OFF => either never or when there are no more outstanding requests of the given type? @@ -143,6 +302,85 @@ class Axi4MainDriver extends PendingClockedDriver { // TODO: wait for the response to complete?? } +} + +/// A driver for the [Axi4WriteInterface] interface. +/// +/// Driving from the perspective of the Main agent. +class Axi4WriteMainDriver extends PendingClockedDriver { + /// AXI4 System Interface. + final Axi4SystemInterface sIntf; + + /// AXI4 Write Interface. + final Axi4WriteInterface wIntf; + + /// Creates a new [Axi4WriteMainDriver]. + Axi4WriteMainDriver({ + required Component parent, + required this.sIntf, + required this.wIntf, + required super.sequencer, + super.timeoutCycles = 500, + super.dropDelayCycles = 30, + String name = 'axi4WriteMainDriver', + }) : super( + name, + parent, + clk: sIntf.clk, + ); + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + Simulator.injectAction(() { + wIntf.awValid.put(0); + wIntf.awId?.put(0); + wIntf.awAddr.put(0); + wIntf.awLen?.put(0); + wIntf.awSize?.put(0); + wIntf.awBurst?.put(0); + wIntf.awLock?.put(0); + wIntf.awCache?.put(0); + wIntf.awProt.put(0); + wIntf.awQos?.put(0); + wIntf.awRegion?.put(0); + wIntf.awUser?.put(0); + wIntf.wValid.put(0); + wIntf.wData.put(0); + wIntf.wStrb.put(0); + wIntf.wLast.put(0); + wIntf.bReady.put(0); + }); + + // wait for reset to complete before driving anything + await sIntf.resetN.nextPosedge; + + while (!Simulator.simulationHasEnded) { + if (pendingSeqItems.isNotEmpty) { + await _drivePacket(pendingSeqItems.removeFirst()); + } else { + await sIntf.clk.nextPosedge; + } + } + } + + /// Drives a packet onto the interface. + Future _drivePacket(Axi4RequestPacket packet) async { + print('Driving packet at time ${Simulator.time}'); + if (packet is Axi4WriteRequestPacket) { + await _driveWritePacket(packet); + } else { + await sIntf.clk.nextPosedge; + } + } + + // TODO: need a more robust way of driving the "ready" signals... + // BREADY for write responses + // specifically, when should they toggle on/off? + // ON => either always or when the associated request is driven? + // OFF => either never or when there are no more outstanding requests of the given type? + // should we enable the ability to backpressure?? Future _driveWritePacket(Axi4WriteRequestPacket packet) async { await sIntf.clk.nextPosedge; diff --git a/lib/src/models/axi4_bfm/axi4_monitor.dart b/lib/src/models/axi4_bfm/axi4_monitor.dart index be3d91fab..a6cd0a246 100644 --- a/lib/src/models/axi4_bfm/axi4_monitor.dart +++ b/lib/src/models/axi4_bfm/axi4_monitor.dart @@ -11,32 +11,170 @@ import 'dart:async'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; -import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; import 'package:rohd_vf/rohd_vf.dart'; -/// A monitor for [Axi4ReadInterface]s and [Axi4WriteInterface]s. -class Axi4Monitor extends Monitor { +// /// A monitor for [Axi4ReadInterface]s and [Axi4WriteInterface]s. +// class Axi4Monitor extends Monitor { +// /// AXI4 System Interface. +// final Axi4SystemInterface sIntf; + +// /// AXI4 Read Interface. +// final Axi4ReadInterface rIntf; + +// /// AXI4 Write Interface. +// final Axi4WriteInterface wIntf; + +// final List _pendingReadRequests = []; +// final List> _pendingReadResponseData = []; + +// final List _pendingWriteRequests = []; + +// /// Creates a new [Axi4Monitor] on [rIntf] and [wIntf]. +// Axi4Monitor( +// {required this.sIntf, +// required this.rIntf, +// required this.wIntf, +// required Component parent, +// String name = 'axi4Monitor'}) +// : super(name, parent); + +// @override +// Future run(Phase phase) async { +// unawaited(super.run(phase)); + +// await sIntf.resetN.nextPosedge; + +// // handle reset +// sIntf.resetN.negedge.listen((event) { +// _pendingReadRequests.clear(); +// _pendingReadResponseData.clear(); +// _pendingWriteRequests.clear(); +// }); + +// sIntf.clk.posedge.listen((event) { +// // read request monitoring +// if (rIntf.arValid.previousValue!.isValid && +// rIntf.arReady.previousValue!.isValid && +// rIntf.arValid.previousValue!.toBool() && +// rIntf.arReady.previousValue!.toBool()) { +// _pendingReadRequests.add( +// Axi4ReadRequestPacket( +// addr: rIntf.arAddr.previousValue!, +// prot: rIntf.arProt.previousValue!, +// id: rIntf.arId?.previousValue, +// len: rIntf.arLen?.previousValue, +// size: rIntf.arSize?.previousValue, +// burst: rIntf.arBurst?.previousValue, +// lock: rIntf.arLock?.previousValue, +// cache: rIntf.arCache?.previousValue, +// qos: rIntf.arQos?.previousValue, +// region: rIntf.arRegion?.previousValue, +// user: rIntf.arUser?.previousValue, +// ), +// ); +// _pendingReadResponseData.add([]); +// } + +// // read response data monitoring +// if (rIntf.rValid.previousValue!.isValid && +// rIntf.rReady.previousValue!.isValid && +// rIntf.rValid.previousValue!.toBool() && +// rIntf.rReady.previousValue!.toBool()) { +// var targIdx = 0; +// if (rIntf.rId != null) { +// targIdx = _pendingReadRequests.indexWhere((element) => +// element.id!.toInt() == rIntf.rId!.previousValue!.toInt()); +// } +// if (targIdx >= 0 && _pendingReadRequests.length > targIdx) { +// _pendingReadResponseData[targIdx].add(rIntf.rData.previousValue!); +// if (rIntf.rLast?.value.toBool() ?? true) { +// add(_pendingReadRequests[targIdx] +// ..complete( +// data: _pendingReadResponseData[targIdx], +// resp: rIntf.rResp?.previousValue, +// user: rIntf.rUser?.previousValue, +// )); +// _pendingReadRequests.removeAt(targIdx); +// _pendingReadResponseData.removeAt(targIdx); +// } +// } +// } + +// // write request monitoring +// if (wIntf.awValid.previousValue!.isValid && +// wIntf.awReady.previousValue!.isValid && +// wIntf.awValid.previousValue!.toBool() && +// wIntf.awReady.previousValue!.toBool()) { +// _pendingWriteRequests.add( +// Axi4WriteRequestPacket( +// addr: wIntf.awAddr.previousValue!, +// prot: wIntf.awProt.previousValue!, +// id: wIntf.awId?.previousValue, +// len: wIntf.awLen?.previousValue, +// size: wIntf.awSize?.previousValue, +// burst: wIntf.awBurst?.previousValue, +// lock: wIntf.awLock?.previousValue, +// cache: wIntf.awCache?.previousValue, +// qos: wIntf.awQos?.previousValue, +// region: wIntf.awRegion?.previousValue, +// user: wIntf.awUser?.previousValue, +// data: [], +// strobe: []), +// ); +// } + +// // write data monitoring +// // NOTE: not dealing with WLAST here b/c it is implicit in how the interface behaves +// if (wIntf.wValid.previousValue!.isValid && +// wIntf.wReady.previousValue!.isValid && +// wIntf.wValid.previousValue!.toBool() && +// wIntf.wReady.previousValue!.toBool()) { +// final targIdx = _pendingWriteRequests.length - 1; +// _pendingWriteRequests[targIdx].data.add(wIntf.wData.previousValue!); +// _pendingWriteRequests[targIdx].strobe.add(wIntf.wStrb.previousValue!); +// _pendingWriteRequests[targIdx].wUser = wIntf.wUser?.previousValue; +// } + +// // write response monitoring +// if (wIntf.bValid.previousValue!.isValid && +// wIntf.bReady.previousValue!.isValid && +// wIntf.bValid.previousValue!.toBool() && +// wIntf.bReady.previousValue!.toBool()) { +// var targIdx = 0; +// if (wIntf.bId != null) { +// targIdx = _pendingWriteRequests.indexWhere((element) => +// element.id!.toInt() == wIntf.bId!.previousValue!.toInt()); +// } +// if (targIdx >= 0 && _pendingWriteRequests.length > targIdx) { +// add(_pendingWriteRequests[targIdx] +// ..complete( +// resp: wIntf.bResp?.previousValue, +// user: wIntf.bUser?.previousValue, +// )); +// _pendingWriteRequests.removeAt(targIdx); +// } +// } +// }); +// } +// } + +/// A monitor for [Axi4ReadInterface]s. +class Axi4ReadMonitor extends Monitor { /// AXI4 System Interface. final Axi4SystemInterface sIntf; /// AXI4 Read Interface. final Axi4ReadInterface rIntf; - /// AXI4 Write Interface. - final Axi4WriteInterface wIntf; - final List _pendingReadRequests = []; final List> _pendingReadResponseData = []; - final List _pendingWriteRequests = []; - - /// Creates a new [Axi4Monitor] on [rIntf] and [wIntf]. - Axi4Monitor( + /// Creates a new [Axi4ReadMonitor] on [rIntf]. + Axi4ReadMonitor( {required this.sIntf, required this.rIntf, - required this.wIntf, required Component parent, - String name = 'axi4Monitor'}) + String name = 'axi4ReadMonitor'}) : super(name, parent); @override @@ -49,7 +187,6 @@ class Axi4Monitor extends Monitor { sIntf.resetN.negedge.listen((event) { _pendingReadRequests.clear(); _pendingReadResponseData.clear(); - _pendingWriteRequests.clear(); }); sIntf.clk.posedge.listen((event) { @@ -100,7 +237,40 @@ class Axi4Monitor extends Monitor { } } } + }); + } +} +/// A monitor for [Axi4WriteInterface]s. +class Axi4WriteMonitor extends Monitor { + /// AXI4 System Interface. + final Axi4SystemInterface sIntf; + + /// AXI4 Write Interface. + final Axi4WriteInterface wIntf; + + final List _pendingWriteRequests = []; + + /// Creates a new [Axi4WriteMonitor] on [wIntf]. + Axi4WriteMonitor( + {required this.sIntf, + required this.wIntf, + required Component parent, + String name = 'axi4WriteMonitor'}) + : super(name, parent); + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + await sIntf.resetN.nextPosedge; + + // handle reset + sIntf.resetN.negedge.listen((event) { + _pendingWriteRequests.clear(); + }); + + sIntf.clk.posedge.listen((event) { // write request monitoring if (wIntf.awValid.previousValue!.isValid && wIntf.awReady.previousValue!.isValid && diff --git a/lib/src/models/axi4_bfm/axi4_packet.dart b/lib/src/models/axi4_bfm/axi4_packet.dart index 649bb133d..cded0f9be 100644 --- a/lib/src/models/axi4_bfm/axi4_packet.dart +++ b/lib/src/models/axi4_bfm/axi4_packet.dart @@ -11,7 +11,6 @@ import 'dart:async'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; -import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; import 'package:rohd_vf/rohd_vf.dart'; /// A request packet on an AXI4 interface. diff --git a/lib/src/models/axi4_bfm/axi4_subordinate.dart b/lib/src/models/axi4_bfm/axi4_subordinate.dart index aaf4aad1c..65ec70195 100644 --- a/lib/src/models/axi4_bfm/axi4_subordinate.dart +++ b/lib/src/models/axi4_bfm/axi4_subordinate.dart @@ -8,10 +8,10 @@ // Author: Josh Kimmel import 'dart:async'; +import 'dart:math'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; -import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; import 'package:rohd_vf/rohd_vf.dart'; /// A model for the subordinate side of @@ -21,16 +21,16 @@ class Axi4SubordinateAgent extends Agent { final Axi4SystemInterface sIntf; /// The read interface to drive. - final Axi4ReadInterface rIntf; + final List rIntfs; /// The write interface to drive. - final Axi4WriteInterface wIntf; + final List wIntfs; /// A place where the subordinate should save and retrieve data. /// /// The [Axi4SubordinateAgent] will reset [storage] whenever /// the `resetN` signal is dropped. - final MemoryStorage storage; + late final MemoryStorage storage; /// A function which delays the response for the given `request`. /// @@ -56,21 +56,21 @@ class Axi4SubordinateAgent extends Agent { final bool dropWriteDataOnError; // to handle response read responses - final List _dataReadResponseMetadataQueue = []; - final List> _dataReadResponseDataQueue = []; - int _dataReadResponseIndex = 0; + final List> _dataReadResponseMetadataQueue = []; + final List>> _dataReadResponseDataQueue = []; + final List _dataReadResponseIndex = []; // to handle writes - final List _writeMetadataQueue = []; - bool _writeReadyToOccur = false; + final List> _writeMetadataQueue = []; + final List _writeReadyToOccur = []; /// Creates a new model [Axi4SubordinateAgent]. /// /// If no [storage] is provided, it will use a default [SparseMemoryStorage]. Axi4SubordinateAgent( {required this.sIntf, - required this.rIntf, - required this.wIntf, + required this.rIntfs, + required this.wIntfs, required Component parent, MemoryStorage? storage, this.readResponseDelay, @@ -79,16 +79,33 @@ class Axi4SubordinateAgent extends Agent { this.invalidReadDataOnError = true, this.dropWriteDataOnError = true, String name = 'axi4SubordinateAgent'}) - : assert(rIntf.addrWidth == wIntf.addrWidth, - 'Read and write interfaces should have same address width.'), - assert(rIntf.dataWidth == wIntf.dataWidth, - 'Read and write interfaces should have same data width.'), - storage = storage ?? - SparseMemoryStorage( - addrWidth: rIntf.addrWidth, - dataWidth: rIntf.dataWidth, - ), - super(name, parent); + : super(name, parent) { + var maxAddrWidth = 0; + var maxDataWidth = 0; + for (var i = 0; i < rIntfs.length; i++) { + maxAddrWidth = max(maxAddrWidth, rIntfs[i].addrWidth); + maxDataWidth = max(maxDataWidth, rIntfs[i].dataWidth); + } + for (var i = 0; i < wIntfs.length; i++) { + maxAddrWidth = max(maxAddrWidth, wIntfs[i].addrWidth); + maxDataWidth = max(maxDataWidth, wIntfs[i].dataWidth); + } + + storage = storage ?? + SparseMemoryStorage( + addrWidth: maxAddrWidth, + dataWidth: maxDataWidth, + ); + for (var i = 0; i < rIntfs.length; i++) { + _dataReadResponseMetadataQueue.add([]); + _dataReadResponseDataQueue.add([]); + _dataReadResponseIndex.add(0); + } + for (var i = 0; i < wIntfs.length; i++) { + _writeMetadataQueue.add([]); + _writeReadyToOccur.add(false); + } + } @override Future run(Phase phase) async { @@ -96,11 +113,16 @@ class Axi4SubordinateAgent extends Agent { sIntf.resetN.negedge.listen((event) { storage.reset(); - _dataReadResponseDataQueue.clear(); - _dataReadResponseMetadataQueue.clear(); - _dataReadResponseIndex = 0; - _writeMetadataQueue.clear(); - _writeReadyToOccur = false; + for (var i = 0; i < rIntfs.length; i++) { + _dataReadResponseDataQueue[i].clear(); + _dataReadResponseMetadataQueue[i].clear(); + _dataReadResponseIndex[i] = 0; + } + + for (var i = 0; i < wIntfs.length; i++) { + _writeMetadataQueue[i].clear(); + _writeReadyToOccur[i] = false; + } }); // wait for reset to complete @@ -108,12 +130,18 @@ class Axi4SubordinateAgent extends Agent { while (!Simulator.simulationHasEnded) { await sIntf.clk.nextNegedge; - _driveReadys(); - _respondRead(); - _respondWrite(); - _receiveRead(); - _captureWriteData(); - _receiveWrite(); + for (var i = 0; i < rIntfs.length; i++) { + _driveReadReadys(index: i); + _respondRead(index: i); + _receiveRead(index: i); + } + + for (var i = 0; i < wIntfs.length; i++) { + _driveWriteReadys(index: i); + _respondWrite(index: i); + _captureWriteData(index: i); + _receiveWrite(index: i); + } } } @@ -127,31 +155,37 @@ class Axi4SubordinateAgent extends Agent { ].rswizzle(); // assesses the input ready signals and drives them appropriately - void _driveReadys() { + void _driveReadReadys({int index = 0}) { + // for now, assume we can always handle a new request + rIntfs[index].arReady.put(true); + } + + // assesses the input ready signals and drives them appropriately + void _driveWriteReadys({int index = 0}) { // for now, assume we can always handle a new request - rIntf.arReady.put(true); - wIntf.awReady.put(true); - wIntf.wReady.put(true); + wIntfs[index].awReady.put(true); + wIntfs[index].wReady.put(true); } /// Receives one packet (or returns if not selected). - void _receiveRead() { + void _receiveRead({int index = 0}) { // work to do if main is indicating a valid read that we are ready to handle - if (rIntf.arValid.value.toBool() && rIntf.arReady.value.toBool()) { - logger.info('Received read request.'); + if (rIntfs[index].arValid.value.toBool() && + rIntfs[index].arReady.value.toBool()) { + logger.info('Received read request on interface $index.'); final packet = Axi4ReadRequestPacket( - addr: rIntf.arAddr.value, - prot: rIntf.arProt.value, - id: rIntf.arId?.value, - len: rIntf.arLen?.value, - size: rIntf.arSize?.value, - burst: rIntf.arBurst?.value, - lock: rIntf.arLock?.value, - cache: rIntf.arCache?.value, - qos: rIntf.arQos?.value, - region: rIntf.arRegion?.value, - user: rIntf.arUser?.value); + addr: rIntfs[index].arAddr.value, + prot: rIntfs[index].arProt.value, + id: rIntfs[index].arId?.value, + len: rIntfs[index].arLen?.value, + size: rIntfs[index].arSize?.value, + burst: rIntfs[index].arBurst?.value, + lock: rIntfs[index].arLock?.value, + cache: rIntfs[index].arCache?.value, + qos: rIntfs[index].arQos?.value, + region: rIntfs[index].arRegion?.value, + user: rIntfs[index].arUser?.value); // generic model does not handle the following read request fields: // cache @@ -169,13 +203,13 @@ class Axi4SubordinateAgent extends Agent { // query storage to retrieve the data final data = []; - var addrToRead = rIntf.arAddr.value; - final endCount = (rIntf.arLen?.value.toInt() ?? 0) + 1; - final dSize = (rIntf.arSize?.value.toInt() ?? 0) * 8; + var addrToRead = rIntfs[index].arAddr.value; + final endCount = (rIntfs[index].arLen?.value.toInt() ?? 0) + 1; + final dSize = (rIntfs[index].arSize?.value.toInt() ?? 0) * 8; var increment = 0; - if (rIntf.arBurst == null || - rIntf.arBurst?.value.toInt() == Axi4BurstField.wrap.value || - rIntf.arBurst?.value.toInt() == Axi4BurstField.incr.value) { + if (rIntfs[index].arBurst == null || + rIntfs[index].arBurst?.value.toInt() == Axi4BurstField.wrap.value || + rIntfs[index].arBurst?.value.toInt() == Axi4BurstField.incr.value) { increment = dSize ~/ 8; } @@ -192,23 +226,24 @@ class Axi4SubordinateAgent extends Agent { addrToRead = addrToRead + increment; } - _dataReadResponseMetadataQueue.add(packet); - _dataReadResponseDataQueue.add(data); + _dataReadResponseMetadataQueue[index].add(packet); + _dataReadResponseDataQueue[index].add(data); } } // respond to a read request - void _respondRead() { + void _respondRead({int index = 0}) { // only respond if there is something to respond to // and the main side is indicating that it is ready to receive - if (_dataReadResponseMetadataQueue.isNotEmpty && - _dataReadResponseDataQueue.isNotEmpty && - rIntf.rReady.value.toBool()) { - final packet = _dataReadResponseMetadataQueue[0]; - final currData = _dataReadResponseDataQueue[0][_dataReadResponseIndex]; + if (_dataReadResponseMetadataQueue[index].isNotEmpty && + _dataReadResponseDataQueue[index].isNotEmpty && + rIntfs[index].rReady.value.toBool()) { + final packet = _dataReadResponseMetadataQueue[index][0]; + final currData = + _dataReadResponseDataQueue[0][index][_dataReadResponseIndex[index]]; final error = respondWithError != null && respondWithError!(packet); - final last = - _dataReadResponseIndex == _dataReadResponseDataQueue[0].length - 1; + final last = _dataReadResponseIndex[index] == + _dataReadResponseDataQueue[index][0].length - 1; // TODO: how to deal with delays?? // if (readResponseDelay != null) { @@ -219,101 +254,107 @@ class Axi4SubordinateAgent extends Agent { // } // for now, only support sending slvErr and okay as responses - rIntf.rValid.put(true); - rIntf.rId?.put(packet.id); - rIntf.rData.put(currData); - rIntf.rResp?.put(error - ? LogicValue.ofInt(Axi4RespField.slvErr.value, rIntf.rResp!.width) - : LogicValue.ofInt(Axi4RespField.okay.value, rIntf.rResp!.width)); - rIntf.rUser?.put(0); // don't support user field for now - rIntf.rLast?.put(last); + rIntfs[index].rValid.put(true); + rIntfs[index].rId?.put(packet.id); + rIntfs[index].rData.put(currData); + rIntfs[index].rResp?.put(error + ? LogicValue.ofInt( + Axi4RespField.slvErr.value, rIntfs[index].rResp!.width) + : LogicValue.ofInt( + Axi4RespField.okay.value, rIntfs[index].rResp!.width)); + rIntfs[index].rUser?.put(0); // don't support user field for now + rIntfs[index].rLast?.put(last); if (last) { // pop this read response off the queue - _dataReadResponseIndex = 0; - _dataReadResponseMetadataQueue.removeAt(0); - _dataReadResponseDataQueue.removeAt(0); + _dataReadResponseIndex[index] = 0; + _dataReadResponseMetadataQueue[index].removeAt(0); + _dataReadResponseDataQueue[index].removeAt(0); - logger.info('Finished sending read response.'); + logger.info('Finished sending read response for interface $index.'); } else { // move to the next chunk of data - _dataReadResponseIndex++; - logger.info('Still sending the read response.'); + _dataReadResponseIndex[index]++; + logger.info('Still sending the read response for interface $index.'); } } else { - rIntf.rValid.put(false); + rIntfs[index].rValid.put(false); } } // handle an incoming write request - void _receiveWrite() { + void _receiveWrite({int index = 0}) { // work to do if main is indicating a valid + ready write - if (wIntf.awValid.value.toBool() && wIntf.awReady.value.toBool()) { - logger.info('Received write request.'); + if (wIntfs[index].awValid.value.toBool() && + wIntfs[index].awReady.value.toBool()) { + logger.info('Received write request on interface $index.'); final packet = Axi4WriteRequestPacket( - addr: wIntf.awAddr.value, - prot: wIntf.awProt.value, - id: wIntf.awId?.value, - len: wIntf.awLen?.value, - size: wIntf.awSize?.value, - burst: wIntf.awBurst?.value, - lock: wIntf.awLock?.value, - cache: wIntf.awCache?.value, - qos: wIntf.awQos?.value, - region: wIntf.awRegion?.value, - user: wIntf.awUser?.value, + addr: wIntfs[index].awAddr.value, + prot: wIntfs[index].awProt.value, + id: wIntfs[index].awId?.value, + len: wIntfs[index].awLen?.value, + size: wIntfs[index].awSize?.value, + burst: wIntfs[index].awBurst?.value, + lock: wIntfs[index].awLock?.value, + cache: wIntfs[index].awCache?.value, + qos: wIntfs[index].awQos?.value, + region: wIntfs[index].awRegion?.value, + user: wIntfs[index].awUser?.value, data: [], strobe: []); // might need to capture the first data and strobe simultaneously // NOTE: we are dropping wUser on the floor for now... - if (wIntf.wValid.value.toBool() && wIntf.wReady.value.toBool()) { - packet.data.add(wIntf.wData.value); - packet.strobe.add(wIntf.wStrb.value); - if (wIntf.wLast.value.toBool()) { - _writeReadyToOccur = true; + if (wIntfs[index].wValid.value.toBool() && + wIntfs[index].wReady.value.toBool()) { + packet.data.add(wIntfs[index].wData.value); + packet.strobe.add(wIntfs[index].wStrb.value); + if (wIntfs[index].wLast.value.toBool()) { + _writeReadyToOccur[index] = true; } } // queue up the packet for further processing - _writeMetadataQueue.add(packet); + _writeMetadataQueue[index].add(packet); } } // method to capture incoming write data after the initial request // note that this method does not handle the first flit of write data // if it is transmitted simultaneously with the write request - void _captureWriteData() { + void _captureWriteData({int index = 0}) { // NOTE: we are dropping wUser on the floor for now... - if (_writeMetadataQueue.isNotEmpty && - wIntf.wValid.value.toBool() && - wIntf.wReady.value.toBool()) { - final packet = _writeMetadataQueue[0]; - packet.data.add(wIntf.wData.value); - packet.strobe.add(wIntf.wStrb.value); - logger.info('Captured write data.'); - if (wIntf.wLast.value.toBool()) { - logger.info('Finished capturing write data.'); - _writeReadyToOccur = true; + if (_writeMetadataQueue[index].isNotEmpty && + wIntfs[index].wValid.value.toBool() && + wIntfs[index].wReady.value.toBool()) { + final packet = _writeMetadataQueue[index][0]; + packet.data.add(wIntfs[index].wData.value); + packet.strobe.add(wIntfs[index].wStrb.value); + logger.info('Captured write data on interface $index.'); + if (wIntfs[index].wLast.value.toBool()) { + logger.info('Finished capturing write data on interface $index.'); + _writeReadyToOccur[index] = true; } } } - void _respondWrite() { + void _respondWrite({int index = 0}) { // only work to do if we have received all of the data for our write request - if (_writeReadyToOccur) { + if (_writeReadyToOccur[index]) { // only respond if the main is ready - if (wIntf.bReady.value.toBool()) { - final packet = _writeMetadataQueue[0]; + if (wIntfs[index].bReady.value.toBool()) { + final packet = _writeMetadataQueue[index][0]; final error = respondWithError != null && respondWithError!(packet); // for now, only support sending slvErr and okay as responses - wIntf.bValid.put(true); - wIntf.bId?.put(packet.id); - wIntf.bResp?.put(error - ? LogicValue.ofInt(Axi4RespField.slvErr.value, wIntf.bResp!.width) - : LogicValue.ofInt(Axi4RespField.okay.value, wIntf.bResp!.width)); - wIntf.bUser?.put(0); // don't support user field for now + wIntfs[index].bValid.put(true); + wIntfs[index].bId?.put(packet.id); + wIntfs[index].bResp?.put(error + ? LogicValue.ofInt( + Axi4RespField.slvErr.value, wIntfs[index].bResp!.width) + : LogicValue.ofInt( + Axi4RespField.okay.value, wIntfs[index].bResp!.width)); + wIntfs[index].bUser?.put(0); // don't support user field for now // TODO: how to deal with delays?? // if (readResponseDelay != null) { @@ -362,13 +403,13 @@ class Axi4SubordinateAgent extends Agent { } // pop this write response off the queue - _writeMetadataQueue.removeAt(0); - _writeReadyToOccur = false; + _writeMetadataQueue[index].removeAt(0); + _writeReadyToOccur[index] = false; - logger.info('Sent write response.'); + logger.info('Sent write response on interface $index.'); } } else { - wIntf.bValid.put(false); + wIntfs[index].bValid.put(false); } } } diff --git a/lib/src/models/axi4_bfm/axi4_tracker.dart b/lib/src/models/axi4_bfm/axi4_tracker.dart index 72961744a..f8f14c2a4 100644 --- a/lib/src/models/axi4_bfm/axi4_tracker.dart +++ b/lib/src/models/axi4_bfm/axi4_tracker.dart @@ -8,7 +8,6 @@ // Author: Josh Kimmel import 'package:rohd_hcl/rohd_hcl.dart'; -import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; import 'package:rohd_vf/rohd_vf.dart'; /// A tracker for the [Axi4ReadInterface] or [Axi4WriteInterface]. @@ -65,11 +64,7 @@ class Axi4Tracker extends Tracker { static const strbField = 'STRB'; /// Creates a new tracker for [Axi4ReadInterface] and [Axi4WriteInterface]. - /// - /// If the [selectColumnWidth] is set to 0, the field will be omitted. Axi4Tracker({ - required Axi4ReadInterface rIntf, - required Axi4WriteInterface wIntf, String name = 'Axi4Tracker', super.dumpJson, super.dumpTable, diff --git a/test/axi4_bfm_test.dart b/test/axi4_bfm_test.dart index e3225f17a..c8b9df175 100644 --- a/test/axi4_bfm_test.dart +++ b/test/axi4_bfm_test.dart @@ -12,7 +12,6 @@ import 'dart:io'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; -import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; import 'package:rohd_vf/rohd_vf.dart'; import 'package:test/test.dart'; @@ -20,8 +19,11 @@ import 'axi4_test.dart'; class Axi4BfmTest extends Test { late final Axi4SystemInterface sIntf; - late final Axi4ReadInterface rIntf; - late final Axi4WriteInterface wIntf; + + final int numReads; + final int numWrites; + late final List rIntf; + late final List wIntf; late final Axi4MainAgent main; @@ -48,6 +50,8 @@ class Axi4BfmTest extends Test { Axi4BfmTest( super.name, { + this.numReads = 1, + this.numWrites = 1, this.numTransfers = 10, this.withStrobes = false, this.interTxnDelay = 0, @@ -59,16 +63,24 @@ class Axi4BfmTest extends Test { }) : super(randomSeed: 123) { // using default parameter values for all interfaces sIntf = Axi4SystemInterface(); - rIntf = Axi4ReadInterface( + for (var i = 0; i < numReads; i++) { + rIntf.add(Axi4ReadInterface( addrWidth: addrWidth, dataWidth: dataWidth, lenWidth: lenWidth, - ruserWidth: dataWidth ~/ 2 - 1); - wIntf = Axi4WriteInterface( + ruserWidth: dataWidth ~/ 2 - 1, + )); + Axi4ReadComplianceChecker(sIntf, rIntf.last, parent: this); + } + for (var i = 0; i < numWrites; i++) { + wIntf.add(Axi4WriteInterface( addrWidth: addrWidth, dataWidth: dataWidth, lenWidth: lenWidth, - wuserWidth: dataWidth ~/ 2 - 1); + wuserWidth: dataWidth ~/ 2 - 1, + )); + Axi4WriteComplianceChecker(sIntf, wIntf.last, parent: this); + } storage = SparseMemoryStorage( addrWidth: addrWidth, @@ -80,12 +92,12 @@ class Axi4BfmTest extends Test { sIntf.clk <= SimpleClockGenerator(10).clk; main = - Axi4MainAgent(sIntf: sIntf, rIntf: rIntf, wIntf: wIntf, parent: this); + Axi4MainAgent(sIntf: sIntf, rIntfs: rIntf, wIntfs: wIntf, parent: this); Axi4SubordinateAgent( sIntf: sIntf, - rIntf: rIntf, - wIntf: wIntf, + rIntfs: rIntf, + wIntfs: wIntf, parent: this, storage: storage, readResponseDelay: @@ -95,24 +107,13 @@ class Axi4BfmTest extends Test { respondWithError: withErrors ? (request) => true : null, ); - final monitor = Axi4Monitor( - sIntf: sIntf, - rIntf: rIntf, - wIntf: wIntf, - parent: this, - ); - Directory(outFolder).createSync(recursive: true); final tracker = Axi4Tracker( - rIntf: rIntf, - wIntf: wIntf, dumpTable: false, outputFolder: outFolder, ); - Axi4ComplianceChecker(sIntf, rIntf, wIntf, parent: this); - Simulator.registerEndOfSimulationAction(() async { await tracker.terminate(); @@ -126,7 +127,12 @@ class Axi4BfmTest extends Test { // Directory(outFolder).deleteSync(recursive: true); }); - monitor.stream.listen(tracker.record); + for (var i = 0; i < numReads; i++) { + main.rdMonitors[i].stream.listen(tracker.record); + } + for (var i = 0; i < numWrites; i++) { + main.wrMonitors[i].stream.listen(tracker.record); + } } int numTransfersCompleted = 0; @@ -155,20 +161,24 @@ class Axi4BfmTest extends Test { // normal writes for (var i = 0; i < numTransfers; i++) { + // pick a random write interface + final currW = Test.random!.nextInt(wIntf.length); + final wIntfC = wIntf[currW]; + // generate a completely random access - final transLen = Test.random!.nextInt(1 << wIntf.lenWidth); + final transLen = Test.random!.nextInt(1 << wIntfC.lenWidth); final transSize = - Test.random!.nextInt(1 << wIntf.sizeWidth) % (dataWidth ~/ 8); + Test.random!.nextInt(1 << wIntfC.sizeWidth) % (dataWidth ~/ 8); final randomData = List.generate( transLen + 1, (index) => LogicValue.ofInt( - Test.random!.nextInt(1 << wIntf.dataWidth), wIntf.dataWidth)); + Test.random!.nextInt(1 << wIntfC.dataWidth), wIntfC.dataWidth)); final randomStrobes = List.generate( transLen + 1, (index) => withStrobes ? LogicValue.ofInt( - Test.random!.nextInt(1 << wIntf.strbWidth), wIntf.strbWidth) - : LogicValue.filled(wIntf.strbWidth, LogicValue.one)); + Test.random!.nextInt(1 << wIntfC.strbWidth), wIntfC.strbWidth) + : LogicValue.filled(wIntfC.strbWidth, LogicValue.one)); lens.add(transLen); sizes.add(transSize); data.add(randomData); @@ -176,23 +186,23 @@ class Axi4BfmTest extends Test { final wrPkt = Axi4WriteRequestPacket( addr: LogicValue.ofInt(i, 32), - prot: LogicValue.ofInt(0, wIntf.protWidth), // not supported + prot: LogicValue.ofInt(0, wIntfC.protWidth), // not supported data: randomData, - id: LogicValue.ofInt(i, wIntf.idWidth), - len: LogicValue.ofInt(transLen, wIntf.lenWidth), - size: LogicValue.ofInt(transSize, wIntf.sizeWidth), + id: LogicValue.ofInt(i, wIntfC.idWidth), + len: LogicValue.ofInt(transLen, wIntfC.lenWidth), + size: LogicValue.ofInt(transSize, wIntfC.sizeWidth), burst: LogicValue.ofInt( - Axi4BurstField.incr.value, wIntf.burstWidth), // fixed for now + Axi4BurstField.incr.value, wIntfC.burstWidth), // fixed for now lock: LogicValue.ofInt(0, 1), // not supported - cache: LogicValue.ofInt(0, wIntf.cacheWidth), // not supported - qos: LogicValue.ofInt(0, wIntf.qosWidth), // not supported - region: LogicValue.ofInt(0, wIntf.regionWidth), // not supported - user: LogicValue.ofInt(0, wIntf.awuserWidth), // not supported + cache: LogicValue.ofInt(0, wIntfC.cacheWidth), // not supported + qos: LogicValue.ofInt(0, wIntfC.qosWidth), // not supported + region: LogicValue.ofInt(0, wIntfC.regionWidth), // not supported + user: LogicValue.ofInt(0, wIntfC.awuserWidth), // not supported strobe: randomStrobes, - wUser: LogicValue.ofInt(0, wIntf.wuserWidth), // not supported + wUser: LogicValue.ofInt(0, wIntfC.wuserWidth), // not supported ); - main.sequencer.add(wrPkt); + main.wrSequencers[currW].add(wrPkt); numTransfersCompleted++; // Note that driver will already serialize the writes @@ -202,22 +212,26 @@ class Axi4BfmTest extends Test { // normal reads that check data for (var i = 0; i < numTransfers; i++) { + // pick a random read interface + final currR = Test.random!.nextInt(rIntf.length); + final rIntfC = rIntf[currR]; + final rdPkt = Axi4ReadRequestPacket( addr: LogicValue.ofInt(i, 32), - prot: LogicValue.ofInt(0, rIntf.protWidth), // not supported - id: LogicValue.ofInt(i, rIntf.idWidth), - len: LogicValue.ofInt(lens[i], rIntf.lenWidth), - size: LogicValue.ofInt(sizes[i], rIntf.sizeWidth), + prot: LogicValue.ofInt(0, rIntfC.protWidth), // not supported + id: LogicValue.ofInt(i, rIntfC.idWidth), + len: LogicValue.ofInt(lens[i], rIntfC.lenWidth), + size: LogicValue.ofInt(sizes[i], rIntfC.sizeWidth), burst: LogicValue.ofInt( - Axi4BurstField.incr.value, rIntf.burstWidth), // fixed for now + Axi4BurstField.incr.value, rIntfC.burstWidth), // fixed for now lock: LogicValue.ofInt(0, 1), // not supported - cache: LogicValue.ofInt(0, rIntf.cacheWidth), // not supported - qos: LogicValue.ofInt(0, rIntf.qosWidth), // not supported - region: LogicValue.ofInt(0, rIntf.regionWidth), // not supported - user: LogicValue.ofInt(0, rIntf.aruserWidth), // not supported + cache: LogicValue.ofInt(0, rIntfC.cacheWidth), // not supported + qos: LogicValue.ofInt(0, rIntfC.qosWidth), // not supported + region: LogicValue.ofInt(0, rIntfC.regionWidth), // not supported + user: LogicValue.ofInt(0, rIntfC.aruserWidth), // not supported ); - main.sequencer.add(rdPkt); + main.rdSequencers[currR].add(rdPkt); numTransfersCompleted++; // Note that driver will already serialize the reads @@ -300,6 +314,8 @@ void main() { await runTest(Axi4BfmTest( 'randeverything', numTransfers: 20, + numReads: 4, + numWrites: 8, withRandomRspDelays: true, withStrobes: true, interTxnDelay: 3, diff --git a/test/axi4_test.dart b/test/axi4_test.dart index 0ca672644..563c8f03b 100644 --- a/test/axi4_test.dart +++ b/test/axi4_test.dart @@ -12,43 +12,51 @@ import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:test/test.dart'; class Axi4Subordinate extends Module { - Axi4Subordinate(Axi4SystemInterface sIntf, Axi4ReadInterface rIntf, - Axi4WriteInterface wIntf) { + Axi4Subordinate(Axi4SystemInterface sIntf, List rIntfs, + List wIntfs) { sIntf = Axi4SystemInterface() ..connectIO(this, sIntf, inputTags: {Axi4Direction.misc}); - rIntf = Axi4ReadInterface.clone(rIntf) - ..connectIO(this, rIntf, - inputTags: {Axi4Direction.fromMain}, - outputTags: {Axi4Direction.fromSubordinate}); + for (var i = 0; i < rIntfs.length; i++) { + rIntfs.add(Axi4ReadInterface.clone(rIntfs[i]) + ..connectIO(this, rIntfs[i], + inputTags: {Axi4Direction.fromMain}, + outputTags: {Axi4Direction.fromSubordinate})); + } - wIntf = Axi4WriteInterface.clone(wIntf) - ..connectIO(this, wIntf, - inputTags: {Axi4Direction.fromMain}, - outputTags: {Axi4Direction.fromSubordinate}); + for (var i = 0; i < wIntfs.length; i++) { + wIntfs.add(Axi4WriteInterface.clone(wIntfs[i]) + ..connectIO(this, wIntfs[i], + inputTags: {Axi4Direction.fromMain}, + outputTags: {Axi4Direction.fromSubordinate})); + } } } class Axi4Main extends Module { - Axi4Main(Axi4SystemInterface sIntf, Axi4ReadInterface rIntf, - Axi4WriteInterface wIntf) { + Axi4Main(Axi4SystemInterface sIntf, List rIntfs, + List wIntfs) { sIntf = Axi4SystemInterface() ..connectIO(this, sIntf, inputTags: {Axi4Direction.misc}); - rIntf = Axi4ReadInterface.clone(rIntf) - ..connectIO(this, rIntf, - inputTags: {Axi4Direction.fromSubordinate}, - outputTags: {Axi4Direction.fromMain}); + for (var i = 0; i < rIntfs.length; i++) { + rIntfs.add(Axi4ReadInterface.clone(rIntfs[i]) + ..connectIO(this, rIntfs[i], + inputTags: {Axi4Direction.fromSubordinate}, + outputTags: {Axi4Direction.fromMain})); + } - wIntf = Axi4WriteInterface.clone(wIntf) - ..connectIO(this, wIntf, - inputTags: {Axi4Direction.fromSubordinate}, - outputTags: {Axi4Direction.fromMain}); + for (var i = 0; i < wIntfs.length; i++) { + wIntfs.add(Axi4WriteInterface.clone(wIntfs[i]) + ..connectIO(this, wIntfs[i], + inputTags: {Axi4Direction.fromSubordinate}, + outputTags: {Axi4Direction.fromMain})); + } } } class Axi4Pair extends Module { - Axi4Pair(Logic clk, Logic reset) { + Axi4Pair(Logic clk, Logic reset, {int numReads = 1, int numWrites = 1}) { clk = addInput('clk', clk); reset = addInput('reset', reset); @@ -56,8 +64,15 @@ class Axi4Pair extends Module { sIntf.clk <= clk; sIntf.resetN <= ~reset; - final rIntf = Axi4ReadInterface(); - final wIntf = Axi4WriteInterface(); + final rIntf = []; + for (var i = 0; i < numReads; i++) { + rIntf.add(Axi4ReadInterface()); + } + + final wIntf = []; + for (var i = 0; i < numWrites; i++) { + wIntf.add(Axi4WriteInterface()); + } Axi4Main(sIntf, rIntf, wIntf); Axi4Subordinate(sIntf, rIntf, wIntf); From 86d10919234ba625538969b54f9ec97e425c44ce Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Thu, 6 Feb 2025 11:48:52 -0800 Subject: [PATCH 11/16] small bug fixes. tests not passing. --- lib/src/models/axi4_bfm/axi4_main.dart | 16 ++++++++-------- test/axi4_bfm_test.dart | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/src/models/axi4_bfm/axi4_main.dart b/lib/src/models/axi4_bfm/axi4_main.dart index b7db6ce9d..c29963713 100644 --- a/lib/src/models/axi4_bfm/axi4_main.dart +++ b/lib/src/models/axi4_bfm/axi4_main.dart @@ -19,28 +19,28 @@ class Axi4MainAgent extends Agent { final Axi4SystemInterface sIntf; /// AXI4 Read Interface. - final List rIntfs; + List rIntfs = []; /// AXI4 Write Interface. - final List wIntfs; + List wIntfs = []; /// The sequencers where read requests should be sent. - late final List> rdSequencers; + final List> rdSequencers = []; /// The sequencers where write requests should be sent. - late final List> wrSequencers; + final List> wrSequencers = []; /// The drivers that send read requests over the interface. - late final List rdDrivers; + final List rdDrivers = []; /// The drivers that send write requests over the interface. - late final List wrDrivers; + final List wrDrivers = []; /// Monitoring of read requests over the interface. - late final List rdMonitors; + final List rdMonitors = []; /// Monitoring of write requests over the interface. - late final List wrMonitors; + final List wrMonitors = []; /// The number of cycles before timing out if no transactions can be sent. final int timeoutCycles; diff --git a/test/axi4_bfm_test.dart b/test/axi4_bfm_test.dart index c8b9df175..93c0ffcc3 100644 --- a/test/axi4_bfm_test.dart +++ b/test/axi4_bfm_test.dart @@ -22,8 +22,8 @@ class Axi4BfmTest extends Test { final int numReads; final int numWrites; - late final List rIntf; - late final List wIntf; + final List rIntf = []; + final List wIntf = []; late final Axi4MainAgent main; From 42f18ee0525138119afdbe66b217367022cf50b2 Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Thu, 6 Feb 2025 14:41:02 -0800 Subject: [PATCH 12/16] storage bug fix. random kitchen sink test still failing. --- lib/src/models/axi4_bfm/axi4_subordinate.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/models/axi4_bfm/axi4_subordinate.dart b/lib/src/models/axi4_bfm/axi4_subordinate.dart index 65ec70195..72559063c 100644 --- a/lib/src/models/axi4_bfm/axi4_subordinate.dart +++ b/lib/src/models/axi4_bfm/axi4_subordinate.dart @@ -91,7 +91,7 @@ class Axi4SubordinateAgent extends Agent { maxDataWidth = max(maxDataWidth, wIntfs[i].dataWidth); } - storage = storage ?? + this.storage = storage ?? SparseMemoryStorage( addrWidth: maxAddrWidth, dataWidth: maxDataWidth, From 56bfccccc2cb6514e6058e67e125b94450504707 Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Mon, 10 Feb 2025 13:36:06 -0800 Subject: [PATCH 13/16] Implementing of burst wrap and partial prot support. --- lib/src/models/axi4_bfm/axi4_subordinate.dart | 98 +++++++++++++++++-- 1 file changed, 91 insertions(+), 7 deletions(-) diff --git a/lib/src/models/axi4_bfm/axi4_subordinate.dart b/lib/src/models/axi4_bfm/axi4_subordinate.dart index 72559063c..8fd577105 100644 --- a/lib/src/models/axi4_bfm/axi4_subordinate.dart +++ b/lib/src/models/axi4_bfm/axi4_subordinate.dart @@ -14,6 +14,26 @@ import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:rohd_vf/rohd_vf.dart'; +/// A repesentation of an address region for the +/// AXI Subordinate. Regions can have access modes +/// and can be leveraged for wrapping bursts. +class AxiAddressRange { + /// Starting address of the range. + final LogicValue start; + + /// Ending address of the range (exclusive). + final LogicValue end; + + /// Secure region. + final bool isSecure; + + /// Only accessible in privileged mode. + final bool isPrivileged; + + /// Constructor. + AxiAddressRange({required this.start, required this.end, this.isSecure = false, this.isPrivileged = false}); +} + /// A model for the subordinate side of /// an [Axi4ReadInterface] and [Axi4WriteInterface]. class Axi4SubordinateAgent extends Agent { @@ -55,6 +75,11 @@ class Axi4SubordinateAgent extends Agent { /// [storage]. final bool dropWriteDataOnError; + /// Address range configuration. Controls access to addresses and helps + /// with the wrap mode for bursts. + /// TODO: ensure non-overlapping?? + List ranges = []; + // to handle response read responses final List> _dataReadResponseMetadataQueue = []; final List>> _dataReadResponseDataQueue = []; @@ -78,6 +103,7 @@ class Axi4SubordinateAgent extends Agent { this.respondWithError, this.invalidReadDataOnError = true, this.dropWriteDataOnError = true, + this.ranges = const [], String name = 'axi4SubordinateAgent'}) : super(name, parent) { var maxAddrWidth = 0; @@ -154,6 +180,17 @@ class Axi4SubordinateAgent extends Agent { .getRange(i * 8, i * 8 + 8) ].rswizzle(); + // find the index of the region that the provided address falls in + // if none, return -1 + int _checkRegion(LogicValue addr) { + for (var j=0; j= ranges[j].start).toBool() && (addr < ranges[j].end).toBool()) { + return j; + } + } + return -1; + } + // assesses the input ready signals and drives them appropriately void _driveReadReadys({int index = 0}) { // for now, assume we can always handle a new request @@ -212,6 +249,10 @@ class Axi4SubordinateAgent extends Agent { rIntfs[index].arBurst?.value.toInt() == Axi4BurstField.incr.value) { increment = dSize ~/ 8; } + + // determine if the address falls in a region + final region = _checkRegion(addrToRead); + final inRegion = region >= 0; for (var i = 0; i < endCount; i++) { var currData = storage.readData(addrToRead); @@ -223,7 +264,22 @@ class Axi4SubordinateAgent extends Agent { } } data.add(currData); - addrToRead = addrToRead + increment; + if (inRegion && (addrToRead + increment >= ranges[region].end).toBool()) { + // the original access fell in a region but the next access in + // the burst overflows the region + if (rIntfs[index].arBurst?.value.toInt() == Axi4BurstField.wrap.value) { + // indication that we should wrap around back to the region start + addrToRead = ranges[region].start; + } + else { + // OK to overflow + addrToRead = addrToRead + increment; + } + } + else { + // no region or overflow + addrToRead = addrToRead + increment; + } } _dataReadResponseMetadataQueue[index].add(packet); @@ -245,6 +301,13 @@ class Axi4SubordinateAgent extends Agent { final last = _dataReadResponseIndex[index] == _dataReadResponseDataQueue[index][0].length - 1; + + // check the request's region for legality + final region = _checkRegion(packet.addr); + final inRegion = region >= 0; + + final accessError = inRegion && ((ranges[region].isSecure && ((packet.prot.toInt() & Axi4ProtField.secure.value) == 0)) || (ranges[region].isPrivileged && ((packet.prot.toInt() & Axi4ProtField.privileged.value) == 0))); + // TODO: how to deal with delays?? // if (readResponseDelay != null) { // final delayCycles = readResponseDelay!(packet); @@ -257,7 +320,7 @@ class Axi4SubordinateAgent extends Agent { rIntfs[index].rValid.put(true); rIntfs[index].rId?.put(packet.id); rIntfs[index].rData.put(currData); - rIntfs[index].rResp?.put(error + rIntfs[index].rResp?.put(error | accessError ? LogicValue.ofInt( Axi4RespField.slvErr.value, rIntfs[index].rResp!.width) : LogicValue.ofInt( @@ -265,7 +328,7 @@ class Axi4SubordinateAgent extends Agent { rIntfs[index].rUser?.put(0); // don't support user field for now rIntfs[index].rLast?.put(last); - if (last) { + if (last || accessError) { // pop this read response off the queue _dataReadResponseIndex[index] = 0; _dataReadResponseMetadataQueue[index].removeAt(0); @@ -344,12 +407,19 @@ class Axi4SubordinateAgent extends Agent { // only respond if the main is ready if (wIntfs[index].bReady.value.toBool()) { final packet = _writeMetadataQueue[index][0]; + + // determine if the address falls in a region + var addrToWrite = packet.addr; + final region = _checkRegion(addrToWrite); + final inRegion = region >= 0; + final accessError = inRegion && ((ranges[region].isSecure && ((packet.prot.toInt() & Axi4ProtField.secure.value) == 0)) || (ranges[region].isPrivileged && ((packet.prot.toInt() & Axi4ProtField.privileged.value) == 0))); + final error = respondWithError != null && respondWithError!(packet); // for now, only support sending slvErr and okay as responses wIntfs[index].bValid.put(true); wIntfs[index].bId?.put(packet.id); - wIntfs[index].bResp?.put(error + wIntfs[index].bResp?.put(error || accessError ? LogicValue.ofInt( Axi4RespField.slvErr.value, wIntfs[index].bResp!.width) : LogicValue.ofInt( @@ -378,9 +448,8 @@ class Axi4SubordinateAgent extends Agent { // NOTE: generic model doesn't honor the lock field in write requests. // It will be added as a feature request in the future. - if (!error || !dropWriteDataOnError) { + if (!error && !dropWriteDataOnError && !accessError) { // write the data to the storage - var addrToWrite = packet.addr; final dSize = (packet.size?.toInt() ?? 0) * 8; var increment = 0; if (packet.burst == null || @@ -398,7 +467,22 @@ class Axi4SubordinateAgent extends Agent { .rswizzle() : strobedData; storage.writeData(addrToWrite, wrData); - addrToWrite = addrToWrite + increment; + if (inRegion && (addrToWrite + increment >= ranges[region].end).toBool()) { + // the original access fell in a region but the next access in + // the burst overflows the region + if (packet.burst!.toInt() == Axi4BurstField.wrap.value) { + // indication that we should wrap around back to the region start + addrToWrite = ranges[region].start; + } + else { + // OK to overflow + addrToWrite = addrToWrite + increment; + } + } + else { + // no region or overflow + addrToWrite = addrToWrite + increment; + } } } From 6cef288508976dad916860a6d2c819d2ce84a881 Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Mon, 10 Feb 2025 15:08:38 -0800 Subject: [PATCH 14/16] Refactor to use logical channels instead of decoupled read and write intfs. --- lib/src/models/axi4_bfm/axi4_main.dart | 161 +++++--- lib/src/models/axi4_bfm/axi4_subordinate.dart | 347 ++++++++++-------- test/axi4_bfm_test.dart | 268 ++++++++------ test/axi4_test.dart | 98 +++-- 4 files changed, 524 insertions(+), 350 deletions(-) diff --git a/lib/src/models/axi4_bfm/axi4_main.dart b/lib/src/models/axi4_bfm/axi4_main.dart index c29963713..99e2a36db 100644 --- a/lib/src/models/axi4_bfm/axi4_main.dart +++ b/lib/src/models/axi4_bfm/axi4_main.dart @@ -10,6 +10,46 @@ import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:rohd_vf/rohd_vf.dart'; +/// An AXI4 channel that may or may not support reads and/or writes. +class Axi4Channel { + /// A unique identifier for the given channel. + /// + /// This is a pure SW construct and is not used in the RTL. + final int channelId; + + /// Does this channel support read transactions. + /// + /// If false, the [rIntf] field should be null. + final bool hasRead; + + /// Does this channel support write transactions. + /// + /// If false, the [wIntf] field should be null. + final bool hasWrite; + + /// Read interface. + final Axi4ReadInterface? rIntf; + + /// Write interface. + final Axi4WriteInterface? wIntf; + + /// Constructor. + Axi4Channel({ + this.channelId = 0, + this.hasRead = true, + this.hasWrite = true, + this.rIntf, + this.wIntf, + }) : assert( + hasRead || hasWrite, + 'A channel must support either' + ' reads or writes (or both)'), + assert(!hasRead || rIntf != null, + 'A channel that supports reads must have a read interface'), + assert(!hasWrite || wIntf != null, + 'A channel that supports writes must have a write interface'); +} + /// An agent for sending requests on /// [Axi4ReadInterface]s and [Axi4WriteInterface]s. /// @@ -18,11 +58,8 @@ class Axi4MainAgent extends Agent { /// AXI4 System Interface. final Axi4SystemInterface sIntf; - /// AXI4 Read Interface. - List rIntfs = []; - - /// AXI4 Write Interface. - List wIntfs = []; + /// Channels that the agent can send requests on. + final List channels; /// The sequencers where read requests should be sent. final List> rdSequencers = []; @@ -49,52 +86,94 @@ class Axi4MainAgent extends Agent { /// no pending packets to send. final int dropDelayCycles; + // capture mapping of channel ID to TB object index + final Map _readAddrToChannel = {}; + final Map _writeAddrToChannel = {}; + + /// Get the ith channel's read sequencer. + Sequencer? getRdSequencer(int channelId) => + _readAddrToChannel.containsKey(channelId) + ? rdSequencers[_readAddrToChannel[channelId]!] + : null; + + /// Get the ith channel's write sequencer. + Sequencer? getWrSequencer(int channelId) => + _writeAddrToChannel.containsKey(channelId) + ? wrSequencers[_writeAddrToChannel[channelId]!] + : null; + + /// Get the ith channel's read driver. + Axi4ReadMainDriver? getRdDriver(int channelId) => + _readAddrToChannel.containsKey(channelId) + ? rdDrivers[_readAddrToChannel[channelId]!] + : null; + + /// Get the ith channel's write driver. + Axi4WriteMainDriver? getWrDriver(int channelId) => + _writeAddrToChannel.containsKey(channelId) + ? wrDrivers[_writeAddrToChannel[channelId]!] + : null; + + /// Get the ith channel's read monitor. + Axi4ReadMonitor? getRdMonitor(int channelId) => + _readAddrToChannel.containsKey(channelId) + ? rdMonitors[_readAddrToChannel[channelId]!] + : null; + + /// Get the ith channel's write monitor. + Axi4WriteMonitor? getWrMonitor(int channelId) => + _writeAddrToChannel.containsKey(channelId) + ? wrMonitors[_writeAddrToChannel[channelId]!] + : null; + /// Constructs a new [Axi4MainAgent]. Axi4MainAgent({ required this.sIntf, - required this.rIntfs, - required this.wIntfs, + required this.channels, required Component parent, String name = 'axiMainAgent', this.timeoutCycles = 500, this.dropDelayCycles = 30, }) : super(name, parent) { - for (var i = 0; i < rIntfs.length; i++) { - rdSequencers - .add(Sequencer('axiRdSequencer$i', this)); - rdDrivers.add(Axi4ReadMainDriver( - parent: this, - sIntf: sIntf, - rIntf: rIntfs[i], - sequencer: rdSequencers[i], - timeoutCycles: timeoutCycles, - dropDelayCycles: dropDelayCycles, - name: 'axiRdDriver$i', - )); - rdMonitors.add(Axi4ReadMonitor( + for (var i = 0; i < channels.length; i++) { + if (channels[i].hasRead) { + rdSequencers.add(Sequencer( + 'axiRdSequencer${channels[i].channelId}', this)); + _readAddrToChannel[i] = rdSequencers.length - 1; + rdDrivers.add(Axi4ReadMainDriver( + parent: this, sIntf: sIntf, - rIntf: rIntfs[i], - parent: parent, - name: 'axiRdMonitor$i')); - } - - for (var i = 0; i < wIntfs.length; i++) { - wrSequencers - .add(Sequencer('axiWrSequencer$i', this)); - wrDrivers.add(Axi4WriteMainDriver( - parent: this, - sIntf: sIntf, - wIntf: wIntfs[i], - sequencer: wrSequencers[i], - timeoutCycles: timeoutCycles, - dropDelayCycles: dropDelayCycles, - name: 'axiWrDriver$i', - )); - wrMonitors.add(Axi4WriteMonitor( + rIntf: channels[i].rIntf!, + sequencer: rdSequencers[_readAddrToChannel[i]!], + timeoutCycles: timeoutCycles, + dropDelayCycles: dropDelayCycles, + name: 'axiRdDriver${channels[i].channelId}', + )); + rdMonitors.add(Axi4ReadMonitor( + sIntf: sIntf, + rIntf: channels[i].rIntf!, + parent: parent, + name: 'axiRdMonitor${channels[i].channelId}')); + } + if (channels[i].hasWrite) { + wrSequencers.add(Sequencer( + 'axiWrSequencer${channels[i].channelId}', this)); + _writeAddrToChannel[i] = wrSequencers.length - 1; + wrDrivers.add(Axi4WriteMainDriver( + parent: this, sIntf: sIntf, - wIntf: wIntfs[i], - parent: parent, - name: 'axiWrMonitor$i')); + wIntf: channels[i].wIntf!, + sequencer: wrSequencers[_writeAddrToChannel[i]!], + timeoutCycles: timeoutCycles, + dropDelayCycles: dropDelayCycles, + name: 'axiWrDriver${channels[i].channelId}', + )); + wrMonitors.add(Axi4WriteMonitor( + sIntf: sIntf, + wIntf: channels[i].wIntf!, + parent: parent, + name: 'axiWrMonitor${channels[i].channelId}')); + } } } } diff --git a/lib/src/models/axi4_bfm/axi4_subordinate.dart b/lib/src/models/axi4_bfm/axi4_subordinate.dart index 8fd577105..b4e1a6392 100644 --- a/lib/src/models/axi4_bfm/axi4_subordinate.dart +++ b/lib/src/models/axi4_bfm/axi4_subordinate.dart @@ -20,18 +20,22 @@ import 'package:rohd_vf/rohd_vf.dart'; class AxiAddressRange { /// Starting address of the range. final LogicValue start; - + /// Ending address of the range (exclusive). final LogicValue end; /// Secure region. final bool isSecure; - + /// Only accessible in privileged mode. final bool isPrivileged; /// Constructor. - AxiAddressRange({required this.start, required this.end, this.isSecure = false, this.isPrivileged = false}); + AxiAddressRange( + {required this.start, + required this.end, + this.isSecure = false, + this.isPrivileged = false}); } /// A model for the subordinate side of @@ -40,11 +44,8 @@ class Axi4SubordinateAgent extends Agent { /// The system interface. final Axi4SystemInterface sIntf; - /// The read interface to drive. - final List rIntfs; - - /// The write interface to drive. - final List wIntfs; + /// Channels that the subordinate manages. + final List channels; /// A place where the subordinate should save and retrieve data. /// @@ -89,13 +90,16 @@ class Axi4SubordinateAgent extends Agent { final List> _writeMetadataQueue = []; final List _writeReadyToOccur = []; + // capture mapping of channel ID to TB object index + final Map _readAddrToChannel = {}; + final Map _writeAddrToChannel = {}; + /// Creates a new model [Axi4SubordinateAgent]. /// /// If no [storage] is provided, it will use a default [SparseMemoryStorage]. Axi4SubordinateAgent( {required this.sIntf, - required this.rIntfs, - required this.wIntfs, + required this.channels, required Component parent, MemoryStorage? storage, this.readResponseDelay, @@ -108,13 +112,15 @@ class Axi4SubordinateAgent extends Agent { : super(name, parent) { var maxAddrWidth = 0; var maxDataWidth = 0; - for (var i = 0; i < rIntfs.length; i++) { - maxAddrWidth = max(maxAddrWidth, rIntfs[i].addrWidth); - maxDataWidth = max(maxDataWidth, rIntfs[i].dataWidth); - } - for (var i = 0; i < wIntfs.length; i++) { - maxAddrWidth = max(maxAddrWidth, wIntfs[i].addrWidth); - maxDataWidth = max(maxDataWidth, wIntfs[i].dataWidth); + for (var i = 0; i < channels.length; i++) { + if (channels[i].hasRead) { + maxAddrWidth = max(maxAddrWidth, channels[i].rIntf!.addrWidth); + maxDataWidth = max(maxDataWidth, channels[i].rIntf!.dataWidth); + } + if (channels[i].hasWrite) { + maxAddrWidth = max(maxAddrWidth, channels[i].wIntf!.addrWidth); + maxDataWidth = max(maxDataWidth, channels[i].wIntf!.dataWidth); + } } this.storage = storage ?? @@ -122,14 +128,18 @@ class Axi4SubordinateAgent extends Agent { addrWidth: maxAddrWidth, dataWidth: maxDataWidth, ); - for (var i = 0; i < rIntfs.length; i++) { - _dataReadResponseMetadataQueue.add([]); - _dataReadResponseDataQueue.add([]); - _dataReadResponseIndex.add(0); - } - for (var i = 0; i < wIntfs.length; i++) { - _writeMetadataQueue.add([]); - _writeReadyToOccur.add(false); + for (var i = 0; i < channels.length; i++) { + if (channels[i].hasRead) { + _dataReadResponseMetadataQueue.add([]); + _dataReadResponseDataQueue.add([]); + _dataReadResponseIndex.add(0); + _readAddrToChannel[i] = _dataReadResponseMetadataQueue.length - 1; + } + if (channels[i].hasWrite) { + _writeMetadataQueue.add([]); + _writeReadyToOccur.add(false); + _writeAddrToChannel[i] = _writeMetadataQueue.length - 1; + } } } @@ -139,15 +149,16 @@ class Axi4SubordinateAgent extends Agent { sIntf.resetN.negedge.listen((event) { storage.reset(); - for (var i = 0; i < rIntfs.length; i++) { - _dataReadResponseDataQueue[i].clear(); - _dataReadResponseMetadataQueue[i].clear(); - _dataReadResponseIndex[i] = 0; - } - - for (var i = 0; i < wIntfs.length; i++) { - _writeMetadataQueue[i].clear(); - _writeReadyToOccur[i] = false; + for (var i = 0; i < channels.length; i++) { + if (channels[i].hasRead) { + _dataReadResponseMetadataQueue[_readAddrToChannel[i]!].clear(); + _dataReadResponseDataQueue[_readAddrToChannel[i]!].clear(); + _dataReadResponseIndex[_readAddrToChannel[i]!] = 0; + } + if (channels[i].hasWrite) { + _writeMetadataQueue[_writeAddrToChannel[i]!].clear(); + _writeReadyToOccur[_writeAddrToChannel[i]!] = false; + } } }); @@ -156,17 +167,18 @@ class Axi4SubordinateAgent extends Agent { while (!Simulator.simulationHasEnded) { await sIntf.clk.nextNegedge; - for (var i = 0; i < rIntfs.length; i++) { - _driveReadReadys(index: i); - _respondRead(index: i); - _receiveRead(index: i); - } - - for (var i = 0; i < wIntfs.length; i++) { - _driveWriteReadys(index: i); - _respondWrite(index: i); - _captureWriteData(index: i); - _receiveWrite(index: i); + for (var i = 0; i < channels.length; i++) { + if (channels[i].hasRead) { + _driveReadReadys(index: i); + _respondRead(index: i); + _receiveRead(index: i); + } + if (channels[i].hasWrite) { + _driveWriteReadys(index: i); + _respondWrite(index: i); + _captureWriteData(index: i); + _receiveWrite(index: i); + } } } } @@ -183,8 +195,9 @@ class Axi4SubordinateAgent extends Agent { // find the index of the region that the provided address falls in // if none, return -1 int _checkRegion(LogicValue addr) { - for (var j=0; j= ranges[j].start).toBool() && (addr < ranges[j].end).toBool()) { + for (var j = 0; j < ranges.length; j++) { + if ((addr >= ranges[j].start).toBool() && + (addr < ranges[j].end).toBool()) { return j; } } @@ -194,35 +207,37 @@ class Axi4SubordinateAgent extends Agent { // assesses the input ready signals and drives them appropriately void _driveReadReadys({int index = 0}) { // for now, assume we can always handle a new request - rIntfs[index].arReady.put(true); + channels[index].rIntf!.arReady.put(true); } // assesses the input ready signals and drives them appropriately void _driveWriteReadys({int index = 0}) { // for now, assume we can always handle a new request - wIntfs[index].awReady.put(true); - wIntfs[index].wReady.put(true); + channels[index].wIntf!.awReady.put(true); + channels[index].wIntf!.wReady.put(true); } /// Receives one packet (or returns if not selected). void _receiveRead({int index = 0}) { + final rIntf = channels[index].rIntf!; + final mapIdx = _readAddrToChannel[index]!; + // work to do if main is indicating a valid read that we are ready to handle - if (rIntfs[index].arValid.value.toBool() && - rIntfs[index].arReady.value.toBool()) { - logger.info('Received read request on interface $index.'); + if (rIntf.arValid.value.toBool() && rIntf.arReady.value.toBool()) { + logger.info('Received read request on channel $index.'); final packet = Axi4ReadRequestPacket( - addr: rIntfs[index].arAddr.value, - prot: rIntfs[index].arProt.value, - id: rIntfs[index].arId?.value, - len: rIntfs[index].arLen?.value, - size: rIntfs[index].arSize?.value, - burst: rIntfs[index].arBurst?.value, - lock: rIntfs[index].arLock?.value, - cache: rIntfs[index].arCache?.value, - qos: rIntfs[index].arQos?.value, - region: rIntfs[index].arRegion?.value, - user: rIntfs[index].arUser?.value); + addr: rIntf.arAddr.value, + prot: rIntf.arProt.value, + id: rIntf.arId?.value, + len: rIntf.arLen?.value, + size: rIntf.arSize?.value, + burst: rIntf.arBurst?.value, + lock: rIntf.arLock?.value, + cache: rIntf.arCache?.value, + qos: rIntf.arQos?.value, + region: rIntf.arRegion?.value, + user: rIntf.arUser?.value); // generic model does not handle the following read request fields: // cache @@ -240,16 +255,16 @@ class Axi4SubordinateAgent extends Agent { // query storage to retrieve the data final data = []; - var addrToRead = rIntfs[index].arAddr.value; - final endCount = (rIntfs[index].arLen?.value.toInt() ?? 0) + 1; - final dSize = (rIntfs[index].arSize?.value.toInt() ?? 0) * 8; + var addrToRead = rIntf.arAddr.value; + final endCount = (rIntf.arLen?.value.toInt() ?? 0) + 1; + final dSize = (rIntf.arSize?.value.toInt() ?? 0) * 8; var increment = 0; - if (rIntfs[index].arBurst == null || - rIntfs[index].arBurst?.value.toInt() == Axi4BurstField.wrap.value || - rIntfs[index].arBurst?.value.toInt() == Axi4BurstField.incr.value) { + if (rIntf.arBurst == null || + rIntf.arBurst?.value.toInt() == Axi4BurstField.wrap.value || + rIntf.arBurst?.value.toInt() == Axi4BurstField.incr.value) { increment = dSize ~/ 8; } - + // determine if the address falls in a region final region = _checkRegion(addrToRead); final inRegion = region >= 0; @@ -264,49 +279,55 @@ class Axi4SubordinateAgent extends Agent { } } data.add(currData); - if (inRegion && (addrToRead + increment >= ranges[region].end).toBool()) { + if (inRegion && + (addrToRead + increment >= ranges[region].end).toBool()) { // the original access fell in a region but the next access in // the burst overflows the region - if (rIntfs[index].arBurst?.value.toInt() == Axi4BurstField.wrap.value) { + if (rIntf.arBurst?.value.toInt() == Axi4BurstField.wrap.value) { // indication that we should wrap around back to the region start addrToRead = ranges[region].start; - } - else { + } else { // OK to overflow addrToRead = addrToRead + increment; } - } - else { + } else { // no region or overflow addrToRead = addrToRead + increment; } } - _dataReadResponseMetadataQueue[index].add(packet); - _dataReadResponseDataQueue[index].add(data); + _dataReadResponseMetadataQueue[mapIdx].add(packet); + _dataReadResponseDataQueue[mapIdx].add(data); } } // respond to a read request void _respondRead({int index = 0}) { + final rIntf = channels[index].rIntf!; + final mapIdx = _readAddrToChannel[index]!; + // only respond if there is something to respond to // and the main side is indicating that it is ready to receive - if (_dataReadResponseMetadataQueue[index].isNotEmpty && - _dataReadResponseDataQueue[index].isNotEmpty && - rIntfs[index].rReady.value.toBool()) { - final packet = _dataReadResponseMetadataQueue[index][0]; + if (_dataReadResponseMetadataQueue[mapIdx].isNotEmpty && + _dataReadResponseDataQueue[mapIdx].isNotEmpty && + rIntf.rReady.value.toBool()) { + final packet = _dataReadResponseMetadataQueue[mapIdx][0]; final currData = - _dataReadResponseDataQueue[0][index][_dataReadResponseIndex[index]]; + _dataReadResponseDataQueue[mapIdx][0][_dataReadResponseIndex[mapIdx]]; final error = respondWithError != null && respondWithError!(packet); - final last = _dataReadResponseIndex[index] == - _dataReadResponseDataQueue[index][0].length - 1; + final last = _dataReadResponseIndex[mapIdx] == + _dataReadResponseDataQueue[mapIdx][0].length - 1; - // check the request's region for legality final region = _checkRegion(packet.addr); final inRegion = region >= 0; - final accessError = inRegion && ((ranges[region].isSecure && ((packet.prot.toInt() & Axi4ProtField.secure.value) == 0)) || (ranges[region].isPrivileged && ((packet.prot.toInt() & Axi4ProtField.privileged.value) == 0))); + final accessError = inRegion && + ((ranges[region].isSecure && + ((packet.prot.toInt() & Axi4ProtField.secure.value) == 0)) || + (ranges[region].isPrivileged && + ((packet.prot.toInt() & Axi4ProtField.privileged.value) == + 0))); // TODO: how to deal with delays?? // if (readResponseDelay != null) { @@ -317,68 +338,67 @@ class Axi4SubordinateAgent extends Agent { // } // for now, only support sending slvErr and okay as responses - rIntfs[index].rValid.put(true); - rIntfs[index].rId?.put(packet.id); - rIntfs[index].rData.put(currData); - rIntfs[index].rResp?.put(error | accessError - ? LogicValue.ofInt( - Axi4RespField.slvErr.value, rIntfs[index].rResp!.width) - : LogicValue.ofInt( - Axi4RespField.okay.value, rIntfs[index].rResp!.width)); - rIntfs[index].rUser?.put(0); // don't support user field for now - rIntfs[index].rLast?.put(last); + rIntf.rValid.put(true); + rIntf.rId?.put(packet.id); + rIntf.rData.put(currData); + rIntf.rResp?.put(error | accessError + ? LogicValue.ofInt(Axi4RespField.slvErr.value, rIntf.rResp!.width) + : LogicValue.ofInt(Axi4RespField.okay.value, rIntf.rResp!.width)); + rIntf.rUser?.put(0); // don't support user field for now + rIntf.rLast?.put(last); if (last || accessError) { // pop this read response off the queue - _dataReadResponseIndex[index] = 0; - _dataReadResponseMetadataQueue[index].removeAt(0); - _dataReadResponseDataQueue[index].removeAt(0); + _dataReadResponseIndex[mapIdx] = 0; + _dataReadResponseMetadataQueue[mapIdx].removeAt(0); + _dataReadResponseDataQueue[mapIdx].removeAt(0); - logger.info('Finished sending read response for interface $index.'); + logger.info('Finished sending read response for channel $index.'); } else { // move to the next chunk of data - _dataReadResponseIndex[index]++; - logger.info('Still sending the read response for interface $index.'); + _dataReadResponseIndex[mapIdx]++; + logger.info('Still sending the read response for channel $index.'); } } else { - rIntfs[index].rValid.put(false); + rIntf.rValid.put(false); } } // handle an incoming write request void _receiveWrite({int index = 0}) { + final wIntf = channels[index].wIntf!; + final mapIdx = _writeAddrToChannel[index]!; + // work to do if main is indicating a valid + ready write - if (wIntfs[index].awValid.value.toBool() && - wIntfs[index].awReady.value.toBool()) { - logger.info('Received write request on interface $index.'); + if (wIntf.awValid.value.toBool() && wIntf.awReady.value.toBool()) { + logger.info('Received write request on channel $index.'); final packet = Axi4WriteRequestPacket( - addr: wIntfs[index].awAddr.value, - prot: wIntfs[index].awProt.value, - id: wIntfs[index].awId?.value, - len: wIntfs[index].awLen?.value, - size: wIntfs[index].awSize?.value, - burst: wIntfs[index].awBurst?.value, - lock: wIntfs[index].awLock?.value, - cache: wIntfs[index].awCache?.value, - qos: wIntfs[index].awQos?.value, - region: wIntfs[index].awRegion?.value, - user: wIntfs[index].awUser?.value, + addr: wIntf.awAddr.value, + prot: wIntf.awProt.value, + id: wIntf.awId?.value, + len: wIntf.awLen?.value, + size: wIntf.awSize?.value, + burst: wIntf.awBurst?.value, + lock: wIntf.awLock?.value, + cache: wIntf.awCache?.value, + qos: wIntf.awQos?.value, + region: wIntf.awRegion?.value, + user: wIntf.awUser?.value, data: [], strobe: []); // might need to capture the first data and strobe simultaneously // NOTE: we are dropping wUser on the floor for now... - if (wIntfs[index].wValid.value.toBool() && - wIntfs[index].wReady.value.toBool()) { - packet.data.add(wIntfs[index].wData.value); - packet.strobe.add(wIntfs[index].wStrb.value); - if (wIntfs[index].wLast.value.toBool()) { - _writeReadyToOccur[index] = true; + if (wIntf.wValid.value.toBool() && wIntf.wReady.value.toBool()) { + packet.data.add(wIntf.wData.value); + packet.strobe.add(wIntf.wStrb.value); + if (wIntf.wLast.value.toBool()) { + _writeReadyToOccur[mapIdx] = true; } } // queue up the packet for further processing - _writeMetadataQueue[index].add(packet); + _writeMetadataQueue[mapIdx].add(packet); } } @@ -386,45 +406,55 @@ class Axi4SubordinateAgent extends Agent { // note that this method does not handle the first flit of write data // if it is transmitted simultaneously with the write request void _captureWriteData({int index = 0}) { + final wIntf = channels[index].wIntf!; + final mapIdx = _writeAddrToChannel[index]!; + // NOTE: we are dropping wUser on the floor for now... - if (_writeMetadataQueue[index].isNotEmpty && - wIntfs[index].wValid.value.toBool() && - wIntfs[index].wReady.value.toBool()) { - final packet = _writeMetadataQueue[index][0]; - packet.data.add(wIntfs[index].wData.value); - packet.strobe.add(wIntfs[index].wStrb.value); - logger.info('Captured write data on interface $index.'); - if (wIntfs[index].wLast.value.toBool()) { - logger.info('Finished capturing write data on interface $index.'); - _writeReadyToOccur[index] = true; + if (_writeMetadataQueue[mapIdx].isNotEmpty && + wIntf.wValid.value.toBool() && + wIntf.wReady.value.toBool()) { + final packet = _writeMetadataQueue[mapIdx][0]; + packet.data.add(wIntf.wData.value); + packet.strobe.add(wIntf.wStrb.value); + logger.info('Captured write data on channel $index.'); + if (wIntf.wLast.value.toBool()) { + logger.info('Finished capturing write data on channel $index.'); + _writeReadyToOccur[mapIdx] = true; } } } void _respondWrite({int index = 0}) { + final wIntf = channels[index].wIntf!; + final mapIdx = _writeAddrToChannel[index]!; + // only work to do if we have received all of the data for our write request - if (_writeReadyToOccur[index]) { + if (_writeReadyToOccur[mapIdx]) { // only respond if the main is ready - if (wIntfs[index].bReady.value.toBool()) { - final packet = _writeMetadataQueue[index][0]; - + if (wIntf.bReady.value.toBool()) { + final packet = _writeMetadataQueue[mapIdx][0]; + // determine if the address falls in a region var addrToWrite = packet.addr; final region = _checkRegion(addrToWrite); final inRegion = region >= 0; - final accessError = inRegion && ((ranges[region].isSecure && ((packet.prot.toInt() & Axi4ProtField.secure.value) == 0)) || (ranges[region].isPrivileged && ((packet.prot.toInt() & Axi4ProtField.privileged.value) == 0))); - + final accessError = inRegion && + ((ranges[region].isSecure && + ((packet.prot.toInt() & Axi4ProtField.secure.value) == + 0)) || + (ranges[region].isPrivileged && + ((packet.prot.toInt() & Axi4ProtField.privileged.value) == + 0))); + final error = respondWithError != null && respondWithError!(packet); // for now, only support sending slvErr and okay as responses - wIntfs[index].bValid.put(true); - wIntfs[index].bId?.put(packet.id); - wIntfs[index].bResp?.put(error || accessError - ? LogicValue.ofInt( - Axi4RespField.slvErr.value, wIntfs[index].bResp!.width) - : LogicValue.ofInt( - Axi4RespField.okay.value, wIntfs[index].bResp!.width)); - wIntfs[index].bUser?.put(0); // don't support user field for now + wIntf.bValid.put(true); + wIntf.bId?.put(packet.id); + wIntf.bResp?.put(error || accessError + ? LogicValue.ofInt(Axi4RespField.slvErr.value, wIntf.bResp!.width) + : LogicValue.ofInt(Axi4RespField.okay.value, wIntf.bResp!.width)); + wIntf.bUser?.put(0); // don't support user field for now // TODO: how to deal with delays?? // if (readResponseDelay != null) { @@ -467,19 +497,18 @@ class Axi4SubordinateAgent extends Agent { .rswizzle() : strobedData; storage.writeData(addrToWrite, wrData); - if (inRegion && (addrToWrite + increment >= ranges[region].end).toBool()) { + if (inRegion && + (addrToWrite + increment >= ranges[region].end).toBool()) { // the original access fell in a region but the next access in // the burst overflows the region if (packet.burst!.toInt() == Axi4BurstField.wrap.value) { // indication that we should wrap around back to the region start addrToWrite = ranges[region].start; - } - else { + } else { // OK to overflow addrToWrite = addrToWrite + increment; } - } - else { + } else { // no region or overflow addrToWrite = addrToWrite + increment; } @@ -487,13 +516,13 @@ class Axi4SubordinateAgent extends Agent { } // pop this write response off the queue - _writeMetadataQueue[index].removeAt(0); - _writeReadyToOccur[index] = false; + _writeMetadataQueue[mapIdx].removeAt(0); + _writeReadyToOccur[mapIdx] = false; - logger.info('Sent write response on interface $index.'); + logger.info('Sent write response on channel $index.'); } } else { - wIntfs[index].bValid.put(false); + wIntf.bValid.put(false); } } } diff --git a/test/axi4_bfm_test.dart b/test/axi4_bfm_test.dart index 93c0ffcc3..f29f6d31a 100644 --- a/test/axi4_bfm_test.dart +++ b/test/axi4_bfm_test.dart @@ -17,13 +17,14 @@ import 'package:test/test.dart'; import 'axi4_test.dart'; +enum Axi4BfmTestChannelConfig { read, write, readWrite } + class Axi4BfmTest extends Test { late final Axi4SystemInterface sIntf; - final int numReads; - final int numWrites; - final List rIntf = []; - final List wIntf = []; + final int numChannels; + final List channelConfigs; + final List channels = []; late final Axi4MainAgent main; @@ -48,10 +49,14 @@ class Axi4BfmTest extends Test { String get outFolder => 'tmp_test/axi4bfm/$name/'; + bool get hasAnyReads => channels.any((element) => element.hasRead); + + bool get hasAnyWrites => channels.any((element) => element.hasWrite); + Axi4BfmTest( super.name, { - this.numReads = 1, - this.numWrites = 1, + this.numChannels = 1, + this.channelConfigs = const [Axi4BfmTestChannelConfig.readWrite], this.numTransfers = 10, this.withStrobes = false, this.interTxnDelay = 0, @@ -60,26 +65,53 @@ class Axi4BfmTest extends Test { this.addrWidth = 32, this.dataWidth = 32, this.lenWidth = 2, - }) : super(randomSeed: 123) { + }) : assert(numChannels > 0, 'Every test must have at least one channel.'), + assert(numChannels == channelConfigs.length, + 'Every channel must have a config.'), + super(randomSeed: 123) { // using default parameter values for all interfaces sIntf = Axi4SystemInterface(); - for (var i = 0; i < numReads; i++) { - rIntf.add(Axi4ReadInterface( - addrWidth: addrWidth, - dataWidth: dataWidth, - lenWidth: lenWidth, - ruserWidth: dataWidth ~/ 2 - 1, - )); - Axi4ReadComplianceChecker(sIntf, rIntf.last, parent: this); - } - for (var i = 0; i < numWrites; i++) { - wIntf.add(Axi4WriteInterface( - addrWidth: addrWidth, - dataWidth: dataWidth, - lenWidth: lenWidth, - wuserWidth: dataWidth ~/ 2 - 1, - )); - Axi4WriteComplianceChecker(sIntf, wIntf.last, parent: this); + for (var i = 0; i < numChannels; i++) { + if (channelConfigs[i] == Axi4BfmTestChannelConfig.readWrite) { + channels.add(Axi4Channel( + rIntf: Axi4ReadInterface( + addrWidth: addrWidth, + dataWidth: dataWidth, + lenWidth: lenWidth, + ruserWidth: dataWidth ~/ 2 - 1, + ), + wIntf: Axi4WriteInterface( + addrWidth: addrWidth, + dataWidth: dataWidth, + lenWidth: lenWidth, + wuserWidth: dataWidth ~/ 2 - 1, + ), + )); + Axi4ReadComplianceChecker(sIntf, channels.last.rIntf!, parent: this); + Axi4WriteComplianceChecker(sIntf, channels.last.wIntf!, parent: this); + } else if (channelConfigs[i] == Axi4BfmTestChannelConfig.read) { + channels.add(Axi4Channel( + hasWrite: false, + rIntf: Axi4ReadInterface( + addrWidth: addrWidth, + dataWidth: dataWidth, + lenWidth: lenWidth, + ruserWidth: dataWidth ~/ 2 - 1, + ), + )); + Axi4ReadComplianceChecker(sIntf, channels.last.rIntf!, parent: this); + } else if (channelConfigs[i] == Axi4BfmTestChannelConfig.write) { + channels.add(Axi4Channel( + hasRead: false, + wIntf: Axi4WriteInterface( + addrWidth: addrWidth, + dataWidth: dataWidth, + lenWidth: lenWidth, + wuserWidth: dataWidth ~/ 2 - 1, + ), + )); + Axi4WriteComplianceChecker(sIntf, channels.last.wIntf!, parent: this); + } } storage = SparseMemoryStorage( @@ -91,13 +123,11 @@ class Axi4BfmTest extends Test { sIntf.clk <= SimpleClockGenerator(10).clk; - main = - Axi4MainAgent(sIntf: sIntf, rIntfs: rIntf, wIntfs: wIntf, parent: this); + main = Axi4MainAgent(sIntf: sIntf, channels: channels, parent: this); Axi4SubordinateAgent( sIntf: sIntf, - rIntfs: rIntf, - wIntfs: wIntf, + channels: channels, parent: this, storage: storage, readResponseDelay: @@ -127,11 +157,9 @@ class Axi4BfmTest extends Test { // Directory(outFolder).deleteSync(recursive: true); }); - for (var i = 0; i < numReads; i++) { - main.rdMonitors[i].stream.listen(tracker.record); - } - for (var i = 0; i < numWrites; i++) { - main.wrMonitors[i].stream.listen(tracker.record); + for (var i = 0; i < numChannels; i++) { + main.getRdMonitor(i)?.stream.listen(tracker.record); + main.getWrMonitor(i)?.stream.listen(tracker.record); } } @@ -160,83 +188,97 @@ class Axi4BfmTest extends Test { final strobes = >[]; // normal writes - for (var i = 0; i < numTransfers; i++) { - // pick a random write interface - final currW = Test.random!.nextInt(wIntf.length); - final wIntfC = wIntf[currW]; - - // generate a completely random access - final transLen = Test.random!.nextInt(1 << wIntfC.lenWidth); - final transSize = - Test.random!.nextInt(1 << wIntfC.sizeWidth) % (dataWidth ~/ 8); - final randomData = List.generate( - transLen + 1, - (index) => LogicValue.ofInt( - Test.random!.nextInt(1 << wIntfC.dataWidth), wIntfC.dataWidth)); - final randomStrobes = List.generate( - transLen + 1, - (index) => withStrobes - ? LogicValue.ofInt( - Test.random!.nextInt(1 << wIntfC.strbWidth), wIntfC.strbWidth) - : LogicValue.filled(wIntfC.strbWidth, LogicValue.one)); - lens.add(transLen); - sizes.add(transSize); - data.add(randomData); - strobes.add(randomStrobes); - - final wrPkt = Axi4WriteRequestPacket( - addr: LogicValue.ofInt(i, 32), - prot: LogicValue.ofInt(0, wIntfC.protWidth), // not supported - data: randomData, - id: LogicValue.ofInt(i, wIntfC.idWidth), - len: LogicValue.ofInt(transLen, wIntfC.lenWidth), - size: LogicValue.ofInt(transSize, wIntfC.sizeWidth), - burst: LogicValue.ofInt( - Axi4BurstField.incr.value, wIntfC.burstWidth), // fixed for now - lock: LogicValue.ofInt(0, 1), // not supported - cache: LogicValue.ofInt(0, wIntfC.cacheWidth), // not supported - qos: LogicValue.ofInt(0, wIntfC.qosWidth), // not supported - region: LogicValue.ofInt(0, wIntfC.regionWidth), // not supported - user: LogicValue.ofInt(0, wIntfC.awuserWidth), // not supported - strobe: randomStrobes, - wUser: LogicValue.ofInt(0, wIntfC.wuserWidth), // not supported - ); - - main.wrSequencers[currW].add(wrPkt); - numTransfersCompleted++; - - // Note that driver will already serialize the writes - await sIntf.clk.waitCycles(mandatoryTransWaitPeriod); - await sIntf.clk.waitCycles(interTxnDelay); + if (hasAnyWrites) { + for (var i = 0; i < numTransfers; i++) { + // pick a random write interface + var foundWrChannel = false; + var currW = 0; + while (!foundWrChannel) { + currW = Test.random!.nextInt(channels.length); + foundWrChannel = channels[currW].hasWrite; + } + final wIntfC = channels[currW].wIntf!; + + // generate a completely random access + final transLen = Test.random!.nextInt(1 << wIntfC.lenWidth); + final transSize = + Test.random!.nextInt(1 << wIntfC.sizeWidth) % (dataWidth ~/ 8); + final randomData = List.generate( + transLen + 1, + (index) => LogicValue.ofInt( + Test.random!.nextInt(1 << wIntfC.dataWidth), wIntfC.dataWidth)); + final randomStrobes = List.generate( + transLen + 1, + (index) => withStrobes + ? LogicValue.ofInt(Test.random!.nextInt(1 << wIntfC.strbWidth), + wIntfC.strbWidth) + : LogicValue.filled(wIntfC.strbWidth, LogicValue.one)); + lens.add(transLen); + sizes.add(transSize); + data.add(randomData); + strobes.add(randomStrobes); + + final wrPkt = Axi4WriteRequestPacket( + addr: LogicValue.ofInt(i, 32), + prot: LogicValue.ofInt(0, wIntfC.protWidth), // not supported + data: randomData, + id: LogicValue.ofInt(i, wIntfC.idWidth), + len: LogicValue.ofInt(transLen, wIntfC.lenWidth), + size: LogicValue.ofInt(transSize, wIntfC.sizeWidth), + burst: LogicValue.ofInt( + Axi4BurstField.incr.value, wIntfC.burstWidth), // fixed for now + lock: LogicValue.ofInt(0, 1), // not supported + cache: LogicValue.ofInt(0, wIntfC.cacheWidth), // not supported + qos: LogicValue.ofInt(0, wIntfC.qosWidth), // not supported + region: LogicValue.ofInt(0, wIntfC.regionWidth), // not supported + user: LogicValue.ofInt(0, wIntfC.awuserWidth), // not supported + strobe: randomStrobes, + wUser: LogicValue.ofInt(0, wIntfC.wuserWidth), // not supported + ); + + main.getWrSequencer(currW)!.add(wrPkt); + numTransfersCompleted++; + + // Note that driver will already serialize the writes + await sIntf.clk.waitCycles(mandatoryTransWaitPeriod); + await sIntf.clk.waitCycles(interTxnDelay); + } } // normal reads that check data - for (var i = 0; i < numTransfers; i++) { - // pick a random read interface - final currR = Test.random!.nextInt(rIntf.length); - final rIntfC = rIntf[currR]; - - final rdPkt = Axi4ReadRequestPacket( - addr: LogicValue.ofInt(i, 32), - prot: LogicValue.ofInt(0, rIntfC.protWidth), // not supported - id: LogicValue.ofInt(i, rIntfC.idWidth), - len: LogicValue.ofInt(lens[i], rIntfC.lenWidth), - size: LogicValue.ofInt(sizes[i], rIntfC.sizeWidth), - burst: LogicValue.ofInt( - Axi4BurstField.incr.value, rIntfC.burstWidth), // fixed for now - lock: LogicValue.ofInt(0, 1), // not supported - cache: LogicValue.ofInt(0, rIntfC.cacheWidth), // not supported - qos: LogicValue.ofInt(0, rIntfC.qosWidth), // not supported - region: LogicValue.ofInt(0, rIntfC.regionWidth), // not supported - user: LogicValue.ofInt(0, rIntfC.aruserWidth), // not supported - ); - - main.rdSequencers[currR].add(rdPkt); - numTransfersCompleted++; - - // Note that driver will already serialize the reads - await sIntf.clk.waitCycles(mandatoryTransWaitPeriod); - await sIntf.clk.waitCycles(interTxnDelay); + if (hasAnyReads) { + for (var i = 0; i < numTransfers; i++) { + // pick a random read interface + var foundRdChannel = false; + var currR = 0; + while (!foundRdChannel) { + currR = Test.random!.nextInt(channels.length); + foundRdChannel = channels[currR].hasRead; + } + final rIntfC = channels[currR].rIntf!; + + final rdPkt = Axi4ReadRequestPacket( + addr: LogicValue.ofInt(i, 32), + prot: LogicValue.ofInt(0, rIntfC.protWidth), // not supported + id: LogicValue.ofInt(i, rIntfC.idWidth), + len: LogicValue.ofInt(lens[i], rIntfC.lenWidth), + size: LogicValue.ofInt(sizes[i], rIntfC.sizeWidth), + burst: LogicValue.ofInt( + Axi4BurstField.incr.value, rIntfC.burstWidth), // fixed for now + lock: LogicValue.ofInt(0, 1), // not supported + cache: LogicValue.ofInt(0, rIntfC.cacheWidth), // not supported + qos: LogicValue.ofInt(0, rIntfC.qosWidth), // not supported + region: LogicValue.ofInt(0, rIntfC.regionWidth), // not supported + user: LogicValue.ofInt(0, rIntfC.aruserWidth), // not supported + ); + + main.getRdSequencer(currR)!.add(rdPkt); + numTransfersCompleted++; + + // Note that driver will already serialize the reads + await sIntf.clk.waitCycles(mandatoryTransWaitPeriod); + await sIntf.clk.waitCycles(interTxnDelay); + } } obj.drop(); @@ -269,8 +311,7 @@ void main() { Simulator.setMaxSimTime(30000); if (dumpWaves) { - final mod = Axi4Subordinate( - axi4BfmTest.sIntf, axi4BfmTest.rIntf, axi4BfmTest.wIntf); + final mod = Axi4Subordinate(axi4BfmTest.sIntf, axi4BfmTest.channels); await mod.build(); WaveDumper(mod); } @@ -314,8 +355,13 @@ void main() { await runTest(Axi4BfmTest( 'randeverything', numTransfers: 20, - numReads: 4, - numWrites: 8, + numChannels: 4, + channelConfigs: [ + Axi4BfmTestChannelConfig.read, + Axi4BfmTestChannelConfig.write, + Axi4BfmTestChannelConfig.readWrite, + Axi4BfmTestChannelConfig.write, + ], withRandomRspDelays: true, withStrobes: true, interTxnDelay: 3, diff --git a/test/axi4_test.dart b/test/axi4_test.dart index 563c8f03b..6d27a0714 100644 --- a/test/axi4_test.dart +++ b/test/axi4_test.dart @@ -11,52 +11,68 @@ import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:test/test.dart'; +import 'axi4_bfm_test.dart'; + class Axi4Subordinate extends Module { - Axi4Subordinate(Axi4SystemInterface sIntf, List rIntfs, - List wIntfs) { + Axi4Subordinate(Axi4SystemInterface sIntf, List channels) { sIntf = Axi4SystemInterface() ..connectIO(this, sIntf, inputTags: {Axi4Direction.misc}); - for (var i = 0; i < rIntfs.length; i++) { - rIntfs.add(Axi4ReadInterface.clone(rIntfs[i]) - ..connectIO(this, rIntfs[i], - inputTags: {Axi4Direction.fromMain}, - outputTags: {Axi4Direction.fromSubordinate})); - } - - for (var i = 0; i < wIntfs.length; i++) { - wIntfs.add(Axi4WriteInterface.clone(wIntfs[i]) - ..connectIO(this, wIntfs[i], - inputTags: {Axi4Direction.fromMain}, - outputTags: {Axi4Direction.fromSubordinate})); + final channelsL = []; + for (var i = 0; i < channels.length; i++) { + channelsL.add(Axi4Channel( + channelId: channels[i].channelId, + hasRead: channels[i].hasRead, + hasWrite: channels[i].hasWrite, + rIntf: channels[i].hasRead + ? (Axi4ReadInterface.clone(channels[i].rIntf!) + ..connectIO(this, channels[i].rIntf!, + inputTags: {Axi4Direction.fromMain}, + outputTags: {Axi4Direction.fromSubordinate})) + : null, + wIntf: channels[i].hasWrite + ? (Axi4WriteInterface.clone(channels[i].wIntf!) + ..connectIO(this, channels[i].wIntf!, + inputTags: {Axi4Direction.fromMain}, + outputTags: {Axi4Direction.fromSubordinate})) + : null)); } } } class Axi4Main extends Module { - Axi4Main(Axi4SystemInterface sIntf, List rIntfs, - List wIntfs) { + Axi4Main(Axi4SystemInterface sIntf, List channels) { sIntf = Axi4SystemInterface() ..connectIO(this, sIntf, inputTags: {Axi4Direction.misc}); - for (var i = 0; i < rIntfs.length; i++) { - rIntfs.add(Axi4ReadInterface.clone(rIntfs[i]) - ..connectIO(this, rIntfs[i], - inputTags: {Axi4Direction.fromSubordinate}, - outputTags: {Axi4Direction.fromMain})); - } - - for (var i = 0; i < wIntfs.length; i++) { - wIntfs.add(Axi4WriteInterface.clone(wIntfs[i]) - ..connectIO(this, wIntfs[i], - inputTags: {Axi4Direction.fromSubordinate}, - outputTags: {Axi4Direction.fromMain})); + final channelsL = []; + for (var i = 0; i < channels.length; i++) { + channelsL.add(Axi4Channel( + channelId: channels[i].channelId, + hasRead: channels[i].hasRead, + hasWrite: channels[i].hasWrite, + rIntf: channels[i].hasRead + ? (Axi4ReadInterface.clone(channels[i].rIntf!) + ..connectIO(this, channels[i].rIntf!, + inputTags: {Axi4Direction.fromSubordinate}, + outputTags: {Axi4Direction.fromMain})) + : null, + wIntf: channels[i].hasWrite + ? (Axi4WriteInterface.clone(channels[i].wIntf!) + ..connectIO(this, channels[i].wIntf!, + inputTags: {Axi4Direction.fromSubordinate}, + outputTags: {Axi4Direction.fromMain})) + : null)); } } } class Axi4Pair extends Module { - Axi4Pair(Logic clk, Logic reset, {int numReads = 1, int numWrites = 1}) { + Axi4Pair(Logic clk, Logic reset, + {int numChannels = 1, + List channelConfigs = const [ + Axi4BfmTestChannelConfig.readWrite + ]}) { clk = addInput('clk', clk); reset = addInput('reset', reset); @@ -64,18 +80,22 @@ class Axi4Pair extends Module { sIntf.clk <= clk; sIntf.resetN <= ~reset; - final rIntf = []; - for (var i = 0; i < numReads; i++) { - rIntf.add(Axi4ReadInterface()); - } - - final wIntf = []; - for (var i = 0; i < numWrites; i++) { - wIntf.add(Axi4WriteInterface()); + final channels = []; + for (var i = 0; i < numChannels; i++) { + final hasRead = channelConfigs[i] == Axi4BfmTestChannelConfig.read || + channelConfigs[i] == Axi4BfmTestChannelConfig.readWrite; + final hasWrite = channelConfigs[i] == Axi4BfmTestChannelConfig.write || + channelConfigs[i] == Axi4BfmTestChannelConfig.readWrite; + channels.add(Axi4Channel( + channelId: i, + hasRead: hasRead, + hasWrite: hasWrite, + rIntf: hasRead ? Axi4ReadInterface() : null, + wIntf: hasWrite ? Axi4WriteInterface() : null)); } - Axi4Main(sIntf, rIntf, wIntf); - Axi4Subordinate(sIntf, rIntf, wIntf); + Axi4Main(sIntf, channels); + Axi4Subordinate(sIntf, channels); } } From 75d8d85c347ef61c359ac4a991d851a237476d6a Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Mon, 10 Feb 2025 22:54:30 -0800 Subject: [PATCH 15/16] Adding support for lock field. --- lib/src/models/axi4_bfm/axi4_subordinate.dart | 202 ++++++++++++++---- test/axi4_bfm_test.dart | 17 +- 2 files changed, 179 insertions(+), 40 deletions(-) diff --git a/lib/src/models/axi4_bfm/axi4_subordinate.dart b/lib/src/models/axi4_bfm/axi4_subordinate.dart index b4e1a6392..76b417873 100644 --- a/lib/src/models/axi4_bfm/axi4_subordinate.dart +++ b/lib/src/models/axi4_bfm/axi4_subordinate.dart @@ -81,9 +81,13 @@ class Axi4SubordinateAgent extends Agent { /// TODO: ensure non-overlapping?? List ranges = []; + /// Enable locking functionality as per AXI4 spec. + final bool supportLocking; + // to handle response read responses final List> _dataReadResponseMetadataQueue = []; final List>> _dataReadResponseDataQueue = []; + final List> _dataReadResponseErrorQueue = []; final List _dataReadResponseIndex = []; // to handle writes @@ -94,6 +98,10 @@ class Axi4SubordinateAgent extends Agent { final Map _readAddrToChannel = {}; final Map _writeAddrToChannel = {}; + // for locking behavior + final Map _rmwLocks = {}; + final Map _hardLocks = {}; + /// Creates a new model [Axi4SubordinateAgent]. /// /// If no [storage] is provided, it will use a default [SparseMemoryStorage]. @@ -108,6 +116,7 @@ class Axi4SubordinateAgent extends Agent { this.invalidReadDataOnError = true, this.dropWriteDataOnError = true, this.ranges = const [], + this.supportLocking = false, String name = 'axi4SubordinateAgent'}) : super(name, parent) { var maxAddrWidth = 0; @@ -132,6 +141,7 @@ class Axi4SubordinateAgent extends Agent { if (channels[i].hasRead) { _dataReadResponseMetadataQueue.add([]); _dataReadResponseDataQueue.add([]); + _dataReadResponseErrorQueue.add([]); _dataReadResponseIndex.add(0); _readAddrToChannel[i] = _dataReadResponseMetadataQueue.length - 1; } @@ -153,6 +163,7 @@ class Axi4SubordinateAgent extends Agent { if (channels[i].hasRead) { _dataReadResponseMetadataQueue[_readAddrToChannel[i]!].clear(); _dataReadResponseDataQueue[_readAddrToChannel[i]!].clear(); + _dataReadResponseErrorQueue[_readAddrToChannel[i]!].clear(); _dataReadResponseIndex[_readAddrToChannel[i]!] = 0; } if (channels[i].hasWrite) { @@ -247,11 +258,7 @@ class Axi4SubordinateAgent extends Agent { // These can be handled in a derived class of this model if need be. // Because they require implementation specific handling. - // NOTE: generic model doesn't honor the prot field in read requests. - // It will be added as a feature request in the future. - - // NOTE: generic model doesn't honor the lock field in read requests. - // It will be added as a feature request in the future. + // NOTE: generic model doesn't use the instruction/data bit of the prot field. // query storage to retrieve the data final data = []; @@ -269,6 +276,13 @@ class Axi4SubordinateAgent extends Agent { final region = _checkRegion(addrToRead); final inRegion = region >= 0; + // examine locking behavior + final isRmw = supportLocking && + rIntf.arLock?.value.toInt() == Axi4LockField.exclusive.value; + final isHardLock = supportLocking && + rIntf.arLock?.value.toInt() == Axi4LockField.locked.value; + + var hardLockErr = false; for (var i = 0; i < endCount; i++) { var currData = storage.readData(addrToRead); if (dSize > 0) { @@ -279,6 +293,47 @@ class Axi4SubordinateAgent extends Agent { } } data.add(currData); + + // lock handling logic + if (supportLocking) { + // part of an rmw operation + if (isRmw) { + // assign the lock to this channel + // regardless if it existed before + _rmwLocks[addrToRead] = index; + } + // hard lock request + else if (isHardLock) { + // there must not be a hard lock on this address + // from another channel + if (_hardLocks.containsKey(addrToRead) && + _hardLocks[addrToRead] != index) { + hardLockErr |= true; + } + // make sure to add the hard lock if it doesn't already exist + else { + _hardLocks[addrToRead] = index; + } + } else { + // remove the rmw lock if it is there + // regardless of what channel we came from + if (_rmwLocks.containsKey(addrToRead)) { + _rmwLocks.remove(addrToRead); + } + + // there must not be a hard lock on this address + // from another channel + if (_hardLocks.containsKey(addrToRead) && + _hardLocks[addrToRead] != index) { + hardLockErr |= true; + } + // if we have a hard lock, we can remove it now + else if (_hardLocks.containsKey(addrToRead)) { + _hardLocks.remove(addrToRead); + } + } + } + if (inRegion && (addrToRead + increment >= ranges[region].end).toBool()) { // the original access fell in a region but the next access in @@ -298,6 +353,7 @@ class Axi4SubordinateAgent extends Agent { _dataReadResponseMetadataQueue[mapIdx].add(packet); _dataReadResponseDataQueue[mapIdx].add(data); + _dataReadResponseErrorQueue[mapIdx].add(hardLockErr); } } @@ -310,8 +366,10 @@ class Axi4SubordinateAgent extends Agent { // and the main side is indicating that it is ready to receive if (_dataReadResponseMetadataQueue[mapIdx].isNotEmpty && _dataReadResponseDataQueue[mapIdx].isNotEmpty && + _dataReadResponseErrorQueue[mapIdx].isNotEmpty && rIntf.rReady.value.toBool()) { final packet = _dataReadResponseMetadataQueue[mapIdx][0]; + final reqSideError = _dataReadResponseErrorQueue[mapIdx][0]; final currData = _dataReadResponseDataQueue[mapIdx][0][_dataReadResponseIndex[mapIdx]]; final error = respondWithError != null && respondWithError!(packet); @@ -341,7 +399,7 @@ class Axi4SubordinateAgent extends Agent { rIntf.rValid.put(true); rIntf.rId?.put(packet.id); rIntf.rData.put(currData); - rIntf.rResp?.put(error | accessError + rIntf.rResp?.put(error || accessError || reqSideError ? LogicValue.ofInt(Axi4RespField.slvErr.value, rIntf.rResp!.width) : LogicValue.ofInt(Axi4RespField.okay.value, rIntf.rResp!.width)); rIntf.rUser?.put(0); // don't support user field for now @@ -352,6 +410,7 @@ class Axi4SubordinateAgent extends Agent { _dataReadResponseIndex[mapIdx] = 0; _dataReadResponseMetadataQueue[mapIdx].removeAt(0); _dataReadResponseDataQueue[mapIdx].removeAt(0); + _dataReadResponseErrorQueue[mapIdx].removeAt(0); logger.info('Finished sending read response for channel $index.'); } else { @@ -446,12 +505,101 @@ class Axi4SubordinateAgent extends Agent { ((packet.prot.toInt() & Axi4ProtField.privileged.value) == 0))); + // examine locking behavior + final isRmw = supportLocking && + packet.lock != null && + packet.lock!.toInt() == Axi4LockField.exclusive.value; + final isHardLock = supportLocking && + packet.lock != null && + packet.lock!.toInt() == Axi4LockField.locked.value; + + // compute data size and increment + final dSize = (packet.size?.toInt() ?? 0) * 8; + var increment = 0; + if (packet.burst == null || + packet.burst!.toInt() == Axi4BurstField.wrap.value || + packet.burst!.toInt() == Axi4BurstField.incr.value) { + increment = dSize ~/ 8; + } + + // compute the addresses to write to + // based on the burst mode, len, and size + final addrsToWrite = []; + for (var i = 0; i < packet.data.length; i++) { + addrsToWrite.add(addrToWrite); + if (inRegion && + (addrToWrite + increment >= ranges[region].end).toBool()) { + // the original access fell in a region but the next access in + // the burst overflows the region + if (packet.burst!.toInt() == Axi4BurstField.wrap.value) { + // indication that we should wrap around back to the region start + addrToWrite = ranges[region].start; + } else { + // OK to overflow + addrToWrite = addrToWrite + increment; + } + } else { + // no region or overflow + addrToWrite = addrToWrite + increment; + } + } + + // locking logic for write ops + var rmwErr = false; + var hardLockErr = false; + if (supportLocking) { + for (final addr in addrsToWrite) { + // given write is rmw locked + if (isRmw) { + // rmw lock must be associated with our channel + // if not, must respond with an error + // also remove the lock moving forward + if (!_rmwLocks.containsKey(addr) || _rmwLocks[addr] != index) { + rmwErr |= true; + if (_rmwLocks.containsKey(addr)) { + _rmwLocks.remove(addr); + } + } + } + // given write is hard locked + else if (isHardLock) { + // there must not be a hard lock on this address + // from another channel + if (_hardLocks.containsKey(addr) && _hardLocks[addr] != index) { + hardLockErr |= true; + } + // make sure to add the hard lock if it doesn't already exist + else { + _hardLocks[addr] = index; + } + } + // given write is not rmw locked + else { + // remove the rmw lock if it is there + // regardless of what channel we came from + if (_rmwLocks.containsKey(addr)) { + _rmwLocks.remove(addr); + } + + // there must not be a hard lock on this address + // from another channel + if (_hardLocks.containsKey(addr) && _hardLocks[addr] != index) { + hardLockErr |= true; + } + // if we have a hard lock, we can remove it now + else if (_hardLocks.containsKey(addr)) { + _hardLocks.remove(addr); + } + } + } + } + final error = respondWithError != null && respondWithError!(packet); // for now, only support sending slvErr and okay as responses wIntf.bValid.put(true); wIntf.bId?.put(packet.id); - wIntf.bResp?.put(error || accessError + wIntf.bResp?.put(error || accessError || rmwErr || hardLockErr ? LogicValue.ofInt(Axi4RespField.slvErr.value, wIntf.bResp!.width) : LogicValue.ofInt(Axi4RespField.okay.value, wIntf.bResp!.width)); wIntf.bUser?.put(0); // don't support user field for now @@ -472,24 +620,17 @@ class Axi4SubordinateAgent extends Agent { // These can be handled in a derived class of this model if need be. // Because they require implementation specific handling. - // NOTE: generic model doesn't honor the prot field in write requests. - // It will be added as a feature request in the future. - - // NOTE: generic model doesn't honor the lock field in write requests. - // It will be added as a feature request in the future. - - if (!error && !dropWriteDataOnError && !accessError) { - // write the data to the storage - final dSize = (packet.size?.toInt() ?? 0) * 8; - var increment = 0; - if (packet.burst == null || - packet.burst!.toInt() == Axi4BurstField.wrap.value || - packet.burst!.toInt() == Axi4BurstField.incr.value) { - increment = dSize ~/ 8; - } + // NOTE: generic model doesn't use the instruction/data bit of the prot field. + // apply the write to storage + // only if there were no errors + if (!error && + !dropWriteDataOnError && + !accessError && + !rmwErr && + !hardLockErr) { for (var i = 0; i < packet.data.length; i++) { - final rdData = storage.readData(addrToWrite); + final rdData = storage.readData(addrsToWrite[i]); final strobedData = _strobeData(rdData, packet.data[i], packet.strobe[i]); final wrData = (dSize < strobedData.width) @@ -497,21 +638,6 @@ class Axi4SubordinateAgent extends Agent { .rswizzle() : strobedData; storage.writeData(addrToWrite, wrData); - if (inRegion && - (addrToWrite + increment >= ranges[region].end).toBool()) { - // the original access fell in a region but the next access in - // the burst overflows the region - if (packet.burst!.toInt() == Axi4BurstField.wrap.value) { - // indication that we should wrap around back to the region start - addrToWrite = ranges[region].start; - } else { - // OK to overflow - addrToWrite = addrToWrite + increment; - } - } else { - // no region or overflow - addrToWrite = addrToWrite + increment; - } } } diff --git a/test/axi4_bfm_test.dart b/test/axi4_bfm_test.dart index f29f6d31a..fb0f97a4e 100644 --- a/test/axi4_bfm_test.dart +++ b/test/axi4_bfm_test.dart @@ -47,6 +47,8 @@ class Axi4BfmTest extends Test { // large lens can make transactions really long... final int lenWidth; + final bool supportLocking; + String get outFolder => 'tmp_test/axi4bfm/$name/'; bool get hasAnyReads => channels.any((element) => element.hasRead); @@ -65,6 +67,7 @@ class Axi4BfmTest extends Test { this.addrWidth = 32, this.dataWidth = 32, this.lenWidth = 2, + this.supportLocking = false, }) : assert(numChannels > 0, 'Every test must have at least one channel.'), assert(numChannels == channelConfigs.length, 'Every channel must have a config.'), @@ -74,6 +77,7 @@ class Axi4BfmTest extends Test { for (var i = 0; i < numChannels; i++) { if (channelConfigs[i] == Axi4BfmTestChannelConfig.readWrite) { channels.add(Axi4Channel( + channelId: i, rIntf: Axi4ReadInterface( addrWidth: addrWidth, dataWidth: dataWidth, @@ -91,6 +95,7 @@ class Axi4BfmTest extends Test { Axi4WriteComplianceChecker(sIntf, channels.last.wIntf!, parent: this); } else if (channelConfigs[i] == Axi4BfmTestChannelConfig.read) { channels.add(Axi4Channel( + channelId: i, hasWrite: false, rIntf: Axi4ReadInterface( addrWidth: addrWidth, @@ -102,6 +107,7 @@ class Axi4BfmTest extends Test { Axi4ReadComplianceChecker(sIntf, channels.last.rIntf!, parent: this); } else if (channelConfigs[i] == Axi4BfmTestChannelConfig.write) { channels.add(Axi4Channel( + channelId: i, hasRead: false, wIntf: Axi4WriteInterface( addrWidth: addrWidth, @@ -135,6 +141,7 @@ class Axi4BfmTest extends Test { writeResponseDelay: withRandomRspDelays ? (request) => Test.random!.nextInt(5) : null, respondWithError: withErrors ? (request) => true : null, + supportLocking: supportLocking, ); Directory(outFolder).createSync(recursive: true); @@ -186,6 +193,7 @@ class Axi4BfmTest extends Test { final sizes = []; final data = >[]; final strobes = >[]; + final locks = []; // normal writes if (hasAnyWrites) { @@ -213,10 +221,14 @@ class Axi4BfmTest extends Test { ? LogicValue.ofInt(Test.random!.nextInt(1 << wIntfC.strbWidth), wIntfC.strbWidth) : LogicValue.filled(wIntfC.strbWidth, LogicValue.one)); + final randomLock = (supportLocking + ? (Test.random!.nextInt(2) == 1 ? LogicValue.one : LogicValue.zero) + : LogicValue.zero); lens.add(transLen); sizes.add(transSize); data.add(randomData); strobes.add(randomStrobes); + locks.add(randomLock); final wrPkt = Axi4WriteRequestPacket( addr: LogicValue.ofInt(i, 32), @@ -227,7 +239,7 @@ class Axi4BfmTest extends Test { size: LogicValue.ofInt(transSize, wIntfC.sizeWidth), burst: LogicValue.ofInt( Axi4BurstField.incr.value, wIntfC.burstWidth), // fixed for now - lock: LogicValue.ofInt(0, 1), // not supported + lock: randomLock, cache: LogicValue.ofInt(0, wIntfC.cacheWidth), // not supported qos: LogicValue.ofInt(0, wIntfC.qosWidth), // not supported region: LogicValue.ofInt(0, wIntfC.regionWidth), // not supported @@ -265,7 +277,7 @@ class Axi4BfmTest extends Test { size: LogicValue.ofInt(sizes[i], rIntfC.sizeWidth), burst: LogicValue.ofInt( Axi4BurstField.incr.value, rIntfC.burstWidth), // fixed for now - lock: LogicValue.ofInt(0, 1), // not supported + lock: locks[i], cache: LogicValue.ofInt(0, rIntfC.cacheWidth), // not supported qos: LogicValue.ofInt(0, rIntfC.qosWidth), // not supported region: LogicValue.ofInt(0, rIntfC.regionWidth), // not supported @@ -365,6 +377,7 @@ void main() { withRandomRspDelays: true, withStrobes: true, interTxnDelay: 3, + supportLocking: true, )); }); From 9c1dbf19a517ecc2162de19a9b0ca0acc03b7679 Mon Sep 17 00:00:00 2001 From: Josh Kimmel Date: Fri, 21 Feb 2025 11:27:25 -0800 Subject: [PATCH 16/16] Non functional tweaks based on feedback. --- lib/src/models/axi4_bfm/axi4_bfm.dart | 9 +- .../axi4_bfm/axi4_compliance_checker.dart | 270 ----------- lib/src/models/axi4_bfm/axi4_main.dart | 14 +- lib/src/models/axi4_bfm/axi4_main_driver.dart | 436 ------------------ .../axi4_bfm/axi4_main_read_driver.dart | 122 +++++ .../axi4_bfm/axi4_main_write_driver.dart | 146 ++++++ lib/src/models/axi4_bfm/axi4_monitor.dart | 330 ------------- lib/src/models/axi4_bfm/axi4_packet.dart | 2 +- .../axi4_read_compliance_checker.dart | 85 ++++ .../models/axi4_bfm/axi4_read_monitor.dart | 97 ++++ lib/src/models/axi4_bfm/axi4_subordinate.dart | 30 +- .../axi4_write_compliance_checker.dart | 83 ++++ .../models/axi4_bfm/axi4_write_monitor.dart | 101 ++++ test/axi4_bfm_test.dart | 2 - test/axi4_test.dart | 6 - 15 files changed, 661 insertions(+), 1072 deletions(-) delete mode 100644 lib/src/models/axi4_bfm/axi4_compliance_checker.dart delete mode 100644 lib/src/models/axi4_bfm/axi4_main_driver.dart create mode 100644 lib/src/models/axi4_bfm/axi4_main_read_driver.dart create mode 100644 lib/src/models/axi4_bfm/axi4_main_write_driver.dart delete mode 100644 lib/src/models/axi4_bfm/axi4_monitor.dart create mode 100644 lib/src/models/axi4_bfm/axi4_read_compliance_checker.dart create mode 100644 lib/src/models/axi4_bfm/axi4_read_monitor.dart create mode 100644 lib/src/models/axi4_bfm/axi4_write_compliance_checker.dart create mode 100644 lib/src/models/axi4_bfm/axi4_write_monitor.dart diff --git a/lib/src/models/axi4_bfm/axi4_bfm.dart b/lib/src/models/axi4_bfm/axi4_bfm.dart index d0ef643b7..0bcead1ee 100644 --- a/lib/src/models/axi4_bfm/axi4_bfm.dart +++ b/lib/src/models/axi4_bfm/axi4_bfm.dart @@ -1,10 +1,13 @@ // Copyright (C) 2024-2025 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause -export 'axi4_compliance_checker.dart'; export 'axi4_main.dart'; -export 'axi4_main_driver.dart'; -export 'axi4_monitor.dart'; +export 'axi4_main_read_driver.dart'; +export 'axi4_main_write_driver.dart'; export 'axi4_packet.dart'; +export 'axi4_read_compliance_checker.dart'; +export 'axi4_read_monitor.dart'; export 'axi4_subordinate.dart'; export 'axi4_tracker.dart'; +export 'axi4_write_compliance_checker.dart'; +export 'axi4_write_monitor.dart'; diff --git a/lib/src/models/axi4_bfm/axi4_compliance_checker.dart b/lib/src/models/axi4_bfm/axi4_compliance_checker.dart deleted file mode 100644 index d6d819ecc..000000000 --- a/lib/src/models/axi4_bfm/axi4_compliance_checker.dart +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright (C) 2024-2025 Intel Corporation -// SPDX-License-Identifier: BSD-3-Clause -// -// axi4_compliance_checker.dart -// Compliance checking for AXI4. -// -// 2025 January -// Author: Josh Kimmel - -import 'dart:async'; - -import 'package:rohd_hcl/rohd_hcl.dart'; -import 'package:rohd_vf/rohd_vf.dart'; - -// /// A checker for some of the rules defined in the AXI4 interface specification. -// /// -// /// This does not necessarily cover all rules defined in the spec. -// class Axi4ComplianceChecker extends Component { -// /// AXI4 System Interface. -// final Axi4SystemInterface sIntf; - -// /// AXI4 Read Interface. -// final Axi4ReadInterface rIntf; - -// /// AXI4 Write Interface. -// final Axi4WriteInterface wIntf; - -// /// Creates a new compliance checker for AXI4. -// Axi4ComplianceChecker( -// this.sIntf, -// this.rIntf, -// this.wIntf, { -// required Component parent, -// String name = 'axi4ComplianceChecker', -// }) : super(name, parent); - -// @override -// Future run(Phase phase) async { -// unawaited(super.run(phase)); - -// // wait for reset to complete -// await sIntf.resetN.nextPosedge; - -// // checks to run -// // READ REQUESTS -// // number of flits returned matches ARLEN if no error -// // if RLAST is present, asserted on the final flit only -// // if RID is present, every read response should match -// // a pending request ARID -// // WRITE REQUESTS -// // number of flits sent matches AWLEN -// // WLAST is asserted on the final flit only -// // if BID is present, every write response should match -// // a pending request AWID - -// final rLastPresent = rIntf.rLast != null; - -// final readReqMap = >{}; -// final writeReqMap = >{}; -// var lastWriteReqId = -1; - -// sIntf.clk.posedge.listen((event) { -// // capture read requests for counting -// if (rIntf.arValid.previousValue!.isValid && -// rIntf.arValid.previousValue!.toBool()) { -// final id = rIntf.arId?.previousValue?.toInt() ?? 0; -// final len = (rIntf.arLen?.previousValue?.toInt() ?? 0) + 1; -// readReqMap[id] = [len, 0]; -// } - -// // track read response flits -// if (rIntf.rValid.previousValue!.isValid && -// rIntf.rValid.previousValue!.toBool()) { -// final id = rIntf.rId?.previousValue?.toInt() ?? 0; -// if (!readReqMap.containsKey(id)) { -// logger.severe( -// 'Cannot match a read response to any pending read request. ' -// 'ID captured by the response was $id.'); -// } - -// readReqMap[id]![1] = readReqMap[id]![1] + 1; -// final len = readReqMap[id]![0]; -// final currCount = readReqMap[id]![1]; -// if (currCount > len) { -// logger.severe( -// 'Received more read response data flits than indicated by the ' -// 'request with ID $id ARLEN. Expected $len but got $currCount'); -// } else if (currCount == len && -// rLastPresent && -// !rIntf.rLast!.previousValue!.toBool()) { -// logger.severe('Received the final flit in the read response data per ' -// 'the request with ID $id ARLEN but RLAST is not asserted.'); -// } -// } - -// // track write requests -// if (wIntf.awValid.previousValue!.isValid && -// wIntf.awValid.previousValue!.toBool()) { -// final id = wIntf.awId?.previousValue?.toInt() ?? 0; -// final len = (wIntf.awLen?.previousValue?.toInt() ?? 0) + 1; -// writeReqMap[id] = [len, 0]; -// lastWriteReqId = id; -// } - -// // track write data flits -// if (wIntf.wValid.previousValue!.isValid && -// wIntf.wValid.previousValue!.toBool()) { -// final id = lastWriteReqId; -// if (!writeReqMap.containsKey(id)) { -// logger.severe('There is no pending write request ' -// 'to associate with valid write data.'); -// } - -// writeReqMap[id]![1] = writeReqMap[id]![1] + 1; -// final len = writeReqMap[id]![0]; -// final currCount = writeReqMap[id]![1]; -// if (currCount > len) { -// logger.severe( -// 'Sent more write data flits than indicated by the request ' -// 'with ID $id AWLEN. Expected $len but sent $currCount'); -// } else if (currCount == len && !wIntf.wLast.previousValue!.toBool()) { -// logger.severe('Sent the final flit in the write data per the request ' -// 'with ID $id AWLEN but WLAST is not asserted.'); -// } -// } -// }); -// } -// } - -/// A checker for some of the rules defined in the AXI4 interface specification. -/// -/// This does not necessarily cover all rules defined in the spec. -class Axi4ReadComplianceChecker extends Component { - /// AXI4 System Interface. - final Axi4SystemInterface sIntf; - - /// AXI4 Read Interface. - final Axi4ReadInterface rIntf; - - /// Creates a new compliance checker for AXI4. - Axi4ReadComplianceChecker( - this.sIntf, - this.rIntf, { - required Component parent, - String name = 'axi4ReadComplianceChecker', - }) : super(name, parent); - - @override - Future run(Phase phase) async { - unawaited(super.run(phase)); - - // wait for reset to complete - await sIntf.resetN.nextPosedge; - - // checks to run - // READ REQUESTS - // number of flits returned matches ARLEN if no error - // if RLAST is present, asserted on the final flit only - // if RID is present, every read response should match - // a pending request ARID - - final rLastPresent = rIntf.rLast != null; - final readReqMap = >{}; - - sIntf.clk.posedge.listen((event) { - // capture read requests for counting - if (rIntf.arValid.previousValue!.isValid && - rIntf.arValid.previousValue!.toBool()) { - final id = rIntf.arId?.previousValue?.toInt() ?? 0; - final len = (rIntf.arLen?.previousValue?.toInt() ?? 0) + 1; - readReqMap[id] = [len, 0]; - } - - // track read response flits - if (rIntf.rValid.previousValue!.isValid && - rIntf.rValid.previousValue!.toBool()) { - final id = rIntf.rId?.previousValue?.toInt() ?? 0; - if (!readReqMap.containsKey(id)) { - logger.severe( - 'Cannot match a read response to any pending read request. ' - 'ID captured by the response was $id.'); - } - - readReqMap[id]![1] = readReqMap[id]![1] + 1; - final len = readReqMap[id]![0]; - final currCount = readReqMap[id]![1]; - if (currCount > len) { - logger.severe( - 'Received more read response data flits than indicated by the ' - 'request with ID $id ARLEN. Expected $len but got $currCount'); - } else if (currCount == len && - rLastPresent && - !rIntf.rLast!.previousValue!.toBool()) { - logger.severe('Received the final flit in the read response data per ' - 'the request with ID $id ARLEN but RLAST is not asserted.'); - } - } - }); - } -} - -/// A checker for some of the rules defined in the AXI4 interface specification. -/// -/// This does not necessarily cover all rules defined in the spec. -class Axi4WriteComplianceChecker extends Component { - /// AXI4 System Interface. - final Axi4SystemInterface sIntf; - - /// AXI4 Write Interface. - final Axi4WriteInterface wIntf; - - /// Creates a new compliance checker for AXI4. - Axi4WriteComplianceChecker( - this.sIntf, - this.wIntf, { - required Component parent, - String name = 'axi4WriteComplianceChecker', - }) : super(name, parent); - - @override - Future run(Phase phase) async { - unawaited(super.run(phase)); - - // wait for reset to complete - await sIntf.resetN.nextPosedge; - - // checks to run - // WRITE REQUESTS - // number of flits sent matches AWLEN - // WLAST is asserted on the final flit only - // if BID is present, every write response should match - // a pending request AWID - - final writeReqMap = >{}; - var lastWriteReqId = -1; - - sIntf.clk.posedge.listen((event) { - // track write requests - if (wIntf.awValid.previousValue!.isValid && - wIntf.awValid.previousValue!.toBool()) { - final id = wIntf.awId?.previousValue?.toInt() ?? 0; - final len = (wIntf.awLen?.previousValue?.toInt() ?? 0) + 1; - writeReqMap[id] = [len, 0]; - lastWriteReqId = id; - } - - // track write data flits - if (wIntf.wValid.previousValue!.isValid && - wIntf.wValid.previousValue!.toBool()) { - final id = lastWriteReqId; - if (!writeReqMap.containsKey(id)) { - logger.severe('There is no pending write request ' - 'to associate with valid write data.'); - } - - writeReqMap[id]![1] = writeReqMap[id]![1] + 1; - final len = writeReqMap[id]![0]; - final currCount = writeReqMap[id]![1]; - if (currCount > len) { - logger.severe( - 'Sent more write data flits than indicated by the request ' - 'with ID $id AWLEN. Expected $len but sent $currCount'); - } else if (currCount == len && !wIntf.wLast.previousValue!.toBool()) { - logger.severe('Sent the final flit in the write data per the request ' - 'with ID $id AWLEN but WLAST is not asserted.'); - } - } - }); - } -} diff --git a/lib/src/models/axi4_bfm/axi4_main.dart b/lib/src/models/axi4_bfm/axi4_main.dart index 99e2a36db..645f83d22 100644 --- a/lib/src/models/axi4_bfm/axi4_main.dart +++ b/lib/src/models/axi4_bfm/axi4_main.dart @@ -36,18 +36,14 @@ class Axi4Channel { /// Constructor. Axi4Channel({ this.channelId = 0, - this.hasRead = true, - this.hasWrite = true, this.rIntf, this.wIntf, - }) : assert( - hasRead || hasWrite, + }) : hasRead = rIntf != null, + hasWrite = wIntf != null, + assert( + rIntf != null || wIntf != null, 'A channel must support either' - ' reads or writes (or both)'), - assert(!hasRead || rIntf != null, - 'A channel that supports reads must have a read interface'), - assert(!hasWrite || wIntf != null, - 'A channel that supports writes must have a write interface'); + ' reads or writes (or both)'); } /// An agent for sending requests on diff --git a/lib/src/models/axi4_bfm/axi4_main_driver.dart b/lib/src/models/axi4_bfm/axi4_main_driver.dart deleted file mode 100644 index 6c413db0b..000000000 --- a/lib/src/models/axi4_bfm/axi4_main_driver.dart +++ /dev/null @@ -1,436 +0,0 @@ -// Copyright (C) 2024-2025 Intel Corporation -// SPDX-License-Identifier: BSD-3-Clause -// -// axi4_main_driver.dart -// A driver for AXI4 requests. -// -// 2025 January -// Author: Josh Kimmel - -import 'dart:async'; - -import 'package:rohd/rohd.dart'; -import 'package:rohd_hcl/src/interfaces/interfaces.dart'; -import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; -import 'package:rohd_vf/rohd_vf.dart'; - -// /// A driver for the [Axi4ReadInterface] and [Axi4WriteInterface] interfaces. -// /// -// /// Driving from the perspective of the Main agent. -// class Axi4MainDriver extends PendingClockedDriver { -// /// AXI4 System Interface. -// final Axi4SystemInterface sIntf; - -// /// AXI4 Read Interface. -// final Axi4ReadInterface rIntf; - -// /// AXI4 Write Interface. -// final Axi4WriteInterface wIntf; - -// /// Creates a new [Axi4MainDriver]. -// Axi4MainDriver({ -// required Component parent, -// required this.sIntf, -// required this.rIntf, -// required this.wIntf, -// required super.sequencer, -// super.timeoutCycles = 500, -// super.dropDelayCycles = 30, -// String name = 'axi4MainDriver', -// }) : super( -// name, -// parent, -// clk: sIntf.clk, -// ); - -// @override -// Future run(Phase phase) async { -// unawaited(super.run(phase)); - -// Simulator.injectAction(() { -// rIntf.arValid.put(0); -// rIntf.arId?.put(0); -// rIntf.arAddr.put(0); -// rIntf.arLen?.put(0); -// rIntf.arSize?.put(0); -// rIntf.arBurst?.put(0); -// rIntf.arLock?.put(0); -// rIntf.arCache?.put(0); -// rIntf.arProt.put(0); -// rIntf.arQos?.put(0); -// rIntf.arRegion?.put(0); -// rIntf.arUser?.put(0); -// rIntf.rReady.put(0); -// wIntf.awValid.put(0); -// wIntf.awId?.put(0); -// wIntf.awAddr.put(0); -// wIntf.awLen?.put(0); -// wIntf.awSize?.put(0); -// wIntf.awBurst?.put(0); -// wIntf.awLock?.put(0); -// wIntf.awCache?.put(0); -// wIntf.awProt.put(0); -// wIntf.awQos?.put(0); -// wIntf.awRegion?.put(0); -// wIntf.awUser?.put(0); -// wIntf.wValid.put(0); -// wIntf.wData.put(0); -// wIntf.wStrb.put(0); -// wIntf.wLast.put(0); -// wIntf.bReady.put(0); -// }); - -// // wait for reset to complete before driving anything -// await sIntf.resetN.nextPosedge; - -// while (!Simulator.simulationHasEnded) { -// if (pendingSeqItems.isNotEmpty) { -// await _drivePacket(pendingSeqItems.removeFirst()); -// } else { -// await sIntf.clk.nextPosedge; -// } -// } -// } - -// /// Drives a packet onto the interface. -// Future _drivePacket(Axi4RequestPacket packet) async { -// print('Driving packet at time ${Simulator.time}'); -// if (packet is Axi4ReadRequestPacket) { -// await _driveReadPacket(packet); -// } else if (packet is Axi4WriteRequestPacket) { -// await _driveWritePacket(packet); -// } else { -// await sIntf.clk.nextPosedge; -// } -// } - -// // TODO: need a more robust way of driving the "ready" signals... -// // RREADY for read data responses -// // BREADY for write responses -// // specifically, when should they toggle on/off? -// // ON => either always or when the associated request is driven? -// // OFF => either never or when there are no more outstanding requests of the given type? -// // should we enable the ability to backpressure?? - -// Future _driveReadPacket(Axi4ReadRequestPacket packet) async { -// await sIntf.clk.nextPosedge; -// Simulator.injectAction(() { -// rIntf.arValid.put(1); -// rIntf.arId?.put(packet.id); -// rIntf.arAddr.put(packet.addr); -// rIntf.arLen?.put(packet.len); -// rIntf.arSize?.put(packet.size); -// rIntf.arBurst?.put(packet.burst); -// rIntf.arLock?.put(packet.lock); -// rIntf.arCache?.put(packet.cache); -// rIntf.arProt.put(packet.prot); -// rIntf.arQos?.put(packet.qos); -// rIntf.arRegion?.put(packet.region); -// rIntf.arUser?.put(packet.user); -// rIntf.rReady.put(1); -// }); - -// // need to hold the request until receiver is ready -// await sIntf.clk.nextPosedge; -// if (!rIntf.arReady.value.toBool()) { -// await rIntf.arReady.nextPosedge; -// } - -// // now we can release the request -// Simulator.injectAction(() { -// rIntf.arValid.put(0); -// }); - -// // TODO: wait for the response to complete?? -// } - -// Future _driveWritePacket(Axi4WriteRequestPacket packet) async { -// await sIntf.clk.nextPosedge; -// Simulator.injectAction(() { -// wIntf.awValid.put(1); -// wIntf.awId?.put(packet.id); -// wIntf.awAddr.put(packet.addr); -// wIntf.awLen?.put(packet.len); -// wIntf.awSize?.put(packet.size); -// wIntf.awBurst?.put(packet.burst); -// wIntf.awLock?.put(packet.lock); -// wIntf.awCache?.put(packet.cache); -// wIntf.awProt.put(packet.prot); -// wIntf.awQos?.put(packet.qos); -// wIntf.awRegion?.put(packet.region); -// wIntf.awUser?.put(packet.user); -// wIntf.bReady.put(1); -// }); - -// // need to hold the request until receiver is ready -// await sIntf.clk.nextPosedge; -// if (!wIntf.awReady.value.toBool()) { -// await wIntf.awReady.nextPosedge; -// } - -// // now we can release the request -// Simulator.injectAction(() { -// wIntf.awValid.put(0); -// }); - -// // next send the data for the write -// for (var i = 0; i < packet.data.length; i++) { -// if (!wIntf.wReady.value.toBool()) { -// await wIntf.wReady.nextPosedge; -// } -// Simulator.injectAction(() { -// wIntf.wValid.put(1); -// wIntf.wData.put(packet.data[i]); -// wIntf.wStrb.put(packet.strobe[i]); -// wIntf.wLast.put(i == packet.data.length - 1 ? 1 : 0); -// wIntf.wUser?.put(packet.wUser); -// }); -// await sIntf.clk.nextPosedge; -// } - -// // now we can stop the write data -// Simulator.injectAction(() { -// wIntf.wValid.put(0); -// }); - -// // TODO: wait for the response to complete?? -// } -// } - -/// A driver for the [Axi4ReadInterface] interface. -/// -/// Driving from the perspective of the Main agent. -class Axi4ReadMainDriver extends PendingClockedDriver { - /// AXI4 System Interface. - final Axi4SystemInterface sIntf; - - /// AXI4 Read Interface. - final Axi4ReadInterface rIntf; - - /// Creates a new [Axi4MainDriver]. - Axi4ReadMainDriver({ - required Component parent, - required this.sIntf, - required this.rIntf, - required super.sequencer, - super.timeoutCycles = 500, - super.dropDelayCycles = 30, - String name = 'axi4ReadMainDriver', - }) : super( - name, - parent, - clk: sIntf.clk, - ); - - @override - Future run(Phase phase) async { - unawaited(super.run(phase)); - - Simulator.injectAction(() { - rIntf.arValid.put(0); - rIntf.arId?.put(0); - rIntf.arAddr.put(0); - rIntf.arLen?.put(0); - rIntf.arSize?.put(0); - rIntf.arBurst?.put(0); - rIntf.arLock?.put(0); - rIntf.arCache?.put(0); - rIntf.arProt.put(0); - rIntf.arQos?.put(0); - rIntf.arRegion?.put(0); - rIntf.arUser?.put(0); - rIntf.rReady.put(0); - }); - - // wait for reset to complete before driving anything - await sIntf.resetN.nextPosedge; - - while (!Simulator.simulationHasEnded) { - if (pendingSeqItems.isNotEmpty) { - await _drivePacket(pendingSeqItems.removeFirst()); - } else { - await sIntf.clk.nextPosedge; - } - } - } - - /// Drives a packet onto the interface. - Future _drivePacket(Axi4RequestPacket packet) async { - print('Driving packet at time ${Simulator.time}'); - if (packet is Axi4ReadRequestPacket) { - await _driveReadPacket(packet); - } else { - await sIntf.clk.nextPosedge; - } - } - - // TODO: need a more robust way of driving the "ready" signals... - // RREADY for read data responses - // specifically, when should they toggle on/off? - // ON => either always or when the associated request is driven? - // OFF => either never or when there are no more outstanding requests of the given type? - // should we enable the ability to backpressure?? - - Future _driveReadPacket(Axi4ReadRequestPacket packet) async { - await sIntf.clk.nextPosedge; - Simulator.injectAction(() { - rIntf.arValid.put(1); - rIntf.arId?.put(packet.id); - rIntf.arAddr.put(packet.addr); - rIntf.arLen?.put(packet.len); - rIntf.arSize?.put(packet.size); - rIntf.arBurst?.put(packet.burst); - rIntf.arLock?.put(packet.lock); - rIntf.arCache?.put(packet.cache); - rIntf.arProt.put(packet.prot); - rIntf.arQos?.put(packet.qos); - rIntf.arRegion?.put(packet.region); - rIntf.arUser?.put(packet.user); - rIntf.rReady.put(1); - }); - - // need to hold the request until receiver is ready - await sIntf.clk.nextPosedge; - if (!rIntf.arReady.value.toBool()) { - await rIntf.arReady.nextPosedge; - } - - // now we can release the request - Simulator.injectAction(() { - rIntf.arValid.put(0); - }); - - // TODO: wait for the response to complete?? - } -} - -/// A driver for the [Axi4WriteInterface] interface. -/// -/// Driving from the perspective of the Main agent. -class Axi4WriteMainDriver extends PendingClockedDriver { - /// AXI4 System Interface. - final Axi4SystemInterface sIntf; - - /// AXI4 Write Interface. - final Axi4WriteInterface wIntf; - - /// Creates a new [Axi4WriteMainDriver]. - Axi4WriteMainDriver({ - required Component parent, - required this.sIntf, - required this.wIntf, - required super.sequencer, - super.timeoutCycles = 500, - super.dropDelayCycles = 30, - String name = 'axi4WriteMainDriver', - }) : super( - name, - parent, - clk: sIntf.clk, - ); - - @override - Future run(Phase phase) async { - unawaited(super.run(phase)); - - Simulator.injectAction(() { - wIntf.awValid.put(0); - wIntf.awId?.put(0); - wIntf.awAddr.put(0); - wIntf.awLen?.put(0); - wIntf.awSize?.put(0); - wIntf.awBurst?.put(0); - wIntf.awLock?.put(0); - wIntf.awCache?.put(0); - wIntf.awProt.put(0); - wIntf.awQos?.put(0); - wIntf.awRegion?.put(0); - wIntf.awUser?.put(0); - wIntf.wValid.put(0); - wIntf.wData.put(0); - wIntf.wStrb.put(0); - wIntf.wLast.put(0); - wIntf.bReady.put(0); - }); - - // wait for reset to complete before driving anything - await sIntf.resetN.nextPosedge; - - while (!Simulator.simulationHasEnded) { - if (pendingSeqItems.isNotEmpty) { - await _drivePacket(pendingSeqItems.removeFirst()); - } else { - await sIntf.clk.nextPosedge; - } - } - } - - /// Drives a packet onto the interface. - Future _drivePacket(Axi4RequestPacket packet) async { - print('Driving packet at time ${Simulator.time}'); - if (packet is Axi4WriteRequestPacket) { - await _driveWritePacket(packet); - } else { - await sIntf.clk.nextPosedge; - } - } - - // TODO: need a more robust way of driving the "ready" signals... - // BREADY for write responses - // specifically, when should they toggle on/off? - // ON => either always or when the associated request is driven? - // OFF => either never or when there are no more outstanding requests of the given type? - // should we enable the ability to backpressure?? - - Future _driveWritePacket(Axi4WriteRequestPacket packet) async { - await sIntf.clk.nextPosedge; - Simulator.injectAction(() { - wIntf.awValid.put(1); - wIntf.awId?.put(packet.id); - wIntf.awAddr.put(packet.addr); - wIntf.awLen?.put(packet.len); - wIntf.awSize?.put(packet.size); - wIntf.awBurst?.put(packet.burst); - wIntf.awLock?.put(packet.lock); - wIntf.awCache?.put(packet.cache); - wIntf.awProt.put(packet.prot); - wIntf.awQos?.put(packet.qos); - wIntf.awRegion?.put(packet.region); - wIntf.awUser?.put(packet.user); - wIntf.bReady.put(1); - }); - - // need to hold the request until receiver is ready - await sIntf.clk.nextPosedge; - if (!wIntf.awReady.value.toBool()) { - await wIntf.awReady.nextPosedge; - } - - // now we can release the request - Simulator.injectAction(() { - wIntf.awValid.put(0); - }); - - // next send the data for the write - for (var i = 0; i < packet.data.length; i++) { - if (!wIntf.wReady.value.toBool()) { - await wIntf.wReady.nextPosedge; - } - Simulator.injectAction(() { - wIntf.wValid.put(1); - wIntf.wData.put(packet.data[i]); - wIntf.wStrb.put(packet.strobe[i]); - wIntf.wLast.put(i == packet.data.length - 1 ? 1 : 0); - wIntf.wUser?.put(packet.wUser); - }); - await sIntf.clk.nextPosedge; - } - - // now we can stop the write data - Simulator.injectAction(() { - wIntf.wValid.put(0); - }); - - // TODO: wait for the response to complete?? - } -} diff --git a/lib/src/models/axi4_bfm/axi4_main_read_driver.dart b/lib/src/models/axi4_bfm/axi4_main_read_driver.dart new file mode 100644 index 000000000..9236f4dfa --- /dev/null +++ b/lib/src/models/axi4_bfm/axi4_main_read_driver.dart @@ -0,0 +1,122 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// axi4_main_driver.dart +// A driver for AXI4 requests. +// +// 2025 January +// Author: Josh Kimmel + +import 'dart:async'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/src/interfaces/interfaces.dart'; +import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A driver for the [Axi4ReadInterface] interface. +/// +/// Driving from the perspective of the Main agent. +class Axi4ReadMainDriver extends PendingClockedDriver { + /// AXI4 System Interface. + final Axi4SystemInterface sIntf; + + /// AXI4 Read Interface. + final Axi4ReadInterface rIntf; + + /// Creates a new [Axi4ReadMainDriver]. + Axi4ReadMainDriver({ + required Component parent, + required this.sIntf, + required this.rIntf, + required super.sequencer, + super.timeoutCycles = 500, + super.dropDelayCycles = 30, + String name = 'axi4ReadMainDriver', + }) : super( + name, + parent, + clk: sIntf.clk, + ); + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + Simulator.injectAction(() { + rIntf.arValid.put(0); + rIntf.arId?.put(0); + rIntf.arAddr.put(0); + rIntf.arLen?.put(0); + rIntf.arSize?.put(0); + rIntf.arBurst?.put(0); + rIntf.arLock?.put(0); + rIntf.arCache?.put(0); + rIntf.arProt.put(0); + rIntf.arQos?.put(0); + rIntf.arRegion?.put(0); + rIntf.arUser?.put(0); + rIntf.rReady.put(0); + }); + + // wait for reset to complete before driving anything + await sIntf.resetN.nextPosedge; + + while (!Simulator.simulationHasEnded) { + if (pendingSeqItems.isNotEmpty) { + await _drivePacket(pendingSeqItems.removeFirst()); + } else { + await sIntf.clk.nextPosedge; + } + } + } + + /// Drives a packet onto the interface. + Future _drivePacket(Axi4RequestPacket packet) async { + print('Driving packet at time ${Simulator.time}'); + if (packet is Axi4ReadRequestPacket) { + await _driveReadPacket(packet); + } else { + await sIntf.clk.nextPosedge; + } + } + + // TODO: need a more robust way of driving the "ready" signals... + // RREADY for read data responses + // specifically, when should they toggle on/off? + // ON => either always or when the associated request is driven? + // OFF => either never or when there are no more outstanding requests of the given type? + // should we enable the ability to backpressure?? + + Future _driveReadPacket(Axi4ReadRequestPacket packet) async { + await sIntf.clk.nextPosedge; + Simulator.injectAction(() { + rIntf.arValid.put(1); + rIntf.arId?.put(packet.id); + rIntf.arAddr.put(packet.addr); + rIntf.arLen?.put(packet.len); + rIntf.arSize?.put(packet.size); + rIntf.arBurst?.put(packet.burst); + rIntf.arLock?.put(packet.lock); + rIntf.arCache?.put(packet.cache); + rIntf.arProt.put(packet.prot); + rIntf.arQos?.put(packet.qos); + rIntf.arRegion?.put(packet.region); + rIntf.arUser?.put(packet.user); + rIntf.rReady.put(1); + }); + + // need to hold the request until receiver is ready + await sIntf.clk.nextPosedge; + if (!rIntf.arReady.value.toBool()) { + await rIntf.arReady.nextPosedge; + } + + // now we can release the request + Simulator.injectAction(() { + rIntf.arValid.put(0); + }); + + // TODO: wait for the response to complete?? + } +} diff --git a/lib/src/models/axi4_bfm/axi4_main_write_driver.dart b/lib/src/models/axi4_bfm/axi4_main_write_driver.dart new file mode 100644 index 000000000..462185d04 --- /dev/null +++ b/lib/src/models/axi4_bfm/axi4_main_write_driver.dart @@ -0,0 +1,146 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// axi4_main_driver.dart +// A driver for AXI4 requests. +// +// 2025 January +// Author: Josh Kimmel + +import 'dart:async'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/src/interfaces/interfaces.dart'; +import 'package:rohd_hcl/src/models/axi4_bfm/axi4_bfm.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A driver for the [Axi4WriteInterface] interface. +/// +/// Driving from the perspective of the Main agent. +class Axi4WriteMainDriver extends PendingClockedDriver { + /// AXI4 System Interface. + final Axi4SystemInterface sIntf; + + /// AXI4 Write Interface. + final Axi4WriteInterface wIntf; + + /// Creates a new [Axi4WriteMainDriver]. + Axi4WriteMainDriver({ + required Component parent, + required this.sIntf, + required this.wIntf, + required super.sequencer, + super.timeoutCycles = 500, + super.dropDelayCycles = 30, + String name = 'axi4WriteMainDriver', + }) : super( + name, + parent, + clk: sIntf.clk, + ); + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + Simulator.injectAction(() { + wIntf.awValid.put(0); + wIntf.awId?.put(0); + wIntf.awAddr.put(0); + wIntf.awLen?.put(0); + wIntf.awSize?.put(0); + wIntf.awBurst?.put(0); + wIntf.awLock?.put(0); + wIntf.awCache?.put(0); + wIntf.awProt.put(0); + wIntf.awQos?.put(0); + wIntf.awRegion?.put(0); + wIntf.awUser?.put(0); + wIntf.wValid.put(0); + wIntf.wData.put(0); + wIntf.wStrb.put(0); + wIntf.wLast.put(0); + wIntf.bReady.put(0); + }); + + // wait for reset to complete before driving anything + await sIntf.resetN.nextPosedge; + + while (!Simulator.simulationHasEnded) { + if (pendingSeqItems.isNotEmpty) { + await _drivePacket(pendingSeqItems.removeFirst()); + } else { + await sIntf.clk.nextPosedge; + } + } + } + + /// Drives a packet onto the interface. + Future _drivePacket(Axi4RequestPacket packet) async { + print('Driving packet at time ${Simulator.time}'); + if (packet is Axi4WriteRequestPacket) { + await _driveWritePacket(packet); + } else { + await sIntf.clk.nextPosedge; + } + } + + // TODO: need a more robust way of driving the "ready" signals... + // BREADY for write responses + // specifically, when should they toggle on/off? + // ON => either always or when the associated request is driven? + // OFF => either never or when there are no more outstanding requests of the given type? + // should we enable the ability to backpressure?? + + Future _driveWritePacket(Axi4WriteRequestPacket packet) async { + await sIntf.clk.nextPosedge; + Simulator.injectAction(() { + wIntf.awValid.put(1); + wIntf.awId?.put(packet.id); + wIntf.awAddr.put(packet.addr); + wIntf.awLen?.put(packet.len); + wIntf.awSize?.put(packet.size); + wIntf.awBurst?.put(packet.burst); + wIntf.awLock?.put(packet.lock); + wIntf.awCache?.put(packet.cache); + wIntf.awProt.put(packet.prot); + wIntf.awQos?.put(packet.qos); + wIntf.awRegion?.put(packet.region); + wIntf.awUser?.put(packet.user); + wIntf.bReady.put(1); + }); + + // need to hold the request until receiver is ready + await sIntf.clk.nextPosedge; + if (!wIntf.awReady.value.toBool()) { + await wIntf.awReady.nextPosedge; + } + + // now we can release the request + Simulator.injectAction(() { + wIntf.awValid.put(0); + }); + + // next send the data for the write + for (var i = 0; i < packet.data.length; i++) { + if (!wIntf.wReady.value.toBool()) { + await wIntf.wReady.nextPosedge; + } + Simulator.injectAction(() { + wIntf.wValid.put(1); + wIntf.wData.put(packet.data[i]); + wIntf.wStrb.put(packet.strobe[i]); + wIntf.wLast.put(i == packet.data.length - 1 ? 1 : 0); + wIntf.wUser?.put(packet.wUser); + }); + await sIntf.clk.nextPosedge; + } + + // now we can stop the write data + Simulator.injectAction(() { + wIntf.wValid.put(0); + }); + + // TODO: wait for the response to complete?? + } +} diff --git a/lib/src/models/axi4_bfm/axi4_monitor.dart b/lib/src/models/axi4_bfm/axi4_monitor.dart deleted file mode 100644 index a6cd0a246..000000000 --- a/lib/src/models/axi4_bfm/axi4_monitor.dart +++ /dev/null @@ -1,330 +0,0 @@ -// Copyright (C) 2024-2025 Intel Corporation -// SPDX-License-Identifier: BSD-3-Clause -// -// axi4_monitor.dart -// A monitor that watches the AXI4 interfaces. -// -// 2025 January -// 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'; - -// /// A monitor for [Axi4ReadInterface]s and [Axi4WriteInterface]s. -// class Axi4Monitor extends Monitor { -// /// AXI4 System Interface. -// final Axi4SystemInterface sIntf; - -// /// AXI4 Read Interface. -// final Axi4ReadInterface rIntf; - -// /// AXI4 Write Interface. -// final Axi4WriteInterface wIntf; - -// final List _pendingReadRequests = []; -// final List> _pendingReadResponseData = []; - -// final List _pendingWriteRequests = []; - -// /// Creates a new [Axi4Monitor] on [rIntf] and [wIntf]. -// Axi4Monitor( -// {required this.sIntf, -// required this.rIntf, -// required this.wIntf, -// required Component parent, -// String name = 'axi4Monitor'}) -// : super(name, parent); - -// @override -// Future run(Phase phase) async { -// unawaited(super.run(phase)); - -// await sIntf.resetN.nextPosedge; - -// // handle reset -// sIntf.resetN.negedge.listen((event) { -// _pendingReadRequests.clear(); -// _pendingReadResponseData.clear(); -// _pendingWriteRequests.clear(); -// }); - -// sIntf.clk.posedge.listen((event) { -// // read request monitoring -// if (rIntf.arValid.previousValue!.isValid && -// rIntf.arReady.previousValue!.isValid && -// rIntf.arValid.previousValue!.toBool() && -// rIntf.arReady.previousValue!.toBool()) { -// _pendingReadRequests.add( -// Axi4ReadRequestPacket( -// addr: rIntf.arAddr.previousValue!, -// prot: rIntf.arProt.previousValue!, -// id: rIntf.arId?.previousValue, -// len: rIntf.arLen?.previousValue, -// size: rIntf.arSize?.previousValue, -// burst: rIntf.arBurst?.previousValue, -// lock: rIntf.arLock?.previousValue, -// cache: rIntf.arCache?.previousValue, -// qos: rIntf.arQos?.previousValue, -// region: rIntf.arRegion?.previousValue, -// user: rIntf.arUser?.previousValue, -// ), -// ); -// _pendingReadResponseData.add([]); -// } - -// // read response data monitoring -// if (rIntf.rValid.previousValue!.isValid && -// rIntf.rReady.previousValue!.isValid && -// rIntf.rValid.previousValue!.toBool() && -// rIntf.rReady.previousValue!.toBool()) { -// var targIdx = 0; -// if (rIntf.rId != null) { -// targIdx = _pendingReadRequests.indexWhere((element) => -// element.id!.toInt() == rIntf.rId!.previousValue!.toInt()); -// } -// if (targIdx >= 0 && _pendingReadRequests.length > targIdx) { -// _pendingReadResponseData[targIdx].add(rIntf.rData.previousValue!); -// if (rIntf.rLast?.value.toBool() ?? true) { -// add(_pendingReadRequests[targIdx] -// ..complete( -// data: _pendingReadResponseData[targIdx], -// resp: rIntf.rResp?.previousValue, -// user: rIntf.rUser?.previousValue, -// )); -// _pendingReadRequests.removeAt(targIdx); -// _pendingReadResponseData.removeAt(targIdx); -// } -// } -// } - -// // write request monitoring -// if (wIntf.awValid.previousValue!.isValid && -// wIntf.awReady.previousValue!.isValid && -// wIntf.awValid.previousValue!.toBool() && -// wIntf.awReady.previousValue!.toBool()) { -// _pendingWriteRequests.add( -// Axi4WriteRequestPacket( -// addr: wIntf.awAddr.previousValue!, -// prot: wIntf.awProt.previousValue!, -// id: wIntf.awId?.previousValue, -// len: wIntf.awLen?.previousValue, -// size: wIntf.awSize?.previousValue, -// burst: wIntf.awBurst?.previousValue, -// lock: wIntf.awLock?.previousValue, -// cache: wIntf.awCache?.previousValue, -// qos: wIntf.awQos?.previousValue, -// region: wIntf.awRegion?.previousValue, -// user: wIntf.awUser?.previousValue, -// data: [], -// strobe: []), -// ); -// } - -// // write data monitoring -// // NOTE: not dealing with WLAST here b/c it is implicit in how the interface behaves -// if (wIntf.wValid.previousValue!.isValid && -// wIntf.wReady.previousValue!.isValid && -// wIntf.wValid.previousValue!.toBool() && -// wIntf.wReady.previousValue!.toBool()) { -// final targIdx = _pendingWriteRequests.length - 1; -// _pendingWriteRequests[targIdx].data.add(wIntf.wData.previousValue!); -// _pendingWriteRequests[targIdx].strobe.add(wIntf.wStrb.previousValue!); -// _pendingWriteRequests[targIdx].wUser = wIntf.wUser?.previousValue; -// } - -// // write response monitoring -// if (wIntf.bValid.previousValue!.isValid && -// wIntf.bReady.previousValue!.isValid && -// wIntf.bValid.previousValue!.toBool() && -// wIntf.bReady.previousValue!.toBool()) { -// var targIdx = 0; -// if (wIntf.bId != null) { -// targIdx = _pendingWriteRequests.indexWhere((element) => -// element.id!.toInt() == wIntf.bId!.previousValue!.toInt()); -// } -// if (targIdx >= 0 && _pendingWriteRequests.length > targIdx) { -// add(_pendingWriteRequests[targIdx] -// ..complete( -// resp: wIntf.bResp?.previousValue, -// user: wIntf.bUser?.previousValue, -// )); -// _pendingWriteRequests.removeAt(targIdx); -// } -// } -// }); -// } -// } - -/// A monitor for [Axi4ReadInterface]s. -class Axi4ReadMonitor extends Monitor { - /// AXI4 System Interface. - final Axi4SystemInterface sIntf; - - /// AXI4 Read Interface. - final Axi4ReadInterface rIntf; - - final List _pendingReadRequests = []; - final List> _pendingReadResponseData = []; - - /// Creates a new [Axi4ReadMonitor] on [rIntf]. - Axi4ReadMonitor( - {required this.sIntf, - required this.rIntf, - required Component parent, - String name = 'axi4ReadMonitor'}) - : super(name, parent); - - @override - Future run(Phase phase) async { - unawaited(super.run(phase)); - - await sIntf.resetN.nextPosedge; - - // handle reset - sIntf.resetN.negedge.listen((event) { - _pendingReadRequests.clear(); - _pendingReadResponseData.clear(); - }); - - sIntf.clk.posedge.listen((event) { - // read request monitoring - if (rIntf.arValid.previousValue!.isValid && - rIntf.arReady.previousValue!.isValid && - rIntf.arValid.previousValue!.toBool() && - rIntf.arReady.previousValue!.toBool()) { - _pendingReadRequests.add( - Axi4ReadRequestPacket( - addr: rIntf.arAddr.previousValue!, - prot: rIntf.arProt.previousValue!, - id: rIntf.arId?.previousValue, - len: rIntf.arLen?.previousValue, - size: rIntf.arSize?.previousValue, - burst: rIntf.arBurst?.previousValue, - lock: rIntf.arLock?.previousValue, - cache: rIntf.arCache?.previousValue, - qos: rIntf.arQos?.previousValue, - region: rIntf.arRegion?.previousValue, - user: rIntf.arUser?.previousValue, - ), - ); - _pendingReadResponseData.add([]); - } - - // read response data monitoring - if (rIntf.rValid.previousValue!.isValid && - rIntf.rReady.previousValue!.isValid && - rIntf.rValid.previousValue!.toBool() && - rIntf.rReady.previousValue!.toBool()) { - var targIdx = 0; - if (rIntf.rId != null) { - targIdx = _pendingReadRequests.indexWhere((element) => - element.id!.toInt() == rIntf.rId!.previousValue!.toInt()); - } - if (targIdx >= 0 && _pendingReadRequests.length > targIdx) { - _pendingReadResponseData[targIdx].add(rIntf.rData.previousValue!); - if (rIntf.rLast?.value.toBool() ?? true) { - add(_pendingReadRequests[targIdx] - ..complete( - data: _pendingReadResponseData[targIdx], - resp: rIntf.rResp?.previousValue, - user: rIntf.rUser?.previousValue, - )); - _pendingReadRequests.removeAt(targIdx); - _pendingReadResponseData.removeAt(targIdx); - } - } - } - }); - } -} - -/// A monitor for [Axi4WriteInterface]s. -class Axi4WriteMonitor extends Monitor { - /// AXI4 System Interface. - final Axi4SystemInterface sIntf; - - /// AXI4 Write Interface. - final Axi4WriteInterface wIntf; - - final List _pendingWriteRequests = []; - - /// Creates a new [Axi4WriteMonitor] on [wIntf]. - Axi4WriteMonitor( - {required this.sIntf, - required this.wIntf, - required Component parent, - String name = 'axi4WriteMonitor'}) - : super(name, parent); - - @override - Future run(Phase phase) async { - unawaited(super.run(phase)); - - await sIntf.resetN.nextPosedge; - - // handle reset - sIntf.resetN.negedge.listen((event) { - _pendingWriteRequests.clear(); - }); - - sIntf.clk.posedge.listen((event) { - // write request monitoring - if (wIntf.awValid.previousValue!.isValid && - wIntf.awReady.previousValue!.isValid && - wIntf.awValid.previousValue!.toBool() && - wIntf.awReady.previousValue!.toBool()) { - _pendingWriteRequests.add( - Axi4WriteRequestPacket( - addr: wIntf.awAddr.previousValue!, - prot: wIntf.awProt.previousValue!, - id: wIntf.awId?.previousValue, - len: wIntf.awLen?.previousValue, - size: wIntf.awSize?.previousValue, - burst: wIntf.awBurst?.previousValue, - lock: wIntf.awLock?.previousValue, - cache: wIntf.awCache?.previousValue, - qos: wIntf.awQos?.previousValue, - region: wIntf.awRegion?.previousValue, - user: wIntf.awUser?.previousValue, - data: [], - strobe: []), - ); - } - - // write data monitoring - // NOTE: not dealing with WLAST here b/c it is implicit in how the interface behaves - if (wIntf.wValid.previousValue!.isValid && - wIntf.wReady.previousValue!.isValid && - wIntf.wValid.previousValue!.toBool() && - wIntf.wReady.previousValue!.toBool()) { - final targIdx = _pendingWriteRequests.length - 1; - _pendingWriteRequests[targIdx].data.add(wIntf.wData.previousValue!); - _pendingWriteRequests[targIdx].strobe.add(wIntf.wStrb.previousValue!); - _pendingWriteRequests[targIdx].wUser = wIntf.wUser?.previousValue; - } - - // write response monitoring - if (wIntf.bValid.previousValue!.isValid && - wIntf.bReady.previousValue!.isValid && - wIntf.bValid.previousValue!.toBool() && - wIntf.bReady.previousValue!.toBool()) { - var targIdx = 0; - if (wIntf.bId != null) { - targIdx = _pendingWriteRequests.indexWhere((element) => - element.id!.toInt() == wIntf.bId!.previousValue!.toInt()); - } - if (targIdx >= 0 && _pendingWriteRequests.length > targIdx) { - add(_pendingWriteRequests[targIdx] - ..complete( - resp: wIntf.bResp?.previousValue, - user: wIntf.bUser?.previousValue, - )); - _pendingWriteRequests.removeAt(targIdx); - } - } - }); - } -} diff --git a/lib/src/models/axi4_bfm/axi4_packet.dart b/lib/src/models/axi4_bfm/axi4_packet.dart index cded0f9be..cf0280239 100644 --- a/lib/src/models/axi4_bfm/axi4_packet.dart +++ b/lib/src/models/axi4_bfm/axi4_packet.dart @@ -92,7 +92,7 @@ abstract class Axi4RequestPacket extends SequenceItem implements Trackable { case Axi4Tracker.timeField: return Simulator.time.toString(); case Axi4Tracker.idField: - return Simulator.time.toString(); + return id.toString(); case Axi4Tracker.addrField: return addr.toString(); case Axi4Tracker.lenField: diff --git a/lib/src/models/axi4_bfm/axi4_read_compliance_checker.dart b/lib/src/models/axi4_bfm/axi4_read_compliance_checker.dart new file mode 100644 index 000000000..6991ab389 --- /dev/null +++ b/lib/src/models/axi4_bfm/axi4_read_compliance_checker.dart @@ -0,0 +1,85 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// axi4_compliance_checker.dart +// Compliance checking for AXI4. +// +// 2025 January +// Author: Josh Kimmel + +import 'dart:async'; + +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A checker for some of the rules defined in the AXI4 interface specification. +/// +/// This does not necessarily cover all rules defined in the spec. +class Axi4ReadComplianceChecker extends Component { + /// AXI4 System Interface. + final Axi4SystemInterface sIntf; + + /// AXI4 Read Interface. + final Axi4ReadInterface rIntf; + + /// Creates a new compliance checker for AXI4. + Axi4ReadComplianceChecker( + this.sIntf, + this.rIntf, { + required Component parent, + String name = 'axi4ReadComplianceChecker', + }) : super(name, parent); + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + // wait for reset to complete + await sIntf.resetN.nextPosedge; + + // checks to run + // READ REQUESTS + // number of flits returned matches ARLEN if no error + // if RLAST is present, asserted on the final flit only + // if RID is present, every read response should match + // a pending request ARID + + final rLastPresent = rIntf.rLast != null; + final readReqMap = >{}; + + sIntf.clk.posedge.listen((event) { + // capture read requests for counting + if (rIntf.arValid.previousValue!.isValid && + rIntf.arValid.previousValue!.toBool()) { + final id = rIntf.arId?.previousValue!.toInt() ?? 0; + final len = (rIntf.arLen?.previousValue!.toInt() ?? 0) + 1; + readReqMap[id] = [len, 0]; + } + + // track read response flits + if (rIntf.rValid.previousValue!.isValid && + rIntf.rValid.previousValue!.toBool()) { + final id = rIntf.rId?.previousValue!.toInt() ?? 0; + if (!readReqMap.containsKey(id)) { + logger.severe( + 'Cannot match a read response to any pending read request. ' + 'ID captured by the response was $id.'); + } + + readReqMap[id]![1] = readReqMap[id]![1] + 1; + final len = readReqMap[id]![0]; + final currCount = readReqMap[id]![1]; + if (currCount > len) { + logger.severe( + 'Received more read response data flits than indicated by the ' + 'request with ID $id ARLEN. Expected $len but got $currCount'); + } else if (currCount == len && + rLastPresent && + !rIntf.rLast!.previousValue!.toBool()) { + logger.severe('Received the final flit in the read response data per ' + 'the request with ID $id ARLEN but RLAST is not asserted.'); + } + } + }); + } +} diff --git a/lib/src/models/axi4_bfm/axi4_read_monitor.dart b/lib/src/models/axi4_bfm/axi4_read_monitor.dart new file mode 100644 index 000000000..7306ff12a --- /dev/null +++ b/lib/src/models/axi4_bfm/axi4_read_monitor.dart @@ -0,0 +1,97 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// axi4_monitor.dart +// A monitor that watches the AXI4 interfaces. +// +// 2025 January +// 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'; + +/// A monitor for [Axi4ReadInterface]s. +class Axi4ReadMonitor extends Monitor { + /// AXI4 System Interface. + final Axi4SystemInterface sIntf; + + /// AXI4 Read Interface. + final Axi4ReadInterface rIntf; + + final List _pendingReadRequests = []; + final List> _pendingReadResponseData = []; + + /// Creates a new [Axi4ReadMonitor] on [rIntf]. + Axi4ReadMonitor( + {required this.sIntf, + required this.rIntf, + required Component parent, + String name = 'axi4ReadMonitor'}) + : super(name, parent); + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + await sIntf.resetN.nextPosedge; + + // handle reset + sIntf.resetN.negedge.listen((event) { + _pendingReadRequests.clear(); + _pendingReadResponseData.clear(); + }); + + sIntf.clk.posedge.listen((event) { + // read request monitoring + if (rIntf.arValid.previousValue!.isValid && + rIntf.arReady.previousValue!.isValid && + rIntf.arValid.previousValue!.toBool() && + rIntf.arReady.previousValue!.toBool()) { + _pendingReadRequests.add( + Axi4ReadRequestPacket( + addr: rIntf.arAddr.previousValue!, + prot: rIntf.arProt.previousValue!, + id: rIntf.arId?.previousValue, + len: rIntf.arLen?.previousValue, + size: rIntf.arSize?.previousValue, + burst: rIntf.arBurst?.previousValue, + lock: rIntf.arLock?.previousValue, + cache: rIntf.arCache?.previousValue, + qos: rIntf.arQos?.previousValue, + region: rIntf.arRegion?.previousValue, + user: rIntf.arUser?.previousValue, + ), + ); + _pendingReadResponseData.add([]); + } + + // read response data monitoring + if (rIntf.rValid.previousValue!.isValid && + rIntf.rReady.previousValue!.isValid && + rIntf.rValid.previousValue!.toBool() && + rIntf.rReady.previousValue!.toBool()) { + var targIdx = 0; + if (rIntf.rId != null) { + targIdx = _pendingReadRequests.indexWhere((element) => + element.id!.toInt() == rIntf.rId!.previousValue!.toInt()); + } + if (targIdx >= 0 && _pendingReadRequests.length > targIdx) { + _pendingReadResponseData[targIdx].add(rIntf.rData.previousValue!); + if (rIntf.rLast?.value.toBool() ?? true) { + add(_pendingReadRequests[targIdx] + ..complete( + data: _pendingReadResponseData[targIdx], + resp: rIntf.rResp?.previousValue, + user: rIntf.rUser?.previousValue, + )); + _pendingReadRequests.removeAt(targIdx); + _pendingReadResponseData.removeAt(targIdx); + } + } + } + }); + } +} diff --git a/lib/src/models/axi4_bfm/axi4_subordinate.dart b/lib/src/models/axi4_bfm/axi4_subordinate.dart index 76b417873..16fa65b81 100644 --- a/lib/src/models/axi4_bfm/axi4_subordinate.dart +++ b/lib/src/models/axi4_bfm/axi4_subordinate.dart @@ -218,14 +218,14 @@ class Axi4SubordinateAgent extends Agent { // assesses the input ready signals and drives them appropriately void _driveReadReadys({int index = 0}) { // for now, assume we can always handle a new request - channels[index].rIntf!.arReady.put(true); + channels[index].rIntf!.arReady.inject(true); } // assesses the input ready signals and drives them appropriately void _driveWriteReadys({int index = 0}) { // for now, assume we can always handle a new request - channels[index].wIntf!.awReady.put(true); - channels[index].wIntf!.wReady.put(true); + channels[index].wIntf!.awReady.inject(true); + channels[index].wIntf!.wReady.inject(true); } /// Receives one packet (or returns if not selected). @@ -396,14 +396,14 @@ class Axi4SubordinateAgent extends Agent { // } // for now, only support sending slvErr and okay as responses - rIntf.rValid.put(true); - rIntf.rId?.put(packet.id); - rIntf.rData.put(currData); - rIntf.rResp?.put(error || accessError || reqSideError + rIntf.rValid.inject(true); + rIntf.rId?.inject(packet.id); + rIntf.rData.inject(currData); + rIntf.rResp?.inject(error || accessError || reqSideError ? LogicValue.ofInt(Axi4RespField.slvErr.value, rIntf.rResp!.width) : LogicValue.ofInt(Axi4RespField.okay.value, rIntf.rResp!.width)); - rIntf.rUser?.put(0); // don't support user field for now - rIntf.rLast?.put(last); + rIntf.rUser?.inject(0); // don't support user field for now + rIntf.rLast?.inject(last); if (last || accessError) { // pop this read response off the queue @@ -419,7 +419,7 @@ class Axi4SubordinateAgent extends Agent { logger.info('Still sending the read response for channel $index.'); } } else { - rIntf.rValid.put(false); + rIntf.rValid.inject(false); } } @@ -597,12 +597,12 @@ class Axi4SubordinateAgent extends Agent { final error = respondWithError != null && respondWithError!(packet); // for now, only support sending slvErr and okay as responses - wIntf.bValid.put(true); - wIntf.bId?.put(packet.id); - wIntf.bResp?.put(error || accessError || rmwErr || hardLockErr + wIntf.bValid.inject(true); + wIntf.bId?.inject(packet.id); + wIntf.bResp?.inject(error || accessError || rmwErr || hardLockErr ? LogicValue.ofInt(Axi4RespField.slvErr.value, wIntf.bResp!.width) : LogicValue.ofInt(Axi4RespField.okay.value, wIntf.bResp!.width)); - wIntf.bUser?.put(0); // don't support user field for now + wIntf.bUser?.inject(0); // don't support user field for now // TODO: how to deal with delays?? // if (readResponseDelay != null) { @@ -648,7 +648,7 @@ class Axi4SubordinateAgent extends Agent { logger.info('Sent write response on channel $index.'); } } else { - wIntf.bValid.put(false); + wIntf.bValid.inject(false); } } } diff --git a/lib/src/models/axi4_bfm/axi4_write_compliance_checker.dart b/lib/src/models/axi4_bfm/axi4_write_compliance_checker.dart new file mode 100644 index 000000000..eca454a25 --- /dev/null +++ b/lib/src/models/axi4_bfm/axi4_write_compliance_checker.dart @@ -0,0 +1,83 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// axi4_compliance_checker.dart +// Compliance checking for AXI4. +// +// 2025 January +// Author: Josh Kimmel + +import 'dart:async'; + +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A checker for some of the rules defined in the AXI4 interface specification. +/// +/// This does not necessarily cover all rules defined in the spec. +class Axi4WriteComplianceChecker extends Component { + /// AXI4 System Interface. + final Axi4SystemInterface sIntf; + + /// AXI4 Write Interface. + final Axi4WriteInterface wIntf; + + /// Creates a new compliance checker for AXI4. + Axi4WriteComplianceChecker( + this.sIntf, + this.wIntf, { + required Component parent, + String name = 'axi4WriteComplianceChecker', + }) : super(name, parent); + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + // wait for reset to complete + await sIntf.resetN.nextPosedge; + + // checks to run + // WRITE REQUESTS + // number of flits sent matches AWLEN + // WLAST is asserted on the final flit only + // if BID is present, every write response should match + // a pending request AWID + + final writeReqMap = >{}; + var lastWriteReqId = -1; + + sIntf.clk.posedge.listen((event) { + // track write requests + if (wIntf.awValid.previousValue!.isValid && + wIntf.awValid.previousValue!.toBool()) { + final id = wIntf.awId?.previousValue!.toInt() ?? 0; + final len = (wIntf.awLen?.previousValue!.toInt() ?? 0) + 1; + writeReqMap[id] = [len, 0]; + lastWriteReqId = id; + } + + // track write data flits + if (wIntf.wValid.previousValue!.isValid && + wIntf.wValid.previousValue!.toBool()) { + final id = lastWriteReqId; + if (!writeReqMap.containsKey(id)) { + logger.severe('There is no pending write request ' + 'to associate with valid write data.'); + } + + writeReqMap[id]![1] = writeReqMap[id]![1] + 1; + final len = writeReqMap[id]![0]; + final currCount = writeReqMap[id]![1]; + if (currCount > len) { + logger.severe( + 'Sent more write data flits than indicated by the request ' + 'with ID $id AWLEN. Expected $len but sent $currCount'); + } else if (currCount == len && !wIntf.wLast.previousValue!.toBool()) { + logger.severe('Sent the final flit in the write data per the request ' + 'with ID $id AWLEN but WLAST is not asserted.'); + } + } + }); + } +} diff --git a/lib/src/models/axi4_bfm/axi4_write_monitor.dart b/lib/src/models/axi4_bfm/axi4_write_monitor.dart new file mode 100644 index 000000000..c95338ad5 --- /dev/null +++ b/lib/src/models/axi4_bfm/axi4_write_monitor.dart @@ -0,0 +1,101 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// axi4_monitor.dart +// A monitor that watches the AXI4 interfaces. +// +// 2025 January +// Author: Josh Kimmel + +import 'dart:async'; + +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A monitor for [Axi4WriteInterface]s. +class Axi4WriteMonitor extends Monitor { + /// AXI4 System Interface. + final Axi4SystemInterface sIntf; + + /// AXI4 Write Interface. + final Axi4WriteInterface wIntf; + + final List _pendingWriteRequests = []; + + /// Creates a new [Axi4WriteMonitor] on [wIntf]. + Axi4WriteMonitor( + {required this.sIntf, + required this.wIntf, + required Component parent, + String name = 'axi4WriteMonitor'}) + : super(name, parent); + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + await sIntf.resetN.nextPosedge; + + // handle reset + sIntf.resetN.negedge.listen((event) { + _pendingWriteRequests.clear(); + }); + + sIntf.clk.posedge.listen((event) { + // write request monitoring + if (wIntf.awValid.previousValue!.isValid && + wIntf.awReady.previousValue!.isValid && + wIntf.awValid.previousValue!.toBool() && + wIntf.awReady.previousValue!.toBool()) { + _pendingWriteRequests.add( + Axi4WriteRequestPacket( + addr: wIntf.awAddr.previousValue!, + prot: wIntf.awProt.previousValue!, + id: wIntf.awId?.previousValue, + len: wIntf.awLen?.previousValue, + size: wIntf.awSize?.previousValue, + burst: wIntf.awBurst?.previousValue, + lock: wIntf.awLock?.previousValue, + cache: wIntf.awCache?.previousValue, + qos: wIntf.awQos?.previousValue, + region: wIntf.awRegion?.previousValue, + user: wIntf.awUser?.previousValue, + data: [], + strobe: []), + ); + } + + // write data monitoring + // NOTE: not dealing with WLAST here b/c it is implicit in how the interface behaves + if (wIntf.wValid.previousValue!.isValid && + wIntf.wReady.previousValue!.isValid && + wIntf.wValid.previousValue!.toBool() && + wIntf.wReady.previousValue!.toBool()) { + final targIdx = _pendingWriteRequests.length - 1; + _pendingWriteRequests[targIdx].data.add(wIntf.wData.previousValue!); + _pendingWriteRequests[targIdx].strobe.add(wIntf.wStrb.previousValue!); + _pendingWriteRequests[targIdx].wUser = wIntf.wUser?.previousValue; + } + + // write response monitoring + if (wIntf.bValid.previousValue!.isValid && + wIntf.bReady.previousValue!.isValid && + wIntf.bValid.previousValue!.toBool() && + wIntf.bReady.previousValue!.toBool()) { + var targIdx = 0; + if (wIntf.bId != null) { + targIdx = _pendingWriteRequests.indexWhere((element) => + element.id!.toInt() == wIntf.bId!.previousValue!.toInt()); + } + if (targIdx >= 0 && _pendingWriteRequests.length > targIdx) { + add(_pendingWriteRequests[targIdx] + ..complete( + resp: wIntf.bResp?.previousValue, + user: wIntf.bUser?.previousValue, + )); + _pendingWriteRequests.removeAt(targIdx); + } + } + }); + } +} diff --git a/test/axi4_bfm_test.dart b/test/axi4_bfm_test.dart index fb0f97a4e..8cc6eeed4 100644 --- a/test/axi4_bfm_test.dart +++ b/test/axi4_bfm_test.dart @@ -96,7 +96,6 @@ class Axi4BfmTest extends Test { } else if (channelConfigs[i] == Axi4BfmTestChannelConfig.read) { channels.add(Axi4Channel( channelId: i, - hasWrite: false, rIntf: Axi4ReadInterface( addrWidth: addrWidth, dataWidth: dataWidth, @@ -108,7 +107,6 @@ class Axi4BfmTest extends Test { } else if (channelConfigs[i] == Axi4BfmTestChannelConfig.write) { channels.add(Axi4Channel( channelId: i, - hasRead: false, wIntf: Axi4WriteInterface( addrWidth: addrWidth, dataWidth: dataWidth, diff --git a/test/axi4_test.dart b/test/axi4_test.dart index 6d27a0714..d7c564da7 100644 --- a/test/axi4_test.dart +++ b/test/axi4_test.dart @@ -22,8 +22,6 @@ class Axi4Subordinate extends Module { for (var i = 0; i < channels.length; i++) { channelsL.add(Axi4Channel( channelId: channels[i].channelId, - hasRead: channels[i].hasRead, - hasWrite: channels[i].hasWrite, rIntf: channels[i].hasRead ? (Axi4ReadInterface.clone(channels[i].rIntf!) ..connectIO(this, channels[i].rIntf!, @@ -49,8 +47,6 @@ class Axi4Main extends Module { for (var i = 0; i < channels.length; i++) { channelsL.add(Axi4Channel( channelId: channels[i].channelId, - hasRead: channels[i].hasRead, - hasWrite: channels[i].hasWrite, rIntf: channels[i].hasRead ? (Axi4ReadInterface.clone(channels[i].rIntf!) ..connectIO(this, channels[i].rIntf!, @@ -88,8 +84,6 @@ class Axi4Pair extends Module { channelConfigs[i] == Axi4BfmTestChannelConfig.readWrite; channels.add(Axi4Channel( channelId: i, - hasRead: hasRead, - hasWrite: hasWrite, rIntf: hasRead ? Axi4ReadInterface() : null, wIntf: hasWrite ? Axi4WriteInterface() : null)); }