#!/usr/bin/env python

# SMP.py

# SimPy example:  Symmetric multiprocessor system.  Have m processors
# and m memory modules on a single shared bus.  When a processor
# generates a memory request, it must first queue for possession of the
# bus.  Then it takes 1.0 amount of time to reach the proper memory
# module.  The request is queued at the memory module, and when finally
# served, the service takes 0.6 time.  The memory module must then queue
# for the bus.  When it acquires the bus, it sends the response (value
# to be read in the case of a read request, acknowledgement in the case
# of a write) along the bus, together with the processor number.  The
# processor which originally made the request has been watching the bus,
# and thus is able to pick up the response.

# When a memory module finishes a read or write operation, it will not
# start any other operations until it finishes sending the result of the
# operation along the bus.

# For any given processor, the time between the completion of a previous
# memory request and the generation of a new request has an exponential
# distribution with mean 3.0.  The specific memory module requested is
# assumed to be chosen at random (i.e. uniform distribution) from the m
# modules.  While a processor has a request pending, it does not
# generate any new ones.

# The processors are assumed to act independently of each other, and the
# requests for a given processor are assumed independent through time.
# Of course, more complex assumptions could be modeled.

from __future__ import generators  # delete if use Python >= 2.3 
from SimPy.Simulation import *
from random import Random,expovariate,uniform

import sys

class Processor(Process):
   M = int(sys.argv[1])  # number of CPUs/memory modules
   InterMemReqRate = 1.0/float(sys.argv[2])  
   Debug = int(sys.argv[3])  # 1 if debug, 0 otherwise
   NDone = 0  # number of memory requests copleted so far
   TotWait = 0.0  # total wait for those requests
   WaitMem = 0
   NextID = 0  
   def __init__(self):
      Process.__init__(self)  
      self.ID = Processor.NextID
      Processor.NextID += 1
   def Run(self):
      while 1:
         yield hold,self,expovariate(Processor.InterMemReqRate)
         if Processor.Debug:
            print now(), "CPU", self.ID, "starts wait"
         self.StartWait = now()  # start of wait for mem request
         # acquire bus
         if Processor.Debug:
            print now(), "CPU", self.ID, "tries to get bus"
         yield request,self,G.Bus
         if Processor.Debug:
            print now(), "CPU", self.ID, "gets bus"
         # use bus
         yield hold,self,1.0
         # relinquish bus
         yield release,self,G.Bus
         self.Module = G.Rnd.randrange(0,Processor.M)
         if Processor.Debug:
            print now(), "CPU", self.ID, "releases bus, tries module", \
               self.Module 
         # go to memory
         self.StartMemQ = now()
         yield request,self,G.Mem[self.Module] 
         if Processor.Debug:
            print now(), "CPU", self.ID, "starts op at module", \
               self.Module 
         if now() > self.StartMemQ:
            Processor.WaitMem += 1
         # simulate memory operation
         yield hold,self,0.6
         if Processor.Debug:
            print now(),"module",self.Module,"done for CPU",self.ID,"tries bus"
         # memory sends result back to requesting CPU
         yield request,self,G.Bus
         if Processor.Debug:
            print now(),"module",self.Module,"gets bus for CPU",self.ID
         yield hold,self,1.0
         # done
         yield release,self,G.Bus
         yield release,self,G.Mem[self.Module] 
         if Processor.Debug:
            print now(), "CPU", self.ID, "request done, module",self.Module,\
               "releases bus"
         Processor.NDone += 1 
         Processor.TotWait += now() - self.StartWait   

# globals
class G:
   Rnd = Random(12345)
   Bus = Resource(1)
   CPU = []  # array of processors
   Mem = []  # array of memory modules

# "main" program starts here

def main():
   initialize()  
   for I in range(Processor.M):
      G.CPU.append(Processor())
      activate(G.CPU[I],G.CPU[I].Run(),delay=0.0)
      G.Mem.append(Resource(1)) 
   MaxSimtime = 10000.0
   simulate(until=MaxSimtime)
   print "mean residence time", Processor.TotWait/Processor.NDone
   print "prop. wait for mem", float(Processor.WaitMem)/Processor.NDone

if __name__ == '__main__':
   main()
