From fb92fffb28520439c11bf450b5cba1e81bc19f93 Mon Sep 17 00:00:00 2001 From: raojiayi Date: Sun, 27 Oct 2024 17:12:24 +0800 Subject: [PATCH] add ftb test --- tests/FTB/pytest.ini | 3 + .../env => FTB/src/ftb_env}/ftb.py | 40 ++- tests/FTB/src/ftb_env/ftb_wrapper.py | 191 +++++++++++ .../env/utils.py => FTB/src/ftb_utils.py} | 33 +- tests/FTB/tests/conftest.py | 14 + tests/FTB/tests/test_ftb_predict.py | 88 +++++ tests/FTB/tests/test_ftb_update.py | 131 ++++++++ tests/Makefile | 13 +- tests/conftest.py | 14 + tests/pytest.ini | 20 ++ tests/uFTB-raw/FTBEntry.py | 63 ---- tests/uFTB-raw/FauFTB.py | 203 ----------- tests/uFTB-raw/README.md | 58 ---- tests/uFTB-raw/test_raw.py | 62 ---- tests/uFTB-with-ftq/README.assets/env.png | Bin 38481 -> 0 bytes tests/uFTB-with-ftq/README.md | 113 ------- tests/uFTB-with-ftq/env/bpu_top.py | 221 ------------ tests/uFTB-with-ftq/env/bundle.py | 53 --- tests/uFTB-with-ftq/env/config.py | 26 -- tests/uFTB-with-ftq/env/executor.py | 106 ------ tests/uFTB-with-ftq/env/ftq.py | 315 ------------------ tests/uFTB-with-ftq/env/uftb_model.py | 138 -------- tests/uFTB-with-ftq/tests/test_with_ftq.py | 71 ---- 23 files changed, 539 insertions(+), 1437 deletions(-) create mode 100644 tests/FTB/pytest.ini rename tests/{uFTB-with-ftq/env => FTB/src/ftb_env}/ftb.py (83%) create mode 100644 tests/FTB/src/ftb_env/ftb_wrapper.py rename tests/{uFTB-with-ftq/env/utils.py => FTB/src/ftb_utils.py} (88%) create mode 100644 tests/FTB/tests/conftest.py create mode 100644 tests/FTB/tests/test_ftb_predict.py create mode 100644 tests/FTB/tests/test_ftb_update.py create mode 100644 tests/conftest.py create mode 100644 tests/pytest.ini delete mode 100644 tests/uFTB-raw/FTBEntry.py delete mode 100644 tests/uFTB-raw/FauFTB.py delete mode 100644 tests/uFTB-raw/README.md delete mode 100644 tests/uFTB-raw/test_raw.py delete mode 100644 tests/uFTB-with-ftq/README.assets/env.png delete mode 100644 tests/uFTB-with-ftq/README.md delete mode 100644 tests/uFTB-with-ftq/env/bpu_top.py delete mode 100644 tests/uFTB-with-ftq/env/bundle.py delete mode 100644 tests/uFTB-with-ftq/env/config.py delete mode 100644 tests/uFTB-with-ftq/env/executor.py delete mode 100644 tests/uFTB-with-ftq/env/ftq.py delete mode 100644 tests/uFTB-with-ftq/env/uftb_model.py delete mode 100644 tests/uFTB-with-ftq/tests/test_with_ftq.py diff --git a/tests/FTB/pytest.ini b/tests/FTB/pytest.ini new file mode 100644 index 0000000..641780c --- /dev/null +++ b/tests/FTB/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +pythonpath = ./src ../../out/picker_out_FTB ../../utils +testpaths = test \ No newline at end of file diff --git a/tests/uFTB-with-ftq/env/ftb.py b/tests/FTB/src/ftb_env/ftb.py similarity index 83% rename from tests/uFTB-with-ftq/env/ftb.py rename to tests/FTB/src/ftb_env/ftb.py index 50c00dc..81fac66 100644 --- a/tests/uFTB-with-ftq/env/ftb.py +++ b/tests/FTB/src/ftb_env/ftb.py @@ -1,4 +1,5 @@ -from .utils import * +from ftb_utils import * + class FTBSlot: def __init__(self): @@ -8,6 +9,10 @@ class FTBSlot: self.tarStart = 0 self.sharing = 0 + def print_slot(self): + print("valid ", self.valid, "offset ", self.offset, "lower ", self.lower, "tarStart ", self.tarStart, + "sharing ", self.sharing) + def __str__(self, pc, is_cond_branch): str = "" if not self.valid: @@ -79,11 +84,14 @@ class FTBEntry: 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["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) + # pdb.set_trace() + 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 @@ -94,7 +102,6 @@ class FTBEntry: 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, @@ -180,6 +187,21 @@ class FTBEntry: str += f"*\tlast_may_be_rvi_call: {self.last_may_be_rvi_call}, always_taken: {self.always_taken}\n" return str + def print_entry(self): + # pdb.set_trace() + print("valid", self.valid) + self.brSlot.print_slot() + self.tailSlot.print_slot() + print("pftAddr", self.pftAddr) + print("carry", self.carry) + print("isCall", self.isCall) + print("isRet", self.isRet) + print("isJal", self.isJal) + print("isJalr", self.isJalr) + print("last_may_be_rvi_call", self.last_may_be_rvi_call) + print("always_taken", self.always_taken) + + class FTBProvider(): def __init__(self): self.entries = {} @@ -187,9 +209,19 @@ class FTBProvider(): def update(self, update_request): if update_request["valid"]: self.entries[update_request["bits_pc"]] = FTBEntry.from_dict(update_request["ftb_entry"]) + # print("==========update provider ", hex(update_request["bits_pc"])) + # if self.entries[update_request["bits_pc"]] is not None: + # self.entries[update_request["bits_pc"]].print_entry() + # pdb.set_trace() + # print(self.entries[update_request["bits_pc"]]) def provide_ftb_entry(self, fire, pc): if fire and pc in self.entries: + # print("==========get provider ", hex(pc)) + # if self.entries[pc] is not None: + # self.entries[pc].print_entry() + # pdb.set_trace() + # print(self.entries[pc]) return self.entries[pc] else: return None diff --git a/tests/FTB/src/ftb_env/ftb_wrapper.py b/tests/FTB/src/ftb_env/ftb_wrapper.py new file mode 100644 index 0000000..5886706 --- /dev/null +++ b/tests/FTB/src/ftb_env/ftb_wrapper.py @@ -0,0 +1,191 @@ +import random + +from mlvp import Bundle, setup_logging, WriteMode +from mlvp.logger import ERROR + +from ftb_env.ftb import * + +setup_logging(ERROR) + + +class PipelineCtrlBundle(Bundle): + signals = ["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(Bundle): + signals = ["ubtb_enable", "btb_enable", "bim_enable", "tage_enable", + "sc_enable", "ras_enable", "loop_enable"] + + +class FTBEntryBundle(Bundle): + signals = ["brSlots_0_offset", "brSlots_0_lower", "brSlots_0_tarStat", "brSlots_0_sharing", "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", "valid"] + + +class UpdateBundle(Bundle): + signals = ["valid", "bits_pc", "bits_old_entry", "bits_meta"] + + def __init__(self): + super().__init__() + self.ftb_entry = FTBEntryBundle.from_prefix("bits_ftb_entry_") + + +class FullBranchPredirectionBundle(Bundle): + signals = ["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(Bundle): + signals = ["pc_3", "valid", "hasRedirect", "ftq_idx"] + + def __init__(self): + super().__init__() + self.full_pred = FullBranchPredirectionBundle.from_regex(r"full_pred_\d_(.*)") + + +class BranchPredictionResp(Bundle): + signals = ["last_stage_meta"] + + def __init__(self): + super().__init__() + self.s1 = BranchPredictionBundle.from_prefix("s1_") + self.s2 = BranchPredictionBundle.from_prefix("s2_") + self.s3 = BranchPredictionBundle.from_prefix("s3_") + self.last_stage_ftb_entry = FTBEntryBundle.from_prefix("last_stage_ftb_entry_") + + +class FTBWrapper(Bundle): + + def __init__(self, dut): + super().__init__() + self.dut = dut + + self.dut_out = BranchPredictionResp.from_prefix("io_out_").set_name("FTB_out").bind(self.dut) + self.dut_update = UpdateBundle.from_prefix("io_update_").set_name("FTB_update").bind(self.dut) + self.pipeline_ctrl = PipelineCtrlBundle.from_prefix("io_").set_name("pipeline_ctrl").bind(self.dut) + self.enable_ctrl = EnableCtrlBundle.from_prefix("io_ctrl_").set_name("enable_ctrl").bind(self.dut) + + self.ftb_provider = FTBProvider() + + self.enable_ctrl.btb_enable.value = 1 + + ##### reset ##### + self.dut.reset.value = 1 + self.dut.Step(10) + self.dut.reset.value = 0 + self.dut.Step(1000) + print("$$$ FINISH RESET !") + + self.pipeline_ctrl.s0_fire_0.value = 1 + self.pipeline_ctrl.s1_fire_0.value = 1 + self.pipeline_ctrl.s2_fire_0.value = 1 + self.pipeline_ctrl.s3_fire_0.value = 1 + + def predict(self, s0_pc, do_assert, do_hit): + # s0 + self.dut.io_update_valid.value = 0 + self.dut.io_in_bits_s0_pc_0.value = s0_pc + self.dut.Step(1) + # s1 + std_ftb_entry = self.ftb_provider.provide_ftb_entry(1, s0_pc) + std_full_pred = {} + if std_ftb_entry is not None: + std_ftb_entry.put_to_full_pred_dict(s0_pc, std_full_pred) + self.dut.Step(1) + # s2 + a = self.dut.io_in_bits_resp_in_0_s2_full_pred_0_br_taken_mask_0.value = random.randint(0, 1) + b = self.dut.io_in_bits_resp_in_0_s2_full_pred_0_br_taken_mask_1.value = random.randint(0, 1) + if std_ftb_entry is not None: + s2_mask_0 = std_full_pred["br_taken_mask_0"] or a + s2_mask_1 = std_full_pred["br_taken_mask_1"] or b + self.dut.Step(1) + if std_ftb_entry is not None and do_hit == 1: + assert (self.dut.io_out_s2_full_pred_0_hit.value == 1) + else: + assert (self.dut.io_out_s2_full_pred_0_hit.value == 0) + if do_assert == 1: + assert (s2_mask_0 == self.dut.io_out_s2_full_pred_0_br_taken_mask_0.value) + assert (s2_mask_1 == self.dut.io_out_s2_full_pred_0_br_taken_mask_1.value) + assert (std_full_pred["fallThroughAddr"] == self.dut.io_out_s2_full_pred_0_fallThroughAddr.value) + + # s3 + a = self.dut.io_in_bits_resp_in_0_s3_full_pred_0_br_taken_mask_0.value = random.randint(0, 1) + b = self.dut.io_in_bits_resp_in_0_s3_full_pred_0_br_taken_mask_1.value = random.randint(0, 1) + if std_ftb_entry is not None: + s3_mask_0 = std_full_pred["br_taken_mask_0"] or a + s3_mask_1 = std_full_pred["br_taken_mask_1"] or b + self.dut.Step(1) + if std_ftb_entry is not None and do_hit == 1: + assert (self.dut.io_out_s3_full_pred_0_hit.value == 1) + else: + assert (self.dut.io_out_s3_full_pred_0_hit.value == 0) + if do_assert == 1: + assert (s3_mask_0 == self.dut.io_out_s3_full_pred_0_br_taken_mask_0.value) + assert (s3_mask_1 == self.dut.io_out_s3_full_pred_0_br_taken_mask_1.value) + assert (std_full_pred["fallThroughErr"] == self.dut.io_out_s3_full_pred_0_fallThroughErr.value) + assert (std_full_pred["fallThroughAddr"] == self.dut.io_out_s3_full_pred_0_fallThroughAddr.value) + + def update(self, update_pc, meta_hit, update_valid, old_entry, entry_valid): + """ + 放入provider + """ + + self.dut.io_update_valid.value = update_valid + self.dut.io_update_bits_pc.value = update_pc + self.dut.io_update_bits_ftb_entry_valid.value = entry_valid + self.dut.io_update_bits_old_entry.value = old_entry + self.dut.io_update_bits_meta.value = meta_hit + a = self.dut.io_update_bits_ftb_entry_always_taken_0.value = random.randint(0, 1) + b = self.dut.io_update_bits_ftb_entry_always_taken_1.value = random.randint(0, 1) + h = self.dut.io_update_bits_ftb_entry_brSlots_0_lower.value = random.randint(0, 2 ** 12 - 1) + self.dut.io_update_bits_ftb_entry_brSlots_0_offset.value = 0 + self.dut.io_update_bits_ftb_entry_brSlots_0_sharing.value = 0 + e = self.dut.io_update_bits_ftb_entry_brSlots_0_tarStat.value = random.randint(0, 2) + self.dut.io_update_bits_ftb_entry_brSlots_0_valid.value = 1 + g = self.dut.io_update_bits_ftb_entry_tailSlot_sharing.value = random.randint(0, 1) + h = self.dut.io_update_bits_ftb_entry_tailSlot_lower.value = random.randint(0, + 2 ** 20 - 1) if g else random.randint( + 0, 2 ** 12 - 1) + self.dut.io_update_bits_ftb_entry_tailSlot_offset.value = 0 + f = self.dut.io_update_bits_ftb_entry_tailSlot_tarStat.value = random.randint(0, 2) + self.dut.io_update_bits_ftb_entry_tailSlot_valid.value = 1 + self.dut.io_update_bits_ftb_entry_pftAddr.value = 0 + d = self.dut.io_update_bits_ftb_entry_carry.value = random.randint(0, 1) + + update_request = {} + update_request["ftb_entry"] = {} + update_request["valid"] = update_valid + update_request["bits_pc"] = update_pc + update_request["ftb_entry"]["valid"] = entry_valid + update_request["ftb_entry"]["brSlots_0_offset"] = 0 + update_request["ftb_entry"]["brSlots_0_lower"] = h + update_request["ftb_entry"]["brSlots_0_tarStat"] = e + update_request["ftb_entry"]["brSlots_0_valid"] = 1 + update_request["ftb_entry"]["tailSlot_offset"] = 0 + update_request["ftb_entry"]["tailSlot_lower"] = h + update_request["ftb_entry"]["tailSlot_tarStat"] = f + update_request["ftb_entry"]["tailSlot_sharing"] = g + update_request["ftb_entry"]["tailSlot_valid"] = 1 + update_request["ftb_entry"]["pftAddr"] = 0 # + update_request["ftb_entry"]["carry"] = d + update_request["ftb_entry"]["isCall"] = 0 + update_request["ftb_entry"]["isRet"] = 0 + update_request["ftb_entry"]["isJalr"] = 0 + update_request["ftb_entry"]["last_may_be_rvi_call"] = 0 + update_request["ftb_entry"]["always_taken_0"] = a + update_request["ftb_entry"]["always_taken_1"] = b + update_request["bits_old_entry"] = old_entry + update_request["bits_meta"] = meta_hit + + self.ftb_provider.update(update_request) diff --git a/tests/uFTB-with-ftq/env/utils.py b/tests/FTB/src/ftb_utils.py similarity index 88% rename from tests/uFTB-with-ftq/env/utils.py rename to tests/FTB/src/ftb_utils.py index cc14a38..3a43e0a 100644 --- a/tests/uFTB-with-ftq/env/utils.py +++ b/tests/FTB/src/ftb_utils.py @@ -1,8 +1,24 @@ -from .config import * +INST_OFFSET_BITS = 1 +PREDICT_WIDTH_OFFSET_BITS = 4 + +PREDICT_WIDTH_BYTES = 32 + +UFTB_WAYS_NUM = 32 +UFTB_TAG_SIZE = 16 + +FTB_WAYS_NUM = 4 +FTB_TAG_SIZE = 20 +INDEX_BITS = 9 + +TAR_OVF = 1 +TAR_UDF = 2 +TAR_FIT = 0 + 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) @@ -10,20 +26,22 @@ def get_slot_addr(pc, offset): 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 @@ -32,6 +50,7 @@ def get_target_stat(pc_higher, target_higher): 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: @@ -54,6 +73,7 @@ def get_cfi_addr_from_full_pred_dict(pc, d): else: return None + def get_target_from_full_pred_dict(pc, d): if not d["hit"]: return pc + PREDICT_WIDTH_BYTES @@ -67,13 +87,22 @@ def get_target_from_full_pred_dict(pc, d): 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 } + + +def parse_ftb_meta(meta): + return { + "pred_way": meta >> 1, + "hit": meta & 1 + } diff --git a/tests/FTB/tests/conftest.py b/tests/FTB/tests/conftest.py new file mode 100644 index 0000000..6033702 --- /dev/null +++ b/tests/FTB/tests/conftest.py @@ -0,0 +1,14 @@ +import pytest +from mlvp.reporter import process_context, process_func_coverage + + +@pytest.hookimpl(trylast=True, optionalhook=True) +def pytest_reporter_context(context, config): + process_context(context, config) + + +@pytest.hookimpl(tryfirst=True, hookwrapper=True) +def pytest_runtest_makereport(item, call): + outcome = yield + report = outcome.get_result() + return process_func_coverage(item, call, report) diff --git a/tests/FTB/tests/test_ftb_predict.py b/tests/FTB/tests/test_ftb_predict.py new file mode 100644 index 0000000..29bccc4 --- /dev/null +++ b/tests/FTB/tests/test_ftb_predict.py @@ -0,0 +1,88 @@ +import os + +import mlvp.funcov as fc +import pytest +from mlvp.reporter import set_func_coverage, set_line_coverage + +from UT_FTB import DUTFTB, xspcomm +from ftb_env import ftb_wrapper + +g = fc.CovGroup("test_ftb_pred") ### + + +def init_ftb_pins(func_name): + waveform_dir = "report/FTB/fst" + coverage_dir = "report/FTB/cov_dat" + if not os.path.exists(coverage_dir): + os.makedirs(coverage_dir, exist_ok=True) + if not os.path.exists(waveform_dir): + os.makedirs(waveform_dir, exist_ok=True) + dut = DUTFTB( + ["+verilator+seed+19198107"], + waveform_filename=os.path.join(waveform_dir, f"FTB-{func_name}.vcd"), + coverage_filename=os.path.join(coverage_dir, f"FTB-{func_name}.dat"), + ) + dut.InitClock("clock") + dut.Step() + while dut.io_s1_ready.value == 0: + dut.Step() + return ftb_wrapper.FTBWrapper(dut) + + +def finish_ftb_pins(request, group, func_name): + set_func_coverage(request, group) + coverage_file = f"report/FTB/cov_dat/FTB-{func_name}.dat" + if not os.path.exists(coverage_file): + raise FileNotFoundError(f"File not found: {coverage_file}") + set_line_coverage(request, coverage_file) + + +@pytest.fixture() +def ftb_fixture(request): + func_name = request.node.name + ftb_pins = init_ftb_pins(func_name) + yield ftb_pins + ftb_pins.dut.Finish() + finish_ftb_pins(request, [g], func_name) + g.clear() + + +def test_ftb_pred_result(ftb_fixture): + g.add_watch_point(ftb_fixture.dut.io_out_s2_full_pred_0_hit, {"s2_hit": fc.Eq(1), "s2_not_hit": fc.Eq(0)}, + name="s2_hit") + g.add_watch_point(ftb_fixture.dut.io_out_s2_full_pred_0_br_taken_mask_0, + {"s2_mask_0_is_1": fc.Eq(1), "s2_mask_0_is_0": fc.Eq(0)}, name="s2_mask_0") + g.add_watch_point(ftb_fixture.dut.io_out_s2_full_pred_0_br_taken_mask_1, + {"s2_mask_1_is_1": fc.Eq(1), "s2_mask_1_is_0": fc.Eq(0)}, name="s2_mask_1") + g.add_watch_point(ftb_fixture.dut.io_out_s2_full_pred_0_targets_0, {"s2_targets_0": fc.Ne(0), }, + name="s2_targets_0") + g.add_watch_point(ftb_fixture.dut.io_out_s2_full_pred_0_targets_1, {"s2_targets_1": fc.Ne(0), }, + name="s2_targets_1") + g.add_watch_point(ftb_fixture.dut.io_out_s2_full_pred_0_jalr_target, {"s2_jalr_target": fc.Ne(0), }, + name="s2_jalr_target") + g.add_watch_point(ftb_fixture.dut.io_out_s2_full_pred_0_fallThroughAddr, {"s2_fallThroughAddr": fc.Ne(0), }, + name="s2_fallThroughAddr") + g.add_watch_point(ftb_fixture.dut.io_out_s3_full_pred_0_hit, {"s3_hit": fc.Eq(1), "s3_not_hit": fc.Eq(0)}, + name="s3_hit") + g.add_watch_point(ftb_fixture.dut.io_out_s3_full_pred_0_br_taken_mask_0, + {"s3_mask_0_is_1": fc.Eq(1), "s3_mask_0_is_0": fc.Eq(0)}, name="s3_mask_0") + g.add_watch_point(ftb_fixture.dut.io_out_s3_full_pred_0_br_taken_mask_1, + {"s3_mask_1_is_1": fc.Eq(1), "s3_mask_1_is_0": fc.Eq(0)}, name="s3_mask_1") + g.add_watch_point(ftb_fixture.dut.io_out_s3_full_pred_0_targets_0, {"s3_targets_0": fc.Ne(0), }, + name="s3_targets_0") + g.add_watch_point(ftb_fixture.dut.io_out_s3_full_pred_0_targets_1, {"s3_targets_1": fc.Ne(0), }, + name="s3_targets_1") + g.add_watch_point(ftb_fixture.dut.io_out_s3_full_pred_0_jalr_target, {"s3_jalr_target": fc.Ne(0), }, + name="s3_jalr_target") + g.add_watch_point(ftb_fixture.dut.io_out_s3_full_pred_0_fallThroughAddr, {"s3_fallThroughAddr": fc.Ne(0), }, + name="s3_fallThroughAddr") + g.add_watch_point(ftb_fixture.dut.io_out_s3_full_pred_0_fallThroughErr, + {"s3_fallThroughErr": fc.Eq(1), "s3_not_fallThroughErr": fc.Eq(0)}, name="s3_fallThroughErr") + g.add_watch_point(ftb_fixture.dut.io_out_last_stage_meta, {"meta=0": fc.Eq(0), "meta=1": fc.Eq(1)}, + name="last_stage_meta") + ftb_fixture.dut.xclock.StepRis(lambda _: g.sample()) + for i in range(1000): + ftb_fixture.update(0x80000000 + i * 32, 0, 1, 0, 1) + ftb_fixture.dut.Step(10) + ftb_fixture.predict(0x80000000 + i * 32, 1, 1) + ftb_fixture.dut.Step(10) diff --git a/tests/FTB/tests/test_ftb_update.py b/tests/FTB/tests/test_ftb_update.py new file mode 100644 index 0000000..90a0b0b --- /dev/null +++ b/tests/FTB/tests/test_ftb_update.py @@ -0,0 +1,131 @@ +import os +import mlvp +import random +from datetime import datetime + +import mlvp.funcov as fc +import pytest +from mlvp.reporter import set_func_coverage, set_line_coverage + +from UT_FTB import DUTFTB +from ftb_env import ftb_wrapper + +g = fc.CovGroup("test_ftb_update") ### + + +def init_ftb_pins(func_name): + waveform_dir = "report/FTB/fst" + coverage_dir = "report/FTB/cov_dat" + if not os.path.exists(coverage_dir): + os.makedirs(coverage_dir, exist_ok=True) + if not os.path.exists(waveform_dir): + os.makedirs(waveform_dir, exist_ok=True) + + random.seed(datetime.now().timestamp() * 100000) + v = 1919810 + random.randint(7, 114514) + dut = DUTFTB( + [f"+verilator+seed+{v}"], + waveform_filename=os.path.join(waveform_dir, f"FTB-{func_name}.fst"), + coverage_filename=os.path.join(coverage_dir, f"FTB-{func_name}.dat"), + ) + dut.InitClock("clock") + dut.Step() + while dut.io_s1_ready.value == 0: + dut.Step() + return ftb_wrapper.FTBWrapper(dut) + + +def finish_ftb_pins(request, group, func_name): + set_func_coverage(request, group) + coverage_file = f"report/FTB/cov_dat/FTB-{func_name}.dat" + if not os.path.exists(coverage_file): + raise FileNotFoundError(f"File not found: {coverage_file}") + set_line_coverage(request, coverage_file) + + +@pytest.fixture() +def ftb_fixture(request): + func_name = request.node.name + ftb_pins = init_ftb_pins(func_name) + yield ftb_pins + ftb_pins.dut.Finish() + finish_ftb_pins(request, [g], func_name) + g.clear() + + +def test_ftb_meta_hit_is_1(ftb_fixture): + g.add_watch_point(ftb_fixture.dut.io_out_s2_full_pred_0_hit, {"s2_hit=1": fc.Eq(1)}, name="meta_hit_1_s2") + g.add_watch_point(ftb_fixture.dut.io_out_s3_full_pred_0_hit, {"s3_hit=1": fc.Eq(1)}, name="meta_hit_1_s3") + ftb_fixture.dut.xclock.StepRis(lambda _: g.sample()) + for i in range(1000): + ftb_fixture.update(0x80000000 + i * 32, 1, 1, 0, 1) + ftb_fixture.dut.Step(1) + ftb_fixture.predict(0x80000000 + i * 32, 0, 1) + ftb_fixture.dut.Step(10) + + +def test_ftb_meta_hit_is_0(ftb_fixture): + g.add_watch_point(ftb_fixture.dut.io_out_s2_full_pred_0_hit, {"s2_hit=1": fc.Eq(1)}, name="meta_hit_0_s2") + g.add_watch_point(ftb_fixture.dut.io_out_s3_full_pred_0_hit, {"s3_hit=1": fc.Eq(1)}, name="meta_hit_0_s3") + ftb_fixture.dut.xclock.StepRis(lambda _: g.sample()) + for i in range(1000): + ftb_fixture.update(0x80000000 + i * 32, 0, 1, 0, 1) + ftb_fixture.dut.Step(3) ## + ftb_fixture.predict(0x80000000 + i * 32, 0, 1) + ftb_fixture.dut.Step(10) + + +def test_ftb_update_queue(ftb_fixture): + g.add_watch_point(ftb_fixture.dut.io_out_s2_full_pred_0_hit, {"update_queue_s2": fc.Eq(1)}, name="update_queue_s2") + g.add_watch_point(ftb_fixture.dut.io_out_s3_full_pred_0_hit, {"update_queue_s3": fc.Eq(1)}, name="update_queue_s3") + ftb_fixture.dut.xclock.StepRis(lambda _: g.sample()) + for i in range(100): + ftb_fixture.update(0x80000000 + i * 32, 0, 1, 0, 1) + ftb_fixture.dut.Step(1) + for i in range(100): + ftb_fixture.predict(0x80000000 + i * 32, 0, 1) + ftb_fixture.dut.Step(10) + + +def test_ftb_update_valid(ftb_fixture): + g.add_watch_point(ftb_fixture.dut.io_out_s2_full_pred_0_hit, {"update_valid_s2": fc.Eq(0)}, name="update_valid_s2") + g.add_watch_point(ftb_fixture.dut.io_out_s3_full_pred_0_hit, {"update_valid_s3": fc.Eq(0)}, name="update_valid_s3") + ftb_fixture.dut.xclock.StepRis(lambda _: g.sample()) + for i in range(1000): + ftb_fixture.update(0x80000000 + i * 32, 0, 0, 0, 1) + ftb_fixture.dut.Step(10) + ftb_fixture.predict(0x80000000 + i * 32, 0, 0) + ftb_fixture.dut.Step(10) + + +def test_ftb_old_entry(ftb_fixture): + g.add_watch_point(ftb_fixture.dut.io_out_s2_full_pred_0_hit, {"old_entry_s2": fc.Eq(0)}, name="old_entry_s2") + g.add_watch_point(ftb_fixture.dut.io_out_s3_full_pred_0_hit, {"old_entry_s3": fc.Eq(0)}, name="old_entry_s3") + ftb_fixture.dut.xclock.StepRis(lambda _: g.sample()) + for i in range(1000): + ftb_fixture.update(0x80000000 + i * 32, 0, 1, 1, 1) + ftb_fixture.dut.Step(10) + ftb_fixture.predict(0x80000000 + i * 32, 0, 0) + ftb_fixture.dut.Step(10) + + +def test_ftb_entry_valid(ftb_fixture): + g.add_watch_point(ftb_fixture.dut.io_out_s2_full_pred_0_hit, {"entry_valid_s2": fc.Eq(0)}, name="entry_valid_s2") + g.add_watch_point(ftb_fixture.dut.io_out_s3_full_pred_0_hit, {"entry_valid_s3": fc.Eq(0)}, name="entry_valid_s3") + ftb_fixture.dut.xclock.StepRis(lambda _: g.sample()) + for i in range(1000): + ftb_fixture.update(0x80000000 + i * 32, 0, 1, 0, 0) + ftb_fixture.dut.Step(10) + ftb_fixture.predict(0x80000000 + i * 32, 0, 0) + ftb_fixture.dut.Step(10) + + +def test_ftb_cover(ftb_fixture): + g.add_watch_point(ftb_fixture.dut.io_out_s2_full_pred_0_hit, {"cover_s2": fc.Eq(0)}, name="cover_s2") + g.add_watch_point(ftb_fixture.dut.io_out_s3_full_pred_0_hit, {"cover_s3": fc.Eq(0)}, name="cover_s3") + ftb_fixture.dut.xclock.StepRis(lambda _: g.sample()) + for i in range(1000): + ftb_fixture.update(0x80000000 + i * 0b10, 0, 1, 0, 1) + ftb_fixture.dut.Step(10) + ftb_fixture.predict(0x80000000 + i * 0b10, 0, 1) + ftb_fixture.dut.Step(10) diff --git a/tests/Makefile b/tests/Makefile index bbaf223..d2d5eea 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,9 +1,18 @@ TEST=. TEST_FOLDER=./$(TEST) + PYTHON=python3 +START_CODE="from mlvp.reporter import *;\ +set_meta_info('test_case', '$(TEST)');\ +report = 'report/$(TEST)/report.html';\ +generate_pytest_report(report, args=['-s', '-n=auto', '$(TEST_FOLDER)'], );\ +" + run: - pytest --mlvp-report -n=auto -sv $(TEST_FOLDER) + @echo "Running test $(TEST)..." + @mkdir report/$(TEST) -p + @$(PYTHON) -c $(START_CODE) clean: - rm -rf reports/ + rm -rf report/ *.fst *.dat *.log *.hier diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..6033702 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,14 @@ +import pytest +from mlvp.reporter import process_context, process_func_coverage + + +@pytest.hookimpl(trylast=True, optionalhook=True) +def pytest_reporter_context(context, config): + process_context(context, config) + + +@pytest.hookimpl(tryfirst=True, hookwrapper=True) +def pytest_runtest_makereport(item, call): + outcome = yield + report = outcome.get_result() + return process_func_coverage(item, call, report) diff --git a/tests/pytest.ini b/tests/pytest.ini new file mode 100644 index 0000000..26f2dd2 --- /dev/null +++ b/tests/pytest.ini @@ -0,0 +1,20 @@ +[pytest] +pythonpath = + ../utils + ./FTB/src + ../out/picker_out_FTB + ./uFTB/src + ../out/picker_out_uFTB + ./TAGE-SC/src + ../out/picker_out_TageSC + ./ITTAGE/src + ../out/picker_out_ITTAGE + ./RAS/src + ../out/picker_out_RAS + +testpaths = + ./FTB/tests + ./uFTB/tests + ./TAGE-SC/tests + ./ITTAGE/tests + ./RAS/tests diff --git a/tests/uFTB-raw/FTBEntry.py b/tests/uFTB-raw/FTBEntry.py deleted file mode 100644 index 9ee996b..0000000 --- a/tests/uFTB-raw/FTBEntry.py +++ /dev/null @@ -1,63 +0,0 @@ -from types import SimpleNamespace -import random - - -class FTBEntry: - - - class BrSlot: - valid=False - offset=0 - target=0 - targetCoA=0 - alwaysTaken=False - - class TailSlot: - valid=False - offset=0 - target=0 - targetCoA=0 - alwaysTaken=False - is_br_sharing=False - - def __init__(self): - self.valid = False - self.brSlot = self.BrSlot() - self.tailSlot = self.TailSlot() - - self.pftAddr = 0 - self.carry = False - self.isCall = False - self.isRet = False - self.isJalr = False - self.rviCall = False - - self.brSlot.valid = False - self.tailSlot.valid = False - - -def gen_ftb_entry(pc, br_slotv, tail_slotv) -> FTBEntry: - entry = FTBEntry() - entry.valid = True - if br_slotv: - entry.brSlot.valid = True - entry.brSlot.target = random.randint(0, 2**20) - entry.brSlot.offset = random.randint(0, 2**12) - entry.brSlot.alwaysTaken = random.choice([True, False]) - if tail_slotv: - entry.tailSlot.valid = True - entry.tailSlot.target = random.randint(0, 2**20) - entry.tailSlot.offset = random.randint(0, 2**12) - entry.tailSlot.is_br_sharing = random.choice([True, False]) - entry.pftAddr = pc + random.randint(0, 2**12) - if tail_slotv and entry.tailSlot.is_br_sharing == False: - flag = random.randint(0, 3) - if flag == 0: - entry.isCall = True - elif flag == 1: - entry.isRet = True - elif flag == 2: - entry.isJalr = True - else: - entry.rviCall = True - return entry diff --git a/tests/uFTB-raw/FauFTB.py b/tests/uFTB-raw/FauFTB.py deleted file mode 100644 index a793c14..0000000 --- a/tests/uFTB-raw/FauFTB.py +++ /dev/null @@ -1,203 +0,0 @@ -import os -ROOT_PATH = os.path.dirname(os.path.abspath(__file__)) + "/../.." -DUT_PATH = ROOT_PATH + "/out/picker_out_uFTB" -os.sys.path.append(DUT_PATH) - -from typing import Any, Tuple -from UT_FauFTB import * -from FTBEntry import * -from collections import namedtuple - - -class FauFTB(DUTFauFTB): - - class Io_in_bits_s0_pc: - - def __init__(self, outer_instance: "FauFTB"): - self.outer_instance = outer_instance - for i in range(4): - setattr(self, f"_{i}", getattr(outer_instance, f"io_in_bits_s0_pc_{i}")) - - def set(self, arg): - for i in range(4): - getattr(self, f"_{i}").value = arg - - class Io_out_s1_pc: - - def __init__(self, outer_instance: "FauFTB"): - self.outer_instance = outer_instance - for i in range(4): - setattr(self, f"_{i}", getattr(outer_instance, f"io_out_s1_pc_{i}")) - - def get(self): - assert all(self._0.value == getattr(self, f"_{i}").value for i in range(4)) - return self._0 - - class Io_s0_fire: - - def __init__(self, outer_instance: "FauFTB"): - self.outer_instance = outer_instance - for i in range(4): - setattr(self, f"_{i}", getattr(outer_instance, f"io_s0_fire_{i}")) - - def set(self,arg): - for i in range(4): - getattr(self, f"_{i}").value = arg - - class Io_out_s1_full_pred: - - def __init__(self, outer_instance: "FauFTB"): - self.outer_instance = outer_instance - self.sig_array = [ - "br_taken_mask_0", - "br_taken_mask_1", - "slot_valids_0", - "slot_valids_1", - "targets_0", - "targets_1", - "offsets_0", - "offsets_1", - "fallThroughAddr", - "is_br_sharing", - "hit", - ] - for i in range(4): - setattr( - self, - f"_{i}", - namedtuple(f"_{i}", self.sig_array)( - *[ - getattr(outer_instance, f"io_out_s1_full_pred_{i}_{sig}") - for sig in self.sig_array - ] - ), - ) - - def get(self): - for sig in self.sig_array: - assert all( - getattr(self, f"_{i}")._asdict()[sig].value - == getattr(self, f"_{i}")._asdict()[sig].value - for i in range(4) - ) - return self._0 - - class Io_out_last_stage_meta: - - def __init__(self, outer_instance: "FauFTB"): - self.outer_instance = outer_instance - - self.resp_meta_hit_r_1 = xsp.XPin( - outer_instance.io_out_last_stage_meta.xdata.SubDataRef( - 0, - 0, - "resp_meta_hit_r_1" - ), - outer_instance.event, - ) - - self.resp_meta_pred_way_r_1 = xsp.XPin( - outer_instance.io_out_last_stage_meta.xdata.SubDataRef( - 1, - 5, - "resp_meta_pred_way_r_1" - ), - outer_instance.event, - ) - - pass - - pass - - class Io_update_bits_ftb_entry: - - def __init__(self, outer_instance: "FauFTB"): - self.outer_instance = outer_instance - self.sig_array = [ - "brSlots_0_valid", - "brSlots_0_offset", - "brSlots_0_lower", - "brSlots_0_tarStat", - "always_taken_0", - "tailSlot_valid", - "tailSlot_offset", - "tailSlot_lower", - "tailSlot_tarStat", - "tailSlot_sharing", - "always_taken_1", - "pftAddr", - "carry", - ] - namedtuple( - "Io_update_bits_ftb_entry", - self.sig_array, - )( - *[ - setattr( - self, - sig, - getattr(outer_instance, f"io_update_bits_ftb_entry_{sig}"), - ) - for sig in self.sig_array - ] - ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.io_in_bits_s0_pc = self.Io_in_bits_s0_pc(self) - self.io_out_s1_pc = self.Io_out_s1_pc(self) - self.io_s0_fire = self.Io_s0_fire(self) - self.io_out_s1_full_pred = self.Io_out_s1_full_pred(self) - self._io_out_last_stage_meta = self.Io_out_last_stage_meta(self) - self.io_update_bits_ftb_entry = self.Io_update_bits_ftb_entry(self) - - def check_dup_equation(self, *inputs): - return all(i == inputs[0] for i in inputs) - - def s1_full_pred(self) -> Tuple[int, FTBEntry, int, int]: - io_out_s1_full_pred = self.io_out_s1_full_pred.get() - - entry = FTBEntry() - - # FauFTB - entry.valid = io_out_s1_full_pred.hit.value - entry.pftAddr = io_out_s1_full_pred.fallThroughAddr.value - # Br slot - entry.brSlot.valid = io_out_s1_full_pred.slot_valids_0.value - entry.brSlot.offset = io_out_s1_full_pred.offsets_0.value - entry.brSlot.target = io_out_s1_full_pred.targets_0.value - entry.brSlot.targetCoA = None - entry.brSlot.alwaysTaken = io_out_s1_full_pred.br_taken_mask_0.value - # Tail slot - entry.tailSlot.valid = io_out_s1_full_pred.slot_valids_1.value - entry.tailSlot.offset = io_out_s1_full_pred.offsets_1.value - entry.tailSlot.target = io_out_s1_full_pred.targets_1.value - entry.tailSlot.targetCoA = None - entry.tailSlot.alwaysTaken = io_out_s1_full_pred.br_taken_mask_1.value - entry.tailSlot.is_br_sharing = io_out_s1_full_pred.is_br_sharing.value - - return (self.io_out_s1_pc.get().value, entry, io_out_s1_full_pred.br_taken_mask_0.value, io_out_s1_full_pred.br_taken_mask_1.value) - - def update_ftb_entry(self, update_pc: int, entry: FTBEntry, taken: Tuple[int, int]): - - self.io_update_bits_pc.value = update_pc - self.io_update_bits_br_taken_mask_0.value = taken[0] - self.io_update_bits_br_taken_mask_1.value = taken[1] - - update = self.io_update_bits_ftb_entry - - update.brSlots_0_valid.value = entry.brSlot.valid - update.brSlots_0_offset.value = entry.brSlot.offset - update.brSlots_0_lower.value = entry.brSlot.target - update.brSlots_0_tarStat.value = entry.brSlot.targetCoA - update.always_taken_0.value = entry.brSlot.alwaysTaken - - update.tailSlot_valid.value = entry.tailSlot.valid - update.tailSlot_offset.value = entry.tailSlot.offset - update.tailSlot_lower.value = entry.tailSlot.target - update.tailSlot_tarStat.value = entry.tailSlot.targetCoA - update.tailSlot_sharing.value = entry.tailSlot.is_br_sharing - update.always_taken_1.value = entry.tailSlot.alwaysTaken - - update.pftAddr.value = entry.pftAddr - update.carry.value = entry.carry diff --git a/tests/uFTB-raw/README.md b/tests/uFTB-raw/README.md deleted file mode 100644 index 8f27646..0000000 --- a/tests/uFTB-raw/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# uFTB-raw - -## 介绍 - -本测试用例提供了基于模拟随机数据的香山处理器 uFTB 分支预测器的仿真验证环境,用于验证 uFTB 的缓存功能和预测功能。 - -为此,我们为 uFTB 提供了简易的 uFTB Wrapper,以向 uFTB 提供时序控制和输入输出处理。具体而言,我们提供了三种操作: - -1. 生成数据队列:由于uFTB实际上可以被是为FTB表项的一个缓存,所以我们将传递的信息封装为 FTBEntry,以便于 uFTB 的使用。而 FTBEntry 本身是一组受约束的数据,因此只要随机数据符合结果即可。 -2. 读取操作:uFTB 会根据传递的信息,从自身缓存的 FTB 表项中读取预测结果。因为我们的测试用例是随机生成的,所以我们不需要真实的预测结果,只需要保证 uFTB 的读取操作正确即可。 -3. 更新操作:uFTB 会根据传递的信息,更新自身缓存的 FTB 表项。只要更新生效,即可认为 uFTB 的更新操作正确。 - -对读取和更新操作,我们将对Pin接口的操作封装为 `get_pred` 和 `set_update` 方法,以便于 uFTB 的使用。 -同时对原始的 uFTB 模型进行了封装,由于存在很多重复的接口,我们使用 python 的元编程机制,将重复的接口合并,以减少对pin接口操作时的代码量。 - - -## 快速使用 - -### 环境配置 - -**1. 安装 mlvp** - -具体步骤参见 https://github.com/XS-MLVP/mlvp - -**2. 编译 DUT** - -在本仓库根目录下执行 - -```shell -make uftb TL=python -``` - -即可生成 DUT 编译结果,编译结果无需移动,程序会自动检索对应目录。 - -### 仿真验证 - -在 `tests` 目录下执行 - -```shell -make TEST=uFTB-raw run -``` - -即可开始仿真验证。 - -程序运行结束后,会生成对应的波形文件及覆盖率报告。波形文件位于 `tests/report/uFTB-raw` 目录下,覆盖率报告位于 `tests/report/uFTB-raw.html`。 - - -## 使用说明 - -### 目录结构 - -```bash -uFTB-raw # 测试用例名称 -|-- FTBEntry.py # 针对 FTB 表项的封装 -|-- FauFTB.py # 针对 uFTB 的二次封装,将重复信号合并,并恢复信号结构体层次 -|-- README.md -`-- test_raw.py # 测试入口,用于驱动随机数据生成、uFTB 操作 -``` diff --git a/tests/uFTB-raw/test_raw.py b/tests/uFTB-raw/test_raw.py deleted file mode 100644 index 3143a18..0000000 --- a/tests/uFTB-raw/test_raw.py +++ /dev/null @@ -1,62 +0,0 @@ -from typing import List, Tuple -from FauFTB import * -from FTBEntry import * - -EntryList: List[Tuple[int, FTBEntry, bool, bool]] = [] - - -def ftb_entry_list(): - for i in range(10000): - pc = random.randint(0, 2**39) - gentry = gen_ftb_entry(pc, True, True) - EntryList.append( - (pc, gentry, random.choice([True, False]), random.choice([True, False])) - ) - # print("ftb_entry_list", EntryList[i][1].brSlot) - return EntryList - - -def get_pred(uFTB: FauFTB, pc: int) -> Tuple[int, FTBEntry, bool, bool]: - uFTB.io_in_bits_s0_pc.set(pc) - uFTB.io_ctrl_ubtb_enable.value = 1 - uFTB.io_s0_fire.set(1) - uFTB.io_s1_fire_0.value = 1 - uFTB.io_s2_fire_0.value = 1 - return uFTB.s1_full_pred() - - -def set_update(uFTB: FauFTB, entry: Tuple[int, FTBEntry, bool, bool]): - uFTB.io_update_valid.value = True - uFTB.io_update_bits_pc.value = entry[0] - # print("set_update", entry[1].brSlot) - uFTB.update_ftb_entry(entry[0], entry[1], (entry[2], entry[3])) - - -from mlvp import * -import mlvp.funcov as fc -from mlvp.reporter import * - - -def test_raw(mlvp_request): - uFTB: FauFTB = mlvp_request - - ftb_entry_list() - uFTB.reset.value = 1 - uFTB.Step(100) - uFTB.reset.value = 0 - - for i in range(10000): - # print("main1", EntryList[i - 10][1].brSlot) - j = i - pred = get_pred(uFTB, EntryList[j][0]) - if i > 9: - # print("main2", EntryList[i - 10][1].brSlot) - set_update(uFTB, EntryList[(i - 10)%10000]) # update data 10 cycles ago - pass - print("main", pred[0], pred[1].__dict__) - uFTB.Step(1) - -import pytest -@pytest.fixture() -def mlvp_request(mlvp_pre_request: PreRequest): - return mlvp_pre_request.create_dut(FauFTB, "clock") diff --git a/tests/uFTB-with-ftq/README.assets/env.png b/tests/uFTB-with-ftq/README.assets/env.png deleted file mode 100644 index 58da9939915590a98553cb8fc777d984464dbd3e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38481 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 JRCDjde*-YzvC04d diff --git a/tests/uFTB-with-ftq/README.md b/tests/uFTB-with-ftq/README.md deleted file mode 100644 index 013fa41..0000000 --- a/tests/uFTB-with-ftq/README.md +++ /dev/null @@ -1,113 +0,0 @@ -# uFTB-env - -## 介绍 - -本测试用例提供了基于真实指令流的香山处理器 uFTB 分支预测器的仿真验证环境,以及时钟精确的 uFTB 参考模型,最终可给出 uFTB 的分支预测准确率。 - -为此,我们为 uFTB 提供了简易的 BPU Top Wrapper,以向 uFTB 提供时序控制和输入输出处理。并向 BPU Top 提供了简易的 FTQ 实现,FTQ 中实例化了一个真实的程序仿真器,用于生成真实指令流,FTQ 会处理 BPU 产生的预测结果,并向 BPU 提供更新请求与重定向请求的执行反馈。大致的结构可参考下图: - -![env](README.assets/env.png) - -$\color{red}{对于香山 BPU 中其他子预测器的验证,可复用本项目中的真实指令执行环境,但需要对 DUT 的接口、时序以及需要使用的预测结果通道等进行适配。}$ - -## 快速使用 - -### 环境配置 - -**1. 安装 mlvp** - -具体步骤参见 https://github.com/XS-MLVP/mlvp - -**2. 生成 BRTParser Trace 工具** - -为了生成真实的指令流,BRTParser 作为一个自定义的工具已经被放置在仓库根目录下的 `utils` 文件夹中,但其中缺少了模拟器仿真程序,需要自行编译生成,具体步骤参见 https://github.com/yaozhicheng/NEMU - -生成编译结果 `NemuBR` 后,将其放置在 `utils/BRTParser` 目录下,工具即可正常使用。 - -**3. 编译 DUT** - -在本仓库根目录下执行 - -```shell -make uftb TL=python -``` - -即可生成 DUT 编译结果,编译结果无需移动,程序会自动检索对应目录。 - -### 仿真验证 - -在 `tests` 目录下执行 - -```shell -make TEST=uFTB-with-ftq run -``` - -即可开始仿真验证。 - -程序运行结束后,会打印出分支预测的统计信息。 - -若要更改需要执行的程序,可在 `config.py` 中更改相应变量的值,仿真所需的真实程序已经放置仓库 `utils/ready-to-run` 目录下。若要更改仿真所持续的周期数,可在 `config.py` 中更改 `MAX_CYCLE` 的值。 - -如果需要启用随机trace,可以设置环境变量 RANDOM_BPT=1 -```shell -RANDOM_BPT=1 make TEST=uFTB-with-ftq run -``` - -## 使用说明 - -### 目录结构 - -``` -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 # 相关工具函数 -``` - -### 指令执行器 - -`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/tests/uFTB-with-ftq/env/bpu_top.py b/tests/uFTB-with-ftq/env/bpu_top.py deleted file mode 100644 index 1d95d97..0000000 --- a/tests/uFTB-with-ftq/env/bpu_top.py +++ /dev/null @@ -1,221 +0,0 @@ -from mlvp import * -from mlvp.triggers import * -from .bundle import * -from .ftq import * -from .uftb_model import uFTBModel - -def assert_equal(a, b): - if a != b: - error(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.as_dict() - 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 - - debug("-" * 30) - if self.s1_fire: - # Debug Imformation - debug("[BPU]") - debug(f"New prediction at {hex(self.s1_pc)}") - if bpu_output["s1"]["full_pred"]["hit"]: - debug("Dut Hit") - - debug("FTB Entry in pred result: ") - if bpu_output["s1"]["full_pred"]["hit"]: - debug(ftb_entry.__str__(self.s1_pc)) - else: - debug("No FTB Entry") - debug(f"br_taken_mask: {bpu_output['s1']['full_pred']['br_taken_mask_0']}, {bpu_output['s1']['full_pred']['br_taken_mask_1']}") - - debug("FTB Entry in uFTB Model: ") - if model_output: - debug(model_output[0].__str__(self.s1_pc)) - debug(f"br_taken_mask: {model_output[1]}") - else: - debug("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/tests/uFTB-with-ftq/env/bundle.py b/tests/uFTB-with-ftq/env/bundle.py deleted file mode 100644 index 9313775..0000000 --- a/tests/uFTB-with-ftq/env/bundle.py +++ /dev/null @@ -1,53 +0,0 @@ -from mlvp import Bundle - -class PipelineCtrlBundle(Bundle): - signals = ["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(Bundle): - signals = ["ubtb_enable", "btb_enable", "bim_enable", "tage_enable", - "sc_enable", "ras_enable", "loop_enable"] - - -class FTBEntryBundle(Bundle): - signals = ["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(Bundle): - signals = ["valid", "bits_pc", "bits_br_taken_mask_0", "bits_br_taken_mask_1"] - - def __init__(self): - super().__init__() - self.ftb_entry = FTBEntryBundle.from_prefix("bits_ftb_entry_") - -class FullBranchPredirectionBundle(Bundle): - signals = ["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(Bundle): - signals = ["pc_3", "valid", "hasRedirect", "ftq_idx"] - - def __init__(self): - super().__init__() - self.full_pred = FullBranchPredirectionBundle.from_regex(r"full_pred_\d_(.*)") - - -class BranchPredictionResp(Bundle): - signals = ["last_stage_meta"] - - def __init__(self): - super().__init__() - self.s1 = BranchPredictionBundle.from_prefix("s1_") - self.s2 = BranchPredictionBundle.from_prefix("s2_") - self.s3 = BranchPredictionBundle.from_prefix("s3_") - self.last_stage_ftb_entry = FTBEntryBundle.from_prefix("last_stage_ftb_entry_") diff --git a/tests/uFTB-with-ftq/env/config.py b/tests/uFTB-with-ftq/env/config.py deleted file mode 100644 index db3235e..0000000 --- a/tests/uFTB-with-ftq/env/config.py +++ /dev/null @@ -1,26 +0,0 @@ -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/tests/uFTB-with-ftq/env/executor.py b/tests/uFTB-with-ftq/env/executor.py deleted file mode 100644 index bc6dcd3..0000000 --- a/tests/uFTB-with-ftq/env/executor.py +++ /dev/null @@ -1,106 +0,0 @@ -from .config import * - -import os -os.sys.path.append(UTILS_PATH) - -from BRTParser import BRTParser, RandomBPTTrace - -class Executor: - """Get program real execution instruction flow.""" - - def __init__(self, filename, reset_vector=0x80000000): - if str(os.getenv("RANDOM_BPT")).lower() in ["1", "true"]: - self._executor = RandomBPTTrace().gen(start_address=reset_vector, pc_range_size=100_000) - else: - 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): - # When current pc < next branch pc, force to execute next branch - 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/tests/uFTB-with-ftq/env/ftq.py b/tests/uFTB-with-ftq/env/ftq.py deleted file mode 100644 index b452e55..0000000 --- a/tests/uFTB-with-ftq/env/ftq.py +++ /dev/null @@ -1,315 +0,0 @@ -from mlvp import * -from random import random -from .bundle import * -from .config import * -from .utils import * -from .executor import Executor -from .ftb import * - -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): - summary_str = "" - summary_str += "=" * 30 + "\n" - summary_str += "Summary\n" - summary_str += "[Conditional Branches]\n" - 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()]) - summary_str += f"Total: {cond_branches_total}, Correct: {cond_branches_correct}, Accuracy: {cond_branches_correct / max(1,cond_branches_total)}\n" - - for pc, record in self.cond_branches_list.items(): - summary_str += f"PC: {hex(pc)}\tTotal: {record[0]}\tCorrect: {record[1]}\tAccuracy: {record[1] / record[0]}\n" - - summary_str += "[Jump Branches]\n" - 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()]) - summary_str += f"Total: {jmp_branches_total}, Correct: {jmp_branches_correct}, Accuracy: {jmp_branches_correct / max(1,jmp_branches_total)}\n" - for pc, record in self.jmp_branches_list.items(): - summary_str += f"PC: {hex(pc)}\tType: {record[0]}\tTotal: {record[1]}\tCorrect: {record[2]}\tAccuracy: {record[2] / max(1,record[1])}\n" - - summary_str += "[All Branches]\n" - total = cond_branches_total + jmp_branches_total - correct = cond_branches_correct + jmp_branches_correct - summary_str += f"Total: {total}, Correct: {correct}, Accuracy: {correct /max(1, total)}\n" - - info(summary_str) - - @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)) - debug(f"Send Update Request: {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) - debug("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 - debug("Executing FTQ entry at pc %s" % hex(entry.pc)) - - # Prediction Block Hit - if entry.full_pred["hit"] and entry.pc == executor_current_pc: - debug("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: - debug("Predicition is correct") - else: - debug("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: - debug("Prediction Block Miss") - if entry.pc != executor_current_pc: - debug("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) - - debug("Generate FTB Entry") - debug(ftb_entry.__str__(pc)) - - return ftb_entry, br_taken_mask - - def _update_entries(self, bpu_out, ftb_entry): - if bpu_out["s1"]["valid"]: - debug("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/tests/uFTB-with-ftq/env/uftb_model.py b/tests/uFTB-with-ftq/env/uftb_model.py deleted file mode 100644 index 0d140a7..0000000 --- a/tests/uFTB-with-ftq/env/uftb_model.py +++ /dev/null @@ -1,138 +0,0 @@ -from mlvp.utils import PLRU, TwoBitsCounter -from mlvp import * -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): - debug(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 - debug(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 - - debug(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/tests/uFTB-with-ftq/tests/test_with_ftq.py b/tests/uFTB-with-ftq/tests/test_with_ftq.py deleted file mode 100644 index 36f7ef7..0000000 --- a/tests/uFTB-with-ftq/tests/test_with_ftq.py +++ /dev/null @@ -1,71 +0,0 @@ -import mlvp -import pytest -from mlvp.triggers import * - -import os -os.sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/..") - -from env.bundle import * -from env.bpu_top import * -from env.config import * - -os.sys.path.append(DUT_PATH) - -from UT_FauFTB import * - -def set_imm_mode(uFTB): - 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) - - -@pytest.mark.mlvp_async -async def test_uftb(mlvp_request): - uFTB = mlvp_request - set_imm_mode(uFTB) - - uFTB_update = UpdateBundle.from_prefix("io_update_").set_name("uFTB_update").bind(uFTB) - uFTB_out = BranchPredictionResp.from_prefix("io_out_").set_name("uFTB_out").bind(uFTB) - pipeline_ctrl = PipelineCtrlBundle.from_prefix("io_").set_name("pipeline_ctrl").bind(uFTB) - enable_ctrl = EnableCtrlBundle.from_prefix("io_ctrl_").set_name("enable_ctrl").bind(uFTB) - - mlvp.start_clock(uFTB) - mlvp.create_task(BPUTop(uFTB, uFTB_out, uFTB_update, pipeline_ctrl, enable_ctrl).run()) - - await ClockCycles(uFTB, MAX_CYCLE) - - pred_stat.summary() - -import mlvp.funcov as fc -from mlvp.reporter import * -from mlvp import PreRequest - -@pytest.fixture() -def mlvp_request(mlvp_pre_request: PreRequest): - mlvp.setup_logging(mlvp.INFO) - uFTB = mlvp_pre_request.create_dut(DUTFauFTB, "clock") - - # Set Coverage - g1 = fc.CovGroup("interaction") - g1.add_watch_point(uFTB.reset, { "reset": fc.Eq(1), }, name="reset") - g1.add_watch_point(uFTB.io_ctrl_ubtb_enable, { "ubtb_enable": fc.Eq(1), }, name="ubtb_enable") - g1.add_watch_point(uFTB.io_s0_fire_0, { "s0_fire": fc.Eq(1), }, name="s0_fire_0") - g1.add_watch_point(uFTB.io_s1_fire_0, { "s1_fire": fc.Eq(1), }, name="s1_fire_0") - g1.add_watch_point(uFTB.io_s2_fire_0, { "s2_fire": fc.Eq(1), }, name="s2_fire_0") - g1.add_watch_point(uFTB.io_update_valid, { "update_valid": fc.Eq(1), }, name="update_valid") - - g2 = fc.CovGroup("ftb_entry") - g2.add_watch_point(uFTB.io_out_s1_full_pred_0_hit, { "hit": fc.Eq(1), "not_hit": fc.Eq(0) }, name="s1_full_pred_0_hit") - g2.add_watch_point(uFTB.io_out_s1_full_pred_3_fallThroughErr, { "fallThroughErr": fc.Eq(1), "not_fallThroughErr": fc.Eq(0) }, name="s1_full_pred_3_fallThroughErr") - g2.add_watch_point(uFTB.io_out_s1_full_pred_0_slot_valids_0, { "slot_valids_0": fc.Eq(1), "slot_valids_0_invalid": fc.Eq(0) }, name="s1_full_pred_0_slot_valids_0") - g2.add_watch_point(uFTB.io_out_s1_full_pred_0_slot_valids_1, { "slot_valids_1": fc.Eq(1), "slot_valids_1_invalid": fc.Eq(0) }, name="s1_full_pred_0_slot_valids_1") - g2.add_watch_point(uFTB.io_out_s1_full_pred_0_br_taken_mask_0, { "br_taken_mask_0": fc.Eq(1), "br_taken_mask_0_invalid": fc.Eq(0) }, name="s1_full_pred_0_br_taken_mask_0") - g2.add_watch_point(uFTB.io_out_s1_full_pred_0_br_taken_mask_1, { "br_taken_mask_1": fc.Eq(1), "br_taken_mask_1_invalid": fc.Eq(0) }, name="s1_full_pred_0_br_taken_mask_1") - g2.add_watch_point(uFTB.io_out_s1_full_pred_0_is_br_sharing, { "is_br_sharing": fc.Eq(1), "is_br_sharing_invalid": fc.Eq(0) }, name="s1_full_pred_0_is_br_sharing") - - mlvp_pre_request.add_cov_groups([g1, g2]) - - return uFTB