pwr: Adds logic to enter power gating for the cpu model
[gem5.git] / src / cpu / simple / atomic.cc
index 0ac4a54953facbbcdb30f5f72ceb9d3e63345ece..9039e6137b1d3164e7f3b524c13a17d1fe4483e3 100644 (file)
@@ -1,5 +1,6 @@
 /*
- * Copyright (c) 2012-2013 ARM Limited
+ * Copyright 2014 Google, Inc.
+ * Copyright (c) 2012-2013,2015,2017 ARM Limited
  * All rights reserved.
  *
  * The license below extends only to copyright in the software and shall
  * Authors: Steve Reinhardt
  */
 
+#include "cpu/simple/atomic.hh"
+
 #include "arch/locked_mem.hh"
 #include "arch/mmapped_ipr.hh"
 #include "arch/utility.hh"
 #include "base/bigint.hh"
 #include "base/output.hh"
 #include "config/the_isa.hh"
-#include "cpu/simple/atomic.hh"
 #include "cpu/exetrace.hh"
 #include "debug/Drain.hh"
 #include "debug/ExecFaulting.hh"
 #include "mem/physical.hh"
 #include "params/AtomicSimpleCPU.hh"
 #include "sim/faults.hh"
-#include "sim/system.hh"
 #include "sim/full_system.hh"
+#include "sim/system.hh"
 
 using namespace std;
 using namespace TheISA;
 
-AtomicSimpleCPU::TickEvent::TickEvent(AtomicSimpleCPU *c)
-    : Event(CPU_Tick_Pri), cpu(c)
-{
-}
-
-
-void
-AtomicSimpleCPU::TickEvent::process()
-{
-    cpu->tick();
-}
-
-const char *
-AtomicSimpleCPU::TickEvent::description() const
-{
-    return "AtomicSimpleCPU tick";
-}
-
 void
 AtomicSimpleCPU::init()
 {
-    BaseCPU::init();
+    BaseSimpleCPU::init();
 
-    // Initialise the ThreadContext's memory proxies
-    tcBase()->initMemProxies(tcBase());
-
-    if (FullSystem && !params()->switched_out) {
-        ThreadID size = threadContexts.size();
-        for (ThreadID i = 0; i < size; ++i) {
-            ThreadContext *tc = threadContexts[i];
-            // initialize CPU, including PC
-            TheISA::initCPU(tc, tc->contextId());
-        }
-    }
-
-    // Atomic doesn't do MT right now, so contextId == threadId
-    ifetch_req.setThreadContext(_cpuId, 0); // Add thread ID if we add MT
-    data_read_req.setThreadContext(_cpuId, 0); // Add thread ID here too
-    data_write_req.setThreadContext(_cpuId, 0); // Add thread ID here too
+    int cid = threadContexts[0]->contextId();
+    ifetch_req.setContext(cid);
+    data_read_req.setContext(cid);
+    data_write_req.setContext(cid);
 }
 
 AtomicSimpleCPU::AtomicSimpleCPU(AtomicSimpleCPUParams *p)
-    : BaseSimpleCPU(p), tickEvent(this), width(p->width), locked(false),
+    : BaseSimpleCPU(p),
+      tickEvent([this]{ tick(); }, "AtomicSimpleCPU tick",
+                false, Event::CPU_Tick_Pri),
+      width(p->width), locked(false),
       simulate_data_stalls(p->simulate_data_stalls),
       simulate_inst_stalls(p->simulate_inst_stalls),
-      drain_manager(NULL),
       icachePort(name() + ".icache_port", this),
       dcachePort(name() + ".dcache_port", this),
-      fastmem(p->fastmem)
+      fastmem(p->fastmem), dcache_access(false), dcache_latency(0),
+      ppCommit(nullptr)
 {
     _status = Idle;
 }
@@ -123,23 +98,43 @@ AtomicSimpleCPU::~AtomicSimpleCPU()
     }
 }
 
-unsigned int
-AtomicSimpleCPU::drain(DrainManager *dm)
+DrainState
+AtomicSimpleCPU::drain()
 {
-    assert(!drain_manager);
+    // Deschedule any power gating event (if any)
+    deschedulePowerGatingEvent();
+
     if (switchedOut())
-        return 0;
+        return DrainState::Drained;
 
     if (!isDrained()) {
-        DPRINTF(Drain, "Requesting drain: %s\n", pcState());
-        drain_manager = dm;
-        return 1;
+        DPRINTF(Drain, "Requesting drain.\n");
+        return DrainState::Draining;
     } else {
         if (tickEvent.scheduled())
             deschedule(tickEvent);
 
+        activeThreads.clear();
         DPRINTF(Drain, "Not executing microcode, no need to drain.\n");
-        return 0;
+        return DrainState::Drained;
+    }
+}
+
+void
+AtomicSimpleCPU::threadSnoop(PacketPtr pkt, ThreadID sender)
+{
+    DPRINTF(SimpleCPU, "received snoop pkt for addr:%#x %s\n", pkt->getAddr(),
+            pkt->cmdString());
+
+    for (ThreadID tid = 0; tid < numThreads; tid++) {
+        if (tid != sender) {
+            if (getCpuAddrMonitor(tid)->doMonitor(pkt)) {
+                wakeup(tid);
+            }
+
+            TheISA::handleLockedSnoop(threadInfo[tid]->thread,
+                                      pkt, dcachePort.cacheBlockMask);
+        }
     }
 }
 
@@ -147,7 +142,6 @@ void
 AtomicSimpleCPU::drainResume()
 {
     assert(!tickEvent.scheduled());
-    assert(!drain_manager);
     if (switchedOut())
         return;
 
@@ -155,34 +149,40 @@ AtomicSimpleCPU::drainResume()
     verifyMemoryMode();
 
     assert(!threadContexts.empty());
-    if (threadContexts.size() > 1)
-        fatal("The atomic CPU only supports one thread.\n");
 
-    if (thread->status() == ThreadContext::Active) {
-        schedule(tickEvent, nextCycle());
-        _status = BaseSimpleCPU::Running;
-        notIdleFraction = 1;
-    } else {
-        _status = BaseSimpleCPU::Idle;
-        notIdleFraction = 0;
+    _status = BaseSimpleCPU::Idle;
+
+    for (ThreadID tid = 0; tid < numThreads; tid++) {
+        if (threadInfo[tid]->thread->status() == ThreadContext::Active) {
+            threadInfo[tid]->notIdleFraction = 1;
+            activeThreads.push_back(tid);
+            _status = BaseSimpleCPU::Running;
+
+            // Tick if any threads active
+            if (!tickEvent.scheduled()) {
+                schedule(tickEvent, nextCycle());
+            }
+        } else {
+            threadInfo[tid]->notIdleFraction = 0;
+        }
     }
 
-    system->totalNumInsts = 0;
+    // Reschedule any power gating event (if any)
+    schedulePowerGatingEvent();
 }
 
 bool
 AtomicSimpleCPU::tryCompleteDrain()
 {
-    if (!drain_manager)
+    if (drainState() != DrainState::Draining)
         return false;
 
-    DPRINTF(Drain, "tryCompleteDrain: %s\n", pcState());
+    DPRINTF(Drain, "tryCompleteDrain.\n");
     if (!isDrained())
         return false;
 
     DPRINTF(Drain, "CPU done draining, processing drain event\n");
-    drain_manager->signalDrainDone();
-    drain_manager = NULL;
+    signalDrainDone();
 
     return true;
 }
@@ -206,10 +206,6 @@ AtomicSimpleCPU::takeOverFrom(BaseCPU *oldCPU)
 
     // The tick event should have been descheduled by drain()
     assert(!tickEvent.scheduled());
-
-    ifetch_req.setThreadContext(_cpuId, 0); // Add thread ID if we add MT
-    data_read_req.setThreadContext(_cpuId, 0); // Add thread ID here too
-    data_write_req.setThreadContext(_cpuId, 0); // Add thread ID here too
 }
 
 void
@@ -222,22 +218,29 @@ AtomicSimpleCPU::verifyMemoryMode() const
 }
 
 void
-AtomicSimpleCPU::activateContext(ThreadID thread_num, Cycles delay)
+AtomicSimpleCPU::activateContext(ThreadID thread_num)
 {
-    DPRINTF(SimpleCPU, "ActivateContext %d (%d cycles)\n", thread_num, delay);
-
-    assert(thread_num == 0);
-    assert(thread);
+    DPRINTF(SimpleCPU, "ActivateContext %d\n", thread_num);
 
-    assert(_status == Idle);
-    assert(!tickEvent.scheduled());
+    assert(thread_num < numThreads);
 
-    notIdleFraction = 1;
-    numCycles += ticksToCycles(thread->lastActivate - thread->lastSuspend);
+    threadInfo[thread_num]->notIdleFraction = 1;
+    Cycles delta = ticksToCycles(threadInfo[thread_num]->thread->lastActivate -
+                                 threadInfo[thread_num]->thread->lastSuspend);
+    numCycles += delta;
+    ppCycles->notify(delta);
 
-    //Make sure ticks are still on multiples of cycles
-    schedule(tickEvent, clockEdge(delay));
+    if (!tickEvent.scheduled()) {
+        //Make sure ticks are still on multiples of cycles
+        schedule(tickEvent, clockEdge(Cycles(0)));
+    }
     _status = BaseSimpleCPU::Running;
+    if (std::find(activeThreads.begin(), activeThreads.end(), thread_num)
+        == activeThreads.end()) {
+        activeThreads.push_back(thread_num);
+    }
+
+    BaseCPU::activateContext(thread_num);
 }
 
 
@@ -246,21 +249,25 @@ AtomicSimpleCPU::suspendContext(ThreadID thread_num)
 {
     DPRINTF(SimpleCPU, "SuspendContext %d\n", thread_num);
 
-    assert(thread_num == 0);
-    assert(thread);
+    assert(thread_num < numThreads);
+    activeThreads.remove(thread_num);
 
     if (_status == Idle)
         return;
 
     assert(_status == BaseSimpleCPU::Running);
 
-    // tick event may not be scheduled if this gets called from inside
-    // an instruction's execution, e.g. "quiesce"
-    if (tickEvent.scheduled())
-        deschedule(tickEvent);
+    threadInfo[thread_num]->notIdleFraction = 0;
 
-    notIdleFraction = 0;
-    _status = Idle;
+    if (activeThreads.empty()) {
+        _status = Idle;
+
+        if (tickEvent.scheduled()) {
+            deschedule(tickEvent);
+        }
+    }
+
+    BaseCPU::suspendContext(thread_num);
 }
 
 
@@ -270,11 +277,25 @@ AtomicSimpleCPU::AtomicCPUDPort::recvAtomicSnoop(PacketPtr pkt)
     DPRINTF(SimpleCPU, "received snoop pkt for addr:%#x %s\n", pkt->getAddr(),
             pkt->cmdString());
 
+    // X86 ISA: Snooping an invalidation for monitor/mwait
+    AtomicSimpleCPU *cpu = (AtomicSimpleCPU *)(&owner);
+
+    for (ThreadID tid = 0; tid < cpu->numThreads; tid++) {
+        if (cpu->getCpuAddrMonitor(tid)->doMonitor(pkt)) {
+            cpu->wakeup(tid);
+        }
+    }
+
     // if snoop invalidates, release any associated locks
-    if (pkt->isInvalidate()) {
+    // When run without caches, Invalidation packets will not be received
+    // hence we must check if the incoming packets are writes and wakeup
+    // the processor accordingly
+    if (pkt->isInvalidate() || pkt->isWrite()) {
         DPRINTF(SimpleCPU, "received invalidation for addr:%#x\n",
                 pkt->getAddr());
-        TheISA::handleLockedSnoop(cpu->thread, pkt, cacheBlockMask);
+        for (auto &t_info : cpu->threadInfo) {
+            TheISA::handleLockedSnoop(t_info->thread, pkt, cacheBlockMask);
+        }
     }
 
     return 0;
@@ -286,24 +307,36 @@ AtomicSimpleCPU::AtomicCPUDPort::recvFunctionalSnoop(PacketPtr pkt)
     DPRINTF(SimpleCPU, "received snoop pkt for addr:%#x %s\n", pkt->getAddr(),
             pkt->cmdString());
 
+    // X86 ISA: Snooping an invalidation for monitor/mwait
+    AtomicSimpleCPU *cpu = (AtomicSimpleCPU *)(&owner);
+    for (ThreadID tid = 0; tid < cpu->numThreads; tid++) {
+        if (cpu->getCpuAddrMonitor(tid)->doMonitor(pkt)) {
+            cpu->wakeup(tid);
+        }
+    }
+
     // if snoop invalidates, release any associated locks
     if (pkt->isInvalidate()) {
         DPRINTF(SimpleCPU, "received invalidation for addr:%#x\n",
                 pkt->getAddr());
-        TheISA::handleLockedSnoop(cpu->thread, pkt, cacheBlockMask);
+        for (auto &t_info : cpu->threadInfo) {
+            TheISA::handleLockedSnoop(t_info->thread, pkt, cacheBlockMask);
+        }
     }
 }
 
 Fault
-AtomicSimpleCPU::readMem(Addr addr, uint8_t * data,
-                         unsigned size, unsigned flags)
+AtomicSimpleCPU::readMem(Addr addr, uint8_t * data, unsigned size,
+                         Request::Flags flags)
 {
+    SimpleExecContext& t_info = *threadInfo[curThread];
+    SimpleThread* thread = t_info.thread;
+
     // use the CPU's statically allocated read request and packet objects
     Request *req = &data_read_req;
 
-    if (traceData) {
-        traceData->setAddr(addr);
-    }
+    if (traceData)
+        traceData->setMem(addr, size, flags);
 
     //The size of the data we're trying to read.
     int fullSize = size;
@@ -322,12 +355,12 @@ AtomicSimpleCPU::readMem(Addr addr, uint8_t * data,
         req->setVirt(0, addr, size, flags, dataMasterId(), thread->pcState().instAddr());
 
         // translate to physical address
-        Fault fault = thread->dtb->translateAtomic(req, tc, BaseTLB::Read);
+        Fault fault = thread->dtb->translateAtomic(req, thread->getTC(),
+                                                          BaseTLB::Read);
 
         // Now do the access.
         if (fault == NoFault && !req->getFlags().isSet(Request::NO_ACCESS)) {
-            Packet pkt(req, MemCmd::ReadReq);
-            pkt.refineCommand();
+            Packet pkt(req, Packet::makeReadCmd(req));
             pkt.dataStatic(data);
 
             if (req->isMmappedIpr())
@@ -359,10 +392,11 @@ AtomicSimpleCPU::readMem(Addr addr, uint8_t * data,
         //If we don't need to access a second cache line, stop now.
         if (secondAddr <= addr)
         {
-            if (req->isLocked() && fault == NoFault) {
+            if (req->isLockedRMW() && fault == NoFault) {
                 assert(!locked);
                 locked = true;
             }
+
             return fault;
         }
 
@@ -379,12 +413,20 @@ AtomicSimpleCPU::readMem(Addr addr, uint8_t * data,
     }
 }
 
-
 Fault
-AtomicSimpleCPU::writeMem(uint8_t *data, unsigned size,
-                          Addr addr, unsigned flags, uint64_t *res)
+AtomicSimpleCPU::initiateMemRead(Addr addr, unsigned size,
+                                 Request::Flags flags)
 {
+    panic("initiateMemRead() is for timing accesses, and should "
+          "never be called on AtomicSimpleCPU.\n");
+}
 
+Fault
+AtomicSimpleCPU::writeMem(uint8_t *data, unsigned size, Addr addr,
+                          Request::Flags flags, uint64_t *res)
+{
+    SimpleExecContext& t_info = *threadInfo[curThread];
+    SimpleThread* thread = t_info.thread;
     static uint8_t zero_array[64] = {};
 
     if (data == NULL) {
@@ -397,9 +439,8 @@ AtomicSimpleCPU::writeMem(uint8_t *data, unsigned size,
     // use the CPU's statically allocated write request and packet objects
     Request *req = &data_write_req;
 
-    if (traceData) {
-        traceData->setAddr(addr);
-    }
+    if (traceData)
+        traceData->setMem(addr, size, flags);
 
     //The size of the data we're trying to read.
     int fullSize = size;
@@ -408,17 +449,17 @@ AtomicSimpleCPU::writeMem(uint8_t *data, unsigned size,
     //across a cache line boundary.
     Addr secondAddr = roundDown(addr + size - 1, cacheLineSize());
 
-    if(secondAddr > addr)
+    if (secondAddr > addr)
         size = secondAddr - addr;
 
     dcache_latency = 0;
 
     req->taskId(taskId());
-    while(1) {
+    while (1) {
         req->setVirt(0, addr, size, flags, dataMasterId(), thread->pcState().instAddr());
 
         // translate to physical address
-        Fault fault = thread->dtb->translateAtomic(req, tc, BaseTLB::Write);
+        Fault fault = thread->dtb->translateAtomic(req, thread->getTC(), BaseTLB::Write);
 
         // Now do the access.
         if (fault == NoFault) {
@@ -448,13 +489,16 @@ AtomicSimpleCPU::writeMem(uint8_t *data, unsigned size,
                         system->getPhysMem().access(&pkt);
                     else
                         dcache_latency += dcachePort.sendAtomic(&pkt);
+
+                    // Notify other threads on this CPU of write
+                    threadSnoop(&pkt, curThread);
                 }
                 dcache_access = true;
                 assert(!pkt.isError());
 
                 if (req->isSwap()) {
                     assert(res);
-                    memcpy(res, pkt.getPtr<uint8_t>(), fullSize);
+                    memcpy(res, pkt.getConstPtr<uint8_t>(), fullSize);
                 }
             }
 
@@ -467,10 +511,12 @@ AtomicSimpleCPU::writeMem(uint8_t *data, unsigned size,
         //stop now.
         if (fault != NoFault || secondAddr <= addr)
         {
-            if (req->isLocked() && fault == NoFault) {
+            if (req->isLockedRMW() && fault == NoFault) {
                 assert(locked);
                 locked = false;
             }
+
+
             if (fault != NoFault && req->isPrefetch()) {
                 return NoFault;
             } else {
@@ -497,15 +543,32 @@ AtomicSimpleCPU::tick()
 {
     DPRINTF(SimpleCPU, "Tick\n");
 
+    // Change thread if multi-threaded
+    swapActiveThread();
+
+    // Set memroy request ids to current thread
+    if (numThreads > 1) {
+        ContextID cid = threadContexts[curThread]->contextId();
+
+        ifetch_req.setContext(cid);
+        data_read_req.setContext(cid);
+        data_write_req.setContext(cid);
+    }
+
+    SimpleExecContext& t_info = *threadInfo[curThread];
+    SimpleThread* thread = t_info.thread;
+
     Tick latency = 0;
 
     for (int i = 0; i < width || locked; ++i) {
         numCycles++;
+        ppCycles->notify(1);
 
-        if (!curStaticInst || !curStaticInst->isDelayedCommit())
+        if (!curStaticInst || !curStaticInst->isDelayedCommit()) {
             checkForInterrupts();
+            checkPcEventQueue();
+        }
 
-        checkPcEventQueue();
         // We must have just got suspended by a PC event
         if (_status == Idle) {
             tryCompleteDrain();
@@ -521,7 +584,7 @@ AtomicSimpleCPU::tick()
         if (needToFetch) {
             ifetch_req.taskId(taskId());
             setupFetchRequest(&ifetch_req);
-            fault = thread->itb->translateAtomic(&ifetch_req, tc,
+            fault = thread->itb->translateAtomic(&ifetch_req, thread->getTC(),
                                                  BaseTLB::Execute);
         }
 
@@ -536,7 +599,7 @@ AtomicSimpleCPU::tick()
                 // like the I cache. It should be flushed, and when that works
                 // this code should be uncommented.
                 //Fetch more instruction memory if necessary
-                //if(decoder.needMoreBytes())
+                //if (decoder.needMoreBytes())
                 //{
                     icache_access = true;
                     Packet ifetch_pkt = Packet(&ifetch_req, MemCmd::ReadReq);
@@ -556,22 +619,27 @@ AtomicSimpleCPU::tick()
 
             preExecute();
 
+            Tick stall_ticks = 0;
             if (curStaticInst) {
-                fault = curStaticInst->execute(this, traceData);
+                fault = curStaticInst->execute(&t_info, traceData);
 
                 // keep an instruction count
                 if (fault == NoFault) {
                     countInst();
-                    if (!curStaticInst->isMicroop() ||
-                         curStaticInst->isLastMicroop()) {
-                        ppCommit->notify(std::make_pair(thread, curStaticInst));
-                    }
+                    ppCommit->notify(std::make_pair(thread, curStaticInst));
                 }
                 else if (traceData && !DTRACE(ExecFaulting)) {
                     delete traceData;
                     traceData = NULL;
                 }
 
+                if (dynamic_pointer_cast<SyscallRetryFault>(fault)) {
+                    // Retry execution of system calls after a delay.
+                    // Prevents immediate re-execution since conditions which
+                    // caused the retry are unlikely to change every tick.
+                    stall_ticks += clockEdge(syscallRetryLatency) - curTick();
+                }
+
                 postExecute();
             }
 
@@ -580,7 +648,6 @@ AtomicSimpleCPU::tick()
                         curStaticInst->isFirstMicroop()))
                 instCnt++;
 
-            Tick stall_ticks = 0;
             if (simulate_inst_stalls && icache_access)
                 stall_ticks += icache_latency;
 
@@ -596,7 +663,7 @@ AtomicSimpleCPU::tick()
             }
 
         }
-        if(fault != NoFault || !stayAtPC)
+        if (fault != NoFault || !t_info.stayAtPC)
             advancePC(fault);
     }
 
@@ -608,12 +675,14 @@ AtomicSimpleCPU::tick()
         latency = clockPeriod();
 
     if (_status != Idle)
-        schedule(tickEvent, curTick() + latency);
+        reschedule(tickEvent, curTick() + latency, true);
 }
 
 void
 AtomicSimpleCPU::regProbePoints()
 {
+    BaseCPU::regProbePoints();
+
     ppCommit = new ProbePointArg<pair<SimpleThread*, const StaticInstPtr>>
                                 (getProbeManager(), "Commit");
 }
@@ -631,8 +700,5 @@ AtomicSimpleCPU::printAddr(Addr a)
 AtomicSimpleCPU *
 AtomicSimpleCPUParams::create()
 {
-    numThreads = 1;
-    if (!FullSystem && workload.size() != 1)
-        panic("only one workload allowed");
     return new AtomicSimpleCPU(this);
 }