LAB/GEM5

GEM5 Tutorial 1 - 가장 간단한 시스템 정의

RyoTTa 2021. 3. 3. 14:59
반응형

Creating a simple configuration script

gem5 architecture

 gem5는 "SimObjects"로 구성되어 있다.

 대부분의 C++ 객체들은 SimObject로 부터 상속받는다.

 각 클래스들은 물리적인 시스템 구성 요소를 나타낸다.

 

 gem5는 discrete event simulator(개별 이벤트 시뮬레이터)

 1. Event at head dequeued
 2. Event executed
 3. More events queued

 

 모든 SimObject는 Event들을 EventQueue에 삽입할 수 있다.

Event Queue 모식도

 

 

gem5 configuration scripts

 

 gem5는 Python Scripts로 완전히 Control 되며 Model에서의 System을 정의한다.

 http://learning.gem5.org/book/_downloads/simple.py

 위의 Simple Script에서는 Single CPU 가 Memory Bus에 연결된 것을 정의한다.

Simple config script 의 모식도

vim configs/learning_gem5/part1/simple.py

 

 이번에 간단한 시뮬레이터를 정의하는 코드는 해당 위치에 저장되어 있습니다.

from __future__ import print_function
from __future__ import absolute_import

import m5
from m5.objects import *

 m5 라이브러리와 전에 컴파일한 SimObject들을 모두 연결합니다.

system = System()

 시뮬레이션할 시스템의 첫번째 SimObject를 생성합니다. System 객체는 시뮬레이션 시스템의 모든 다른 객체들의 Parent가 됩니다.

 System 객체는 Physical memory ranges, root clock domain, root voltage domain, kernel(in full-system simulation) 등의 funtional information등을 포함하고 있습니다.(timing-level은 포함하지 않음)

 

system.clk_domain = SrcClockDomain() 
system.clk_domain.clock = '1GHz' 
system.clk_domain.voltage_domain = VoltageDomain()

 이제 시뮬레이션할 시스템에 대한 Reference가 있으므로 System에 Clock을 설정합니다.

 Clock domain을 생성하고 SimObject에 Parameter를 제공해 설정합니다. 

 마지막으로 Voltage domain을 생성하고 이번 simple.py에서는 전압에 대해 설정하지 않으므로 default options만 설정합니다.

system.mem_mode = 'timing' 
system.mem_ranges = [AddrRange('512MB')]

 System에 존재하는 메모리를 시뮬레이션하는 방법을 설정해 보겠습니다. 

 메모리 시뮬레이션을 위해 timing mode 를 사용합니다. (fast-forward, restroing from checkpoint와 같은 특수 상황을 제외한 대부분의 상황에서 timing mode를 사용합니다.)

 single memory 의 용량을 512MB가지는 small system 입니다. 

system.cpu = TimingSimpleCPU()

 이제 CPU를 구성하는 단계입니다. gem5에서 지원하는 가장 간단하고 timing을 지원하는 TimingSimpleCPU 부터 시작하겠습니다. 이 CPU 모델은 메모리 시스템을 통해 전달되는 메모리 요청을 제외하고 실행하기 위해 단일 클럭 사이클에서 각 명령어를 실행합니다. 

 이러한 CPU를 생성하려면 간단히 객체를 인스턴스화 하면됩니다.

system.membus = SystemXBar()

 다음으로 System 전반의 Memory Bus를 생성합니다. 

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

 Memory Bus가 존재하므로 CPU의 Cache Port를 해당 Bus에 연결하겠습니다. simple.py 경우 System에 캐시가 없으므로 I-Cache, D-Cache Port를 Memory Bus에 직접 연결합니다. (Cache가 존재하지 않기 때문에)

system.cpu.createInterruptController()
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

 다음으로 System이 올바르게 작동하는지 확인하기 위해 몇 개의 다른 Port를 연결해야 합니다.

 CPU에 I/O Controller를 생성하고 이를 Memory Bus에 연결해야 합니다. 

 PIO 및 Interrupt Port를 Memory Bus에 연결하는 것은 x86 관련 요구사항이며 다른 ISA(ex ARM) 에는 3개의 추가 라인피 필요하지 않습니다.

system.mem_ctrl = MemCtrl()
system.mem_ctrl = DDR3_1600_8x8()
system.mem_ctrl.range = system.mem_ranges[0]
system.mem_ctrl.port = system.membus.master

 다음으로 Memory Controller를 생성하고 Memory Bus에 연결합니다. 

 이 System에는 간단한 DDR3 Controller를 사용하고 모든 Memory Range를 System에 할당합니다.

system.system_port = system.membus.slave

 또한 System의 특수 Port를 Memory Bus 까지 연결해야 합니다. 해당 Port는 System이 Memory를 Read/Write할 수 있도록 하는 Functional-only Port 입니다.

 

 이제 위 "Simple config script 의 모식도"그림처럼 캐시가 없는 단순한 시스템 구성을 마쳤습니다. 

isa = str(m5.defines.buildEnv['TARGET_ISA']).lower()

thispath = os.path.dirname(os.path.realpath(__file__))
binary = os.path.join(thispath, '../../../',
                      'tests/test-progs/hello/bin/', isa, 'linux/hello')
                      
process = Process()
process.cmd = [binary]
system.cpu.workload = process
system.cpu.createThreads()

 다음으로 CPU가 실행할 Process를 설정해야 합니다. Syscall Emulation Mode(SE)를 실행하고 있으므로 컴파일 된 실행파일이 필요로 합니다. 간단한 "Hello World" 프로그램을 실행해보도록 하겠습니다. gem5 디렉토리 내부에 프로그램을 제공합니다. 

 빌드된 ISA를 참조하여 빌드에 맞는 미리 컴파일된 Binary Program을 선택하도록 합니다. 그다음 Process(다른 SimObject)를 만들어야 합니다. 다음으로 해당 Process를 실행하려는 Command를 설정합니다. 이는 argc와 유사하게 첫번째 위치에는 실행 파일을 작성하고, 나머지 목록에는 Parameter에 대해 작성하면 됩니다. 그런다음 Process를 Workload로 사용하도록 CPU를 설정하고 마지막으로 CPU에서 Funtional Execution Contexts를 생성합니다.

root = Root(full_system = False, system = system)
m5.instantiate()

 마지막으로 System을 인스턴스화 하고 실행합니다.

 먼저 root 객체를 생성하고 시뮬레이션을 인스턴스화 합니다. 인스턴스화 프로세스는 Python으로 작성된 모든 SimObject를 거치고 C++으로 생성합니다.

 참고로 Python Class를 인스턴스화 한 다음 매개변수를 멤버변수로 명시적으로 지정할 필요가 없습니다. root 객체와 같이 매개 변수를 명명 된 인수로 전달할 수도 있습니다.

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

 마지막으로 실제 시뮬레이션을 시작할 수 있습니다.~!!!

 이제 gem5는 Python3 스타일의 print 함수를 사용하므로 함수를 호출해야 합니다. 

 시뮬레이션이 완료되면 시스템 상태를 검사 할 수 있습니다.

 

./build/X86/gem5.opt configs/learning_gem5/part1/simple.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

전체 코드

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 *

# 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 a memory bus, a system crossbar, in this case
system.membus = SystemXBar()

# Hook the CPU ports up to the membus
system.cpu.icache_port = system.membus.slave
system.cpu.dcache_port = system.membus.slave

# create the interrupt controller for the CPU and connect to the membus
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

# Create a DDR3 memory controller and connect it to the membus
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

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

# get ISA for the binary to run.
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')

# 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()))
반응형