本次课程的目的是介绍 Python 编程及其在 gem5 中的应用。
核心概念:gem5 解释 Python
gem5 模拟器可以被视为一个 C++ 程序,它解释执行定义模拟的 Python 脚本。简单来说,gem5 就是一个包含 gem5 Python 库的 Python 解释器。
警告:这是一个简化的说法。 一些模拟配置信息实际上存在于 C++ 代码中。 但是,这个简单的概念可以作为一个有用的思维模型。
Python 脚本负责 导入 (import) 模拟组件(SimObjects),并使用 Python 配置脚本来指定它们的配置以及与其他 SimObjects 的连接关系。
python
from m5.objects import CPU, L1Cache
cpu = CPU() # 创建 CPU SimObject
cpu.clock = '1GHz' # 设置它的参数
cpu.l1_cache = L1Cache(size="64kB") # 将其连接到其他 SimObjects
# ... 更多配置 ...
CPU 和 L1Cache 并非真实的 SimObjects 名称,这只是一个展示如何使用 Python 配置脚本的示例。
使用 gem5 运行脚本
以下是如何使用 Python 配置脚本运行 gem5 模拟的示例。 gem5 二进制文件用于运行模拟,并具有可配置模拟的参数(如 --outdir)。 Python 配置脚本被传递给 gem5。 Python 配置脚本之后的所有参数都会传递给该 Python 脚本。
gem5 --outdir=mydir my-simulation.py --num-cores 4
语法格式为:gem5 {gem5的参数} {gem5 python配置脚本} {脚本的参数}。
虽然 gem5 配置脚本主要是 Python,但它也有一些特殊的特性和限制。 我们将在本次课程中涵盖这些内容。 最重要的区别在于,gem5 二进制文件为脚本提供了 m5 模块,该模块提供了配置脚本与 gem5 模拟器之间的接口。
一些 Python 基础回顾
练习:gem5 中的字面意义上的 "Hello world"
对于所有的编码示例,我们将处于 materials 目录下。
Bash
cd /workspaces/2024/materials/01-Introduction/03-python-background
- 创建一个名为 "mysim.py" 的文件并添加以下内容:
Python
print("hello from the config script")
使用 gem5 执行该脚本:
Shell
gem5 mysim.py
Python 文件名请以 ".py" 结尾。 Python 无法导入名称中带有连字符 (
-) 的文件。对于模块请使用下划线 (_)。 对于要运行的脚本(你不希望别人导入的),可以使用连字符 (-)。
Python 入门:基本数据类型 (Primitives)
在最底层,Python 有 4 种原始数据类型。 所有其他数据类型都建立在这些之上。
-
整数 (Integer,
int) -
浮点数 (Floating point number,
float) -
字符串 (String,
str) -
布尔值 (Boolean,
bool)
这些是所有 Python 程序的基本构建块,可以通过各种方式进行设置和运算。
基本类型:整数 (Integers)
可疑链接已删除\] 可以作为基本整数用法的参考。 本教程将涵盖基础知识。 #### 声明整数 Python x = 1 y = 2 *** ** * ** *** ### 基本整数运算 Python a = x + y b = x - y c = x * y d = x / y # 使用 f-strings 打印值。 print(f"a: {a}, b: {b}, c: {c}, d: {d}") **关于 f-strings** :f-strings 是 Python 中格式化字符串的一种方式。 它们通过字符串前的 `f` 定义,并允许你通过将变量包裹在花括号 `{}` 中来将变量插入字符串。 我们这里稍微超前了一点,但它们非常有用,我们建议在代码中使用它们来输出变量。 *** ** * ** *** ### 基本类型:浮点数 (Floats) #### 声明浮点数 浮点数是 Python 中的一种原始数据类型。它们是"实数",声明方式如下。这里我们声明一个变量 `x` 并将其赋值为字面值 `1.5`。 Python x = 1.5 *** ** * ** *** ### 基本浮点数运算 Python # 像整数一样,浮点数可以通过算术运算来设置。 # 将变量 `y` 设置为 `10.5 + 5.5`。 y = 10.5 + 5.5 # 将变量 `z` 设置为 `y - x` (在此例中为 16 - 1.5)。 z = y - x print(f"Value of z: {z}") > 在 Python 3(gem5 使用的版本)中,两个整数相除将返回一个浮点数。 *** ** * ** *** Python # 乘法 multi_xy = x * y print(f"Value of multi_xy: {multi_xy}") # 除法 div_xy = y / x print(f"Value of div_xy: {div_xy}") *** ** * ** *** ### 基本类型:字符串 (Strings) 可以作为基本字符串用法的参考。 字符串是 Python 中的一种原始数据类型。它们是字符序列,声明方式如下。这里我们声明一个变量 `x` 并赋值为字面值 `"Hello World!"`。 Python x = "Hello World!" print(x) 两个字符串的连接 (Concatenation) 注意这里使用了字面字符串 ("GoodBye!") 和变量 `x`。 Python y = x + " GoodBye!" print(y) *** ** * ** *** ### 打印字符串 我们使用 "f-string" 语法将字符串的值插入到其他字符串中。花括号之间的内容会被作为 Python 代码求值。 在下面,我们将 x 与 " GoodBye " 以及 x + y 的值 ("Hello World! GoodBye!") 连接起来。z 将被设置为 "Hello World! GoodBye Hello World! Goodbye!"。 Python z = f"{x} GoodBye {x + y}" print(z) *** ** * ** *** ### 基本类型:布尔值 (Booleans) 可以作为基本布尔值用法的参考。 布尔值是 Python 中的一种原始数据类型。它们是 "True"(真)或 "False"(假),声明方式如下。这里我们声明一个变量 `x` 并赋值为字面值 `True`。 Python x = True print(f"Value of x: {x}") 布尔值可以通过字面值或其他布尔变量的逻辑运算来设置。这些逻辑运算符是 `is`、`and`、`or` 和 `not`,用于比较值。 Python y = x and True print(f"Value of y: {y}") z = x or False print(f"Value of z: {z}") a = not x print(f"Value of a: {a}") *** ** * ** *** ### 布尔比较 `==`, `!=`, `<`, `>`, `<=`, 和 `>=` 运算符可用于比较其他原始数据类型的值。这些运算的结果是一个布尔值。 Python # 如果 `1 + 1` 等于 `2`,则将 `b` 设为 True。 b = (1 + 1) == 2 print(f"Value of b: {b}") # 如果 `1 + 1` 不等于 `2`,则将 `c` 设为 True。 c = (1 + 1) != 2 print(f"Value of c: {c}") Python # 如果 `1 + 1` 小于 `3`,则将 `d` 设为 True。 d = (1 + 1) < 3 print(f"Value of d: {d}") # 如果 `1 + 1` 大于 `3`,则将 `e` 设为 True。 e = (1 + 1) > 3 print(f"Value of e: {e}") # 如果 `1 + 1` 小于或等于 `2`,则将 `f` 设为 True。 f = (1 + 1) <= 2 # 如果 `1 + 1` 大于或等于 `2`,则将 `g` 设为 True。 g = (1 + 1) >= 2 *** ** * ** *** ### Python 入门:集合 (Collections) Python 有许多内置的集合类型,尽管最常用的是列表 (lists)、字典 (dictionaries) 和集合 (sets)。在所有情况下,它们的作用都是在一个单一的集合变量中存储多个变量。 列表是变量的有序集合。允许重复。 它们使用方括号。 Python a_list = [1, 1, 2] 更多关于列表的内容可以在 \[可疑链接已删除\] 找到。 #### 集合 (Sets) Sets 是变量的无序集合。不允许重复。 它们使用花括号。 Python a_set = {"one", "two", "three", "four", "five"} 更多关于集合的示例可以在 \[可疑链接已删除\] 找到。 *** ** * ** *** ### 字典 (Dictionaries) 字典是键值对 (key-value pairs) 的集合。这实际上是集合 (Sets),其中集合中的每个值('key',键)映射到另一个变量('value',值)。不允许重复的键(但允许重复的值)。 Python a_dict = {1: "one", 2: "two"} 更多关于字典的示例可以在 \[可疑链接已删除\] 找到。 *** ** * ** *** #### Python 集合用法 Python # 过去几张幻灯片中的集合示例 a_list = [1, 1, 2] a_set = {"one", "two", "three", "four", "five"} a_dict = {1: "one", 2: "two"} # 访问列表中的元素 # 每个元素都有一个索引,可用于访问该元素。索引从 0 开始 print(a_list[0]) print(a_list[1]) # 向列表末尾添加一个元素。`a_list` 将变为 `[1, 1, 2, 1]`。 a_list.append(1) # 访问集合 (Set) 中的元素。集合中不使用索引访问元素。 for element in a_set: print(element) *** ** * ** *** ### 更多关于 Python 原始类型和集合的内容 Python 相当独特,因为它自带了许多内置功能。 虽然很有用,但这通常意味着做同一件事有多种方法。 例如,在以下片段中,`dict_1`、`dict_2` 和 `dict_3` 都是等价的。 Python dict_1 = {'key_one': "one", 'key_two': "two"} dict_2 = dict(key_one="one", key_two="two") dict_3 = dict() dict_3['key_one'] = "one" dict_3['key_two'] = "two" 各位 Python 新手可能想今晚花点时间通过示例来学习 Python 的"内置"函数。 *** ** * ** *** ### 列表推导式 (List Comprehensions) 列表推导式是 Python 中创建列表的一种方式,在 gem5 中很常用。 它们允许在一行代码中通过迭代另一个列表并对每个元素应用操作来创建列表。 下面的代码创建了一个从 1 到 20 的偶数列表: Python even_numbers = [x for x in range(1, 21) if x % 2 == 0] 它的非推导式等价代码是: Python even_numbers = [] for x in range(1, 21): if x % 2 == 0: even_numbers.append(x) *** ** * ** *** ### 列表推导式 列表推导式的语法是: Python [expression for item in iterable if condition] # [表达式 for 项目 in 可迭代对象 if 条件] 例如,假设我们想要一个商店中所有价格低于 10 美元的商品列表。 假设 `store` 是商品的集合,每个商品都有一个返回商品价格的函数 `get_price` 和一个返回商品名称的函数 `get_name`。 下面将获取这些商品的列表。 Python item_under_10 = [item.get_name() for item in store if item.get_price() < 10] *** ** * ** *** ### 列表推导式 列表推导式也可以嵌套。 例如,假设我们有一个列表的列表,我们想要将其展平 (flatten)。 Python list_of_lists = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] flattened_list = [item for sublist in list_of_lists for item in sublist] 在上面我们有一个列表的列表。 外层循环迭代列表中的每个子列表。 内层循环迭代子列表中的每个项目。 非推导式的等价代码是: Python flattened_list = [] for sublist in list_of_lists: for item in sublist: flattened_list.append(item) *** ** * ** *** ### Python `if` 语句 Python condition = True and False if condition: print("The condition is True") else: print("The condition is False") 可以作为基本 `if` 用法的参考。 * Python 不使用花括号来定义代码块,而是使用缩进。 * `print` 语句被缩进以表明它们是 `if` 块的一部分(例如,只有当 `condition` 为 `True` 时,"The condition is True" `print` 才会被执行)。 * 确保你的缩进是一致的。如果不一致,Python 会报错。 * 在 gem5 中,我们使用 4 个空格进行缩进。 *** ** * ** *** ### Python `for` 循环 `for` 遍历项目的集合。 Python for value in [1, 2, 3]: print(value) 同样,`print` 语句被缩进以表明它是 `for` 的一部分。 可以作为基本 `for` 用法的参考。 *** ** * ** *** ### Python `while` 循环 `while` 将执行代码块直到条件为 `False`。 Python counter = 0 while counter < 3: print(counter) counter += 1 \[可疑链接已删除\] 可以作为基本 `while` 用法的参考。 > **注意:** `counter += 1` 行是 `counter = counter + 1` 的简写。 这会将 counter 的值设置为当前 counter 值加 1。例如, 如果 counter 是 0,`counter += 1` 会将 `counter` 变量设置为 1。 *** ** * ** *** ### Python 函数 函数使用 `def` 关键字定义。 Python def my_function(arg1, arg2): return arg1 + arg2 result = my_function(1, 2) print(result) # 3 *** ** * ** *** ### Python 函数 以下显式引用参数类型的风格也很常见(类型提示)。 Python def my_function(arg1: int, arg2: int) -> int: return arg1 + arg2 我们强烈建议在函数中使用类型提示。这提高了代码的可读性并有助于捕获错误。 \[可疑链接已删除\] 可以作为基本函数用法的参考。 > 幻灯片中的示例以及许多材料为了节省空间没有使用类型提示。这不是推荐的做法。 *** ** * ** *** ### 导入代码 Python 允许你从其他文件导入代码。 假设我们在名为 `math_funcs.py` 的文件中有函数 `add`、`subtract` 和 `multiply`: Python def add(a: int b: int) -> int: return a + b def subtract(a: int, b: int) -> int: return a - b def multiply(a: int, b: int) -> int: return a * b 我们可以导入这些函数并使用它们: Python from math_funcs import add, subtract, multiply print(add(1,2)) print(subtract(4,2)) print(multiply(3,3)) 如果 `math_funcs.py` 在一个目录中,比如 "math_dir",我们可以使用: Python from math_dir.math_funcs import add, subtract, multiply 一个完整且扩展的示例可以在 \[可疑链接已删除\] 中找到。 *** ** * ** *** ### Python 生成器 (Generators) 生成器是 Python 中创建迭代器的一种方式。它们类似于函数,但不是一次返回所有值,而是一次 `yield`(产出)一个值。 Python def my_generator(): yield 1 yield 2 yield 3 for value in my_generator(): print(value) 语法上的区别在于 `yield` 关键字。这用于从生成器返回一个值。 *** ** * ** *** ### Python 生成器 除了更加节省内存外,生成器对于创建无限序列也非常有用。 Python def infinite_flip_flop() -> Generator[bool]: bool val = True while True: yield val val = not val 上面的生成器将无限地产生 `True`, `False`, `True`, `False`, `True`, `False` 等等。 虽然返回一个列表可能很诱人,但如果你想一次迭代一个值的序列,生成器是最好的选择。 *** ** * ** *** ### gem5 与面向对象设计 (Object Oriented Design) gem5 利用面向对象设计 (OOD) 来模拟计算机系统的组件。这是一种模拟复杂系统的强大方式,也是软件工程中常见的设计模式。简而言之,它是一种将逻辑上属于一起的数据和函数封装在称为"对象"的实体中的方法。 类 (Classes) 允许你在 Python 中创建自己的数据类型。它们是将数据和功能捆绑在一个单元中的一种方式。类是对象的蓝图。它定义了该类的对象实例将拥有的属性和方法。例如,我们可以有一个 `Car` 类,具有 `color`(颜色)、`make`(制造商)、`model`(型号)等属性,以及 `drive`(驾驶)、`stop`(停止)、`park`(停车)等方法。 当我们创建一个 `Car` 类的对象时,我们可以设置该汽车对象的属性,如 `color`、 `make`、`model`,并调用如 `drive`、`stop`、`park` 的方法。 虽然 `Car` 类的每个对象都有相同的属性和方法,但每个对象的属性值可以不同。 *** ** * ** *** ### Python 中的基本面向对象设计 让我们创建一个简单的类并实例化一些对象。 Python class Animal: def __init__(self, name, age): self.name = name self.age = age def eat(self, food): print(f"{self.name} is eating {food}") def sleep(self): print(f"{self.name} is sleeping") *** ** * ** *** ### Python 中的基本面向对象设计 `__init__` 方法是一个特殊的方法,当对象被创建时调用。 它用于初始化对象的属性。 现在我们可以从这个类创建对象。 Python dog = Animal("Dog", 5) cat = Animal("Cat", 6) 我们可以像这样访问属性: Python print(f"Name of animal: {dog.name}") print(f"Age of animal: {dog.age}") 并像这样调用它的方法: Python dog.eat("meat") dog.sleep() *** ** * ** *** ### Python 中的基本面向对象设计 尽管 `name` 和 `age` 的值不同,`dog` 和 `cat` 对象都具有相同的类型:`Animal`。 因此,它们可以被传递给期望 `Animal` 对象的函数。 Python def feed_animal(animal): animal.eat("food") feed_animal(dog) feed_animal(cat) *** ** * ** *** ### 你应该知道的面向对象设计术语 * **Class (类)** :对象的蓝图。它定义了该类的对象实例将拥有的属性和方法。 (例如我们例子中的 `Animal`)。 * **Object (对象)** :类的实例。 (例如我们例子中的 `dog` 和 `cat`)。 * **Member Variable (成员变量)** :封装在特定对象中的变量。 (例如我们例子中的 `weight`, `height`, 和 `name` - *注:原文例子代码中是 name 和 age*)。 * **Member Function (成员函数)** :封装在特定对象中的函数。 (例如我们例子中的 `eat` 和 `sleep`)。 * **Constructor (构造函数)** :用于创建对象的特殊方法。 (例如我们例子中的 `__init__`)。 * **Instantiation** /**Construction (实例化/构造)** :从类创建对象的过程。 (例如我们例子中的 `dog = Animal(100, 5, "Dog")` - *注:对应原文注释代码,实际代码是 Animal("Dog", 5)*)。 *** ** * ** *** ### 继承 (Inheritance) 继承允许根据另一个类来定义一个类。这个"另一个类"被称为基类 (base class)、父类 (parent class) 或超类 (super class),而新类被称为派生类 (derived class)、子类 (child class) 或亚类 (sub class)。 在许多情况下,我们需要一个新类,但它共享现有类的许多相同属性和方法。在这些情况下,可以继承现有类并用新的属性和方法扩展它。 让我们想象一下,我们想向我们的 Animal 类添加一个大象 (Elephant) 对象。我们需要一个新的成员变量 `trunk_length`(象鼻长度)和一个新的成员函数 `trumpet`(吼叫)。这里的见解是:大象是动物,但并非所有动物都是大象。大象将始终拥有动物的所有共同属性和方法,但并非所有动物都拥有大象的属性和方法。 *** ** * ** *** ### 继承示例 本节代码可以在 \[可疑链接已删除\] 中找到。 Python class Elephant(Animal): def __init__(self, name, age, trunk_length): # 调用父类的构造函数 super().__init__(name, age) self.trunk_length = trunk_length def trumpet(self): print("Trumpeting") Elephant 类继承自 Animal 类。这意味着 Elephant 类拥有 Animal 类的所有属性和方法。这不仅通过借用 Animal 类的属性和方法节省了大量的打字时间和时间,而且还使代码更具可读性和可维护性。 *** ** * ** *** ### 继承示例 最重要的是,Elephant 可以作为 Animal 传递给任何函数。 Python def print_animal(animal): print(f"Name: {animal.name}") print(f"Age: {animal.age}") dog = Animal("Dog", 10) elephant = Elephant("Dumbo", 10) print_animal(elephant) print_animal(dog) 但是,期望 Elephant 对象的函数将不接受 Animal 对象。这是因为大象是动物,但动物不是大象。 Python def toot_horn(elephant): elephant.trumpet() # 这将工作 toot_horn(elephant) # 这将无法工作 toot_horn(dog) *** ** * ** *** ### 方法重写 (Overriding Methods) 最后,子类可以重写父类的方法。当父类的方法对子类没有意义时,这很有用。例如,Elephant 类可以重写 Animal 类的 `eat` 方法,以便在大象进食时打印不同的消息。 Python class Elephant(Animal): def __init__(self, name, age, trunk_length): super().__init__(name, age) self.trunk_length = trunk_length def trumpet(self): print("Trumpeting") def eat(self, food): # 重写 eat 方法 print(f"{self.name} is eating {food} with its trunk") *** ** * ** *** ### 方法重写 因此,期望 `Animal` 的代码段可以根据传递给它的对象类型执行完全不同的代码。 Python def feed_animal(animal): animal.eat("food") feed_animal(dog) feed_animal(elephant) 回到日常的面向对象设计术语,`Animal` 类是基类,`Elephant` 类是派生类。 派生类可以重写基类的方法。这意味着期望基类对象的函数可以根据传递给它的对象类型执行完全不同的代码。 *** ** * ** *** ### 抽象类 (Abstract Classes) 在过去的几个示例中,我们设想了一个简单的类 `Animal`,它有对象实例化。但在某些情况下,你不希望某个类有任何对象实例化。这就是抽象基类 (Abstract Base Classes) 发挥作用的地方。在我们的案例中,既然我们可以为每种动物类型创建一个子类,那么拥有一个通用的 Animal 就没有意义了。 抽象基类是旨在被继承但不被实例化的类。它们用于定义子类要实现的通用接口。 Python 中的 `abc` 模块提供了 `ABC` 类,可以继承它来创建抽象基类。 方法不必在抽象基类中实现,但也可以实现。这对于希望强制子类定义某个方法的情况非常有用。 *** ** * ** *** ### 抽象类示例 本节代码可以在 \[可疑链接已删除\] 中找到。 Python from abc import ABC, abstractmethod class Animal(ABC): """ 表示动物的抽象类。 """ def eat(self, food): print("Is eating {food}") @abstractmethod def move(self): raise NotImplementedError("move method not implemented") *** ** * ** *** ### 抽象类示例 然后我们可以添加动物。比如说狗和猫: Python class Dog(Animal): def move(self): print("Dog is running") class Cat(Animal): def move(self): print("Cat is walking") 我们要做的就是在子类中指定 Animal 类中未实现的方法。 *** ** * ** *** ### 抽象类示例 我们可以给猫添加一个子类。比如说 "LazyCat"(懒猫),它有一个独特的新方法 "sleep"(睡觉),同时共享所有其他 Cat 方法。 Python class LazyCat(Cat): def sleep(self): print("Cat is sleeping") 我们可以实例化这些类并调用它们的方法,除了抽象基类。 Python dog1 = Dog(); dog2 = Dog(); cat = Cat() lazy_cat = LazyCat() dog1.eat("meat") dog1.move() dog2.eat("bones") dog2.move() cat.eat("fish") cat.move() lazy_cat.eat("milk") lazy_cat.move() lazy_cat.sleep() *** ** * ** *** ### 更多面向对象设计术语 * **Inheritance (继承)** :一个类从另一个类继承属性和方法的能力。 (即我们例子中的 `Elephant` 继承自 `Animal`)。 * **Base Class (基类)** :属性和方法被继承的来源类。 这也称为父类或超类。 (即我们例子中的 `Animal`)。 * **Derived Class (派生类)** :从另一个类继承属性和方法的类。 这也称为子类。 (即我们例子中的 `Elephant`)。 * **Overriding (重写)** :子类提供已由其超类之一提供的方法的特定实现的能力。 (即我们例子中的 `Elephant`重写了 `Animal` 的 `eat` 方法)。 * **Abstract class (抽象类)** :旨在被继承但不直接实例化的类。 (即我们例子中的 `Animal`)。 * **Abstract Method (抽象方法)** :在抽象类中声明但未实现的方法。 由子类来实现。 (即我们例子中的 `move`)。 *** ** * ** *** ### SimObjects 与面向对象设计 SimObject 是 gem5 中代表模拟系统组件的对象。 它们从继承自 `SimObject` 抽象类的类实例化而来,并封装了模拟组件的参数(例如,内存大小)以及它用于以标准方式与其他组件交互的方法。 由于每一个都共享一个共同的基类,gem5 可以以一致的方式处理它们,尽管模拟的是各种各样的组件。 如果需要一个新组件,我们只需从最合乎逻辑的现有组件创建一个子类,并用新功能扩展它。 > gem5 还有称为 "Ports"(端口)的特殊参数,用于定义 SimObjects 之间的通信通道。 更多内容将在未来的课程中介绍。 *** ** * ** *** ### 一个 SimObject OO 设计示例 在 gem5 中,获取一个 SimObject 并扩展它以添加新功能是非常有用的。 gem5 理想上应该是 **对扩展开放但对修改关闭** 的。 直接修改 gem5 代码可能难以维护,并且在更新到 gem5 新版本时可能导致合并冲突。 下面展示了一个将 gem5 SimObject 特化以创建抽象 L1 缓存的示例。这随后被用作 L1 指令缓存的基类。 以下示例的代码也可以在 \[可疑链接已删除\] 中找到。 *** ** * ** *** ### 一个 SimObject OO 设计示例 Python from m5.objects import Cache from abc import ABC class L1Cache(type(Cache), type(ABC)): """具有默认值的简单 L1 缓存""" def __init__(self): # 这里我们设置/重写缓存的默认值。 self.assoc = 8 self.tag_latency = 1 self.data_latency = 1 self.response_latency = 1 self.mshrs = 16 self.tgts_per_mshr = 20 self.writeback_clean = True super().__init__() *** ** * ** *** ### 一个 SimObject OO 设计示例 我们扩展功能。在本例中,通过添加一个方法来辅助将缓存添加到总线和处理器。连接到 cpu 的部分留作未实现,因为对于每种类型的缓存它是不同的。 Python class L1Cache(type(Cache), type(ABC)): ... def connectBus(self, bus): """将此缓存连接到内存侧总线""" self.mem_side = bus.cpu_side_ports def connectCPU(self, cpu): """将此缓存的端口连接到 CPU 侧端口 这必须在子类中定义""" raise NotImplementedError Python class L1ICache(L1Cache): """具有默认值的简单 L1 指令缓存 """ def __init__(self): # 设置大小 self.size = "32kB" super().__init__() # 这是 L1ICache 连接到 CPU 所需的实现。 def connectCPU(self, cpu): """将此缓存的端口连接到 CPU icache 端口 """ self.cpu_side = cpu.icache_port *** ** * ** *** ### 有时 gem5 有点不同 虽然配置脚本主要是 Python,但 Python 和 gem5 的 Python 之间存在一些差异。 以下是一些需要牢记的重要差异: #### gem5 有一个特殊的模块 `m5` `m5` 模块是一个特殊的模块,它提供了配置脚本与 gem5 模拟器之间的接口。这是 *编译进 gem5 二进制文件* 中的,因此不是一个标准的 Python 模块。最常见的抱怨是 `import m5` 会被大多数 Python 智能感知工具视为错误。但是,当脚本由 gem5 解释时,它是有效的导入。 *** ** * ** *** ### SimObject 参数赋值是特殊的 在大多数情况下,Python 允许这样做: Python class Example(): hello = 6 bye = 6 example = Example() example.whatever = 5 print(f"{example.hello} {example.whatever} {example.bye}") 这里我们向对象添加了另一个变量。 但是,如果你尝试对 SimObject 这样做,gem5 会报错。 Shell AttributeError: 'example' object has no attribute 'whatever' 关于可以向 SimObject 赋值什么和不可以赋值什么是有规则的。 SimObjects 仅允许在 3 种情况下进行参数赋值: 1. 该参数存在于参数列表中。也就是你正在设置参数 (`simobject.param1 = 3`)。 2. 你设置的值是一个 SimObject 并且其变量名不与 SimObject 参数冲突 (`simobject.another_simobject = Cache()`)。 3. 参数名称以 `_` 开头。gem5 将忽略这个 (`simobject._new_variable = 5`)。 *** ** * ** *** ### SimObject 端口 (Port) 赋值是特殊的 端口是一类特殊的 SimObject 变量。 它们用于将 SimObjects 连接在一起。 设置响应端口和请求端口的语法是 `simobject1.{response_port} = simobject1.{request_port}`(反之亦然)。 这不是传统的 `=` 赋值,而是调用端口上的 `connect` 函数。 *** ** * ** *** ### SimObject 向量参数 (Vector parameters) 是不可变的 向量参数是其他 SimObjects 的参数值的向量。 它们是一种特殊的 SimObject 参数类型,用于在单个参数中存储多个值。 但是,与典型的 Python 列表不同,它们一旦创建就无法更改。你不能在向量创建后向其添加或移除 SimObjects。 Python simobject = ASimObject() simobject.vector_param = [1, 2] simobject.vector_param = [3, 4] # 这是可以的,但这只是覆盖了之前的值。 simobject.vector_param.append(5) # 这是不允许的。 simobject.vector_param.remove(1) # 这是不允许的。 *** ** * ** *** ### SimObject 向量参数是不可变的 以下是一个常见的错误: Python processor_simobject.cpus = [] for cpu in range(4): processor_simobject.cpus.append(CPU()) 正确的方法是一次性设置向量参数: Python simobject.cpus = [CPU() for _ in range(4)] *** ** * ** *** ### 模拟初始化后,你不能向 SimObject 添加新变量 Python simobject = ASimObject() simobject.var1 = 5 simobject.var2 = 6 m5.instantiate() # 也可以是 `Simulator` 的 `run()` 函数。 simobject.var3 = 7 # 这是不允许的 在某些情况下,这可能不会失败,但 SimObject 配置的更改将不会反映在模拟中。 *** ** * ** *** ### 总结 * Python 是一种强大且灵活的语言。 * 它在 gem5 中用于配置和运行模拟。 * Python 有许多内置的数据类型和集合。 * Python 是一种面向对象的语言。 * gem5 使用面向对象设计来模拟计算机系统的组件。 * gem5 对 SimObjects 有一些特殊的规则。