LAB/GEM5

GEM5 Tutorial 2 - Cache를 추가하여 실행

RyoTTa 2021. 3. 3. 21:23
반응형

Adding cache to the configuration script

이전 Simple.py 스크립트에 Cache를 추가하여 약간 더 복잡한 구성을 정의하겠습니다.

 

아래 그림과 같이 System에 Cache 계층 구조를 추가 합니다.

L1, L2 캐시 추가

 단일 CPU 시스템을 모델링하고 캐시 일관성 모델링을 신경 쓰지 않기 때문에 Ruby 대신 Classic 캐시를 사용합니다.

 Cache SimObject를 확장하고 시스템에 맞게 구성하겠습니다.

 

 Classic 캐시 : M5 시뮬레이터로부터 상속, Ruby에 비해 캐시 일관성을 자세하게 모델링 하지 않음, 단순하고 유연하지 않은 MOESI 프로토콜을 구현한다.

 Ruby 캐시 : GEMS 시뮬레이터로부터 상속, 캐시 일관성을 자세하게 모델링하도록 설계되어 있다. 일부는 캐시 일관성 프로토콜을 정의하는 언어 SLICC 이다. 

 

 Cache SimObject 은 src/mem/cache/Cache.py에서 선언된다. 해당 파일은 SimObject를 설정할 수 있는 매개 변수를 정의하고 있다. 

 

 BaseCache 클래스 내에는 여러가지 Parameter가 존재한다. 해당 코드를 보면 tag_latency = Pram.Cycles("Tag lookup latency")와 같이 Parameter에 대해 정보가 적혀있다.

 

 이러한 Parameter 대부분은 Default값이 존재하지 않으므로 설정해야 한다. m5.instantiate().

 

 특정한 Parameter와 Cache를 구현하기 위해, 먼저 새 파일을 만들려고 한다.

 Tutorial - 1에서 분석한 simple.py와 같은 디렉토리에, cache.py를 정의한다. 

 

from m5.objects import Cache

 첫 단계로 확장할 SimObject를 가져 오도록한다.

 다음으로 BaseCache 객체를 다른 Python Class와 마찬가지로 처리하고 확장 할 수 있다. 여기서 새 캐시의 이름을 원하는대로 지정 가능하며 L1 Cache부터 정의 하겠다.

class L1Cache(Cache):

    assoc = 2
    tag_latency = 2
    data_latency = 2
    response_latency = 2
    mshrs = 4
    tgts_per_mshr = 20

 Default 값이 없는 BaseCache의 일부 Parameter를 설정한다. 가능한 모든 구성 옵션을 확인하고 필수 및 선택사항을 찾으려면 SimObject의 코드를 직접 확인해야 한다. 

class L1ICache(L1Cache):
    size = '16kB'

class L1DCache(L1Cache):
    size = '64kB'

 다음으로 L1Cache의 하위 클래스인 L1ICache, L1DCache를 정의한다.

class L2Cache(Cache):

    size = '256kB'
    assoc = 8
    tag_latency = 20
    data_latency = 20
    response_latency = 20
    mshrs = 20
    tgts_per_mshr = 12

 다음으로 L2Cache에 필요한 Parameter를 추가하여 L2 Cache를 정의한다.

def connectCPU(self, cpu):
    # need to define this in a base class!
    raise NotImplementedError

def connectBus(self, bus):
    self.mem_side = bus.slave

 모든 Parameter들을 지정 했으므로 BaseCache 하위 클래스를 인스턴스화 하고 캐시를 상호 연결하기만 하면 된다. 그러나 많은 객체들을 복잡한 상호 연결하려면 Config 파일이 커지고 읽을 수 없게 된다, 따라서 먼저 Python Class 로 할 수 있는 모든 작업을 수행한다.

 L1 Cache를 CPU와 연결하기위한 connectCPU와 L1 Cache를 Memory-side Bus에 연결하기위한 connectBus()를 코드에 추가한다.

class L1ICache(L1Cache):
    size = '16kB'

    def connectCPU(self, cpu):
        self.cpu_side = cpu.icache_port

class L1DCache(L1Cache):
    size = '64kB'

    def connectCPU(self, cpu):
        self.cpu_side = cpu.dcache_port

 다음으로 L1 Cache는 I-Cache와 D-Cache가 있으므로 (Port의 이름이 다름) 명령 및 데이터에 대해 별도의 함수를 정의한다.

def connectCPUSideBus(self, bus):
    self.cpu_side = bus.master

def connectMemSideBus(self, bus):
    self.mem_side = bus.slave

 마지막으로 L2Cache에 각각 메모리측 버스, CPU측 버스에 연결하는 기능을 추가한다.

 

 

 이후 simple.py에 위에서 정의한 Cache를 추가하도록 한다.

from caches import *

 cache.py 파일에서 네임스페이스로 가져와야 한다. 

system.cpu.icache = L1ICache(opts)
system.cpu.dcache = L1DCache(opts)

 CPU를 생성한 후 우리가 만든 L1ICache, L1DCache를 생성한다.

system.cpu.icache.connectCPU(system.cpu)
system.cpu.dcache.connectCPU(system.cpu)

 그리고 system.cpu 로 L1ICache, L1DCache 를 connectCPU()를 통해 Port를 연결한다.

#system.cpu.icache_port = system.membus.slave
#system.cpu.dcache_port = system.membus.slave

 따라서 simple.py에서 위의 코드를 제거 해야하다.

system.l2bus = L2XBar()

system.cpu.icache.connectBus(system.l2bus)
system.cpu.dcache.connectBus(system.l2bus)

 L2Cache는 단일 Port만 연결할 것이기 때문에(Inst, Data 구분 x) L1Cache를 L2Cache를 직접 연결 할 수 없다. 따라서 L2 Bus를 정의 해야 한다. 

system.l2cache = L2Cache(opts)
system.l2cache.connectCPUSideBus(system.l2bus)

system.l2cache.connectMemSideBus(system.membus)

마지막으로 L2Cache를 정의하여 L2 Bus와 Memory Bus에 연결 할 수있다.

 

./build/X86/gem5.opt configs/learning_gem5/part1/two_level.py

위의 명령어로 실행할수 있다.

gem5 Simulator System.  http://gem5.org
gem5 is copyrighted software; use the --copyright option for details.

gem5 version 20.1.0.4
gem5 compiled Mar  2 2021 15:29:34
gem5 started Mar  3 2021 21:17:48
gem5 executing on compasslab2, pid 78545
command line: ./build/X86/gem5.opt configs/learning_gem5/part1/two_level.py

Global frequency set at 1000000000000 ticks per second
warn: DRAM device capacity (8192 Mbytes) does not match the address range assigned (512 Mbytes)
0: system.remote_gdb: listening for remote gdb on port 7000
Beginning simulation!
info: Entering event queue @ 0.  Starting simulation...
Hello world!
Exiting @ tick 58236000 because exiting with last active thread context

정상적으로 실행하면 위와같은 결과를 볼 수 있다.

config.dot.svg

cache.py

from __future__ import print_function
from __future__ import absolute_import

import m5
from m5.objects import Cache

# Add the common scripts to our path
m5.util.addToPath('../../')

from common import SimpleOpts

# Some specific options for caches
# For all options see src/mem/cache/BaseCache.py

class L1Cache(Cache):
    """Simple L1 Cache with default values"""

    assoc = 2
    tag_latency = 2
    data_latency = 2
    response_latency = 2
    mshrs = 4
    tgts_per_mshr = 20

    def __init__(self, options=None):
        super(L1Cache, self).__init__()
        pass

    def connectBus(self, bus):
        """Connect this cache to a memory-side bus"""
        self.mem_side = bus.slave

    def connectCPU(self, cpu):
        """Connect this cache's port to a CPU-side port
           This must be defined in a subclass"""
        raise NotImplementedError

class L1ICache(L1Cache):
    """Simple L1 instruction cache with default values"""

    # Set the default size
    size = '16kB'

    SimpleOpts.add_option('--l1i_size',
                          help="L1 instruction cache size. Default: %s" % size)

    def __init__(self, opts=None):
        super(L1ICache, self).__init__(opts)
        if not opts or not opts.l1i_size:
            return
        self.size = opts.l1i_size

    def connectCPU(self, cpu):
        """Connect this cache's port to a CPU icache port"""
        self.cpu_side = cpu.icache_port

class L1DCache(L1Cache):
    """Simple L1 data cache with default values"""

    # Set the default size
    size = '64kB'

    SimpleOpts.add_option('--l1d_size',
                          help="L1 data cache size. Default: %s" % size)

    def __init__(self, opts=None):
        super(L1DCache, self).__init__(opts)
        if not opts or not opts.l1d_size:
            return
        self.size = opts.l1d_size

    def connectCPU(self, cpu):
        """Connect this cache's port to a CPU dcache port"""
        self.cpu_side = cpu.dcache_port

class L2Cache(Cache):
    """Simple L2 Cache with default values"""

    # Default parameters
    size = '256kB'
    assoc = 8
    tag_latency = 20
    data_latency = 20
    response_latency = 20
    mshrs = 20
    tgts_per_mshr = 12

    SimpleOpts.add_option('--l2_size', help="L2 cache size. Default: %s" % size)

    def __init__(self, opts=None):
        super(L2Cache, self).__init__()
        if not opts or not opts.l2_size:
            return
        self.size = opts.l2_size

    def connectCPUSideBus(self, bus):
        self.cpu_side = bus.master

    def connectMemSideBus(self, bus):
        self.mem_side = bus.slave

 

two_level.py

from __future__ import print_function
from __future__ import absolute_import

# import the m5 (gem5) library created when gem5 is built
import m5
# import all of the SimObjects
from m5.objects import *

# Add the common scripts to our path
m5.util.addToPath('../../')

# import the caches which we made
from caches import *

# import the SimpleOpts module
from common import SimpleOpts

# Set the usage message to display
SimpleOpts.set_usage("usage: %prog [options] <binary to execute>")

# Finalize the arguments and grab the opts so we can pass it on to our objects
(opts, args) = SimpleOpts.parse_args()

# get ISA for the default binary to run. This is mostly for simple testing
isa = str(m5.defines.buildEnv['TARGET_ISA']).lower()

# Default to running 'hello', use the compiled ISA to find the binary
# grab the specific path to the binary
thispath = os.path.dirname(os.path.realpath(__file__))
binary = os.path.join(thispath, '../../../',
                      'tests/test-progs/hello/bin/', isa, 'linux/hello')

# Check if there was a binary passed in via the command line and error if
# there are too many arguments
if len(args) == 1:
    binary = args[0]
elif len(args) > 1:
    SimpleOpts.print_help()
    m5.fatal("Expected a binary to execute as positional argument")

# create the system we are going to simulate
system = System()

# Set the clock fequency of the system (and all of its children)
system.clk_domain = SrcClockDomain()
system.clk_domain.clock = '1GHz'
system.clk_domain.voltage_domain = VoltageDomain()

# Set up the system
system.mem_mode = 'timing'               # Use timing accesses
system.mem_ranges = [AddrRange('512MB')] # Create an address range

# Create a simple CPU
system.cpu = TimingSimpleCPU()

# Create an L1 instruction and data cache
system.cpu.icache = L1ICache(opts)
system.cpu.dcache = L1DCache(opts)

# Connect the instruction and data caches to the CPU
system.cpu.icache.connectCPU(system.cpu)
system.cpu.dcache.connectCPU(system.cpu)

# Create a memory bus, a coherent crossbar, in this case
system.l2bus = L2XBar()

# Hook the CPU ports up to the l2bus
system.cpu.icache.connectBus(system.l2bus)
system.cpu.dcache.connectBus(system.l2bus)

# Create an L2 cache and connect it to the l2bus
system.l2cache = L2Cache(opts)
system.l2cache.connectCPUSideBus(system.l2bus)

# Create a memory bus
system.membus = SystemXBar()

# Connect the L2 cache to the membus
system.l2cache.connectMemSideBus(system.membus)

# create the interrupt controller for the CPU
system.cpu.createInterruptController()

# For x86 only, make sure the interrupts are connected to the memory
# Note: these are directly connected to the memory bus and are not cached
if m5.defines.buildEnv['TARGET_ISA'] == "x86":
    system.cpu.interrupts[0].pio = system.membus.master
    system.cpu.interrupts[0].int_master = system.membus.slave
    system.cpu.interrupts[0].int_slave = system.membus.master

# Connect the system up to the membus
system.system_port = system.membus.slave

# Create a DDR3 memory controller
system.mem_ctrl = MemCtrl()
system.mem_ctrl.dram = DDR3_1600_8x8()
system.mem_ctrl.dram.range = system.mem_ranges[0]
system.mem_ctrl.port = system.membus.master

# Create a process for a simple "Hello World" application
process = Process()
# Set the command
# cmd is a list which begins with the executable (like argv)
process.cmd = [binary]
# Set the cpu to use the process as its workload and create thread contexts
system.cpu.workload = process
system.cpu.createThreads()

# set up the root SimObject and start the simulation
root = Root(full_system = False, system = system)
# instantiate all of the objects we've created above
m5.instantiate()

print("Beginning simulation!")
exit_event = m5.simulate()
print('Exiting @ tick %i because %s' % (m5.curTick(), exit_event.getCause()))
반응형