diff --git a/.gitignore b/.gitignore index 1c57e67..fc7aa96 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ split_verilogs .vscode **/__pycache__/ *.fst -**/UT_* \ No newline at end of file +**/UT_* +NemuBR/ diff --git a/test_src/uFTB-with-ftq/README.assets/env.png b/test_src/uFTB-with-ftq/README.assets/env.png new file mode 100644 index 0000000..58da993 Binary files /dev/null and b/test_src/uFTB-with-ftq/README.assets/env.png differ diff --git a/test_src/uFTB-with-ftq/README.md b/test_src/uFTB-with-ftq/README.md new file mode 100644 index 0000000..18ceeb1 --- /dev/null +++ b/test_src/uFTB-with-ftq/README.md @@ -0,0 +1,128 @@ +# uFTB-env + +## 介绍 + +本项目提供了基于真实指令流的香山处理器 uFTB 分支预测器的仿真验证环境,以及时钟精确的 uFTB 参考模型,最终可给出 uFTB 的分支预测准确率。 + +为此,本项目为 uFTB 提供了简易的 BPU Top Wrapper,以向 uFTB 提供时序控制和输入输出处理。并向 BPU Top 提供了简易的 FTQ 实现,FTQ 中实例化了一个真实的程序仿真器,用于生成真实指令流,FTQ 会处理 BPU 产生的预测结果,并向 BPU 提供更新请求与重定向请求的执行反馈。大致的结构可参考下图: + +![env](README.assets/env.png) + +对于香山 BPU 中其他子预测器的验证,可复用本项目中的真实指令执行环境,但需要对 DUT 的接口、时序以及需要使用的预测结果通道等进行适配。 + +## 快速使用 + +### 环境配置 + +**1. 安装 mlvp** + +具体步骤参见 https://github.com/XS-MLVP/mlvp + +**2. 生成 BRTParser Trace 工具** + +为了生成真实的指令流,BRTParser 作为一个自定义的工具已经被放置在了本仓库中,但其中缺少了模拟器仿真程序,需要自行编译生成,具体步骤参见 https://github.com/yaozhicheng/NEMU + +生成编译结果 `NemuBR` 后,将其放置在 `BRTParser` 目录下,工具即可正常使用。 + +**3. 编译 DUT** + +参照 https://github.com/XS-MLVP/env-xs-ov-00-bpu 编译 DUT,将编译结果(`UT_FauFTB`)放置本仓库根目录下。 + +### 仿真验证 + +在本仓库根目录下执行 + +```shell +python uftb-env/tb.py +``` + +即可开始仿真验证。 + +程序运行结束后,会打印出分支预测的统计信息。 + +若要更改需要执行的程序,可在 `config.py` 中更改相应变量的值,仿真所需的真实程序已经放置在 `ready-to-run` 目录下。若要更改仿真所持续的周期数,可在 `config.py` 中更改 `MAX_CYCLE` 的值。 + +## 使用说明 + +### 目录结构 + +``` +uFTB-env/ +├── BRTParser # BRTParser 工具,用于生成真实指令流 +│   ├── __init__.py +│   ├── NemuBR.txt +│   └── NemuBR # 编译生成的模拟器仿真程序 +├── LICENSE +├── README.assets +│   └── env.png +├── README.md +├── ready-to-run # 可供直接运行的真实程序 +│   ├── coremark-2-iteration.bin +│   ├── linux-0xa0000.bin +│   ├── linux.bin +│   └── microbench.bin +├── uftb-env # uFTB 环境源码 +│   ├── bpu_top.py # BPU Top Wrapper +│   ├── bundle.py # 定义了 DUT 相关接口 +│   ├── config.py # 与 uFTB 相关的配置信息 +│   ├── executor.py # 对 BRTParser 工具的封装 +│   ├── ftb.py # FTB 项相关结构 +│   ├── ftq.py # FTQ 实现 +│   ├── tb.py # 测试用例 +│   ├── uftb_model.py # uFTB 参考模型 +│   └── utils.py # 相关工具函数 +└── UT_FauFTB # DUT 编译结果 +``` + +### 指令执行器 + +`executor.py` 中定义了 `Executor` 类,用于对 BRTParser 工具的封装,提供了生成真实指令流的功能。 + +实现中,由于 BRTParser 工具只提供了分支指令的跳转 Trace,因此普通指令的长度无法获取,为此普通指令的长度是在 `Executor` 中进行随机生成的。 + +使用时需要用到两个主要方法: + +- `current_inst` 用于获取当前指令。调用时返回当前指令 PC、指令长度及分支指令信息。分支指令如果为空则表示当前指令不是分支指令,否则给出分支指令相关信息。 +- `next_inst` 用于执行当前指令。 + +### FTQ + +`ftq.py` 中实现了 FTQ 的相关逻辑,指令执行器也在此被实例化,因此 FTQ 具备了获取真实指令执行情况的能力。 + + +FTQ 的工作流程如下: + +1. 在每个周期 `update` 方法被调用,用于更新 FTQ 的状态。此时,如果传入的 BPU 输出信息中,s1 通道有效,则 FTQ 会将 s1 产生的预测结果存入一个 FTQ 项中。 +2. 执行一个预测块。FTQ 检测队列中是否还有尚未执行的预测块,如果没有则跳过,如有则执行此预测块,分为两种情况。 + - 如果预测块指示 FTB 项没有 hit,这说明预测结果无效。FTB 会调用执行器,生成一个完整的 FTB 项。 + - 如果预测块指示 FTB 项 hit,并且预测结果中的起始 PC 与执行器当前 PC 相同,则说明本次预测有效。FTQ 会根据预测结果调用执行器,若执行过程中出现与预测结果不符的情况,则 FTQ 生成重定向请求,以供 BPU 恢复到正确状态。 +3. 生成更新和重定向请求。FTQ 会使用新生成或者更新后的 FTB 项生成更新请求,如果有预测错误还会生成重定向请求。最终,FTQ 会将更新请求和重定向请求传递给 BPU。 + +在该 FTQ 实现中,仅仅根据 s1 通道的预测结果来更新 FTQ 队列,对于 s2, s3 通道的预测结果没有进行相应。因此,若需要验证 s2, s3 通道的预测结果,需要对 FTQ 的该部分进行相应的修改。 + +### BPU Top Wrapper + +`bpu_top.py` 中实现了 BPU Top Wrapper,用于向 uFTB 提供时序控制和输入输出处理。由于 uFTB 只在 s1 阶段工作,因此 BPU Top 并没有对 s2, s3 通道的预测结果进行处理,并且在 BPU Top 中将 DUT 中的 `s2_fire` 及 `s3_fire` 端口持续置高,以获取 DUT 在 s3 阶段输出的 `meta` 信息 。如果需要验证 s2, s3 通道的预测结果,需要对 BPU Top 的该部分进行相应的修改。 + +具体地,`BPU Top` 会维护流水线控制信息,并驱动 DUT。在每个周期,BPU 的工作流程如下: + +1. 更新 DUT 的流水线控制信号 +2. 获取 DUT 的预测结果并进行加工。BPU Top 会获取 DUT 的预测结果,并且对其中需要 BPU 赋值的部分进行赋值,生成 BPU 的输出信息。 +3. 获取 uFTB Model 的预测结果,并进行对比。以此来验证 uFTB 实现的正确性。 +4. 将 BPU 输出信息传递给 FTQ,获取 FTQ 的更新请求和重定向请求。 +5. 将 FTQ 的更新请求和重定向请求传递给 DUT 和 uFTB Model,并更新流水线控制信号。 + +在本项目中还实现了一个 `FTBProvider` 用于提供基于 FTB 项的基础预测结果,如果需要验证非 FTB 项的预测结果,需要将 `ftb_provider_stage_enable` 中相应阶段开关打开,便可以在相应阶段添加 FTB 的预测结果。 + + + + + + + + + + + + + diff --git a/test_src/uFTB-with-ftq/uftb-env/bpu_top.py b/test_src/uFTB-with-ftq/uftb-env/bpu_top.py new file mode 100644 index 0000000..e3ff2b7 --- /dev/null +++ b/test_src/uFTB-with-ftq/uftb-env/bpu_top.py @@ -0,0 +1,220 @@ +from mlvp import * +from bundle import * +from ftq import * +from uftb_model import uFTBModel + +def assert_equal(a, b): + if a != b: + print(f"[Error] Expected is {a}, but actual is {b}") + exit(1) + +def compare_uftb_full_pred(uftb_output, std_output): + need_compare = ["hit", "slot_valids_0", "slot_valids_1", "targets_0", "targets_1", + "offsets_0", "offsets_1", "fallThroughAddr", "is_br_sharing", + "br_taken_mask_0", "br_taken_mask_1"] + for key in need_compare: + assert_equal(uftb_output[key], std_output[key]) + +class BPUTop: + def __init__(self, dut, dut_out: BranchPredictionResp, dut_update: UpdateBundle, pipeline_ctrl: PipelineCtrlBundle, enable_ctrl: EnableCtrlBundle): + self.dut = dut + + self.dut_out = dut_out + self.dut_update = dut_update + self.pipeline_ctrl = pipeline_ctrl + self.enable_ctrl = enable_ctrl + + self.s0_fire = 0 + self.s1_fire = 0 + self.s2_fire = 0 + self.s3_fire = 0 + self.s0_pc = 0 + self.s1_pc = 0 + self.s2_pc = 0 + self.s3_pc = 0 + self.s1_hit_way = 0 + self.s2_hit_way = 0 + self.s3_hit_way = 0 + + self.ftq = FTQ() + self.uftb_model = uFTBModel() + self.ftb_provider = FTBProvider() + + def pipeline_assign(self): + self.pipeline_ctrl.s0_fire_0.value = self.s0_fire + self.pipeline_ctrl.s0_fire_1.value = self.s0_fire + self.pipeline_ctrl.s0_fire_2.value = self.s0_fire + self.pipeline_ctrl.s0_fire_3.value = self.s0_fire + + self.pipeline_ctrl.s1_fire_0.value = self.s1_fire + self.pipeline_ctrl.s1_fire_1.value = self.s1_fire + self.pipeline_ctrl.s1_fire_2.value = self.s1_fire + self.pipeline_ctrl.s1_fire_3.value = self.s1_fire + + self.pipeline_ctrl.s2_fire_0.value = self.s2_fire + self.pipeline_ctrl.s2_fire_1.value = self.s2_fire + self.pipeline_ctrl.s2_fire_2.value = self.s2_fire + self.pipeline_ctrl.s2_fire_3.value = self.s2_fire + + self.pipeline_ctrl.s3_fire_0.value = self.s3_fire + self.pipeline_ctrl.s3_fire_1.value = self.s3_fire + self.pipeline_ctrl.s3_fire_2.value = self.s3_fire + self.pipeline_ctrl.s3_fire_3.value = self.s3_fire + + # Set the value to 1 forcibly to obtain meta information + self.dut.io_s1_fire_0.value = 1 + self.dut.io_s2_fire_0.value = 1 + + self.dut.io_in_bits_s0_pc_0.value = self.s0_pc + self.dut.io_in_bits_s0_pc_1.value = self.s0_pc + self.dut.io_in_bits_s0_pc_2.value = self.s0_pc + self.dut.io_in_bits_s0_pc_3.value = self.s0_pc + + def generate_bpu_output(self, dut_output): + dut_output["s1"]["valid"] = self.s1_fire + dut_output["s2"]["valid"] = self.s2_fire + dut_output["s3"]["valid"] = self.s3_fire + + dut_output["s2"]["pc_3"] = self.s2_pc + dut_output["s3"]["pc_3"] = self.s3_pc + + # Provide Basic FTB Prediction + ftb_provider_stage_enable = (False, False, False) + + if self.s1_fire and ftb_provider_stage_enable[0]: + ftb_entry = self.ftb_provider.provide_ftb_entry(self.s1_fire, self.s1_pc) + if ftb_entry is not None: + ftb_entry.put_to_full_pred_dict(self.s1_pc, dut_output["s1"]["full_pred"]) + else: + set_all_none_item_to_zero(dut_output["s1"]["full_pred"]) + + if self.s2_fire and ftb_provider_stage_enable[1]: + ftb_entry = self.ftb_provider.provide_ftb_entry(self.s2_fire, self.s2_pc) + if ftb_entry is not None: + ftb_entry.put_to_full_pred_dict(self.s2_pc, dut_output["s2"]["full_pred"]) + else: + set_all_none_item_to_zero(dut_output["s2"]["full_pred"]) + + if self.s3_fire and ftb_provider_stage_enable[2]: + ftb_entry = self.ftb_provider.provide_ftb_entry(self.s3_fire, self.s3_pc) + if ftb_entry is not None: + ftb_entry.put_to_full_pred_dict(self.s3_pc, dut_output["s3"]["full_pred"]) + dut_output["last_stage_ftb_entry"] = ftb_entry.__dict__() + else: + set_all_none_item_to_zero(dut_output["s3"]["full_pred"]) + dut_output["last_stage_ftb_entry"] = FTBEntry().__dict__() + + return dut_output + + async def run(self): + self.enable_ctrl.ubtb_enable.value = 1 + self.s0_pc = RESET_VECTOR + + self.dut.reset.value = 1 + await ClockCycles(self.dut, 10) + self.dut.reset.value = 0 + await ClockCycles(self.dut, 10) + + while True: + self.pipeline_assign() + await ClockCycles(self.dut, 1) + + self.s3_fire = self.s2_fire + self.s2_fire = self.s1_fire + self.s1_fire = self.s0_fire + self.s3_pc = self.s2_pc + self.s2_pc = self.s1_pc + self.s1_pc = self.s0_pc + self.s3_hit_way = self.s2_hit_way + self.s2_hit_way = self.s1_hit_way + + npc_gen = self.s0_pc + next_s0_fire = 1 + s1_flush = False + s2_flush = False + s3_flush = False + + + # Get dut output and generate bpu output + dut_output = self.dut_out.collect() + bpu_output = self.generate_bpu_output(dut_output) + + ftb_entry = FTBEntry.from_full_pred_dict(self.s1_pc, dut_output["s1"]["full_pred"]) + model_output = self.uftb_model.generate_output(self.s1_fire, self.s1_pc) + std_ftb_entry = self.ftb_provider.provide_ftb_entry(self.s1_fire, self.s1_pc) + + if model_output: + self.s1_hit_way = model_output[2] + else: + self.s1_hit_way = None + + # print("-" * 30) + if self.s1_fire: + # Debug Imformation + # print("[BPU]") + # print("New prediction at", hex(self.s1_pc)) + # if bpu_output["s1"]["full_pred"]["hit"]: + # print("Dut Hit") + + # print("FTB Entry in pred result: ") + # if bpu_output["s1"]["full_pred"]["hit"]: + # ftb_entry.print(self.s1_pc) + # else: + # print("No FTB Entry") + # print("br_taken_mask:", bpu_output["s1"]["full_pred"]["br_taken_mask_0"], bpu_output["s1"]["full_pred"]["br_taken_mask_1"]) + + # print("FTB Entry in uFTB Model: ") + # if model_output: + # model_output[0].print(self.s1_pc) + # print("br_taken_mask:", model_output[1]) + # else: + # print("No FTB Entry") + + # Compare dut output and uFTB model output + expected_hit = model_output is not None + actual_hit = bpu_output["s1"]["full_pred"]["hit"] + assert_equal(expected_hit, actual_hit) + if parse_uftb_meta(dut_output["last_stage_meta"])["hit"] or self.s3_hit_way is not None: + expected_hit_way = self.s3_hit_way + actual_hit_way = parse_uftb_meta(dut_output["last_stage_meta"])["pred_way"] + assert_equal(expected_hit_way, actual_hit_way) + + if model_output: + std_full_pred = {} + model_output[0].put_to_full_pred_dict(self.s1_pc, std_full_pred) + std_full_pred["br_taken_mask_0"] = model_output[1][0] + std_full_pred["br_taken_mask_1"] = model_output[1][1] + compare_uftb_full_pred(bpu_output["s1"]["full_pred"], std_full_pred) + + + # Forward to FTQ and get update and redirect request + if self.s1_fire: + npc_gen = get_target_from_full_pred_dict(self.s1_pc, dut_output["s1"]["full_pred"]) + update_request, redirect_request = self.ftq.update(bpu_output, std_ftb_entry) + + ## Update Request + if update_request: + self.uftb_model.update(update_request) + self.ftb_provider.update(update_request) + self.dut_update.assign(update_request) + self.dut_update.valid.value = 1 + else: + self.dut_update.valid.value = 0 + + ## Redirect Request + if redirect_request: + next_s0_fire = 1 + s1_flush = True + s2_flush = True + s3_flush = True + npc_gen = redirect_request["cfiUpdate"]["target"] + + # Add new control information + self.s0_fire = next_s0_fire + self.s0_pc = npc_gen + if s1_flush: + self.s1_fire = 0 + if s2_flush: + self.s2_fire = 0 + if s3_flush: + self.s3_fire = 0 diff --git a/test_src/uFTB-with-ftq/uftb-env/bundle.py b/test_src/uFTB-with-ftq/uftb-env/bundle.py new file mode 100644 index 0000000..69c63e1 --- /dev/null +++ b/test_src/uFTB-with-ftq/uftb-env/bundle.py @@ -0,0 +1,51 @@ +from mlvp import Interface + +class PipelineCtrlBundle(Interface): + signals_list = ["s0_fire_0", "s0_fire_1", "s0_fire_2", "s0_fire_3", + "s1_fire_0", "s1_fire_1", "s1_fire_2", "s1_fire_3", + "s2_fire_0", "s2_fire_1", "s2_fire_2", "s2_fire_3", + "s3_fire_0", "s3_fire_1", "s3_fire_2", "s3_fire_3", + "s1_ready", "s2_ready", "s3_ready", + "s2_redirect", "s3_redirect"] + +class EnableCtrlBundle(Interface): + signals_list = ["ubtb_enable", "btb_enable", "bim_enable", "tage_enable", + "sc_enable", "ras_enable", "loop_enable"] + + +class FTBEntryBundle(Interface): + signals_list = ["brSlots_0_offset", "brSlots_0_lower", "brSlots_0_tarStat", "brSlots_0_valid", + "tailSlot_offset", "tailSlot_lower", "tailSlot_tarStat", "tailSlot_sharing", "tailSlot_valid", + "pftAddr", "carry", "isCall", "isRet", "isJalr", "last_may_be_rvi_call", + "always_taken_0", "always_taken_1"] + +class UpdateBundle(Interface): + signals_list = ["valid", "bits_pc", "bits_br_taken_mask_0", "bits_br_taken_mask_1"] + + sub_interfaces = [ + ("ftb_entry", lambda dut: FTBEntryBundle.from_prefix(dut, "bits_ftb_entry_")) + ] + +class FullBranchPredirectionBundle(Interface): + signals_list = ["hit", "slot_valids_0", "slot_valids_1", "targets_0", "targets_1", + "offsets_0", "offsets_1", "fallThroughAddr", "fallThroughErr", + "is_jal", "is_jalr", "is_call", "is_ret", "is_br_sharing", + "last_may_be_rvi_call", + "br_taken_mask_0", "br_taken_mask_1", + "jalr_target"] + +class BranchPredictionBundle(Interface): + signals_list = ["pc_3", "valid", "hasRedirect", "ftq_idx"] + sub_interfaces = [ + ("full_pred", lambda dut: FullBranchPredirectionBundle.from_regex(dut, r"full_pred_\d_(.*)")) + ] + + +class BranchPredictionResp(Interface): + signals_list = ["last_stage_meta"] + sub_interfaces = [ + ("s1", lambda dut: BranchPredictionBundle.from_prefix(dut, "s1_")), + ("s2", lambda dut: BranchPredictionBundle.from_prefix(dut, "s2_")), + ("s3", lambda dut: BranchPredictionBundle.from_prefix(dut, "s3_")), + ("last_stage_ftb_entry", lambda dut: FTBEntryBundle.from_prefix(dut, "last_stage_ftb_entry_")) + ] diff --git a/test_src/uFTB-with-ftq/uftb-env/config.py b/test_src/uFTB-with-ftq/uftb-env/config.py new file mode 100644 index 0000000..db3235e --- /dev/null +++ b/test_src/uFTB-with-ftq/uftb-env/config.py @@ -0,0 +1,26 @@ +PROGRAM_NAME = "microbench.bin" +MAX_CYCLE = 10000 + +import os +ROOT_PATH = os.path.dirname(os.path.abspath(__file__)) + "/../../.." +DUT_PATH = ROOT_PATH + "/out/picker_out_uFTB" +UTILS_PATH = ROOT_PATH + "/utils" +RROGRAM_FORDER_PATH = UTILS_PATH + "/ready-to-run" +PROGRAM_PATH = RROGRAM_FORDER_PATH + "/" + PROGRAM_NAME + + +INST_OFFSET_BITS = 1 +PREDICT_WIDTH_OFFSET_BITS = 4 + +PREDICT_WIDTH_BYTES = 32 +RESET_VECTOR = 0x80000000 + +UFTB_WAYS_NUM = 32 +UFTB_TAG_SIZE = 16 + + +TAR_OVF = 1 +TAR_UDF = 2 +TAR_FIT = 0 + + diff --git a/test_src/uFTB-with-ftq/uftb-env/executor.py b/test_src/uFTB-with-ftq/uftb-env/executor.py new file mode 100644 index 0000000..b8fbe05 --- /dev/null +++ b/test_src/uFTB-with-ftq/uftb-env/executor.py @@ -0,0 +1,102 @@ +from config import * + +import os +os.sys.path.append(UTILS_PATH) + +from BRTParser import BRTParser + +class Executor: + """Get program real execution instruction flow.""" + + def __init__(self, filename, reset_vector=0x80000000): + self._executor = BRTParser().fetch(filename) + self._current_branch = next(self._executor) + self._current_pc = reset_vector + + self._last_exec_result = { + "pc": 0, + "inst_len": 0, + "branch": 0 + } + + self._exec_once() + + def current_inst(self): + """Return current instruction information.""" + return self._last_exec_result["pc"], self._last_exec_result["inst_len"], self._last_exec_result["branch"] + + def next_inst(self): + """Move to next instruction.""" + self._exec_once() + + def _exec_once(self): + # print(f"- Executor: pc: {hex(self._last_exec_result['pc'])}, inst_len: {self._last_exec_result['inst_len']},\ + # branch: {self._last_exec_result['branch']}") + + self._last_exec_result["pc"] = self._current_pc + + inst_len, branch = 0, None + if (2 <= self._current_branch["pc"] - self._current_pc <= 4): + inst_len = self._current_branch["pc"] - self._current_pc + self._current_pc = self._current_branch["pc"] + + elif (self._current_branch["pc"] == self._current_pc): + inst_len = Executor.branch_inst_len(self._current_branch) + self._current_pc = self._current_branch["target"] if self._current_branch["taken"] \ + else self._current_pc + inst_len + + branch = self._current_branch + self._current_branch = next(self._executor) + + else: + inst_len = Executor.random_inst_len(self._current_pc) + self._current_pc += Executor.random_inst_len(self._current_pc) + + self._last_exec_result["inst_len"] = inst_len + self._last_exec_result["branch"] = branch + + @staticmethod + def random_inst_len(pc): + xor_ans = 0 + for i in range(8): + xor_ans ^= (pc >> i) & 1 + return 2 if xor_ans else 4 + + @staticmethod + def is_cond_branch_inst(branch): + return branch["type"] == "*.CBR" + + @staticmethod + def is_jump_inst(branch): + return not Executor.is_cond_branch_inst(branch) + + @staticmethod + def is_call_inst(branch): + return ".CALL" in branch["type"] + + @staticmethod + def is_ret_inst(branch): + return ".RET" in branch["type"] + + @staticmethod + def is_jal_inst(branch): + return branch["type"] == "I.JAL" or branch["type"] == "P.JAL" + + @staticmethod + def is_jalr_inst(branch): + return ".JALR" in branch["type"] or ".JR" in branch["type"] + + @staticmethod + def is_compressed_inst(branch): + type = branch["type"] + if "C." in type: + return True + elif Executor.is_cond_branch_inst(branch): + return Executor.random_inst_len(branch["pc"]) == 2 + else: + return False + + @staticmethod + def branch_inst_len(branch): + return 2 if Executor.is_compressed_inst(branch) else 4 + diff --git a/test_src/uFTB-with-ftq/uftb-env/ftb.py b/test_src/uFTB-with-ftq/uftb-env/ftb.py new file mode 100644 index 0000000..4647fce --- /dev/null +++ b/test_src/uFTB-with-ftq/uftb-env/ftb.py @@ -0,0 +1,191 @@ +from utils import * + +class FTBSlot: + def __init__(self): + self.valid = 0 + self.offset = 0 + self.lower = 0 + self.tarStart = 0 + self.sharing = 0 + + def print(self, pc, is_cond_branch): + if not self.valid: + print("*\tInvalid FTBSlot") + return + + if is_cond_branch: + print(f"*\t[Conditional Branch Inst] at PC {hex(get_slot_addr(pc, self.offset))}: Target: {hex(get_target_addr(pc, self.tarStart, self.lower, 12))}") + else: + print(f"*\t[Jump Inst] PC {hex(get_slot_addr(pc, self.offset))}: Target: {hex(get_target_addr(pc, self.tarStart, self.lower, 20))}") + + +class FTBEntry: + def __init__(self): + self.valid = 0 + self.brSlot = FTBSlot() + self.tailSlot = FTBSlot() + self.pftAddr = 0 + self.carry = 0 + self.isCall = False + self.isRet = False + self.isJal = False + self.isJalr = False + self.last_may_be_rvi_call = False + self.always_taken = [0, 0] + + def add_cond_branch_inst(self, start_pc, inst_pc, is_taken, target_addr): + if self.brSlot.valid and self.tailSlot.valid: + return False + + slot = FTBSlot() + slot.valid = True + slot.offset = get_slot_offset(start_pc, inst_pc) + slot.lower = get_lower_addr(target_addr, 12) + slot.tarStart = get_target_stat(start_pc >> 12, target_addr >> 12) + + if self.brSlot.valid: + self.tailSlot = slot + self.tailSlot.sharing = True + self.always_taken[1] = is_taken + else: + self.brSlot = slot + self.always_taken[0] = is_taken + + return True + + def add_jmp_inst(self, start_pc, inst_pc, target_addr, inst_len, is_call, is_ret, is_jalr, is_jal): + if self.tailSlot.valid: + return False + + self.tailSlot.valid = True + self.tailSlot.offset = get_slot_offset(start_pc, inst_pc) + self.tailSlot.lower = get_lower_addr(target_addr, 20) + self.tailSlot.tarStart = get_target_stat(start_pc >> 20, target_addr >> 20) + self.tailSlot.sharing = False + + self.isCall = is_call + self.isRet = is_ret + self.isJalr = is_jalr + self.isJal = is_jal + self.last_may_be_rvi_call = is_call and inst_len == 4 + + return True + + def put_to_full_pred_dict(self, pc, d): + d["hit"] = 1 + d["slot_valids_0"] = self.brSlot.valid + d["slot_valids_1"] = self.tailSlot.valid + d["targets_0"] = get_target_addr(pc, self.brSlot.tarStart, self.brSlot.lower, 12) + d["targets_1"] = get_target_addr(pc, self.tailSlot.tarStart, self.tailSlot.lower, 12 if self.tailSlot.sharing else 20) + d["offsets_0"] = self.brSlot.offset + d["offsets_1"] = self.tailSlot.offset + d["fallThroughErr"] = get_fallthrough_addr(pc, self.pftAddr, self.carry) <= pc + d["fallThroughAddr"] = get_fallthrough_addr(pc, self.pftAddr, self.carry) if not d["fallThroughErr"] else pc + (PREDICT_WIDTH_BYTES) + d["is_jal"] = self.isJal + d["is_jalr"] = self.isJalr + d["is_call"] = self.isCall + d["is_ret"] = self.isRet + d["is_br_sharing"] = self.tailSlot.sharing + d["last_may_be_rvi_call"] = self.last_may_be_rvi_call + d["br_taken_mask_0"] = self.always_taken[0] + d["br_taken_mask_1"] = self.always_taken[1] + d["jalr_target"] = get_target_addr(pc, self.tailSlot.tarStart, self.tailSlot.lower, 20) + + + def __dict__(self): + return { + "brSlots_0_offset": self.brSlot.offset, + "brSlots_0_lower": self.brSlot.lower, + "brSlots_0_tarStat": self.brSlot.tarStart, + "brSlots_0_valid": self.brSlot.valid, + "tailSlot_offset": self.tailSlot.offset, + "tailSlot_lower": self.tailSlot.lower, + "tailSlot_tarStat": self.tailSlot.tarStart, + "tailSlot_sharing": self.tailSlot.sharing, + "tailSlot_valid": self.tailSlot.valid, + "pftAddr": self.pftAddr, + "carry": self.carry, + "isCall": self.isCall, + "isRet": self.isRet, + "isJalr": self.isJalr, + "last_may_be_rvi_call": self.last_may_be_rvi_call, + "always_taken_0": self.always_taken[0], + "always_taken_1": self.always_taken[1] + } + + @classmethod + def from_dict(self, d): + entry = FTBEntry() + entry.brSlot.offset = d["brSlots_0_offset"] + entry.brSlot.lower = d["brSlots_0_lower"] + entry.brSlot.tarStart = d["brSlots_0_tarStat"] + entry.brSlot.valid = d["brSlots_0_valid"] + entry.tailSlot.offset = d["tailSlot_offset"] + entry.tailSlot.lower = d["tailSlot_lower"] + entry.tailSlot.tarStart = d["tailSlot_tarStat"] + entry.tailSlot.sharing = d["tailSlot_sharing"] + entry.tailSlot.valid = d["tailSlot_valid"] + entry.pftAddr = d["pftAddr"] + entry.carry = d["carry"] + entry.isCall = d["isCall"] + entry.isRet = d["isRet"] + entry.isJalr = d["isJalr"] + entry.last_may_be_rvi_call = d["last_may_be_rvi_call"] + entry.always_taken[0] = d["always_taken_0"] + entry.always_taken[1] = d["always_taken_1"] + return entry + + @classmethod + def from_full_pred_dict(self, pc, d): + entry = FTBEntry() + entry.brSlot.valid = d["slot_valids_0"] + entry.brSlot.offset = d["offsets_0"] + entry.brSlot.lower = get_lower_addr(d["targets_0"], 12) + entry.brSlot.tarStart = get_target_stat(pc >> 12, d["targets_0"] >> 12) + entry.tailSlot.valid = d["slot_valids_1"] + entry.tailSlot.offset = d["offsets_1"] + entry.tailSlot.sharing = d["is_br_sharing"] + + if entry.tailSlot.sharing: + entry.tailSlot.lower = get_lower_addr(d["targets_1"], 12) + entry.tailSlot.tarStart = get_target_stat(pc >> 12, d["targets_1"] >> 12) + else: + entry.tailSlot.lower = get_lower_addr(d["targets_1"], 20) + entry.tailSlot.tarStart = get_target_stat(pc >> 20, d["targets_1"] >> 20) + + entry.pftAddr = get_pftaddr(d["fallThroughAddr"]) + entry.carry = get_pftaddr_carry(pc, d["fallThroughAddr"]) + entry.isCall = d["is_call"] + entry.isRet = d["is_ret"] + entry.isJal = d["is_jal"] + entry.isJalr = d["is_jalr"] + entry.last_may_be_rvi_call = d["last_may_be_rvi_call"] + entry.always_taken[0] = d["br_taken_mask_0"] + entry.always_taken[1] = d["br_taken_mask_1"] + + return entry + + + def print(self, pc): + print(f"[FTBEntry at {hex(pc)}]") + print(f"* Slots:") + self.brSlot.print(pc, True) + self.tailSlot.print(pc, self.tailSlot.sharing) + print("* Other Info:") + print(f"*\tFallthrough Addr: {hex(get_fallthrough_addr(pc, self.pftAddr, self.carry))}") + print(f"*\tisCall: {self.isCall}, isRet: {self.isRet}, isJalr: {self.isJalr}, isJal: {self.isJal}") + print(f"*\tlast_may_be_rvi_call: {self.last_may_be_rvi_call}, always_taken: {self.always_taken}") + +class FTBProvider(): + def __init__(self): + self.entries = {} + + def update(self, update_request): + if update_request["valid"]: + self.entries[update_request["bits_pc"]] = FTBEntry.from_dict(update_request["ftb_entry"]) + + def provide_ftb_entry(self, fire, pc): + if fire and pc in self.entries: + return self.entries[pc] + else: + return None diff --git a/test_src/uFTB-with-ftq/uftb-env/ftq.py b/test_src/uFTB-with-ftq/uftb-env/ftq.py new file mode 100644 index 0000000..6fcd426 --- /dev/null +++ b/test_src/uFTB-with-ftq/uftb-env/ftq.py @@ -0,0 +1,312 @@ +from bundle import * +from config import * +from utils import * +from executor import Executor +from ftb import * +from random import random + +class PredictionStatistician: + """Predictive condition statistician for branch instructions""" + + def __init__(self): + # { pc : [number, right_number]} + self.cond_branches_list = {} + + # { pc : [type, number, right_number]} + self.jmp_branches_list = {} + + + def record_cond_branch(self, pc, correct): + if pc in self.cond_branches_list: + self.cond_branches_list[pc][0] += 1 + self.cond_branches_list[pc][1] += correct + else: + self.cond_branches_list[pc] = [1, int(correct)] + + def record_jmp_branch(self, pc, branch_type, correct): + if pc in self.jmp_branches_list: + self.jmp_branches_list[pc][1] += 1 + self.jmp_branches_list[pc][2] += correct + else: + self.jmp_branches_list[pc] = [branch_type, 1, int(correct)] + + def summary(self): + print("=" * 30) + print("Summary") + print("[Conditional Branches]") + cond_branches_total = sum([record[0] for record in self.cond_branches_list.values()]) + cond_branches_correct = sum([record[1] for record in self.cond_branches_list.values()]) + print(f"Total: {cond_branches_total}, Correct: {cond_branches_correct}, Accuracy: {cond_branches_correct / cond_branches_total}") + + for pc, record in self.cond_branches_list.items(): + print(f"PC: {hex(pc)}\tTotal: {record[0]}\tCorrect: {record[1]}\tAccuracy: {record[1] / record[0]}") + + print("[Jump Branches]") + jmp_branches_total = sum([record[1] for record in self.jmp_branches_list.values()]) + jmp_branches_correct = sum([record[2] for record in self.jmp_branches_list.values()]) + print(f"Total: {jmp_branches_total}, Correct: {jmp_branches_correct}, Accuracy: {jmp_branches_correct / jmp_branches_total}") + for pc, record in self.jmp_branches_list.items(): + print(f"PC: {hex(pc)}\tType: {record[0]}\tTotal: {record[1]}\tCorrect: {record[2]}\tAccuracy: {record[2] / record[1]}") + + print("[All Branches]") + total = cond_branches_total + jmp_branches_total + correct = cond_branches_correct + jmp_branches_correct + print(f"Total: {total}, Correct: {correct}, Accuracy: {correct / total}") + + @staticmethod + def get_type(is_call, is_ret, is_jalr, is_jal): + if is_call: + return "call" + elif is_ret: + return "ret" + elif is_jalr: + return "jalr" + elif is_jal: + return "jal" + else: + return "jmp" + +pred_stat = PredictionStatistician() + + + +class FTQEntry: + """Stores all the information that FTQ entries need to record.""" + + def __init__(self): + self.pc = None + self.ftb = None + self.full_pred = None + +class FTQ: + """Simulate FTQ behavior.""" + + def __init__(self): + self.executor = Executor(filename=PROGRAM_PATH, reset_vector=RESET_VECTOR) + + self.entries = [FTQEntry() for _ in range(32)] + self.bpu_ptr = 0 + self.exec_ptr = 0 + + self.update_queue = [] + self.redirect_queue = [] + + def update(self, bpu_out, ftb_entry): + # print("[FTQ]") + + # Get the result from BPU out and update the FTQ entry + self._update_entries(bpu_out, ftb_entry) + + # Execute a FTQ entry + self._exec_one_ftq_entry() + + # Generate update and redirect request + update_request, redirect_request = None, None + if self.update_queue: + update_request = self._generate_update_request(self.update_queue.pop(0)) + # print("Send Update Request: %s" % hex(update_request['bits_pc']), \ + # "br_taken_mask:", update_request["bits_br_taken_mask_0"], update_request["bits_br_taken_mask_1"]) + + if self.redirect_queue: + cfi_target = self.redirect_queue.pop(0) + redirect_request = self._generate_redirect_request(cfi_target) + # print("Send Redirect Request: (target: %s)" % hex(cfi_target)) + + return (update_request, redirect_request) + + + + + + def _get_entry(self, ptr): + return self.entries[ptr % 32] + + def _exec_one_ftq_entry(self): + if self.exec_ptr >= self.bpu_ptr: + return None + + # Get a FTQ entry + entry = self._get_entry(self.exec_ptr) + executor_current_pc = self.executor.current_inst()[0] + self.exec_ptr += 1 + # print("Executing FTQ entry at pc %s" % hex(entry.pc)) + + # Prediction Block Hit + if entry.full_pred["hit"] and entry.pc == executor_current_pc: + # print("Prediction Block Hit") + + # Execute the prediction block + all_branches, redirect_addr, br_taken_mask = self._execute_this_pred_block(entry.pc, entry.full_pred) + # if redirect_addr is None: + # print("Predicition is correct") + # else: + # print("Prediction is wrong, redirect to %s" % hex(redirect_addr)) + new_ftb_entry = self._update_ftb_entry_from_branches(entry.pc, entry.ftb, all_branches, br_taken_mask) + self.update_queue.append((entry.pc, new_ftb_entry, br_taken_mask)) + if redirect_addr is not None: + self.redirect_queue.append((redirect_addr)) + + # Prediction Block Miss + else: + # print("Prediction Block Miss") + # if entry.pc != executor_current_pc: + # print("Target Error: actual: %s expected: %s" % (hex(entry.pc), hex(executor_current_pc))) + + # Create a new FTB entry and update & redirect + new_ftb_entry, br_taken_mask = self._generate_new_ftb_entry(executor_current_pc) + self.update_queue.append((executor_current_pc, new_ftb_entry, br_taken_mask)) + self.redirect_queue.append((self.executor.current_inst()[0])) + + def _generate_update_request(self, update_queue_item): + pc, new_ftb_entry, br_taken_mask = update_queue_item[0], update_queue_item[1], update_queue_item[2] + update_request = {} + + update_request["valid"] = True + update_request["bits_pc"] = pc + update_request["ftb_entry"] = new_ftb_entry.__dict__() + update_request["bits_br_taken_mask_0"] = 0 if len(br_taken_mask) == 0 else br_taken_mask[0] + update_request["bits_br_taken_mask_1"] = 0 if len(br_taken_mask) < 2 else br_taken_mask[1] + + return update_request + + def _generate_redirect_request(self, cfi_target): + redirect_request = {} + redirect_request["cfiUpdate"] = {} + redirect_request["cfiUpdate"]["target"] = cfi_target + + return redirect_request + + def _update_ftb_entry_from_branches(self, pc, ftb_entry, branches, br_taken_mask): + # update always_taken + if len(br_taken_mask) >= 1: + ftb_entry.always_taken[0] &= br_taken_mask[0] + if len(br_taken_mask) >= 2: + ftb_entry.always_taken[1] &= br_taken_mask[1] + + # update jmp target + for branch in branches: + if Executor.is_jump_inst(branch): + ftb_entry.tailSlot.lower = get_lower_addr(branch["target"], 20) + ftb_entry.tailSlot.tarStart = get_target_stat(pc >> 20, branch["target"] >> 20) + + return ftb_entry + + def _record_branch_helper(self, branch, cfi_addr, cfi_target): + if Executor.is_cond_branch_inst(branch): + correct = None + if branch["taken"]: + correct = cfi_addr is not None and branch["pc"] == cfi_addr + else: + correct = cfi_addr is None or branch["pc"] != cfi_addr + pred_stat.record_cond_branch(branch["pc"], correct) + else: + correct = cfi_addr is not None and branch["pc"] == cfi_addr and branch["target"] == cfi_target + pred_stat.record_jmp_branch(branch["pc"], PredictionStatistician.get_type(Executor.is_call_inst(branch), + Executor.is_ret_inst(branch), + Executor.is_jalr_inst(branch), + Executor.is_jal_inst(branch)), + correct) + + def _execute_this_pred_block(self, pc, full_pred): + end_pc = full_pred["fallThroughAddr"] + cfi_addr = get_cfi_addr_from_full_pred_dict(pc, full_pred) + cfi_target = get_target_from_full_pred_dict(pc, full_pred) + + all_branches = [] + br_taken_mask = [] + redirect_addr = None + while pc < end_pc: + _, inst_len, branch = self.executor.current_inst() + self.executor.next_inst() + if branch is not None: + br_taken_mask.append(branch["taken"]) + all_branches.append(branch) + self._record_branch_helper(branch, cfi_addr, cfi_target) + + pred_cfi_valid = cfi_addr is not None and pc == cfi_addr + exec_cfi_valid = branch is not None and branch["taken"] + pc += inst_len + + if pred_cfi_valid and exec_cfi_valid: + if cfi_target != branch["target"]: + redirect_addr = branch["target"] + break + elif pred_cfi_valid and not exec_cfi_valid: + redirect_addr = pc + break + elif not pred_cfi_valid and exec_cfi_valid: + redirect_addr = branch["target"] + break + + return all_branches, redirect_addr, br_taken_mask + + def _generate_new_ftb_entry(self, pc): + br_taken_mask = [] + ftb_entry = FTBEntry() + + fallthrough_addr = pc + while fallthrough_addr < pc + PREDICT_WIDTH_BYTES: + _, inst_len, branch = self.executor.current_inst() + + if branch is not None: + if Executor.is_cond_branch_inst(branch): + success = ftb_entry.add_cond_branch_inst(pc, branch["pc"], branch["taken"], branch["target"]) + br_taken_mask.append(branch["taken"]) + + if not success: + break + else: + pred_stat.record_cond_branch(branch["pc"], False) + self.executor.next_inst() + fallthrough_addr += inst_len + if branch["taken"]: + break + else: + success = ftb_entry.add_jmp_inst(pc, + branch["pc"], + branch["target"], + inst_len, + Executor.is_call_inst(branch), + Executor.is_ret_inst(branch), + Executor.is_jalr_inst(branch), + Executor.is_jal_inst(branch)) + if success: + pred_stat.record_jmp_branch(branch["pc"], PredictionStatistician.get_type(Executor.is_call_inst(branch), + Executor.is_ret_inst(branch), + Executor.is_jalr_inst(branch), + Executor.is_jal_inst(branch)), + False) + fallthrough_addr += 2 + self.executor.next_inst() + + break + else: + fallthrough_addr += inst_len + self.executor.next_inst() + + ftb_entry.valid = True + ftb_entry.pftAddr = get_pftaddr(fallthrough_addr) + ftb_entry.carry = get_pftaddr_carry(pc, fallthrough_addr) + + # print("Generate FTB Entry") + # ftb_entry.print(pc) + + return ftb_entry, br_taken_mask + + def _update_entries(self, bpu_out, ftb_entry): + if bpu_out["s1"]["valid"]: + # print("Add ftq entry (pc: %s)" % hex(bpu_out["s1"]["pc_3"])) + entry = self._get_entry(self.bpu_ptr) + entry.full_pred = bpu_out["s1"]["full_pred"] + entry.pc = bpu_out["s1"]["pc_3"] + entry.ftb = ftb_entry + self.bpu_ptr += 1 + + + +if __name__ == "__main__": + parser = Executor() + for _ in range (100): + print(parser.current_inst()) + parser.next_inst() + diff --git a/test_src/uFTB-with-ftq/uftb-env/tb.py b/test_src/uFTB-with-ftq/uftb-env/tb.py new file mode 100644 index 0000000..c27a7ba --- /dev/null +++ b/test_src/uFTB-with-ftq/uftb-env/tb.py @@ -0,0 +1,39 @@ +import mlvp + +from bundle import * +from bpu_top import * +from config import MAX_CYCLE + +import os +os.sys.path.append(DUT_PATH) + +from UT_FauFTB import * +uFTB = DUTFauFTB() +uFTB.init_clock("clock") + +def set_imm_mode(): + imm_mode = uFTB.io_s0_fire_0.xdata.Imme + need_to_write_imm = ["io_s0_fire_0", "io_s0_fire_1", "io_s0_fire_2", "io_s0_fire_3", + "io_s1_fire_0", "io_s2_fire_0", "io_in_bits_s0_pc_0", "io_in_bits_s0_pc_1", + "io_in_bits_s0_pc_2", "io_in_bits_s0_pc_3"] + for name in need_to_write_imm: + getattr(uFTB, name).xdata.SetWriteMode(imm_mode) + +set_imm_mode() + +async def uftb_test(): + uFTB_update = UpdateBundle.from_prefix(uFTB, "io_update_") + uFTB_out = BranchPredictionResp.from_prefix(uFTB, "io_out_") + pipeline_ctrl = PipelineCtrlBundle.from_prefix(uFTB, "io_") + enable_ctrl = EnableCtrlBundle.from_prefix(uFTB, "io_ctrl_") + + mlvp.create_task(mlvp.start_clock(uFTB)) + mlvp.create_task(BPUTop(uFTB, uFTB_out, uFTB_update, pipeline_ctrl, enable_ctrl).run()) + + await ClockCycles(uFTB, MAX_CYCLE) + +if __name__ == "__main__": + mlvp.run(uftb_test()) + uFTB.finalize() + + pred_stat.summary() diff --git a/test_src/uFTB-with-ftq/uftb-env/uftb_model.py b/test_src/uFTB-with-ftq/uftb-env/uftb_model.py new file mode 100644 index 0000000..23116cd --- /dev/null +++ b/test_src/uFTB-with-ftq/uftb-env/uftb_model.py @@ -0,0 +1,137 @@ +from mlvp.utils import PLRU, TwoBitsCounter +from ftb import * + +class uFTBWay: + def __init__(self): + self.valid = 0 + self.tag = 0 + self.ftb_entry = FTBEntry() + + @staticmethod + def get_tag(pc): + return pc >> INST_OFFSET_BITS & ((1 << UFTB_TAG_SIZE) - 1) + +class uFTBModel: + def __init__(self): + self.replacer = PLRU(UFTB_WAYS_NUM) + self.ftbways = [uFTBWay() for _ in range(UFTB_WAYS_NUM)] + self.counters = [[TwoBitsCounter(), TwoBitsCounter()] for _ in range(UFTB_WAYS_NUM)] + + # Update requests are used to update FTBways and counters. + self.update_queue = [] + + # The update queue of the replacement algorithm, and there are two channels, + # the first channel has a higher priority. + self.replacer_update_queue = [[], []] + + def update(self, update_request): + self.update_queue.append((update_request, 2, None)) + + def generate_output(self, s1_fire, s1_pc): + self._process_update() + if s1_fire: + hit_way = self._find_hit_way(s1_pc) + if hit_way is None: + return None + self.replacer_update_queue[0].append((hit_way, 1)) + + ftb_entry = self.ftbways[hit_way].ftb_entry + br_taken_mask = self._generate_br_taken_mask(hit_way) + + return ftb_entry, br_taken_mask, hit_way + + def print_all_ftb_ways(self): + for i in range(UFTB_WAYS_NUM): + print(f"way {i}: valid: {self.ftbways[i].valid}, tag: {hex(self.ftbways[i].tag << 1)}") + + def _generate_br_taken_mask(self, hit_way): + ftb_entry = self.ftbways[hit_way].ftb_entry + br_taken_mask = [self.counters[hit_way][0].get_prediction(), self.counters[hit_way][1].get_prediction()] + for i in range(2): + if ftb_entry.always_taken[i]: + br_taken_mask[i] = 1 + return br_taken_mask + + def _process_update(self): + # Update replacement algorithm + for i in range(2): + new_update_queue = [] + for j in range(len(self.replacer_update_queue[i])): + if self.replacer_update_queue[i][j][1] == 0: + self.replacer.update(self.replacer_update_queue[i][j][0]) + else: + new_update_queue.append((self.replacer_update_queue[i][j][0], self.replacer_update_queue[i][j][1] - 1)) + self.replacer_update_queue[i] = new_update_queue + + # Processing update requests + + # Find the item for the next cycle update to fit the dut hit mode + next_cycle_update_item = [] + for i in range(len(self.update_queue)): + selected_way = self.update_queue[i][2] + if self.update_queue[i][1] == 1: + if selected_way is None: + selected_way = self.replacer.get() + next_cycle_update_item.append((self.update_queue[i][0], selected_way)) + self.update_queue[i] = (self.update_queue[i][0], self.update_queue[i][1], selected_way) + self.replacer_update_queue[1].insert(0, (selected_way, 0)) + + # Update request processing + new_update_queue = [] + for i in range(len(self.update_queue)): + if self.update_queue[i][1] == 0: + self._update_all(self.update_queue[i][0], self.update_queue[i][2]) + else: + selected_way = self.update_queue[i][2] + if self.update_queue[i][1] == 2: + selected_way = self._find_hit_way(self.update_queue[i][0]['bits_pc']) + + for (update_request, way) in next_cycle_update_item: + if uFTBWay.get_tag(self.update_queue[i][0]['bits_pc']) == uFTBWay.get_tag(update_request["bits_pc"]): + if selected_way is None or way < selected_way: + selected_way = way + break + # print(f"Hit selected way is {selected_way}") + + new_update_queue.append((self.update_queue[i][0], self.update_queue[i][1] - 1, selected_way)) + self.update_queue = new_update_queue + + def _find_hit_way(self, pc): + tag = uFTBWay.get_tag(pc) + for i in range(UFTB_WAYS_NUM): + if self.ftbways[i].valid and self.ftbways[i].tag == tag: + return i + return None + + def _update_ftb_ways(self, update_request, selected_way): + if not update_request["valid"]: + return + + # print(f"ftb entry {hex(update_request['bits_pc'])} is put into way {selected_way}") + self.ftbways[selected_way].valid = 1 + self.ftbways[selected_way].tag = uFTBWay.get_tag(update_request["bits_pc"]) + self.ftbways[selected_way].ftb_entry = FTBEntry.from_dict(update_request["ftb_entry"]) + + def _update_counters(self, update_request, selected_way): + if not update_request["valid"]: + return + + need_to_update = [False, False] + brslot_valid = [update_request["ftb_entry"]["brSlots_0_valid"], update_request["ftb_entry"]["tailSlot_valid"] and update_request["ftb_entry"]["tailSlot_sharing"]] + br_taken_mask = [update_request["bits_br_taken_mask_0"], update_request["bits_br_taken_mask_1"]] + always_taken = [update_request["ftb_entry"]["always_taken_0"], update_request["ftb_entry"]["always_taken_1"]] + + cfi_pos = 0 if br_taken_mask[0] else (1 if br_taken_mask[1] else 2) + for i in range(2): + need_to_update[i] = i <= cfi_pos \ + and not always_taken[i] \ + and brslot_valid[i] + + for i in range(2): + if need_to_update[i]: + self.counters[selected_way][i].update(br_taken_mask[i]) + + def _update_all(self, update_request, selected_way): + self._update_ftb_ways(update_request, selected_way) + self._update_counters(update_request, selected_way) + diff --git a/test_src/uFTB-with-ftq/uftb-env/utils.py b/test_src/uFTB-with-ftq/uftb-env/utils.py new file mode 100644 index 0000000..1fe1fbd --- /dev/null +++ b/test_src/uFTB-with-ftq/uftb-env/utils.py @@ -0,0 +1,79 @@ +from config import * + +def get_slot_offset(pc, target): + return ((target - pc) >> INST_OFFSET_BITS) & ((1 << PREDICT_WIDTH_OFFSET_BITS) - 1) + +def get_slot_addr(pc, offset): + return pc + (offset << INST_OFFSET_BITS) + + +def get_pftaddr(target): + return (target >> INST_OFFSET_BITS) & ((1 << PREDICT_WIDTH_OFFSET_BITS) - 1) + +def get_pftaddr_carry(pc, target): + pc_higher = pc >> (INST_OFFSET_BITS + PREDICT_WIDTH_OFFSET_BITS) + target_higher = target >> (INST_OFFSET_BITS + PREDICT_WIDTH_OFFSET_BITS) + return (target_higher - pc_higher) & 1 + +def get_fallthrough_addr(pc, part_addr, carry): + higher = (pc >> (INST_OFFSET_BITS + PREDICT_WIDTH_OFFSET_BITS)) + carry + return (higher << (INST_OFFSET_BITS + PREDICT_WIDTH_OFFSET_BITS)) | (part_addr << INST_OFFSET_BITS) + + + +def get_lower_addr(pc, bits): + return (pc >> INST_OFFSET_BITS) & ((1 << bits) - 1) + +def get_target_stat(pc_higher, target_higher): + if target_higher < pc_higher: + return TAR_UDF + elif target_higher > pc_higher: + return TAR_OVF + else: + return TAR_FIT + +def get_target_addr(pc, target_stat, target_lower, target_lower_bits): + target_higher = pc >> (target_lower_bits + INST_OFFSET_BITS) + if target_stat == TAR_UDF: + target_higher -= 1 + elif target_stat == TAR_OVF: + target_higher += 1 + + return (target_higher << (target_lower_bits + INST_OFFSET_BITS)) | (target_lower << INST_OFFSET_BITS) + + +def get_cfi_addr_from_full_pred_dict(pc, d): + if not d["hit"]: + return None + elif d["slot_valids_0"] and d["br_taken_mask_0"]: + return get_slot_addr(pc, d["offsets_0"]) + elif d["slot_valids_1"] and d["br_taken_mask_1"] and d["is_br_sharing"]: + return get_slot_addr(pc, d["offsets_1"]) + elif d["slot_valids_1"] and not d["is_br_sharing"]: + return get_slot_addr(pc, d["offsets_1"]) + else: + return None + +def get_target_from_full_pred_dict(pc, d): + if not d["hit"]: + return pc + PREDICT_WIDTH_BYTES + elif d["slot_valids_0"] and d["br_taken_mask_0"]: + return d["targets_0"] + elif d["slot_valids_1"] and d["br_taken_mask_1"] and d["is_br_sharing"]: + return d["targets_1"] + elif d["slot_valids_1"] and not d["is_br_sharing"]: + # return d["jalr_target"] + return d["targets_1"] + else: + return d["fallThroughAddr"] + +def set_all_none_item_to_zero(d): + for k, v in d.items(): + if v is None: + d[k] = 0 + +def parse_uftb_meta(meta): + return { + "pred_way": meta >> 1, + "hit": meta & 1 + } diff --git a/utils/BRTParser/NemuBR.txt b/utils/BRTParser/NemuBR.txt new file mode 100644 index 0000000..672e1ac --- /dev/null +++ b/utils/BRTParser/NemuBR.txt @@ -0,0 +1 @@ +Place the generated NemuBR folder in this folder. diff --git a/utils/BRTParser/__init__.py b/utils/BRTParser/__init__.py new file mode 100644 index 0000000..4756afb --- /dev/null +++ b/utils/BRTParser/__init__.py @@ -0,0 +1,131 @@ +#!coding=utf8 + +import os +from .util import * +import time + +class BRTParser: + + def __init__(self, check_trace=True) -> None: + self.magic_head = b'\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe' + self.magic_tail = b'\xed\xed\xed\xed\xed\xed\xed\xed\xed\xed\xed\xed\xed\xed\xed\xed' + self.type_map = { + 101 : "C.J", + 102 : "C.JR", + 103 : "C.CALL", + 104 : "C.RET", + 105 : "C.JALR", + 201 : "P.JAL", + 203 : "P.CALL", + 204 : "P.RET", + 0 : "*.CBR", + 1 : "I.JAL", + 2 : "I.JALR", + 3 : "I.CALL", + 4 : "I.RET", + } + self.logger = get_logger(self.__class__.__name__) + self.check_trace = check_trace + self.clear() + + def clear(self): + self.branchs = {} + self.branchs_step = [] + self.statistics_type = {} + + def disable_check(self): + self.check_trace = False + + def enable_check(self): + self.check_trace = True + + def parse_data(self, data): + index = int.from_bytes(data[0:8], byteorder='little') + pc = int.from_bytes(data[8:16], byteorder='little') + target = int.from_bytes(data[16:24], byteorder='little') + taken = int.from_bytes(data[24:28], byteorder='little') > 0 + btype = int.from_bytes(data[28:32], byteorder='little') + if (btype not in self.type_map) and self.check_trace: + self.logger.warning("Find Unrecognized Type: %d" % btype) + btype = self.type_map.get(btype, "ERROR-%s"%btype) + key = pc + return key, index, pc, target, taken, btype + + def load(self, file): + if not os.path.isfile(file): + self.logger.error("file: %s not find!" % file) + return + self.logger.debug("Load file: %s"%file) + if not self.check_trace: + self.logger.warning("Trace check is disabled!") + time_start = time.time() + with open(file, "rb") as fp: + # read header + header = fp.read(16) + if(header != self.magic_head): + self.logger.error("file[%s] is not a branch/jump trace") + return + pre_pc = -1 + while True: + data = fp.read(32) + if(data == self.magic_tail): + break + key, index, pc, target, taken, btype = self.parse_data(data) + if pc < pre_pc and self.check_trace: + self.logger.warning("Detect disordered PC (0x%x => 0x%x) sequence; potentially indicating a corrupted trace file." % (pre_pc, pc)) + if taken: + pre_pc = target + if key not in self.branchs: + self.branchs[key] = {"pc": pc, "index": [index], "target": [target], "taken": [taken], "type": btype} + # statistic + if btype not in self.statistics_type: + self.statistics_type[btype] = {"count":1, "taken": int(taken), "notaken": int(not taken)} + else: + self.statistics_type[btype]["count"] += 1 + self.statistics_type[btype]["taken"] += int(taken) + self.statistics_type[btype]["notaken"] += int(not taken) + else: + self.branchs[key]["index"].append(index) + self.branchs[key]["target"].append(target) + self.branchs[key]["taken"].append(taken) + self.statistics_type[btype]["taken"] += int(taken) + self.statistics_type[btype]["notaken"] += int(not taken) + + self.branchs_step.append((index, self.branchs[key], len(self.branchs[key]["index"]) - 1)) + self.logger.debug("%d branchs (%d checks), loaded! time cost: %s"%(len(self.branchs), len(self.branchs_step), fmt_seconds(time.time() - time_start))) + + def fetch(self, file): + from . import NemuBR as nbr + nbr.br_monitor_init(["", "-b", file]) + while True: + data = nbr.br_monitor_get() + if data.index < 0: + return None + pc, index, target, taken, btype = data.pc, data.index, data.target, data.taken, data.type + if (btype not in self.type_map) and self.check_trace: + self.logger.warning("Find Unrecognized Type: %d" % btype) + btype = self.type_map.get(btype, "ERROR-%s"%btype) + if btype not in self.statistics_type: + self.statistics_type[btype] = {"count":1, "taken": int(taken), "notaken": int(not taken)} + else: + self.statistics_type[btype]["count"] += 1 + self.statistics_type[btype]["taken"] += int(taken) + self.statistics_type[btype]["notaken"] += int(not taken) + yield {"pc": pc, "index": index, "target": target, "taken": taken, "type": btype} + + def print_stat(self): + keys = self.statistics_type.keys() + print("\n%5s %8s %8s %8s %8s" % ("Index", "Type", "icount", "taken", "notaken")) + count, taken, notaken = 0, 0, 0 + all_cal, all_ret = 0, 0 + for i, k in enumerate(sorted(keys)): + data = self.statistics_type[k] + print("%5d %8s %8d %8d %8d" % (i, k, data["count"], data["taken"], data["notaken"])) + count += data["count"] + taken += data["taken"] + notaken += data["notaken"] + if ".RET" in k: + all_ret += data["taken"] + elif ".CALL" in k: + all_cal += data["taken"] + print("%5d %8s %8d %8d %8d (%d checks, ins.ret - ins.call = %d)\n" % (len(keys), "ALL", count, taken, notaken, taken + notaken, all_ret - all_cal)) diff --git a/utils/BRTParser/util/__init__.py b/utils/BRTParser/util/__init__.py new file mode 100644 index 0000000..adec476 --- /dev/null +++ b/utils/BRTParser/util/__init__.py @@ -0,0 +1,3 @@ +from .logger import * +from .functions import * + diff --git a/utils/BRTParser/util/functions.py b/utils/BRTParser/util/functions.py new file mode 100644 index 0000000..ac9a4d3 --- /dev/null +++ b/utils/BRTParser/util/functions.py @@ -0,0 +1,24 @@ + + +import sys +import time +from datetime import datetime + +def fmt_time(t = None, fmt="%Y-%m-%d %H:%M:%S"): + if t is None: + t = time.time() + return datetime.fromtimestamp(t).strftime(fmt) + +def fmt_seconds(t: float): + if t == 0: + return "0 seconds" + ret = "" + for d, f, u in [(60, " %.2f", "second"), (60, " %d", "minute"), (24, " %d", "hour"), (sys.maxsize, " %d", "day")]: + if t == 0: + break + v = t % d + t = t // d + ret = (f + " %s%s")%(v, u, "" if v == 1 else "s") + ret + return ret.strip() + + diff --git a/utils/BRTParser/util/logger.py b/utils/BRTParser/util/logger.py new file mode 100644 index 0000000..73b89d3 --- /dev/null +++ b/utils/BRTParser/util/logger.py @@ -0,0 +1,19 @@ +#coding=utf8 + +import logging +default_fmt="[%(asctime)s %(levelname)s %(filename)s:%(lineno)d] %(message)s" +default_level = logging.DEBUG + +log_warn = logging.WARNING +log_erro = logging.ERROR + +logging.basicConfig(format=default_fmt, level=default_level) + +def get_logger(name=None, level=default_level, fmt=None): + logger = logging.getLogger(name) + logger.setLevel(level) + if fmt is not None: + for h in logger.handlers: + h.setFormatter(logging.Formatter(fmt)) + return logger + diff --git a/utils/ready-to-run/coremark-2-iteration.bin b/utils/ready-to-run/coremark-2-iteration.bin new file mode 100755 index 0000000..22639f7 Binary files /dev/null and b/utils/ready-to-run/coremark-2-iteration.bin differ diff --git a/utils/ready-to-run/linux-0xa0000.bin b/utils/ready-to-run/linux-0xa0000.bin new file mode 100755 index 0000000..63a9732 Binary files /dev/null and b/utils/ready-to-run/linux-0xa0000.bin differ diff --git a/utils/ready-to-run/linux.bin b/utils/ready-to-run/linux.bin new file mode 100755 index 0000000..de0e9a3 Binary files /dev/null and b/utils/ready-to-run/linux.bin differ diff --git a/utils/ready-to-run/microbench.bin b/utils/ready-to-run/microbench.bin new file mode 100755 index 0000000..7f44bd2 Binary files /dev/null and b/utils/ready-to-run/microbench.bin differ