mem: Add MemChecker and MemCheckerMonitor

This patch adds the MemChecker and MemCheckerMonitor classes. While
MemChecker can be integrated anywhere in the system and is independent,
the most convenient usage is through the MemCheckerMonitor -- this
however, puts limitations on where the MemChecker is able to observe
read/write transactions.
This commit is contained in:
Marco Elver 2014-12-23 09:31:17 -05:00
parent 184fefbb3b
commit dd0f3943e2
6 changed files with 1590 additions and 0 deletions

58
src/mem/MemChecker.py Normal file
View file

@ -0,0 +1,58 @@
# Copyright (c) 2014 ARM Limited
# All rights reserved.
#
# The license below extends only to copyright in the software and shall
# not be construed as granting a license to any other intellectual
# property including but not limited to intellectual property relating
# to a hardware implementation of the functionality of the software
# licensed hereunder. You may use the software subject to the license
# terms below provided that you ensure that this notice is replicated
# unmodified and in its entirety in all distributions of the software,
# modified or unmodified, in source code or in binary form.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met: redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer;
# redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution;
# neither the name of the copyright holders nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# Authors: Marco Elver
from MemObject import MemObject
from m5.SimObject import SimObject
from m5.params import *
from m5.proxy import *
class MemChecker(SimObject):
type = 'MemChecker'
cxx_header = "mem/mem_checker.hh"
class MemCheckerMonitor(MemObject):
type = 'MemCheckerMonitor'
cxx_header = "mem/mem_checker_monitor.hh"
# one port in each direction
master = MasterPort("Master port")
slave = SlavePort("Slave port")
cpu_side = SlavePort("Alias for slave")
mem_side = MasterPort("Alias for master")
warn_only = Param.Bool(False, "Warn about violations only")
memchecker = Param.MemChecker("Instance shared with other monitors")

View file

@ -79,6 +79,10 @@ if env['HAVE_DRAMSIM']:
Source('dramsim2_wrapper.cc')
Source('dramsim2.cc')
SimObject('MemChecker.py')
Source('mem_checker.cc')
Source('mem_checker_monitor.cc')
DebugFlag('AddrRanges')
DebugFlag('BaseXBar')
DebugFlag('CoherentXBar')
@ -99,3 +103,6 @@ DebugFlag('MemoryAccess')
DebugFlag('PacketQueue')
DebugFlag("DRAMSim2")
DebugFlag("MemChecker")
DebugFlag("MemCheckerMonitor")

343
src/mem/mem_checker.cc Normal file
View file

@ -0,0 +1,343 @@
/*
* Copyright (c) 2014 ARM Limited
* All rights reserved
*
* The license below extends only to copyright in the software and shall
* not be construed as granting a license to any other intellectual
* property including but not limited to intellectual property relating
* to a hardware implementation of the functionality of the software
* licensed hereunder. You may use the software subject to the license
* terms below provided that you ensure that this notice is replicated
* unmodified and in its entirety in all distributions of the software,
* modified or unmodified, in source code or in binary form.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met: redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer;
* redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution;
* neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Authors: Rune Holm
* Marco Elver
*/
#include <cassert>
#include "mem/mem_checker.hh"
void
MemChecker::WriteCluster::startWrite(MemChecker::Serial serial, Tick _start,
uint8_t data)
{
assert(!isComplete());
if (start == TICK_FUTURE) {
// Initialize a fresh write cluster
start = _start;
}
chatty_assert(start <= _start, "WriteClusters must filled in order!");
++numIncomplete;
if (complete != TICK_FUTURE) {
// Reopen a closed write cluster
assert(_start < complete); // should open a new write cluster, instead;
// also somewhat fishy wrt causality / ordering of calls vs time
// progression TODO: Check me!
complete = TICK_FUTURE;
}
// Create new transaction, and denote completion time to be in the future.
writes.insert(std::make_pair(serial,
MemChecker::Transaction(serial, _start, TICK_FUTURE, data)));
}
void
MemChecker::WriteCluster::completeWrite(MemChecker::Serial serial, Tick _complete)
{
auto it = writes.find(serial);
if (it == writes.end()) {
warn("Could not locate write transaction: serial = %d, complete = %d\n",
serial, _complete);
return;
}
// Record completion time of the write
assert(it->second.complete == TICK_FUTURE);
it->second.complete = _complete;
// Update max completion time for the cluster
if (completeMax < _complete) {
completeMax = _complete;
}
if (--numIncomplete == 0) {
// All writes have completed, this cluster is now complete and will be
// assigned the max of completion tick values among all writes.
//
// Note that we cannot simply keep updating complete, because that would
// count the cluster as closed already. Instead, we keep TICK_FUTURE
// until all writes have completed.
complete = completeMax;
}
}
void
MemChecker::WriteCluster::abortWrite(MemChecker::Serial serial)
{
if (!writes.erase(serial)) {
warn("Could not locate write transaction: serial = %d\n", serial);
return;
}
if (--numIncomplete == 0 && !writes.empty()) {
// This write cluster is now complete, and we can assign the current
// completeMax value.
complete = completeMax;
}
// Note: this WriteCluster is in pristine state if this was the only
// write present; the cluster will get reused through
// getIncompleteWriteCluster().
}
void
MemChecker::ByteTracker::startRead(MemChecker::Serial serial, Tick start)
{
outstandingReads.insert(std::make_pair(serial,
MemChecker::Transaction(serial, start, TICK_FUTURE)));
}
bool
MemChecker::ByteTracker::inExpectedData(Tick start, Tick complete, uint8_t data)
{
_lastExpectedData.clear();
bool wc_overlap = true;
// Find the last value read from the location
const Transaction& last_obs =
*lastCompletedTransaction(&readObservations, start);
bool last_obs_valid = (last_obs.complete != TICK_INITIAL);
// Scan backwards through the write clusters to find the closest younger
// preceding & overlapping writes.
for (auto cluster = writeClusters.rbegin();
cluster != writeClusters.rend() && wc_overlap; ++cluster) {
for (const auto& addr_write : cluster->writes) {
const Transaction& write = addr_write.second;
if (write.complete < last_obs.start) {
// If this write transaction completed before the last
// observation, we ignore it as the last_observation has the
// correct value
continue;
}
if (write.data == data) {
// Found a match, end search.
return true;
}
// Record possible, but non-matching data for debugging
_lastExpectedData.push_back(write.data);
if (write.complete > start) {
// This write overlapped with the transaction we want to check
// -> continue checking the overlapping write cluster
continue;
}
// This write cluster has writes that have completed before the
// checked transaction. There is no need to check an earlier
// write-cluster -> set the exit condition for the outer loop
wc_overlap = false;
if (last_obs.complete < write.start) {
// We found a write which started after the last observed read,
// therefore we can not longer consider the value seen by the
// last observation as a valid expected value.
//
// Once all writes have been iterated through, we can check if
// the last observation is still valid to compare against.
last_obs_valid = false;
}
}
}
// We have not found any matching write, so far; check other sources of
// confirmation
if (last_obs_valid) {
// The last observation is not outdated according to the writes we have
// seen so far.
assert(last_obs.complete <= start);
if (last_obs.data == data) {
// Matched data from last observation -> all good
return true;
}
// Record non-matching, but possible value
_lastExpectedData.push_back(last_obs.data);
}
if (_lastExpectedData.empty()) {
assert(last_obs.complete == TICK_INITIAL);
// We have not found any possible (non-matching data). Can happen in
// initial system state
DPRINTF(MemChecker, "no last observation nor write! start = %d, "\
"complete = %d, data = %#x\n", start, complete, data);
return true;
}
return false;
}
bool
MemChecker::ByteTracker::completeRead(MemChecker::Serial serial,
Tick complete, uint8_t data)
{
auto it = outstandingReads.find(serial);
if (it == outstandingReads.end()) {
// Can happen if concurrent with reset_address_range
warn("Could not locate read transaction: serial = %d, complete = %d\n",
serial, complete);
return true;
}
Tick start = it->second.start;
outstandingReads.erase(it);
// Verify data
const bool result = inExpectedData(start, complete, data);
readObservations.emplace_back(serial, start, complete, data);
pruneTransactions();
return result;
}
MemChecker::WriteCluster*
MemChecker::ByteTracker::getIncompleteWriteCluster()
{
if (writeClusters.empty() || writeClusters.back().isComplete()) {
writeClusters.emplace_back();
}
return &writeClusters.back();
}
void
MemChecker::ByteTracker::startWrite(MemChecker::Serial serial, Tick start,
uint8_t data)
{
getIncompleteWriteCluster()->startWrite(serial, start, data);
}
void
MemChecker::ByteTracker::completeWrite(MemChecker::Serial serial, Tick complete)
{
getIncompleteWriteCluster()->completeWrite(serial, complete);
pruneTransactions();
}
void
MemChecker::ByteTracker::abortWrite(MemChecker::Serial serial)
{
getIncompleteWriteCluster()->abortWrite(serial);
}
void
MemChecker::ByteTracker::pruneTransactions()
{
// Obtain tick of first outstanding read. If there are no outstanding
// reads, we use curTick(), i.e. we will remove all readObservation except
// the most recent one.
const Tick before = outstandingReads.empty() ? curTick() :
outstandingReads.begin()->second.start;
// Pruning of readObservations
readObservations.erase(readObservations.begin(),
lastCompletedTransaction(&readObservations, before));
// Pruning of writeClusters
if (!writeClusters.empty()) {
writeClusters.erase(writeClusters.begin(),
lastCompletedTransaction(&writeClusters, before));
}
}
bool
MemChecker::completeRead(MemChecker::Serial serial, Tick complete,
Addr addr, size_t size, uint8_t *data)
{
bool result = true;
DPRINTF(MemChecker,
"completing read: serial = %d, complete = %d, "
"addr = %#llx, size = %d\n", serial, complete, addr, size);
for (size_t i = 0; i < size; ++i) {
ByteTracker *tracker = getByteTracker(addr + i);
if (!tracker->completeRead(serial, complete, data[i])) {
// Generate error message, and aggregate all failures for the bytes
// considered in this transaction in one message.
if (result) {
result = false;
errorMessage = "";
} else {
errorMessage += "\n";
}
errorMessage += csprintf(" Read transaction for address %#llx "
"failed: received %#x, expected ",
(unsigned long long)(addr + i), data[i]);
for (size_t j = 0; j < tracker->lastExpectedData().size(); ++j) {
errorMessage +=
csprintf("%#x%s",
tracker->lastExpectedData()[j],
(j == tracker->lastExpectedData().size() - 1)
? "" : "|");
}
}
}
if (!result) {
DPRINTF(MemChecker, "read of %#llx @ cycle %d failed:\n%s\n", addr,
complete, errorMessage);
}
return result;
}
void
MemChecker::reset(Addr addr, size_t size)
{
for (size_t i = 0; i < size; ++i) {
byte_trackers.erase(addr + i);
}
}
MemChecker*
MemCheckerParams::create()
{
return new MemChecker(this);
}

568
src/mem/mem_checker.hh Normal file
View file

@ -0,0 +1,568 @@
/*
* Copyright (c) 2014 ARM Limited
* All rights reserved.
*
* The license below extends only to copyright in the software and shall
* not be construed as granting a license to any other intellectual
* property including but not limited to intellectual property relating
* to a hardware implementation of the functionality of the software
* licensed hereunder. You may use the software subject to the license
* terms below provided that you ensure that this notice is replicated
* unmodified and in its entirety in all distributions of the software,
* modified or unmodified, in source code or in binary form.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met: redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer;
* redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution;
* neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Authors: Rune Holm
* Marco Elver
*/
#ifndef __MEM_MEM_CHECKER_HH__
#define __MEM_MEM_CHECKER_HH__
#include <list>
#include <map>
#include <string>
#include <vector>
#include "base/hashmap.hh"
#include "base/misc.hh"
#include "base/types.hh"
#include "debug/MemChecker.hh"
#include "params/MemChecker.hh"
#include "sim/core.hh"
#include "sim/sim_object.hh"
/**
* MemChecker. Verifies that reads observe the values from permissible writes.
* As memory operations have a start and completion time, we consider them as
* transactions which have a start and end time. Because of this, the lifetimes
* of transactions of memory operations may be overlapping -- we assume that if
* there is overlap between writes, they could be reordered by the memory
* subsystem, and a read could any of these. For more detail, see comments of
* inExpectedData().
*
* For simplicity, the permissible values a read can observe are only dependent
* on the particular location, and we do not consider the effect of multi-byte
* reads or writes. This precludes us from discovering single-copy atomicity
* violations.
*/
class MemChecker : public SimObject
{
public:
/**
* The Serial type is used to be able to uniquely identify a transaction as
* it passes through the system. It's value is independent of any other
* system counters.
*/
typedef uint64_t Serial;
static const Serial SERIAL_INITIAL = 0; //!< Initial serial
/**
* The initial tick the system starts with. Must not be larger than the
* minimum value that curTick() could return at any time in the system's
* execution.
*/
static const Tick TICK_INITIAL = 0;
/**
* The maximum value that curTick() could ever return.
*/
static const Tick TICK_FUTURE = MaxTick;
/**
* Initial data value. No requirements.
*/
static const uint8_t DATA_INITIAL = 0x00;
/**
* The Transaction class captures the lifetimes of read and write
* operations, and the values they consumed or produced respectively.
*/
class Transaction
{
public:
Transaction(Serial _serial,
Tick _start, Tick _complete,
uint8_t _data = DATA_INITIAL)
: serial(_serial),
start(_start), complete(_complete),
data(_data)
{}
public:
Serial serial; //!< Unique identifying serial
Tick start; //!< Start tick
Tick complete; //!< Completion tick
/**
* Depending on the memory operation, the data value either represents:
* for writes, the value written upon start; for reads, the value read
* upon completion.
*/
uint8_t data;
/**
* Orders Transactions for use with std::map.
*/
bool operator<(const Transaction& rhs) const
{ return serial < rhs.serial; }
};
/**
* The WriteCluster class captures sets of writes where all writes are
* overlapping with at least one other write. Capturing writes in this way
* simplifies pruning of writes.
*/
class WriteCluster
{
public:
WriteCluster()
: start(TICK_FUTURE), complete(TICK_FUTURE),
completeMax(TICK_INITIAL), numIncomplete(0)
{}
/**
* Starts a write transaction.
*
* @param serial Unique identifier of the write.
* @param _start When the write was sent off to the memory subsystem.
* @param data The data that this write passed to the memory
* subsystem.
*/
void startWrite(Serial serial, Tick _start, uint8_t data);
/**
* Completes a write transaction.
*
* @param serial Unique identifier of a write *previously started*.
* @param _complete When the write was sent off to the memory
* subsystem.
*/
void completeWrite(Serial serial, Tick _complete);
/**
* Aborts a write transaction.
*
* @param serial Unique identifier of a write *previously started*.
*/
void abortWrite(Serial serial);
/**
* @return true if this cluster's write all completed, false otherwise.
*/
bool isComplete() const { return complete != TICK_FUTURE; }
public:
Tick start; //!< Start of earliest write in cluster
Tick complete; //!< Completion of last write in cluster
/**
* Map of Serial --> Transaction of all writes in cluster; contains
* all, in-flight or already completed.
*/
m5::hash_map<Serial, Transaction> writes;
private:
Tick completeMax;
size_t numIncomplete;
};
typedef std::list<Transaction> TransactionList;
typedef std::list<WriteCluster> WriteClusterList;
/**
* The ByteTracker keeps track of transactions for the *same byte* -- all
* outstanding reads, the completed reads (and what they observed) and write
* clusters (see WriteCluster).
*/
class ByteTracker : public Named
{
public:
ByteTracker(Addr addr = 0, const MemChecker *parent = NULL)
: Named((parent != NULL ? parent->name() : "") +
csprintf(".ByteTracker@%#llx", addr))
{
// The initial transaction has start == complete == TICK_INITIAL,
// indicating that there has been no real write to this location;
// therefore, upon checking, we do not expect any particular value.
readObservations.emplace_back(
Transaction(SERIAL_INITIAL, TICK_INITIAL, TICK_INITIAL,
DATA_INITIAL));
}
/**
* Starts a read transaction.
*
* @param serial Unique identifier for the read.
* @param start When the read was sent off to the memory subsystem.
*/
void startRead(Serial serial, Tick start);
/**
* Given a start and end time (of any read transaction), this function
* iterates through all data that such a read is expected to see. The
* data parameter is the actual value that we observed, and the
* function immediately returns true when a match is found, false
* otherwise.
*
* The set of expected data are:
*
* 1. The last value observed by a read with a completion time before
* this start time (if any).
*
* 2. The data produced by write transactions with a completion after
* the last observed read start time. Only data produced in the
* closest overlapping / earlier write cluster relative to this check
* request is considered, as writes in separate clusters are not
* reordered.
*
* @param start Start time of transaction to validate.
* @param complete End time of transaction to validate.
* @param data The value that we have actually seen.
*
* @return True if a match is found, false otherwise.
*/
bool inExpectedData(Tick start, Tick complete, uint8_t data);
/**
* Completes a read transaction that is still outstanding.
*
* @param serial Unique identifier of a read *previously started*.
* @param complete When the read got a response.
* @param data The data returned by the memory subsystem.
*/
bool completeRead(Serial serial, Tick complete, uint8_t data);
/**
* Starts a write transaction. Wrapper to startWrite of WriteCluster
* instance.
*
* @param serial Unique identifier of the write.
* @param start When the write was sent off to the memory subsystem.
* @param data The data that this write passed to the memory
* subsystem.
*/
void startWrite(Serial serial, Tick start, uint8_t data);
/**
* Completes a write transaction. Wrapper to startWrite of WriteCluster
* instance.
*
* @param serial Unique identifier of a write *previously started*.
* @param complete When the write was sent off to the memory subsystem.
*/
void completeWrite(Serial serial, Tick complete);
/**
* Aborts a write transaction. Wrapper to abortWrite of WriteCluster
* instance.
*
* @param serial Unique identifier of a write *previously started*.
*/
void abortWrite(Serial serial);
/**
* This function returns the expected data that inExpectedData iterated
* through in the last call. If inExpectedData last returned true, the
* set may be incomplete; if inExpectedData last returned false, the
* vector will contain the full set.
*
* @return Reference to internally maintained vector maintaining last
* expected data that inExpectedData iterated through.
*/
const std::vector<uint8_t>& lastExpectedData() const
{ return _lastExpectedData; }
private:
/**
* Convenience function to return the most recent incomplete write
* cluster. Instantiates new write cluster if the most recent one has
* been completed.
*
* @return The most recent incomplete write cluster.
*/
WriteCluster* getIncompleteWriteCluster();
/**
* Helper function to return an iterator to the entry of a container of
* Transaction compatible classes, before a certain tick.
*
* @param before Tick value which should be greater than the
* completion tick of the returned element.
*
* @return Iterator into container.
*/
template <class TList>
typename TList::iterator lastCompletedTransaction(TList *l, Tick before)
{
assert(!l->empty());
// Scanning backwards increases the chances of getting a match
// quicker.
auto it = l->end();
for (--it; it != l->begin() && it->complete >= before; --it);
return it;
}
/**
* Prunes no longer needed transactions. We only keep up to the last /
* most recent of each, readObservations and writeClusters, before the
* first outstanding read.
*
* It depends on the contention / overlap between memory operations to
* the same location of a particular workload how large each of them
* would grow.
*/
void pruneTransactions();
private:
/**
* Maintains a map of Serial -> Transaction for all outstanding reads.
*
* Use an ordered map here, as this makes pruneTransactions() more
* efficient (find first outstanding read).
*/
std::map<Serial, Transaction> outstandingReads;
/**
* List of completed reads, i.e. observations of reads.
*/
TransactionList readObservations;
/**
* List of write clusters for this address.
*/
WriteClusterList writeClusters;
/**
* See lastExpectedData().
*/
std::vector<uint8_t> _lastExpectedData;
};
public:
MemChecker(const MemCheckerParams *p)
: SimObject(p),
nextSerial(SERIAL_INITIAL)
{}
virtual ~MemChecker() {}
/**
* Starts a read transaction.
*
* @param start Tick this read was sent to the memory subsystem.
* @param addr Address for read.
* @param size Size of data expected.
*
* @return Serial representing the unique identifier for this transaction.
*/
Serial startRead(Tick start, Addr addr, size_t size);
/**
* Starts a write transaction.
*
* @param start Tick when this write was sent to the memory subsystem.
* @param addr Address for write.
* @param size Size of data to be written.
* @param data Pointer to size bytes, containing data to be written.
*
* @return Serial representing the unique identifier for this transaction.
*/
Serial startWrite(Tick start, Addr addr, size_t size, const uint8_t *data);
/**
* Completes a previously started read transaction.
*
* @param serial A serial of a read that was previously started and
* matches the address of the previously started read.
* @param complete Tick we received the response from the memory subsystem.
* @param addr Address for read.
* @param size Size of data received.
* @param data Pointer to size bytes, containing data received.
*
* @return True if the data we received is in the expected set, false
* otherwise.
*/
bool completeRead(Serial serial, Tick complete,
Addr addr, size_t size, uint8_t *data);
/**
* Completes a previously started write transaction.
*
* @param serial A serial of a write that was previously started and
* matches the address of the previously started write.
* @param complete Tick we received acknowledgment of completion from the
* memory subsystem.
* @param addr Address for write.
* @param size The size of the data written.
*/
void completeWrite(Serial serial, Tick complete, Addr addr, size_t size);
/**
* Aborts a previously started write transaction.
*
* @param serial A serial of a write that was previously started and
* matches the address of the previously started write.
* @param addr Address for write.
* @param size The size of the data written.
*/
void abortWrite(Serial serial, Addr addr, size_t size);
/**
* Resets the entire checker. Note that if there are transactions
* in-flight, this will cause a warning to be issued if these are completed
* after the reset. This does not reset nextSerial to avoid such a race
* condition: where a transaction started before a reset with serial S,
* then reset() was called, followed by a start of a transaction with the
* same serial S and then receive a completion of the transaction before
* the reset with serial S.
*/
void reset()
{ byte_trackers.clear(); }
/**
* Resets an address-range. This may be useful in case other unmonitored
* parts of the system caused modification to this memory, but we cannot
* track their written values.
*
* @param addr Address base.
* @param size Size of range to be invalidated.
*/
void reset(Addr addr, size_t size);
/**
* In completeRead, if an error is encountered, this does not print nor
* cause an error, but instead should be handled by the caller. However, to
* record information about the cause of an error, completeRead creates an
* errorMessage. This function returns the last error that was detected in
* completeRead.
*
* @return Reference to string of error message.
*/
const std::string& getErrorMessage() const { return errorMessage; }
private:
/**
* Returns the instance of ByteTracker for the requested location.
*/
ByteTracker* getByteTracker(Addr addr)
{
auto it = byte_trackers.find(addr);
if (it == byte_trackers.end()) {
it = byte_trackers.insert(
std::make_pair(addr, ByteTracker(addr, this))).first;
}
return &it->second;
};
private:
/**
* Detailed error message of the last violation in completeRead.
*/
std::string errorMessage;
/**
* Next distinct serial to be assigned to the next transaction to be
* started.
*/
Serial nextSerial;
/**
* Maintain a map of address --> byte-tracker. Per-byte entries are
* initialized as needed.
*
* The required space for this obviously grows with the number of distinct
* addresses used for a particular workload. The used size is independent on
* the number of nodes in the system, those may affect the size of per-byte
* tracking information.
*
* Access via getByteTracker()!
*/
m5::hash_map<Addr, ByteTracker> byte_trackers;
};
inline MemChecker::Serial
MemChecker::startRead(Tick start, Addr addr, size_t size)
{
DPRINTF(MemChecker,
"starting read: serial = %d, start = %d, addr = %#llx, "
"size = %d\n", nextSerial, start, addr , size);
for (size_t i = 0; i < size; ++i) {
getByteTracker(addr + i)->startRead(nextSerial, start);
}
return nextSerial++;
}
inline MemChecker::Serial
MemChecker::startWrite(Tick start, Addr addr, size_t size, const uint8_t *data)
{
DPRINTF(MemChecker,
"starting write: serial = %d, start = %d, addr = %#llx, "
"size = %d\n", nextSerial, start, addr, size);
for (size_t i = 0; i < size; ++i) {
getByteTracker(addr + i)->startWrite(nextSerial, start, data[i]);
}
return nextSerial++;
}
inline void
MemChecker::completeWrite(MemChecker::Serial serial, Tick complete,
Addr addr, size_t size)
{
DPRINTF(MemChecker,
"completing write: serial = %d, complete = %d, "
"addr = %#llx, size = %d\n", serial, complete, addr, size);
for (size_t i = 0; i < size; ++i) {
getByteTracker(addr + i)->completeWrite(serial, complete);
}
}
inline void
MemChecker::abortWrite(MemChecker::Serial serial, Addr addr, size_t size)
{
DPRINTF(MemChecker,
"aborting write: serial = %d, addr = %#llx, size = %d\n",
serial, addr, size);
for (size_t i = 0; i < size; ++i) {
getByteTracker(addr + i)->abortWrite(serial);
}
}
#endif // __MEM_MEM_CHECKER_HH__

View file

@ -0,0 +1,374 @@
/*
* Copyright (c) 2012-2014 ARM Limited
* All rights reserved
*
* The license below extends only to copyright in the software and shall
* not be construed as granting a license to any other intellectual
* property including but not limited to intellectual property relating
* to a hardware implementation of the functionality of the software
* licensed hereunder. You may use the software subject to the license
* terms below provided that you ensure that this notice is replicated
* unmodified and in its entirety in all distributions of the software,
* modified or unmodified, in source code or in binary form.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met: redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer;
* redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution;
* neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Authors: Thomas Grass
* Andreas Hansson
* Marco Elver
*/
#include <memory>
#include "base/output.hh"
#include "base/trace.hh"
#include "debug/MemCheckerMonitor.hh"
#include "mem/mem_checker_monitor.hh"
using namespace std;
MemCheckerMonitor::MemCheckerMonitor(Params* params)
: MemObject(params),
masterPort(name() + "-master", *this),
slavePort(name() + "-slave", *this),
warnOnly(params->warn_only),
memchecker(params->memchecker)
{}
MemCheckerMonitor::~MemCheckerMonitor()
{}
MemCheckerMonitor*
MemCheckerMonitorParams::create()
{
return new MemCheckerMonitor(this);
}
void
MemCheckerMonitor::init()
{
// make sure both sides of the monitor are connected
if (!slavePort.isConnected() || !masterPort.isConnected())
fatal("Communication monitor is not connected on both sides.\n");
}
BaseMasterPort&
MemCheckerMonitor::getMasterPort(const std::string& if_name, PortID idx)
{
if (if_name == "master" || if_name == "mem_side") {
return masterPort;
} else {
return MemObject::getMasterPort(if_name, idx);
}
}
BaseSlavePort&
MemCheckerMonitor::getSlavePort(const std::string& if_name, PortID idx)
{
if (if_name == "slave" || if_name == "cpu_side") {
return slavePort;
} else {
return MemObject::getSlavePort(if_name, idx);
}
}
void
MemCheckerMonitor::recvFunctional(PacketPtr pkt)
{
Addr addr = pkt->getAddr();
unsigned size = pkt->getSize();
// Conservatively reset this address-range. Alternatively we could try to
// update the values seen by the memchecker, however, there may be other
// reads/writes to these location from other devices we do not see.
memchecker->reset(addr, size);
masterPort.sendFunctional(pkt);
DPRINTF(MemCheckerMonitor,
"Forwarded functional access: addr = %#llx, size = %d\n",
addr, size);
}
void
MemCheckerMonitor::recvFunctionalSnoop(PacketPtr pkt)
{
Addr addr = pkt->getAddr();
unsigned size = pkt->getSize();
// See above.
memchecker->reset(addr, size);
slavePort.sendFunctionalSnoop(pkt);
DPRINTF(MemCheckerMonitor,
"Received functional snoop: addr = %#llx, size = %d\n",
addr, size);
}
Tick
MemCheckerMonitor::recvAtomic(PacketPtr pkt)
{
assert(false && "Atomic not supported");
return masterPort.sendAtomic(pkt);
}
Tick
MemCheckerMonitor::recvAtomicSnoop(PacketPtr pkt)
{
assert(false && "Atomic not supported");
return slavePort.sendAtomicSnoop(pkt);
}
bool
MemCheckerMonitor::recvTimingReq(PacketPtr pkt)
{
// should always see a request
assert(pkt->isRequest());
// Store relevant fields of packet, because packet may be modified
// or even deleted when sendTiming() is called.
//
// For reads we are only interested in real reads, and not prefetches, as
// it is not guaranteed that the prefetch returns any useful data.
bool is_read = pkt->isRead() && !pkt->req->isPrefetch();
bool is_write = pkt->isWrite();
unsigned size = pkt->getSize();
Addr addr = pkt->getAddr();
bool expects_response = pkt->needsResponse() && !pkt->memInhibitAsserted();
std::unique_ptr<uint8_t> pkt_data;
MemCheckerMonitorSenderState* state = NULL;
if (expects_response && is_write) {
// On receipt of a request, only need to allocate pkt_data if this is a
// write. For reads, we have no data yet, so it doesn't make sense to
// allocate.
pkt_data.reset(new uint8_t[size]);
memcpy(pkt_data.get(), pkt->getConstPtr<uint8_t*>(), size);
}
// If a cache miss is served by a cache, a monitor near the memory
// would see a request which needs a response, but this response
// would be inhibited and not come back from the memory. Therefore
// we additionally have to check the inhibit flag.
if (expects_response && (is_read || is_write)) {
state = new MemCheckerMonitorSenderState(0);
pkt->pushSenderState(state);
}
// Attempt to send the packet (always succeeds for inhibited
// packets)
bool successful = masterPort.sendTimingReq(pkt);
// If not successful, restore the sender state
if (!successful && expects_response && (is_read || is_write)) {
delete pkt->popSenderState();
}
if (successful && expects_response) {
if (is_read) {
MemChecker::Serial serial = memchecker->startRead(curTick(),
addr,
size);
// At the time where we push the sender-state, we do not yet know
// the serial the MemChecker class will assign to this request. We
// cannot call startRead at the time we push the sender-state, as
// the masterPort may not be successful in executing sendTimingReq,
// and in case of a failure, we must not modify the state of the
// MemChecker.
//
// Once we know that sendTimingReq was successful, we can set the
// serial of the newly constructed sender-state. This is legal, as
// we know that nobody else will touch nor is responsible for
// deletion of our sender-state.
state->serial = serial;
DPRINTF(MemCheckerMonitor,
"Forwarded read request: serial = %d, addr = %#llx, "
"size = %d\n",
serial, addr, size);
} else if (is_write) {
MemChecker::Serial serial = memchecker->startWrite(curTick(),
addr,
size,
pkt_data.get());
state->serial = serial;
DPRINTF(MemCheckerMonitor,
"Forwarded write request: serial = %d, addr = %#llx, "
"size = %d\n",
serial, addr, size);
} else {
DPRINTF(MemCheckerMonitor,
"Forwarded non read/write request: addr = %#llx\n", addr);
}
} else if (successful) {
DPRINTF(MemCheckerMonitor,
"Forwarded inhibited request: addr = %#llx\n", addr);
}
return successful;
}
bool
MemCheckerMonitor::recvTimingResp(PacketPtr pkt)
{
// should always see responses
assert(pkt->isResponse());
// Store relevant fields of packet, because packet may be modified
// or even deleted when sendTiming() is called.
bool is_read = pkt->isRead() && !pkt->req->isPrefetch();
bool is_write = pkt->isWrite();
bool is_failed_LLSC = pkt->isLLSC() && pkt->req->getExtraData() == 0;
unsigned size = pkt->getSize();
Addr addr = pkt->getAddr();
std::unique_ptr<uint8_t> pkt_data;
MemCheckerMonitorSenderState* received_state = NULL;
if (is_read) {
// On receipt of a response, only need to allocate pkt_data if this is
// a read. For writes, we have already given the MemChecker the data on
// the request, so it doesn't make sense to allocate on write.
pkt_data.reset(new uint8_t[size]);
memcpy(pkt_data.get(), pkt->getConstPtr<uint8_t*>(), size);
}
if (is_read || is_write) {
received_state =
dynamic_cast<MemCheckerMonitorSenderState*>(pkt->senderState);
// Restore initial sender state
panic_if(received_state == NULL,
"Monitor got a response without monitor sender state\n");
// Restore the state
pkt->senderState = received_state->predecessor;
}
// Attempt to send the packet
bool successful = slavePort.sendTimingResp(pkt);
// If packet successfully send, complete transaction in MemChecker
// instance, and delete sender state, otherwise restore state.
if (successful) {
if (is_read) {
DPRINTF(MemCheckerMonitor,
"Received read response: serial = %d, addr = %#llx, "
"size = %d\n",
received_state->serial, addr, size);
bool result = memchecker->completeRead(received_state->serial,
curTick(),
addr,
size,
pkt_data.get());
if (!result) {
warn("%s: read of %#llx @ cycle %d failed:\n%s\n",
name(),
addr, curTick(),
memchecker->getErrorMessage().c_str());
panic_if(!warnOnly, "MemChecker violation!");
}
delete received_state;
} else if (is_write) {
DPRINTF(MemCheckerMonitor,
"Received write response: serial = %d, addr = %#llx, "
"size = %d\n",
received_state->serial, addr, size);
if (is_failed_LLSC) {
// The write was not successful, let MemChecker know.
memchecker->abortWrite(received_state->serial,
addr,
size);
} else {
memchecker->completeWrite(received_state->serial,
curTick(),
addr,
size);
}
delete received_state;
} else {
DPRINTF(MemCheckerMonitor,
"Received non read/write response: addr = %#llx\n", addr);
}
} else if (is_read || is_write) {
// Don't delete anything and let the packet look like we
// did not touch it
pkt->senderState = received_state;
}
return successful;
}
void
MemCheckerMonitor::recvTimingSnoopReq(PacketPtr pkt)
{
slavePort.sendTimingSnoopReq(pkt);
}
bool
MemCheckerMonitor::recvTimingSnoopResp(PacketPtr pkt)
{
return masterPort.sendTimingSnoopResp(pkt);
}
bool
MemCheckerMonitor::isSnooping() const
{
// check if the connected master port is snooping
return slavePort.isSnooping();
}
AddrRangeList
MemCheckerMonitor::getAddrRanges() const
{
// get the address ranges of the connected slave port
return masterPort.getAddrRanges();
}
void
MemCheckerMonitor::recvRetryMaster()
{
slavePort.sendRetry();
}
void
MemCheckerMonitor::recvRetrySlave()
{
masterPort.sendRetry();
}
void
MemCheckerMonitor::recvRangeChange()
{
slavePort.sendRangeChange();
}

View file

@ -0,0 +1,240 @@
/*
* Copyright (c) 2012-2014 ARM Limited
* All rights reserved
*
* The license below extends only to copyright in the software and shall
* not be construed as granting a license to any other intellectual
* property including but not limited to intellectual property relating
* to a hardware implementation of the functionality of the software
* licensed hereunder. You may use the software subject to the license
* terms below provided that you ensure that this notice is replicated
* unmodified and in its entirety in all distributions of the software,
* modified or unmodified, in source code or in binary form.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met: redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer;
* redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution;
* neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Authors: Thomas Grass
* Andreas Hansson
* Marco Elver
*/
#ifndef __MEM_MEM_CHECKER_MONITOR_HH__
#define __MEM_MEM_CHECKER_MONITOR_HH__
#include "base/statistics.hh"
#include "mem/mem_checker.hh"
#include "mem/mem_object.hh"
#include "params/MemCheckerMonitor.hh"
#include "sim/system.hh"
/**
* Implements a MemChecker monitor, to be inserted between two ports.
*/
class MemCheckerMonitor : public MemObject
{
public:
/** Parameters of memchecker monitor */
typedef MemCheckerMonitorParams Params;
const Params* params() const
{ return reinterpret_cast<const Params*>(_params); }
/**
* Constructor based on the Python params
*
* @param params Python parameters
*/
MemCheckerMonitor(Params* params);
/** Destructor */
~MemCheckerMonitor();
virtual BaseMasterPort& getMasterPort(const std::string& if_name,
PortID idx = InvalidPortID);
virtual BaseSlavePort& getSlavePort(const std::string& if_name,
PortID idx = InvalidPortID);
virtual void init();
private:
struct MemCheckerMonitorSenderState : public Packet::SenderState
{
MemCheckerMonitorSenderState(MemChecker::Serial _serial)
: serial(_serial)
{}
MemChecker::Serial serial;
};
/**
* This is the master port of the communication monitor. All recv
* functions call a function in MemCheckerMonitor, where the
* send function of the slave port is called. Besides this, these
* functions can also perform actions for capturing statistics.
*/
class MonitorMasterPort : public MasterPort
{
public:
MonitorMasterPort(const std::string& _name, MemCheckerMonitor& _mon)
: MasterPort(_name, &_mon), mon(_mon)
{ }
protected:
void recvFunctionalSnoop(PacketPtr pkt)
{
mon.recvFunctionalSnoop(pkt);
}
Tick recvAtomicSnoop(PacketPtr pkt)
{
return mon.recvAtomicSnoop(pkt);
}
bool recvTimingResp(PacketPtr pkt)
{
return mon.recvTimingResp(pkt);
}
void recvTimingSnoopReq(PacketPtr pkt)
{
mon.recvTimingSnoopReq(pkt);
}
void recvRangeChange()
{
mon.recvRangeChange();
}
bool isSnooping() const
{
return mon.isSnooping();
}
void recvRetry()
{
mon.recvRetryMaster();
}
private:
MemCheckerMonitor& mon;
};
/** Instance of master port, facing the memory side */
MonitorMasterPort masterPort;
/**
* This is the slave port of the communication monitor. All recv
* functions call a function in MemCheckerMonitor, where the
* send function of the master port is called. Besides this, these
* functions can also perform actions for capturing statistics.
*/
class MonitorSlavePort : public SlavePort
{
public:
MonitorSlavePort(const std::string& _name, MemCheckerMonitor& _mon)
: SlavePort(_name, &_mon), mon(_mon)
{ }
protected:
void recvFunctional(PacketPtr pkt)
{
mon.recvFunctional(pkt);
}
Tick recvAtomic(PacketPtr pkt)
{
return mon.recvAtomic(pkt);
}
bool recvTimingReq(PacketPtr pkt)
{
return mon.recvTimingReq(pkt);
}
bool recvTimingSnoopResp(PacketPtr pkt)
{
return mon.recvTimingSnoopResp(pkt);
}
AddrRangeList getAddrRanges() const
{
return mon.getAddrRanges();
}
void recvRetry()
{
mon.recvRetrySlave();
}
private:
MemCheckerMonitor& mon;
};
/** Instance of slave port, i.e. on the CPU side */
MonitorSlavePort slavePort;
void recvFunctional(PacketPtr pkt);
void recvFunctionalSnoop(PacketPtr pkt);
Tick recvAtomic(PacketPtr pkt);
Tick recvAtomicSnoop(PacketPtr pkt);
bool recvTimingReq(PacketPtr pkt);
bool recvTimingResp(PacketPtr pkt);
void recvTimingSnoopReq(PacketPtr pkt);
bool recvTimingSnoopResp(PacketPtr pkt);
AddrRangeList getAddrRanges() const;
bool isSnooping() const;
void recvRetryMaster();
void recvRetrySlave();
void recvRangeChange();
bool warnOnly;
MemChecker *memchecker;
};
#endif //__MEM_MEM_CHECKER_MONITOR_HH__