Skip to main content

Modifying the Processor

This is the easy part, we simply connect everything up all nice and proper.

Setup

Now we can simply import our other file and use our Cache objects in the new processor. Modifying our imports from the first tutorial, we get:

import m5
import os
import sys

# import all of the SimObjects
from m5.objects import *
from m5.util import fatal

from caches import *

Now we can use this to create some cache objects:

system.cpu = RiscvTimingSimpleCPU()

system.cpu.icache = L1ICache()
system.cpu.dcache = L1DCache()
system.l2cache = L2Cache()
note

Note that the L2 Cache is not necessarily a part of the CPU die, you can go down quite a rabbit hole as to the reason for this. Often in modern processors, the cache is within the core boundary. There are an increasing number of processors that place an additional cache outside of the chip boundary, as you can see in the image below.

Image of a CPU die with L3 cache outside the core boundary, but L1 and L2 cache within

You may want to consult some example specifications. On the AMD EPYC and Intel Saphire Rapids, L3 cache will be arranged similarly to the above diagram. Interestingly, Intel experimented with something resembling an L4 cache in their Skylake, Haswell and Broadwell architectures called eDRAM Based Cache intended to give their integrated GPU's a leg up for memory access. Intel has also filed a patent application for L4 cache with the US patent office.

Connecting to the CPU

For this step, we can leverage the helper functions we made in the cache class:

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

Next, we need to connect the L1 caches to the L2 cache. To do this, we can create an L2 crossbar between the L1 caches and the L2 cache.

system.l2bus = L2XBar()

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

Finally, we can connect the L2 cache to main memory using a system crossbar.

system.l2cache.connectCPUSideBus(system.l2bus)
system.membus = SystemXBar()
system.l2cache.connectMemSideBus(system.membus)

And now you have cache.

The Code

simple_cache.py
import m5
import os
import sys

# import all of the SimObjects
from m5.objects import *
from m5.util import fatal

from caches import *

#### CONSTANTS ####
try:
os.environ['GEM_TESTS']
os.environ['GEM_PATH']
os.environ['GEM_CONFIGS']
except e:
fatal("This script requires the GEM_TESTS, GEM_PATH and GEM_CONFIGS environment variables")

# Add the common scripts to our path
# m5.util.addToPath(os.environ['GEM_CONFIGS'])
sys.path.append(os.environ['GEM_CONFIGS'])

# 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__))
default_binary = os.path.join(
os.environ['GEM_TESTS'],
"test-progs/hello/bin/riscv/linux/hello",
)

# Creaing a simple system
system = System() # YAAAAAAAAAAAY

# We can now reference the system and change some parameters
system.clk_domain = SrcClockDomain()
system.clk_domain.clock = '1GHz'
system.clk_domain.voltage_domain = VoltageDomain()

# now we set the memory
system.mem_mode = 'timing'
system.mem_ranges = [AddrRange('512MB')]

# The type of CPU
system.cpu = RiscvTimingSimpleCPU()

# NOTE: add the caches
system.cpu.icache = L1ICache()
system.cpu.dcache = L1DCache()

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

# NOTE: we remove these
# # Cache ports!
# system.cpu.icache_port = system.membus.cpu_side_ports
# system.cpu.dcache_port = system.membus.cpu_side_ports

system.l2bus = L2XBar() # note that this is built in, but you
# can try to make your own too!

# Connect all of the caches to the l2
system.cpu.icache.connectBus(system.l2bus)
system.cpu.dcache.connectBus(system.l2bus)

# Make the L2
system.l2cache = L2Cache()
system.l2cache.connectCPUSideBus(system.l2bus)

# The system bus to main memory
system.membus = SystemXBar()

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

# Setup an interrupt controller
system.cpu.createInterruptController()
system.system_port = system.membus.cpu_side_ports
# NOTE: RISCV does not require the PIO setup they have in the tutorial

# MEMORYYYYY
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.mem_side_ports

# Set up our binary
binary = default_binary

# for gem5 V21 and beyond
system.workload = SEWorkload.init_compatible(binary)

process = Process()
process.cmd = [binary]
system.cpu.workload = process
system.cpu.createThreads()

# Instantiate the system with gem5
root = Root(full_system = False, system = system)
m5.instantiate()

print("Beginning simulation!")
exit_event = m5.simulate()

# And once simulation finishes, we can inspect the state of the system.
print('Exiting @ tick {} because {}'
.format(m5.curTick(), exit_event.getCause()))