diff --git a/lib/src/interfaces/axi4.dart b/lib/src/interfaces/axi4.dart new file mode 100644 index 000000000..06449c43c --- /dev/null +++ b/lib/src/interfaces/axi4.dart @@ -0,0 +1,647 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// axi4.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] and [Axi4WriteInterface] +/// interfaces 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. + /// + /// 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. + /// + /// 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 in a system. + /// + /// Width is equal to [cacheWidth]. + Logic? get awCache => tryPort('AWCACHE'); + + /// Protection attributes of a write transaction. + /// + /// 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), + 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'); + } + } +} + +/// Helper to enumerate the encodings of the xBURST 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 point + /// when an upper boundary point 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/interfaces/interfaces.dart b/lib/src/interfaces/interfaces.dart index 45914619e..112f07e19 100644 --- a/lib/src/interfaces/interfaces.dart +++ b/lib/src/interfaces/interfaces.dart @@ -1,4 +1,5 @@ -// Copyright (C) 2023-2024 Intel Corporation +// Copyright (C) 2024-2025 Intel Corporation // 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..0bcead1ee --- /dev/null +++ b/lib/src/models/axi4_bfm/axi4_bfm.dart @@ -0,0 +1,13 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +export 'axi4_main.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_main.dart b/lib/src/models/axi4_bfm/axi4_main.dart new file mode 100644 index 000000000..645f83d22 --- /dev/null +++ b/lib/src/models/axi4_bfm/axi4_main.dart @@ -0,0 +1,175 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// axi4_main.dart +// An agent sending for AXI4 requests. +// +// 2025 January +// Author: Josh Kimmel + +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.rIntf, + this.wIntf, + }) : hasRead = rIntf != null, + hasWrite = wIntf != null, + assert( + rIntf != null || wIntf != null, + 'A channel must support either' + ' reads or writes (or both)'); +} + +/// An agent for sending requests on +/// [Axi4ReadInterface]s and [Axi4WriteInterface]s. +/// +/// Driven read packets will update the returned data into the same packet. +class Axi4MainAgent extends Agent { + /// AXI4 System Interface. + final Axi4SystemInterface sIntf; + + /// Channels that the agent can send requests on. + final List channels; + + /// The sequencers where read requests should be sent. + final List> rdSequencers = []; + + /// The sequencers where write requests should be sent. + final List> wrSequencers = []; + + /// The drivers that send read requests over the interface. + final List rdDrivers = []; + + /// The drivers that send write requests over the interface. + final List wrDrivers = []; + + /// Monitoring of read requests over the interface. + final List rdMonitors = []; + + /// Monitoring of write requests over the interface. + final List wrMonitors = []; + + /// 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; + + // 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.channels, + required Component parent, + String name = 'axiMainAgent', + this.timeoutCycles = 500, + this.dropDelayCycles = 30, + }) : super(name, parent) { + 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: 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: 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_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_packet.dart b/lib/src/models/axi4_bfm/axi4_packet.dart new file mode 100644 index 000000000..cf0280239 --- /dev/null +++ b/lib/src/models/axi4_bfm/axi4_packet.dart @@ -0,0 +1,231 @@ +// 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 request packet on an AXI4 interface. +abstract class Axi4RequestPacket 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. + Axi4RequestPacket( + {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 || _returnedUserData != 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.idField: + return id.toString(); + case Axi4Tracker.addrField: + return addr.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; + } +} + +/// A read packet on an [Axi4ReadInterface]. +class Axi4ReadRequestPacket extends Axi4RequestPacket { + /// Data returned by the read. + List get returnedData => _returnedData; + + List _returnedData = []; + + /// Creates a read packet. + Axi4ReadRequestPacket({ + 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({ + List data = const [], + LogicValue? resp, + LogicValue? user, + }) { + if (_returnedData.isNotEmpty) { + throw RohdHclException('Packet is already completed.'); + } + _returnedData = data; + super.complete(resp: resp, user: user); + } + + @override + String? trackerString(TrackerField field) { + switch (field.title) { + case Axi4Tracker.typeField: + return 'R'; + case Axi4Tracker.dataField: + return returnedData + .map((d) => d.toRadixString(radix: 16)) + .toList() + .reversed + .join(); + } + + return super.trackerString(field); + } +} + +/// A write packet on an [Axi4WriteInterface]. +class Axi4WriteRequestPacket extends Axi4RequestPacket { + /// The data for this packet. + final List data; + + /// The strobe associated with this write. + final List strobe; + + /// The user metadata associated with this write. + 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, + required this.strobe, + super.id, + super.len, + super.size, + super.burst, + super.lock, + super.cache, + super.qos, + super.region, + super.user, + this.wUser, + }); + + @override + String? trackerString(TrackerField field) { + switch (field.title) { + case Axi4Tracker.typeField: + return 'W'; + case Axi4Tracker.dataField: + return data + .map((d) => d.toRadixString(radix: 16)) + .toList() + .reversed + .join(); + case Axi4Tracker.strbField: + return strobe + .map((d) => d.toRadixString(radix: 16)) + .toList() + .reversed + .join(); + } + + return super.trackerString(field); + } +} 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 new file mode 100644 index 000000000..16fa65b81 --- /dev/null +++ b/lib/src/models/axi4_bfm/axi4_subordinate.dart @@ -0,0 +1,654 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// axi4_subordinate.dart +// A subordinate AXI4 agent. +// +// 2025 January +// Author: Josh Kimmel + +import 'dart:async'; +import 'dart:math'; + +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 { + /// The system interface. + final Axi4SystemInterface sIntf; + + /// Channels that the subordinate manages. + final List channels; + + /// A place where the subordinate should save and retrieve data. + /// + /// The [Axi4SubordinateAgent] will reset [storage] whenever + /// the `resetN` signal is dropped. + late 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(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(Axi4RequestPacket 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; + + /// Address range configuration. Controls access to addresses and helps + /// with the wrap mode for bursts. + /// 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 + final List> _writeMetadataQueue = []; + final List _writeReadyToOccur = []; + + // capture mapping of channel ID to TB object index + 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]. + Axi4SubordinateAgent( + {required this.sIntf, + required this.channels, + required Component parent, + MemoryStorage? storage, + this.readResponseDelay, + this.writeResponseDelay, + this.respondWithError, + this.invalidReadDataOnError = true, + this.dropWriteDataOnError = true, + this.ranges = const [], + this.supportLocking = false, + String name = 'axi4SubordinateAgent'}) + : super(name, parent) { + var maxAddrWidth = 0; + var maxDataWidth = 0; + 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 ?? + SparseMemoryStorage( + addrWidth: maxAddrWidth, + dataWidth: maxDataWidth, + ); + for (var i = 0; i < channels.length; i++) { + if (channels[i].hasRead) { + _dataReadResponseMetadataQueue.add([]); + _dataReadResponseDataQueue.add([]); + _dataReadResponseErrorQueue.add([]); + _dataReadResponseIndex.add(0); + _readAddrToChannel[i] = _dataReadResponseMetadataQueue.length - 1; + } + if (channels[i].hasWrite) { + _writeMetadataQueue.add([]); + _writeReadyToOccur.add(false); + _writeAddrToChannel[i] = _writeMetadataQueue.length - 1; + } + } + } + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + sIntf.resetN.negedge.listen((event) { + storage.reset(); + for (var i = 0; i < channels.length; i++) { + if (channels[i].hasRead) { + _dataReadResponseMetadataQueue[_readAddrToChannel[i]!].clear(); + _dataReadResponseDataQueue[_readAddrToChannel[i]!].clear(); + _dataReadResponseErrorQueue[_readAddrToChannel[i]!].clear(); + _dataReadResponseIndex[_readAddrToChannel[i]!] = 0; + } + if (channels[i].hasWrite) { + _writeMetadataQueue[_writeAddrToChannel[i]!].clear(); + _writeReadyToOccur[_writeAddrToChannel[i]!] = false; + } + } + }); + + // wait for reset to complete + await sIntf.resetN.nextPosedge; + + while (!Simulator.simulationHasEnded) { + await sIntf.clk.nextNegedge; + 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); + } + } + } + } + + /// 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(); + + // 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.length; j++) { + if ((addr >= 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 + 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.inject(true); + channels[index].wIntf!.wReady.inject(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 (rIntf.arValid.value.toBool() && rIntf.arReady.value.toBool()) { + logger.info('Received read request on channel $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); + + // 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 they require implementation specific handling. + + // NOTE: generic model doesn't use the instruction/data bit of the prot field. + + // 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 increment = 0; + 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; + + // 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) { + if (currData.width < dSize) { + currData = currData.zeroExtend(dSize); + } else if (currData.width > dSize) { + currData = currData.getRange(0, dSize); + } + } + 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 + // the burst overflows the region + if (rIntf.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[mapIdx].add(packet); + _dataReadResponseDataQueue[mapIdx].add(data); + _dataReadResponseErrorQueue[mapIdx].add(hardLockErr); + } + } + + // 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[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); + 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))); + + // 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.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?.inject(0); // don't support user field for now + rIntf.rLast?.inject(last); + + if (last || accessError) { + // pop this read response off the queue + _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 { + // move to the next chunk of data + _dataReadResponseIndex[mapIdx]++; + logger.info('Still sending the read response for channel $index.'); + } + } else { + rIntf.rValid.inject(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 (wIntf.awValid.value.toBool() && wIntf.awReady.value.toBool()) { + logger.info('Received write request on channel $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, + 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[mapIdx] = true; + } + } + + // queue up the packet for further processing + _writeMetadataQueue[mapIdx].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({int index = 0}) { + final wIntf = channels[index].wIntf!; + final mapIdx = _writeAddrToChannel[index]!; + + // NOTE: we are dropping wUser on the floor for now... + 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[mapIdx]) { + // only respond if the main is ready + 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))); + + // 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.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?.inject(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); + // } + // } + + // 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 they require implementation specific handling. + + // 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(addrsToWrite[i]); + 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); + } + } + + // pop this write response off the queue + _writeMetadataQueue[mapIdx].removeAt(0); + _writeReadyToOccur[mapIdx] = false; + + logger.info('Sent write response on channel $index.'); + } + } else { + wIntf.bValid.inject(false); + } + } +} 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..f8f14c2a4 --- /dev/null +++ b/lib/src/models/axi4_bfm/axi4_tracker.dart @@ -0,0 +1,119 @@ +// 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 PROT. + static const protField = 'PROT'; + + /// 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] and [Axi4WriteInterface]. + Axi4Tracker({ + String name = 'Axi4Tracker', + super.dumpJson, + super.dumpTable, + super.outputFolder, + int timeColumnWidth = 12, + int idColumnWidth = 0, + int addrColumnWidth = 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), + 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), + if (strbColumnWidth > 0) + TrackerField(strbField, columnWidth: strbColumnWidth), + ]); +} 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/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'; diff --git a/test/axi4_bfm_test.dart b/test/axi4_bfm_test.dart new file mode 100644 index 000000000..8cc6eeed4 --- /dev/null +++ b/test/axi4_bfm_test.dart @@ -0,0 +1,385 @@ +// 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:io'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; +import 'package:test/test.dart'; + +import 'axi4_test.dart'; + +enum Axi4BfmTestChannelConfig { read, write, readWrite } + +class Axi4BfmTest extends Test { + late final Axi4SystemInterface sIntf; + + final int numChannels; + final List channelConfigs; + final List channels = []; + + late final Axi4MainAgent main; + + late SparseMemoryStorage storage; + + final int numTransfers; + + final bool withStrobes; + + final int interTxnDelay; + + final bool withRandomRspDelays; + + final bool withErrors; + + final int addrWidth; + + final int dataWidth; + + // 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); + + bool get hasAnyWrites => channels.any((element) => element.hasWrite); + + Axi4BfmTest( + super.name, { + this.numChannels = 1, + this.channelConfigs = const [Axi4BfmTestChannelConfig.readWrite], + this.numTransfers = 10, + this.withStrobes = false, + this.interTxnDelay = 0, + this.withRandomRspDelays = false, + this.withErrors = false, + 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.'), + super(randomSeed: 123) { + // using default parameter values for all interfaces + sIntf = Axi4SystemInterface(); + for (var i = 0; i < numChannels; i++) { + if (channelConfigs[i] == Axi4BfmTestChannelConfig.readWrite) { + channels.add(Axi4Channel( + channelId: i, + 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( + channelId: i, + 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( + channelId: i, + wIntf: Axi4WriteInterface( + addrWidth: addrWidth, + dataWidth: dataWidth, + lenWidth: lenWidth, + wuserWidth: dataWidth ~/ 2 - 1, + ), + )); + Axi4WriteComplianceChecker(sIntf, channels.last.wIntf!, parent: this); + } + } + + storage = SparseMemoryStorage( + addrWidth: addrWidth, + dataWidth: dataWidth, + onInvalidRead: (addr, dataWidth) => + LogicValue.filled(dataWidth, LogicValue.zero), + ); + + sIntf.clk <= SimpleClockGenerator(10).clk; + + main = Axi4MainAgent(sIntf: sIntf, channels: channels, parent: this); + + Axi4SubordinateAgent( + sIntf: sIntf, + channels: channels, + 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, + supportLocking: supportLocking, + ); + + Directory(outFolder).createSync(recursive: true); + + final tracker = Axi4Tracker( + dumpTable: false, + outputFolder: outFolder, + ); + + Simulator.registerEndOfSimulationAction(() async { + await tracker.terminate(); + + // 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 + + // Directory(outFolder).deleteSync(recursive: true); + }); + + for (var i = 0; i < numChannels; i++) { + main.getRdMonitor(i)?.stream.listen(tracker.record); + main.getWrMonitor(i)?.stream.listen(tracker.record); + } + } + + int numTransfersCompleted = 0; + final mandatoryTransWaitPeriod = 10; + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + final obj = phase.raiseObjection('axi4BfmTestObj'); + + 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(); + + // to track what was written + final lens = []; + final sizes = []; + final data = >[]; + final strobes = >[]; + final locks = []; + + // normal writes + 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)); + 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), + 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: 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 + 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 + 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: 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 + 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(); + } + + 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(30000); + + if (dumpWaves) { + final mod = Axi4Subordinate(axi4BfmTest.sIntf, axi4BfmTest.channels); + await mod.build(); + WaveDumper(mod); + } + + 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, + numChannels: 4, + channelConfigs: [ + Axi4BfmTestChannelConfig.read, + Axi4BfmTestChannelConfig.write, + Axi4BfmTestChannelConfig.readWrite, + Axi4BfmTestChannelConfig.write, + ], + withRandomRspDelays: true, + withStrobes: true, + interTxnDelay: 3, + supportLocking: true, + )); + }); + + test('with errors', () async { + await runTest(Axi4BfmTest('werr', withErrors: true)); + }); +} diff --git a/test/axi4_test.dart b/test/axi4_test.dart new file mode 100644 index 000000000..d7c564da7 --- /dev/null +++ b/test/axi4_test.dart @@ -0,0 +1,132 @@ +// 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'; + +import 'axi4_bfm_test.dart'; + +class Axi4Subordinate extends Module { + Axi4Subordinate(Axi4SystemInterface sIntf, List channels) { + sIntf = Axi4SystemInterface() + ..connectIO(this, sIntf, inputTags: {Axi4Direction.misc}); + + final channelsL = []; + for (var i = 0; i < channels.length; i++) { + channelsL.add(Axi4Channel( + channelId: channels[i].channelId, + 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 channels) { + sIntf = Axi4SystemInterface() + ..connectIO(this, sIntf, inputTags: {Axi4Direction.misc}); + + final channelsL = []; + for (var i = 0; i < channels.length; i++) { + channelsL.add(Axi4Channel( + channelId: channels[i].channelId, + 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 numChannels = 1, + List channelConfigs = const [ + Axi4BfmTestChannelConfig.readWrite + ]}) { + clk = addInput('clk', clk); + reset = addInput('reset', reset); + + final sIntf = Axi4SystemInterface(); + sIntf.clk <= clk; + sIntf.resetN <= ~reset; + + 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, + rIntf: hasRead ? Axi4ReadInterface() : null, + wIntf: hasWrite ? Axi4WriteInterface() : null)); + } + + Axi4Main(sIntf, channels); + Axi4Subordinate(sIntf, channels); + } +} + +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); + }); +}