forked from XS-MLVP/UnityChipForXiangShan
Compare commits
64 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
c062796ac5 | |
|
|
432bdafe20 | |
|
|
5c937d47cf | |
|
|
242f46d868 | |
|
|
34f514e8b7 | |
|
|
fe37344753 | |
|
|
755f40dca5 | |
|
|
75bc6e2b07 | |
|
|
061bd5c3e6 | |
|
|
8d196d2eb2 | |
|
|
b2d32274a6 | |
|
|
e0974aa19e | |
|
|
1d18e71c0c | |
|
|
b5dc2a3bfc | |
|
|
aeb5754b48 | |
|
|
95fe6cd142 | |
|
|
176c7c6000 | |
|
|
e902377722 | |
|
|
8071df2882 | |
|
|
ad55fa4273 | |
|
|
b7cbfe8e36 | |
|
|
001256b1bb | |
|
|
5896eb0684 | |
|
|
7888562df4 | |
|
|
bc90036680 | |
|
|
bcfbe7afca | |
|
|
4c1ee51fc4 | |
|
|
4f2df2f48d | |
|
|
e09e35d212 | |
|
|
71b4962d68 | |
|
|
9a016ef125 | |
|
|
27bbc9ed04 | |
|
|
2bceb6b638 | |
|
|
1281054fee | |
|
|
bf698b6861 | |
|
|
0733627830 | |
|
|
d3ea62888a | |
|
|
6294600944 | |
|
|
2fe7098fc6 | |
|
|
b70a7171d7 | |
|
|
167df40b52 | |
|
|
59460cfdc0 | |
|
|
5c2f4df801 | |
|
|
0536646a2a | |
|
|
f6057a282c | |
|
|
0c723d86f7 | |
|
|
a30b9eae7c | |
|
|
649faaed13 | |
|
|
04e95823c3 | |
|
|
0183b43290 | |
|
|
78582e48f0 | |
|
|
873b483d54 | |
|
|
4c25d694f7 | |
|
|
502b2c0f51 | |
|
|
d354b0077c | |
|
|
ba7c8a02b2 | |
|
|
fee7bc61cb | |
|
|
cd4e5c5e9e | |
|
|
904f80bc9a | |
|
|
73c0bf672f | |
|
|
039e7f65fc | |
|
|
ca353a3373 | |
|
|
162ce67810 | |
|
|
b0bf1aab18 |
|
|
@ -15,6 +15,9 @@ rtl/*
|
|||
documents/static/data/*
|
||||
!documents/static/data/README.txt
|
||||
|
||||
# Auto-generated mapping file
|
||||
.dirmap.autogen
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
|
|
@ -170,4 +173,4 @@ cython_debug/
|
|||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
#.idea/
|
||||
|
|
|
|||
56
Makefile
56
Makefile
|
|
@ -1,17 +1,11 @@
|
|||
.PHONY: dut rtl check_dut
|
||||
.PHONY: dut rtl
|
||||
|
||||
export PYTHONPATH := $(shell pwd):$(shell picker --show_xcom_lib_location_python):$(PYTHONPATH)
|
||||
export PYTHONPATH := $(shell pwd):$(shell picker --show_xcom_lib_location_python | tail -n 1):$(PYTHONPATH)
|
||||
export ENABLE_XINFO := 0
|
||||
export CFG := ""
|
||||
export DUTS := "*"
|
||||
export REPORT := "--toffee-report"
|
||||
|
||||
comma:= ,
|
||||
empty:=
|
||||
space:= $(empty) $(empty)
|
||||
|
||||
PROCESSED_DUTS := $(subst $(comma),$(space),$(strip $(DUTS)))
|
||||
|
||||
all: rtl dut test_all
|
||||
|
||||
update_python_deps:
|
||||
|
|
@ -27,51 +21,23 @@ clean_dut:
|
|||
clean_rtl:
|
||||
cd rtl && ls | grep -v README.md | xargs rm -rf
|
||||
|
||||
test_all: check_all_dut
|
||||
test_all:
|
||||
@python3 run.py --config $(CFG) $(KV) -- $(REPORT) -vs ut_*/ $(args)
|
||||
|
||||
check_all_dut:
|
||||
@python3 run.py --config $(CFG) --build $(DUTS) $(args)
|
||||
test_all_fast:
|
||||
@python3 run.py --config $(CFG) --no-waveform --no-code-cov $(KV) -- -vs ut_*/ $(args)
|
||||
|
||||
test: check_dut
|
||||
@python3 run.py --config $(CFG) $(KV) -- $(REPORT) -vs $(target) $(args)
|
||||
test_without_waveform:
|
||||
@python3 run.py --config $(CFG) --no-waveform $(KV) -- $(REPORT) -vs $(target) $(args)
|
||||
|
||||
check_dut:
|
||||
@if [ -n "$(target)" ]; then \
|
||||
for t in $(target); do \
|
||||
CLEANED_TARGET=$$(echo "$$t" | sed 's/\/$$//'); \
|
||||
MATCHED_LINE=$$(grep ".* --> .* --> $$CLEANED_TARGET" dir_map.f | head -1); \
|
||||
if [ -n "$$MATCHED_LINE" ]; then \
|
||||
DUT_NAME=$$(echo "$$MATCHED_LINE" | awk -F' --> ' '{print $$1}'); \
|
||||
DUT_DIR=dut/$$(echo "$$MATCHED_LINE" | awk -F' --> ' '{print $$2}'); \
|
||||
if [ ! -d "$$DUT_DIR" ]; then \
|
||||
echo "Building missing DUT for target $$t: $$DUT_NAME"; \
|
||||
$(MAKE) dut DUTS="$$DUT_NAME"; \
|
||||
fi; \
|
||||
else \
|
||||
echo "No DUT mapping found for target: $$t, skipping..." >&2; \
|
||||
fi; \
|
||||
done; \
|
||||
fi
|
||||
test:
|
||||
@python3 run.py --config $(CFG) $(KV) -- $(REPORT) -vs $(args) $(target)
|
||||
|
||||
dut: rtl
|
||||
@if [ "$(PROCESSED_DUTS)" = "*" ]; then \
|
||||
$(MAKE) clean_dut; \
|
||||
else \
|
||||
for d in $(PROCESSED_DUTS); do \
|
||||
dir=$$(awk -F' --> ' -v dut="$$d" '$$1 == dut {print $$2; exit}' dir_map.f); \
|
||||
if [ -z "$$dir" ]; then \
|
||||
echo "No mapping found for '$$d' in dir_map.f, skipping deletion" >&2; \
|
||||
continue; \
|
||||
fi; \
|
||||
echo "Cleaning dut/$$dir"; \
|
||||
rm -rf "dut/$$dir"; \
|
||||
done; \
|
||||
fi
|
||||
dut:
|
||||
@python3 run.py --config $(CFG) --build $(DUTS) $(args)
|
||||
|
||||
rtl:
|
||||
@python3 run.py --config $(CFG) --download-rtl $(args)
|
||||
@python3 run.py --config $(CFG) --download-rtl $(args)
|
||||
|
||||
doc:
|
||||
cd documents && hugo server --bind 0.0.0.0
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#coding=utf8
|
||||
#***************************************************************************************
|
||||
# coding=utf8
|
||||
# ***************************************************************************************
|
||||
# This project is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
|
|
@ -10,19 +10,12 @@
|
|||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
#
|
||||
# See the Mulan PSL v2 for more details.
|
||||
#**************************************************************************************/
|
||||
# **************************************************************************************/
|
||||
|
||||
|
||||
from .functions import *
|
||||
from .cfg import *
|
||||
from .logger import *
|
||||
from .constants import *
|
||||
from .functions import *
|
||||
from .logger import *
|
||||
from .result import *
|
||||
|
||||
|
||||
def init(cfg=None):
|
||||
if not cfg:
|
||||
cfg = get_config()
|
||||
init_log(cfg)
|
||||
|
||||
init()
|
||||
from .export_dut import picker_export
|
||||
146
comm/cfg.py
146
comm/cfg.py
|
|
@ -1,146 +0,0 @@
|
|||
#coding=utf8
|
||||
#***************************************************************************************
|
||||
# This project is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
#
|
||||
# See the Mulan PSL v2 for more details.
|
||||
#**************************************************************************************/
|
||||
|
||||
|
||||
from .logger import debug
|
||||
import yaml
|
||||
import os
|
||||
import ast
|
||||
import pytest
|
||||
|
||||
|
||||
global_cfg = None
|
||||
|
||||
|
||||
class CfgObject:
|
||||
_freeze = False
|
||||
def __init__(self, data: dict) -> None:
|
||||
if data:
|
||||
for key, value in data.items():
|
||||
key = key.replace("-", "_")
|
||||
if isinstance(value, dict):
|
||||
super().__setattr__(key, CfgObject(value))
|
||||
else:
|
||||
super().__setattr__(key, value)
|
||||
|
||||
def freeze(self):
|
||||
self._freeze = True
|
||||
for _, value in self.__dict__.items():
|
||||
if isinstance(value, CfgObject):
|
||||
value.freeze()
|
||||
|
||||
def unfreeze(self):
|
||||
self._freeze = False
|
||||
for _, value in self.__dict__.items():
|
||||
if isinstance(value, CfgObject):
|
||||
value.unfreeze()
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if self._freeze and name != "_freeze":
|
||||
raise AttributeError("Cannot set attribute %s in freeze mode" % name)
|
||||
super().__setattr__(name, value)
|
||||
|
||||
def as_dict(self):
|
||||
data = {}
|
||||
for key, value in self.__dict__.items():
|
||||
if key == "_freeze":
|
||||
continue
|
||||
if isinstance(value, CfgObject):
|
||||
data[key] = value.as_dict()
|
||||
else:
|
||||
data[key] = value
|
||||
return data
|
||||
|
||||
def __str__(self):
|
||||
return str(self.as_dict())
|
||||
|
||||
|
||||
def get_config(cfg=None):
|
||||
if cfg is not None:
|
||||
return cfg
|
||||
global global_cfg
|
||||
if global_cfg is not None:
|
||||
return global_cfg
|
||||
import pytest
|
||||
cfg_str = getattr(pytest, "toffee_custom_key_value", {}).get("global_unitychip_cfg")
|
||||
if cfg_str:
|
||||
global_cfg = cfg_from_str(cfg_str)
|
||||
global_cfg.freeze()
|
||||
return global_cfg
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def config():
|
||||
return get_config()
|
||||
|
||||
|
||||
def init_cfg(cfg_file=None, args=[], cfg_str_data=None):
|
||||
from .functions import merge_dict
|
||||
if cfg_str_data:
|
||||
return cfg_from_str(cfg_str_data)
|
||||
default_cfg = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
"../configs/_default.yaml")
|
||||
default_cfg = os.path.abspath(default_cfg)
|
||||
debug("load default config from %s", default_cfg)
|
||||
with open(default_cfg, 'r') as f:
|
||||
cfg = yaml.safe_load(f)
|
||||
cfg["__file__"] = default_cfg
|
||||
if cfg_file:
|
||||
with open(cfg_file, 'r') as f:
|
||||
cfg = merge_dict(cfg, yaml.safe_load(f))
|
||||
cfg["__file__"] = os.path.abspath(cfg_file)
|
||||
for arg in args:
|
||||
tmp_cfg = cfg
|
||||
if "=" in arg:
|
||||
key, value = arg.split("=")
|
||||
keys = key.split(".")
|
||||
for k in keys[:-1]:
|
||||
if k not in tmp_cfg:
|
||||
tmp_cfg = None
|
||||
break
|
||||
tmp_cfg = tmp_cfg[k]
|
||||
if tmp_cfg:
|
||||
k = keys[-1]
|
||||
if k not in tmp_cfg:
|
||||
debug(f"Key {key} not found in config, ignore")
|
||||
else:
|
||||
try:
|
||||
tmp_value = ast.literal_eval(value)
|
||||
except Exception as e:
|
||||
assert False, f"Invalid value for {key}: {value}, error: {e}"
|
||||
assert type(tmp_cfg[k]) == type(tmp_value), f"Type mismatch for {key}: {type(tmp_cfg[k])} != {type(tmp_value)}"
|
||||
tmp_cfg[k] = tmp_value
|
||||
return CfgObject(cfg)
|
||||
|
||||
|
||||
def cfg_as_str(cfg: CfgObject):
|
||||
from .functions import base64_encode
|
||||
return base64_encode(yaml.dump(cfg.as_dict(), default_flow_style=False))
|
||||
|
||||
|
||||
def cfg_from_str(cfg_str):
|
||||
from .functions import base64_decode
|
||||
return CfgObject(yaml.safe_load(base64_decode(cfg_str)))
|
||||
|
||||
|
||||
def dump_cfg(cfg: CfgObject = None, cfg_file=None):
|
||||
from .functions import time_format, get_out_dir
|
||||
if not cfg_file:
|
||||
cfg_file = os.path.join(get_out_dir(), "config-%s.yaml" % time_format())
|
||||
debug(f"Dump config to {cfg_file}")
|
||||
if cfg is None:
|
||||
cfg = get_config()
|
||||
assert cfg is not None, "cfg is not initialized"
|
||||
with open(cfg_file, 'w') as f:
|
||||
f.write(yaml.dump(cfg.as_dict(), default_flow_style=False))
|
||||
|
|
@ -0,0 +1 @@
|
|||
from .model import *
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
__all__ = ['TemplateString']
|
||||
|
||||
import os
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
|
||||
from pydantic import AfterValidator
|
||||
from typing_extensions import Annotated
|
||||
|
||||
|
||||
def replace_default_vars(input_str: str) -> str:
|
||||
"""Replaces predefined placeholder variables in a string.
|
||||
|
||||
Substitutes placeholders like `%{time}`, `%{pid}`, `%{host}`, `%{root}`,
|
||||
`%{gitag}`, and `%{giturl}` with their corresponding dynamic values.
|
||||
|
||||
Args:
|
||||
input_str (str): The string containing placeholder variables.
|
||||
|
||||
Returns:
|
||||
str: The string with placeholders replaced.
|
||||
"""
|
||||
from ..functions.git import get_git_tag, get_git_url_with_commit
|
||||
from ..functions.utils import get_root_dir, time_format
|
||||
|
||||
# Define variable mapping table with lazy evaluation
|
||||
variable_resolvers: dict[str, Callable[[], str]] = {
|
||||
'time': lambda: time_format(fmt="%Y%m%d%H%M%S"),
|
||||
'pid': lambda: str(os.getpid()),
|
||||
'host': lambda: os.uname().nodename,
|
||||
'root': lambda: get_root_dir(),
|
||||
'gitag': lambda: get_git_tag(),
|
||||
'giturl': lambda: get_git_url_with_commit(),
|
||||
}
|
||||
|
||||
# Use regex to find all placeholders at once
|
||||
def replace_match(match):
|
||||
var_name = match.group(1)
|
||||
if var_name in variable_resolvers:
|
||||
return variable_resolvers[var_name]()
|
||||
return match.group(0) # Keep original if variable doesn't exist
|
||||
|
||||
# Use regex to replace all matching placeholders
|
||||
return re.sub(r'%\{([^}]+)}', replace_match, input_str)
|
||||
|
||||
|
||||
TemplateString: type[str] = Annotated[str, AfterValidator(replace_default_vars)]
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
__all__ = [
|
||||
"RTLConfig",
|
||||
"TestConfig",
|
||||
"OutputConfig",
|
||||
"ReportConfig",
|
||||
"ReportInformationConfig",
|
||||
"ReportInformationMetaConfig",
|
||||
"ReportInformationUserConfig",
|
||||
"LogConfig",
|
||||
"DocResultConfig",
|
||||
"UnityChipConfig"
|
||||
]
|
||||
|
||||
from pydantic import BaseModel, PositiveInt, HttpUrl, Field
|
||||
|
||||
from .custom_type import TemplateString
|
||||
|
||||
|
||||
class RTLConfig(BaseModel):
|
||||
base_url: HttpUrl = Field(alias="base-url")
|
||||
version: str
|
||||
cache_dir: TemplateString = Field(alias="cache-dir")
|
||||
|
||||
|
||||
class TestConfig(BaseModel):
|
||||
skip_tags: list[str] = Field(alias="skip-tags")
|
||||
run_tags: list[str] = Field(alias="run-tags")
|
||||
skip_cases: list[str] = Field(alias="skip-cases")
|
||||
run_cases: list[str] = Field(alias="run-cases")
|
||||
skip_exceptions: list[str] = Field(alias="skip-exceptions")
|
||||
|
||||
|
||||
class OutputConfig(BaseModel):
|
||||
out_dir: TemplateString = Field(alias="out-dir")
|
||||
|
||||
|
||||
class ReportInformationUserConfig(BaseModel):
|
||||
name: str
|
||||
email: str
|
||||
|
||||
|
||||
class ReportInformationMetaConfig(BaseModel):
|
||||
version: TemplateString
|
||||
|
||||
|
||||
class ReportInformationConfig(BaseModel):
|
||||
title: TemplateString
|
||||
user: ReportInformationUserConfig
|
||||
line_greate: int = PositiveInt()
|
||||
meta: ReportInformationMetaConfig
|
||||
|
||||
|
||||
class ReportConfig(BaseModel):
|
||||
report_dir: TemplateString = Field(alias="report-dir")
|
||||
report_name: TemplateString = Field(alias="report-name")
|
||||
information: ReportInformationConfig = Field(alias="information")
|
||||
|
||||
|
||||
class LogConfig(BaseModel):
|
||||
root_level: str = Field(alias="root-level")
|
||||
term_level: str = Field(alias="term-level")
|
||||
file_dir: TemplateString = Field(alias="file-dir")
|
||||
file_name: TemplateString = Field(alias="file-name")
|
||||
file_level: str = Field(alias="file-level")
|
||||
|
||||
|
||||
class DocResultConfig(BaseModel):
|
||||
disable: bool
|
||||
dutree: TemplateString
|
||||
result_name: TemplateString = Field(alias="result-name")
|
||||
report_link: TemplateString = Field(alias="report-link")
|
||||
|
||||
|
||||
class UnityChipConfig(BaseModel):
|
||||
# Exclude filed
|
||||
load_path: str = Field(default="", exclude=True)
|
||||
no_waveform: bool = Field(default=False, exclude=True)
|
||||
no_code_cov: bool = Field(default=False, exclude=True)
|
||||
no_func_cov: bool = Field(default=False, exclude=True)
|
||||
# Field from configure file
|
||||
rtl: RTLConfig
|
||||
test: TestConfig
|
||||
output: OutputConfig
|
||||
report: ReportConfig
|
||||
log: LogConfig
|
||||
doc_result: DocResultConfig = Field(alias="doc-result")
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
__all__ = ["picker_export"]
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
from toffee_test.markers import match_version
|
||||
|
||||
from .cfg.model import UnityChipConfig
|
||||
|
||||
|
||||
def picker_export(
|
||||
source_name: str,
|
||||
cfg: UnityChipConfig,
|
||||
target_name: str = "",
|
||||
simulator: str = "verilator",
|
||||
access_mode: int = 0,
|
||||
internal_file: str = "",
|
||||
vflags="",
|
||||
cflags="",
|
||||
rtl_version: str | list[str] = "openxiangshan-kmh-*",
|
||||
) -> bool:
|
||||
"""Export RTL module to Python DUT using picker tool.
|
||||
|
||||
This function exports a specified RTL module to a Python Design Under Test (DUT)
|
||||
class using the picker export tool. It handles dependency resolution, simulator
|
||||
configuration, and various export options including waveform generation and
|
||||
code coverage.
|
||||
|
||||
Args:
|
||||
source_name: Name of the top-level RTL module to export. Cannot be empty.
|
||||
cfg: Configuration object.
|
||||
target_name: Name for the exported DUT class. Defaults to source_name if empty.
|
||||
simulator: Simulator to use for the export. Options include "verilator", "vcs".
|
||||
Defaults to "verilator".
|
||||
access_mode: Memory access mode. 0 for VPI mode, 1 for Mem-Direct mode.
|
||||
Mem-Direct mode only supports verilator. Defaults to 0.
|
||||
internal_file: Path to a YAML file containing internal signals to export to DUT class.
|
||||
Optional.
|
||||
vflags: Additional flags to pass to the simulator.
|
||||
cflags: Additional C++ compilation flags to pass to the compiler.
|
||||
rtl_version: Required RTL version pattern(s). Can be a string or list of strings.
|
||||
Defaults to "openxiangshan-kmh-*".
|
||||
|
||||
Returns:
|
||||
bool: True if export was successful, False otherwise.
|
||||
|
||||
Raises:
|
||||
AssertionError: If source_name is empty, if no RTL files are found for the module,
|
||||
or if Mem-Direct mode is used with a simulator other than verilator.
|
||||
|
||||
Note:
|
||||
- Waveform format is automatically selected based on simulator: fst for verilator,
|
||||
fsdb for vcs, vcd for others
|
||||
- The exported DUT is placed in the {root}/dut directory
|
||||
- RTL version compatibility is checked before export
|
||||
"""
|
||||
from comm import get_root_dir, error, get_all_rtl_files, info
|
||||
|
||||
assert source_name, "Cannot export DUT without module name"
|
||||
|
||||
if not match_version(cfg.rtl.version, rtl_version):
|
||||
error(f"(Export {source_name}): Unsupported RTL version {cfg.rtl.version}")
|
||||
return False
|
||||
|
||||
# Get all dependencies
|
||||
dependencies = get_all_rtl_files(source_name, cfg)
|
||||
assert dependencies, f"Cannot found any rtl for {source_name}"
|
||||
|
||||
# Mem-Direct mode only supports verilator
|
||||
if access_mode == 1:
|
||||
assert simulator == "verilator", "Mem-Direct mode only supports verilator as simulator"
|
||||
|
||||
# If target name is set
|
||||
if not target_name:
|
||||
target_name = source_name
|
||||
|
||||
# Picker export command
|
||||
cmd = [
|
||||
shutil.which('picker'), # Picker export subcommand
|
||||
"export",
|
||||
dependencies[0], # Path of the top module
|
||||
"--autobuild", "1",
|
||||
"--lang", "python", # Export language is python
|
||||
"--cp_lib", "0", # Disable copy xspcomm library
|
||||
"--sim", simulator, # Set simulator
|
||||
"--rw", str(access_mode), # Set access-mode
|
||||
"--sname", source_name, # Top module name to export
|
||||
"--tdir", get_root_dir("dut") + "/", # Export to %{root}/dut dir
|
||||
"--tname", target_name,
|
||||
]
|
||||
|
||||
# Waveform format
|
||||
wave_format = ""
|
||||
if simulator == "verilator":
|
||||
wave_format = "fst"
|
||||
elif simulator == "vcs":
|
||||
wave_format = "fsdb"
|
||||
else:
|
||||
wave_format = "vcd"
|
||||
|
||||
# Enable waveform
|
||||
if not cfg.no_waveform:
|
||||
cmd.extend(["-w", ".".join((target_name, wave_format))])
|
||||
# Enable code coverage
|
||||
if cfg.no_code_cov is not None and cfg.no_code_cov == False:
|
||||
cmd.append("-c")
|
||||
# Export internal signals to DUT class
|
||||
if internal_file:
|
||||
internal_path = os.path.join(get_root_dir(internal_file))
|
||||
assert os.path.exists(internal_path), "Internal file not found"
|
||||
cmd.extend(["--internal", internal_path])
|
||||
# Add Flags
|
||||
if vflags:
|
||||
cmd.extend(["-V", vflags])
|
||||
if cflags:
|
||||
cmd.extend(["-C", cflags])
|
||||
|
||||
with NamedTemporaryFile(mode="w+", encoding="utf-8", suffix=".txt") as filelist:
|
||||
info("Exporting %s", source_name)
|
||||
cmd.extend(["--fs", filelist.name])
|
||||
filelist.write("\n".join(dependencies))
|
||||
filelist.flush()
|
||||
# Execute picker export to generate DUT
|
||||
ret = subprocess.run(cmd, shell=False, stderr=subprocess.PIPE)
|
||||
if ret.returncode:
|
||||
error(f"Failed to export:\n {ret.stderr}")
|
||||
return ret.returncode == 0
|
||||
|
|
@ -1,489 +0,0 @@
|
|||
#coding=utf8
|
||||
#***************************************************************************************
|
||||
# This project is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
#
|
||||
# See the Mulan PSL v2 for more details.
|
||||
#**************************************************************************************/
|
||||
|
||||
|
||||
import os
|
||||
import time
|
||||
import base64
|
||||
import re
|
||||
import tarfile
|
||||
import requests
|
||||
import subprocess
|
||||
import fnmatch
|
||||
import importlib
|
||||
import traceback
|
||||
import copy
|
||||
import inspect
|
||||
from .logger import warning, debug, info
|
||||
from .cfg import get_config
|
||||
|
||||
|
||||
def merge_dict(dict1, dict2):
|
||||
"""
|
||||
Merge two dictionaries
|
||||
"""
|
||||
if not dict1:
|
||||
return dict2
|
||||
if not dict2:
|
||||
return dict1
|
||||
for key in dict2:
|
||||
if key in dict1:
|
||||
if isinstance(dict1[key], dict) and isinstance(dict2[key], dict):
|
||||
merge_dict(dict1[key], dict2[key])
|
||||
else:
|
||||
dict1[key] = dict2[key]
|
||||
else:
|
||||
dict1[key] = dict2[key]
|
||||
return dict1
|
||||
|
||||
|
||||
def get_abs_path(path, sub, cfg):
|
||||
path = replace_default_vars(path, cfg)
|
||||
ret_path = ""
|
||||
if path.startswith("/"):
|
||||
ret_path = os.path.join(path, sub)
|
||||
else:
|
||||
ret_path = os.path.abspath(os.path.join(os.path.dirname(cfg.__file__), path, sub))
|
||||
if ret_path.endswith("/"):
|
||||
ret_path = ret_path[:-1]
|
||||
return ret_path
|
||||
|
||||
|
||||
def get_log_dir(subdir="", cfg=None):
|
||||
cfg = get_config(cfg)
|
||||
return get_abs_path(cfg.output.out_dir, os.path.join(cfg.log.file_dir, subdir), cfg)
|
||||
|
||||
|
||||
def get_out_dir(subdir="", cfg=None):
|
||||
cfg = get_config(cfg)
|
||||
return get_abs_path(cfg.output.out_dir, subdir, cfg)
|
||||
|
||||
|
||||
def get_rtl_dir(subdir="", cfg=None):
|
||||
cfg = get_config(cfg)
|
||||
return get_abs_path(cfg.rtl.cache_dir, subdir, cfg)
|
||||
|
||||
|
||||
def get_rtl_lnk_version(cfg=None):
|
||||
lnk = os.path.join(get_rtl_dir(cfg=cfg), "rtl")
|
||||
assert os.path.exists(lnk), f"rtl link {lnk} not found"
|
||||
assert os.path.islink(lnk), f"{lnk} is not a link, please check"
|
||||
version = os.readlink(lnk).replace("/rtl", "").split("/")[-1].strip()
|
||||
return version
|
||||
|
||||
|
||||
def get_root_dir(subdir=""):
|
||||
return os.path.abspath(os.path.join(os.path.dirname(__file__), "../", subdir))
|
||||
|
||||
|
||||
def is_all_file_exist(files_to_check, dir):
|
||||
for f in files_to_check:
|
||||
if not os.path.exists(os.path.join(dir, f)):
|
||||
return f
|
||||
return True
|
||||
|
||||
|
||||
def time_format(seconds=None, fmt="%Y%m%d-%H%M%S"):
|
||||
"""
|
||||
Convert seconds to time format
|
||||
"""
|
||||
if seconds is None:
|
||||
seconds = time.time()
|
||||
return time.strftime(fmt, time.gmtime(seconds))
|
||||
|
||||
|
||||
def base64_encode(input_str):
|
||||
input_bytes = input_str.encode('utf-8')
|
||||
base64_bytes = base64.b64encode(input_bytes)
|
||||
base64_str = base64_bytes.decode('utf-8')
|
||||
return base64_str
|
||||
|
||||
|
||||
def base64_decode(base64_str):
|
||||
base64_bytes = base64_str.encode('utf-8')
|
||||
input_bytes = base64.b64decode(base64_bytes)
|
||||
return input_bytes.decode('utf-8')
|
||||
|
||||
|
||||
def use_rtl(rtl_file, out_dir):
|
||||
rtl_path = os.path.join(out_dir, rtl_file)
|
||||
dir_name = os.path.basename(rtl_file).replace(".tar.gz", "")
|
||||
rtl_dir = os.path.join(out_dir, dir_name)
|
||||
if not os.path.exists(rtl_dir):
|
||||
debug("Extract %s to %s" % (rtl_path, out_dir))
|
||||
with tarfile.open(rtl_path, "r:gz") as tar:
|
||||
tar.extractall(path=rtl_dir)
|
||||
lnk_file = os.path.join(out_dir, "rtl")
|
||||
if os.path.exists(lnk_file):
|
||||
debug("Remove old link %s" % lnk_file)
|
||||
os.remove(lnk_file)
|
||||
os.symlink(os.path.join(rtl_dir,"rtl"), lnk_file)
|
||||
|
||||
|
||||
def download_rtl(base_url, out_dir, version="latest"):
|
||||
"""
|
||||
Download RTL from url
|
||||
"""
|
||||
debug("Download RTL from %s (%s)", base_url, version)
|
||||
if version != "latest":
|
||||
for f in os.listdir(out_dir):
|
||||
if version in f and "tar.gz" in f:
|
||||
debug("find %s in %s, ignore download" % (f, out_dir))
|
||||
use_rtl(f, out_dir)
|
||||
return True
|
||||
if version in base_url and ".tar.gz" in base_url:
|
||||
os.system(f"wget {base_url} -P {out_dir}")
|
||||
for f in os.listdir(out_dir):
|
||||
if version in f and "tar.gz" in f:
|
||||
debug("download %s success" % f)
|
||||
use_rtl(f, out_dir)
|
||||
return True
|
||||
if not base_url.endswith(".tar.gz"):
|
||||
resp = requests.get(base_url).content.decode('utf-8')
|
||||
all_keys = []
|
||||
all_urls = {}
|
||||
url = None
|
||||
for u in re.findall(r'http[s]?://\S+?\.tar\.gz', resp):
|
||||
key = u.split("/")[-1].strip()
|
||||
all_keys.append(key)
|
||||
all_urls[key] = u
|
||||
if version and version in u:
|
||||
url = u
|
||||
break
|
||||
if url is None:
|
||||
if version:
|
||||
warning(f"version {version} not found in {all_urls.keys()}, download the first one")
|
||||
assert len(all_urls) > 0, "No download url found (resp: %s)" % resp
|
||||
file_to_download = all_keys[0] # find the latest version
|
||||
for f in os.listdir(out_dir):
|
||||
if file_to_download in f and "tar.gz" in f:
|
||||
debug("find %s in %s, ignore download", f, out_dir)
|
||||
use_rtl(f, out_dir)
|
||||
return True
|
||||
url = all_urls[file_to_download]
|
||||
debug(f"download {url} to {out_dir}")
|
||||
assert os.system(f"wget {url} -P {out_dir}") == 0, "Download RTL failed"
|
||||
use_rtl(url.split("/")[-1], out_dir)
|
||||
else:
|
||||
assert os.system(f"wget {base_url} -P {out_dir}") == 0, "Download RTL failed"
|
||||
use_rtl(base_url.split("/")[-1], out_dir)
|
||||
return True
|
||||
|
||||
def _build_dut(d, cfg):
|
||||
try:
|
||||
module = importlib.import_module(f"scripts.{d}")
|
||||
if not module.build(cfg):
|
||||
warning(f"Build scripts/{d}.py failed")
|
||||
else:
|
||||
info(f"Build scripts/{d}.py success")
|
||||
except Exception as e:
|
||||
warning(f"Failed to build {d}, error: {e}\n{traceback.format_exc()}")
|
||||
|
||||
def build_dut(duts, cfg):
|
||||
target_duts = [d.strip() for d in duts.strip().replace(" ", ",").split(",")]
|
||||
if len(target_duts) == 0:
|
||||
warning(f"No dut to build for: {duts}")
|
||||
return
|
||||
prefix = "build_ut_"
|
||||
build_modules = [f.replace(".py", "") for f in
|
||||
os.listdir(get_root_dir("scripts")) if f.startswith(prefix) and f.endswith(".py")]
|
||||
dut_to_build = []
|
||||
searched_dut = []
|
||||
for d in target_duts:
|
||||
if d.startswith("ut_"):
|
||||
d = d[3:]
|
||||
if "/" in d:
|
||||
if d.endswith("/"):
|
||||
d = d[:-1]
|
||||
d = d.replace("/", "_") + "*"
|
||||
d = prefix + d
|
||||
if "*" in d or "?" in d:
|
||||
dut_to_build.extend(fnmatch.filter(build_modules, d))
|
||||
elif d in build_modules:
|
||||
dut_to_build.append(d)
|
||||
searched_dut.append(d)
|
||||
dut_to_build = list(set(dut_to_build))
|
||||
if len(dut_to_build) == 0:
|
||||
warning(f"No dut to build for: {duts}")
|
||||
return
|
||||
info(f"Build duts: {dut_to_build} with: {searched_dut}")
|
||||
import multiprocessing
|
||||
pool = multiprocessing.Pool()
|
||||
pool.starmap(_build_dut, [(d, cfg) for d in dut_to_build])
|
||||
pool.close()
|
||||
pool.join()
|
||||
|
||||
|
||||
def replace_default_vars(input_str, cfg):
|
||||
if "%{time}" in input_str:
|
||||
input_str = input_str.replace("%{time}", time_format(fmt="%Y%m%d%H%M%S"))
|
||||
if "%{pid}" in input_str:
|
||||
input_str = input_str.replace("%{pid}", str(os.getpid()))
|
||||
if "%{host}" in input_str:
|
||||
input_str = input_str.replace("%{host}", os.uname().nodename)
|
||||
if "%{root}" in input_str:
|
||||
input_str = input_str.replace("%{root}", get_root_dir())
|
||||
if "%{gitag}" in input_str:
|
||||
input_str = input_str.replace("%{gitag}", get_git_tag())
|
||||
if "%{giturl}" in input_str:
|
||||
input_str = input_str.replace("%{giturl}", get_git_url_with_commit())
|
||||
return input_str
|
||||
|
||||
|
||||
def replace_default_vars_in_dict(input_dict, cfg):
|
||||
data = copy.deepcopy(input_dict)
|
||||
def _replace_default_vars(target_dict, data_ref):
|
||||
for k, v in target_dict.items():
|
||||
if isinstance(k, str) and "%{" in k:
|
||||
del data_ref[k]
|
||||
k = replace_default_vars(k, cfg)
|
||||
data_ref[k] = v
|
||||
if isinstance(v, str) and "%{" in v:
|
||||
v = replace_default_vars(v, cfg)
|
||||
data_ref[k] = v
|
||||
elif isinstance(v, dict):
|
||||
_replace_default_vars(v, data_ref[k])
|
||||
_replace_default_vars(input_dict, data)
|
||||
return data
|
||||
|
||||
|
||||
def get_report_dir(cfg=None):
|
||||
cfg = get_config(cfg)
|
||||
return os.path.join(get_out_dir(cfg=cfg), cfg.report.report_dir)
|
||||
|
||||
|
||||
def new_report_name(cfg=None):
|
||||
cfg = get_config(cfg)
|
||||
report_dir = get_report_dir(cfg=cfg)
|
||||
report_name = replace_default_vars(str(cfg.report.report_name), cfg)
|
||||
os.makedirs(report_dir, exist_ok=True)
|
||||
return report_dir, report_name
|
||||
|
||||
|
||||
def exe_cmd(cmd, no_log=False):
|
||||
if isinstance(cmd, list):
|
||||
cmd = " ".join(cmd)
|
||||
if no_log:
|
||||
result = subprocess.run(
|
||||
cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
stdout = result.stdout.decode("utf-8")
|
||||
stderr = result.stderr.decode("utf-8")
|
||||
else:
|
||||
result = subprocess.run(cmd, shell=True)
|
||||
stdout = ""
|
||||
stderr = ""
|
||||
success = result.returncode == 0
|
||||
return success, stdout, stderr
|
||||
|
||||
|
||||
def get_git_commit():
|
||||
try:
|
||||
commit = subprocess.check_output(["git", "rev-parse", "HEAD"]).strip().decode("utf-8")
|
||||
return commit
|
||||
except subprocess.CalledProcessError as e:
|
||||
warning(f"Error getting git commit: {e}")
|
||||
return "none"
|
||||
|
||||
|
||||
def is_git_dirty():
|
||||
try:
|
||||
status = subprocess.check_output(["git", "status", "--porcelain"]).strip().decode("utf-8")
|
||||
return len(status) > 0
|
||||
except subprocess.CalledProcessError as e:
|
||||
warning(f"Error checking if git is dirty: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def get_git_branch():
|
||||
try:
|
||||
branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]).strip().decode("utf-8")
|
||||
return branch
|
||||
except subprocess.CalledProcessError as e:
|
||||
warning(f"Error getting git branch: {e}")
|
||||
return "none"
|
||||
|
||||
|
||||
def get_git_tag():
|
||||
return get_git_branch() + "-" + get_git_commit() + ("-dirty" if is_git_dirty() else "")
|
||||
|
||||
|
||||
|
||||
def get_git_remote_url():
|
||||
try:
|
||||
url = subprocess.check_output(["git", "config", "--get", "remote.origin.url"]).strip().decode("utf-8")
|
||||
return url
|
||||
except subprocess.CalledProcessError as e:
|
||||
return "none"
|
||||
|
||||
|
||||
def get_git_url_with_commit():
|
||||
url = get_git_remote_url()
|
||||
commit = get_git_commit()
|
||||
if url != "none" and commit != "none":
|
||||
return f"{url}/tree/{commit}".replace(".git/","/").replace(
|
||||
"git@github.com:", "https://github.com/").strip() + (" (dirty)" if is_git_dirty() else "")
|
||||
return "none"
|
||||
|
||||
|
||||
def UT_FCOV(group, ignore_prefix="ut_"):
|
||||
frame = inspect.currentframe()
|
||||
caller_frame = frame.f_back
|
||||
caller_module = inspect.getmodule(caller_frame)
|
||||
if caller_module:
|
||||
gname = "/".join(caller_module.__name__.split("UnityChipForXiangShan."
|
||||
)[-1].split("."))
|
||||
gname = os.path.normpath(os.path.join(gname, group)).replace("/", ".")
|
||||
if gname.startswith(ignore_prefix):
|
||||
gname = gname[len(ignore_prefix):]
|
||||
return gname
|
||||
return "unknown." + os.path.normpath(group)
|
||||
|
||||
|
||||
def remove_version_tag(version_str, split_char="-", tag_pos=-2):
|
||||
if split_char in version_str:
|
||||
vlist = version_str.split(split_char)
|
||||
vlist.pop(tag_pos) # name-tag-number => name-tag
|
||||
return split_char.join(vlist)
|
||||
return version_str
|
||||
|
||||
|
||||
def get_version_checker(target_version):
|
||||
from toffee_test.markers import match_version
|
||||
version = get_config().rtl.version
|
||||
if not match_version(version, target_version):
|
||||
def _fail():
|
||||
import pytest
|
||||
return pytest.skip(f"Unsupported RTL version {version}, need: {target_version}")
|
||||
return _fail
|
||||
return lambda: None
|
||||
|
||||
|
||||
def module_name_with(names, prefix=None):
|
||||
caller_module = inspect.getmodule(inspect.currentframe().f_back)
|
||||
assert caller_module, "Failed to get caller module"
|
||||
mname = caller_module.__name__
|
||||
if prefix:
|
||||
mname = os.path.normpath(os.path.join(mname.replace(".", "/"),
|
||||
prefix)).replace("/", ".")
|
||||
if isinstance(names, str):
|
||||
return mname + "." + names
|
||||
elif isinstance(names, list):
|
||||
return [mname + "." + n for n in names]
|
||||
raise ValueError("Invalid names type")
|
||||
|
||||
|
||||
def get_all_rtl_files(top_module, cfg):
|
||||
"""
|
||||
Returns the file paths of all modules that the `top_module` depends on,
|
||||
with the path of `top_module` as the first element in the list.
|
||||
|
||||
This function assumes that there is **only one module** in the file and
|
||||
that the **file name matches the module name**.
|
||||
"""
|
||||
import re
|
||||
from glob import iglob
|
||||
from collections import OrderedDict
|
||||
|
||||
some_verilog_keywords = {
|
||||
'for', 'real', 'initial', 'input', 'endcase', 'typedef', 'primitive', 'always_comb', 'always_latch', 'negedge',
|
||||
'repeat', 'while', 'endfunction', 'int', 'output', 'wire', 'logic', 'reg', 'assign', 'function', 'case',
|
||||
'always_ff', 'if', 'posedge', 'table', 'end', 'task', 'forever', 'enum', 'endtask', 'module', 'localparam',
|
||||
'timescale', 'endprimitive', 'else', 'endtable', 'always', 'parameter', 'time', 'endmodule', 'begin',
|
||||
"and", "or", "not", "xor"
|
||||
}
|
||||
|
||||
module_pattern = re.compile(r"\bmodule\s+(\w+)\b")
|
||||
instance_pattern = re.compile(r"\b(\w+)\s+(?!module)(\w+)\s*\(")
|
||||
module_path_map = OrderedDict()
|
||||
|
||||
def resolve_verilog_file(path):
|
||||
module_set = set()
|
||||
instance_set = set()
|
||||
|
||||
def remove_inline_comments(s):
|
||||
# Remove the line comment first
|
||||
s = re.sub(r"//.*$", "", s)
|
||||
# Then remove the block comment
|
||||
return re.sub(r'/\*.*?\*/', "", s)
|
||||
|
||||
def parse_line(line_text: str) -> None:
|
||||
_line = remove_inline_comments(line_text)
|
||||
|
||||
# Extract names of declared modules
|
||||
module_matches = module_pattern.finditer(_line)
|
||||
for match in module_matches:
|
||||
_name = match.group(1)
|
||||
module_set.add(_name)
|
||||
|
||||
# Extract names of instanced modules
|
||||
instance_matches = instance_pattern.finditer(_line)
|
||||
for match in instance_matches:
|
||||
_name = match.group(1)
|
||||
if _name not in some_verilog_keywords:
|
||||
instance_set.add(_name)
|
||||
|
||||
# Code begin
|
||||
block_comment_depth = 0
|
||||
pending_line = ''
|
||||
|
||||
with open(path, "r") as file:
|
||||
while True:
|
||||
chunk = file.read(32768)
|
||||
if not chunk:
|
||||
break
|
||||
|
||||
lines = ("".join((pending_line, chunk))).split("\n")
|
||||
if lines:
|
||||
pending_line = lines.pop()
|
||||
|
||||
for line in lines:
|
||||
# for block comment
|
||||
start_pos = line.find("/*")
|
||||
end_pos = line.find("*/")
|
||||
while start_pos != -1 or end_pos != -1:
|
||||
# if '/*' appears before '*/', increase depth
|
||||
if start_pos != -1 and (end_pos == -1 or start_pos < end_pos):
|
||||
block_comment_depth += 1
|
||||
line = " ".join((line[:start_pos], line[start_pos + 2:]))
|
||||
# if '*/' appears before '/*', decrease depth
|
||||
elif end_pos != -1:
|
||||
block_comment_depth -= 1
|
||||
line = " ".join((line[:end_pos], line[end_pos + 2:]))
|
||||
start_pos = line.find("/*")
|
||||
end_pos = line.find("*/")
|
||||
|
||||
# skip if content of current line is in block comment
|
||||
if block_comment_depth > 0:
|
||||
continue
|
||||
parse_line(line)
|
||||
if pending_line:
|
||||
parse_line(pending_line)
|
||||
return module_set, instance_set
|
||||
|
||||
def get_rtl_helper(top_module_name) -> None:
|
||||
from comm import get_rtl_dir
|
||||
# Walk through the rtl dir
|
||||
rtl_dir = os.path.join(str(get_rtl_dir(cfg=cfg)), f"**/{top_module_name}.*v")
|
||||
for path in iglob(rtl_dir, recursive=True):
|
||||
module_set, inst_set = resolve_verilog_file(path)
|
||||
for _name in module_set:
|
||||
module_path_map[_name] = path
|
||||
module_set.clear()
|
||||
for _name in inst_set:
|
||||
if _name not in module_path_map:
|
||||
get_rtl_helper(_name)
|
||||
|
||||
get_rtl_helper(top_module)
|
||||
return list(module_path_map.values())
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# coding=utf8
|
||||
# ***************************************************************************************
|
||||
# This project is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
#
|
||||
# See the Mulan PSL v2 for more details.
|
||||
# **************************************************************************************/
|
||||
|
||||
from .base64 import *
|
||||
from .build_dut import *
|
||||
from .cfg import *
|
||||
from .download_rtl import *
|
||||
from .git import *
|
||||
from .rtl import *
|
||||
from .utils import *
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
# coding=utf8
|
||||
# ***************************************************************************************
|
||||
# This project is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
#
|
||||
# See the Mulan PSL v2 for more details.
|
||||
# **************************************************************************************/
|
||||
|
||||
__all__ = [
|
||||
"base64_encode",
|
||||
"base64_decode",
|
||||
]
|
||||
|
||||
import base64
|
||||
|
||||
|
||||
def base64_encode(input_str: str) -> str:
|
||||
"""Encodes a string into a Base64 representation.
|
||||
|
||||
This function takes a standard string, encodes it into UTF-8 bytes,
|
||||
and then returns the Base64 encoded string.
|
||||
|
||||
Args:
|
||||
input_str: The string to be encoded.
|
||||
|
||||
Returns:
|
||||
The Base64 encoded string.
|
||||
"""
|
||||
input_bytes = input_str.encode('utf-8')
|
||||
base64_bytes = base64.b64encode(input_bytes)
|
||||
base64_str = base64_bytes.decode('utf-8')
|
||||
return base64_str
|
||||
|
||||
|
||||
def base64_decode(base64_str: str) -> str:
|
||||
"""Decodes a Base64 encoded string into a UTF-8 string.
|
||||
|
||||
Args:
|
||||
base64_str: The Base64 encoded string.
|
||||
|
||||
Returns:
|
||||
The decoded UTF-8 string.
|
||||
|
||||
Raises:
|
||||
binascii.Error: If the input `base64_str` is incorrectly padded or
|
||||
contains non-alphabet characters.
|
||||
UnicodeDecodeError: If the decoded bytes cannot be interpreted as
|
||||
valid UTF-8.
|
||||
"""
|
||||
base64_bytes = base64_str.encode('utf-8')
|
||||
input_bytes = base64.b64decode(base64_bytes)
|
||||
return input_bytes.decode('utf-8')
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
# coding=utf8
|
||||
# ***************************************************************************************
|
||||
# This project is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
#
|
||||
# See the Mulan PSL v2 for more details.
|
||||
# **************************************************************************************/
|
||||
|
||||
__all__ = ["build_dut"]
|
||||
|
||||
import glob
|
||||
import shutil
|
||||
import traceback
|
||||
from importlib import import_module
|
||||
from importlib.util import find_spec
|
||||
from pathlib import Path
|
||||
|
||||
from .utils import get_root_dir
|
||||
from ..logger import warning, info
|
||||
|
||||
|
||||
def _build_dut(d, cfg):
|
||||
try:
|
||||
module = import_module(f"scripts.{d}")
|
||||
if not module.build(cfg):
|
||||
warning(f"Build scripts/{d}.py failed")
|
||||
else:
|
||||
info(f"Build scripts/{d}.py success")
|
||||
except Exception as e:
|
||||
warning(f"Failed to build {d}, error: {e}\n{traceback.format_exc()}")
|
||||
|
||||
|
||||
def is_dut_built(dut: str) -> bool:
|
||||
build_script = import_module(f"scripts.{dut}")
|
||||
assert hasattr(build_script, "TARGET_NAME") and isinstance(build_script.TARGET_NAME, str), \
|
||||
f"Build script: {dut} should have string variable `TARGET_NAME`"
|
||||
dut_package = find_spec(f"dut.{build_script.TARGET_NAME}")
|
||||
if not dut_package:
|
||||
return False
|
||||
elif not dut_package.loader:
|
||||
to_del = f"{get_root_dir()}/dut/{build_script.TARGET_NAME}"
|
||||
shutil.rmtree(to_del, ignore_errors=True)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def build_dut(duts: str, cfg) -> None:
|
||||
"""Builds specified DUTs in parallel.
|
||||
|
||||
This function discovers build scripts matching the DUT names provided,
|
||||
including support for wildcards (*, ?), and then executes them
|
||||
concurrently using a multiprocessing pool.
|
||||
|
||||
Args:
|
||||
duts (str): A comma or space-separated string of DUT names to build.
|
||||
cfg: The configuration object to be passed to each build process.
|
||||
"""
|
||||
target_duts = [d.strip() for d in duts.strip().split(",")]
|
||||
warning(f"{target_duts} DUTs are not supported.")
|
||||
if not target_duts:
|
||||
warning(f"No dut to build for: {duts}")
|
||||
return
|
||||
prefix = "build_ut_"
|
||||
scripts_dir = get_root_dir("scripts")
|
||||
dut_to_build = set()
|
||||
searched_script = []
|
||||
for d in target_duts:
|
||||
if d.startswith("ut_"):
|
||||
d = d[3:]
|
||||
# Target is not a single file, is a dir
|
||||
if "/" in d:
|
||||
d = d.rstrip("/").replace("/", "_") + "*"
|
||||
searched_script.append(d)
|
||||
pattern = f"{scripts_dir}/{prefix}{d}.py"
|
||||
for match in glob.iglob(pattern):
|
||||
module_name = Path(match).stem
|
||||
dut_to_build.add(module_name)
|
||||
if not dut_to_build:
|
||||
warning(f"No dut to build for: {duts}")
|
||||
return
|
||||
|
||||
import multiprocessing
|
||||
with multiprocessing.Pool() as pool:
|
||||
for dut in dut_to_build:
|
||||
if is_dut_built(dut):
|
||||
continue
|
||||
pool.apply_async(_build_dut, args=(dut, cfg))
|
||||
pool.close()
|
||||
pool.join()
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
# coding=utf8
|
||||
# ***************************************************************************************
|
||||
# This project is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
#
|
||||
# See the Mulan PSL v2 for more details.
|
||||
# **************************************************************************************/
|
||||
|
||||
__all__ = [
|
||||
"get_config",
|
||||
"get_log_dir",
|
||||
"get_out_dir",
|
||||
"get_rtl_dir",
|
||||
"get_rtl_lnk_version",
|
||||
"get_report_dir",
|
||||
"get_version_checker",
|
||||
"new_report_name",
|
||||
"init_cfg",
|
||||
"cfg_as_str",
|
||||
"cfg_from_str",
|
||||
"dump_cfg",
|
||||
]
|
||||
|
||||
import ast
|
||||
import functools
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from ..cfg.model import UnityChipConfig
|
||||
from ..functions.base64 import base64_encode, base64_decode
|
||||
from ..functions.utils import get_root_dir, get_abs_path, time_format
|
||||
from ..logger import debug
|
||||
|
||||
global_cfg = None
|
||||
|
||||
|
||||
def get_config(cfg: UnityChipConfig = None):
|
||||
if cfg is not None:
|
||||
return cfg
|
||||
global global_cfg
|
||||
if global_cfg is not None:
|
||||
return global_cfg
|
||||
import pytest
|
||||
cfg_dict = getattr(pytest, "toffee_custom_key_value", None).get("global_unitychip_cfg")
|
||||
global_cfg = UnityChipConfig.model_validate(cfg_dict, by_alias=True)
|
||||
return global_cfg
|
||||
|
||||
|
||||
def get_log_dir(subdir: str = "", cfg: UnityChipConfig = None) -> str:
|
||||
"""Gets the absolute path to the log directory from the configuration.
|
||||
|
||||
Args:
|
||||
subdir (str, optional): A subdirectory to append to the log path.
|
||||
Defaults to "".
|
||||
cfg (optional): The configuration object. Loaded automatically if None.
|
||||
|
||||
Returns:
|
||||
str: The absolute path to the log directory.
|
||||
"""
|
||||
cfg = get_config(cfg)
|
||||
return get_abs_path(cfg.output.out_dir, os.path.join(cfg.log.file_dir, subdir), cfg)
|
||||
|
||||
|
||||
def get_out_dir(subdir: str = "", cfg: UnityChipConfig = None) -> str:
|
||||
"""Gets the absolute path to the output directory from the configuration.
|
||||
|
||||
Args:
|
||||
subdir (str, optional): A subdirectory to append to the output path.
|
||||
Defaults to "".
|
||||
cfg (optional): The configuration object. Loaded automatically if None.
|
||||
|
||||
Returns:
|
||||
str: The absolute path to the output directory.
|
||||
"""
|
||||
cfg = get_config(cfg)
|
||||
return get_abs_path(cfg.output.out_dir, subdir, cfg)
|
||||
|
||||
|
||||
def get_rtl_dir(subdir: str = "", cfg: UnityChipConfig = None) -> str:
|
||||
"""Gets the absolute path to the RTL directory of `rtl.version` from the configuration.
|
||||
|
||||
Args:
|
||||
subdir (str, optional): A subdirectory to append to the RTL path.
|
||||
Defaults to "".
|
||||
cfg (optional): The configuration object. Loaded automatically if None.
|
||||
|
||||
Returns:
|
||||
str: The absolute path to the RTL cache directory.
|
||||
"""
|
||||
cfg = get_config(cfg)
|
||||
subdir = os.path.join("rtl", subdir)
|
||||
return get_abs_path(cfg.rtl.cache_dir, subdir, cfg)
|
||||
|
||||
|
||||
def get_rtl_lnk_version(cfg: UnityChipConfig = None) -> str:
|
||||
"""Reads the RTL version from the 'rtl' symlink in the cache directory.
|
||||
|
||||
This function parses the target of the 'rtl' symlink to extract the
|
||||
version string.
|
||||
|
||||
Args:
|
||||
cfg (optional): The configuration object. Loaded automatically if None.
|
||||
|
||||
Returns:
|
||||
str: The extracted RTL version string.
|
||||
|
||||
Raises:
|
||||
AssertionError: If the 'rtl' symlink does not exist or is not a symlink.
|
||||
"""
|
||||
cfg = get_config(cfg)
|
||||
lnk = os.path.join(cfg.rtl.cache_dir, "rtl")
|
||||
assert os.path.exists(lnk), f"rtl link {lnk} not found"
|
||||
assert os.path.islink(lnk), f"{lnk} is not a link, please check"
|
||||
version = os.readlink(lnk).replace("/rtl", "").split("/")[-1].strip()
|
||||
return version
|
||||
|
||||
|
||||
def get_report_dir(cfg: UnityChipConfig = None) -> str:
|
||||
"""Gets the absolute path to the report directory from the configuration.
|
||||
|
||||
Args:
|
||||
cfg (optional): The configuration object. Loaded automatically if None.
|
||||
|
||||
Returns:
|
||||
str: The absolute path to the report directory.
|
||||
"""
|
||||
cfg = get_config(cfg)
|
||||
return os.path.join(get_out_dir(cfg=cfg), cfg.report.report_dir)
|
||||
|
||||
|
||||
def get_version_checker(target_version: str):
|
||||
"""Returns a function to check RTL version compatibility for pytest.
|
||||
|
||||
This factory returns a checker function. If the project's RTL version
|
||||
does not match `target_version`, the checker calls `pytest.skip`.
|
||||
Otherwise, it does nothing.
|
||||
|
||||
Args:
|
||||
target_version (str): The target version string to match against.
|
||||
|
||||
Returns:
|
||||
Callable[[], None]: A function that performs the version check.
|
||||
"""
|
||||
from toffee_test.markers import match_version
|
||||
version = get_config().rtl.version
|
||||
if not match_version(version, target_version):
|
||||
def _fail():
|
||||
import pytest
|
||||
return pytest.skip(f"Unsupported RTL version {version}, need: {target_version}")
|
||||
|
||||
return _fail
|
||||
return lambda: None
|
||||
|
||||
|
||||
def new_report_name(cfg: UnityChipConfig = None) -> tuple[str, str]:
|
||||
"""Generates a new report name and ensures the report directory exists.
|
||||
|
||||
It creates the report directory if needed and resolves placeholders in the
|
||||
report name template from the configuration.
|
||||
|
||||
Args:
|
||||
cfg (optional): The configuration object. Loaded automatically if None.
|
||||
|
||||
Returns:
|
||||
tuple[str, str]: A tuple containing the report directory path and the
|
||||
generated report name.
|
||||
"""
|
||||
cfg = get_config(cfg)
|
||||
report_dir = get_report_dir(cfg=cfg)
|
||||
report_name = cfg.report.report_name
|
||||
os.makedirs(report_dir, exist_ok=True)
|
||||
return report_dir, report_name
|
||||
|
||||
|
||||
def init_cfg(cfg_file=None, args: list[str] = (), cfg_str_data=None) -> UnityChipConfig:
|
||||
if args is None:
|
||||
args = list()
|
||||
if cfg_str_data:
|
||||
return cfg_from_str(cfg_str_data)
|
||||
default_cfg = os.path.join(get_root_dir(), "configs/_default.yaml")
|
||||
debug("load default config from %s", default_cfg)
|
||||
with open(default_cfg, 'r') as f:
|
||||
cfg = yaml.safe_load(f)
|
||||
cfg["load_path"] = default_cfg
|
||||
if cfg_file:
|
||||
with open(cfg_file, 'r') as f:
|
||||
cfg.update(yaml.safe_load(f))
|
||||
cfg["load_path"] = os.path.abspath(cfg_file)
|
||||
config = UnityChipConfig.model_validate(cfg)
|
||||
for arg in args:
|
||||
if "=" in arg:
|
||||
key, value = arg.split("=")
|
||||
key = key.strip().replace("-", "_")
|
||||
try:
|
||||
value = ast.literal_eval(value)
|
||||
last_attr = key.split(".")[-1]
|
||||
attr = functools.reduce(getattr, key.split(".")[:-1], config)
|
||||
setattr(attr, last_attr, value)
|
||||
except AttributeError as e:
|
||||
debug(f"Key {key} not found in config, ignore")
|
||||
return config
|
||||
|
||||
|
||||
def cfg_as_str(cfg: UnityChipConfig):
|
||||
return base64_encode(yaml.dump(cfg.model_dump(by_alias=True), default_flow_style=False))
|
||||
|
||||
|
||||
def cfg_from_str(cfg_str):
|
||||
return UnityChipConfig.model_validate(yaml.safe_load(base64_decode(cfg_str)))
|
||||
|
||||
|
||||
def dump_cfg(cfg: UnityChipConfig = None, cfg_file=None):
|
||||
if not cfg_file:
|
||||
cfg_file = os.path.join(get_out_dir(), "config-%s.yaml" % time_format())
|
||||
debug(f"Dump config to {cfg_file}")
|
||||
if cfg is None:
|
||||
cfg = get_config()
|
||||
assert cfg is not None, "cfg is not initialized"
|
||||
with open(cfg_file, 'w') as f:
|
||||
f.write(yaml.dump(cfg.model_dump(by_alias=True), default_flow_style=False))
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def config():
|
||||
return get_config()
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
# coding=utf8
|
||||
# ***************************************************************************************
|
||||
# This project is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
#
|
||||
# See the Mulan PSL v2 for more details.
|
||||
# **************************************************************************************/
|
||||
|
||||
__all__ = ["use_rtl", "download_rtl"]
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import tarfile
|
||||
from glob import iglob
|
||||
|
||||
import requests
|
||||
|
||||
from ..logger import warning, debug
|
||||
|
||||
|
||||
def use_rtl(rtl_file: str, out_dir: str) -> None:
|
||||
"""Extracts an RTL tarball and creates a symbolic link to its contents.
|
||||
|
||||
If the tarball is not already extracted, this function extracts it into a
|
||||
directory named after the tarball. It then creates a symlink named 'rtl'
|
||||
in the output directory, pointing to the 'rtl' subdirectory within the
|
||||
extracted folder. Any existing 'rtl' symlink is removed first.
|
||||
|
||||
Args:
|
||||
rtl_file (str): The filename of the RTL tarball (e.g., 'openxiangshan-kmh-abc114514-1919810.tar.gz').
|
||||
out_dir (str): The directory where the tarball is located and where the
|
||||
symlink will be created.
|
||||
"""
|
||||
rtl_path = os.path.join(out_dir, rtl_file)
|
||||
dir_name = os.path.basename(rtl_file).replace(".tar.gz", "")
|
||||
rtl_dir = os.path.join(out_dir, dir_name)
|
||||
if not os.path.exists(rtl_dir):
|
||||
debug("Extract %s to %s" % (rtl_path, out_dir))
|
||||
with tarfile.open(rtl_path, "r:gz") as tar:
|
||||
tar.extractall(path=rtl_dir)
|
||||
lnk_file = os.path.join(out_dir, "rtl")
|
||||
if os.path.exists(lnk_file):
|
||||
debug("Remove old link %s" % lnk_file)
|
||||
os.remove(lnk_file)
|
||||
os.symlink(os.path.join(rtl_dir, "rtl"), lnk_file)
|
||||
|
||||
|
||||
def wget(url: str, out_dir: str) -> int:
|
||||
"""Downloads a file from a URL using wget.
|
||||
Args:
|
||||
url (str): The URL to download the file from.
|
||||
out_dir (str): The directory where the file is located.
|
||||
|
||||
Returns:
|
||||
int: The return code of `wget`
|
||||
"""
|
||||
ret = subprocess.run(["wget", url, "-P", out_dir])
|
||||
return ret.returncode
|
||||
|
||||
|
||||
def download_rtl(base_url: str, out_dir: str, version="latest") -> bool:
|
||||
"""Downloads and sets up a specific version of an RTL.
|
||||
|
||||
Checks for a local copy of the specified RTL version first. If not found,
|
||||
it downloads the tarball from the `base_url`. The URL can be a direct link
|
||||
to a .tar.gz file or a webpage containing such links. If a version is
|
||||
specified, it attempts to find a matching file; otherwise, it defaults to
|
||||
the first available link. After downloading, it calls `use_rtl` to extract
|
||||
the archive and create a symlink.
|
||||
|
||||
Args:
|
||||
base_url (str): The URL to download the RTL from.
|
||||
out_dir (str): The directory to save the downloaded file.
|
||||
version (str): The version of the RTL to download. Defaults to "latest".
|
||||
|
||||
Returns:
|
||||
bool: True if the RTL is successfully downloaded and set up.
|
||||
"""
|
||||
debug("Download RTL from %s (%s)", base_url, version)
|
||||
rtl_pattern = f"{out_dir}/*.tar.gz"
|
||||
if version != "latest":
|
||||
for f in iglob(rtl_pattern):
|
||||
if version in f:
|
||||
debug("find %s in %s, ignore download" % (f, out_dir))
|
||||
use_rtl(f, out_dir)
|
||||
return True
|
||||
if version in base_url and ".tar.gz" in base_url:
|
||||
wget(base_url, out_dir)
|
||||
for f in iglob(rtl_pattern):
|
||||
if version in f:
|
||||
debug("download %s success" % f)
|
||||
use_rtl(f, out_dir)
|
||||
return True
|
||||
if not base_url.endswith(".tar.gz"):
|
||||
resp = requests.get(base_url).content.decode('utf-8')
|
||||
all_keys = []
|
||||
all_urls = {}
|
||||
url = None
|
||||
for u in re.findall(r'http[s]?://\S+?\.tar\.gz', resp):
|
||||
key = u.split("/")[-1].strip()
|
||||
all_keys.append(key)
|
||||
all_urls[key] = u
|
||||
if version and version in u:
|
||||
url = u
|
||||
break
|
||||
if url is None:
|
||||
if version:
|
||||
warning(f"version {version} not found in {all_urls.keys()}, download the first one")
|
||||
assert len(all_urls) > 0, "No download url found (resp: %s)" % resp
|
||||
file_to_download = all_keys[0] # find the latest version
|
||||
for f in iglob(rtl_pattern):
|
||||
if file_to_download in f:
|
||||
debug("find %s in %s, ignore download", f, out_dir)
|
||||
use_rtl(f, out_dir)
|
||||
return True
|
||||
url = all_urls[file_to_download]
|
||||
debug(f"download {url} to {out_dir}")
|
||||
assert wget(url, out_dir) == 0, "Download RTL failed"
|
||||
use_rtl(url.split("/")[-1], out_dir)
|
||||
else:
|
||||
assert wget(base_url, out_dir) == 0, "Download RTL failed"
|
||||
use_rtl(base_url.split("/")[-1], out_dir)
|
||||
return True
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
# coding=utf8
|
||||
# ***************************************************************************************
|
||||
# This project is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
#
|
||||
# See the Mulan PSL v2 for more details.
|
||||
# **************************************************************************************/
|
||||
|
||||
__all__ = [
|
||||
"get_git_commit",
|
||||
"is_git_dirty",
|
||||
"get_git_branch",
|
||||
"get_git_tag",
|
||||
"get_git_remote_url",
|
||||
"get_git_url_with_commit",
|
||||
]
|
||||
|
||||
import subprocess
|
||||
|
||||
from ..logger import warning
|
||||
|
||||
|
||||
def get_git_commit() -> str:
|
||||
"""Gets the current git commit hash.
|
||||
|
||||
Returns:
|
||||
str: The full commit hash, or "none" if an error occurs.
|
||||
"""
|
||||
try:
|
||||
commit = subprocess.check_output(["git", "rev-parse", "HEAD"]).strip().decode("utf-8")
|
||||
return commit
|
||||
except subprocess.CalledProcessError as e:
|
||||
warning(f"Error getting git commit: {e}")
|
||||
return "none"
|
||||
|
||||
|
||||
def is_git_dirty() -> bool:
|
||||
"""Checks if the git working directory is dirty (has uncommitted changes).
|
||||
|
||||
Returns:
|
||||
bool: True if there are uncommitted changes, False otherwise.
|
||||
"""
|
||||
try:
|
||||
status = subprocess.check_output(["git", "status", "--porcelain"]).strip().decode("utf-8")
|
||||
return len(status) > 0
|
||||
except subprocess.CalledProcessError as e:
|
||||
warning(f"Error checking if git is dirty: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def get_git_branch() -> str:
|
||||
"""Gets the current git branch name.
|
||||
|
||||
Returns:
|
||||
str: The current branch name, or "none" if an error occurs.
|
||||
"""
|
||||
try:
|
||||
branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]).strip().decode("utf-8")
|
||||
return branch
|
||||
except subprocess.CalledProcessError as e:
|
||||
warning(f"Error getting git branch: {e}")
|
||||
return "none"
|
||||
|
||||
|
||||
def get_git_tag() -> str:
|
||||
"""Generates a descriptive tag for the current git state.
|
||||
|
||||
The format is 'branch-commit[-dirty]'.
|
||||
|
||||
Returns:
|
||||
str: A string representing the complete git state tag.
|
||||
"""
|
||||
return get_git_branch() + "-" + get_git_commit() + ("-dirty" if is_git_dirty() else "")
|
||||
|
||||
|
||||
def get_git_remote_url() -> str:
|
||||
"""Gets the remote 'origin' URL of the git repository.
|
||||
|
||||
Returns:
|
||||
str: The remote URL, or "none" if not found or an error occurs.
|
||||
"""
|
||||
try:
|
||||
url = subprocess.check_output(["git", "config", "--get", "remote.origin.url"]).strip().decode("utf-8")
|
||||
return url
|
||||
except subprocess.CalledProcessError as e:
|
||||
return "none"
|
||||
|
||||
|
||||
def get_git_url_with_commit() -> str:
|
||||
"""Constructs a web URL for the current commit.
|
||||
|
||||
This function formats the remote git URL into a browseable HTTPS link
|
||||
pointing to the specific commit tree.
|
||||
|
||||
Returns:
|
||||
str: The full URL to the commit, or "none" if an error occurs.
|
||||
"""
|
||||
url = get_git_remote_url()
|
||||
commit = get_git_commit()
|
||||
if url != "none" and commit != "none":
|
||||
return f"{url}/tree/{commit}".replace(".git/", "/").replace(
|
||||
"git@github.com:", "https://github.com/").strip() + (" (dirty)" if is_git_dirty() else "")
|
||||
return "none"
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
# coding=utf8
|
||||
# ***************************************************************************************
|
||||
# This project is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
#
|
||||
# See the Mulan PSL v2 for more details.
|
||||
# **************************************************************************************/
|
||||
|
||||
__all__ = [
|
||||
"get_all_rtl_files",
|
||||
"extract_signals"
|
||||
]
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from .utils import get_root_dir
|
||||
from ..cfg.model import UnityChipConfig
|
||||
from ..functions.cfg import get_rtl_dir
|
||||
|
||||
|
||||
def get_defined_modules(file: str) -> List[str]:
|
||||
cmd = ["sed", "-n", r"s/^ *module \+\([^ (]\+\).*/\1/p", file]
|
||||
ret = subprocess.run(cmd, cwd=get_root_dir(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
assert ret.returncode == 0, ret.stderr
|
||||
return ret.stdout.decode().splitlines()
|
||||
|
||||
|
||||
def get_instant_modules(file: str) -> List[str]:
|
||||
verible_bin = shutil.which("verible-verilog-syntax")
|
||||
assert verible_bin, "verible-verilog-syntax is not installed"
|
||||
verible_cmd = [verible_bin, "--printtokens", file]
|
||||
sed_cmd = [
|
||||
"sed", "-n",
|
||||
r'/#SymbolIdentifier/ { N; /#SymbolIdentifier.*\n.*#SymbolIdentifier/ s/.*"\([^"]*\)".*\n.*/\1/p }'
|
||||
]
|
||||
sort_cmd = ["sort", "-u"]
|
||||
|
||||
# Run verible-verilog-syntax to get the tokens of verilog file
|
||||
verible_proc = subprocess.Popen(verible_cmd, cwd=get_root_dir(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
# Run sed to get the names of all instant modules
|
||||
sed_proc = subprocess.Popen(sed_cmd, stdin=verible_proc.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
# Run sort to unique output
|
||||
sort_proc = subprocess.Popen(sort_cmd, stdin=sed_proc.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
verible_proc.stdout.close()
|
||||
verible_out, verible_err = verible_proc.communicate()
|
||||
assert verible_proc.returncode == 0, verible_err
|
||||
|
||||
sed_proc.stdout.close()
|
||||
sed_out, sed_err = sed_proc.communicate()
|
||||
assert sed_proc.returncode == 0, sed_err
|
||||
|
||||
sort_out, sout_err = sort_proc.communicate()
|
||||
return sort_out.decode().splitlines()
|
||||
|
||||
|
||||
def get_all_rtl_files(top_module: str, cfg: UnityChipConfig = None) -> list[str]:
|
||||
"""Recursively finds all Verilog source files for a given top module.
|
||||
|
||||
This function traverses the Verilog module hierarchy starting from `top_module`
|
||||
to identify all dependent source files. It parses Verilog files to find
|
||||
module instantiations and recursively resolves their file paths.
|
||||
|
||||
Note:
|
||||
This function assumes that each Verilog file contains only one module
|
||||
definition and that the filename matches the module name (e.g., module
|
||||
`my_fifo` is in `my_fifo.v`).
|
||||
|
||||
Args:
|
||||
top_module (str): The name of the top-level module to start the search from.
|
||||
cfg: A configuration object used to determine the root directory for the
|
||||
RTL file search.
|
||||
|
||||
Returns:
|
||||
list[str]: An ordered list of file paths for the top module and all its
|
||||
submodule dependencies.
|
||||
"""
|
||||
from collections import OrderedDict, deque
|
||||
|
||||
module_path: dict[str, str] = OrderedDict()
|
||||
instants = deque([top_module])
|
||||
rtl_dir = Path(get_rtl_dir(cfg=cfg))
|
||||
while instants:
|
||||
t = instants.popleft()
|
||||
for path in rtl_dir.rglob(f"**/{t}.*v"):
|
||||
if t in module_path:
|
||||
break
|
||||
p = str(path.absolute())
|
||||
module_path[t] = p
|
||||
for i in get_instant_modules(p):
|
||||
if i not in module_path:
|
||||
instants.append(i)
|
||||
dependencies = list(module_path.values())
|
||||
return dependencies
|
||||
|
||||
|
||||
def extract_signals(verilog_file: str, output_file: str) -> None:
|
||||
"""Extracts wire/reg signals from a Verilog file into a YAML file.
|
||||
|
||||
This function parses a Verilog source file to find all `wire` and `reg`
|
||||
declarations. It then formats these signal definitions and writes them
|
||||
to a specified YAML file. The `reg` type is converted to `logic`.
|
||||
|
||||
Args:
|
||||
verilog_file (str): The path to the input Verilog source file.
|
||||
output_file (str): The path where the output YAML file will be saved.
|
||||
"""
|
||||
# 定义匹配 wire 和 reg 的正则表达式
|
||||
signal_pattern = re.compile(r'\b(wire|reg)\b\s*(\[[^\]]+\])?\s*([\w, ]+)(;|=)')
|
||||
|
||||
extracted_signals = []
|
||||
|
||||
# 读取 sv 文件内容
|
||||
with open(verilog_file, 'r') as file:
|
||||
lines = file.readlines()
|
||||
|
||||
# 逐行解析
|
||||
for line in lines:
|
||||
match = signal_pattern.search(line)
|
||||
if not match:
|
||||
continue
|
||||
signal_type = match.group(1) # wire or reg
|
||||
width = match.group(2) if match.group(2) else "" # [8:0] or empty
|
||||
names = match.group(3) # 信号名
|
||||
if names.startswith("_GEN"):
|
||||
continue
|
||||
if signal_type == "reg":
|
||||
signal_type = "logic"
|
||||
# 分解信号名并格式化
|
||||
if width == "":
|
||||
for name in names.split(','):
|
||||
extracted_signals.append(f" - \"{signal_type} {name.strip()}\"")
|
||||
else:
|
||||
for name in names.split(','):
|
||||
extracted_signals.append(f" - \"{signal_type} {width.strip()} {name.strip()}\"")
|
||||
|
||||
# 写入到 yaml 文件
|
||||
basename = os.path.basename(verilog_file)
|
||||
filename = os.path.splitext(basename)[0]
|
||||
with open(output_file, 'w') as file:
|
||||
file.write(filename + ':\n')
|
||||
for signal in extracted_signals:
|
||||
file.write(signal + '\n')
|
||||
|
|
@ -0,0 +1,246 @@
|
|||
# coding=utf8
|
||||
# ***************************************************************************************
|
||||
# This project is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
#
|
||||
# See the Mulan PSL v2 for more details.
|
||||
# **************************************************************************************/
|
||||
|
||||
__all__ = [
|
||||
"get_root_dir",
|
||||
"get_abs_path",
|
||||
"time_format",
|
||||
"merge_dict",
|
||||
"exe_cmd",
|
||||
"UT_FCOV",
|
||||
"remove_version_tag",
|
||||
"module_name_with",
|
||||
"is_all_file_exist",
|
||||
]
|
||||
|
||||
import inspect
|
||||
import os
|
||||
import time
|
||||
|
||||
|
||||
def get_root_dir(subdir: str = "") -> str:
|
||||
"""Gets the absolute path to the project's root directory.
|
||||
|
||||
Calculates the root directory based on the location of the current file.
|
||||
|
||||
Args:
|
||||
subdir (str, optional): A subdirectory to append to the root path.
|
||||
Defaults to "".
|
||||
|
||||
Returns:
|
||||
str: The absolute path to the root directory or the specified subdirectory.
|
||||
"""
|
||||
return os.path.abspath(os.path.join(os.path.dirname(__file__), "../../", subdir))
|
||||
|
||||
|
||||
def get_abs_path(path: str, sub: str, cfg) -> str:
|
||||
"""Constructs an absolute path from a base path and a subdirectory.
|
||||
|
||||
If the `path` is absolute, `sub` is joined to it. If it's relative, it
|
||||
is resolved relative to the directory of the configuration file. Placeholders
|
||||
in `path` are resolved first.
|
||||
|
||||
Args:
|
||||
path (str): The base path, which can be absolute or relative.
|
||||
sub (str): The subdirectory to append to the base path.
|
||||
cfg: The configuration object, used for variable replacement and as a
|
||||
base for resolving relative paths.
|
||||
|
||||
Returns:
|
||||
str: The resulting absolute path.
|
||||
"""
|
||||
ret_path = ""
|
||||
if path.startswith("/"):
|
||||
ret_path = os.path.join(path, sub)
|
||||
else:
|
||||
ret_path = os.path.abspath(os.path.join(os.path.dirname(cfg.__file__), path, sub))
|
||||
if ret_path.endswith("/"):
|
||||
ret_path = ret_path[:-1]
|
||||
return ret_path
|
||||
|
||||
|
||||
def time_format(seconds: float = None, fmt: str = "%Y%m%d-%H%M%S") -> str:
|
||||
"""Formats a timestamp into a string.
|
||||
|
||||
Converts a given timestamp (in seconds since the epoch) into a
|
||||
string according to the specified format. Uses the current time if no
|
||||
timestamp is provided.
|
||||
|
||||
Args:
|
||||
seconds (float, optional): Seconds since the epoch. Defaults to `time.time()`.
|
||||
fmt (str, optional): The format string for `time.strftime`.
|
||||
Defaults to "%Y%m%d-%H%M%S".
|
||||
|
||||
Returns:
|
||||
str: The formatted time string.
|
||||
"""
|
||||
if seconds is None:
|
||||
seconds = time.time()
|
||||
return time.strftime(fmt, time.gmtime(seconds))
|
||||
|
||||
|
||||
def merge_dict(dict1: dict, dict2: dict) -> dict:
|
||||
"""Recursively merges two dictionaries.
|
||||
|
||||
Merges `dict2` into `dict1` in-place. If a key exists in both and both
|
||||
values are dictionaries, they are merged recursively. Otherwise, the
|
||||
value from `dict2` overwrites the value in `dict1`.
|
||||
|
||||
Args:
|
||||
dict1 (dict): The dictionary to merge into.
|
||||
dict2 (dict): The dictionary to merge from.
|
||||
|
||||
Returns:
|
||||
dict: The modified `dict1`.
|
||||
"""
|
||||
if not dict1:
|
||||
return dict2
|
||||
if not dict2:
|
||||
return dict1
|
||||
for key in dict2:
|
||||
if key in dict1:
|
||||
if isinstance(dict1[key], dict) and isinstance(dict2[key], dict):
|
||||
merge_dict(dict1[key], dict2[key])
|
||||
else:
|
||||
dict1[key] = dict2[key]
|
||||
else:
|
||||
dict1[key] = dict2[key]
|
||||
return dict1
|
||||
|
||||
|
||||
def exe_cmd(cmd, no_log: bool = False) -> tuple[bool, str, str]:
|
||||
"""Executes a shell command.
|
||||
|
||||
Args:
|
||||
cmd (str | list[str]): The command to execute. If a list, it's joined
|
||||
into a single string.
|
||||
no_log (bool): If True, stdout and stderr are captured and returned.
|
||||
If False, they are printed to the console. Defaults to False.
|
||||
|
||||
Returns:
|
||||
tuple[bool, str, str]: A tuple containing a success boolean (based on
|
||||
return code), the captured stdout, and the captured stderr.
|
||||
Stdout/stderr are empty if `no_log` is False.
|
||||
"""
|
||||
import subprocess
|
||||
if isinstance(cmd, list):
|
||||
cmd = " ".join(cmd)
|
||||
if no_log:
|
||||
result = subprocess.run(
|
||||
cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
stdout = result.stdout.decode("utf-8")
|
||||
stderr = result.stderr.decode("utf-8")
|
||||
else:
|
||||
result = subprocess.run(cmd, shell=True)
|
||||
stdout = ""
|
||||
stderr = ""
|
||||
success = result.returncode == 0
|
||||
return success, stdout, stderr
|
||||
|
||||
|
||||
def UT_FCOV(group: str, ignore_prefix: str = "ut_") -> str:
|
||||
"""Generates a hierarchical name for a unit test feature coverage group.
|
||||
|
||||
Constructs a unique group name based on the calling function's module
|
||||
path, making it easier to organize coverage results.
|
||||
|
||||
Args:
|
||||
group (str): The specific name of the coverage group.
|
||||
ignore_prefix (str, optional): A prefix to remove from the final
|
||||
generated name. Defaults to "ut_".
|
||||
|
||||
Returns:
|
||||
str: A dot-separated string representing the full coverage group path.
|
||||
"""
|
||||
frame = inspect.currentframe()
|
||||
caller_frame = frame.f_back
|
||||
caller_module = inspect.getmodule(caller_frame)
|
||||
if caller_module:
|
||||
gname = "/".join(caller_module.__name__.split("UnityChipForXiangShan.")[-1].split("."))
|
||||
gname = os.path.normpath(os.path.join(gname, group)).replace("/", ".")
|
||||
if gname.startswith(ignore_prefix):
|
||||
gname = gname[len(ignore_prefix):]
|
||||
return gname
|
||||
return "unknown." + os.path.normpath(group)
|
||||
|
||||
|
||||
def remove_version_tag(version_str: str, split_char: str = "-", tag_pos: int = -2) -> str:
|
||||
"""Removes a specific tag from a version string.
|
||||
|
||||
Splits the string and removes the element at a specified position.
|
||||
For example, 'name-tag-number' with default args becomes 'name-number'.
|
||||
|
||||
Args:
|
||||
version_str (str): The version string to modify.
|
||||
split_char (str, optional): The character to split the string by.
|
||||
Defaults to `-`.
|
||||
tag_pos (int, optional): The index of the tag to remove after splitting.
|
||||
Defaults to -2.
|
||||
|
||||
Returns:
|
||||
str: The modified version string.
|
||||
"""
|
||||
if split_char in version_str:
|
||||
vlist = version_str.split(split_char)
|
||||
vlist.pop(tag_pos) # name-tag-number => name-tag
|
||||
return split_char.join(vlist)
|
||||
return version_str
|
||||
|
||||
|
||||
def module_name_with(names, prefix: str = None):
|
||||
"""Prepends the caller's module path to a name or list of names.
|
||||
|
||||
Useful for creating fully qualified names for tests or other components
|
||||
based on their file location.
|
||||
|
||||
Args:
|
||||
names (str | list[str]): A name or list of names to qualify.
|
||||
prefix (str, optional): A path-like prefix to append to the module
|
||||
path before joining with the names.
|
||||
|
||||
Returns:
|
||||
str | list[str]: The fully qualified name or list of names.
|
||||
|
||||
Raises:
|
||||
ValueError: If `names` is not a string or a list.
|
||||
"""
|
||||
caller_module = inspect.getmodule(inspect.currentframe().f_back)
|
||||
assert caller_module, "Failed to get caller module"
|
||||
mname = caller_module.__name__
|
||||
if prefix:
|
||||
mname = os.path.normpath(os.path.join(mname.replace(".", "/"),
|
||||
prefix)).replace("/", ".")
|
||||
if isinstance(names, str):
|
||||
return mname + "." + names
|
||||
elif isinstance(names, list):
|
||||
return [mname + "." + n for n in names]
|
||||
raise ValueError("Invalid names type")
|
||||
|
||||
|
||||
def is_all_file_exist(files_to_check: list[str], dir: str):
|
||||
"""Checks if all files in a list exist within a directory.
|
||||
|
||||
Args:
|
||||
files_to_check (list[str]): A list of filenames to check.
|
||||
dir (str): The directory where files are expected to be.
|
||||
|
||||
Returns:
|
||||
bool | str: Returns `True` if all files exist, otherwise returns the
|
||||
filename of the first file that was not found.
|
||||
"""
|
||||
for f in files_to_check:
|
||||
if not os.path.exists(os.path.join(dir, f)):
|
||||
return f
|
||||
return True
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
#coding=utf8
|
||||
#***************************************************************************************
|
||||
# coding=utf8
|
||||
# ***************************************************************************************
|
||||
# This project is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
|
|
@ -10,14 +10,13 @@
|
|||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
#
|
||||
# See the Mulan PSL v2 for more details.
|
||||
#**************************************************************************************/
|
||||
# **************************************************************************************/
|
||||
|
||||
|
||||
import os
|
||||
import atexit
|
||||
import os
|
||||
from logging import getLogger, DEBUG, INFO, WARNING, ERROR, basicConfig
|
||||
|
||||
|
||||
FORMAT = '[%(asctime)s,%(pathname)s:%(lineno)s,%(levelname)s] %(message)s'
|
||||
basicConfig(format=FORMAT)
|
||||
|
||||
|
|
@ -28,7 +27,6 @@ info = log.info
|
|||
warning = log.warning
|
||||
error = log.error
|
||||
|
||||
|
||||
level_map = {
|
||||
"debug": DEBUG,
|
||||
"info": INFO,
|
||||
|
|
@ -42,12 +40,10 @@ def init_log(cfg):
|
|||
return
|
||||
global debug, info, warning, error
|
||||
log.setLevel(level_map.get(str(cfg.log.root_level).lower(), INFO))
|
||||
from .functions import get_log_dir
|
||||
from comm.functions.cfg import get_log_dir
|
||||
log_path = get_log_dir(cfg=cfg)
|
||||
os.makedirs(log_path, exist_ok=True)
|
||||
from .functions import replace_default_vars
|
||||
log_file = replace_default_vars(cfg.log.file_name, cfg=cfg)
|
||||
log_file = os.path.join(log_path, log_file)
|
||||
log_file = os.path.join(log_path, cfg.log.file_name)
|
||||
from logging import FileHandler, Formatter, StreamHandler
|
||||
ch = StreamHandler()
|
||||
ch.setLevel(level_map.get(str(cfg.log.term_level).lower(), INFO))
|
||||
|
|
@ -59,9 +55,11 @@ def init_log(cfg):
|
|||
log.removeHandler(handler)
|
||||
log.addHandler(ch)
|
||||
log.addHandler(fh)
|
||||
|
||||
def close_fh():
|
||||
fh.close()
|
||||
log.removeHandler(fh)
|
||||
|
||||
atexit.register(lambda: close_fh())
|
||||
debug = log.debug
|
||||
info = log.info
|
||||
|
|
@ -71,10 +69,11 @@ def init_log(cfg):
|
|||
|
||||
|
||||
__cached_loggers__ = {}
|
||||
|
||||
|
||||
def get_file_logger(file_name=None, name=None, level="debug", format=FORMAT, cfg=None, propagate=False):
|
||||
assert file_name, "file_name must be specified"
|
||||
from .functions import get_log_dir, replace_default_vars
|
||||
file_name = replace_default_vars(file_name, cfg=cfg)
|
||||
from comm.functions.cfg import get_log_dir
|
||||
# Cache the logger to avoid creating multiple loggers for the same file
|
||||
global __cached_loggers__
|
||||
if not file_name.startswith("/"):
|
||||
|
|
@ -98,9 +97,11 @@ def get_file_logger(file_name=None, name=None, level="debug", format=FORMAT, cfg
|
|||
for handler in logger.handlers:
|
||||
logger.removeHandler(handler)
|
||||
logger.addHandler(fh)
|
||||
|
||||
# Close the file handler when the program exits
|
||||
def close_fh():
|
||||
fh.close()
|
||||
logger.removeHandler(fh)
|
||||
|
||||
atexit.register(lambda: close_fh())
|
||||
return logger
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#coding=utf8
|
||||
#***************************************************************************************
|
||||
# coding=utf8
|
||||
# ***************************************************************************************
|
||||
# This project is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
#
|
||||
# See the Mulan PSL v2 for more details.
|
||||
#**************************************************************************************/
|
||||
# **************************************************************************************/
|
||||
|
||||
# Default configuration file
|
||||
# Configuration load order: _default.yaml -> user-specified *.yaml -> command line arguments e.g., log.term-level='debug'
|
||||
|
|
@ -60,10 +60,11 @@ report:
|
|||
line_grate: 99
|
||||
# Other information to display, key is the title, value is the content
|
||||
meta:
|
||||
Version: "%{giturl}"
|
||||
version: "%{giturl}"
|
||||
|
||||
# Log configuration
|
||||
log:
|
||||
# Level categories: debug, info, warning, error
|
||||
# Root log level
|
||||
root-level: "debug"
|
||||
# Terminal log level
|
||||
|
|
|
|||
|
|
@ -48,7 +48,31 @@ children:
|
|||
priority: high
|
||||
- name: "icache"
|
||||
desc: "指令缓存 (Instruction Cache)"
|
||||
priority: high
|
||||
children:
|
||||
- name: "iprefetchpipe"
|
||||
desc: "预取指模块"
|
||||
meta:
|
||||
doc_url: "https://open-verify.cc/UnityChipForXiangShan/docs/98_ut/01_frontend/04_icache/01_iprefetchpipe/"
|
||||
- name: "mainpipe"
|
||||
desc: "取指模块"
|
||||
meta:
|
||||
doc_url: "https://open-verify.cc/UnityChipForXiangShan/docs/98_ut/01_frontend/04_icache/02_mainpipe/"
|
||||
- name: "waylookup"
|
||||
desc: "元数据缓冲队列"
|
||||
meta:
|
||||
doc_url: "https://open-verify.cc/UnityChipForXiangShan/docs/98_ut/01_frontend/04_icache/03_waylookup/"
|
||||
- name: "missunit"
|
||||
desc: "缺失处理单元"
|
||||
meta:
|
||||
doc_url: "https://open-verify.cc/UnityChipForXiangShan/docs/98_ut/01_frontend/04_icache/04_missunit/"
|
||||
- name: "ctrlunit"
|
||||
desc: "控制单元"
|
||||
meta:
|
||||
doc_url: "https://open-verify.cc/UnityChipForXiangShan/docs/98_ut/01_frontend/04_icache/05_ctrlunit/"
|
||||
- name: "icache"
|
||||
desc: "icache顶层模块"
|
||||
meta:
|
||||
doc_url: "https://open-verify.cc/UnityChipForXiangShan/docs/98_ut/01_frontend/04_icache/06_icache/"
|
||||
- name: "ifu"
|
||||
desc: "指令单元 (Instruction Fetch Unit)"
|
||||
children:
|
||||
|
|
@ -77,6 +101,11 @@ children:
|
|||
desc: "指令非缓存"
|
||||
- name: "itlb"
|
||||
desc: "指令TLB"
|
||||
- name: "l2tlb"
|
||||
desc: "l2 TLB"
|
||||
children:
|
||||
- name: "ptw"
|
||||
desc: "Page Table Walker模块"
|
||||
- name: "pmp"
|
||||
desc: "物理内存保护"
|
||||
- name: "backend"
|
||||
|
|
@ -181,23 +210,16 @@ children:
|
|||
desc: "Load/Store队列"
|
||||
priority: critical
|
||||
children:
|
||||
- name: "load_queue"
|
||||
desc: "Load队列"
|
||||
- name: "load_queue"
|
||||
desc: "Load队列"
|
||||
children:
|
||||
- name: "virtual_load_queue"
|
||||
desc: "虚拟Load队列"
|
||||
- name: "rar_queue"
|
||||
desc: "RAR队列"
|
||||
- name: "raw_queue"
|
||||
desc: "RAW队列"
|
||||
- name: "replay_queue"
|
||||
desc: "重发队列"
|
||||
- name: "uncache_queue"
|
||||
desc: "非缓存队列"
|
||||
- name: "exception_queue"
|
||||
desc: "异常队列"
|
||||
- name: "virtual_load_queue"
|
||||
desc: "虚拟Load队列"
|
||||
- name: "rar_queue"
|
||||
desc: "RAR队列"
|
||||
- name: "raw_queue"
|
||||
desc: "RAW队列"
|
||||
- name: "replay_queue"
|
||||
desc: "重发队列"
|
||||
- name: "uncache_queue"
|
||||
desc: "非缓存队列"
|
||||
- name: "store_queue"
|
||||
desc: "Store队列"
|
||||
- name: "dtlb"
|
||||
|
|
@ -225,8 +247,6 @@ children:
|
|||
desc: "向量长度合并缓冲器"
|
||||
- name: "vs_merge_buffer"
|
||||
desc: "向量段合并缓冲器"
|
||||
- name: "ptw"
|
||||
desc: "页表Walker"
|
||||
- name: "uncache"
|
||||
desc: "非缓存"
|
||||
- name: "sbuffer"
|
||||
|
|
|
|||
15
dir_map.f
15
dir_map.f
|
|
@ -1,15 +0,0 @@
|
|||
backend_ctrl_block_decode --> DecodeStage --> ut_backend/ctrl_block/decode
|
||||
frontend_bpu_ittage --> ITTage --> ut_frontend/bpu/ittage
|
||||
frontend_bpu_tagesc --> Tage_SC --> ut_frontend/bpu/tagesc
|
||||
frontend_ifu_f3predecoder --> F3Predecoder --> ut_frontend/ifu/f3predecoder
|
||||
frontend_ifu_frontend_trigger --> FrontendTrigger --> ut_frontend/ifu/frontend_trigger
|
||||
frontend_ifu_pred_checker --> PredChecker --> ut_frontend/ifu/pred_checker
|
||||
frontend_ifu_predecode --> PreDecode --> ut_frontend/ifu/predecode
|
||||
frontend_ifu_rvc_expander --> RVCExpander --> ut_frontend/ifu/rvc_expander
|
||||
frontend_ifu_top --> NewIFU -->
|
||||
frontend_itlb --> TLB --> ut_frontend/itlb/classical_version
|
||||
frontend_itlb --> TLB --> ut_frontend/itlb/toffee_version
|
||||
frontend_tlb_fa --> TLBFA --> ut_frontend/itlb/submodules/TLBFA
|
||||
frontend_tlb_nonblock --> TLBNonBlock --> ut_frontend/itlb/submodules/TLBNonBlock
|
||||
frontend_tlb_storage_wrapper --> TlbStorageWrapper --> ut_frontend/itlb/submodules/TlbStorageWrapper
|
||||
frontend_tlbuffer --> TLBuffer --> ut_frontend/itlb/submodules/TLBuffer
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
init:
|
||||
curl -sL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
||||
sudo apt-get install -y nodejs
|
||||
sudo pip3 install hugo==0.124.1
|
||||
sudo pip3 install hugo==0.145.0
|
||||
sudo add-apt-repository ppa:longsleep/golang-backports
|
||||
sudo apt update
|
||||
sudo apt install golang-go
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
title: Target Verification Units
|
||||
linkTitle: Target Verification Units
|
||||
#menu: {main: {weight: 20}}
|
||||
weight: 12
|
||||
---
|
||||
|
||||
<script src="../../../js/echarts.min.js"></script>
|
||||
<script src="../../../js/chart_datatree.js"></script>
|
||||
<script>
|
||||
function update_dut_charts(data_url){
|
||||
show_datatree_chart("datatree_chart", data_url)
|
||||
}
|
||||
</script>
|
||||
|
||||
<br>
|
||||
|
||||
<div id="datatree_chart" style="width: 90%;height:800px;"></div>
|
||||
<div style="text-align: center; width: 100%;">
|
||||
{{<list-report baseurl="../../../data/reports" label="Current Version:" detail="View Test Report" id="dut" onchange="update_dut_charts">}}
|
||||
</div>
|
||||
<br>
|
||||
|
||||
In the above chart, there are a total of <em id="em_id_report_dut_total">-</em> modules. By default, modules are gray. When the number of test cases in a module exceeds <em id="em_id_report_dut_min_light">-</em>, the module is fully lit. Currently, <em id="em_id_report_dut_lighted">-</em> modules are fully lit, and <em id="em_id_report_dut_lighted_no">-</em> modules are yet to be lit.
|
||||
|
||||
#### Overview of General Processor Modules
|
||||
|
||||
High-performance processors are the core of modern computing devices. They usually consist of three main parts: the frontend, the backend, and the memory subsystem. These parts work together to ensure the processor can efficiently execute complex computational tasks.
|
||||
|
||||
- **Frontend**: The frontend, also known as the instruction fetch and decode stage, is responsible for fetching instructions from memory and decoding them into a format the processor can understand. This stage is critical to processor performance because it directly affects how quickly the processor can start executing instructions. The frontend typically includes an instruction cache, branch predictor, and instruction decoder. The instruction cache stores recently accessed instructions to reduce accesses to main memory, thus improving speed. The branch predictor tries to predict conditional branches in the program to fetch and decode subsequent instructions in advance, reducing the time spent waiting for branch results.
|
||||
|
||||
- **Backend**: The backend, also known as the execution stage, is where the processor actually executes instructions. This stage includes the Arithmetic Logic Unit (ALU), Floating Point Unit (FPU), and various execution units. These units handle arithmetic operations, logic operations, data transfers, and other processor operations. The backend design is usually very complex because it needs to support multiple instruction set architectures (ISA) and optimize performance. To improve efficiency, modern processors often use superscalar architectures, meaning they can execute multiple instructions simultaneously.
|
||||
|
||||
- **Memory Subsystem**: The memory subsystem is the bridge between the processor and memory. It includes data caches, memory controllers, and cache coherence protocols. Data caches store data frequently accessed by the processor to reduce accesses to main memory. The memory controller manages data transfers between the processor and memory. Cache coherence protocols ensure that in multiprocessor systems, all processors see a consistent memory state.
|
||||
|
||||
Designing high-performance processors requires balancing these three parts to achieve optimal performance. This often involves complex microarchitecture design and pipeline optimization.
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
---
|
||||
title: Prepare Verification Environment
|
||||
linkTitle: Prepare Verification Environment
|
||||
#menu: {main: {weight: 20}}
|
||||
weight: 13
|
||||
---
|
||||
|
||||
#### Basic Environment Requirements
|
||||
|
||||
This project uses the `Python` programming language for UT verification, with [picker](https://github.com/XS-MLVP/picker) and [toffee](https://github.com/XS-MLVP/toffee) as the main tools and test frameworks. **Environment requirements** are as follows:
|
||||
|
||||
1. Linux operating system. It is recommended to install Ubuntu 22.04 under WSL2.
|
||||
1. Python. Python 3.11 is recommended.
|
||||
1. picker. Install the latest version as instructed in the [Quick Start](https://open-verify.cc/mlvp/docs/quick-start/installer/).
|
||||
1. toffee. It will be installed automatically later. You can also manually install the latest version as instructed in the [Quick Start](https://open-verify.cc/mlvp/docs/mlvp/quick-start/).
|
||||
1. lcov. Used for report generation in the test stage. Install via package manager: `sudo apt install lcov`
|
||||
|
||||
**After environment setup**, clone the repository:
|
||||
```bash
|
||||
git clone https://github.com/XS-MLVP/UnityChipForXiangShan.git
|
||||
cd UnityChipForXiangShan
|
||||
pip3 install -r requirements.txt # Install python dependencies (e.g., toffee)
|
||||
```
|
||||
|
||||
#### Download RTL Code
|
||||
|
||||
By default, download from the repository [https://github.com/XS-MLVP/UnityChipXiangShanRTLs](https://github.com/XS-MLVP/UnityChipXiangShanRTLs). Users can also generate RTL by compiling according to the XiangShan documentation.
|
||||
|
||||
```bash
|
||||
make rtl # This command downloads the latest rtl code, unpacks it to the rtl directory, and creates a symlink
|
||||
```
|
||||
|
||||
You can specify the rtl version to download with the following command:
|
||||
|
||||
```bash
|
||||
make rtl args="rtl.version='openxiangshan-kmh-fad7803d-24120901'"
|
||||
```
|
||||
|
||||
All RTL download packages can be found at [UnityChipXiangShanRTLs](https://github.com/XS-MLVP/UnityChipXiangShanRTLs).
|
||||
|
||||
The naming convention for RTL archives is: `name-microarchitecture-GitTag-date.tar.gz`, for example, `openxiangshan-kmh-97e37a2237-24092701.tar.gz`. When used, the repository code will filter out the git tag and suffix, so the version accessed via cfg.rtl.version is: `openxiangshan-kmh-24092701`. The directory structure inside the archive is:
|
||||
|
||||
```bash
|
||||
openxiangshan-kmh-97e37a2237-24092701.tar.gz
|
||||
└── rtl # directory
|
||||
|-- *.sv # all sv files
|
||||
`-- *.v # all v files
|
||||
```
|
||||
|
||||
#### Compile DUT
|
||||
|
||||
The purpose of this process is to package the RTL into a Python module using the picker tool. You can specify the DUT to be packaged via the make command, or package all DUTs at once.
|
||||
|
||||
If you want to package a specific dut yourself, you need to create a script named build_ut_<name>.py in the scripts directory. This script must implement a build method, which will be called automatically during packaging. There is also a line_coverage_files method for specifying files used for line coverage reference.
|
||||
|
||||
Picker's packaging supports adding internal signals; See the --internal parameter of picker and pass a custom yaml.
|
||||
|
||||
```bash
|
||||
# Calls the build method in scripts/build_ut_<name>.py to create the Python DUT to be verified
|
||||
make dut DUTS=<name> # If there are multiple DUTS, separate them with commas. Wildcards are supported. The default value is "*", which compiles all DUTs.
|
||||
# Example:
|
||||
make dut DUTS=backend_ctrl_block_decode
|
||||
```
|
||||
|
||||
For example, after running `make dut DUTS=backend_ctrl_block_decode`, the corresponding Python package will be generated in the dut directory:
|
||||
|
||||
```
|
||||
dut/
|
||||
├── __init__.py
|
||||
├── DecodeStage
|
||||
├── Predecode
|
||||
└── RVCExpander
|
||||
```
|
||||
|
||||
After conversion, you can import the corresponding DUT in your test case code, for example:
|
||||
```python
|
||||
from dut.PreDecode import DUTPreDecode
|
||||
dut = DUTPreDecode()
|
||||
```
|
||||
|
||||
#### Edit Configuration
|
||||
|
||||
When running rtl, dut, test, and other commands, the default configuration is used from configs/_default.yaml.
|
||||
|
||||
Of course, you can also use a custom configuration as follows:
|
||||
|
||||
```bash
|
||||
# Specify a custom CFG file
|
||||
make CFG=path/to/your_cfg.yaml
|
||||
```
|
||||
|
||||
Similarly, you can specify key-value pairs directly on the command line. Currently, only the test-related stage supports command-line configuration key-value pairs:
|
||||
```bash
|
||||
# Specify KV, pass command-line arguments, separate key-value pairs with spaces
|
||||
make test KV="log.term-level='debug' test.skip-tags=['RARELY_USED']"
|
||||
```
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
title: Run Tests
|
||||
linkTitle: Run Tests
|
||||
#menu: {main: {weight: 20}}
|
||||
weight: 14
|
||||
---
|
||||
|
||||
This project uses the PyTest testing framework for verification. When running tests, the PyTest framework automatically searches for all `test_*.py` files and executes all test cases that start with `test_`.
|
||||
|
||||
```bash
|
||||
# Run all test cases in ut_* directories
|
||||
make test_all
|
||||
# Run test cases in the specified directory
|
||||
make test target=<dir>
|
||||
# For example, run all test cases in the ut_backend/ctrl_block/decode directory
|
||||
make test target=ut_backend/ctrl_block/decode
|
||||
```
|
||||
|
||||
You can pass Pytest runtime parameters via the `args` parameter, such as enabling the x-dist plugin for multi-core execution:
|
||||
|
||||
```bash
|
||||
make test args="-n 4" # Use 4 processes
|
||||
make test args="-n auto" # Let the framework automatically choose the number of processes
|
||||
```
|
||||
|
||||
*Note: x-dist can run tests concurrently on multiple nodes. See its [documentation](https://pytest-xdist.readthedocs.io/en/stable/remote.html) for details.
|
||||
|
||||
After running, an HTML version of the test report will be generated by default in the `out/report` directory. The HTML file can be opened directly in a browser (it is recommended to install the `Open In Default Browser` plugin in VS Code IDE).
|
||||
|
||||
Running tests mainly completes the following three parts:
|
||||
|
||||
1. Run Test Cases as required, which can be configured via options in `cfg.tests`
|
||||
1. Collect test results and output test reports. The toffee-report tool automatically generates (a total test report, merging results of all tests)
|
||||
1. Further data statistics on the test report as needed (`cfg.doc_result.disable = True`)
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
---
|
||||
title: Add Compilation Script
|
||||
linkTitle: Add Compilation Script
|
||||
#menu: {main: {weight: 20}}
|
||||
weight: 2
|
||||
---
|
||||
|
||||
## Script Target
|
||||
|
||||
Write a compilation file for the corresponding RTL in the `scripts` directory using Python (e.g., `build_ut_frontend_ifu_rvc_expander.py`).
|
||||
The goal of this script is to provide RTL-to-Python DUT compilation, target coverage files, and custom functionality.
|
||||
|
||||
## Creation Process
|
||||
|
||||
### Determine File Name
|
||||
|
||||
Select the UT to be verified in [XiangShan Kunming Lake DUT Verification Progress](). If it is not available or needs further refinement, you can manually add it by editing `configs/dutree/xiangshan-kmh.yaml`.
|
||||
For example, if we want to verify the `rvc_expander` module under the `ifu` module in the frontend, we need to add the corresponding part to `configs/dutree/xiangshan-kmh.yaml` (this module already exists in the YAML file; this is just an example):
|
||||
|
||||
```yaml
|
||||
name: "kmh_dut"
|
||||
desc: "All Kunming Lake DUTs"
|
||||
children:
|
||||
- name: "frontend"
|
||||
desc: "Frontend Module"
|
||||
children:
|
||||
- name: "ifu"
|
||||
desc: "Instruction Fetch Unit"
|
||||
children:
|
||||
- name: "rvc_expander"
|
||||
desc: "RVC Instruction Expander"
|
||||
```
|
||||
|
||||
The naming format for the script file is as follows:
|
||||
|
||||
```bash
|
||||
scripts/build_<top_module>_<sub_module>_..._<target_module>.py
|
||||
```
|
||||
|
||||
Currently, the project includes four top-level modules:
|
||||
|
||||
1. ut_frontend (Frontend)
|
||||
1. ut_backend (Backend)
|
||||
1. ut_mem_block (Memory Access)
|
||||
1. ut_misc (Miscellaneous)
|
||||
|
||||
Submodules do not have the `ut_` prefix (the top-level directories have this prefix to distinguish them from other directories).
|
||||
|
||||
For example, if the target DUT to be verified is the `rvc_expander` module:
|
||||
This module belongs to the frontend, so the top-level module is `ut_frontend`. Its submodule is `ifu`, and the target module is `rvc_expander`.
|
||||
From the previously opened `yaml` file, we can also see that the `children` of `frontend` is `ifu`, and the `children` of `ifu` is `rvc_expander`.
|
||||
Thus, the script name to be created is `build_ut_frontend_ifu_rvc_expander.py`.
|
||||
|
||||
### Write the `build(cfg) -> bool` Function
|
||||
|
||||
The `build` function is defined as follows:
|
||||
|
||||
```python
|
||||
def build(cfg) -> bool:
|
||||
"""Compile DUT
|
||||
Args:
|
||||
cfg: Runtime configuration, which can be used to access configuration items, e.g., cfg.rtl.version
|
||||
Return:
|
||||
Returns True or False, indicating whether the function achieved its intended goal
|
||||
"""
|
||||
```
|
||||
|
||||
The `build` function is called during `make dut`. Its main purpose is to convert the target RTL into a Python module. Other necessary processes, such as compiling dependencies, can also be added. For example, in `build_ut_frontend_ifu_rvc_expander.py`, the function primarily performs RTL checks, DUT checks, RTL compilation, and disasm dependency compilation:
|
||||
|
||||
```python
|
||||
import os
|
||||
from comm import warning, info
|
||||
|
||||
|
||||
def build(cfg):
|
||||
# Import related dependencies
|
||||
from toffee_test.markers import match_version
|
||||
from comm import is_all_file_exist, get_rtl_dir, exe_cmd, get_root_dir
|
||||
# Check RTL version (an empty version parameter means all versions are supported)
|
||||
if not match_version(cfg.rtl.version, "openxiangshan-kmh-*"):
|
||||
warning("ifu frontend rvc expander: %s" % f"Unsupported RTL version {cfg.rtl.version}")
|
||||
return False
|
||||
# Check if the target file exists in the current RTL
|
||||
f = is_all_file_exist(["rtl/RVCExpander.sv"], get_rtl_dir(cfg=cfg))
|
||||
assert f is True, f"File {f} not found"
|
||||
# If the DUT does not contain RVCExpander, use picker to package it into Python
|
||||
if not os.path.exists(get_root_dir("dut/RVCExpander")):
|
||||
info("Exporting RVCExpander.sv")
|
||||
s, out, err = exe_cmd(f'picker export --cp_lib false {get_rtl_dir("rtl/RVCExpander.sv", cfg=cfg)} --lang python --tdir {get_root_dir("dut")}/ -w rvc.fst -c')
|
||||
assert s, "Failed to export RVCExpander.sv: %s\n%s" % (out, err)
|
||||
# If disasm/build does not exist in tools, compile disasm
|
||||
if not os.path.exists(get_root_dir("tools/disasm/build")):
|
||||
info("Building disasm")
|
||||
s, _, _ = exe_cmd("make -C %s" % get_root_dir("tools/disasm"))
|
||||
assert s, "Failed to build disasm"
|
||||
# Compilation successful
|
||||
return True
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["RVCExpander.v"]
|
||||
```
|
||||
|
||||
For details on how to use `picker`, refer to its [documentation](https://github.com/XS-MLVP/picker/blob/master/README.zh.md) and [usage guide](https://open-verify.cc/mlvp/docs/env_usage/picker_usage/).
|
||||
|
||||
In the `scripts` directory, you can create subdirectories to store files needed for UT verification. For example, the `rvc_expander` module creates a `scripts/frontend_ifu_rvc_expander` directory, where `rtl_file.f` specifies the input RTL file, and `line_coverage.ignore` stores lines of code to be ignored in coverage statistics. Custom directory names should be reasonable and should indicate the module and file they belong to.
|
||||
|
||||
### Write the `line_coverage_files(cfg) -> list[str]` Function
|
||||
|
||||
The `line_coverage_files` function is defined as follows:
|
||||
|
||||
```python
|
||||
def line_coverage_files(cfg) -> list[str]:
|
||||
"""Specify files to be covered
|
||||
Args:
|
||||
cfg: Runtime configuration, which can be used to access configuration items, e.g., cfg.rtl.version
|
||||
Return:
|
||||
Returns the names of RTL files targeted for line coverage statistics
|
||||
"""
|
||||
```
|
||||
|
||||
In the `build_ut_frontend_ifu_rvc_expander.py` file, the `line_coverage_files` function is defined as follows:
|
||||
|
||||
```python
|
||||
def line_coverage_files(cfg):
|
||||
return ["RVCExpander.v"]
|
||||
```
|
||||
|
||||
This indicates that the module focuses on coverage for the `RVCExpander.v` file. If you want to enable test result processing, set `disable=False` under `doc-result` in `configs/_default.yaml` (the default parameter is `False`, meaning it is enabled). If you do not enable test result processing (`disable=True`), the above function will not be called.
|
||||
|
|
@ -0,0 +1,350 @@
|
|||
---
|
||||
title: Build Test Environment
|
||||
linkTitle: Build Test Environment
|
||||
#menu: {main: {weight: 20}}
|
||||
weight: 3
|
||||
---
|
||||
|
||||
## Determine Directory Structure
|
||||
|
||||
The directory structure of the Unit Test (UT) should match its naming convention. For example, `frontend.ifu.rvc_expander` should be located in the `ut_frontend/ifu/rvc_expander` directory, and each directory level must include an `__init__.py` file to enable Python imports.
|
||||
|
||||
**The file for this chapter is `your_module_wrapper.py`** (if your module is `rvc_expander`, the file would be `rvc_expander_wrapper.py`).
|
||||
|
||||
A wrapper is essentially a layer of abstraction that encapsulates the methods needed for testing into APIs decoupled from the DUT. These APIs are then used in test cases.
|
||||
|
||||
\*Note: Decoupling ensures that test cases are independent of the DUT, allowing them to be written and debugged without needing to know the DUT's implementation details. For more information, refer to [Decoupling Verification Code from the DUT](https://open-verify.cc/mlvp/docs/mlvp/canonical_env/#%E5%B0%86%E9%AA%8C%E8%AF%81%E4%BB%A3%E7%A0%81%E4%B8%8Edut%E8%BF%9B%E8%A1%8C%E8%A7%A3%E8%80%A6).
|
||||
|
||||
This file should be placed in the `ut_frontend_or_backend/top_module/your_module/env` directory. For example, if `rvc_expander` belongs to the frontend, its top-level directory should be `ut_frontend`. The next-level directory would be `ifu`, followed by `rvc_expander`. Since we are **building the test environment**, an additional `env` directory is created. The full path would be: `ut_frontend_or_backend/top_module/your_module/env`.
|
||||
|
||||
```shell
|
||||
ut_frontend/ifu/rvc_expander
|
||||
├── classical_version
|
||||
│ ├── env
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── rvc_expander_wrapper.py
|
||||
│ ├── __init__.py
|
||||
│ └── test_rvc_expander.py
|
||||
├── __init__.py
|
||||
├── README.md
|
||||
└── toffee_version
|
||||
├── agent
|
||||
│ └── __init__.py
|
||||
├── bundle
|
||||
│ └── __init__.py
|
||||
├── env
|
||||
│ ├── __init__.py
|
||||
│ └── ref_rvc_expand.py
|
||||
├── __init__.py
|
||||
└── test
|
||||
├── __init__.py
|
||||
├── rvc_expander_fixture.py
|
||||
└── test_rvc.py
|
||||
```
|
||||
|
||||
In the `rvc_expander` directory, there are two versions: `classical_version` (traditional) and `toffee_version` (using Toffee).
|
||||
The traditional version uses the `pytest` framework for testing, while the Toffee version leverages more features of the Toffee framework.
|
||||
In general, **the traditional version is sufficient for most cases**, and the Toffee version is only needed when the traditional version cannot meet the requirements.
|
||||
When building the test environment, **choose one version**.
|
||||
The directory structure within a module (e.g., `rvc_expander`) is determined by the contributor. You do **not** need to create additional `classical_version` or `toffee_version` directories, but the structure must comply with Python standards and be logically and consistently named.
|
||||
|
||||
## Env Requirements
|
||||
|
||||
- Perform RTL version checks.
|
||||
- The APIs provided by Env must be independent of pins and timing.
|
||||
- The APIs provided by Env must be stable and should not undergo arbitrary changes in interfaces or return values.
|
||||
- Define necessary fixtures.
|
||||
- Initialize functional checkpoints (functional checkpoints can be independent modules).
|
||||
- Perform coverage statistics.
|
||||
- Include documentation.
|
||||
|
||||
## Building the Test Environment: Traditional Version
|
||||
|
||||
In the test environment for the UT verification module, the goal is to accomplish the following:
|
||||
|
||||
1. Encapsulate DUT functionality to provide stable APIs for testing.
|
||||
2. Define functional coverage.
|
||||
3. Define necessary fixtures for test cases.
|
||||
4. Collect coverage statistics at appropriate times.
|
||||
|
||||
Taking the RVCExpander in the IFU environment as an example (`ut_frontend/ifu/rvc_expander/classical_version/env/rvc_expander_wrapper.py`):
|
||||
|
||||
### 1. DUT Encapsulation
|
||||
|
||||
The following content is located in `ut_frontend/ifu/rvc_expander/classical_version/env/rvc_expander_wrapper.py`.
|
||||
|
||||
```python
|
||||
class RVCExpander(toffee.Bundle):
|
||||
def __init__(self, cover_group, **kwargs):
|
||||
super().__init__()
|
||||
self.cover_group = cover_group
|
||||
self.dut = DUTRVCExpander(**kwargs) # Create DUT
|
||||
self.io = toffee.Bundle.from_prefix("io_", self.dut) # Bind pins using Bundle and prefix
|
||||
self.bind(self.dut) # Bind Bundle to DUT
|
||||
|
||||
def expand(self, instr, fsIsOff):
|
||||
self.io["in"].value = instr # Assign value to DUT pin
|
||||
self.io["fsIsOff"].value = fsIsOff # Assign value to DUT pin
|
||||
self.dut.RefreshComb() # Trigger combinational logic
|
||||
self.cover_group.sample() # Collect functional coverage statistics
|
||||
return self.io["out_bits"].value, self.io["ill"].value # Return result and illegal instruction flag
|
||||
|
||||
def stat(self): # Get current state
|
||||
return {
|
||||
"instr": self.io["in"].value, # Input instruction
|
||||
"decode": self.io["out_bits"].value, # Decoded result
|
||||
"illegal": self.io["ill"].value != 0, # Whether the input is illegal
|
||||
}
|
||||
```
|
||||
|
||||
In the example above, `class RVCExpander` encapsulates `DUTRVCExpander` and provides two APIs:
|
||||
|
||||
- `expand(instr: int, fsIsOff: bool) -> (int, int)`: Accepts an input instruction `instr` for decoding and returns `(result, illegal instruction flag)`. If the illegal instruction flag is non-zero, the input instruction is illegal.
|
||||
- `stat() -> dict(instr, decode, illegal)`: Returns the current state, including the input instruction, decoded result, and illegal instruction flag.
|
||||
|
||||
These APIs **abstract away the DUT's pins**, exposing only general functionality to external programs.
|
||||
|
||||
### 2. Define Functional Coverage
|
||||
|
||||
Define functional coverage in the environment whenever possible. If necessary, coverage can also be defined in test cases. For details on defining functional coverage with Toffee, refer to [What is Functional Coverage](http://localhost:1313/docs/03_add_test/05_cover_func/). To establish a clear relationship between functional checkpoints and test cases, functional coverage definitions should be linked to test cases (reverse marking).
|
||||
|
||||
The following content is located in `ut_frontend/ifu/rvc_expander/classical_version/env/rvc_expander_wrapper.py`.
|
||||
|
||||
```python
|
||||
import toffee.funcov as fc
|
||||
# Create a functional coverage group
|
||||
g = fc.CovGroup(UT_FCOV("../../../CLASSIC"))
|
||||
|
||||
def init_rvc_expander_funcov(expander, g: fc.CovGroup):
|
||||
"""Add watch points to the RVCExpander module to collect functional coverage information"""
|
||||
|
||||
# 1. Add point RVC_EXPAND_RET to check expander return value:
|
||||
# - bin ERROR: The instruction is not illegal
|
||||
# - bin SUCCE: The instruction is not expanded
|
||||
g.add_watch_point(expander, {
|
||||
"ERROR": lambda x: x.stat()["illegal"] == False,
|
||||
"SUCCE": lambda x: x.stat()["illegal"] != False,
|
||||
}, name="RVC_EXPAND_RET")
|
||||
...
|
||||
# 5. Reverse mark functional coverage to the checkpoint
|
||||
def _M(name):
|
||||
# Get the module name
|
||||
return module_name_with(name, "../../test_rv_decode")
|
||||
|
||||
# - Mark RVC_EXPAND_RET
|
||||
g.mark_function("RVC_EXPAND_RET", _M(["test_rvc_expand_16bit_full",
|
||||
"test_rvc_expand_32bit_full",
|
||||
"test_rvc_expand_32bit_randomN"]), bin_name=["ERROR", "SUCCE"])
|
||||
...
|
||||
```
|
||||
|
||||
In the code above, a functional checkpoint named `RVC_EXPAND_RET` is added to check whether the `RVCExpander` module can return illegal instructions. The checkpoint requires both `ERROR` and `SUCCE` conditions to be met, meaning the `illegal` field in `stat()` must have both `True` and `False` values. After defining the checkpoint, the `mark_function` method is used to link it to the relevant test cases.
|
||||
|
||||
### 3. Define Necessary Fixtures
|
||||
|
||||
The following content is located in `ut_frontend/ifu/rvc_expander/classical_version/env/rvc_expander_wrapper.py`.
|
||||
|
||||
```python
|
||||
version_check = get_version_checker("openxiangshan-kmh-*") # Specify the required RTL version
|
||||
@pytest.fixture()
|
||||
def rvc_expander(request):
|
||||
version_check() # Perform version check
|
||||
fname = request.node.name # Get the name of the test case using this fixture
|
||||
wave_file = get_out_dir("decoder/rvc_expander_%s.fst" % fname) # Set waveform file path
|
||||
coverage_file = get_out_dir("decoder/rvc_expander_%s.dat" % fname) # Set code coverage file path
|
||||
coverage_dir = os.path.dirname(coverage_file)
|
||||
os.makedirs(coverage_dir, exist_ok=True) # Create directory if it doesn't exist
|
||||
expander = RVCExpander(g, coverage_filename=coverage_file, waveform_filename=wave_file)
|
||||
# Create RVCExpander
|
||||
expander.dut.io_in.AsImmWrite() # Set immediate write timing for io_in pin
|
||||
expander.dut.io_fsIsOff.AsImmWrite() # Set immediate write timing for io_fsIsOff pin
|
||||
init_rvc_expander_funcov(expander, g) # Initialize functional checkpoints
|
||||
yield expander # Return the created RVCExpander to the test case
|
||||
expander.dut.Finish() # End DUT after the test case is executed
|
||||
set_line_coverage(request, coverage_file) # Report code coverage file to toffee-report
|
||||
set_func_coverage(request, g) # Report functional coverage data to toffee-report
|
||||
g.clear() # Clear functional coverage statistics
|
||||
```
|
||||
|
||||
This fixture accomplishes the following:
|
||||
|
||||
1. Performs RTL version checks. If the version does not meet the `"openxiangshan-kmh-*"` requirement, the test case using this fixture is skipped.
|
||||
2. Creates the DUT and specifies the paths for waveform and code coverage files (the paths include the name of the test case using the fixture: `fname`).
|
||||
3. Calls `init_rvc_expander_funcov` to add functional coverage points.
|
||||
4. Ends the DUT and processes code and functional coverage (sending them to `toffee-report` for processing).
|
||||
5. Clears functional coverage statistics.
|
||||
|
||||
\*Note: In PyTest, before executing a test case like `test_A(rvc_expander, ...)`, (**rvc_expander is the method name we defined when we used the fixure decorator**), the part of `rvc_expander(request)` before the `yield` keyword will be automatically called and executed (which is equivalent to initialization). and then `rvc_expander` will be returned to call the `test_A` case via `yield` (**the object returned by yield is the method name we defined in our fixture of the test case**). After the execution of the case is completed, then continue to execute the part of the `fixture` after the `field` keyword. For example: refer to the following code of statistical coverage, the penultimate line of `rvc_expand(rvc_expander, generate_rvc_instructions(start, end))`, where `rvc_expander` is the name of the method that we defined in the `fixture`, that is, the `yield` return object.
|
||||
|
||||
### 4. Collect Coverage Statistics
|
||||
|
||||
The following content is located in `ut_frontend/ifu/rvc_expander/classical_version/test_rvc_expander.py`.
|
||||
|
||||
```python
|
||||
N = 10
|
||||
T = 1 << 16
|
||||
@pytest.mark.toffee_tags(TAG_LONG_TIME_RUN)
|
||||
@pytest.mark.parametrize("start,end",
|
||||
[(r * (T // N), (r + 1) * (T // N) if r < N - 1 else T) for r in range(N)])
|
||||
def test_rvc_expand_16bit_full(rvc_expander, start, end):
|
||||
"""Test the RVC expand function with a full compressed instruction set
|
||||
|
||||
Description:
|
||||
Perform an expand check on 16-bit compressed instructions within the range from 'start' to 'end'.
|
||||
"""
|
||||
# Add checkpoint: RVC_EXPAND_RANGE to check expander input range.
|
||||
# When run to here, the range[start, end] is covered
|
||||
covered = -1
|
||||
g.add_watch_point(rvc_expander, {
|
||||
"RANGE[%d-%d]" % (start, end): lambda _: covered == end
|
||||
}, name="RVC_EXPAND_ALL_16B", dynamic_bin=True)
|
||||
# Reverse mark function to the checkpoint
|
||||
g.mark_function("RVC_EXPAND_ALL_16B", test_rvc_expand_16bit_full, bin_name="RANGE[%d-%d]" % (start, end))
|
||||
# Drive the expander and check the result
|
||||
rvc_expand(rvc_expander, generate_rvc_instructions(start, end))
|
||||
# When go to here, the range[start, end] is covered
|
||||
covered = end
|
||||
g.sample() # Sample coverage
|
||||
```
|
||||
|
||||
After defining coverage, it must be collected in the test cases. In the code above, a functional checkpoint `rvc_expander` is added in the test case using `add_watch_point`. The checkpoint is then marked and sampled. Coverage sampling triggers a callback function to evaluate the `bins` defined in `add_watch_point`. If any `bins`'s condition evaluates to `True`, it is counted as a `pass`.
|
||||
|
||||
## Building the Test Environment: Toffee Version
|
||||
|
||||
Testing with Python can be enhanced by using our open-source testing framework [Toffee](https://github.com/XS-MLVP/toffee).
|
||||
|
||||
The official Toffee tutorial can be found [here](https://open-verify.cc/mlvp/docs/mlvp/).
|
||||
|
||||
### Bundle: Quick DUT Encapsulation
|
||||
|
||||
Toffee uses Bundles to bind to DUTs. It provides multiple methods for establishing Bundle-to-DUT bindings. Relevant code can be found in `ut_frontend/ifu/rvc_expander/toffee_version/bundle`.
|
||||
|
||||
#### Manual Binding
|
||||
|
||||
In the Toffee framework, the lowest-level class supporting pin binding is `Signal`, which binds to DUT pins using name matching. For example, consider the simplest RVCExpander with the following I/O pins:
|
||||
|
||||
```verilog
|
||||
module RVCExpander(
|
||||
input [31:0] io_in,
|
||||
input io_fsIsOff,
|
||||
output [31:0] io_out_bits,
|
||||
output io_ill
|
||||
);
|
||||
```
|
||||
|
||||
There are four signals: `io_in`, `io_fsIsOff`, `io_out_bits`, and `io_ill`. A common prefix, such as `io_`, can be extracted (note that `in` cannot be used directly as a variable name in Python). The remaining parts can be defined as pin names in the corresponding Bundle class:
|
||||
|
||||
```python
|
||||
class RVCExpanderIOBundle(Bundle):
|
||||
_in, _fsIsOff, _out_bits, _ill = Signals(4)
|
||||
```
|
||||
|
||||
In a higher-level Env or Bundle, the `from_prefix` method can be used to complete the prefix binding:
|
||||
|
||||
```python
|
||||
self.agent = RVCExpanderAgent(RVCExpanderIOBundle.from_prefix("io").bind(dut))
|
||||
```
|
||||
|
||||
#### Automatic Bundle Definition
|
||||
|
||||
The Bundle class definition can also be omitted by using prefix binding:
|
||||
|
||||
```python
|
||||
self.io = toffee.Bundle.from_prefix("io_", self.dut) # Bind pins using Bundle and prefix
|
||||
self.bind(self.dut)
|
||||
```
|
||||
|
||||
If the `from_prefix` method is passed a DUT, it automatically generates pin definitions based on the prefix and DUT pin names. Accessing the pins can then be done using a dictionary-like approach:
|
||||
|
||||
```python
|
||||
self.io["in"].value = instr
|
||||
self.io["fsIsOff"].value = False
|
||||
```
|
||||
|
||||
#### Bundle Code Generation
|
||||
|
||||
The Toffee framework's [scripts](https://github.com/XS-MLVP/toffee/tree/master/scripts) provide two scripts.
|
||||
|
||||
The `bundle_code_gen.py` script offers three methods:
|
||||
|
||||
```python
|
||||
def gen_bundle_code_from_dict(bundle_name: str, dut, dict: dict, max_width: int = 120)
|
||||
def gen_bundle_code_from_prefix(bundle_name: str, dut, prefix: str = "", max_width: int = 120)
|
||||
def gen_bundle_code_from_regex(bundle_name: str, dut, regex: str, max_width: int = 120)
|
||||
```
|
||||
|
||||
These methods generate Bundle code by passing in a DUT and generation rules (dict, prefix, or regex).
|
||||
|
||||
The `bundle_code_intel_gen.py` script parses the `signals.json` file generated by Picker to automatically generate hierarchical Bundle code. It can be invoked from the command line:
|
||||
|
||||
```bash
|
||||
python bundle_code_intel_gen.py [signal] [target]
|
||||
```
|
||||
|
||||
If you encounter bugs in the auto-generation scripts, feel free to submit an issue for us to fix.
|
||||
|
||||
### Agent: Driving Methods
|
||||
|
||||
If Bundles abstract the data responsibilities of a DUT, Agents encapsulate its behavioral responsibilities into interfaces. Simply put, an Agent provides multiple methods that abstract groups of I/O operations into specific behaviors:
|
||||
|
||||
```python
|
||||
class RVCExpanderAgent(Agent):
|
||||
def __init__(self, bundle: RVCExpanderIOBundle):
|
||||
super().__init__(bundle)
|
||||
self.bundle = bundle
|
||||
|
||||
@driver_method()
|
||||
async def expand(self, instr, fsIsOff): # Accepts RVC instruction and fs.status enable flag
|
||||
self.bundle._in.value = instr # Assign value to pin
|
||||
self.bundle._fsIsOff.value = fsIsOff # Assign value to pin
|
||||
|
||||
await self.bundle.step() # Trigger clock
|
||||
return self.bundle._out_bits.value, # Return expanded instruction
|
||||
self.bundle._ill.value # Return legality check
|
||||
```
|
||||
|
||||
For example, the RVCExpander's instruction expansion function accepts an input instruction (which could be an RVI or RVC instruction) and the CSR's enable flag for `fs.status`. This functionality is abstracted into the `expand` method, which takes two parameters in addition to `self`. The method returns the corresponding RVI instruction and a legality check for the input instruction.
|
||||
|
||||
### Env: Test Environment
|
||||
|
||||
```python
|
||||
class RVCExpanderEnv(Env):
|
||||
def __init__(self, dut: DUTRVCExpander):
|
||||
super().__init__()
|
||||
dut.io_in.xdata.AsImmWrite()
|
||||
dut.io_fsIsOff.xdata.AsImmWrite() # Set pin write timing
|
||||
self.agent = RVCExpanderAgent(RVCExpanderIOBundle.from_prefix("io").bind(dut)) # Complete prefix and bind DUT
|
||||
```
|
||||
|
||||
### Coverage Definition
|
||||
|
||||
The method for defining coverage groups is similar to the one described earlier and will not be repeated here.
|
||||
|
||||
### Test Suite Definition
|
||||
|
||||
The definition of test suites differs slightly:
|
||||
|
||||
```python
|
||||
@toffee_test.fixture
|
||||
async def rvc_expander(toffee_request: toffee_test.ToffeeRequest):
|
||||
import asyncio
|
||||
version_check()
|
||||
dut = toffee_request.create_dut(DUTRVCExpander)
|
||||
start_clock(dut)
|
||||
init_rvc_expander_funcov(dut, gr)
|
||||
|
||||
toffee_request.add_cov_groups([gr])
|
||||
expander = RVCExpanderEnv(dut)
|
||||
yield expander
|
||||
|
||||
cur_loop = asyncio.get_event_loop()
|
||||
for task in asyncio.all_tasks(cur_loop):
|
||||
if task.get_name() == "__clock_loop":
|
||||
task.cancel()
|
||||
try:
|
||||
await task
|
||||
except asyncio.CancelledError:
|
||||
break
|
||||
```
|
||||
|
||||
Due to Toffee's more powerful coverage management features, manual line coverage settings are not needed. Additionally, because of Toffee's clock mechanism, it is recommended to check if all tasks have ended at the end of the suite code.
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
---
|
||||
title: Add Test Cases
|
||||
linkTitle: Add Test Cases
|
||||
#menu: {main: {weight: 20}}
|
||||
weight: 4
|
||||
---
|
||||
|
||||
## Naming Requirements
|
||||
|
||||
All test case files should be named in the format `test_*.py`, where `*` is replaced with the test target (e.g., `test_rvc_expander.py`). All test cases should also start with the `test_` prefix. The test case names must have clear and meaningful descriptions.
|
||||
|
||||
Examples of naming:
|
||||
|
||||
```python
|
||||
def test_a(): # Not acceptable, as "a" does not indicate the test target
|
||||
pass
|
||||
|
||||
def test_rvc_expand_16bit_full(): # Acceptable, as the name indicates the test content
|
||||
pass
|
||||
```
|
||||
|
||||
## Using Assert
|
||||
|
||||
Each test case must use `assert` to determine whether the test passes.
|
||||
`pytest` relies on the results of `assert` statements, so these statements must ensure correctness.
|
||||
|
||||
The following content is located in `ut_frontend/ifu/rvc_expander/classical_version/test_rvc_expander.py`:
|
||||
|
||||
```python
|
||||
def rvc_expand(rvc_expander, ref_insts, is_32bit=False, fsIsOff=False):
|
||||
"""Compare the RVC expand result with the reference
|
||||
|
||||
Args:
|
||||
rvc_expander (wrapper): the fixture of the RVC expander
|
||||
ref_insts (list[int]): the reference instruction list
|
||||
"""
|
||||
find_error = 0
|
||||
for insn in ref_insts:
|
||||
insn_disasm = disasmbly(insn)
|
||||
value, instr_ex = rvc_expander.expand(insn, fsIsOff)
|
||||
if is_32bit:
|
||||
assert value == insn, "RVC expand error, 32-bit instruction must remain unchanged"
|
||||
if (insn_disasm == "unknown") and (instr_ex == 0):
|
||||
debug(f"Found bad instruction: {insn}, ref: 1, dut: 0")
|
||||
find_error += 1
|
||||
elif (insn_disasm != "unknown") and (instr_ex == 1):
|
||||
if (instr_filter(insn_disasm) != 1):
|
||||
debug(f"Found bad instruction: {insn}, disasm: {insn_disasm}, ref: 0, dut: 1")
|
||||
find_error += 1
|
||||
assert find_error == 0, f"RVC expand error ({find_error} errors)"
|
||||
```
|
||||
|
||||
## Writing Comments
|
||||
|
||||
Each test case must include necessary explanations and comments, adhering to the [Python Docstring Conventions](https://peps.python.org/pep-0257/).
|
||||
|
||||
Example format for test case documentation:
|
||||
|
||||
```python
|
||||
def test_<name>(a: type_a, b: type_b):
|
||||
"""Test abstract
|
||||
|
||||
Args:
|
||||
a (type_a): Description of argument a.
|
||||
b (type_b): Description of argument b.
|
||||
|
||||
Detailed test description here (if needed).
|
||||
"""
|
||||
...
|
||||
```
|
||||
|
||||
## Test Case Management
|
||||
|
||||
To facilitate test case management, use the `@pytest.mark.toffee_tags` tag feature provided by `toffee-test`. Refer to the [Other](https://open-verify.cc/UnityChipForXiangShan/docs/98_others/) section of this site and the [toffee-test documentation](https://github.com/XS-MLVP/toffee-test/blob/master/README_zh.md#%E7%AE%A1%E7%90%86%E6%B5%8B%E8%AF%95%E7%94%A8%E4%BE%8B%E8%B5%84%E6%BA%90).
|
||||
|
||||
## Reference Test Cases
|
||||
|
||||
If many test cases share the same operations, the common parts can be extracted into a utility function. For example, in RVCExpander verification, the comparison of compressed instruction expansion with the reference model (`disasm`) can be encapsulated into the following function:
|
||||
|
||||
The following content is located in `ut_frontend/ifu/rvc_expander/classical_version/test_rvc_expander.py`:
|
||||
|
||||
```python
|
||||
def rvc_expand(rvc_expander, ref_insts, is_32bit=False, fsIsOff=False):
|
||||
"""Compare the RVC expand result with the reference
|
||||
|
||||
Args:
|
||||
rvc_expander (wrapper): the fixture of the RVC expander
|
||||
ref_insts (list[int]): the reference instruction list
|
||||
"""
|
||||
find_error = 0
|
||||
for insn in ref_insts:
|
||||
insn_disasm = disasmbly(insn)
|
||||
value, instr_ex = rvc_expander.expand(insn, fsIsOff)
|
||||
if is_32bit:
|
||||
assert value == insn, "RVC expand error, 32-bit instruction must remain unchanged"
|
||||
if (insn_disasm == "unknown") and (instr_ex == 0):
|
||||
debug(f"Found bad instruction: {insn}, ref: 1, dut: 0")
|
||||
find_error += 1
|
||||
elif (insn_disasm != "unknown") and (instr_ex == 1):
|
||||
if (instr_filter(insn_disasm) != 1):
|
||||
debug(f"Found bad instruction: {insn}, disasm: {insn_disasm}, ref: 0, dut: 1")
|
||||
find_error += 1
|
||||
assert find_error == 0, f"RVC expand error ({find_error} errors)"
|
||||
```
|
||||
|
||||
The above utility function includes `assert` statements, so the test cases calling this function can also rely on these assertions to determine the results.
|
||||
|
||||
During test case development, debugging is often required. To quickly set up the verification environment, "smoke tests" can be written for debugging. For example, a smoke test for expanding 16-bit compressed instructions in RVCExpander is as follows:
|
||||
|
||||
```python
|
||||
@pytest.mark.toffee_tags(TAG_SMOKE)
|
||||
def test_rvc_expand_16bit_smoke(rvc_expander):
|
||||
"""Test the RVC expand function with 1 compressed instruction"""
|
||||
rvc_expand(rvc_expander, generate_rvc_instructions(start=100, end=101))
|
||||
```
|
||||
|
||||
For easier management, the above test case is tagged with the `SMOKE` label using `toffee_tags`. Its input parameter is `rvc_expander`, which will automatically invoke the corresponding `fixture` with the same name during runtime.
|
||||
|
||||
The goal of testing 16-bit compressed instructions in RVCExpander is to traverse all 2^16 compressed instructions and verify that all cases match the reference model (`disasm`). If a single test is used for traversal, it would take a significant amount of time. To address this, we can use `pytest`'s `parametrize` feature to configure test parameters and execute them in parallel using the `pytest-xdist` plugin:
|
||||
|
||||
The following content is located in `ut_frontend/ifu/rvc_expander/classical_version/test_rvc_expander.py`:
|
||||
|
||||
```python
|
||||
N = 10
|
||||
T = 1 << 16
|
||||
@pytest.mark.toffee_tags(TAG_LONG_TIME_RUN)
|
||||
@pytest.mark.parametrize("start,end",
|
||||
[(r * (T // N), (r + 1) * (T // N) if r < N - 1 else T) for r in range(N)])
|
||||
def test_rvc_expand_16bit_full(rvc_expander, start, end):
|
||||
"""Test the RVC expand function with a full compressed instruction set
|
||||
|
||||
Description:
|
||||
Perform an expand check on 16-bit compressed instructions within the range from 'start' to 'end'.
|
||||
"""
|
||||
# Add checkpoint: RVC_EXPAND_RANGE to check expander input range.
|
||||
# When run to here, the range [start, end] is covered
|
||||
g.add_watch_point(rvc_expander, {
|
||||
"RANGE[%d-%d]" % (start, end): lambda _: True
|
||||
}, name="RVC_EXPAND_ALL_16B").sample()
|
||||
|
||||
# Reverse mark function to the checkpoint
|
||||
g.mark_function("RVC_EXPAND_ALL_16B", test_rvc_expand_16bit_full, bin_name="RANGE[%d-%d]" % (start, end))
|
||||
|
||||
# Drive the expander and check the result
|
||||
rvc_expand(rvc_expander, generate_rvc_instructions(start, end))
|
||||
```
|
||||
|
||||
In the above test case, the parameters `start` and `end` are defined to specify the range of compressed instructions. These parameters are grouped and assigned using the `@pytest.mark.parametrize` decorator. The variable `N` specifies the number of groups for the target data, with a default of 10 groups. During runtime, the test case `test_rvc_expand_16bit_full` will expand into 10 test cases, such as `test_rvc_expand_16bit_full[0-6553]` to `test_rvc_expand_16bit_full[58977-65536]`.
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
---
|
||||
title: Code Coverage
|
||||
linkTitle: Code Coverage
|
||||
#menu: {main: {weight: 20}}
|
||||
weight: 5
|
||||
---
|
||||
|
||||
Code coverage is a metric that measures which parts of the tested code have been executed and which parts have not. By analyzing code coverage, the effectiveness and thoroughness of testing can be evaluated.
|
||||
|
||||
Code coverage includes:
|
||||
|
||||
- **Line Coverage**: The number of lines executed in the tested code. This is the simplest metric, and the goal is usually 100%.
|
||||
- **Branch Coverage**: Whether each branch of every control structure has been executed. For example, in an `if` statement, have both the `true` and `false` branches been executed?
|
||||
- **FSM Coverage**: Whether all states of a finite state machine have been reached.
|
||||
- **Toggle Coverage**: Tracks the toggling of signals in the tested code, ensuring that every circuit node has both `0 -> 1` and `1 -> 0` transitions.
|
||||
- **Path Coverage**: Examines the coverage of paths. In `always` or `initial` blocks, `if ... else` and `case` statements can create various data paths in the circuit structure.
|
||||
|
||||
\* The primary simulator used in this project is Verilator, with a focus on **line coverage**. Verilator supports coverage statistics, so when building the DUT, the `-c` option must be added to the compilation options to enable coverage statistics.
|
||||
|
||||
## Relevant Locations in This Project
|
||||
|
||||
To enable coverage, the `-c` option must be added during compilation (when using the `picker` command). Refer to the [Picker Parameter Explanation](https://github.com/XS-MLVP/picker/blob/master/README.zh.md#%E5%8F%82%E6%95%B0%E8%A7%A3%E9%87%8A). Additionally, the line coverage function must be implemented and enabled in the test files to generate coverage statistics during Toffee testing.
|
||||
|
||||
In conjunction with the above description, code coverage will be involved when compiling, writing and enabling line coverage functions and tests in this project:
|
||||
|
||||
### Adding Compilation Scripts
|
||||
|
||||
[Write the `build(cfg) -> bool` Function](01_build_script.md#write-the-buildcfg---bool-function)
|
||||
|
||||
```python
|
||||
# Omitted earlier code
|
||||
if not os.path.exists(get_root_dir("dut/RVCExpander")):
|
||||
info("Exporting RVCExpander.sv")
|
||||
s, out, err = exe_cmd(f'picker export --cp_lib false {get_rtl_dir("rtl/RVCExpander.sv", cfg=cfg)
|
||||
} --lang python --tdir {get_root_dir("dut")}/ -w rvc.fst -c')
|
||||
assert s, "Failed to export RVCExpander.sv: %s\n%s" % (out, err)
|
||||
# Omitted later code
|
||||
```
|
||||
|
||||
In the line `s, out, err=...`, the `picker` command is used with the `-c` option to enable code coverage.
|
||||
|
||||
[Set Target Coverage Files (`line_coverage_files` Function)](01_build_script.md#write-the-line_coverage_filescfg---liststr-function)
|
||||
|
||||
Write the `line_coverage_files(cfg) -> list[str]` function as needed, and enable test result processing (`doc_result.disable = False`) to ensure it is invoked.
|
||||
|
||||
### Building the Test Environment
|
||||
|
||||
[Define Necessary Fixtures](02_build_env.md#3-define-necessary-fixtures)
|
||||
|
||||
```python
|
||||
set_line_coverage(request, coverage_file) # Pass the generated code coverage file to toffee-report
|
||||
```
|
||||
|
||||
Use the `toffee-test.set_line_coverage` function to pass the coverage file to Toffee-Test, enabling it to collect data for generating reports with line coverage.
|
||||
|
||||
## Ignoring Specific Statistics
|
||||
|
||||
Sometimes, certain parts of the code may need to be excluded from coverage statistics. For example, some parts may not need to be tested, or it may be normal for certain parts to remain uncovered. Ignoring these parts can help optimize coverage reports or assist in debugging. Our framework supports two methods for ignoring coverage:
|
||||
|
||||
### 1. Using Verilator to Specify Ignored Sections
|
||||
|
||||
#### Using `verilator_coverage_off/on` Directives
|
||||
|
||||
Verilator supports ignoring specific code sections from coverage statistics using comment directives. For example:
|
||||
|
||||
```verilog
|
||||
// *verilator coverage_off*
|
||||
// Code section to ignore
|
||||
...
|
||||
// *verilator coverage_on*
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```verilog
|
||||
module example;
|
||||
always @(posedge clk) begin
|
||||
// *verilator coverage_off*
|
||||
if (debug_signal) begin
|
||||
$display("This is for debugging only");
|
||||
end
|
||||
// *verilator coverage_on*
|
||||
if (enable) begin
|
||||
do_something();
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
```
|
||||
|
||||
In the above example, the `debug_signal` section will not be included in coverage statistics, while the `enable` section will still be counted.
|
||||
|
||||
For more ways to ignore coverage in Verilator, refer to the [Verilator Documentation](https://veripool.org/guide/latest/exe_verilator.html#configuration-files).
|
||||
|
||||
### 2. Using Toffee to Specify Filters
|
||||
|
||||
```python
|
||||
def set_line_coverage(request, datfile, ignore=[]):
|
||||
"""Pass
|
||||
|
||||
Args:
|
||||
request (pytest.Request): Pytest's default fixture.
|
||||
datfile (string): The coverage file generated by the DUT.
|
||||
ignore (list[str]): Coverage filter files or directories.
|
||||
"""
|
||||
```
|
||||
|
||||
The `ignore` parameter can specify content to be filtered out from the coverage file. For example:
|
||||
|
||||
```python
|
||||
...
|
||||
set_line_coverage(request, coverage_file,
|
||||
get_root_dir("scripts/frontend_ifu_rvc_expander"))
|
||||
```
|
||||
|
||||
During coverage statistics, the `line_coverage.ignore` file in the `scripts/frontend_ifu_rvc_expander` directory will be searched, and its wildcard patterns will be used for filtering.
|
||||
|
||||
```ignore
|
||||
# Line coverage ignore file
|
||||
# Ignore Top file
|
||||
*/RVCExpander_top*%
|
||||
```
|
||||
|
||||
The above file indicates that files containing the keyword `RVCExpander_top` will be ignored during coverage statistics (the corresponding data is collected but excluded from the final report).
|
||||
|
||||
## Viewing Statistics Results
|
||||
|
||||
After completing all the steps, including preparing the test environment ([Download RTL Code](../01_verfiy_env.md#download-rtl-code), [Compile DUT](../01_verfiy_env.md#compile-dut), [Edit Configuration](../01_verfiy_env.md#edit-configuration)), and adding tests ([Add Compilation Scripts](01_build_script.md), [Build Test Environment](02_build_env.md), [Add Test Cases](03_add_test.md)):
|
||||
|
||||
Now, [Run Tests](../02_run_test.md). Afterward, an HTML version of the test report will be generated in the `out/report` directory by default.
|
||||
|
||||
You can also view the statistics results by selecting the corresponding test report (named by test time) under "Current Version" in the [Progress Overview](https://open-verify.cc/UnityChipForXiangShan/docs/) section and clicking the link on the right.
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
---
|
||||
title: Functional Coverage
|
||||
linkTitle: Functional Coverage
|
||||
#menu: {main: {weight: 20}}
|
||||
weight: 6
|
||||
---
|
||||
|
||||
Functional Coverage is a **user-defined** metric used to measure the proportion of design specifications executed during verification. Functional coverage focuses on whether the features and functionalities of the design have been covered by the test cases.
|
||||
|
||||
Mapping refers to associating functional points with test cases. This allows you to see which test cases correspond to each functional point during statistics, making it easier to identify which functional points have more test cases and which have fewer. This helps optimize test cases in the later stages.
|
||||
|
||||
## Relevant Locations in This Project
|
||||
|
||||
Functional coverage must be defined before it can be collected, primarily during the process of building the test environment.
|
||||
|
||||
In [Building the Test Environment](https://open-verify.cc/UnityChipForXiangShan/docs/03_add_test/02_build_env/):
|
||||
|
||||
- [Define Functional Coverage](02_build_env.md#2-define-functional-coverage): Create functional coverage groups, add watch points, and map them.
|
||||
- [Define Necessary Fixtures](02_build_env.md#3-define-necessary-fixtures): Pass the collected results to `toffee-report`.
|
||||
- [Collect Coverage](02_build_env.md#4-collect-coverage): Add watch points and mappings.
|
||||
|
||||
Other:
|
||||
|
||||
- Functional points can also be written in each test case for use in test cases.
|
||||
|
||||
## Functional Coverage Workflow
|
||||
|
||||
### Specify Group Name
|
||||
|
||||
The test report matches the Group name with the DUT name. Use `comm.UT_FCOV` to obtain the DUT prefix. For example, in the Python module `ut_frontend/ifu/rvc_expander/classical_version/env/rvc_expander_wrapper.py`, the following call is made:
|
||||
|
||||
```python
|
||||
from comm import UT_FCOV
|
||||
# Module name: ut_frontend.ifu.rvc_expander.classical_version.env.rvc_expander_wrapper
|
||||
# Remove classical_version and the parent module env, rvc_expander_wrapper using ../../../
|
||||
# UT_FCOV will automatically remove the prefix ut_
|
||||
g = fc.CovGroup(UT_FCOV("../../../CLASSIC"))
|
||||
# name = UT_FCOV("../../../CLASSIC")
|
||||
```
|
||||
|
||||
The value of `name` is `frontend.ifu.rvc_expander.CLASSIC`. When collecting the final results, the longest prefix will be matched to the target UT (i.e., matched to the `frontend.ifu.rvc_expander` module).
|
||||
|
||||
### Create Coverage Group
|
||||
|
||||
Use `toffee`'s `funcov` to create a coverage group.
|
||||
|
||||
```python
|
||||
import toffee.funcov as fc
|
||||
# Use the GROUP name specified above
|
||||
g = fc.CovGroup(name)
|
||||
```
|
||||
|
||||
These two steps can also be combined into one: `g = fc.CovGroup(UT_FCOV("../../../CLASSIC"))`.
|
||||
The created `g` object represents a functional coverage group, which can be used to provide watch points and mappings.
|
||||
|
||||
### Add Watch Points and Mappings
|
||||
|
||||
Inside each test case, you can use `add_watch_point` (or its alias `add_cover_point`, which is identical) to add watch points and `mark_function` to add mappings.
|
||||
A watch point is triggered when the signal meets the conditions defined in the watch point, and its name (i.e., the functional point) will be recorded in the functional coverage.
|
||||
A mapping associates functional points with test cases, allowing you to see which test cases correspond to each functional point during statistics.
|
||||
|
||||
The location of the watch point depends on the actual situation. Generally, adding watch points outside the test case is acceptable. However, sometimes more flexibility is required.
|
||||
|
||||
1. Outside the test case (in `decode_wrapper.py`):
|
||||
|
||||
```python
|
||||
def init_rvc_expander_funcov(expander, g: fc.CovGroup):
|
||||
"""Add watch points to the RVCExpander module to collect functional coverage information"""
|
||||
# 1. Add point RVC_EXPAND_RET to check expander return value:
|
||||
# - bin ERROR: The instruction is not illegal
|
||||
# - bin SUCCE: The instruction is not expanded
|
||||
g.add_watch_point(expander, {
|
||||
"ERROR": lambda x: x.stat()["ilegal"] == False,
|
||||
"SUCCE": lambda x: x.stat()["ilegal"] != False,
|
||||
}, name="RVC_EXPAND_RET")
|
||||
# 5. Reverse mark function coverage to the check point
|
||||
def _M(name):
|
||||
# Get the module name
|
||||
return module_name_with(name, "../../test_rv_decode")
|
||||
|
||||
# - mark RVC_EXPAND_RET
|
||||
g.mark_function("RVC_EXPAND_RET", _M(["test_rvc_expand_16bit_full",
|
||||
"test_rvc_expand_32bit_full",
|
||||
"test_rvc_expand_32bit_randomN"]), bin_name=["ERROR", "SUCCE"])
|
||||
|
||||
# The End
|
||||
return None
|
||||
```
|
||||
|
||||
In this example, the first `g.add_watch_point` is placed outside the test case because it is not directly related to the existing test cases. Placing it outside the test case is more convenient. Once the conditions in the `bins` of the `add_watch_point` method are triggered, the `toffee-test` framework will collect the corresponding functional points.
|
||||
|
||||
2. Inside the test case (in `test_rvc_expander.py`):
|
||||
|
||||
```python
|
||||
N = 10
|
||||
T = 1 << 32
|
||||
@pytest.mark.toffee_tags([TAG_LONG_TIME_RUN, TAG_RARELY_USED])
|
||||
@pytest.mark.parametrize("start,end",
|
||||
[(r * (T // N), (r + 1) * (T // N) if r < N - 1 else T) for r in range(N)])
|
||||
def test_rvc_expand_32bit_full(rvc_expander, start, end):
|
||||
"""Test the RVC expand function with a full 32-bit instruction set
|
||||
|
||||
Description:
|
||||
Randomly generate N 32-bit instructions for each check, and repeat the process K times.
|
||||
"""
|
||||
# Add check point: RVC_EXPAND_ALL_32B to check instr bits.
|
||||
covered = -1
|
||||
g.add_watch_point(rvc_expander, {"RANGE[%d-%d]" % (start, end): lambda _: covered == end},
|
||||
name="RVC_EXPAND_ALL_32B", dynamic_bin=True)
|
||||
# Reverse mark function to the check point
|
||||
g.mark_function("RVC_EXPAND_ALL_32B", test_rvc_expand_32bit_full)
|
||||
# Drive the expander and check the result
|
||||
rvc_expand(rvc_expander, list([_ for _ in range(start, end)]))
|
||||
# When reaching here, the range [start, end] is covered
|
||||
covered = end
|
||||
g.sample()
|
||||
```
|
||||
|
||||
In this example, the watch point is inside the test case because `start` and `end` are determined by `pytest.mark.parametrize`. Since the values are not fixed, the watch point needs to be added inside the test case.
|
||||
|
||||
### Sampling
|
||||
|
||||
At the end of the previous example, we called `g.sample()`. This function notifies `toffee-test` that the `bins` in `add_watch_point` have been executed. If the conditions are met, the watch point is recorded as a pass.
|
||||
|
||||
There is also an automatic sampling option. During the test environment setup, you can add `StepRis(lambda x: g.sample())` in the fixture definition. This will automatically sample at the rising edge of each clock cycle.
|
||||
|
||||
The following content is from `ut_backend/ctrl_block/decode/env/decode_wrapper.py`:
|
||||
|
||||
```python
|
||||
@pytest.fixture()
|
||||
def decoder(request):
|
||||
# Before test
|
||||
init_rv_decoder_funcov(g)
|
||||
func_name = request.node.name
|
||||
# If the output directory does not exist, create it
|
||||
output_dir_path = get_out_dir("decoder/log")
|
||||
os.makedirs(output_dir_path, exist_ok=True)
|
||||
decoder = Decode(DUTDecodeStage(
|
||||
waveform_filename=get_out_dir("decoder/decode_%s.fst" % func_name),
|
||||
coverage_filename=get_out_dir("decoder/decode_%s.dat" % func_name),
|
||||
))
|
||||
decoder.dut.InitClock("clock")
|
||||
decoder.dut.StepRis(lambda x: g.sample())
|
||||
yield decoder
|
||||
# After test
|
||||
decoder.dut.Finish()
|
||||
coverage_file = get_out_dir("decoder/decode_%s.dat" % func_name)
|
||||
if not os.path.exists(coverage_file):
|
||||
raise FileNotFoundError(f"File not found: {coverage_file}")
|
||||
set_line_coverage(request, coverage_file, get_root_dir("scripts/backend_ctrlblock_decode"))
|
||||
set_func_coverage(request, g)
|
||||
g.clear()
|
||||
```
|
||||
|
||||
As shown above, we call `g.sample()` before `yield`, enabling automatic sampling at the rising edge of each clock cycle.
|
||||
|
||||
The `StepRis` function executes the passed function at the rising edge of each clock cycle. For more details, refer to the [Picker Usage Guide](https://open-verify.cc/mlvp/docs/env_usage/picker_usage/).
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
title: Add Test
|
||||
linkTitle: Add Test
|
||||
#menu: {main: {weight: 20}}
|
||||
weight: 15
|
||||
---
|
||||
|
||||
To add a brand-new DUT test case, the following three steps need to be completed (this section uses the `rvc_expander` under the frontend `ifu` as an example):
|
||||
|
||||
1. **Add a compilation script**: Write a compilation file for the corresponding `rtl` in the `scripts` directory using `python` (e.g., `build_ut_frontend_ifu_rvc_expander.py`).
|
||||
1. **Build the test environment**: Create the target test UT directory in the appropriate location (e.g., `ut_frontend/ifu/rvc_expander`). If necessary, add the basic tools required for the DUT test in modules such as `tools` or `comm`.
|
||||
1. **Add test cases**: Add test cases in the UT directory following the [PyTest specification](https://docs.pytest.org/en/stable/).
|
||||
|
||||
If you are adding content to an existing DUT test, simply follow the original directory structure.
|
||||
|
||||
For information on how to perform Python chip verification using the picker and toffee libraries, refer to: [https://open-verify.cc/mlvp/docs](https://open-verify.cc/mlvp/docs)
|
||||
|
||||
When testing, you also need to pay attention to the following:
|
||||
|
||||
1. **UT Module Description**: Add a `README.md` file in the top-level folder of the added module to provide an explanation. For specific formats and requirements, refer to the [template](https://open-verify.cc/UnityChipForXiangShan/docs/10_template_ut_readme/).
|
||||
1. **Code Coverage**: Code coverage is an important metric for chip verification. Generally, all code of the target DUT needs to be covered.
|
||||
1. **Functional Coverage**: Functional coverage indicates how much of the target functionality has been verified. It usually needs to reach 100%.
|
||||
|
||||
In subsequent documentation, we will continue to use the `rvc_expander` module as an example to explain the above process in detail.
|
||||
|
||||
\*Note: Directory or file names should be reasonable so that their specific meaning can be inferred from the naming.
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
title: How to Participate in This Project
|
||||
linkTitle: How to Participate in This Project
|
||||
#menu: {main: {weight: 20}}
|
||||
weight: 18
|
||||
---
|
||||
|
||||
### How to Submit a Bug
|
||||
|
||||
Submit according to the ISSUE template and mark the corresponding labels (bug, bug level, etc.).
|
||||
|
||||
The maintainer of the corresponding module will check and modify the labels and XiangShan branch as needed.
|
||||
|
||||
### How to Submit Documentation
|
||||
|
||||
Documentation for this repository should be submitted via PR to this repository. DUT documentation should be submitted in the repository at [UnityChipForXiangShan/documents/content/zh-cn/docs/98_UT](https://github.com/XS-MLVP/UnityChipForXiangShan/tree/main/documents/content/zh-cn/docs/98_UT).
|
||||
|
||||
This project welcomes anyone to participate via [`ISSUE`](https://github.com/XS-MLVP/UnityChipForXiangShan/issues), [`DISCUSS`](https://github.com/XS-MLVP/env-xs-ov-00-bpu/discussions), [`Fork`](https://github.com/XS-MLVP/UnityChipForXiangShan/fork), or [`PR`](https://github.com/XS-MLVP/env-xs-ov-00-bpu/pulls).
|
||||
|
||||
WanZhongYiXin QQ Group:
|
||||
|
||||
<image src="600480230.jpg" alter="600480230" width=300px />
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
---
|
||||
title: Template-PR
|
||||
linkTitle: Template-PR
|
||||
#menu: {main: {weight: 20}}
|
||||
weight: 19
|
||||
---
|
||||
|
||||
|
||||
```markdown
|
||||
# Description
|
||||
|
||||
Please include a summary of the changes and the related issue.
|
||||
Please also include relevant motivation and context.
|
||||
List any dependencies that are required for this change.
|
||||
|
||||
Fixes # (issue)
|
||||
|
||||
## Type of change
|
||||
|
||||
Please delete options that are not relevant.
|
||||
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] This change requires a documentation update
|
||||
|
||||
# How Has This Been Tested?
|
||||
|
||||
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce.
|
||||
Please also list any relevant details for your test configuration
|
||||
|
||||
- [ ] Test A
|
||||
- [x] Test B
|
||||
|
||||
**Test Configuration**:
|
||||
* Firmware version:
|
||||
* Hardware:
|
||||
* Toolchain:
|
||||
* SDK:
|
||||
|
||||
# Checklist:
|
||||
|
||||
- [ ] My code follows the style guidelines of this project
|
||||
- [ ] I have performed a self-review of my code
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
- [ ] I have made corresponding changes to the documentation
|
||||
- [ ] My changes generate no new warnings
|
||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||
- [ ] New and existing unit tests pass locally with my changes
|
||||
- [ ] Any dependent changes have been merged and published in downstream modules
|
||||
|
||||
```
|
||||
|
||||
The rendered effect is as follows:
|
||||
|
||||
# Description
|
||||
|
||||
Please include a summary of the changes and the related issue. Please also include relevant motivation
|
||||
and context. List any dependencies that are required for this change.
|
||||
|
||||
Fixes # (issue)
|
||||
|
||||
## Type of change
|
||||
|
||||
Please delete options that are not relevant.
|
||||
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] This change requires a documentation update
|
||||
|
||||
# How Has This Been Tested?
|
||||
|
||||
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce.
|
||||
Please also list any relevant details for your test configuration
|
||||
|
||||
- [ ] Test A
|
||||
- [x] Test B
|
||||
|
||||
**Test Configuration**:
|
||||
* Firmware version:
|
||||
* Hardware:
|
||||
* Toolchain:
|
||||
* SDK:
|
||||
|
||||
# Checklist:
|
||||
|
||||
- [ ] My code follows the style guidelines of this project
|
||||
- [ ] I have added the appropriate labels
|
||||
- [ ] I have performed a self-review of my code
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
- [ ] I have made corresponding changes to the documentation
|
||||
- [ ] My changes generate no new warnings
|
||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||
- [ ] New and existing unit tests pass locally with my changes
|
||||
- [ ] Any dependent changes have been merged and published in downstream modules
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
---
|
||||
title: Template-ISSUE
|
||||
linkTitle: Template-ISSUE
|
||||
#menu: {main: {weight: 20}}
|
||||
weight: 20
|
||||
---
|
||||
|
||||
```markdown
|
||||
## Description
|
||||
|
||||
A brief description of the issue.
|
||||
|
||||
## Steps to Reproduce
|
||||
|
||||
1. Describe the first step
|
||||
2. Describe the second step
|
||||
3. Describe the third step
|
||||
4. ...
|
||||
|
||||
## Expected Result
|
||||
|
||||
Describe what you expected to happen.
|
||||
|
||||
## Actual Result
|
||||
|
||||
Describe what actually happened.
|
||||
|
||||
## Screenshots
|
||||
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
## Environment
|
||||
|
||||
- OS: [e.g. Windows 10, macOS 10.15, Ubuntu 20.04]
|
||||
- Browser: [e.g. Chrome 86, Firefox 82, Safari 14]
|
||||
- Version: [e.g. 1.0.0]
|
||||
|
||||
## Additional Information
|
||||
|
||||
Add any other context about the problem here.
|
||||
|
||||
```
|
||||
|
||||
The rendered effect is as follows:
|
||||
|
||||
## Description
|
||||
|
||||
A brief description of the issue.
|
||||
|
||||
## Steps to Reproduce
|
||||
|
||||
1. Describe the first step
|
||||
2. Describe the second step
|
||||
3. Describe the third step
|
||||
4. ...
|
||||
|
||||
## Expected Result
|
||||
|
||||
Describe what you expected to happen.
|
||||
|
||||
## Actual Result
|
||||
|
||||
Describe what actually happened.
|
||||
|
||||
## Screenshots
|
||||
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
## Environment
|
||||
|
||||
- OS: [e.g. Windows 10, macOS 10.15, Ubuntu 20.04]
|
||||
- Browser: [e.g. Chrome 86, Firefox 82, Safari 14]
|
||||
- Version: [e.g. 1.0.0]
|
||||
|
||||
## Additional Information
|
||||
|
||||
Add any other context about the problem here.
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] I have searched the existing issues
|
||||
- [ ] I have added the appropriate labels
|
||||
- [ ] I have reproduced the issue with the latest version
|
||||
- [ ] I have provided a detailed description of the bug
|
||||
- [ ] I have provided steps to reproduce the issue
|
||||
- [ ] I have included screenshots (if applicable)
|
||||
- [ ] I have provided the environment details (OS, version, etc.)
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
---
|
||||
title: Template-UT-README
|
||||
linkTitle: Template-UT-README
|
||||
#menu: {main: {weight: 20}}
|
||||
weight: 21
|
||||
---
|
||||
|
||||
```markdown
|
||||
# Module Name
|
||||
|
||||
## Test Objectives
|
||||
|
||||
<Description of test objectives and methods>
|
||||
|
||||
|
||||
## Test Environment
|
||||
|
||||
<Description of test environment and dependencies>
|
||||
|
||||
## Function Check
|
||||
|
||||
<Describe the target functions to be tested and the corresponding checking methods>
|
||||
|
||||
|No.|Module|Function Description|Checkpoint Description|Check Identifier|Check Item|
|
||||
|-|-|-|-|-|-|
|
||||
|-|-|-|-|-|-|
|
||||
|
||||
|
||||
## Verification Interface
|
||||
|
||||
<Description of the interface>
|
||||
|
||||
|
||||
## Test Case Description
|
||||
|
||||
#### Test Case 1
|
||||
|
||||
|Step|Operation|Expected Result|Covered Function Point|
|
||||
|-|-|-|-|
|
||||
|-|-|-|-|
|
||||
|
||||
#### Test Case 2
|
||||
|
||||
|Step|Operation|Expected Result|Covered Function Point|
|
||||
|-|-|-|-|
|
||||
|-|-|-|-|
|
||||
|
||||
|
||||
## Directory Structure
|
||||
|
||||
<Description of the directory structure for this module>
|
||||
|
||||
|
||||
## Checklist
|
||||
|
||||
|
||||
- [ ] This document meets the specified [template]() requirements
|
||||
- [ ] The API provided by Env does not contain any DUT pins or timing information
|
||||
- [ ] The API of Env remains stable (total [ X ])
|
||||
- [ ] Supported RTL versions in Env have been checked (supported versions [ X ])
|
||||
- [ ] Function points (total [ X ]) are consistent with the [design document]()
|
||||
- [ ] Checkpoints (total [ X ]) cover all function points
|
||||
- [ ] The input of checkpoints does not depend on any DUT pins, only on the standard API of Env
|
||||
- [ ] All test cases (total [ X ]) are mapped to function checkpoints
|
||||
- [ ] All test cases use assert for result checking
|
||||
- [ ] All DUTs or corresponding wrappers are created via fixture
|
||||
- [ ] RTL version is checked in the above fixtures
|
||||
- [ ] The fixture for creating DUT or corresponding wrapper performs function and code line coverage statistics
|
||||
- [ ] Filtering requirements are checked when setting code line coverage
|
||||
|
||||
The rendered effect is as follows:
|
||||
|
||||
# Module Name
|
||||
|
||||
## Test Objectives
|
||||
|
||||
<Description of test objectives and methods>
|
||||
|
||||
|
||||
## Test Environment
|
||||
|
||||
<Description of test environment and dependencies>
|
||||
|
||||
## Function Check
|
||||
|
||||
<Describe the target functions to be tested and the corresponding checking methods>
|
||||
|
||||
|No.|Module|Function Description|Checkpoint Description|Check Identifier|Check Item|
|
||||
|-|-|-|-|-|-|
|
||||
|-|-|-|-|-|-|
|
||||
|
||||
|
||||
## Verification Interface
|
||||
|
||||
<Description of the interface>
|
||||
|
||||
|
||||
## Test Case Description
|
||||
|
||||
#### Test Case 1
|
||||
|
||||
|Step|Operation|Expected Result|Covered Function Point|
|
||||
|-|-|-|-|
|
||||
|-|-|-|-|
|
||||
|
||||
#### Test Case 2
|
||||
|
||||
|Step|Operation|Expected Result|Covered Function Point|
|
||||
|-|-|-|-|
|
||||
|-|-|-|-|
|
||||
|
||||
|
||||
## Directory Structure
|
||||
|
||||
<Description of the directory structure for this module>
|
||||
|
||||
|
||||
## Checklist
|
||||
|
||||
|
||||
- [ ] This document meets the specified [template]() requirements
|
||||
- [ ] The API provided by Env does not contain any DUT pins or timing information
|
||||
- [ ] The API of Env remains stable (total [ X ])
|
||||
- [ ] Supported RTL versions in Env have been checked (supported versions [ X ])
|
||||
- [ ] Function points (total [ X ]) are consistent with the [design document]()
|
||||
- [ ] Checkpoints (total [ X ]) cover all function points
|
||||
- [ ] The input of checkpoints does not depend on any DUT pins, only on the standard API of Env
|
||||
- [ ] All test cases (total [ X ]) are mapped to function checkpoints
|
||||
- [ ] All test cases use assert for result checking
|
||||
- [ ] All DUTs or corresponding wrappers are created via fixture
|
||||
- [ ] RTL version is checked in the above fixtures
|
||||
- [ ] The fixture for creating DUT or corresponding wrapper performs function and code line coverage statistics
|
||||
- [ ] Filtering requirements are checked when setting code line coverage
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
---
|
||||
title: Common APIs
|
||||
linkTitle: Common APIs
|
||||
#menu: {main: {weight: 99}}
|
||||
weight: 95
|
||||
---
|
||||
|
||||
## comm Module
|
||||
|
||||
The comm module provides some commonly used APIs, which can be called in the following ways:
|
||||
|
||||
```python
|
||||
# import all
|
||||
from comm import *
|
||||
# or direct import functions you need
|
||||
from comm import function_you_need
|
||||
# or access from module
|
||||
import comm
|
||||
comm.function_you_need()
|
||||
```
|
||||
|
||||
### cfg Submodule
|
||||
|
||||
#### get_config(cfg=None)
|
||||
Get the current Config configuration
|
||||
- Input: If cfg is not empty, return cfg. Otherwise, automatically get the global Config via toffee.
|
||||
- Return: Config object
|
||||
|
||||
```python
|
||||
import comm
|
||||
cfg = comm.get_config()
|
||||
print(cfg.rtl.version)
|
||||
```
|
||||
|
||||
#### cfg_as_str(cfg: CfgObject)
|
||||
Convert the config object to a string type
|
||||
- Input: Config object
|
||||
- Return: Encoded Config object
|
||||
|
||||
```python
|
||||
import comm
|
||||
cfg_str = comm.cfg_as_str(comm.get_config())
|
||||
```
|
||||
|
||||
#### cfg_from_str(cfg_str)
|
||||
Restore the Config object from a string
|
||||
- Input: Encoded Config object
|
||||
- Return: Config object
|
||||
|
||||
```python
|
||||
import comm
|
||||
cfg = comm.cfg_from_str(cfg_str)
|
||||
```
|
||||
|
||||
#### dump_cfg(cfg: CfgObject = None, cfg_file=None)
|
||||
Save the config object to a file
|
||||
- Input:
|
||||
- cfg: the config to save
|
||||
- cfg_file: target file
|
||||
|
||||
```python
|
||||
import comm
|
||||
cfg = comm.get_config()
|
||||
comm.dump_cfg(cfg, "config.yaml")
|
||||
```
|
||||
|
||||
### functions Submodule
|
||||
|
||||
#### get_log_dir(subdir="", cfg=None)
|
||||
Get the log directory
|
||||
- Input:
|
||||
- subdir: subdirectory
|
||||
- cfg: config file
|
||||
- Output: log directory
|
||||
|
||||
```python
|
||||
import comm
|
||||
my_log = comm.get_log_dir("my_log")
|
||||
print(my_log) # /workspace/UnityChipForXiangShan/out/log/my_log
|
||||
```
|
||||
|
||||
#### get_out_dir(subdir="", cfg=None)
|
||||
Get the output directory
|
||||
- Input:
|
||||
- subdir: subdirectory
|
||||
- cfg: config file
|
||||
- Output: output directory
|
||||
|
||||
#### get_rtl_dir(subdir="", cfg=None)
|
||||
Get the RTL directory
|
||||
- Input:
|
||||
- subdir: subdirectory
|
||||
- cfg: config file
|
||||
- Output: RTL directory
|
||||
|
||||
#### get_root_dir(subdir="")
|
||||
Get the root directory:
|
||||
- Input: subdirectory under the root directory
|
||||
- Output: root directory of the current repository
|
||||
|
||||
#### is_all_file_exist(files_to_check, dir)
|
||||
Check whether all files exist in the specified directory
|
||||
- Input:
|
||||
- files_to_check: list of files to check
|
||||
- dir: target directory
|
||||
- Output: whether all exist; returns False if any file does not exist
|
||||
|
||||
#### time_format(seconds=None, fmt="%Y%m%d-%H%M%S")
|
||||
Format time
|
||||
- Input:
|
||||
- seconds: time to format, None means current time
|
||||
- fmt: time format
|
||||
- Return: formatted time string
|
||||
```python
|
||||
import comm
|
||||
import time
|
||||
print(time_format(time.time())) # 20241202-083726
|
||||
```
|
||||
|
||||
#### base64_encode(input_str)
|
||||
Base64 encode:
|
||||
- Input: string to encode
|
||||
- Output: encoded string
|
||||
```python
|
||||
import comm
|
||||
print(comm.base64_encode("test")) # dGVzdA==
|
||||
```
|
||||
|
||||
#### base64_decode(base64_str)
|
||||
Base64 decode:
|
||||
- Input: base64 encoded string
|
||||
- Output: decoded original string
|
||||
```python
|
||||
import comm
|
||||
print(comm.base64_decode("dGVzdA==")) # test
|
||||
```
|
||||
|
||||
#### exe_cmd(cmd, no_log=False)
|
||||
Execute an OS command:
|
||||
- Input:
|
||||
- cmd: OS command to execute
|
||||
- no_log: whether to return command line output
|
||||
- Output: success, stdout, stderr
|
||||
- success: whether the command executed successfully
|
||||
- command standard output string (forced to empty if no_log=True)
|
||||
- command standard error string (forced to empty if no_log=True)
|
||||
|
||||
```python
|
||||
import comm
|
||||
su, st, er = exe_cmd("pwd")
|
||||
print(st)
|
||||
```
|
||||
|
||||
#### get_git_commit()
|
||||
Get the current repository git commit hash
|
||||
|
||||
#### get_git_branch()
|
||||
Get the current repository git branch name
|
||||
|
||||
#### UT_FCOV(group, ignore_prefix="ut_")
|
||||
Get function coverage group
|
||||
- Input:
|
||||
- group: group name
|
||||
- ignore_prefix: prefix to remove
|
||||
- Output: coverage group name with module prefix
|
||||
|
||||
For example, called in `ut_backend/ctrl_block/decode/env/decode_wrapper.py`:
|
||||
```python
|
||||
print(UT_FCOV("../../INT"))
|
||||
# out
|
||||
backend.ctrl_block.decode.INT
|
||||
```
|
||||
|
||||
#### get_version_checker(target_version)
|
||||
Get version check function
|
||||
- Input: target version string
|
||||
- Output: check function
|
||||
|
||||
The returned check function is usually used for version checking in fixtures.
|
||||
```python
|
||||
import comm
|
||||
import pytest
|
||||
|
||||
checker = comm.get_version_checker("openxiangshan-kmh-24092701+")
|
||||
|
||||
@pytest.fixture
|
||||
def fixture():
|
||||
checker()
|
||||
...
|
||||
```
|
||||
|
||||
#### module_name_with(names, prefix=None)
|
||||
Add a module prefix to names
|
||||
- Input:
|
||||
- names: list of strings to add prefix to
|
||||
- prefix: module prefix
|
||||
- Return: list of strings with prefix added
|
||||
|
||||
For example, called in a/b/c/d/e.py:
|
||||
```python
|
||||
import comm
|
||||
print(comm.module_name_with(["X", "Y"], "../../x"))
|
||||
# out
|
||||
["a.b.c.x.X", "a.b.c.x.Y"]
|
||||
```
|
||||
|
||||
#### `get_all_rtl_files(top_module, cfg)`
|
||||
|
||||
Get a list of all RTL files (`.v` or `.sv`) that the module named `top_module` depends on, and ensure that the first element of the list is the absolute path of the file where `top_module` is located. All RTL files are located in the `UnityChipForXiangShan/rtl/rtl` directory.
|
||||
|
||||
- Input:
|
||||
- `top_module`: module name, type `str`
|
||||
- `cfg`: config info, type `CfgObject`
|
||||
|
||||
- Output:
|
||||
- Returns a list of strings, each string is the absolute path of an RTL file that the module depends on. The first element of the list is the path of the file where `top_module` is located.
|
||||
|
||||
Suppose `top_module` is `"ALU"`, and its dependent RTL files include `ALU.sv`, `adder.v`, and `multiplier.v`:
|
||||
```python
|
||||
paths = get_all_rtl_files("ALU", cfg)
|
||||
|
||||
"""
|
||||
Possible contents of paths:
|
||||
[
|
||||
"/path/to/UnityChipForXiangShan/rtl/rtl/ALU.sv",
|
||||
"/path/to/UnityChipForXiangShan/rtl/rtl/adder.v",
|
||||
"/path/to/UnityChipForXiangShan/rtl/rtl/multiplier.v"
|
||||
]
|
||||
"""
|
||||
```
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
---
|
||||
title: Others
|
||||
linkTitle: Others
|
||||
#menu: {main: {weight: 99}}
|
||||
weight: 96
|
||||
---
|
||||
|
||||
## Test Case Management
|
||||
|
||||
If test cases are closely related to the target RTL version, changes in RTL may render previous test cases unsuitable. In addition, different scenarios have different requirements, such as not running time-consuming cases when verifying the test environment. Therefore, test cases need to be managed so that users can skip certain cases in specific scenarios. To achieve this, we use `pytest.mark.toffee_tags` to tag and version each test case. Then, in the configuration file, you can set which tags to skip or which tags to run.
|
||||
|
||||
```python
|
||||
@pytest.mark.toffee_tags("my_tag", "version1 < version13")
|
||||
def test_case_1():
|
||||
...
|
||||
```
|
||||
|
||||
For example, the above `test_case_1` is tagged with `my_tag` and supports versions from `version1` to `version13`. Therefore, you can specify `test.skip-tags=["my_tag"]` in the configuration file to skip this case during execution.
|
||||
|
||||
The parameters for `pytest.mark.toffee_tags` are as follows:
|
||||
|
||||
```python
|
||||
@pytest.mark.toffee_tags(
|
||||
tag: Optional[list, str] = [] # Case tag
|
||||
version: Optional[list, str] = [], # RTL version requirement for the case
|
||||
skip: callable = None, # Custom skip logic, skip(tag, version, item): (skip, reason)
|
||||
)
|
||||
```
|
||||
|
||||
The `tag` parameter of `toffee_tags` supports both `str` and `list[str]` types. The `version` parameter can also be `str` or `list[str]`. If it is a list, it matches exactly; if it is a string, the matching rules are as follows:
|
||||
|
||||
1. `name-number1 < name-number2:` means the version must be between `number1` and `number2` (inclusive, `number` can be a decimal, e.g., `1.11`)
|
||||
2. `name-number1+`: means version `number1` and later
|
||||
3. `name-number1-`: means version `number1` and earlier
|
||||
|
||||
If none of the above, and there is a `*` or `?`, it is treated as a wildcard. Other cases are exact matches.
|
||||
|
||||
Predefined tags can be found in `comm/constants.py`, for example:
|
||||
|
||||
```python
|
||||
# Predefined tags for test cases
|
||||
TAG_LONG_TIME_RUN = "LONG_TIME_RUN" # Long-running
|
||||
TAG_SMOKE = "SMOKE" # Smoke test
|
||||
TAG_RARELY_USED = "RARELY_USED" # Rarely used
|
||||
TAG_REGRESSION = "REGRESSION" # Regression test
|
||||
TAG_PERFORMANCE = "PERFORMANCE" # Performance test
|
||||
TAG_STABILITY = "STABILITY" # Stability test
|
||||
TAG_SECURITY = "SECURITY" # Security test
|
||||
TAG_COMPATIBILITY = "COMPATIBILITY" # Compatibility test
|
||||
TAG_OTHER = "OTHER" # Other
|
||||
TAG_CI = "CI" # Continuous integration test
|
||||
TAG_DEBUG = "DEBUG" # Debug test
|
||||
TAG_DEMO = "DEMO" # Demo
|
||||
```
|
||||
|
||||
In the default configuration (`config/_default.yaml`), tests marked with `LONG_TIME_RUN`, `REGRESSION`, `RARELY_USED`, and `CI` are filtered out.
|
||||
|
||||
You can use `@pytest.mark.toffee_tags` to add tags to each case, or define the following variables in a module to add tags to all test cases in the module:
|
||||
|
||||
```python
|
||||
toffee_tags_default_tag = [] # Corresponds to the tag parameter
|
||||
toffee_tags_default_version = [] # Corresponds to the version parameter
|
||||
toffee_tags_default_skip = None # Corresponds to the skip parameter
|
||||
```
|
||||
|
||||
*Note: The version number in this environment will automatically filter out git tags. For example, if the downloaded RTL is named `openxiangshan-kmh-97e37a2237-24092701.tar.gz`, its version number in this project is `openxiangshan-kmh-24092701`, which can be obtained via `cfg.rtl.version` or `comm.get_config().rtl.version`.
|
||||
|
||||
## Version Checking
|
||||
|
||||
In addition to using the `toffee_tags` tag for automatic version checking, you can also actively check versions via `get_version_checker`. A unit test usually consists of a test environment (Test Env) and test cases (Test Case). The Env encapsulates RTL pins and functions, then provides a stable API to the Case, so version checking is needed in the Env to determine whether to skip all test cases using this environment. For example, in Env:
|
||||
|
||||
```python
|
||||
...
|
||||
from comm import get_version_checker
|
||||
|
||||
version_check = get_version_checker("openxiangshan-kmh-*") # Get RTL version checker, same as the version parameter in toffee_tags
|
||||
|
||||
@pytest.fixture()
|
||||
def my_fixture(request):
|
||||
version_check() # Actively check in the fixture
|
||||
....
|
||||
yield dut
|
||||
...
|
||||
```
|
||||
|
||||
In the above example, the Env actively performs version checking in the fixture named `my_fixture`. Therefore, every time the test case calls it, version checking is performed, and if the check fails, the case will be skipped.
|
||||
|
||||
## Repository Directory Structure
|
||||
|
||||
```bash
|
||||
UnityChipForXiangShan
|
||||
├── LICENSE # Open source license
|
||||
├── Makefile # Main Makefile
|
||||
├── README.en.md # English readme
|
||||
├── README.zh.md # Chinese readme
|
||||
├── __init__.py # Python module file, allows importing UnityChipForXiangShan as a module
|
||||
├── pytest.ini # PyTest configuration file
|
||||
├── comm # Common components: logs, functions, configs, etc.
|
||||
├── configs # Configuration files directory
|
||||
├── documents # Documentation
|
||||
├── dut # DUT generation directory
|
||||
├── out # Output directory for logs, reports, etc.
|
||||
├── requirements.txt # Python dependencies
|
||||
├── rtl # RTL cache
|
||||
├── run.py # Main Python entry file
|
||||
├── scripts # DUT compilation scripts
|
||||
├── tools # Common tool modules
|
||||
├── ut_backend # Backend test cases
|
||||
├── ut_frontend # Frontend test cases
|
||||
├── ut_mem_block # Memory access test cases
|
||||
└── ut_misc # Other test cases
|
||||
```
|
||||
|
||||
## Configuration File Description
|
||||
|
||||
Default configuration and explanation:
|
||||
|
||||
```yaml
|
||||
# Default configuration file
|
||||
# Configuration loading order: _default.yaml -> user-specified *.yaml -> command line parameters eg: log.term-level='debug'
|
||||
# RTL configuration
|
||||
rtl:
|
||||
# RTL download address, all *.gz.tar files from this address are treated as target RTL
|
||||
base-url: https://<your_rtl_download_address>
|
||||
# RTL version to download, e.g., openxiangshan-kmh-97e37a2237-24092701
|
||||
version: latest
|
||||
# Directory to store RTL, relative to the current config file path
|
||||
cache-dir: "../rtl"
|
||||
# Test case configuration (tag and case support wildcards)
|
||||
test:
|
||||
# Skip tags, all test cases with these tags will be skipped
|
||||
skip-tags: ["LONG_TIME_RUN", "RARELY_USED", "REGRESSION", "CI"]
|
||||
# Target tags, only test cases with these tags will be executed (skip-tags overrides run-tags)
|
||||
run-tags: []
|
||||
# Skipped test cases, all test cases (or module names) with these names will be skipped.
|
||||
skip-cases: []
|
||||
# Target test cases, only test cases (or module names) with these names will be executed (skip-cases overrides run-cases).
|
||||
run-cases: []
|
||||
# Skip exceptions, all test cases that throw these exceptions will be skipped
|
||||
skip-exceptions: []
|
||||
# Output configuration
|
||||
output:
|
||||
# Output directory, relative to the current config file path
|
||||
out-dir: "../out"
|
||||
# Test report configuration
|
||||
report:
|
||||
# Report generation directory, relative to output.out-dir
|
||||
report-dir: "report"
|
||||
# Report name, supports variable substitution: %{host} hostname, %{pid} process ID, %{time} current time
|
||||
report-name: "%{host}-%{pid}-%{time}/index.html"
|
||||
# Report content
|
||||
information:
|
||||
# Report title
|
||||
title: "XiangShan KMH Test Report"
|
||||
# Report user information
|
||||
user:
|
||||
name: "User"
|
||||
email: "User@example.email.com"
|
||||
# Target line coverage, e.g., 90 means 90%
|
||||
line_grate: 99
|
||||
# Other information to display, key is the title, value is the content
|
||||
meta:
|
||||
Version: "1.0"
|
||||
# Log configuration
|
||||
log:
|
||||
# Root output level
|
||||
root-level: "debug"
|
||||
# Terminal output level
|
||||
term-level: "info"
|
||||
# File log output directory
|
||||
file-dir: "log"
|
||||
# File log name, supports variable substitution: %{host} hostname, %{pid} process ID, %{time} current time
|
||||
file-name: "%{host}-%{pid}-%{time}.log"
|
||||
# File log output level
|
||||
file-level: "info"
|
||||
# Test result configuration (this data is used to populate statistics charts in documents, original data comes from toffee-test generated reports)
|
||||
# After running the tests, you can view the results via `make doc`
|
||||
doc-result:
|
||||
# Whether to enable test result post-processing
|
||||
disable: False
|
||||
# Organizational structure configuration of target DUT
|
||||
dutree: "%{root}/configs/dutree/xiangshan-kmh.yaml"
|
||||
# Result name, will be saved to the output report directory
|
||||
result-name: "ut_data_progress.json"
|
||||
# Symlink to the created test report for hugo
|
||||
report-link: "%{root}/documents/static/data/reports"
|
||||
```
|
||||
|
||||
You can add custom parameters in the above configuration file, get global config info via `cfg = comm.get_config()`, and then access via `cfg.your_key`. The `cfg` info is read-only and cannot be modified by default.
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
title: Required Specifications
|
||||
linkTitle: Required Specifications
|
||||
#menu: {main: {weight: 99}}
|
||||
weight: 97
|
||||
---
|
||||
|
||||
In order to facilitate the integration of everyone's contributions, it is necessary to adopt the same "specifications" in coding, environment, and documentation.
|
||||
|
||||
### Environment Requirements
|
||||
|
||||
- **python:** When coding in Python, use the standard library as much as possible, and use general syntax compatible with most Python 3 versions (try to be compatible with Python 3.6 - Python 3.12). Do not use syntax that is too old or too new.
|
||||
- **Operating System:** Ubuntu 22.04 is recommended. On Windows, it is recommended to use the WSL2 environment.
|
||||
- **hugo:** Recommended version is 0.124.1 (older versions do not support symlinks)
|
||||
- **Minimal dependencies:** Try to minimize the use of third-party C++/C libraries.
|
||||
- **picker:** It is recommended to install the picker tool and xspcomm library via wheel.
|
||||
|
||||
### Test Cases
|
||||
|
||||
- **Code Style:** It is recommended to follow the [PEP 8 standard](https://peps.python.org/pep-0008/)
|
||||
- **Build Scripts:** The naming of build scripts must follow the DUT naming structure, otherwise verification results cannot be collected correctly. For example, the build file for the `backend.ctrl_block.decode` UT in the scripts directory should be named `build_ut_backend_ctrl_block_decode.py` (with the fixed prefix `build_ut_`, and dots `.` replaced by underscores `_`). The script should implement the `build(cfg) -> bool` and `line_coverage_files(cfg) -> list[str]` methods. `build` is used to compile the DUT into a Python module, and `line_coverage_files` is used to return the files for code line coverage statistics.
|
||||
- **Test Case Tags:** If a test case cannot be version-agnostic, it needs to be marked with `pytest.mark.toffee_tags` to indicate the supported versions.
|
||||
- **Test Case Abstraction:** The input of the test case should not contain specific DUT pins or other strongly coupled content. Only functions encapsulated on top of the DUT can be called. For example, for an adder, the DUT's target function should be encapsulated as `dut_wrapper.add(a: int, b: int) -> int, bool`, and in the test_case, only `sum, c = add(a, b)` should be called for testing.
|
||||
- **Coverage Abstraction:** When writing functional coverage, the input of the checkpoint function should also not include DUT pins.
|
||||
- **Environment Abstraction:** For a verification, it is usually divided into two parts: Test Case and Env (everything except the test case is called Env, which includes DUT, drivers, monitors, etc.). The Env should provide abstract functional interfaces to the outside and should not expose too many details.
|
||||
- **Test Documentation:** In the verification environment of each DUT, a `README.md` should be provided to explain the environment, such as the interfaces provided by Env to Case, directory structure, etc.
|
||||
|
||||
### PR Writing
|
||||
|
||||
- **Title:** Concise and clear, able to summarize the main content of the PR.
|
||||
- **Detailed Description:** Clearly explain the purpose of the PR, the changes made, and relevant background information. If solving an existing issue, provide a link (e.g., Issue).
|
||||
- **Related Issues:** Link related issues in the description, such as `Fixes #123`, so that the related issue is closed when the PR is merged.
|
||||
- **Testing:** Testing is required, and the test results should be described.
|
||||
- **Documentation:** Any documentation involved in the PR should be updated accordingly.
|
||||
- **Decomposition:** If the PR involves many changes, consider splitting it into multiple PRs.
|
||||
- **Checklist:** Check whether compilation passes, code style is reasonable, tests pass, necessary comments are present, etc.
|
||||
- **Template:** Please refer to the provided PR template [reference link](08_pr_template/).
|
||||
|
||||
### ISSUE Writing
|
||||
|
||||
Same requirements as above.
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
title: Maintainers
|
||||
linkTitle: Maintainers
|
||||
#menu: {main: {weight: 99}}
|
||||
weight: 99
|
||||
---
|
||||
|
||||
When submitting an issue, pull request, or discussion, specifying the maintainer of the corresponding module can help you get a quicker response. The current maintainers are listed below (in alphabetical order):
|
||||
|
||||
**Verification Tools:**
|
||||
- picker: [Makiras](https://github.com/Makiras), [SFangYy](https://github.com/SFangYy), [yaozhicheng](https://github.com/yaozhicheng)
|
||||
- toffee/toffee-test: [Miical](https://github.com/Miical), [yaozhicheng](https://github.com/yaozhicheng)
|
||||
|
||||
<!-- <script src="../../js/echarts.min.js"></script> -->
|
||||
|
||||
<script>
|
||||
function update_maintainers(data_url){
|
||||
updateMaintainers(data_url)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div style="text-align: center; width: 100%;">
|
||||
{{<list-report baseurl="../../../data/reports" label="Current Version:" id="maintainers" onchange="update_maintainers">}}
|
||||
</div>
|
||||
<br>
|
||||
|
||||
{{<maintainers>}}
|
||||
|
||||
*Other maintainers will be updated continuously.
|
||||
|
||||
If you are interested in this project, you are welcome to apply to become a maintainer.
|
||||
|
|
@ -1,8 +1,87 @@
|
|||
---
|
||||
title: XiangShan UT
|
||||
linkTitle: XiangShan UT
|
||||
title: Progress Overview
|
||||
linkTitle: Progress Overview
|
||||
#menu: {main: {weight: 20}}
|
||||
weight: 20
|
||||
weight: 10
|
||||
---
|
||||
|
||||
TBD
|
||||
<script src="../../js/echarts.min.js"></script>
|
||||
<script src="../../js/chart_meta.js"></script>
|
||||
<script>
|
||||
function update_charts(data_url){
|
||||
show_meta_chart("meta_chart", data_url)
|
||||
updateDUTestStatus(data_url)
|
||||
}
|
||||
</script>
|
||||
|
||||
This project aims to perform unit testing (Unit Test, UT) verification of the [XiangShan Processor](https://github.com/OpenXiangShan/XiangShan) Kunming Lake architecture through open-source crowdsourcing. The chart below shows the verification status of each module in the XiangShan Kunming Lake architecture.
|
||||
|
||||
<div id="meta_chart" style="width: 100%;height:400px;"></div>
|
||||
<div style="text-align: center; width: 100%;">
|
||||
{{<list-report baseurl="../../data/reports" label="Current Version:" detail="View Test Report" id="index" onchange="update_charts">}}
|
||||
</div>
|
||||
<br>
|
||||
|
||||
Overall statistics are as follows:
|
||||
|
||||
<table>
|
||||
<ol>
|
||||
<tr>
|
||||
<td>Total Cases:</td>
|
||||
<td style="text-align: left; font-weight: bold;"><em id="em_id_report_cases_toal">-</em></td>
|
||||
<td>Passed Cases:</td>
|
||||
<td style="text-align: left; font-weight: bold;"><em id="em_id_report_cases_pass">-</em></td>
|
||||
<td>Passed Rate:</td>
|
||||
<td style="text-align: left; font-weight: bold;"><em id="em_id_report_cases_prate">-</em></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Failed Cases:</td>
|
||||
<td style="text-align: left; font-weight: bold;"><em id="em_id_report_cases_fail">-</em></td>
|
||||
<td>Skipped Cases:</td>
|
||||
<td style="text-align: left; font-weight: bold;"><em id="em_id_report_cases_skip">-</em></td>
|
||||
<td>Skip Rate:</td>
|
||||
<td style="text-align: left; font-weight: bold;"><em id="em_id_report_cases_srate">-</em></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Function Coverage:</td>
|
||||
<td style="text-align: left; font-weight: bold;"><em id="em_id_report_function_total">-</em></td>
|
||||
<td>Covered Functions:</td>
|
||||
<td style="text-align: left; font-weight: bold;"><em id="em_id_report_function_cover">-</em></td>
|
||||
<td>Covered Rate:</td>
|
||||
<td style="text-align: left; font-weight: bold;"><em id="em_id_report_function_rate">-</em></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total Lines:</td>
|
||||
<td style="text-align: left; font-weight: bold;"><em id="em_id_report_line_total">-</em></td>
|
||||
<td>Covered Lines:</td>
|
||||
<td style="text-align: left; font-weight: bold;"><em id="em_id_report_line_cover">-</em></td>
|
||||
<td>Covered Rate:</td>
|
||||
<td style="text-align: left; font-weight: bold;"><em id="em_id_report_line_rate">-</em></td>
|
||||
</tr>
|
||||
</ol>
|
||||
</table>
|
||||
|
||||
*The total number of lines will continue to increase as DUTs are added, so: the total line coverage is not the final coverage.
|
||||
|
||||
Other quick links:
|
||||
|
||||
- **[DUT Documentation & Functions](https://open-verify.cc/UnityChipForXiangShan/docs/98_ut/)**
|
||||
- **[Pending Bug List](https://github.com/XS-MLVP/UnityChipForXiangShan/labels/bug%20need%20to%20confirm)**
|
||||
- **[Confirmed Bug List](https://github.com/XS-MLVP/UnityChipForXiangShan/labels/bug%20confirmed)**
|
||||
- **[Fixed Bug List](https://github.com/XS-MLVP/UnityChipForXiangShan/labels/bug%20fixed)**
|
||||
- **[Ongoing Task List](https://open-verify.cc/crowdsourcing/kunming_lake)**
|
||||
- **[Completed Task List](https://open-verify.cc/crowdsourcing/kunming_lake)**
|
||||
|
||||
<br>
|
||||
<div style="text-align: center; width: 100%;">
|
||||
<h4 id="testmap">XiangShan Kunming Lake DUT Verification Progress</h4>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
{{<list-dut-test-status>}}
|
||||
|
||||
<div style="text-align: center; width: 100%;">
|
||||
<br>
|
||||
Note: The statistics in this document are automatically generated based on test results.<br>
|
||||
Data auto-update date: <em id="em_id_report_date">1970-01-01 00:00:00</em>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
title: FTB
|
||||
linkTitle: FTB
|
||||
#menu: {main: {weight: 20}}
|
||||
weight: 20
|
||||
---
|
||||
|
||||
TBD
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
title: TAGE
|
||||
linkTitle: TAGE
|
||||
#menu: {main: {weight: 20}}
|
||||
weight: 20
|
||||
---
|
||||
|
||||
TBD
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
title: ITTAGE
|
||||
linkTitle: ITTAGE
|
||||
#menu: {main: {weight: 20}}
|
||||
weight: 20
|
||||
---
|
||||
|
||||
TBD
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
---
|
||||
title: 分支预测器(Branch Prediction Unit, BPU)
|
||||
linkTitle: BPU
|
||||
#menu: {main: {weight: 20}}
|
||||
weight: 20
|
||||
---
|
||||
|
||||
{{% pageinfo %}}
|
||||
什么是BPU
|
||||
{{% /pageinfo %}}
|
||||
|
|
@ -12,14 +12,13 @@ weight: 13
|
|||
1. Linux操作系统。建议WSL2下安装Ubuntu22.04。
|
||||
1. Python。建议Python3.11。
|
||||
1. picker。按照[快速开始](https://open-verify.cc/mlvp/docs/quick-start/installer/)中的提示安装最新版本。
|
||||
1. toffee。将在后面自动安装。也可按照[快速开始](https://open-verify.cc/mlvp/docs/mlvp/quick-start/)中的提示手动安装最新版本。
|
||||
1. lcov。用于后续test阶段报告生成。使用包管理器即可下载:`sudo apt install lcov`
|
||||
|
||||
**环境配置完成**后,clone仓库:
|
||||
```bash
|
||||
git clone https://github.com/XS-MLVP/UnityChipForXiangShan.git
|
||||
cd UnityChipForXiangShan
|
||||
pip3 install -r requirements.txt # 安装python依赖(例如 toffee)
|
||||
pip3 install -r requirements.txt # 安装 python 依赖(例如 toffee)
|
||||
```
|
||||
|
||||
#### 下载RTL代码:
|
||||
|
|
@ -27,10 +26,10 @@ pip3 install -r requirements.txt # 安装python依赖(例如 toffee)
|
|||
默认从仓库[https://github.com/XS-MLVP/UnityChipXiangShanRTLs](https://github.com/XS-MLVP/UnityChipXiangShanRTLs)中下载。用户也可以自行按照XiangShan文档编译生成RTL。
|
||||
|
||||
```bash
|
||||
make rtl # 该命下载最新的rtl代码,并解压至rtl目录,并创建软连接
|
||||
make rtl # 该命令下载最新的 rtl 代码,解压至 rtl 目录,并创建软链接
|
||||
```
|
||||
|
||||
可以用以下命令指定下载的rtl版本:
|
||||
可以用以下命令指定下载的 rtl 版本:
|
||||
|
||||
```bash
|
||||
make rtl args="rtl.version=\'openxiangshan-kmh-fad7803d-24120901\'"
|
||||
|
|
@ -38,7 +37,7 @@ make rtl args="rtl.version=\'openxiangshan-kmh-fad7803d-24120901\'"
|
|||
|
||||
所有RTL下载包请在[UnityChipXiangShanRTLs](https://github.com/XS-MLVP/UnityChipXiangShanRTLs)中查看。
|
||||
|
||||
RTL压缩包的命名规范为:`名称-微架构-Git标记-日期编号.tar.gz`,例如`openxiangshan-kmh-97e37a2237-24092701.tar.gz`。在使用时,仓库代码会过滤掉git标记和后缀,例如通过 cfg.rtl.version 访问到的版本号为:`openxiangshan-kmh-24092701`。压缩包内的目录结构为:
|
||||
RTL压缩包的命名规范为:`名称-微架构-Git标记-日期编号.tar.gz`,例如`openxiangshan-kmh-97e37a2237-24092701.tar.gz`。在使用时,仓库代码会过滤掉 git 标记和后缀,例如通过 cfg.rtl.version 访问到的版本号为:`openxiangshan-kmh-24092701`。压缩包内的目录结构为:
|
||||
|
||||
```bash
|
||||
openxiangshan-kmh-97e37a2237-24092701.tar.gz
|
||||
|
|
|
|||
|
|
@ -123,8 +123,8 @@ def init_rvc_expander_funcov(expander, g: fc.CovGroup):
|
|||
# - bin ERROR. The instruction is not illegal
|
||||
# - bin SUCCE. The instruction is not expanded
|
||||
g.add_watch_point(expander, {
|
||||
"ERROR": lambda x: x.stat()["ilegal"] == False,
|
||||
"SUCCE": lambda x: x.stat()["ilegal"] != False,
|
||||
"ERROR": lambda x: x.stat()["illegal"] == False,
|
||||
"SUCCE": lambda x: x.stat()["illegal"] != False,
|
||||
}, name = "RVC_EXPAND_RET")
|
||||
...
|
||||
# 5. Reverse mark function coverage to the check point
|
||||
|
|
@ -139,7 +139,7 @@ def init_rvc_expander_funcov(expander, g: fc.CovGroup):
|
|||
...
|
||||
```
|
||||
|
||||
在上述代码中添加了名为`RVC_EXPAND_RET`的功能检查点来检查`RVCExpander`模块是否具有返回非法指令的能力。需要满足`ERROR`和`SUCCE`两个条件,即`stat()`中的`ileage`需要有`True`也需要有`False`值。在定义完检查点后,通过`mark_function`方法,对会覆盖到该检查的测试用例进行了标记。
|
||||
在上述代码中添加了名为`RVC_EXPAND_RET`的功能检查点来检查`RVCExpander`模块是否具有返回非法指令的能力。需要满足`ERROR`和`SUCCE`两个条件,即`stat()`中的`illegal`需要有`True`也需要有`False`值。在定义完检查点后,通过`mark_function`方法,对会覆盖到该检查的测试用例进行了标记。
|
||||
|
||||
### 3. 定义必要fixture
|
||||
|
||||
|
|
@ -221,7 +221,7 @@ toffee的官方教程可以参考[这里](https://open-verify.cc/mlvp/docs/mlvp/
|
|||
|
||||
### bundle:快捷DUT封装
|
||||
|
||||
toffee通过Bundle实现了对DUT的绑定。toffee提供了多种建立Bundle与DUT绑定的方法。相关代码
|
||||
toffee通过Bundle实现了对DUT的绑定。toffee提供了多种建立Bundle与DUT绑定的方法。相关代码参照`ut_frontend/ifu/rvc_expander/toffee_version/bundle`。
|
||||
|
||||
#### 手动绑定
|
||||
|
||||
|
|
|
|||
|
|
@ -82,9 +82,9 @@ out\_pd:每条指令的预译码信息,在F3Predecoder分析得到的是brTy
|
|||
| 1\.3 | CFI指令类型判定 | JAL判定 | 对传入的JAL指令,应该判定为类型2 |
|
||||
| 1\.4 | CFI指令类型判定 | JALR判定 | 对传入的JALR指令,应该判定为类型3 |
|
||||
| 2\.1 | ret、call判定 | 非CFI和BR不判定 | 对传入的非CFI和BR指令,都不应判定为call或者ret |
|
||||
| 2\.2\.1\.1 | ret、call判定 | RVC\.JAL判定call | 对传入的RVC\.JAL指令,当rd设置为1或5,应当判定该指令为call |
|
||||
| 2\.2\.1\.2 | ret、call判定 | RVC\.JAL例外 | 对传入的RVC\.JAL指令,当rd设置为1和5之外的值,不应当判定该指令为call或ret |
|
||||
| 2\.2\.2 | ret、call判定 | RVI\.JAL不判定 | 对传入的RVI\.JAL指令,无论什么情况都不能判定为call或ret |
|
||||
| 2\.2\.1\.1 | ret、call判定 | RVI\.JAL判定call | 对传入的RVC\.JAL指令,当rd设置为1或5,应当判定该指令为call |
|
||||
| 2\.2\.1\.2 | ret、call判定 | RVI\.JAL例外 | 对传入的RVC\.JAL指令,当rd设置为1和5之外的值,不应当判定该指令为call或ret |
|
||||
| 2\.2\.2 | ret、call判定 | RVC\.JAL不判定 | 对传入的RVI\.JAL指令,无论什么情况都不能判定为call或ret |
|
||||
| 2\.3\.1\.1 | ret、call判定 | RVI\.JALR和rd为link | 传入RVI\.JALR指令,并且rd为1或5,无论其他取值,都应判定为call |
|
||||
| 2\.3\.1\.2 | ret、call判定 | RVI\.JALR且仅rs为link | 传入RVI\.JALR指令,rd不为1和5,rs为1或5,应判定为ret |
|
||||
| 2\.3\.1\.3 | ret、call判定 | RVI\.JALR无link | 对传入的JALR指令,若rd和rs均不为link,则不应判定为ret和cal |
|
||||
|
|
|
|||
|
|
@ -26,6 +26,13 @@ RVI指令永远判断为合法。
|
|||
|
||||
对于RVC指令的判定,详细内容参阅20240411的RISCV手册的26\.8节表格列出的指令条件。
|
||||
|
||||
## 常量说明
|
||||
|
||||
| 常量名 | 常量值 | 解释 |
|
||||
| ---- | ---- | ---- |
|
||||
| XLEN | 64 | 通用寄存器位宽,决定指令扩展时使用rv32还是rv64还是rv128 |
|
||||
| fLen | 64 | 香山支持d扩展,故为64 |
|
||||
|
||||
## RVCExpander接口说明
|
||||
|
||||
### 输入接口
|
||||
|
|
@ -292,6 +299,8 @@ addi的格式形如:\| imm\[11\:0\] \| rs1 \| 000 \| rd \| 0010011 \|
|
|||
|
||||
lui指令的格式形如: \| imm[31:12] \| rd \| 0110111 \|
|
||||
|
||||
当立即数为0时,这一字段reserved
|
||||
|
||||
当rd为0时,为hint,也可当作cli进行译码。
|
||||
|
||||
当rd为2时,为addi16sp指令:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ linkTitle: IFU
|
|||
weight: 12
|
||||
---
|
||||
|
||||
**本文档参考[香山IFU设计文档](https://github.com/OpenXiangShan/XiangShan-Design-Doc/blob/master/docs/frontend/IFU/IFU.md)写成**
|
||||
**本文档参考[香山IFU设计文档](https://github.com/OpenXiangShan/XiangShan-Design-Doc/blob/master/docs/frontend/IFU/index.md)写成**
|
||||
|
||||
本文档撰写的内容截至[c670557]
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ linkTitle: 环境配置
|
|||
weight: 12
|
||||
---
|
||||
|
||||
## 推荐使用WSL2+Ubuntu22.04+GTKWave
|
||||
## WSL2+Ubuntu22.04+GTKWave(Windows用户推荐使用)
|
||||
|
||||
我们推荐Windows10/11用户通过WSL2进行开发,在此给出通过此方法进行环境配置的教程集锦,仅供参考。如环境安装过程中出现任何问题,欢迎在QQ群(群号:<b>976081653</b>)中提出,我们将尽力帮助解决。此页面将收集大家提出的所有环境配置相关问题并提供解决方案,欢迎随时向我们提问!
|
||||
我们推荐 Windows10/11 用户通过 WSL2 进行开发,在此给出通过此方法进行环境配置的教程集锦,仅供参考。如环境安装过程中出现任何问题,欢迎在QQ群(群号:<b>976081653</b>)中提出,我们将尽力帮助解决。此页面将收集大家提出的所有环境配置相关问题并提供解决方案,欢迎随时向我们提问!
|
||||
|
||||
## 1、在Windows下安装WSL2(Ubuntu22.04)
|
||||
### 1、在 Windows 下安装 WSL2(Ubuntu22.04)
|
||||
|
||||
参考资源:
|
||||
|
||||
|
|
@ -16,25 +16,83 @@ weight: 12
|
|||
|
||||
--- 其它资源:[安装WSL2和Ubuntu22.04版本](https://blog.csdn.net/HHHBan/article/details/126843786)
|
||||
|
||||
## 2、打开WSL,换源
|
||||
### 2、打开 WSL,换源
|
||||
|
||||
推荐使用清华源:[清华大学开源软件镜像站-Ubuntu软件仓库](https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/)
|
||||
|
||||
## 3、配置验证环境
|
||||
### 3、配置验证环境
|
||||
|
||||
请参照[开放验证平台学习资源-快速开始-搭建验证环境](https://open-verify.cc/mlvp/docs/quick-start/installer/)配置picker环境。
|
||||
|
||||
## 4、使用 GTKWave
|
||||
|
||||
使用[重庆大学硬件综合设计实验文档-Windows原生GTKWave](https://co.ccslab.cn/tips/win-gtkwave/)给出的方法,可以通过在WSL中输入 `gtkwave.exe wave.fst` 打开在Windows下安装的GTKWave。请注意,gtkwave在使用中需要进入fst文件所在文件夹,否则会出现无法
|
||||
initialize的情况。
|
||||
请参照[开放验证平台学习资源-快速开始-搭建验证环境](https://open-verify.cc/mlvp/docs/quick-start/installer/)配置环境。
|
||||
|
||||
以下是示例方法:
|
||||
```bash
|
||||
cd out
|
||||
gtkwave.exe {test_name}.fst
|
||||
cd ..
|
||||
# 基本工具包
|
||||
cd ~ && sudo apt-get update
|
||||
sudo apt-get install -y build-essential cmake git wget curl lcov autoconf flex bison libgoogle-perftools-dev gcc python3.11 python3.11-dev python3.11-distutils python3-pip python-is-python3
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 1
|
||||
curl -sS https://bootstrap.pypa.io/get-pip.py | python3.11
|
||||
# verilator
|
||||
git clone https://github.com/verilator/verilator.git
|
||||
cd verilator
|
||||
git checkout v4.218 # 4.218为最低需求版本,可自行查看并选择新版本
|
||||
autoconf && ./configure && make -j$(nproc) && make install
|
||||
cd .. && rm -rf verilator
|
||||
# verible
|
||||
curl -sS https://github.com/chipsalliance/verible/releases/download/v0.0-3946-g851d3ff4/verible-v0.0-3946-g851d3ff4-linux-static-x86_64.tar.gz -o /tmp/
|
||||
tar -zxvf /tmp/verible-v0.0-3946-g851d3ff4-linux-static-x86_64.tar.gz -C /tmp/
|
||||
copy /tmp/verible-v0.0-3946-g851d3ff4/bin/verible-* /usr/local/bin/
|
||||
sudo chmod +x /usr/local/bin/verible-*
|
||||
rm /tmp/verible-*
|
||||
# pcre2
|
||||
curl -sS https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.45/pcre2-10.45.tar.gz -o /tmp/
|
||||
tar -zxvf /tmp/pcre2-10.45.tar.gz -C /tmp/
|
||||
cd /tmp/pcre2-10.45
|
||||
./configure --prefix=/usr/local && make -j$(nproc) && make install
|
||||
rm -rf /tmp/pcre2* && cd ~
|
||||
# swig
|
||||
# 注意不要使用 apt install swig,将会下载不符合最低要求的版本 4.0.2
|
||||
curl -sS http://prdownloads.sourceforge.net/swig/swig-4.3.0.tar.gz -o /tmp/
|
||||
tar -zxvf /tmp/swig-4.3.0.tar.gz -C /tmp/
|
||||
cd /tmp/swig-4.3.0
|
||||
./configure --prefix=/usr/local && make -j$(nproc) && make install
|
||||
rm -rf /tmp/swig* && cd ~
|
||||
# 更新本地包
|
||||
apt-get update && apt-get -y upgrade
|
||||
# picker
|
||||
git clone https://github.com/XS-MLVP/picker.git --depth=1
|
||||
cd picker
|
||||
make init && make && make install
|
||||
cd .. && rm -rf picker
|
||||
# UnityChipForXiangShan
|
||||
git clone https://github.com/XS-MLVP/UnityChipForXiangShan.git
|
||||
cd UnityChipForXiangShan
|
||||
pip3 install --no-cache-dir -r requirements.txt
|
||||
```
|
||||
|
||||
## 5、使用VSCode插件Live Server查看验证报告
|
||||
### 4、使用 GTKWave 查看波形文件
|
||||
|
||||
使用[重庆大学硬件综合设计实验文档-Windows原生GTKWave](https://co.ccslab.cn/tips/win-gtkwave/)给出的方法,可以通过在WSL中输入 `gtkwave.exe wave.fst` 打开在 Windows 下安装的 GTKWave。请注意,gtkwave在使用中需要进入 fst 文件所在文件夹,否则会出现无法
|
||||
initialize 的情况。
|
||||
|
||||
```bash
|
||||
gtkwave.exe /out/{test_name}.fst
|
||||
```
|
||||
|
||||
### 5、使用 VSCode 插件 Live Server 查看验证报告
|
||||
|
||||
成功安装插件Live Server后,打开文件列表,定位到 `/out/report/2025*-itlb-doc-*/index.html` 右键并选择 `Open With Live Server`,之后在浏览器中打开提示的端口(默认为`//localhost:5500`)即可。
|
||||
|
||||
## docker一键部署方案(MAC用户可用)
|
||||
|
||||
我们提供了 MAC 可用的 docker 环境,已在 Docker Hub 发布,名称为 `unitychip-env`。安装 Docker Desktop 后在命令行使用以下命令即可获取并打开开发环境。需下载约 500MB 的镜像,展开后约占用 1GB 空间。
|
||||
|
||||
```bash
|
||||
docker search unitychip-env
|
||||
docker pull dingjunbi/unitychip-env && docker run unitychip-env
|
||||
cd UnityChipForXiangShan && git pull
|
||||
```
|
||||
|
||||
[Docker Hub使用文档](https://docs.docker.com/docker-hub/)
|
||||
|
||||
[Docker:docker 拉取镜像及查看pull下来的image在哪里](https://blog.csdn.net/sj349781478/article/details/105267887/)
|
||||
|
|
@ -58,8 +58,8 @@ TLB 应当正常接收来自 IFU 与 ICache 的取指令请求,查找自身页
|
|||
|
||||
| No. | 名称 | 说明 |
|
||||
|--------|-------------------|------------------------|
|
||||
| 1.1 | 接收来自 IFU 请求(requestor0、1) | ITLB 根据请求查找自身缓存 TLBuffer,返回 hit/miss 结果 |
|
||||
| 1.2 | 接收来自 ICache 请求(requestor2) | 注意此处为阻塞式访问,每次访问后若 miss 应当 reset 后再次访问 |
|
||||
| 1.1 | 接收来自 ICache 请求(requestor0、1) | ITLB 根据请求查找自身缓存 TLBuffer,返回 hit/miss 结果 |
|
||||
| 1.2 | 接收来自 IFU 请求(requestor2) | 注意此处为阻塞式访问,每次访问后若 miss 应当 reset 后再次访问 |
|
||||
| 1.3 | 接收条件判断(requestor0、1) | valid 信号 |
|
||||
| 1.4 | 接受条件判断(requestor2) | valid-ready 信号 |
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: FTQ顶层
|
||||
linkTitle: FTQ顶层
|
||||
linkTitle: 01_FTQ顶层
|
||||
weight: 12
|
||||
---
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ weight: 12
|
|||
- **fromBackend:接受后端执行结果和commit信号的接口(CtrlToFtqIO)**
|
||||
- **toBpu:向BPU发送训练信息和重定向信息的接口(FtqToBpuIO)**
|
||||
- **toIfu:向IFU发送取值目标和重定向信息的接口(FtqToIfuIO)**
|
||||
- toICache:向ICache发送取值目标的接口(FtqToICacheIO)
|
||||
- toICache:向ICache发送取值目标的接口(FtqToICacheIO)
|
||||
- **toBackend:向后端发送取值目标的接口(FtqToCtrlIO)**
|
||||
- toPrefetch:向Prefetch发送取值目标的接口(FtqToPrefetchIO)
|
||||
- mmio
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
---
|
||||
title: FTQ子队列
|
||||
linkTitle: FTQ子队列
|
||||
linkTitle: 02_FTQ子队列
|
||||
weight: 12
|
||||
---
|
||||
|
||||
|
||||
# FTQ子队列
|
||||
## 文档概述
|
||||
***请注意:从本篇开始,就涉及待验证的功能点和测试点了***
|
||||
|
||||
在之前的介绍中,我们采用FTQ项这个术语描述描述FTQ队列中的每一个元素,实际上,这只是一种便于抽象的说法。
|
||||
|
||||
实际上的FTQ队列,是由好多个子队列共同构成的,一些子队列维护一类信息,另一些子队列维护另一类信息,相同ftqIdx索引的子队列信息共同构成一个完整的FTQ项。
|
||||
|
|
@ -16,9 +16,10 @@ weight: 12
|
|||
下面来介绍一些FTQ的主要子队列,以及它们内部存储的数据结构。此外,FTQ还有一些存储中间状态的更小的队列
|
||||
## 术语说明
|
||||
|
||||
| 名称 | 定义 |
|
||||
| --- | --- |
|
||||
| 暂无 | 暂无 |
|
||||
| 名称 | 定义 |
|
||||
| -------------------------------------------------------- | ------------------------------------------------------ |
|
||||
| [FTB项](https://open-verify.cc/xs-bpu/docs/ports/00_ftb/) | 分支预测结果的基本组成项,包含对预测块中分支指令和跳转指令的预测 |
|
||||
| 取指目标 | 一个预测块内包含的所有指令PC,当然,它不是直接发送所有PC,而是发送部分信号,接收方可由该信号推出所有PC |
|
||||
|
||||
## 子模块列表
|
||||
|
||||
|
|
@ -255,7 +256,7 @@ ftq_redirect_mem存储的表项。继承自SpeculativeInfo,存储RAS预测器
|
|||
| 5\.2 | FTQ_META_1R_SRAM | READ | 向多端口中输入ren,raddr决定是否读以及读取地址,从rdata读取 |
|
||||
|
||||
## 附录
|
||||
***虽然列在附录,但实际上这段内容依然十分重要,请一定要查看。***
|
||||
***虽然列在附录,但实际上这段内容依然十分重要,当你需要的时候请一定要查看。***
|
||||
### 其余状态子队列
|
||||
上述存储结构是FTQ中比较核心的存储结构,实际上,还有一些子队列用来存储一些状态信息,也同样都是存储ftqsize个(64)元素。主要有以下:
|
||||
|
||||
|
|
@ -265,6 +266,8 @@ update_target:记录每个FTQ项的跳转目标,跳转目标有两种,一
|
|||
|
||||
写入时机:上一个周期的bpu_in_fire有效的时候,或者说相对于bpu_in_fire有效时延迟一个周期写入。
|
||||
|
||||
newest_entry_ptr,newest_entry_target:这几个内部信号,表明我们当前最新的有效FTQ项。BPU新的写入,重定向等等都会对最新FTQ项进行新的安排,在相应的文档中,对其生成方式做具体的描述。
|
||||
|
||||
cfiIndex_vec:记录每个FTQ项的发生跳转的指令cfi(control flow instruction)指令在其分支预测块中的位置
|
||||
写入时机:相对于bpu_in_fire有效时延迟一个周期写入。
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
---
|
||||
title: FTQ接收BPU分支预测结果
|
||||
linkTitle: FTQ接收BPU分支预测结果
|
||||
linkTitle: 03_FTQ接收BPU分支预测结果
|
||||
weight: 12
|
||||
---
|
||||
|
||||
# FTQ接收BPU分支预测结果
|
||||
|
||||
## 文档概述
|
||||
|
||||
BPU会将分支预测结果和meta数据发给FTQ。
|
||||
|
|
@ -22,7 +20,7 @@ BPU会将分支预测结果和meta数据发给FTQ。
|
|||
| RAS (Return Address Stack) | 返回地址堆 |
|
||||
| FTQ Entry | FTQ队列中的单个表项 |
|
||||
|
||||
## 模块功能说明 。
|
||||
## 模块功能说明
|
||||
|
||||
### 1. 新的预测块进队条件
|
||||
#### **1.1 成功接收数据**
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
---
|
||||
title: FTQ向IFU发送取指目标
|
||||
linkTitle: FTQ向IFU发送取指目标
|
||||
linkTitle: 04_FTQ向IFU发送取指目标
|
||||
weight: 12
|
||||
---
|
||||
|
||||
|
||||
# FTQ向IFU发送取指目标
|
||||
|
||||
## 文档概述
|
||||
IFU需要取FTQ中的项进行取指令操作,同时也会简单地对指令进行解析,并写回错误的指令
|
||||
FTQ发送给IFU的信号同时也需发送给ICache一份,ICache是指令缓存,帮助快速读取指令。
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
---
|
||||
title: IFU向FTQ写回预译码信息
|
||||
linkTitle: IFU向FTQ写回预译码信息
|
||||
linkTitle: 05_IFU向FTQ写回预译码信息
|
||||
weight: 12
|
||||
---
|
||||
|
||||
|
||||
# IFU向FTQ写回预译码信息
|
||||
|
||||
## 文档概述
|
||||
|
||||
IFU获取来自BPU的预测信息之后,会执行预译码,并将FTQ项写回FTQ中去。我们会比对FTQ中原BPU预测项和预译码的结果,判断是否有预测错误
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
---
|
||||
title: FTQ接收后端重定向
|
||||
linkTitle: FTQ接收后端重定向
|
||||
linkTitle: 06_FTQ接收后端重定向
|
||||
weight: 12
|
||||
---
|
||||
|
||||
# FTQ接收后端重定向
|
||||
|
||||
## 文档概述
|
||||
|
||||
FTQ重定向信息有两个来源,分别是IFU 和 后端。两者的 重定向接口大致相似,但重定向的过程有一定区别。
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
---
|
||||
title: FTQ接收IFU重定向
|
||||
linkTitle: FTQ接收IFU重定向
|
||||
linkTitle: 07_FTQ接收IFU重定向
|
||||
weight: 12
|
||||
---
|
||||
|
||||
|
||||
# FTQ接收IFU重定向
|
||||
|
||||
## 文档概述
|
||||
除了后端,IFU也会发送重定向相关消息,和后端不同,IFU的重定向信息来自于预译码写回信息。相同的是,它们都是通过BranchPredictionRedirect的接口传递重定向信息。
|
||||
## 术语说明
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
---
|
||||
title: FTQ向后端发送取指目标
|
||||
linkTitle: FTQ向后端发送取指目标
|
||||
linkTitle: 08_FTQ向后端发送取指目标
|
||||
weight: 12
|
||||
---
|
||||
|
||||
|
||||
# FTQ向后端发送取指目标
|
||||
|
||||
## 文档概述
|
||||
|
||||
pc取值目标会发给后端pc mem让他自己进行存储,之后从自己的pc mem取指,此外,最新的FTQ项和对应的跳转目标也会发给后端。
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
---
|
||||
title: 执行单元修改FTQ状态队列
|
||||
linkTitle: 执行单元修改FTQ状态队列
|
||||
linkTitle: 09_执行单元修改FTQ状态队列
|
||||
weight: 12
|
||||
---
|
||||
|
||||
# 执行单元修改FTQ状态队列
|
||||
|
||||
## 文档概述
|
||||
后端的写回信息,包括重定向信息和更新信息,实际上都是执行之后,由实际执行单元根据结果发回的
|
||||
## 术语说明
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
---
|
||||
title: 冲刷指针和状态队列
|
||||
linkTitle: 冲刷指针和状态队列
|
||||
linkTitle: 10_冲刷指针和状态队列
|
||||
weight: 12
|
||||
---
|
||||
|
||||
|
||||
# 冲刷FTQ指针和状态队列
|
||||
|
||||
## 文档概述
|
||||
之前讲了,后端和IFU重定向写回会修改一些状态队列。此外,FtqPtr也是一种比较重要的维护信息。由后端或者IFU引起的重定向,需要恢复各种类型用来索引FTQ项的FtqPtr。而当重定向是由后端发起的时候,还要修改提交状态队列,说明指令已经被执行。
|
||||
## 术语说明
|
||||
|
|
@ -70,7 +67,7 @@ rob_commits的valid字段有效,可以根据其中信息对指令进行提交
|
|||
| fromIfu | 接收IFU重定向 |
|
||||
| icacheFlush | 将flush信号转发到icache |
|
||||
| toIFU | 将后端重定向转发到IFU |
|
||||
所有顶层信号均在FTQ顶层IO一文中详述。
|
||||
|
||||
## 测试点总表
|
||||
|
||||
| 序号 | 功能名称 | 测试点名称 | 描述 |
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
---
|
||||
title: FTQ向BPU发送更新与重定向信息
|
||||
linkTitle: FTQ向BPU发送更新与重定向信息
|
||||
linkTitle: 11_FTQ向BPU发送更新与重定向信息
|
||||
weight: 12
|
||||
---
|
||||
|
||||
|
||||
# FTQ向BPU发送更新与重定向信息
|
||||
|
||||
## 文档概述
|
||||
|
||||
FTQ将已提交指令的更新信息发往BPU进行训练,同时转发重定向信息。
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
title: FTQ概述
|
||||
linkTitle: FTQ概述
|
||||
linkTitle: FTQ
|
||||
weight: 12
|
||||
---
|
||||
|
||||
*下文(包括所有的FTQ文档)中会提到一些关于BPU和IFU的相关知识,详情需要去查看对应的文档: *
|
||||
下文(包括所有的FTQ文档)中会提到一些关于BPU和IFU的相关知识,详情需要去查看对应的文档:
|
||||
- [BPU文档链接](https://open-verify.cc/xs-bpu/docs/)
|
||||
- [IFU文档链接](https://open-verify.cc/UnityChipForXiangShan/docs/98_ut/01_frontend/01_ifu/)
|
||||
|
||||
|
|
@ -67,7 +67,4 @@ FTQ的全名叫取值目标队列,队列中的一个项叫做FTQ项,BPU写
|
|||
|
||||
## 循环队列
|
||||
FTQ队列实际上是一个循环队列,所有类型的FTQ指针都是同一类型,ftqPtr的value字段用来表示索引,flag字段则用来表示循环轮数,flag只有一位,进入新的循环时flag位翻转。
|
||||
这样,我们就可以在一个有限的队列空间内不断更新新的项,以及正确进行比较,判断哪个项在队列中更靠前。
|
||||
*在香山的代码中,设计了isAfter和isBefore的函数来比较指针大小,字面上的意思是指针在队列靠后还是在队列靠前的位置,但从取指令块来说*
|
||||
### 最新项
|
||||
有几个内部信号我想在这里提一下,newest_entry_ptr,newest_entry_target这几个内部信号,表明我们当前最新的有效FTQ项。BPU新的写入,重定向等等都会对最新FTQ项进行新的安排,在相应的文档中,对其生成方式做具体的描述。
|
||||
这样,我们就可以在一个有限的队列空间内不断更新新的项,以及正确进行比较,判断哪个项在队列中更靠前或者更靠后。
|
||||
|
|
|
|||
|
|
@ -0,0 +1,424 @@
|
|||
---
|
||||
title: IPrefetchPipe
|
||||
linkTitle: IPrefetchPipe
|
||||
weight: 12
|
||||
---
|
||||
|
||||
<div class="icache-ctx">
|
||||
|
||||
</div>
|
||||
|
||||
## IPrefetchPipe
|
||||
|
||||
IPrefetchPipe 为预取的流水线,三级流水设计,负责预取请求的过滤。
|
||||
|
||||
<div>
|
||||
<center>
|
||||
<img src="../iprefetchpipe_structure.png"
|
||||
alt="IPrefetchPipe模块结构示意图"
|
||||
style="zoom:40%"/>
|
||||
<br>
|
||||
IPrefetchPipe结构示意图
|
||||
</center>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
1. 接收预取请求(s0 阶段):
|
||||
|
||||
- 从 FTQ 或后端接收预取请求。
|
||||
- 发送读请求到 ITLB 和 MetaArray 缓存元数据模块。
|
||||
|
||||
2. 地址转换和缓存检查(s1 阶段):
|
||||
|
||||
- 接收 ITLB 的地址转换结果,处理可能的缺失和重发。
|
||||
- 从缓存元数据中读取标签和有效位,检查是否命中。
|
||||
- 进行 PMP 权限检查,合并异常信息。
|
||||
- 根据情况决定是否发送请求到 WayLookup 模块。
|
||||
|
||||
3. 未命中请求处理(s2 阶段):
|
||||
|
||||
- 检查与 missUnit 的交互,更新命中状态。
|
||||
- 对于无异常的未命中请求,向 missUnit 发送请求以获取数据。
|
||||
- 控制流水线的推进和刷新,处理可能的阻塞和异常。
|
||||
|
||||
### S0 流水级
|
||||
|
||||
在 S0 流水级,接收来自 FTQ 的预取请求,向 MetaArray 和 ITLB 发送请求。
|
||||
|
||||
- 接收预取请求:从 FTQ 或后端接收预取请求,提取预取请求的虚拟地址、FTQ 索引、是否为软件预取、是否跨缓存行信、虚拟组索引(s0_req_vSetIdx)和后端的异常信息。
|
||||
- 发送请求到 ITLB:将虚拟地址发送到 ITLB 进行地址转换。
|
||||
- 发送请求到缓存元数据(Meta SRAM):将请求发送到缓存的元数据存储器,以便在后续阶段读取缓存标签和有效位。
|
||||
|
||||
### S1 流水级
|
||||
|
||||
软件预取 enqway 持续一拍...
|
||||
|
||||
- 接收 ITLB 的响应:从 ITLB 接收地址转换的结果,包括物理地址 paddr、异常类型(`af`/`pf`)和特殊情况(`pbmt.nc`/`pbmt.io`)。
|
||||
- 接收缓存元数据的响应并检查缓存命中:从缓存元数据存储器 MetaArray 读取缓存标签 `tag` 和有效位,检查预取地址是否在缓存中已存在,命中结果存入 `waymask` 中。
|
||||
- 权限检查:使用 PMP 对物理地址进行权限检查,确保预取操作的合法性。
|
||||
- 异常处理和合并:合并来自后端、ITLB、PMP 的异常信息,准备在后续阶段处理。
|
||||
- 发送请求到 WayLookup 模块:当条件满足时,将元数据(命中信息 `waymask`、ITLB 信息 `paddr`/`af`/`pf`)发送到 WayLookup 模块,以便进行后续的缓存访问。
|
||||
- 状态机转换:根据当前状态和条件,更新下一个状态。
|
||||
- 状态机初始状态为 `idle`,当 S1 流水级进入新的请求时,首先判断 ITLB 是否缺失,如果缺失,就进入 `itlbResend`;如果 ITLB 命中但命中信息未入队 WayLookup,就进入 `enqWay`;如果 ITLB 命中且 WayLookup 入队但 S2 请求未处理完,就进入 `enterS2`。
|
||||
- 在 `itlbResend` 状态,重发 ITLB 请求,此时占用 ITLB 端口,直至请求回填完成,在回填完成的当拍向 MetaArray 再次发送读请求,回填期间可能发生新的写入,如果 MetaArray 繁忙(正在被写入),就进入`metaResend`,否则进入 `enqWay`。
|
||||
- 在 `metaResend` 状态,重发 MetaArray 读请求,发送成功后进入 `enqWay`。
|
||||
- 在 `enqWay` 状态,尝试将元数据入队 WayLookup,如果 WayLookup 队列已满,就阻塞至 WayLookup 入队成功,另外在 MSHR 发生新的写入时禁止入队,主要是为了防止写入的信息与命中信息所冲突,需要对命中信息进行更新。当成功入队 WayLookup 或者是软件预取时,如果 S2 空闲,就直接进入 `idle`,否则进入 `enterS2`。
|
||||
- 在 `enterS2` 状态,尝试将请求流入下一流水级,流入后进入 `idle`。
|
||||
|
||||
<div> <!--块级封装-->
|
||||
<center> <!--将图片和文字居中-->
|
||||
<img src="../iprefetchpipe_fsm.png"
|
||||
alt="IPrefetchPipe状态机"
|
||||
style="zoom:100%"/>
|
||||
<br> <!--换行-->
|
||||
IPrefetchPipe S1状态机 <!--标题-->
|
||||
</center>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
### S2 流水级
|
||||
|
||||
- 监控 missUnit 的请求:更新 MSHR 的匹配状态。综合该请求的命中结果、ITLB 异常、PMP 异常、meta 损坏,判断是否需要预取,只有不存在异常时才进行预取。
|
||||
- 发送请求到 missUnit:因为同一个预测块可能对应两个 cacheline,所以通过 Arbiter 依次将请求发送至 MissUnit。
|
||||
|
||||
### 命中信息的更新
|
||||
|
||||
在 S1 流水级中得到命中信息后,距离命中信息真正在 MainPipe 中被使用要经过两个阶段,分别是在 IPrefetchPipe 中等待入队 WayLookup 阶段和在 WayLookup 中等待出队阶段,在等待期间可能会发生 MSHR 对 Meta/DataArray 的更新,因此需要对 MSHR 的响应进行监听,分为两种情况:
|
||||
|
||||
1. 请求在 MetaArray 中未命中,监听到 MSHR 将该请求对应的 cacheline 写入了 SRAM,需要将命中信息更新为命中状态。
|
||||
2. 请求在 MetaArray 中已经命中,监听到同样的位置发生了其它 cacheline 的写入,原有数据被覆盖,需要将命中信息更新为缺失状态。
|
||||
|
||||
为了防止更新逻辑的延迟引入到 DataArray 的访问路径上,在 MSHR 发生新的写入时禁止入队 WayLookup,在下一拍入队。
|
||||
|
||||
### 刷新机制
|
||||
|
||||
在 IPrefetch 中如果收到后端重定向、IFU 预译码、fencei 带来的刷新,就冲刷整个流水线
|
||||
|
||||
IPrefetchPipe 模块中的刷新信号主要来自以下两个方面:
|
||||
|
||||
1. 全局刷新信号:由系统的其他模块发出的全局刷新信号,如怀疑流水线中存在错误数据或需要清除流水线时触发。
|
||||
|
||||
- io.flush:模块输入的全局刷新信号。当系统需要清除所有流水线阶段的数据时,该信号被置为高。
|
||||
|
||||
2. 来自分支预测单元(BPU)的刷新信号:当分支预测错误或需要更新预测信息时,BPU 会发出刷新信号。
|
||||
|
||||
- io.flushFromBpu:包含来自 BPU 的刷新信息,指示哪些指令需要被刷新。
|
||||
|
||||
## IPrefetchPipe 的功能点和测试点
|
||||
|
||||
### 接收预取请求
|
||||
|
||||
从 FTQ 接收预取请求,请求可能有效( io.req.valid 为高),可能无效; IPrefetchPipe 可能处于空闲( io.req.ready 为高),可能处于非空闲状态。
|
||||
只有在请求有效且 IPrefetchPipe 处于空闲状态时,预取请求才会被接收(这里暂不考虑 s0 的刷新信号 s0_flush ,默认其为低)。
|
||||
预取请求分为不同类型,包括硬件预取请求 (isSoftPrefetch = false)和软件预取请求 (isSoftPrefetch = true)。
|
||||
cacheline 也分为单 cacheline 和双 cacheline。
|
||||
|
||||
1. 硬件预取请求:
|
||||
预取请求为硬件 (isSoftPrefetch = false)
|
||||
|
||||
1. 预取请求可以继续:
|
||||
- 当预取请求有效且 IPrefetchPipe 处于空闲状态时,预取请求应该被接收。
|
||||
- s0_fire 信号在没有 s0 的刷新信号( s0_flush 为低)时,应该被置为高。
|
||||
2. 预取请求被拒绝--预取请求无效时:
|
||||
- 当预取请求无效时,预取请求应该被拒绝。
|
||||
- s0_fire 信号应该被置为低。
|
||||
3. 预取请求被拒绝--IPrefetchPipe 非空闲时:
|
||||
- 当 IPrefetchPipe 非空闲时,预取请求应该被拒绝。
|
||||
- s0_fire 信号应该被置为低。
|
||||
4. 预取请求被拒绝--预取请求无效且 IPrefetchPipe 非空闲时:
|
||||
- 当预取请求无效且 IPrefetchPipe 非空闲时,预取请求应该被拒绝。
|
||||
- s0_fire 信号应该被置为低。
|
||||
5. 预取请求有效且为单 cacheline 时:
|
||||
- 当预取请求有效且为单 cacheline 时,预取请求应该被接收。
|
||||
- s0_fire 为高,s0_doubleline 应该被置低(false)。
|
||||
6. 预取请求有效且为双 cacheline 时:
|
||||
- 当预取请求有效且为双 cacheline 时,预取请求应该被接收。
|
||||
- s0_fire 为高,s0_doubleline 应该被置高(true)。
|
||||
|
||||
2. 软件预取请求:
|
||||
预取请求为软件 (isSoftPrefetch = true)
|
||||
|
||||
1. 软件预取请求可以继续:
|
||||
- 当预取请求有效且 IPrefetchPipe 处于空闲状态时,软件预取请求应该被接收。
|
||||
- s0_fire 信号在没有 s0 的刷新信号( s0_flush 为低)时,应该被置为高。
|
||||
2. 软件预取请求被拒绝--预取请求无效时:
|
||||
- 当预取请求无效时,软件预取请求应该被拒绝。
|
||||
- s0_fire 信号应该被置为低。
|
||||
3. 软件预取请求被拒绝--IPrefetchPipe 非空闲时:
|
||||
- 当 IPrefetchPipe 非空闲时,软件预取请求应该被拒绝。
|
||||
- s0_fire 信号应该被置为低。
|
||||
4. 软件预取请求被拒绝--预取请求无效且 IPrefetchPipe 非空闲时:
|
||||
- 当预取请求无效且 IPrefetchPipe 非空闲时,软件预取请求应该被拒绝。
|
||||
- s0_fire 信号应该被置为低。
|
||||
5. 软件预取请求有效且为单 cacheline 时:
|
||||
- 当软件预取请求有效且为单 cacheline 时,软件预取请求应该被接收。
|
||||
- s0_fire 为高,s0_doubleline 应该被置低(false)。
|
||||
6. 软件预取请求有效且为双 cacheline 时:
|
||||
- 当软件预取请求有效且为双 cacheline 时,软件预取请求应该被接收。
|
||||
- s0_fire 为高,s0_doubleline 应该被置高(true)。
|
||||
|
||||
### 接收来自 ITLB 的响应并处理结果
|
||||
|
||||
接收 ITLB 的响应,完成虚拟地址到物理地址的转换。
|
||||
当 ITLB 发生缺失(miss)时,保存请求信息,等待 ITLB 完成后再继续处理。
|
||||
|
||||
1. 地址转换完成:
|
||||
|
||||
- 根据 ITLB 的响应,接收物理地址(paddr),并完成地址转换。
|
||||
- 处理 ITLB 响应可能在不同周期到达的情况,管理有效信号和数据保持机制,确保正确使用物理地址。
|
||||
|
||||
1. 当 ITLB 正常返回物理地址时:
|
||||
|
||||
- ITLB 在一个周期内成功返回物理地址 paddr,s1_valid 为高。
|
||||
- 确认 s1 阶段正确接收到 paddr。
|
||||
|
||||
2. 当 ITLB 发生 TLB 缺失,需要重试时:
|
||||
- fromITLB(PortNumber).bits.miss 为高,表示对应通道的 ITLB 发生了 TLB 缺失,需要重发。
|
||||
- 重发完成后,后续步骤继续进行,fromITLB(PortNumber).bits.miss 为低。
|
||||
|
||||
2. 处理 ITLB 异常:
|
||||
|
||||
- 根据 ITLB 的异常信息,处理可能的异常。pf 缺页、pgf 虚拟机缺页、af 访问错误。
|
||||
|
||||
1. 当 ITLB 发生页错误异常时:
|
||||
- s1_itlb_exception 返回的页错误。
|
||||
- iTLB 返回的物理地址有效(fromITLB(PortNumber).bits.miss 为低),s1_itlb_exception 指示页错误 pf。
|
||||
2. 当 ITLB 发生虚拟机页错误异常时:
|
||||
- s1_itlb_exception 返回的虚拟机页错误。
|
||||
- iTLB 返回的物理地址有效(fromITLB(PortNumber).bits.miss 为低),s1_itlb_exception 指示虚拟机页错误 pgf。
|
||||
3. 当 ITLB 发生访问错误异常时:
|
||||
- s1_itlb_exception 返回的访问错误。
|
||||
- iTLB 返回的物理地址有效(fromITLB(PortNumber).bits.miss 为低),s1_itlb_exception 指示访问错误 af。
|
||||
|
||||
3. 处理虚拟机物理地址(用于虚拟化):
|
||||
|
||||
- 在虚拟化环境下,处理虚拟机物理地址(gpaddr),确定访问是否针对二级虚拟机的非叶子页表项(isForVSnonLeafPTE)。
|
||||
|
||||
1. 发生虚拟机页错误异常返回虚拟机物理地址(gpaddr):
|
||||
- 发生 pgf 后,需要返回对应的 gpaddr。
|
||||
- 只有一个通道发生 pgf 时,返回对应通道的 gpaddr 即可;多个通道发生 pgf 时,返回第一个通道的 gpaddr。
|
||||
2. 当访问二级虚拟机的非叶子页表项时:
|
||||
- 发生 gpf 后,如果是访问二级虚拟机的非叶子页表项时,需要返回对应的 gpaddr。
|
||||
- 只有一个通道发生 pgf 时,返回对应通道的 gpaddr 即可;多个通道发生 pgf 时,返回第一个通道的 gpaddr。
|
||||
|
||||
4. 返回基于页面的内存类型 pbmt 信息:
|
||||
- TLB 有效时,返回 pbmt 信息。
|
||||
|
||||
### 接收来自 IMeta(缓存元数据)的响应并检查缓存命中
|
||||
|
||||
从 Meta SRAM 中读取缓存标签和有效位。
|
||||
将物理地址的标签部分与缓存元数据中的标签比较,确定是否命中。
|
||||
|
||||
1. 缓存标签比较和有效位检查:
|
||||
|
||||
- 从物理地址中提取物理标签(ptag),将其与缓存元数据中的标签进行比较,检查所有缓存路(Way)。检查有效位,确保只考虑有效的缓存行。
|
||||
|
||||
1. 缓存未命中(标签不匹配或有效位为假):
|
||||
- 当标签不匹配或者标签匹配,但是有效位为假时,表示缓存未命中。
|
||||
- s1_meta_ptags(PortNumber)(nWays) 不等于 ptags(PortNumber) 或者它们相等,但是对应的 s1_meta_valids 为低时,总之返回的 waymasks 为全 0。
|
||||
2. 单路缓存命中(标签匹配且有效位为真):
|
||||
- 当标签匹配,且有效位为真时,表示缓存命中。
|
||||
- waymasks 对应的位为 1。
|
||||
|
||||
### PMP(物理内存保护)权限检查
|
||||
|
||||
对物理地址进行 PMP 权限检查,确保预取操作的合法性。
|
||||
处理 PMP 返回的异常和 MMIO 信息
|
||||
|
||||
1. 访问被允许的内存区域
|
||||
- itlb 返回的物理地址在 PMP 允许的范围内。
|
||||
- s1_pmp_exception(i) 为 none。
|
||||
2. 访问被禁止的内存区域
|
||||
- s1_req_paddr(i) 对应的地址在 PMP 禁止的范围内。
|
||||
- s1_pmp_exception(i) 为 af。
|
||||
3. 访问 MMIO 区域
|
||||
- itlb 返回的物理地址在 MMIO 区域。
|
||||
- s1_pmp_mmio 为高。
|
||||
|
||||
### 异常处理和合并
|
||||
|
||||
backend 优先级最高,merge 方法里的异常越靠前优先级越高
|
||||
|
||||
合并来自后端、ITLB、PMP 的异常信息,按照优先级确定最终的异常类型。
|
||||
|
||||
1. 仅 ITLB 产生异常
|
||||
- s1_itlb_exception(i) 为非零,s1_pmp_exception(i) 为零。
|
||||
- s1_exception_out(i) 正确包含 ITLB 异常。
|
||||
2. 仅 PMP 产生异常
|
||||
- s1_itlb_exception(i) 为零,s1_pmp_exception(i) 为非零。
|
||||
- s1_exception_out(i) 正确包含 PMP 异常。
|
||||
3. 仅 后端 产生异常
|
||||
- s1_itlb_exception(i) 为零,s1_pmp_exception(i) 为零。
|
||||
- s1_exception_out(i) 正确包含 后端 异常。
|
||||
4. ITLB 和 PMP 都产生异常
|
||||
- s1_itlb_exception(i) 和 s1_pmp_exception(i) 都为非零。
|
||||
- s1_exception_out(i) 包含 ITLB 异常(优先级更高)。
|
||||
5. ITLB 和 后端 都产生异常
|
||||
- s1_itlb_exception(i) 和 s1_backendException(i) 都为非零。
|
||||
- s1_exception_out(i) 包含 后端 异常(优先级更高)。
|
||||
6. PMP 和 后端 都产生异常
|
||||
- s1_pmp_exception(i) 和 s1_backendException(i) 都为非零。
|
||||
- s1_exception_out(i) 包含 后端 异常(优先级更高)。
|
||||
7. ITLB、PMP 和 后端 都产生异常
|
||||
- s1_itlb_exception(i)、s1_pmp_exception(i) 和 s1_backendException(i) 都为非零。
|
||||
- s1_exception_out(i) 包含 后端 异常(优先级更高)。
|
||||
8. 无任何异常
|
||||
- s1_itlb_exception(i)、s1_pmp_exception(i)、s1_backendException(i) 都为零。
|
||||
- s1_exception_out(i) 指示无异常。
|
||||
|
||||
### 发送请求到 WayLookup 模块
|
||||
|
||||
当条件满足时,将请求发送到 WayLookup 模块,以进行后续的缓存访问。
|
||||
|
||||
1. 正常发送请求到 WayLookup
|
||||
- toWayLookup.valid 为高,toWayLookup.ready 为高,s1_isSoftPrefetch 为假。
|
||||
- 请求成功发送,包含正确的地址、标签、waymask 和异常信息。
|
||||
2. WayLookup 无法接收请求
|
||||
- toWayLookup.valid 为高,toWayLookup.ready 为假。
|
||||
- 状态机等待 WayLookup 准备好,不会错误地推进。
|
||||
3. 软件预取请求不发送到 WayLookup
|
||||
- s1_isSoftPrefetch 为真。
|
||||
- toWayLookup.valid 为假,不会发送预取请求到 WayLookup。
|
||||
|
||||
### 状态机控制和请求处理流程
|
||||
|
||||
使用状态机管理 s1 阶段的请求处理流程。
|
||||
包括处理 ITLB 重发、Meta 重发、进入 WayLookup、等待 s2 准备等状态
|
||||
|
||||
1. 初始为 m_idle 状态:
|
||||
|
||||
1. 正常流程推进,保持 m_idle 状态
|
||||
|
||||
- s1_valid 为高,itlb_finish 为真,toWayLookup.fire 为真,s2_ready 为真。
|
||||
- 状态机保持在 m_idle 状态,s1 阶段顺利推进。
|
||||
|
||||
2. ITLB 未完成,需要重发
|
||||
|
||||
- s1_valid 为高,itlb_finish 为假。
|
||||
- 状态机进入 m_itlbResend 状态,等待 ITLB 完成。
|
||||
|
||||
3. ITLB 完成,WayLookup 未命中
|
||||
|
||||
- s1_valid 为高,itlb_finish 为真,toWayLookup.fire 为假。
|
||||
- 状态机进入 m_enqWay 状态,等待 WayLookup 入队。
|
||||
|
||||
2. 初始为 m_itlbResend 状态:
|
||||
1. ITLB 命中, MetaArray 空闲,需要 WayLookup 入队
|
||||
- itlb_finish 为假,toMeta.ready 为真。
|
||||
- 状态机进入 m_enqWay 状态,等待 WayLookup 入队。
|
||||
2. ITLB 命中, MetaArray 繁忙,等待 MetaArray 读请求
|
||||
- itlb_finish 为假,toMeta.ready 为假。
|
||||
- 状态机进入 m_metaResend 状态,MetaArray 读请求
|
||||
3. 初始为 m_metaResend 状态:
|
||||
|
||||
1. MetaArray 空闲 ,需要 WayLookup 入队
|
||||
- toMeta.ready 为真。
|
||||
- 状态机进入 m_enqWay 状态,等待 WayLookup 入队。
|
||||
|
||||
4. 初始为 m_enqWay 状态:
|
||||
|
||||
1. WayLookup 入队完成或者为软件预取, S2 空闲, 重新进入空闲状态
|
||||
- toWayLookup.fire 或 s1_isSoftPrefetch 为真,s2_ready 为假。
|
||||
- 状态机进入空闲状态 m_idle。
|
||||
2. WayLookup 入队完成或者为软件预取, S2 繁忙,需要 enterS2 状态
|
||||
- toWayLookup.fire 或 s1_isSoftPrefetch 为真,s2_ready 为真。
|
||||
- 状态机进入 m_enterS2 状态,等待 s2 阶段准备好。
|
||||
|
||||
5. 初始为 m_enterS2 状态:
|
||||
1. s2 阶段准备好,请求进入下流水级,流入后进入 m_idle 状态
|
||||
- s2_ready 为真。
|
||||
- 状态机进入空闲状态 m_idle。
|
||||
|
||||
### 监控 missUnit 的请求
|
||||
|
||||
检查 missUnit 的响应,更新缓存的命中状态和 MSHR 的匹配状态。
|
||||
|
||||
1. 请求与 MSHR 匹配且有效:
|
||||
|
||||
- s2_req_vSetIdx 和 s2_req_ptags 与 fromMSHR 中的数据匹配,且 fromMSHR.valid 为高,fromMSHR.bits.corrupt 为假。
|
||||
- s2_MSHR_match(PortNumber) 为真, s2_MSHR_hits(PortNumber) 应保持为真
|
||||
|
||||
2. 请求在 SRAM 中命中:
|
||||
|
||||
- s2_waymasks(PortNumber) 中有一位为高,表示在缓存中命中。
|
||||
- s2_SRAM_hits(PortNumber) 为真,s2_hits(PortNumber) 应为真。
|
||||
|
||||
3. 请求未命中 MSHR 和 SRAM:
|
||||
- 请求未匹配 MSHR,且 s2_waymasks(PortNumber) 为空。
|
||||
- s2_MSHR_hits(PortNumber)、s2_SRAM_hits(PortNumber) 均为假, s2_hits(PortNumber) 为假。
|
||||
|
||||
### 发送请求到 missUnit
|
||||
|
||||
对于未命中的预取请求,向 missUnit 发送请求,以获取缺失的数据。
|
||||
|
||||
1. 确定需要发送给 missUnit 的请求
|
||||
|
||||
- 根据命中状态、异常信息、MMIO 信息等,确定哪些请求需要发送到 missUnit(即 s2_miss)。
|
||||
|
||||
1. 请求未命中且无异常,需要发送到 missUnit:
|
||||
|
||||
- s2_hits(PortNumber) 为假(未命中缓存),s2_exception 无异常,s2_mmio 为假(不是 MMIO 或不可缓存的内存)。
|
||||
- s2_miss(PortNumber) 为真,表示需要发送请求到 missUnit。
|
||||
|
||||
2. 请求命中或有异常,不需要发送到 missUnit:
|
||||
|
||||
- s2_hits(i) 为真(已命中)或者 s2_exception 有异常 或者 s2_mmio 为真(MMIO 访问)。
|
||||
- s2_miss(i) 为假,不会发送请求到 missUnit。
|
||||
|
||||
3. 双行预取时,处理第二个请求的条件:
|
||||
- s2_doubleline 为真,处理第二个请求。
|
||||
- 如果第一个请求有异常或 MMIO,s2_miss(1) 应为假,后续请求被取消或处理。
|
||||
|
||||
2. 避免发送重复请求,发送请求到 missUnit
|
||||
|
||||
- 使用寄存器 has_send 记录每个端口是否已发送请求,避免重复发送。
|
||||
- 将需要发送的请求通过仲裁器 toMSHRArbiter 发送到 missUnit。
|
||||
|
||||
1. 在 s1_real_fire 时,复位 has_send:
|
||||
|
||||
- s1_real_fire 为高。
|
||||
- has_send(PortNumber) 应被复位为假,表示新的请求周期开始。
|
||||
|
||||
2. 当请求成功发送时,更新 has_send:
|
||||
|
||||
- toMSHRArbiter.io.in(PortNumber).fire 为高(请求已发送)。
|
||||
- has_send(PortNumber) 被设置为真,表示该端口已发送请求。
|
||||
|
||||
3. 避免重复发送请求:
|
||||
|
||||
- 同一请求周期内,has_send(PortNumber) 为真,s2_miss(PortNumber) 为真。
|
||||
- toMSHRArbiter.io.in(PortNumber).valid 为假,不会再次发送请求。
|
||||
|
||||
4. 正确发送需要的请求到 missUnit:
|
||||
|
||||
- s2_valid 为高,s2_miss(i) 为真,has_send(i) 为假。
|
||||
- toMSHRArbiter.io.in(i).valid 为高,请求被成功发送。
|
||||
|
||||
5. 仲裁器正确仲裁多个请求:
|
||||
- 多个端口同时需要发送请求。
|
||||
- 仲裁器按照优先级或设计要求选择请求发送到 missUnit,未被选中的请求在下个周期继续尝试发送。
|
||||
|
||||
### 刷新机制
|
||||
|
||||
- io.flush: 全局刷新信号,当该信号为高时,所有请求都需要刷新。
|
||||
- from_bpu_s0_flush:当请求不是软件预取(!s0_isSoftPrefetch, 软件预取请求是由特定的指令触发的,与指令流中的分支预测无关。因此,在处理刷新信号时,对于软件预取请求,通常不受来自 BPU 的刷新信号影响。),且 BPU 指示需要在 Stage 2 或 Stage 3 刷新的请求,由于该请求尚未进入 s1 阶段,因此在 s0 阶段也需要刷新。
|
||||
- s0_flush:综合考虑全局刷新信号、来自 BPU 的刷新信号,以及 s1 阶段的刷新信号
|
||||
- from_bpu_s1_flush:当 s1 阶段的请求有效且不是软件预取,且 BPU 指示在 Stage 3 需要刷新,则在 s1 阶段需要刷新。
|
||||
- io.itlbFlushPipe:当 s1 阶段需要刷新时,该信号用于通知 ITLB 刷新其流水线,以保持一致性。
|
||||
- s1_flush:综合考虑全局刷新信号和来自 BPU 的刷新信号。
|
||||
- s2_flush:用于控制 s2 阶段是否需要刷新。
|
||||
|
||||
1. 发生全局刷新
|
||||
- io.flush 为高。
|
||||
- s0_flush、s1_flush、s2_flush 分别为高,所有阶段的请求被正确清除。
|
||||
2. 来自 BPU 的刷新
|
||||
- io.flushFromBpu.shouldFlushByStageX 为真(X 为 2 或 3),且请求不是软件预取。
|
||||
- 对应阶段的 from_bpu_sX_flush 为高,sX_flush 为高,阶段请求被刷新。
|
||||
3. 刷新时状态机复位
|
||||
- s1_flush 为高。
|
||||
- 状态机 state 被重置为 m_idle 状态。
|
||||
4. ITLB 管道同步刷新
|
||||
- s1_flush 为高。
|
||||
- io.itlbFlushPipe 为高,ITLB 被同步刷新。
|
||||
|
|
@ -0,0 +1,362 @@
|
|||
---
|
||||
title: MainPipe
|
||||
linkTitle: MainPipe
|
||||
weight: 12
|
||||
---
|
||||
|
||||
<div class="icache-ctx">
|
||||
|
||||
</div>
|
||||
|
||||
## MainPipe
|
||||
|
||||
MainPipe 为 ICache 的主流水,为三级流水设计,负责从 DataArray 中读取数据,pmp 检查,缺失处理,并且将结果返回给 IFU。
|
||||
|
||||
<div>
|
||||
<center>
|
||||
<img src="../mainpipe_structure.png"
|
||||
alt="MainPipe结构示意图"
|
||||
style="zoom:100%"/>
|
||||
<br>
|
||||
MainPipe结构示意图
|
||||
</center>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
1. 从 WayLookup 获取信息,访问 DataArray 单路(S0 阶段)
|
||||
在 S0 流水级,从 WayLookup 获取元数据,包括路命中信息和 ITLB 查询结果,访问 DataArray 的单路,如果 DataArray 正在被写或 WayLookup 中没有有效表项,流水线就会阻塞。每次重定向后,FTQ 中同一个请求被同时发送到 MainPipe 和 IPrefetchPipe 中,MainPipe 始终需要等待 IPrefetchPipe 将请求的查询信息写入 WayLookup 后才能向下走,导致了 1 拍重定向延迟,当预取超过取指时,该延迟就会被覆盖。
|
||||
|
||||
- 接收并解析来自 FTQ 的取指请求,提取必要的请求信息,如虚拟地址、缓存组索引、块内偏移、是否为双行读、后端的异常信息。
|
||||
- 从 WayLookup 模块获取缓存命中信息和 TLB 信息,包括 waymask、物理标签、虚拟机物理地址、是否为叶节点、 ITLB 异常、ITLB 的 PBMT 信息、缓存元数据的校验码。
|
||||
- 访问 DataArray 的单路,如果 DataArray 正在被写或 WayLookup 中没有有效表项,流水线就会阻塞。
|
||||
- 每次重定向后,FTQ 中同一个请求被同时发送到 MainPipe 和 IPrefetchPipe 中,MainPipe 始终需要等待 IPrefetchPipe 将请求的查询信息写入 WayLookup 后才能向下走,导致了 1 拍重定向延迟,当预取超过取指时,该延迟就会被覆盖。
|
||||
|
||||
2. 接收上一个阶段的信息并进行数据暂存、PMP 检查、从 DataArray 获取读响应异常合并、替换策略更新以及监控 MissUnit(S1 阶段)
|
||||
|
||||
- 寄存并延迟 S0 阶段信息
|
||||
- 从 S0 获取的地址、tag、命中方式(waymask)、TLB 异常标志、下一拍要用的数据等,都会在 S1 寄存一拍,保证在流水线停顿时也能维持正确值。
|
||||
- Meta ECC 检查
|
||||
- 对 S0 读出的 meta 和其校验码(ECC/Parity)进行比对,判断是否发生错误。如果关闭 parity 功能,则跳过该检查。
|
||||
- 更新 replacer
|
||||
- 对确定命中的访问请求,进行“touch”更新,标记最近使用过的 way,以便后续替换算法正确运行。
|
||||
- PMP 检查
|
||||
- 根据 S0 得到的物理地址(paddr),在 S1 对其进行 PMP 检查,判断是否拥有执行权限、是否为 MMIO 等。在当拍收到响应,将结果寄存到下一流水级进行处理。
|
||||
- 需要指出,IPrefetchPipe s1 流水级也会进行 PMP 检查,和此处的检查实际上是完全一样的,分别检查只是为了优化时序(避免 `ITLB(reg) -> ITLB.resp -> PMP.req -> PMP.resp -> WayLookup.write -> bypass -> WayLookup.read -> MainPipe s1(reg)` 的超长组合逻辑路径)。
|
||||
- 异常合并
|
||||
- 将 ITLB 与 PMP 异常进行优先级合并,产生最终的异常标记。
|
||||
- 选择数据来源:MSHR 或 SRAM
|
||||
- 接收 DataArray 返回的 data 和 code 并寄存,同时监听 MSHR 的响应,当 DataArray 和 MSHR 的响应同时有效时,后者的优先级更高。当 MSHR 已在填充一些数据,如果当前请求与 MSHR 命中,可以在 S1 阶段直接选用 MSHR 的数据,而不必依赖 SRAM 读出的结果。
|
||||
|
||||
3. 监控 MissUnit,在 ECC 校验、异常处理和缺失处理之后,将最终的数据、异常信息传递给 IFU,完成取指流程(S2 阶段)
|
||||
|
||||
- ECC 校验
|
||||
- DataArray ECC 校验,对 S1 流水级寄存的 code 进行校验,生成 data 是否损坏信号 s2_data_corrupt。如果校验出错,就将错误报告给 BEU。
|
||||
- MetaArray ECC 校验,IPrefetchPipe 读出 MetaArray 的数据后会直接进行校验,并将校验结果随命中信息一起入队 WayLookup 并随 MainPipe 流水到达 S2 级(meta_corrupt 信号),在此处随 DataArray 的 ECC 校验结果一起报告给 BEU。
|
||||
- 监控 MissUnit 响应端口
|
||||
- 检查当前 S2 阶段的请求是否与 MSHR 中的条目匹配,命中时寄存 MSHR 响应的数据,为了时序在下一拍才将数据发送到 IFU。
|
||||
- 更新 Data 和其是否来自 MSHR 的信息。
|
||||
- 更新 s2_hits 和处理异常。
|
||||
- 处理 L2 Cache 的 Corrupt 标志。
|
||||
- 缺失处理,发送 Miss 请求到 MSHR
|
||||
- 计算是否需要重新获取(Refetch)。
|
||||
- 通过是否命中、ECC 错误、正确跨行、是否异常和是否属于 MMIO 区域来发送 Miss 请求。
|
||||
- 设置 Arbiter 合并多个端口的 Miss 请求,确保一次只处理一个请求,同时有避免重复请求的设置。
|
||||
- 判断 Fetch 是否完成。
|
||||
- 生成 L2 Cache 的异常标记,再将当前 S2 阶段的异常(包括 ITLB、PMP)与 L2 Cache 的异常进行合并。
|
||||
- 响应 IFU
|
||||
- 将最终的数据、异常信息传递给 IFU,完成取指流程。
|
||||
- 根据请求是否为跨行,决定如何处理双行数据。
|
||||
- 报告 TileLink 的 Corrupt 错误
|
||||
- 对于每个端口,如果在当前周期 s2_fire 时检测到来自 L2 Cache 的数据 corrupt 错误,就将错误报告给 BEU。
|
||||
|
||||
## MainPipe 的功能点和测试点
|
||||
|
||||
### 访问 DataArray 的单路
|
||||
|
||||
根据从 WayLookup 获取信息,包括路命中信息和 ITLB 查询结果还有 DataArray 当前的情况,决定是否需要从 DataArray 中读取数据。
|
||||
|
||||
1. 访问 DataArray 的单路
|
||||
- 当 WayLookup 中的信息表明路命中时,ITLB 查询成功,并且 DataArray 当前没有写时,MainPipe 会向 DataArray 发送读取请求,以获取数据。
|
||||
- s0_hits 为高(一路命中),s0_itlb_exception 信号为零(ITLB 查询成功),toData.last.ready 为高(DataArray 没有正在进行的写操作)。
|
||||
- toData.valid 信号为高,表示 MainPipe 向 DataArray 发出了读取请求。
|
||||
2. 不访问 DataArray(Way 未命中) ==会访问,但是返回数据无效==
|
||||
- 当 WayLookup 中的信息表明路未命中时,MainPipe 不会向 DataArray 发送读取请求。
|
||||
- s0_hits 为低表示缓存未命中
|
||||
- toData.valid 信号为低,表示 MainPipe 未向 DataArray 发出读取请求。
|
||||
3. 不访问 DataArray(ITLB 查询失败)==会访问,但是返回数据无效==
|
||||
- 当 ITLB 查询失败时,MainPipe 不会向 DataArray 发送读取请求。
|
||||
- s0_itlb_exception 信号不为零(ITLB 查询失败)。
|
||||
- toData.valid 信号为低,表示 MainPipe 未向 DataArray 发出读取请求。
|
||||
4. 不访问 DataArray(DataArray 正在进行写操作)
|
||||
- 当 DataArray 正在进行写操作时,MainPipe 不会向 DataArray 发送读取请求。
|
||||
- toData.last.ready 信号为低,表示 DataArray 正在进行写操作。
|
||||
- toData.valid 信号为低,表示 MainPipe 未向 DataArray 发出读取请求。
|
||||
|
||||
### Meta ECC 校验
|
||||
|
||||
将物理地址的标签部分与对应的 Meta 进行 ECC 校验,以确保 Meta 的完整性。
|
||||
|
||||
1. 无 ECC 错误
|
||||
|
||||
- 当 waymask 全为 0(没有命中),则 hit_num 为 0 或 waymask 有一位为 1(一路命中),hit_num 为 1 且 ECC 对比通过(encodeMetaECC(meta) == code)
|
||||
- s1_meta_corrupt 为假。
|
||||
|
||||
2. 单路命中的 ECC 错误
|
||||
|
||||
- 当 waymask 有一位为 1(一路命中),ECC 对比失败(encodeMetaECC(meta) != code)
|
||||
- s1_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。
|
||||
|
||||
3. 多路命中
|
||||
> hit multi-way, must be an ECC failure
|
||||
|
||||
- 当 waymask 有两位及以上为 1(多路命中),视为 ECC 错误。
|
||||
- s1_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。
|
||||
|
||||
4. ECC 功能关闭
|
||||
|
||||
- 当奇偶校验关闭时(ecc_enable 为低),强制清除 s1_meta_corrupt 信号置位。
|
||||
- 不管是否发生 ECC 错误,s1_meta_corrupt 都为假。
|
||||
|
||||
### PMP 检查
|
||||
|
||||
- 将 S1 的物理地址 s1_req_paddr(i) 和指令 TlbCmd.exec 发往 PMP,判断取指是否合法。
|
||||
- 防止非法地址,区分普通内存和 MMIO 内存。
|
||||
|
||||
1. 没有异常
|
||||
- s1_pmp_exception 为全零,表示没有 PMP 异常。
|
||||
2. 通道 0 有 PMP 异常
|
||||
- s1_pmp_exception(0) 为真,表示通道 0 有 PMP 异常。
|
||||
3. 通道 1 有 PMP 异常
|
||||
- s1_pmp_exception(1) 为真,表示通道 1 有 PMP 异常。
|
||||
4. 通道 0 和通道 1 都有 PMP 异常
|
||||
- s1_pmp_exception(0) 和 s1_pmp_exception(1) 都为真,表示通道 0 和通道 1 都有 PMP 异常。
|
||||
5. 没有映射到 MMIO 区域
|
||||
- s1_pmp_mmio(0) 和 s1_pmp_mmio(1) 都为假,表示没有映射到 MMIO 区域。
|
||||
6. 通道 0 映射到了 MMIO 区域
|
||||
- s1_pmp_mmio(0) 为真,表示映射到了 MMIO 区域。
|
||||
7. 通道 1 映射到了 MMIO 区域
|
||||
- s1_pmp_mmio(1) 为真,表示映射到了 MMIO 区域。
|
||||
8. 通道 0 和通道 1 都映射到了 MMIO 区域
|
||||
- s1_pmp_mmio(0) 和 s1_pmp_mmio(1) 都为真,表示通道 0 和通道 1 都映射到了 MMIO 区域。
|
||||
|
||||
### 异常合并
|
||||
|
||||
- 将 s1_itlb_exception 与 s1_pmp_exception 合并生成 s1_exception_out。
|
||||
- ITLB 异常通常优先于 PMP 异常。merge
|
||||
|
||||
1. 没有异常
|
||||
- s1_exception_out 为全零,表示没有异常。
|
||||
2. 只有 ITLB 异常
|
||||
- s1_exception_out 和 s1_itlb_exception 一致
|
||||
3. 只有 PMP 异常
|
||||
- s1_exception_out 和 s1_pmp_exception 一致
|
||||
4. ITLB 与 PMP 异常同时出现
|
||||
> itlb has the highest priority, pmp next
|
||||
- s1_exception_out 和 s1_itlb_exception 一致
|
||||
|
||||
### MSHR 匹配和数据选择
|
||||
|
||||
- 检查当前的请求是否与 MSHR 中正在处理的缺失请求匹配。
|
||||
- 判断 缓存组索引相同(s1_req_vSetIdx(i) == fromMSHR.bits.vSetIdx) ,物理标签相同 (s1_req_ptags(i) == fromMSHR.bits.blkPaddr);若匹配 MSHR 有效且没有错误(fromMSHR.valid && !fromMSHR.bits.corrupt),则优先使用 MSHR 中的数据
|
||||
- 避免重复访问 Data SRAM,提升性能;当 MSHR 中已有重填结果时,可立即命中。
|
||||
|
||||
1. 命中 MSHR
|
||||
|
||||
- MSHR 中已有正确数据时,S1 阶段能直接拿到
|
||||
- s1_MSHR_hits(i) 为 true 时,s1_datas(i) 为 s1_bankMSHRHit(i),s1_data_is_from_MSHR(i) 为 true
|
||||
|
||||
2. 未命中 MSHR
|
||||
|
||||
- MSHR 中存放的地址与当前请求不同,那么应该读取 SRAM 的数据
|
||||
- s1_MSHR_hits(i) 为 true 时,s1_datas(i) 为 fromData.datas(i),s1_data_is_from_MSHR(i) 为 false
|
||||
|
||||
3. MSHR 数据 corrupt
|
||||
|
||||
- fromMSHR.bits.corrupt = true,那么 MSHR 将不匹配,应该读取 SRAM 的数据
|
||||
- s1_datas(i) 为 fromData.datas(i),s1_data_is_from_MSHR(i) 为 false
|
||||
|
||||
### Data ECC 校验
|
||||
|
||||
在 S2 阶段,对从 S1 或 MSHR 获得的数据(如 s2_datas)进行 ECC 校验:
|
||||
|
||||
- 若 ECC 校验失败,则标记 s2_data_corrupt(i) = true。
|
||||
- 若数据来自 MSHR,则不重复进行 ECC 校验(或忽略 corrupt)
|
||||
|
||||
1. 无 ECC 错误
|
||||
|
||||
- s2_bank 全部没有损坏,bank 也选对了对应的端口和 bank,数据不来自 MSHR
|
||||
- s2_data_corrupt(i) 为 false,没有 ECC 错误。
|
||||
|
||||
2. 单 Bank ECC 错误
|
||||
|
||||
- s2_bank_corrupt(bank) 有一个为 true ,即对应的 bank 有损坏;同时 bank 也选对了对应的端口和 bank,数据不来自 MSHR
|
||||
- s2_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。
|
||||
|
||||
3. 多 Bank ECC 错误
|
||||
|
||||
- s2_bank_corrupt(bank) 有两个或以上为 true,即对应的 bank 有损坏;同时 bank 也选对了对应的端口和 bank,数据不来自 MSHR
|
||||
- s2_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。
|
||||
|
||||
4. ECC 功能关闭
|
||||
|
||||
- 当奇偶校验关闭时(ecc_enable 为低),强制清除 s2_data_corrupt 信号置位。
|
||||
- 不管是否发生 ECC 错误,s2_data_corrupt 都为假。
|
||||
|
||||
### 冲刷 MetaArray
|
||||
|
||||
Meta 或者 Data ECC 校验错误时,会冲刷 MetaArray,为重取做准备。
|
||||
|
||||
1. 只有 Meta ECC 校验错误
|
||||
> if is meta corrupt, clear all way (since waymask may be unreliable)
|
||||
|
||||
- 当 s1_meta_corrupt 为真时,MetaArray 的所有路都会被冲刷。
|
||||
- toMetaFlush(i).valid 为真,toMetaFlush(i).bits.waymask 对应端口的所有路置位。
|
||||
|
||||
2. 只有 Data ECC 校验错误
|
||||
> if is data corrupt, only clear the way that has error
|
||||
|
||||
- 当 s2_data_corrupt 为真时,只有对应路会被冲刷。
|
||||
- toMetaFlush(i).valid 为真,toMetaFlush(i).bits.waymask 对应端口的对应路置位。
|
||||
|
||||
3. 同时有 Meta ECC 校验错误和 Data ECC 校验错误
|
||||
|
||||
- 处理 Meta ECC 的优先级更高, 将 MetaArray 的所有路冲刷。
|
||||
- toMetaFlush(i).valid 为真,toMetaFlush(i).bits.waymask 对应端口的所有路置位。
|
||||
|
||||
### 监控 MSHR 匹配与数据更新
|
||||
|
||||
- 判断是否命中 MSHR
|
||||
- 根据 MSHR 是否命中和 s1 阶段是否发射来更新 s2 的数据,s2 的命中状态和 l2 是否损坏
|
||||
|
||||
1. MSHR 命中(匹配且本阶段有效)
|
||||
|
||||
- MSHR 的 vSetIdx / blkPaddr 与 S2 请求一致, fromMSHR.valid 有效,s2_valid 也有效
|
||||
- s2_MSHR_match,s2_MSHR_hits 为高,s2_bankMSHRHit 对应 bank 为高
|
||||
- s1_fire 无效时,s2_datas 更新为 MSHR 的数据,将 s2_data_is_from_MSHR 对应位置位,s2_hits 置位,清除 s2_data_corrupt,l2 的 corrupt 更新为 fromMSHR.bits.corrupt
|
||||
- s1_fire 有效时,s2_datas 为 s1_datas 的数据,将 s2_data_is_from_MSHR 对应位置为 s1 的 s1_data_is_from_MSHR,s2_hits 置为 s1_hits,清除 s2_data_corrupt,l2 的 corrupt 为 false
|
||||
|
||||
2. MSHR 未命中
|
||||
|
||||
- MSHR 的 vSetIdx / blkPaddr 与 S2 请求一致, fromMSHR.valid 有效,s2_valid 也有效,至少有一个未达成
|
||||
- s2_MSHR_hits(i) = false,S2 不会更新 s2_datas,继续保持原先 SRAM 数据或进入 Miss 流程。
|
||||
|
||||
### Miss 请求发送逻辑和合并异常
|
||||
|
||||
- 通过计算 s2_should_fetch(i) 判断是否需要向 MSHR 发送 Miss 请求:
|
||||
- 当出现未命中 (!s2_hits(i)) 或 ECC 错误(s2_meta_corrupt(i) || s2_data_corrupt(i)) 时,需要请求重新获取。
|
||||
- 若端口存在异常或处于 MMIO 区域,则不发送 Miss 请求。
|
||||
- 使用 Arbiter 将多个端口的请求合并后发送至 MSHR。
|
||||
- 通过 s2_has_send(i) 避免重复请求。
|
||||
- 将 S2 阶段已有的 ITLB/PMP 异常(s2_exception)与 L2 Cache 报告的 s2_l2_corrupt(i)(封装后为 s2_l2_exception(i))进行合并。
|
||||
|
||||
1. 未发生 Miss
|
||||
|
||||
- 当 s2_hits(i) 为高(s2 已经命中),s2 的 meta 和 data 都没有错误,s2 异常,处于 mmio 区域
|
||||
- 以上条件至少满足一个时,s2_should_fetch(i) 为低,表示不发送 Miss 请求。
|
||||
|
||||
2. 单口 Miss
|
||||
|
||||
- 当出现未命中 (!s2_hits(i)) 或 ECC 错误(s2_meta_corrupt(i) || s2_data_corrupt(i)),端口不存在异常且未处于 MMIO 区域时,会向 MSHR 发送 Miss 请求。
|
||||
- toMSHRArbiter.io.in(i).valid = true ,Arbiter 只发送一条 Miss 请求。
|
||||
|
||||
3. 双口都需要 Miss
|
||||
|
||||
- 同上,但是两个端口都满足 s2_should_fetch 为高的条件。
|
||||
- toMSHRArbiter.io.in(0).valid、toMSHRArbiter.io.in(1).valid 均为 true,Arbiter 根据仲裁顺序依次发出请求。
|
||||
|
||||
4. 重复请求屏蔽
|
||||
|
||||
- 当 s1_fire 为高,表示可以进入 s2 阶段,那么 s2 还没有发送 s2_has_send(i) := false.B
|
||||
- 如果已经有请求发送了,那么对应的 toMSHRArbiter.io.in(i).fire 为高,表示对应的请求可以发送,s2_has_send(i) := true。
|
||||
- 此时再次发送,toMSHRArbiter.io.in(i).valid 为低,表示发送失败。
|
||||
|
||||
5. 仅 ITLB/PMP 异常
|
||||
|
||||
- S1 阶段已记录了 ITLB 或 PMP 异常,L2 corrupt = false。
|
||||
- 2_exception_out 仅保留 ITLB/PMP 异常标记,无新增 AF 异常。
|
||||
|
||||
6. 仅 L2 异常
|
||||
|
||||
- S2 阶段 s2_l2_corrupt(i) = true,且无 ITLB/PMP 异常。
|
||||
- s2_exception_out(i) 表示 L2 访问错误(AF)。
|
||||
|
||||
7. ITLB + L2 同时出现
|
||||
|
||||
- 同时触发 ITLB 异常和 L2 corrupt。
|
||||
- s2_exception_out 优先保留 ITLB 异常类型,不被 L2 覆盖。
|
||||
|
||||
8. s2 阶段取指完成
|
||||
|
||||
- s2_should_fetch 的所有端口都为低,表示需要取指,那么取指完成
|
||||
- s2_fetch_finish 为高
|
||||
|
||||
### 响应 IFU
|
||||
|
||||
- 若当前周期 S2 成功发射(s2_fire = true)且数据获取完毕(s2_fetch_finish),则把数据、异常信息、物理地址等打包到 toIFU.bits 输出。
|
||||
- 若为双行请求(s2_doubleline = true),也会向 IFU 发送第二路的信息(地址、异常)。
|
||||
|
||||
1. 正常命中并返回
|
||||
|
||||
- 不存在任何异常或 Miss,s2 命中,s2 阶段取指完成,外部的 respStall 停止信号也为低 。
|
||||
- toIFU.valid = true,toIFU.bits.data 为正确的 Cacheline 数据,toIFU.bits.exception、pmp_mmio、itlb_pbmt = none。
|
||||
|
||||
2. 异常返回
|
||||
|
||||
- 设置 ITLB、PMP、或 L2 corrupt 异常。
|
||||
- toIFU.bits.exception(i) = 对应异常类型,pmp_mmio、itlb_pbmt 根据是否有对应的异常设置为 true。
|
||||
|
||||
3. 跨行取指
|
||||
|
||||
- s2_doubleline = true,同时检查第一路、第二路返回情况。
|
||||
- toIFU.bits.doubleline = true。
|
||||
- 若第二路正常,toIFU.bits.exception(1) = none;若第二路异常,则 exception(1) 标记相应类型。
|
||||
- pmp_mmio、itlb_pbmt 类似。
|
||||
|
||||
4. RespStall
|
||||
|
||||
- 外部 io.respStall = true,导致 S2 阶段无法发射到 IFU。
|
||||
- s2_fire = false,toIFU.valid 也不拉高,S2 保持原状态等待下一拍(或直到 respStall 解除)。
|
||||
|
||||
### L2 Corrupt 报告
|
||||
|
||||
- 当检测到 L2 Cache 返回的 corrupt 标记时(s2_l2_corrupt(i) = true),在 S2 完成发射后额外向外部错误接口 io.errors(i) 报告。
|
||||
- 与 Data ECC 或 Meta ECC 不同,L2 corrupt 由 L2 自己报告给 BEU,这里不需要再次报告给 beu。
|
||||
|
||||
1. L2 Corrupt 单路
|
||||
|
||||
- s2 阶段准备完成可以发射(s2_fire 为高),s2_MSHR_hits(0)和 fromMSHR.bits.corrupt 为高
|
||||
- s2_l2_corrupt(0) = true,io.errors(0).valid = true,io.errors(0).bits.source.l2 = true。
|
||||
|
||||
2. 双路同时 corrupt
|
||||
|
||||
- 端口 0 和端口 1 都从 L2 corrupt 数据中获取。
|
||||
- s2_l2_corrupt 均为 true,发射后分别报告到 io.errors(0) 和 io.errors(1)。
|
||||
|
||||
### 刷新机制
|
||||
|
||||
- io.flush:外部的全局刷新信号,它用于指示整个流水线需要被冲刷(清空)。
|
||||
- s0_flush: S0 阶段内部的刷新信号,它由 io.flush 传递而来,用于控制 S0 阶段的刷新操作。
|
||||
- s1_flush: S1 阶段内部的刷新信号,它由 io.flush 传递而来,用于控制 S1 阶段的刷新操作。
|
||||
- s2_flush: S2 阶段内部的刷新信号,它由 io.flush 传递而来,用于控制 S2 阶段的刷新操作。
|
||||
|
||||
1. 全局刷新
|
||||
|
||||
- io.flush 被激活时,流水线的各个阶段(S0, S1 和 S2)都能正确响应并执行刷新操作。
|
||||
- io.flush = true。
|
||||
- s0_flush, s1_flush, s2_flush = true。
|
||||
|
||||
2. S0 阶段刷新
|
||||
|
||||
- s0_flush = true。
|
||||
- s0_fire = false。
|
||||
|
||||
3. S1 阶段刷新
|
||||
|
||||
- s1_flush = true。
|
||||
- s1_valid, s1_fire = false。
|
||||
|
||||
4. S2 阶段刷新
|
||||
|
||||
- s2_flush = true。
|
||||
- s2_valid, toMSHRArbiter.io.in(i).valid , s2_fire = false
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
---
|
||||
title: WayLookup
|
||||
linkTitle: WayLookup
|
||||
weight: 12
|
||||
---
|
||||
|
||||
<div class="icache-ctx">
|
||||
|
||||
</div>
|
||||
|
||||
## WayLookup
|
||||
|
||||
<div>
|
||||
<center>
|
||||
<img src="../waylookup_structure_rw.png"
|
||||
alt="WayLookup 读写结构"
|
||||
style="zoom:40%"/>
|
||||
<br>
|
||||
WayLookup 读写结构
|
||||
</center>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<div>
|
||||
<center>
|
||||
<img src="../waylookup_structure_update.png"
|
||||
alt="WayLookup 更新结构"
|
||||
style="zoom:40%"/>
|
||||
<br>
|
||||
WayLookup 更新结构
|
||||
</center>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
- 内部是 FIFO 环形队列结构。暂存 IPrefetchPipe 查询 MetaArray 和 ITLB 得到的元数据,以备 MainPipe 使用。同时监听 MSHR 写入 SRAM 的 cacheline,对命中信息进行更新。
|
||||
- 通过 readPtr 和 writePtr 来管理读写位置。当有 flush 信号时,读写指针都会被重置。当写入数据时,写指针递增;读取时,读指针递增。需要处理队列的空和满的情况,empty 是读指针等于写指针,而 full 则是两者的值相同且标志位不同。
|
||||
- 处理 GPF 的部分,有一个 gpf_entry 寄存器,存储 GPF 的相关信息。当写入的数据包含 GPF 异常时,需要将信息存入 gpf_entry,并记录当前的写指针位置到 gpfPtr。当读取的时候,如果当前读指针的位置与 gpfPtr 匹配,并且 gpf_entry 有效,那么就将 GPF 信息一并输出。
|
||||
- IPrefetchPipe 向其写入 WayLookupInfo 信息(包括 vSetIdx,waymask,ptag,itlb_exception,itlb_pbmt,meta_codes,gpaddr,isForVSnonLeafPTE)。
|
||||
- 写入前,需要考虑队列是否已满,以及是否有 GPF 阻塞。如果有 GPF 信息待读取且未被处理,则写入需要等待,防止覆盖 GPF 信息。写入时,如果数据中包含 GPF 异常,就将信息存入 gpf_entry,并更新 gpfPtr。
|
||||
- MainPipe 从其读出 WayLookupInfo 信息。
|
||||
- 在读取上,有两种情况:当队列为空但有写请求时,可以直接将写的数据旁路(bypass)给读端口;否则就从 entries 数组中读取对应读指针的数据。同时,如果当前读的位置存在 GPF 信息,就将 GPF 信息一起输出,并在读取后清除有效位。
|
||||
- 允许 bypass(当队列为空但有写请求时,可以直接将写的数据旁路给读端口),为了不将更新逻辑的延迟引入到 DataArray 的访问路径上,在 MSHR 有新的写入时禁止出队,MainPipe 的 S0 流水级也需要访问 DataArray,当 MSHR 有新的写入时无法向下走,所以该措施并不会带来额外影响。
|
||||
- MissUnit 向其写入命中信息。
|
||||
- 若是命中则将 waymask 更新 ICacheMissResp 信息(包括 blkPaddr,vSetIdx,waymask,data,corrupt)且 meta_codes 也更新,否则 waymask 清零。更新逻辑与 IPrefetchPipe 中相同,见 [IPrefetchPipe 子模块文档中的“命中信息的更新”](./01_iprefetchpipe.md#命中信息的更新)一节。
|
||||
|
||||
### GPaddr 省面积机制
|
||||
|
||||
由于 `gpaddr` 仅在 guest page fault 发生时有用,并且每次发生 gpf 后前端实际上工作在错误路径上,后端保证会送一个 redirect(WayLookup flush)到前端(无论是发生 gpf 前就已经预测错误/发生异常中断导致的;还是 gpf 本身导致的),因此在 WayLookup 中只需存储 reset/flush 后第一个 gpf 有效时的 gpaddr。对双行请求,只需存储第一个有 gpf 的行的 `gpaddr。`
|
||||
|
||||
在实现上,把 gpf 相关信号(目前只有 `gpaddr`)与其它信号(`paddr`,etc.)拆成两个 bundle,其它信号实例化 nWayLookupSize 个,gpf 相关只实例化一个寄存器。同时另用一个 `gpfPtr` 指针。总计可以节省$(\text{nWayLookupSize}\times2-1)\times \text{GPAddrBits} - \log_2{(\text{nWayLookupSize})} - 1$bit 的寄存器。
|
||||
当 prefetch 向 WayLookup 写入时,若有 gpf 发生,且 WayLookup 中没有已经存在的 gpf,则将 gpf/gpaddr 写入 `gpf_entry` 寄存器,同时将 `gpfPtr` 设置为此时的 `writePtr。`
|
||||
当 MainPipe 从 WayLookup 读取时,若 bypass,则仍然直接将 prefetch 入队的数据出队;否则,若 `readPtr === gpfPtr`,则读出 gpf_entry;否则读出全 0。
|
||||
需要指出:
|
||||
|
||||
1. 考虑双行请求,`gpaddr` 只需要存一份(若第一行发生 gpf,则第二行肯定也在错误路径上,不必存储),但 gpf 信号本身仍然需要存两份,因为 ifu 需要判断是否是跨行异常。
|
||||
2. `readPtr===gpfPtr` 这一条件可能导致 flush 来的比较慢时 `readPtr` 转了一圈再次与 `gpfPtr` 相等,从而错误地再次读出 gpf,但如前所述,此时工作在错误路径上,因此即使再次读出 gpf 也无所谓。
|
||||
3. 需要注意一个特殊情况:一个跨页的取指块,其 32B 在前一页且无异常,后 2B 在后一页且发生 gpf,若前 32B 正好是 16 条 RVC 压缩指令,则 IFU 会将后 2B 及对应的异常信息丢弃,此时可能导致下一个取指块的 `gpaddr` 丢失。需要在 WayLookup 中已有一个未被 MainPipe 取走的 gpf 及相关信息时阻塞 WayLookup 的入队(即 IPrefetchPipe s1 流水级),见 PR#3719。
|
||||
|
||||
## WayLookup 的功能点和测试点
|
||||
|
||||
### 刷新操作
|
||||
|
||||
- 接收到全局刷新刷新信号 io.flush 后,读、写指针和 GPF 信息都被重置。
|
||||
|
||||
1. 刷新读指针
|
||||
|
||||
- io.flush 为高时,重置读指针。
|
||||
- readPtr.value 为 0, readPtr.flag 为 false。
|
||||
|
||||
2. 刷新写指针
|
||||
|
||||
- io.flush 为高时,重置写指针。
|
||||
- writePtr.value 为 0, writePtr.flag 为 false。
|
||||
|
||||
3. 刷新 GPF 信息
|
||||
|
||||
- io.flush 为高时,重置 GPF 信息。
|
||||
- gpf_entry.valid 为 0, gpf_entry.bits 为 0。
|
||||
|
||||
### 读写指针更新
|
||||
|
||||
- 读写信号握手完毕之后(io.read.fire/io.write.fire 为高),对应指针加一。
|
||||
- 因为是在环形队列上,所以超过队列大小后,指针会回到队列头部。
|
||||
|
||||
1. 读指针更新
|
||||
|
||||
- 当 io.read.fire 为高时,读指针加一。
|
||||
- readPtr.value 加一。
|
||||
- 如果 readPtr.value 超过环形队列的大小,readPtr.flag 会翻转。
|
||||
|
||||
2. 写指针更新
|
||||
|
||||
- 当 io.write.fire 为高时,写指针加一。
|
||||
- writePtr.value 加一。
|
||||
- 如果 writePtr.value 超过环形队列的大小,writePtr.flag 会翻转。
|
||||
|
||||
### 更新操作
|
||||
|
||||
- MissUnit 处理完 Cache miss 后,向 WayLookup 写入命中信息,也就是 update 操作。
|
||||
- 情况分为两种:
|
||||
- 命中:更新 waymask 和 meta_codes。
|
||||
- 未命中:重置 waymask。
|
||||
|
||||
1. 命中更新
|
||||
|
||||
- MissUnit 返回的更新信息和 WayLookup 的信息相同时,更新 waymask 和 meta_codes。
|
||||
- vset_same 和 ptag_same 为真。
|
||||
- waymask 和 meta_codes 更新。
|
||||
- hits 对应位为高。
|
||||
|
||||
2. 未命中更新
|
||||
|
||||
- vset_same 和 way_same 为真。
|
||||
- waymask 清零。
|
||||
- hit 对应位为高。
|
||||
|
||||
3. 不更新
|
||||
|
||||
- 其他情况下不更新。
|
||||
- vset_same 为假或者 ptag_same 和 way_same 都为假。
|
||||
- hits 对应位为低。
|
||||
|
||||
### 读操作
|
||||
|
||||
- 读操作会根据读指针从环形队列中读取信息。
|
||||
- 如果达成了绕过条件,优先绕过。
|
||||
|
||||
1. Bypass 读
|
||||
|
||||
- 队列为空,并且 io.write.valid 写有效时,可以直接读取,而不经过队列。
|
||||
- empty 和 io.write.valid 都为真。
|
||||
- io.read.bits = io.write.bits
|
||||
|
||||
2. 读信号无效
|
||||
|
||||
- 队列为空(readPtr === writePtr)且写信号 io.write.valid 为低。
|
||||
- io.read.valid 为低,读信号无效。
|
||||
|
||||
3. 正常读
|
||||
|
||||
- 未达成绕过条件(empty 和 io.write.valid 至少有一个为假)且 io.read.valid 为高。
|
||||
- 从环形队列中读取信息。
|
||||
- io.read.bits.entry = entries(readPtr.value)
|
||||
|
||||
4. gpf 命中
|
||||
|
||||
- io.read.valid 为高,可以读。
|
||||
- 当 gpf_hits 为高时,从 GPF 队列中读取信息。
|
||||
- io.read.bits.gpf = gpf_entry.bits
|
||||
|
||||
5. gpf 命中且被读取
|
||||
|
||||
- io.read.valid 为高,可以读。
|
||||
|
||||
> also clear gpf_entry.valid when it's read
|
||||
|
||||
- 当 gpf 命中且被读取其时(io.read.fire 为高),gpf_entry.valid 会被置为 0。
|
||||
|
||||
6. gpf 未命中
|
||||
|
||||
- io.read.valid 为高,可以读。
|
||||
- io.read.bits.gpf 清零。
|
||||
|
||||
### 写操作
|
||||
|
||||
- 写操作会根据写指针从环形队列中读取信息。
|
||||
- 如果有 gpf 停止,就会停止写。
|
||||
|
||||
1. gpf 停止
|
||||
> if there is a valid gpf to be read, we should stall write
|
||||
|
||||
- gpf 队列数据有效,并且没有被读取或者没有命中,就会产生 gpf 停止,此时写操作会被停止。
|
||||
- gpf_entry.valid && !(io.read.fire && gpf_hit) 为高时,写操作会被停止(io.write.ready 为低)。
|
||||
|
||||
2. 写就绪无效
|
||||
|
||||
- 当队列为满((readPtr.value === writePtr.value) && (readPtr.flag ^ writePtr.flag))或者 gpf 停止时,写操作会被停止。
|
||||
- (io.write.ready 为低)
|
||||
|
||||
3. 正常写
|
||||
|
||||
- 当 io.write.valid 为高时(没满且没有 gpf 停止),写操作会被执行。
|
||||
- 正常握手完毕 io.write.fire 为高。
|
||||
- 写信息会被写入环形队列。
|
||||
- entries(writePtr.value) = io.write.bits.entry。
|
||||
|
||||
有 ITLB 异常的写
|
||||
|
||||
- 前面与正常写相同,只不过当写信息中存在 ITLB 异常时,会更新 gpf 队列和 gpf 指针。
|
||||
- 此时如果已经被绕过直接读取了,那么就不需要存储它了。
|
||||
- 4. 被绕过直接读取了
|
||||
- can_bypass 和 io.read.fire 都为高。
|
||||
- gpf_entry.valid 为 false。
|
||||
- gpf_entry.bits = io.write.bits.gpf
|
||||
- gpfPtr = writePtr
|
||||
- 5. 没有被绕过直接读取
|
||||
- can_bypass 为低。
|
||||
- gpf_entry.valid 为 true。
|
||||
- gpf_entry.bits = io.write.bits.gpf
|
||||
- gpfPtr = writePtr
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
---
|
||||
title: MissUnit
|
||||
linkTitle: MissUnit
|
||||
weight: 12
|
||||
---
|
||||
|
||||
<div class="icache-ctx">
|
||||
|
||||
</div>
|
||||
|
||||
## 子模块:FIFO
|
||||
|
||||
- 一个先入先出的循环队列,目前仅在 MissUnit 中有使用,作为优先队列 priorityFIFO。
|
||||
- 按照在 MissUnit 中的实例化,pipe 是默认值 false,hasflush 是 true。
|
||||
- 队列的指针都是环形的,分为入队指针(写指针,ent_ptr)和出队指针(读指针,deq_ptr),记录读和写的位置。
|
||||
- 两个指针都有对应的 flag 位,当指针超过队列大小时,flag 位会翻转,用以判断是否已经循环。
|
||||
- 在入队、出队对应的 fire(valid && ready) 信号有效时,移动对应的指针。
|
||||
|
||||
## FIFO 的功能点和测试点
|
||||
|
||||
### 入队操作
|
||||
|
||||
1. 队未满,正常入队
|
||||
|
||||
- 当队列未满,且空位不小于一时,可以正常入队,如果从零号位开始入队到最大容量,入队指针的 flag 不会翻转。
|
||||
- io.enq.fire 为高有效,regFiles(enq_ptr.value) = io.enq.bits,enq_ptr.value+1 入队指针移动,入队指针标记位不翻转。
|
||||
- 重复以上操作至队满。
|
||||
|
||||
2. 队未满,入队后标记位翻转
|
||||
|
||||
- 当队未满,但是空位却是靠近队尾时,入队一位后就到达了队头,入队指针的 flag 会翻转。
|
||||
- 队列的容量为 10,入队指针指向 9,队未满。此时如果 io.enq.fire 为高,则 regFiles(9) = io.enq.bits,enq_ptr.value+1(循环队列,加完后 enq_ptr.value=0)入队指针移动,入队指针标记位翻转。
|
||||
|
||||
3. 队满,入队就绪信号为低,无法入队
|
||||
|
||||
- 当队满时,(enq_ptr.value === deq_ptr.value) && (enq_ptr.flag ^ deq_ptr.flag) 为高,io.enq.ready 为低,io.enq.fire 为低无效。
|
||||
- 此时入队,入队指针的 value 和 flag 不变。
|
||||
|
||||
### 出队操作
|
||||
|
||||
1. 队非空,正常出队
|
||||
|
||||
- 当队列非空时,可以正常出队,如果出队指针不经过最大容量位置,出队指针的 flag 不会翻转。
|
||||
- io.deq.fire 为高有效,io.deq.bits = regFiles(deq_ptr.value),deq_ptr.value+1 出队指针移动,出队指针标记位不翻转。
|
||||
|
||||
2. 队非空,出队后标记位翻转
|
||||
|
||||
- 当队非空,但是出队指针是靠近队尾时,出队一位后就到达了队头,出队指针的 flag 会翻转。
|
||||
- 队列的容量为 10,出队指针指向 9,队非空。此时如果 io.deq.fire 为高,则 io.deq.bits = regFiles(9),deq_ptr.value+1(循环队列,加完后 deq_ptr.value=0)出队指针移动,出队指针标记位翻转。
|
||||
|
||||
3. 队空,出队有效信号为低,无法出队
|
||||
|
||||
- 当队空时,enq_ptr === deq_ptr 为高,io.deq.valid 为低,io.deq.fire 为低无效。
|
||||
- 此时出队,出队指针的 value 和 flag 不变。
|
||||
|
||||
### 刷新清空操作
|
||||
|
||||
1. flush 清空
|
||||
|
||||
- 当刷新信号有效时,重置出队和入队的指针和标记位,清空队列。
|
||||
- 当 flush 为高时,deq_ptr.value=0,enq_ptr.value=0,deq_ptr.flag=false,enq_ptr.flag=false,empty=true,full=false。
|
||||
|
||||
## MissUnit
|
||||
|
||||
<div>
|
||||
<center>
|
||||
<img src="../missunit_structure.png"
|
||||
alt="MissUnit 结构"
|
||||
style="zoom:100%"/>
|
||||
<br>
|
||||
MissUnit 结构
|
||||
</center>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
- 接收并管理多个 Miss 请求
|
||||
- 处理来自 Fetch 和 Prefetch 的 Miss 请求。
|
||||
- 将这些请求分派给适当数量的 MSHR 进行排队和状态管理。
|
||||
- 管理 MSHR
|
||||
- ICacheMissUnit 使用多个 MSHR 来跟踪和管理未完成的缓存未命中请求。为了防止 flush 时取指 MSHR 不能完全释放,设置取指 MSHR 的数量为 4,预取 MSHR 的数量为 10。采用数据和地址分离的设计方法,所有的 MSHR 共用一组数据寄存器,在 MSHR 只存储请求的地址信息、状态等信息。
|
||||
- 接收来自 MainPipe 的取指请求和来自 IPrfetchPipe 的预取请求,取指请求只能被分配到 fetchMSHR,预取请求只能分配到 prefetchMSHR,入队时采用低 index 优先的分配方式。
|
||||
- 在入队的同时对 MSHR 进行查询,如果请求已经在 MSHR 中存在,就丢弃该请求,对外接口仍表现 fire,只是不入队到 MSHR 中。==在入队时向 Replacer 请求写入 waymask==。当请求完成后,MSHR 会被释放,以便处理新的请求。
|
||||
- 通过 TileLink 协议与 L2 缓存进行通信,发送获取缓存块的请求(mem_acquire),并接收 L2 缓存的响应(mem_grant)。
|
||||
- 当到 L2 的总线空闲时,选择 MSHR 表现进行处理,整体 fetchMSHR 的优先级高于 prefetchMSHR,只有没有需要处理的 fetchMSHR,才会处理 prefetchMSHR。
|
||||
- 对于 fetchMSHR,采用低 index 优先的优先级策略,因为同时最多只有两个请求需要处理,并且只有当两个请求都处理完成时才能向下走,所有 fetchMSHR 之间的优先级并不重要。
|
||||
- 对于 prefetchMSHR,考虑到预取请求之间具有时间顺序,采用先到先得的优先级策略,在入队时通过一个 FIFO 记录入队顺序,处理时按照入队顺序进行处理。
|
||||
- 通过状态机与 Tilelink 的 D 通道进行交互,到 L2 的带宽为 32byte,需要分 2 次传输,并且不同的请求不会发生交织,所以只需要一组寄存器来存储数据。
|
||||
- 当一次传输完成时,根据传输的 id 选出对应的 MSHR,从 MSHR 中读取地址、掩码等信息,将相关信息写入 SRAM,同时将 MSHR 释放。
|
||||
- 向 MetaArray 和 DataArray 发送写请求,向 MainPipe 发送响应
|
||||
- 当数据传回后,MissUnit 根据相应的替换策略信息(victim way),将新数据写回 ICache 的 SRAM(Meta/Data) 。
|
||||
- 同时向取指端(或预取端)返回“Miss 已完成”的响应,包括:写入了哪一路(way)、实际数据以及可能的校验信息(如 corrupt 标记等)。
|
||||
- 处理特殊情况(如 flush、fencei、数据损坏等)
|
||||
- 遇到 Flush 或 fence.i 等指令时,MissUnit 可以终止或跳过某些 Miss 请求的写回,从而保证不在无效或过期的情况下写入缓存。
|
||||
- 数据若出现 corrupt(部分拍损坏),也会在写回或发给前端时进行特殊处理或标记。
|
||||
|
||||
过程:
|
||||
|
||||
1. fetch_req 和 prefetch_req 分别先经过 DeMultiplexer (Demux),把请求分发给对应数量的 MSHR。fetch 的 MSHR 和 prefetch 的 MSHR 分成两组,分别处理取指和预取请求。
|
||||
2. 每个 MSHR 内部会记录当前 Miss 请求的地址、索引、是否已经发出 acquire 等状态。当有其它相同的 miss 请求进来时,可以直接 “ hit MSHR ” 而不用重复创建新的请求。
|
||||
3. 对于 fetchMSHR,采用低 index 优先的优先级策略;对于 prefetchMSHR,采用先到先得的优先级策略,在入队 prefetchMSHR 前通过一个 priorityFIFO.记录入队顺序,处理时按照入队顺序进行处理。
|
||||
4. fetchMSHR 发出的请求与 prefetchArb 选出的 prefetchMSHR 通过 acquireArb 合并后,通过 mem_acquire 发送给下一级或外部存储。
|
||||
5. mem_grant 表示对这一条 Miss 请求的返回数据。需要分多个 beat 收集,直到收满一个 Cacheline。
|
||||
6. 收集完 Cacheline 数据后,会根据对应 MSHR 的信息向 metaArray 和 dataArray 发起写操作 (meta_write, data_write),同时向取指端 (fetch_resp) 发送补全后的数据和标记 (waymask 等)。
|
||||
7. 如果发生 flush 或 fencei,在未发出请求前,请求会被无效化;请求被发出后,会持续阻止新请求进入,已经发出的访问最终会将返回过程走完,但收到的响应并不会回复给 MainPipe 和 IPrefetchPipe,也不会写给 MetaArray 和 DataArray。
|
||||
|
||||
Demultiplexer 类
|
||||
grant:选择第一个 ready(能写)的 mshr,写进去(第 0 到 n 个端口,前面有 ready 的。比如 grant=seq(false,true),grant(1)为 true,表示 1 端口前面有一个 ready 的端口(0 端口))
|
||||
io.out(i).valid:前 i-1 个 mshr 没有 ready 的,输入的写有效。
|
||||
io.in.ready := grant.last || io.out.last.ready,给 MissUnit 的 ready 信号有一个有效,那么 MissUnit 给 MSHR 的 ready 信号就有效。
|
||||
|
||||
## MissUnit 的功能点和测试点
|
||||
|
||||
### 处理取指缺失请求
|
||||
|
||||
处理来自 MainPipe 的取指单元的缓存缺失请求,将缺失请求分发到多个 Fetch MSHR 中的一个,避免重复请求。
|
||||
低索引的请求优先处理。
|
||||
|
||||
1. 接受新的取指请求
|
||||
|
||||
- 当新的 fetch miss 与 MSHR 中的已有请求不重复时(通过 io.fetch_req.bits.blkPaddr / vSetIdx 给出具体地址),MissUnit 会将请求分配到一个空闲的 Fetch MSHR 中。
|
||||
- 当有新的取指缺失请求到达时(io.fetch_req.valid 为高),且没有命中已有的 MSHR(fetchHit 为低),io.fetch_req.ready 应为高,表示可以接受请求。
|
||||
- io.fetch_req.fire 成功握手后,该 MSHR 处于 valid = true 状态,并记录地址。
|
||||
|
||||
2. 处理已有的取指请求
|
||||
|
||||
- 当已有取指缺失请求到达时(io.fetch_req.valid 为高),且命中已有的 MSHR(fetchHit 为高),io.fetch_req.ready 应为高,虽然不接受请求,但是表现出来为已经接收请求。
|
||||
- fetchDemux.io.in.valid 应为低,fetchDemux.io.in.fire 为低,表示没有新的请求被分发到 MSHR。
|
||||
|
||||
3. 低索引的请求优先进入 MSHR
|
||||
- Fetch 的请求会通过 fetchDemux 分配到多个 Fetch MSHR,fetchDemux 的实现中,低索引的 MSHR 会优先被分配请求。
|
||||
- 当取指请求有多个 io.out(i).read 时,选择其中的第一个,也就是低索引的写入 MSHR,io.chose 为对应的索引。
|
||||
|
||||
### 处理预取缺失请求
|
||||
|
||||
与 Fetch Miss 类似,但走另一些 MSHR(Prefetch MSHR)。
|
||||
|
||||
1. 接受新的预取请求
|
||||
|
||||
- 当新的 prefetch miss 与 MSHR 中的已有请求不重复时(通过 io.prefetch_req.bits.blkPaddr / vSetIdx 给出具体地址),MissUnit 会将请求分配到一个空闲的 Prefetch MSHR 中。
|
||||
- 当有新的预取缺失请求到达时(io.prefetch_req.valid 为高),且没有命中已有的 MSHR(prefetchHit 为低),io.prefetch_req.ready 应为高,表示可以接受请求。
|
||||
- io.prefetch_req.fire 成功握手后,该 MSHR 处于 valid = true 状态,并记录地址。
|
||||
|
||||
2. 处理已有的预取请求
|
||||
|
||||
- 当已有预取缺失请求到达时(io.prefetch_req.valid 为高),且命中已有的 MSHR(prefetchHit 为高),io.prefetch_req.ready 应为高,虽然不接受请求,但是表现出来为已经接收请求。
|
||||
- prefetchDemux.io.in.valid 应为低,prefetchDemux.io.in.fire 为低,表示请求被接受但未分发到新的 MSHR。
|
||||
|
||||
3. 低索引的请求优先进入 MSHR
|
||||
|
||||
- Prefetch 的请求会通过 prefetchDemux 分配到多个 Prefetch MSHR,prefetchDemux 的实现中,低索引的 MSHR 会优先被分配请求。
|
||||
- 当取指请求有多个 io.out(i).read 时,选择其中的第一个,也就是低索引的写入 MSHR,io.chose 为对应的索引。
|
||||
|
||||
4. 先进入 MSHR 的优先进入 prefetchArb
|
||||
- 从 prefetchDemux 离开后,请求的编号会进入 priorityFIFO,priorityFIFO 会根据进入队列的顺序排序,先进入队列的请求会先进入 prefetchArb。
|
||||
- prefetchDemux.io.in.fire 为高,并且 prefetchDemux.io.chosen 有数据时,将其编号写入 priorityFIFO。
|
||||
- 在 priorityFIFO 中有多个编号时,出队的顺序和入队顺序一致。
|
||||
- 检查 priorityFIFO.io.deq.bit 中的数据即可。
|
||||
|
||||
### MSHR 管理与查找
|
||||
|
||||
1. MSHR 查找命中逻辑
|
||||
|
||||
- 当新的请求到来时,能够正确查找所有 MSHR,判断请求是否命中已有 MSHR。
|
||||
- 当新的请求(取指或预取)到来时,系统遍历所有 MSHR,根据所有 MSHR 的查找信号 allMSHRs(i).io.lookUps(j).hit,检查请求是否已经存在于某个 MSHR 中。
|
||||
- 如果命中,则对应的 fetchHit 或 prefetchHit 为高。
|
||||
- 对于 prefetchHit 为高,还有一种情况:预取的物理块地址和组索引与取指的相等((io.prefetch_req.bits.blkPaddr === io.fetch_req.bits.blkPaddr) && (io.prefetch_req.bits.vSetIdx === io.fetch_req.bits.vSetIdx))并且有取指请求 io.fetch_req.valid 有效时,也算命中
|
||||
|
||||
2. MSHR 状态的更新与释放
|
||||
|
||||
- 当请求完成后,也就是来自内存总线的响应完成(D 通道接收完所有节拍),MSHR 能够正确地释放(清除其有效位),以便接收新的请求。
|
||||
- TileLink D 通道返回的 source ID ,即 io.mem_grant.bits.source。
|
||||
- 无效化信号 allMSHRs(i).io.invalid 为高,对应的 MSHR 的有效位 allMSHRs(i).valid 变为低
|
||||
|
||||
### acquireArb 仲裁
|
||||
|
||||
预取和取指的 acquire 都会发送给 acquireArb,acquireArb 会选择一个 acquire 发送给 mem_acquire。
|
||||
acquireArb 使用 chisel 自带的 Arbiter 实现,Arbiter 使用固定优先级仲裁,优先级从编号 0 开始,编号越小优先级越高。
|
||||
|
||||
1. acquireArb 仲裁
|
||||
- acquireArb 会选择一个 acquire 发送给 mem_acquire。
|
||||
- 当有多个 MSHR 同时发出请求时,acquireArb 会根据优先级进行仲裁,选择优先级最高的 MSHR 发送请求。
|
||||
- 取指请求总是在 0-3 号,预取请求直接在最后一号,所以取指请求优先级高于预取请求。
|
||||
- 当取指 acquire 和预取 acquire 同时发出时,fetchMSHRs(i).io.acquire 和 prefetchMSHRs(i).io.acquire 都有效,仲裁结果 acquireArb.io.out 应该和 fetchMSHRs(i).io.acquire 一致。
|
||||
|
||||
### Grant 数据接收与 Refill
|
||||
|
||||
在收到 TileLink D 通道数据时收集整行
|
||||
|
||||
- 累计 beat 数(readBeatCnt),直到完成一整行 (last_fire)
|
||||
- 记录 corrupt 标志
|
||||
- 将完成的请求映射回对应的 MSHR (id_r = mem_grant.bits.source)
|
||||
|
||||
1. 正常完整 Grant 流程,readBeatCnt 为 0 时
|
||||
- readBeatCnt 初始为 0,refillCycles - 1 也为 0。
|
||||
- io.mem_grant.valid 为高(因为 io.mem_grant.ready 默认为高,所以 io.mem_grant.fire 为高只需要 io.mem_grant.valid 为高)且 io.mem_grant.bits.opcpde(0)为高。
|
||||
- 此时 respDataReg(0)= io.mem_grant.bits.data
|
||||
- readBeatCnt 加一为 1。
|
||||
2. 正常完整 Grant 流程,readBeatCnt 为 1 时
|
||||
- io.mem_grant.valid 为高且 io.mem_grant.bits.opcpde(0)为高。
|
||||
- 此时 respDataReg(1)= io.mem_grant.bits.data
|
||||
- readBeatCnt 重置回 0。
|
||||
- last_fire 为高。
|
||||
- 下一拍 last_fire_r 为高,id_r=io.mem_grant.bits.source。
|
||||
3. 正常完整 Grant 流程,last_fire_r 为高
|
||||
- last_fire_r 为高,并且 id_r 为 0-13 中的一个。
|
||||
- 对应的 fetchMSHRs 或者 prefetchMSHRs 会被无效,也就是 fetchMSHRs_i 或 prefetchMSHRs_i-4 的 io_invalid 会被置高。
|
||||
4. Grant 带有 corrupt 标志
|
||||
- io.mem_grant.valid 为高且 io.mem_grant.bits.opcpde(0)为高,io.mem_grant.bits.corrupt 为高,则 corrupt_r 应为高。
|
||||
- 如果 io.mem_grant.valid 为高且 io.mem_grant.bits.opcpde(0)为高,io.mem_grant.bits.corrupt 为高中有一个不满足,且此时 last_fire_r 为高,则 corrupt_r 重置为低。
|
||||
|
||||
### 替换策略更新 (Replacer)
|
||||
|
||||
MissUnit 在发出 Acquire 请求时,还会将本次选中的 victim way 对应的索引告诉 io.victim,让替换策略更新其记录(替换策略采用 PLRU)
|
||||
只有当 Acquire 真正“fire”时,才说明成功替换,replacer 需要更新状态
|
||||
|
||||
1. 正常替换更新
|
||||
- 当 io.mem.acquire.ready & acquireArb.io.out.valid 同时为高,也就是 acquireArb.io.out.fir 为高时,io.victim.vSetIdx.valid 也为高。
|
||||
- io.victim.vSetIdx.bits = 当前 MSHR 请求的 acquireArb.io.out.bits.vSetIdx。
|
||||
2. 生成 waymask
|
||||
- 根据从 L2 返回的 mshr_resp 中 mshr_resp.bits.way 生成 waymask 信息。
|
||||
- 返回的 mshr_resp.bits.way 有 16 位,通过独热码生成一位掩码信息,waymask 表示其中哪一路被替换。
|
||||
- 生成的 waymask 应该和 mshr_resp.bits.way 一致。
|
||||
|
||||
### 写回 SRAM (Meta / Data)
|
||||
|
||||
在一条 Miss Request refill 完成时,将新得到的 Cache line 写到 ICache。
|
||||
生成 io.meta_write 和 io.data_write 的请求,带上 waymask, tag, idx, data 。
|
||||
生成 io.meta_write.valid 和 io.data_write.valid 信号。
|
||||
|
||||
1. 生成 io.meta_write.valid 和 io.data_write.valid 信号
|
||||
- 当 grant 传输完成后,经过一拍后,即 last_fire_r 为高,且从 TileLink 返回的 mshr_resp 中的 mshr_resp.valid 为高。
|
||||
- 并且此时没有硬件刷新信号和软件刷新信号,也就是 io.flush 和 io.fencei 为低。 在等待 l2 响应的过程中,没有刷新信号
|
||||
- 也没有数据 corrupt,即 corrupt_r 为低。
|
||||
- 那么 io.meta_write.valid 和 io.data_write.valid 均为高。
|
||||
2. 正常写 SRAM
|
||||
- io.meta_write.bits 的 virIdx、phyTag、waymask、bankIdx、poison 应该正常更新
|
||||
- io.data_write.bits 的 virIdx、data、waymask、bankIdx、poison 应该正常更新
|
||||
|
||||
### 向 mainPipe/prefetchPipe 发出 Miss 完成响应(fetch_resp)
|
||||
|
||||
在完成 refill 后,无论是否要真正写阵列,都会向取指端发送“Miss 请求完成”
|
||||
更新 io.fetch_resp.valid 和 fetch_resp.bits。
|
||||
|
||||
1. 正常 Miss 完成响应
|
||||
- 当 grant 传输完成后,经过一拍后,即 last_fire_r 为高,且从 TileLink 返回的 mshr_resp 中的 mshr_resp.valid 为高。
|
||||
- 无论此时是否有硬件刷新信号和软件刷新信号, io.fetch_resp.valid 都为高,说明可向取指端发送响应。
|
||||
- io.fetch_resp.bits 中的数据更新:
|
||||
- io.fetch_resp.bits.blkPaddr = mshr_resp.bits.blkPaddr
|
||||
- io.fetch_resp.bits.vSetIdx = mshr_resp.bits.vSetIdx
|
||||
- io.fetch_resp.bits.waymask = waymask
|
||||
- io.fetch_resp.bits.data = respDataReg.asUInt
|
||||
- io.fetch_resp.bits.corrupt = corrupt_r
|
||||
|
||||
### 处理 flush / fencei
|
||||
|
||||
一旦收到 io.flush 或 io.fencei 时,对未发射的请求可立即取消,对已经发射的请求在拿到数据后也不写 SRAM。
|
||||
|
||||
1. MSHR 未发射前 fencei
|
||||
- 如果 MSHR 还没有通过 io.acquire.fire 发出请求,就应立即取消该 MSHR(mshr_resp.valid= false),既不发出请求,也不要写 SRAM。
|
||||
- 当 io.fencei 为高时,fetchMSHRs 和 prefetchMSHRs 的 io.req.ready 和 io.acquire.valid 均为低,表示请求不发射。
|
||||
2. MSHR 未发射前 flush
|
||||
- 由于 fetchMSHRs 的 io.flush 被直接设置为 false,所以 io.flush 对 fetchMSHRs 无效,但是对 prefetchMSHRs 有效。
|
||||
- 当 io.flush 为高时,只能发射 fetchMSHRs 的请求。
|
||||
3. MSHR 已发射后 flush/fencei
|
||||
- 已经发射了请求,之后再有刷新信号,那么等数据回来了但不写 SRAM。
|
||||
- 在发射后,io.flush/io.fencei 为高时,等待数据回来,但是写 SRAM 的信号,write_sram_valid、io.meta_write.valid 和 io.data_write.valid 均为低,表示不写 SRAM。
|
||||
- 对于 response fetch 无影响。
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
---
|
||||
title: CtrlUnit
|
||||
linkTitle: CtrlUnit
|
||||
weight: 12
|
||||
---
|
||||
|
||||
<div class="icache-ctx">
|
||||
|
||||
</div>
|
||||
|
||||
## CtrlUnit
|
||||
|
||||
目前 CtrlUnit 主要负责 ECC 校验使能/错误注入等功能。
|
||||
RegField 案例类和伴生对象的作用,RegReadFn 和 RegWriteFn 案例类和伴生对象的作用。
|
||||
|
||||
通过两个控制寄存器 CSR:eccctrl 和 ecciaddr,来实现错误注入。
|
||||
在 eccctrlBundle 中,定义 eccctrl 的 ierror、istatus、itarget、inject、enable 域的初始值。
|
||||
在 ecciaddrBundle 中,定义 ecciaddr 的 paddr 域的初始值。
|
||||
|
||||
### mmio-mapped CSR
|
||||
|
||||
CtrlUnit 内实现了一组 mmio-mapped CSR,连接在 tilelink 总线上,地址可由参数 `cacheCtrlAddressOpt` 配置,默认地址为`0x38022080`。总大小为 128B。
|
||||
|
||||
当参数 `cacheCtrlAddressOpt` 为 `None` 时,CtrlUnit **不会实例化**。此时 ECC 校验使能**默认开启**,软件不可控制关闭;软件不可控制错误注入。
|
||||
|
||||
目前实现的 CSR 如下:
|
||||
|
||||
```plain
|
||||
64 10 7 4 2 1 0
|
||||
0x00 eccctrl | WARL | ierror | istatus | itarget | inject | enable |
|
||||
|
||||
64 PAddrBits-1 0
|
||||
0x08 ecciaddr | WARL | paddr |
|
||||
```
|
||||
|
||||
| CSR | field | desp |
|
||||
| ------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| eccctrl | enable | ECC 错误校验使能,原 sfetchctl(0) 。 <br>注:即使不使能,在 icache 重填时仍会计算 parity,可能会有额外功耗;但如果不计算,则在未使能转换成使能时需要冲刷 icache(否则读出的 parity 有 50%概率是错的)。 |
|
||||
| eccctrl | inject | ECC 错误注入使能,写 1 即使能,读恒 0 |
|
||||
| eccctrl | itarget | ECC 错误注入目标 <br>0: metaArray<br>1: rsvd<br>2: dataArray<br>3: rsvd |
|
||||
| eccctrl | istatus | ECC 错误注入状态(read-only)<br>0: idle:注入控制器闲置<br>1: working:收到注入请求,注入中<br>2: injected:注入完成,等待触发<br>3: rsvd<br>4: rsvd<br>5: rsvd<br>6: rsvd<br>7: error:注入出错 |
|
||||
| eccctrl | ierror | ECC 错误原因(read-only)<br>0: ECC 未使能 (i.e. !eccctrl.enable) <br>1: inject 目标 SRAM 无效 (i.e. eccctrl.itarget==rsvd) <br>2: inject 目标地址 (i.e. ecciaddr.paddr) 不在 ICache 中<br>3: rsvd <br>4: rsvd <br>5: rsvd <br>6: rsvd <br>7: rsvd |
|
||||
| ecciaddr | paddr | ECC 错误注入物理地址 |
|
||||
| RERI standard | | RERI 手册还要求了错误计数等寄存器,用于软件获取 RAS controller 状态,参考手册,可能需要与 dcache、L2cache 统一在后端实现,icache 像现在给 BEU 送 error 一样送给后端。<br>即:暂时不需要在 icache 实现,但要把错误计数等机制所需的接口准备出来 |
|
||||
|
||||
### 错误校验使能
|
||||
|
||||
CtrlUnit 的 `eccctrl.enable` 位直接连接到 MainPipe,控制 ECC 校验使能。当该位为 0 时,ICache 不会进行 ECC 校验。但仍会在重填时计算校验码并存储,这可能会有少量的额外功耗;如果不计算,则在未使能转换成使能时需要冲刷 ICache(否则读出的 parity code 可能是错的)。
|
||||
|
||||
### 错误注入使能
|
||||
|
||||
CtrlUnit 内部使用一个状态机控制错误注入过程,其 status (注意:与 `eccctrl.istatus` 不同)有:
|
||||
|
||||
- idle:注入控制器闲置
|
||||
- readMetaReq:发送读取 metaArray 请求
|
||||
- readMetaResp:接收读取 metaArray 响应
|
||||
- writeMeta:写入 metaArray
|
||||
- writeData:写入 dataArray
|
||||
|
||||
当软件向 `eccctrl.inject` 写入 1 时,进行以下简单检查,检查通过时状态机进入 `readMetaReq` 状态:
|
||||
|
||||
- 若 `eccctrl.enable` 为 0,报错 `eccctrl.ierror=0`
|
||||
- 若 `eccctrl.itarget` 为 rsvd(1/3),报错 `eccctrl.ierror=1`
|
||||
|
||||
在 `readMetaReq` 状态下,CtrlUnit 向 MetaArray 发送 `ecciaddr.paddr` 地址对应的 set 读取的请求,等待握手。握手后转移到 `readMetaResp` 状态。
|
||||
|
||||
在 `readMetaResp` 状态下,CtrlUnit 接收到 MetaArray 的响应,检查 `ecciaddr.paddr` 地址对应的 ptag 是否命中,若未命中则报错 `eccctrl.ierror=2`。否则,根据 `eccctrl.itarget` 进入 `writeMeta` 或 `writeData` 状态。
|
||||
|
||||
在 `writeMeta` 或 `writeData` 状态下,CtrlUnit 向 MetaArray/DataArray 写入任意数据,同时拉高 `poison` 位,写入完成后状态机进入 `idle` 状态。
|
||||
|
||||
ICache 顶层中实现了一个 Mux,当 CtrlUnit 的状态机不为 `idle` 时,将 MetaArray/DataArray 的读写口连接到 CtrlUnit,而非 MainPipe/IPrefetchPipe/MissUnit。当状态机 `idle` 时反之。
|
||||
|
||||
状态机和错误注入流程
|
||||
|
||||
- `is_idle`:模块处于空闲状态,等待错误注入的触发。只有当 `eccctrl.istatus` 为 `working` 时,状态机才会转移到 `is_readMetaReq` 状态,准备读取元数据。
|
||||
- `is_readMetaReq`:发送读取元数据请求。通过接口 `metaRead` 向缓存发送读取请求。当握手成功后状态会转移到 `is_readMetaResp`。
|
||||
- `is_readMetaResp`:接收元数据响应并验证。如果未命中,则会设置错误状态。没命中会转移状态到`is_idle`,并设置错误错误注入状态和错误原因;如果找到有效的缓存行并且标签匹配,根据错误注入目标来判断是向元数据还是数据阵列写入错误。
|
||||
- `is_writeMeta`:写入带 poison 标记的数据完成注入。当握手成功后,错误注入状态设置为 injected(注入完成,等待触发),状态转移到`is_idle`。
|
||||
- `is_writeData`:写入带 poison 标记的数据完成注入。当握手成功后,向数据阵列写入错误数据,错误注入状态设置为 injected(注入完成,等待触发),状态转移到`is_idle`。
|
||||
|
||||
寄存器和接口映射
|
||||
|
||||
- eccctrl:控制 ECC 启用、错误注入状态等。寄存器通过 eccctrlRegField 进行映射。
|
||||
- ecciaddr:指定错误注入的物理地址。通过 ecciaddrRegField 映射。
|
||||
- 通过寄存器描述符(RegFieldDesc)和寄存器字段(RegField),定义了寄存器的这些是寄存器的描述信息和读写逻辑。
|
||||
- 通过 node.regmap,这两个寄存器被映射到指定的地址偏移。eccctrl 寄存器被映射到 params.eccctrlOffset 地址,ecciaddr 寄存器被映射到 params.ecciaddrOffset 地址。
|
||||
- `node.regmap` 使得这两个寄存器可以通过外部的 TileLink 接口进行访问,外部模块可以读写这些寄存器以控制 ECC 和错误注入功能。
|
||||
|
||||
## CtrlUnit 的功能点和测试点
|
||||
|
||||
### ECC 启用/禁用
|
||||
|
||||
控制 eccctrl.enable 字段来启用或禁用 ECC 功能。外部系统可以通过写寄存器 eccctrl 来控制 ECC 是否启用。
|
||||
|
||||
- 通过寄存器写入控制信号 enable,当 enable 为 true 时,ECC 功能启用;为 false 时,ECC 功能禁用。
|
||||
|
||||
1. 启用 ECC
|
||||
|
||||
- 向 eccctrl.enable 寄存器写入 true,验证模块内部 eccctrl.enable 设置为 true,并确保后续的错误注入操作能够成功进行。此测试确保 eccctrl.enable 写操作被执行。
|
||||
- 确保 eccctrl.enable 被正确设置为 true,并触发 eccctrlRegWriteFn 中的写操作逻辑。
|
||||
|
||||
2. 禁用 ECC
|
||||
|
||||
- 向 eccctrl.enable 寄存器写入 false,验证模块内部 eccctrl.enable 设置为 false,并确保在后续的错误注入过程中,ECC 功能被禁用,不允许进行错误注入。此测试确保 eccctrl.enable 写操作被正确设置为 false。
|
||||
- 验证禁用 ECC 时 eccctrl.enable 为 false,并触发 eccctrlRegWriteFn 中的错误处理分支。x.istatus = eccctrlInjStatus.error 和 x.ierror = eccctrlInjError.notEnabled
|
||||
|
||||
### 状态机转换
|
||||
|
||||
根据状态机的状态,验证错误注入的流程是否正确。
|
||||
|
||||
1. is_idle 状态
|
||||
|
||||
- 初始为 is_idle 状态。
|
||||
- 当 eccctrl.istatus 为 working 时,验证此时的状态为 is_readMetaReq。
|
||||
|
||||
2. is_readMetaReq 状态
|
||||
|
||||
- 当握手成功后(io.metaRead.ready 和 io.metaRead.valid 都为高),验证此时的状态为 is_readMetaResp。
|
||||
|
||||
3. is_readMetaResp 状态
|
||||
|
||||
- 未命中
|
||||
- 当 waymask 全零的时候,表示没有命中,会进入 is_idle 状态,并且设置错误错误注入状态和错误原因。
|
||||
- 验证此时的状态为 is_idle, eccctrl.istatus = error 和 eccctrl.ierror = notFound。
|
||||
- 命中
|
||||
- 当 waymask 不全零的时候,表示命中,会根据错误注入目标来判断是向元数据还是数据阵列写入错误。
|
||||
- 当 eccctrl.itarget=metaArray 时,验证此时的状态为 is_writeMeta ;当 eccctrl.itarget!=metaArray 时,验证此时的状态为 is_writeData。
|
||||
|
||||
4. is_writeMeta 状态
|
||||
|
||||
- RegWriteFn
|
||||
- 此状态进入后,io.dataWrite.valid 会为高
|
||||
- x.itarget = req.itarget
|
||||
- 当 req.inject 为高并且 x.istatus = idle 时:
|
||||
- 如果 ecc 的 req.enable = false,则验证 x.istatus = error 且 x.ierror = notEnabled
|
||||
- 否则,如果 req.itarget != metaArray 和 dataArray,则验证 x.istatus = error 且 x.ierror = targetInvalid
|
||||
- 如果都不满足,则验证 x.istatus = working
|
||||
- 状态转换
|
||||
- 当 io.metaWrite.fire 为高, 验证下一个状态为 is_idle,并且 eccctrl.istatus = injected。
|
||||
|
||||
5. is_writeData 状态
|
||||
|
||||
- RegWriteFn
|
||||
- 此状态进入后,io.dataWrite.valid 会为高
|
||||
- res.inject = false
|
||||
- 当 ready 为高,且 x.istatus = injected 或 x.istatus = error 时,验证 x.istatus = idle 和 x.ierror = notEnabled
|
||||
- 状态转换
|
||||
- 当 io.dataWrite.fire 为高, 验证下一个状态为 is_idle,并且 eccctrl.istatus = injected。
|
||||
|
||||
### 寄存器映射和外部访问
|
||||
|
||||
通过 TileLink 总线将寄存器映射到特定地址,使外部模块可以读写 ECC 控制寄存器和注入地址寄存器。
|
||||
|
||||
- 使用 TLRegisterNode 实现寄存器的映射,使得外部系统可以通过地址访问寄存器。寄存器的读写操作通过 TileLink 协议进行。
|
||||
|
||||
1. 外部读取和写入 ECC 控制寄存器
|
||||
|
||||
- 验证外部模块可以通过 TileLink 协议正确读取和写入 eccctrl 和 ecciaddr 寄存器,并对模块内部的状态产生影响,确保读写操作完全覆盖。
|
||||
|
||||
2. 外部模块触发错误注入
|
||||
|
||||
- 通过外部模块经 TileLink 总线向 eccctrl.inject 寄存器写入 true,触发错误注入,验证内部状态是否按 RegWriteFn 内部过程执行。
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
---
|
||||
title: ICache
|
||||
linkTitle: ICache
|
||||
weight: 12
|
||||
---
|
||||
|
||||
<div class="icache-ctx">
|
||||
|
||||
</div>
|
||||
|
||||
## ICache
|
||||
|
||||
各种组合数据的宽度以 system verilog/verilog 中的为准。
|
||||
|
||||
|
||||
- IPrefetchPipe 接收来自 FTQ 的预取请求,然后向 MetaArray 和 ITLB 发送请求,再从 ITLB 的响应得到 paddr,之后与 MetaArray 返回的 tag 进行比较得到命中信息,把命中信息、MetaArray ECC 校验信息和 ITLB 信息一并写入 WayLookup,同时进行 PMP 检查。未命中就将信息发送给 MissUnit 处理,MissUnit 通过 TileLink 总线向 L2Cache 发起请求,获取数据后返回给 MetaArray 和 IPrefetchPipe。之后会判断是否 Miss,如果 Miss 则把预取请求发送到 MissUnit,它会通过 TileLink 向 L2 做预取指。
|
||||
- MainPipe 接收来自 FTQ 的取指请求,然后从 WayLookup 获取路命中信息和 ITLB 查询结果,再访问 DataArray。命中后向 replacer 发送 touch 请求,replacer 采用 PLRU 替换策略,接收到 MainPipe 的命中更新,向 MissUnit 提供写入的 waymask。同时进行 PMP 检查,接收 DataArry 返回的数据。对 DataArray 做 ECC 校验,根据 DataArry 和 MetaArry 的校验结果(MetaArray 的校验结果来自 Waylookup)判断是否将错误报告给总线(beu)。之后,如果 DataArry 没有命中,将信息发往 MissUnit 处理。MissUnit 通过 TileLink 总线向 L2Cache 发起请求,获取数据后返回给 DataArray 和 MainPipe。之后就可以将数据返回给 IFU。
|
||||
- MetaArray 存储缓存行的标签(Tag)和 ECC 校验码
|
||||
- 使用双 Bank SRAM 结构,支持双线访问(Double-Line),每个 Bank 存储部分元数据。
|
||||
- 标签包含物理地址的高位,用于地址匹配。
|
||||
- 支持标签 ECC 校验,检测和纠正存储错误。
|
||||
- valid_array 记录每个 Way 的有效状态,Flush 操作会清零
|
||||
- DataArray 存储实际的指令数据块。
|
||||
- 数据按 Bank 划分为八个,每个 Bank 宽度为 64 位,支持多 Bank 并行访问。
|
||||
- 数据 ECC 校验,分段生成校验码,增强错误检测能力。
|
||||
- 支持双线访问,根据地址偏移选择 Bank,单周期可读取 32 字节数据。
|
||||
- 冲刷信号有三种:ftqPrefetch.flushFromBpu,itlbFlushPipe,模块外部的 fencei 和 flush 信号。
|
||||
- ftqPrefetch.flushFromBpu:通过 FTQ 来自的 BPU 刷新信号,用于控制预取请求的冲刷。
|
||||
- itlbFlushPipe:ITLB 的冲刷信号,itlb 在收到该信号时会冲刷 gpf 缓存。
|
||||
- fencei:刷新 MetaArray,清除所有路的 valid_array 清零;missUnit 中所有 MSHR 置无效。
|
||||
- flush:mainPipe 和 prefetchPipe 所有流水级直接置无效,wayLookup 读写指针复位,gpf_entry 直接置无效,missUnit 中所有 MSHR 置无效。
|
||||
|
||||
### Replacer
|
||||
|
||||
采用 PLRU 更新算法,考虑到每次取指可能访问连续的 doubleline,对于奇地址和偶地址设置两个 replacer,在进行 touch 和 victim 时根据地址的奇偶分别更新 replacer。
|
||||
|
||||
<div>
|
||||
<center>
|
||||
<img src="../plru.png"
|
||||
alt="PLRU 算法示意"
|
||||
style="zoom:100%"/>
|
||||
<br>
|
||||
PLRU 算法示意
|
||||
</center>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
#### touch
|
||||
|
||||
Replacer 具有两个 touch 端口,用以支持双行,根据 touch 的地址奇偶分配到对应的 replacer 进行更新。
|
||||
|
||||
#### victim
|
||||
|
||||
Replacer 只有一个 victim 端口,因为同时只有一个 MSHR 会写入 SRAM,同样根据地址的奇偶从对应的 replacer 获取 waymask。并且在下一拍再进行 touch 操作更新 replacer。
|
||||
|
||||
## ICache 的功能点和测试点
|
||||
|
||||
### FTQ 预取请求处理
|
||||
|
||||
接收来自 FTQ 的预取请求,经 IPrefetchPipe 请求过滤(查询 ITLB 地址,是否命中 MetaArry,PMP 检查),(有异常则由 MissUnit 处理)后进入 WayLookup。
|
||||
|
||||
1. 预取地址命中,无异常
|
||||
|
||||
- io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 命中无异常,itlb 查询到的地址与 MetaArry 的 ptag 匹配,pmp 检查通过。
|
||||
- 如果没有监听到 MSHR 同样的位置发生了其它 cacheline 的写入,那么验证 wayLookup.io.write 的内容应该命中的取指数据。
|
||||
- 如果监听到 MSHR 同样的位置发生了其它 cacheline 的写入,那么验证 wayLookup.io.write 的内容应该是未命中的取指数据。
|
||||
|
||||
2. 预取地址未命中,无异常
|
||||
|
||||
- io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 命中无异常,itlb 查询到的地址与 MetaArry 的 ptag 不匹配,pmp 检查通过。
|
||||
- 如果监听到 MSHR 将该请求对应的 cacheline 写入了 SRAM,那么验证 wayLookup.io.write 的内容应该命中的取指数据。
|
||||
- 如果监听到 MSHR 没有将该请求对应的 cacheline 写入了 SRAM,那么验证 wayLookup.io.write 的内容应该未命中的取指数据。
|
||||
|
||||
3. 预取地址 TLB 异常,无其他异常
|
||||
|
||||
- io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 异常。
|
||||
- 验证 wayLookup.io.write 的 itlb_exception 内容中,其有对应的异常类型编号(pf:01;gpf:10;af:11)。
|
||||
|
||||
4. 预取地址 PMP 异常,无其他异常
|
||||
|
||||
- io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 命中无异常,itlb 查询到的地址与 MetaArry 的 ptag 匹配,pmp 检查未通过。
|
||||
- 验证 wayLookup.io.write 的 tlb_pbmt 内容中,其有对应的异常类型编号(nc:01;io:10)。
|
||||
|
||||
### FTQ 取指请求处理
|
||||
|
||||
io.fetch.resp <> mainPipe.io.fetch.resp 发送回 IFU 的数据是在 io.fetch.resp。
|
||||
|
||||
接收来自 FTQ 的取指请求,从 WayLookup 获取路命中信息和 ITLB 查询结果,再访问 DataArray,监控 MSHR 的响应。更新 replacer,做 pmp 检查。后做 DataArray 和 MetaArray 的 ECC 校验。最后将数据发送给 IFU。
|
||||
|
||||
1. 取指请求命中,无异常
|
||||
|
||||
- io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查正常,DataArray 和 MetaArray 的 ECC 校验正常。
|
||||
- 验证 replacer.io.touch 的 vSetIdx 和 way 和 ftq 的 fetch 一致,missUnit.io.victim 的 vSetIdx 和 way 是按照制定的算法生成的。
|
||||
- 验证 io.fetch.resp 的数据应该是取指的数据。
|
||||
|
||||
2. 取指请求未命中,MSHR 返回的响应命中,无异常
|
||||
|
||||
- io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,未命中,pmp 检查正常,DataArray 和 MetaArray 的 ECC 校验正常。
|
||||
- 请求在 MSHR 返回的响应命中。
|
||||
- 验证 missUnit.io.victim 的 vSetIdx 和 way 是按照制定的算法生成的。
|
||||
- 验证 io.fetch.resp 的数据应该是取指的数据。
|
||||
|
||||
3. 取指请求命中,ECC 校验错误,无其他异常
|
||||
|
||||
- io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查正常,DataArray 或 MetaArray 的 ECC 校验错误。
|
||||
- 验证 io.error.valid 为高,且 io.error.bits 内容为对应的错误源和错误类型。
|
||||
- 先刷 MetaArray 的 ValidArray,给 MissUnit 发请求,由其在 L2 重填,阻塞至数据返回。
|
||||
- 验证 replacer.io.touch 的 vSetIdx 和 way 和 ftq 的 fetch 一致,missUnit.io.victim 的 vSetIdx 和 way 是按照制定的算法生成的。
|
||||
- 验证 io.fetch.resp 的数据应该是取指的数据。
|
||||
|
||||
4. 取指请求未命中,但是 exception 非 0(af、gpf、pf),无其他异常
|
||||
|
||||
- io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查未通过,DataArray 和 MetaArray 的 ECC 校验正常。
|
||||
- 验证 io.fetch.resp 为对应的错误源和错误类型。
|
||||
- 验证 io.fetch.resp 的数据无效,里面有异常类型。
|
||||
|
||||
5. 取指请求未命中,通过 WayLookup 中读取到的预取过来的 itlb 中返回 pbmt。
|
||||
|
||||
- 有 itlb_pbmt 和 pmp_mmio 时,他们合成 s1_mmio,传递到 s2_mmio,生成 s2_miss,有特殊情况就不会取指。
|
||||
- io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查通过,DataArray 和 MetaArray 的 ECC 校验正常。
|
||||
- 验证 io.fetch.resp 为对应的错误源和错误类型。
|
||||
- 验证 io.fetch.resp 的数据无效,里面有特殊情况类型类型。
|
||||
|
||||
6. 取指请求未命中,pmp 返回 mmio ,处理同 5。
|
||||
|
||||
### MetaArray 功能
|
||||
|
||||
在 IPrefetchPipe 的 S0,接收来自 IPrefetchPipe 的读请求 read,返回对应路和组的响应 readResp。
|
||||
在 miss 的时候,MissUnit 会将会应的数据写入 write 到 MetaArray。
|
||||
MetaArray 主要存储了每个 Cache 行的标签和 ECC 校验码。
|
||||
|
||||
1. 元数据写入操作(对应的 Set 已满): ICacheMetaArray 应当能够正确地将元数据(标签和有效位)写入到指定的 Set 和 Way 。
|
||||
|
||||
- 从 MissUnit 返回的请求都是未命中的请求(已命中不会向 MissUnit 请求,那么 MissUnit 自然也不会向 MetaArray 写入)。
|
||||
- 发送一个写请求 write 到 ICacheMetaArray,ICacheReplacer 根据 PLRU 替换策略指定 way,替换路被写入 waymask,最后指定 virIdx、phyTag、waymask、bankIdx、poison。
|
||||
- 写入操作后,发起一个对相同虚拟索引的读请求。验证 readResp 的 metas 和 codes 分别包含写入的 ptag 和 ecc code,并且对于写入的路,readResp.entryValid 信号被置为有效。
|
||||
|
||||
2. 元数据读取操作 (命中): 当一个读请求在 ICacheMetaArray 中命中时(存在有效的条目),它应该返回正确的元数据(标签和有效位)。
|
||||
|
||||
- 首先,向特定的虚拟索引(组和路)写入元数据(参照上面的写入操作)。然后,向相同的虚拟索引发送一个读请求。
|
||||
- 验证 readResp.metas 包含之前写入的物理标签,并且对于相应的路,readResp.entryValid 信号被置为有效。
|
||||
|
||||
3. 元数据读取操作 (未命中): 当读取一个尚未被写入的地址时,ICacheMetaArray 应当指示未命中(条目无效)。
|
||||
|
||||
- 向 ICacheMetaArray 发送一个读请求,请求的虚拟索引在复位后从未被写入过。
|
||||
- 验证对于任何路,readResp.entryValid 信号都没有被置为有效。 对应的 readResp.metas 和 codes 的内容是 DontCare 也就是 0。
|
||||
|
||||
4. 独立的缓存组刷新:在第 i 个端口是有效的刷新请求,并且该请求的 waymask 指定了当前正在处理的第 w 路时,应该使第 i 个端口的条目无效。
|
||||
|
||||
- 先向 ICacheMetaArray 写入指定一个或多个端口的元数据,然后再给对应的端口的路发送刷新请求 io.flush,其包含虚拟索引 virIdx 和路掩码 waymask。
|
||||
- 验证 valid_array 对应的路中的 virIdx 被置为无效,io.readResp.entryValid 对应路的对应端口为无效。
|
||||
|
||||
5. 全部刷新操作: ICacheMetaArray 应当能够在接收到全部刷新请求时,使所有条目无效。
|
||||
|
||||
- 先向多个不同的虚拟索引写入元数据。然后置位 io.flushAll 信号。
|
||||
- 验证步骤: 在 io.flushAll 信号置位后,发起对所有之前写入过的虚拟索引的读请求。验证在所有的读取响应中,对于任何路,readResp.entryValid 信号都没有被置为有效。
|
||||
|
||||
### DataArray 功能
|
||||
|
||||
与 MetaArray 类似,在 MainPipe 的 S0,接收来自 MainPipe 的读请求 read,返回对应路和组的响应 readResp。
|
||||
在 miss 的时候,MissUnit 会将会应的数据写入 write 到 DataArray。
|
||||
DataArray 主要存储了每个 Cache 行的标签和 ECC 校验码。
|
||||
|
||||
1. 数据写入操作(对应的 Set 已满): ICacheDataArray 应当能够正确地将数据写入到指定的 Set (组)、Way (路) 和数据 Bank (存储体)。
|
||||
|
||||
- 发送一个写请求 write 到 ICacheDataArray,ICacheReplacer 根据 PLRU 替换策略指定 way,替换路被写入 waymask,最终指定虚拟索引、数据、路掩码、存储体索引 bankIdx 和毒化位。写入的数据模式应跨越多个数据存储体。
|
||||
- 写入操作后,发起一个对相同虚拟索引和块偏移量的读请求。验证 readResp.datas 与写入的数据相匹配。
|
||||
|
||||
2. 数据读取操作 (命中): 当一个读请求命中时(相应的元数据有效),它应该从相应的组、路和数据存储体返回正确的数据。
|
||||
|
||||
- 首先,向特定的虚拟索引和块偏移量写入数据。然后,向相同的虚拟索引和块偏移量发送一个读请求。使用不同的块偏移量进行测试,以覆盖存储体的选择逻辑。
|
||||
- 验证 readResp.datas 包含之前写入的数据。
|
||||
|
||||
3. 数据读取操作 (未命中): 当读取一个尚未被写入的地址时,ICacheDataArray 的输出应该是默认值或无关值。
|
||||
|
||||
- 向 ICacheDataArray 发送一个读请求,请求的虚拟索引在复位后从未被写入过。
|
||||
- 验证 readResp.datas 为 0。
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,176 @@
|
|||
---
|
||||
title: LoadQueueRAR
|
||||
linkTitle: LoadQueueRAR
|
||||
weight: 12
|
||||
---
|
||||
|
||||
**本文档参考[香山LSQ设计文档](https://github.com/OpenXiangShan/XiangShan-Design-Doc/tree/master/docs/memblock/LSU/LSQ)写成**
|
||||
|
||||
本文档撰写的内容截至[ca892e73]
|
||||
|
||||
请注意,本文档撰写的测试点仅供参考,如能补充更多测试点,最终获得的奖励可能更高!
|
||||
|
||||
# LoadQueueRAR 简介
|
||||
|
||||
LoadQueueRAR用于保存已经完成的load指令的用于load to load违例检测的信息。
|
||||
|
||||
多核环境下会出现load to load违例。单核环境下相同地址的load乱序执行本来是不关心的,但是如果两个load之间有另外一个核做了相同地址的store,并且本身这个核的两个load做了乱序调度,就有可能导致新的load没有看到store更新的结果,但是旧的load看到了,出现了顺序错误。
|
||||
|
||||
多核环境下的load-load违例有一个特征,当前DCache一定会收到L2 cache发来的Probe请求,使得DCache主动释放掉这个数据副本,这时DCache会通知load queue,将相同地址的load queue中已经完成访存的项做一个release标记。后续发往流水线的load指令会查询load queue中在它之后相同地址的load指令,如果存在release标记,就发生了load-load违例。
|
||||
|
||||
## 术语说明
|
||||
|
||||
| 名称 | 描述 |
|
||||
| ------------------------------- | ------------------------------------------ |
|
||||
| L2Cache | 二级高速缓存 |
|
||||
| DCache | 数据缓存 |
|
||||
| ROB | 重排序缓冲区 |
|
||||
| CAM | 内容可寻址存储器 |
|
||||
| FTQ | 取指目标队列 |
|
||||
|
||||
## ld-ld违例
|
||||
|
||||
多核环境下,可能会出现load to load违例:在单核环境中,相同地址的load乱序执行通常不被关注,因为它们在同一核内执行,不会影响其他核的状态,也不会被其他核的操作影响。但是,当两个load操作之间有另一个核对相同地址进行了store操作,情况就变得复杂。
|
||||
|
||||
考虑以下指令序列:
|
||||
```
|
||||
load1(core1)
|
||||
store(core2)
|
||||
load2(core1)
|
||||
```
|
||||
指令的实际执行顺序为:
|
||||
```
|
||||
load2(core1)
|
||||
store(core2)
|
||||
load1(core1)
|
||||
```
|
||||
由于指令的乱序执行,可能导致以下情况:旧的 load1 指令在执行时读取到了 store 修改后的新数据,而新的 load2 指令却读取到了未被修改的旧数据。这种执行顺序的变化会导致数据的不一致性,进而引发访存错误。
|
||||
|
||||
因此,在多核环境中,正确处理指令的执行顺序和内存一致性是至关重要的,以确保所有核都能看到一致的内存状态。
|
||||
|
||||
## 整体框图
|
||||
|
||||
<div>
|
||||
<center>
|
||||
<img src="../LoadQueueRAR_structure.svg"
|
||||
alt="LoadQueueRAR结构示意图"
|
||||
style="zoom:100%"/>
|
||||
<br>
|
||||
图1:LoadQueueRAR结构示意图<br><br>
|
||||
</center>
|
||||
</div>
|
||||
|
||||
LoadQueueRAR最多能够存储72条指令(为了同VirtualLoadQueue的大小保持一致),每条指令占用一个条目。每个条目包含指令的物理地址(paddr)、与指令相关的信息(uop)、以及标记为已释放(released)和已分配(allocated)的状态寄存器。
|
||||
|
||||
该模块通过 FreeList 子模块管理 entry 资源,FreeList 中存储的是 entry 的编号。当一条指令满足入队条件时,FreeList 会为其分配一个 entry 编号,并将该指令存放在相应的 entry 中。指令出队时,需要释放所占用的 entry 资源,并将条目编号重新放回 FreeList 中,以供后续指令使用。
|
||||
|
||||
PaddrModule 的实现基于内容可寻址存储器(CAM),其深度为 72,数据宽度为 48。CAM 为每条流水线提供一个写端口,其中物理地址(paddr)作为写数据(wdata),条目编号作为写地址(waddr)。此外,CAM 还为每条流水线提供了一个地址查询端口(releaseViolationMdata),并为数据缓存(DCache)提供另一个地址查询端口(releaseMdata)。
|
||||
|
||||
<mrs-functions>
|
||||
|
||||
## 模块功能说明
|
||||
|
||||
### 功能1:发生ld-ld违例的指令请求入队
|
||||
|
||||
当query到达load流水线的s2时,判断是否满足入队条件,如果在当前load指令之前有未完成的load指令,且当前指令没有被flush时,当前load可以入队。
|
||||
|
||||
具体入队条件如下:
|
||||
|
||||
1. 指令的入队请求必须有效,具体通过检查 `query.req.valid` 是否等于 1。如果该条件满足,系统将继续处理指令的入队。
|
||||
|
||||
2. 指令必须确认尚未写回到重排序缓冲区(ROB)。这一条件通过比较指令在 VirtualLoadQueue 中的写回指针与该指令分配的 `lqIdx` 来验证。指令只有在到达 VirtualLoadQueue 的队头,并且其地址和数据均已准备好后,才能被写回到 ROB。这一机制确保了指令执行的顺序性和数据的有效性。
|
||||
|
||||
3. 指令不能处于冲刷状态。为此,系统需要比较重定向指针所指向的指令与该指令的 `robIdx`、`ftqidx`及 FTQ 内的偏移(`ftqoffset`)。如果两者不相同,则说明该指令可以安全入队,从而避免潜在的冲突和数据不一致性。
|
||||
|
||||
在 LoadQueueRAR 指令成功入队后,系统会执行一系列响应操作,以确保指令被正确管理和处理。具体的入队响应操作如下:
|
||||
|
||||
1. 拉高 allocated 寄存器。系统将指令的 `allocated` 寄存器设置为高电平。这一操作的目的是明确标识该指令已成功分配到 LoadQueueRAR 中。通过将 `allocated` 寄存器拉高,后续的处理逻辑能够迅速识别出该指令的状态,从而避免对未分配指令的误操作。
|
||||
|
||||
2. 写入指令相关信息到 uop。指令的相关信息将被写入到微操作(`uop`)中。这些信息包括指令的类型、目标寄存器、操作数等关键信息。将这些信息存储在 `uop` 中,确保后续的执行阶段能够准确获取和使用这些数据,从而执行相应的操作。这一过程对于指令的正确执行至关重要。
|
||||
|
||||
3. 物理地址写入 PaddrModule。指令的物理地址将被写入到 PaddrModule 中。这一操作的主要目的是为后续的地址查询和管理提供支持。
|
||||
|
||||
4. 检测 Release 的 Valid 信号。系统将检测 `release` 的有效信号是否被拉高。如果该信号有效,将进一步比较物理地址是否相同。如果物理地址一致,则对应条目的 `released` 信号将被设置为高电平,可以用于后续操作。
|
||||
|
||||
### 功能2:检测ld-ld违例条件
|
||||
|
||||
在 Load 指令的处理过程中,为了确保数据的一致性和正确性,系统需要检测潜在的 Load-Load 违例。当 load 到达流水线的 s2 时,会检查RAR队列中是否存在与当前load指令物理地址相同且比当前指令年轻的load指令,如果这些 load 已经拿到了数据,并且被标记了release,说明发生 load - load 违例,被标记release的指令需要从取指重发。 该检测过程主要涉及将查询指令的物理地址和相关信息与队列中存储的指令进行对比。具体流程如下:
|
||||
|
||||
1. 对比 ROB 索引。通过对比两条指令的robidx识别队列中是否存在比查询指令更年轻的指令。
|
||||
2. 物理地址匹配。检查这两条指令的物理地址是否相同。这一对比通过 `releaseViolationMmask(w)(i)` 来进行,以确定两条指令是否访问了相同的内存位置。
|
||||
3. 检查 Released 标记。如果该条指令的 `released` 寄存器被拉高,表明该指令已被标记为释放,说明它可以被重新使用。
|
||||
|
||||
一旦检测到 Load-Load 违例,系统将在下一个时钟周期内将 `resp.rep_rm_fetch` 信号拉高,以通知其他组件发生了违例。触发 Load-Load 违例的 Load 指令将被标记为需要重新从取指阶段执行。重定向请求将在这些指令到达 ROB 队列的尾部时发出,确保指令能够在合适的时机得到正确的处理。
|
||||
|
||||
该过程分为两个时钟周期进行:
|
||||
|
||||
- 第一拍进行条件匹配,对比物理地址和指令状态,得到mask。
|
||||
- 第二拍生成是否发生违例的响应信号(`resp.rep_rm_fetch` )。
|
||||
|
||||
由于 Load-Load 违例出现的频率相对较低,因此系统会选择在指令到达 ROB 的头部时才进行处理。这种处理方式类似于异常处理,确保系统能够在合适的时机对潜在的违例情况进行响应。
|
||||
|
||||
### 功能3:released寄存器更新
|
||||
|
||||
released寄存器需要更新的三种情况:
|
||||
|
||||
1. missQueue模块的replace_req在mainpipe流水线的s3栈发起release释放dcache块,release信号在下一拍进入loadqueue。
|
||||
2. probeQueue模块的probe_req在mainpipe流水线的s3栈发起release释放dcache块,release信号在下一拍进入loadqueue。
|
||||
3. atomicsUnit模块的请求在mainpipe流水线的s3栈发生miss时需要释放dcache块,release信号在下一拍进入loadQueue。
|
||||
|
||||
release信号的到达时机可以分为以下两种情况:
|
||||
|
||||
1. 指令入队时到达。如果查询指令传来的paddr的高42位信号与paddr的高位信号相同,并且该指令能够成功入队将对应entry的released寄存器信号拉高
|
||||
2. 指令入队后到达。如果paddrmodule中存放的paddr的高42位信号与paddr的高位信号相同,将对应的released寄存器信号拉高
|
||||
|
||||
值得注意的是,dcache release 信号在更新 load queue 中 `released` 状态位时, 会与正常 load 流水线中的 load-load 违例检查争用 load paddr cam 端口. release 信号更新 load queue 有更高的优先级. 如果争用不到资源, 流水线中的 load 指令将立刻被从保留站重发.
|
||||
|
||||
### 功能4:指令的出队
|
||||
|
||||
Load指令的出队需要满足以下条件其中之一:
|
||||
|
||||
1. 当比队列entry中存放的指令更老的指令已经全部写回到ROB时,该指令可以出队。
|
||||
2. 当这条指令需要被冲刷时,通常是出现数据依赖性问题、预测错误、异常或错误的情况下,迫使系统强制性地移除该指令,以保证处理器能够恢复到一个稳定的状态。
|
||||
|
||||
出队执行的操作:
|
||||
|
||||
1. 将指令对应的 `allocated` 寄存器设置为低电平。这一操作的目的是标识该指令不再占用 LoadQueueRAR 的资源,从而为后续指令的入队和处理腾出空间。
|
||||
2. 将entry对应的`free`掩码拉高,表示该条目已被释放并可供后续使用。
|
||||
|
||||
在load流水线的s3阶段可以向队列发送revoke信号撤销上一拍的请求。如果指令当前周期的revoke信号拉高(revoke ==1),并且在上一个周期已经入队,需要执行撤销操作:
|
||||
|
||||
1. 该entry对应的allocated寄存器清零
|
||||
2. 该entry对应的free掩码拉高
|
||||
|
||||
</mrs-functions>
|
||||
|
||||
## 接口说明
|
||||
|
||||
| | name | I/O | width | Description |
|
||||
| ---------------------- | ---------------------------------------- | ------------------- | ------------------ | ------------------------------------------------ |
|
||||
| redirect | | | | |
|
||||
| io.redirect.valid | input | 1 | 后端重定向的有效位 | |
|
||||
| | io.redirect.bits.robIdx.flag | input | 1 | 后端重定向的flag,用于在循环列表中判断先后 |
|
||||
| | io.redirect.bits.robIdx.value | input | 8 | 后端重定向的位置value |
|
||||
| | io.redirect.bits.level | | | |
|
||||
| input | 1 | 后端重定向的level: | | |
|
||||
| 1’b0:冲刷之后的指令; | | | | |
|
||||
| 1‘b1:冲刷这条指令本身 | | | | |
|
||||
| vecFeedback | io.vecFeedback_0/1.valid | input | 1 | 来自两条流水线的向量反馈信息有效位 |
|
||||
| | io.vecFeedback_0/1.bits | input | 17 | 来自两条流水线的向量反馈信息 |
|
||||
| query | io.query_0/1/2.req.ready | output | 1 | 能否接收3条数据通路中load违例检查请求 |
|
||||
| | io.query_0/1/2.req.valid | input | 1 | 3条数据通路中load违例检查有效位 |
|
||||
| | io.query_0/1/2.req.bits.uop.robIdx.flag | input | 1 | 3条数据通路中load违例检查uop在rob中的flag |
|
||||
| | io.query_0/1/2.req.bits.uop.robIdx.value | input | 8 | 3条数据通路中load违例检查uop在rob中的value |
|
||||
| | io.query_0/1/2.req.bits.uop.lqIdx.flag | input | 1 | 3条数据通路中load违例检查uop在LoadQueue中的flag |
|
||||
| | io.query_0/1/2.req.bits.uop.lqIdx.value | input | 7 | 3条数据通路中load违例检查uop在LoadQueue中的value |
|
||||
| | io.query_0/1/2.req.bits.paddr | input | 48 | 3条数据通路中load违例检查的物理地址 |
|
||||
| | io.query_0/1/2.req.bits.data.valid | input | 1 | 3条数据通路中load违例检查data的有效 |
|
||||
| | io.query_0/1/2.resp.valid | output | 1 | 3条数据通路中load违例检查响应的有效位 |
|
||||
| | io.query_0/1/2.resp.bits.rep.frm.fetch | output | 1 | 3条数据通路中load违例检查的响应 |
|
||||
| | io.query_0/1/2.revoke | input | 1 | 3条数据通路中load违例检查的撤销 |
|
||||
| release | io.release.valid | input | 1 | Dcache释放块有效位 |
|
||||
| | io.release.bits.paddr | input | 48 | Dcache释放块的物理地址 |
|
||||
| ldwbptr | io.ldWbPtr.flag | input | 1 | VirtualLoadQueue中writeback的flag |
|
||||
| | io.ldWbPtr.value | input | 7 | VirtualLoadQueue中writeback的位置value |
|
||||
| Lqfull | io.lqFull | output | 1 | 表示loadqueue RAR满了 |
|
||||
| performance | io.perf_0/1_value | output | 6 | 性能计数器 |
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
---
|
||||
title: LoadQueueRAW
|
||||
linkTitle: LoadQueueRAW
|
||||
weight: 12
|
||||
---
|
||||
|
||||
**本文档参考[香山LSQ设计文档](https://github.com/OpenXiangShan/XiangShan-Design-Doc/tree/master/docs/memblock/LSU/LSQ)写成**
|
||||
|
||||
本文档撰写的内容截至[ca892e73]
|
||||
|
||||
请注意,本文档撰写的测试点仅供参考,如能补充更多测试点,最终获得的奖励可能更高!
|
||||
|
||||
# LoadQueueRAW 简介
|
||||
|
||||
LoadQueueRAW是用于处理store-load违例的。由于load和store在流水线中都是乱序执行,会经常出现load越过了更老的相同地址的store,即这条load本应该前递store的数据,但是由于store地址或者数据没有准备好,导致这条load没有前递到store的数据就已经提交,后续使用这条load结果的指令也都发生了错误,于是产生store to load forwarding违例。
|
||||
|
||||
当store address通过STA保留站发射出来进入store流水线时,会去查询LQRAW中在这条store后面的所有已经完成访存的相同地址的load,以及load流水线中正在进行的在该条store之后的相同地址的load,一旦发现有,就发生了store to load forwarding违例,可能有多个load发生了违例,需要找到离store最近的load,也就是最老的违例的load,然后给RedirectGenerator部件发送重定向请求,冲刷最老的违例的load及之后的所有指令。
|
||||
|
||||
当store流水线执行cbo zero指令时,也需要进行store-load违例检查。
|
||||
|
||||
## st-ld违例
|
||||
|
||||
在现代处理器中,Load 和 Store 指令通常采用乱序执行的方式进行处理。这种执行策略旨在提高处理器的并行性和整体性能。然而,由于 Load 和 Store 指令在流水线中的乱序执行,常常会出现 Load 指令越过更早的相同地址的 Store 指令的情况。这意味着,Load 指令本应通过前递(forwarding)机制从 Store 指令获取数据,但由于 Store 指令的地址或数据尚未准备好,导致 Load 指令未能成功前递到 Store 的数据,而 Store 指令已被提交。由此,后续依赖于该 Load 指令结果的指令可能会出现错误,这就是 st-ld 违例。
|
||||
|
||||
考虑以下伪代码示例:
|
||||
|
||||
```
|
||||
ST R1, 0(R2) ; 将 R1 的值存储到 R2 指向的内存地址
|
||||
LD R3, 0(R2) ; 从 R2 指向的内存地址加载值到 R3
|
||||
ADD R4, R3, R5 ; 使用 R3 的值进行计算
|
||||
```
|
||||
|
||||
假设在这个过程中,Store 指令由于某种原因(如缓存未命中)未能及时完成,而 Load 指令已经执行并读取了旧的数据(例如,从内存中读取到的值为 `0`)。此时,Load 指令并未获得 Store 指令更新后的值,导致后续计算的数据错误。
|
||||
|
||||
通过上述例子,可以清楚地看到 Store-to-Load 违例如何在乱序执行的环境中导致数据一致性问题。这种问题强调了在指令调度和执行过程中,确保正确的数据流动的重要性。现代处理器通过多种机制来检测和解决这种违例,以维护程序的正确性和稳定性。
|
||||
|
||||
## 整体框图
|
||||
|
||||
<div>
|
||||
<center>
|
||||
<img src="../LoadQueueRAW_structure.svg"
|
||||
alt="LoadQueueRAW结构示意图"
|
||||
style="zoom:100%"/>
|
||||
<br>
|
||||
图1:LoadQueueRAW结构示意图<br><br>
|
||||
</center>
|
||||
</div>
|
||||
|
||||
LoadQueueRAW最多能够存储64条指令,通过FreeList子模块管理空闲资源。FreeList 中存储的是 entry 的编号。当一条指令满足入队条件时,FreeList 会为其分配一个 entry 编号,并将该指令存放在相应的 entry 中。指令出队时,需要释放所占用的 entry 资源,并将条目编号重新放回 FreeList 中,以供后续指令使用。Load指令在s2阶段在 LoadQueueRAR 中查询 store-to-load 违例,在s3阶段返回响应。
|
||||
|
||||
<mrs-functions>
|
||||
|
||||
## 模块功能说明
|
||||
|
||||
### 功能1:发生st-ld违例的指令请求入队
|
||||
|
||||
当query到达load流水线的s2时,判断是否满足入队条件,如果在当前load指令之前有地址未准备好的store指令,且当前指令没有被flush时,当前load可以入队。具体流程如下:
|
||||
|
||||
1. 判断入队条件:检查在当前 Load 指令之前是否存在未准备好的 Store 指令。如果存在这样的 Store 指令,并且当前 Load 指令尚未被冲刷(flush),则当前 Load 指令可以入队。
|
||||
2. 分配 Entry 和 Index:在 Freelist 中,系统将获得一个可分配的 Entry 及其对应的 Index,以便为 Load 指令分配资源。
|
||||
3. 保存物理地址:在 PaddrModule 中将入队的 Load 指令的物理地址保存到对应的 Entry。这一操作确保在后续访问中能够正确引用该地址。
|
||||
4. 保存掩码信息:在 MaskModule 中,系统将入队的 Load 指令的掩码信息保存到对应的 Entry。掩码信息用于后续的地址匹配和数据访问。
|
||||
5. 写入uop:将 Load 指令的uop信息写入到相应的结构中,以完成入队过程。
|
||||
|
||||
### 功能2:检测st-ld违例条件
|
||||
|
||||
在 Store 指令到达 Store 流水线的 s1 阶段时,系统会进行 Store-to-Load 违例检查。此时,Store 指令需要与 Load Queue 中已经完成访存的 Load 指令,以及在 Load 流水线 s1 和 s2 阶段正在访存的 Load 指令进行比较。这些 Load 指令可能尚未通过前递(forwarding)机制获取 Store 指令执行的结果。
|
||||
|
||||
具体的违例检查流程如下:
|
||||
|
||||
1. 物理地址匹配:在第一拍中,系统将进行物理地址匹配,并检查条件。此时,将匹配在当前 Store 指令之后的所有新的 Load 指令。如果这些 Load 指令已经成功获取了数据(`datavalid`),或者由于缓存未命中正在等待数据回填(`dcache miss`),则可以确定这些 Load 指令不会将数据前递给当前的 Store 指令。
|
||||
2. 匹配 Load 指令:在第二拍中,Store 流水线中的 Store 指令根据匹配结果中的掩码(mask),在 Load Queue 的 RAW(Read After Write)结构中查找所有匹配的 Load 指令。Load Queue 中共有 32 项,这些项将被平分为4组。每组从 8 项中选出一个最老的 Load 指令,最多可得到 4 个候选最老的 Load 指令。
|
||||
3. 选择最老的 Load:在第三拍中,从上述 4 个候选最老的 Load 指令中,系统将选出一个最老的 Load 指令,作为最终的目标。
|
||||
4. 处理违例情况:在第四拍中,如果在两条 Store 流水线中均发生了 Store-to-Load 违例,系统将从各自的 Queue 中匹配到的最老 Load 指令中选出一个更老的 Load 指令,以产生回滚请求并发送给重定向模块(Redirect)。此时,违例的条件包括:
|
||||
- Load 和 Store 的地址相同。
|
||||
- Load 指令比 Store 指令年轻。
|
||||
- Load 指令已经成功获取了数据。
|
||||
|
||||
### 功能3:指令的出队
|
||||
|
||||
Load指令的出队需要满足以下条件其中之一:
|
||||
|
||||
1. 当比队列entry中存放的指令更老的指令已经全部写回到ROB时,该指令可以出队。
|
||||
2. 当这条指令需要被冲刷时,通常是出现数据依赖性问题、预测错误、异常或错误的情况下,迫使系统强制性地移除该指令,以保证处理器能够恢复到一个稳定的状态。
|
||||
|
||||
出队执行的操作:
|
||||
|
||||
1. 将指令对应的 `allocated` 寄存器设置为低电平。这一操作的目的是标识该指令不再占用 LoadQueueRAR 的资源,从而为后续指令的入队和处理腾出空间。
|
||||
2. 将entry对应的`free`掩码拉高,表示该条目已被释放并可供后续使用。
|
||||
|
||||
在load流水线的s3阶段可以向队列发送revoke信号撤销上一拍的请求。如果指令当前周期的revoke信号拉高(revoke ==1),并且在上一个周期已经入队,需要执行撤销操作:
|
||||
|
||||
1. 该entry对应的allocated寄存器清零
|
||||
2. 该entry对应的free掩码拉高
|
||||
|
||||
</mrs-functions>
|
||||
|
||||
## 接口说明
|
||||
|
||||
| | name | I/O | width | Description |
|
||||
| ---------------- | ---------------------------------------- | ------ | ----- | ------------------------------------------------------------ |
|
||||
| redirect | io.redirect.valid | input | 1 | 后端重定向的有效位 |
|
||||
| | io.redirect.bits.robIdx.flag | input | 1 | 后端重定向的flag,用于在循环列表中判断先后 |
|
||||
| | io.redirect.bits.robIdx.value | input | 8 | 后端重定向的位置value |
|
||||
| | io.redirect.bits.level | input | 1 | 后端重定向的level:1’b0:冲刷之后的指令;1‘b1:冲刷这条指令本身 |
|
||||
| vecFeedback | io.vecFeedback_0/1.valid | input | 1 | 来自两条流水线的向量反馈信息有效位 |
|
||||
| | io.vecFeedback_0/1.bits | input | 17 | 来自两条流水线的向量反馈信息 |
|
||||
| query | io.query_0/1/2.req.ready | output | 1 | 能否接收3条数据通路中load违例检查请求 |
|
||||
| | io.query_0/1/2.req.valid | input | 1 | 3条数据通路中load违例检查有效位 |
|
||||
| | io.query_0/1/2.req.bits.uop.robIdx.flag | input | 1 | 3条数据通路中load违例检查uop在rob中的flag |
|
||||
| | io.query_0/1/2.req.bits.uop.robIdx.value | input | 8 | 3条数据通路中load违例检查uop在rob中的value |
|
||||
| | io.query_0/1/2.req.bits.uop.lqIdx.flag | input | 1 | 3条数据通路中load违例检查uop在LoadQueue中的flag |
|
||||
| | io.query_0/1/2.req.bits.uop.lqIdx.value | input | 7 | 3条数据通路中load违例检查uop在LoadQueue中的value |
|
||||
| | io.query_0/1/2.req.bits.paddr | input | 48 | 3条数据通路中load违例检查的物理地址 |
|
||||
| | io.query_0/1/2.req.bits.data.valid | input | 1 | 3条数据通路中load违例检查data的有效 |
|
||||
| | io.query_0/1/2.revoke | input | 1 | 3条数据通路中load违例检查的撤销 |
|
||||
| storeIn | storeIn_0/1.bits | input | 84 | 两条store流水线store指令相关信息 |
|
||||
| | storeIn_0/1.valid | input | 1 | 两条store流水线store指令相关信息有效位 |
|
||||
| rollback | rollback_0/1.valid | output | 1 | 两条store流水线回滚信息的有效性 |
|
||||
| | rollback_0/1.bits | output | 31 | 两条store流水线回滚信息 |
|
||||
| stAddrReadySqPtr | stAddrReadySqPtr | input | 7 | 指向 store 队列中已准备好的地址条目 |
|
||||
| stIssuePtr | stIssuePtr | input | 7 | 指向 store 队列中准备发射执行的指令条目 |
|
||||
| lqFull | lqFull | output | 1 | 判断队列是否满 |
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
---
|
||||
title: LoadQueueReplay
|
||||
linkTitle: LoadQueueReplay
|
||||
weight: 12
|
||||
---
|
||||
|
||||
**本文档参考[香山LSQ设计文档](https://github.com/OpenXiangShan/XiangShan-Design-Doc/tree/master/docs/memblock/LSU/LSQ)写成**
|
||||
|
||||
本文档撰写的内容截至[ca892e73]
|
||||
|
||||
请注意,本文档撰写的测试点仅供参考,如能补充更多测试点,最终获得的奖励可能更高!
|
||||
|
||||
# LoadQueueReplay 简介
|
||||
|
||||
LoadQueueReplay 模块是现代处理器架构中用于处理 Load 指令重发的重要组成部分。它负责管理因各种原因而需要重发的 Load 指令,确保指令执行的正确性和高效性。
|
||||
|
||||
## 整体框图
|
||||
|
||||
<div>
|
||||
<center>
|
||||
<img src="../LoadQueueReplay_structure.png"
|
||||
alt="LoadQueueReplay结构示意图"
|
||||
style="zoom:100%"/>
|
||||
<br>
|
||||
图1:LoadQueueReplay结构示意图<br><br>
|
||||
</center>
|
||||
</div>
|
||||
|
||||
LoadQueueReplay 最多存放72条指令,涉及多个状态和存储的信息。其关键组成部分如下:
|
||||
|
||||
- **Allocated**:
|
||||
- 表示某个 Load 重发队列项是否已经被分配,反映该项的有效性。
|
||||
- **Scheduled**:
|
||||
- 指示某个 Load 重发队列项是否已被调度,意味着该项已经被选出,并将被发送至 Load Unit 进行重发。
|
||||
- **Uop**:
|
||||
- 该队列项对应的 Load 指令执行信息,包括微操作(uop)。
|
||||
- **Cause**:指示该 Load 指令重发的原因,主要包括以下几种情况:
|
||||
- **C_MA**:存储-加载(st-ld)违反重新执行。
|
||||
- **C_TM**:TLB(翻译后备页表)缺失。
|
||||
- **C_FF**:存储-加载转发。
|
||||
- **C_DR**:数据缓存(dcache)需要重发。
|
||||
- **C_DM**:数据缓存缺失。
|
||||
- **C_WF**:路径预测失败。
|
||||
- **C_BC**:数据缓存路径冲突。
|
||||
- **C_RAR**:读取-读取(RAR)队列无法接收。
|
||||
- **C_RAW**:读取-写入(RAW)队列无法接收。
|
||||
- **C_NK**:存储-加载违反。
|
||||
- **Blocking**:指示该 Load 指令因等待条件而被阻塞,不能被调度重发。阻塞的原因和解除阻塞的条件包括:
|
||||
- **C_MA**:存储指令的地址就绪。
|
||||
- **C_TM**:TLB 回填完毕,并发送 Hint 信号。
|
||||
- **C_FF**:存储指令的数据就绪。
|
||||
- **C_DM**:数据缓存回填完毕。
|
||||
- **C_RAR**:RAR 队列未满,且 Load 指令比 Load Queue 的写回项更老。
|
||||
- **C_RAW**:RAW 队列未满,且 Load 指令比 Store Queue 中所有地址准备好的项都更老。
|
||||
|
||||
LoadReplayQueue 通过 FreeList 管理队列的空闲状态。FreeList 的大小等于 LoadReplayQueue 的项数,分配宽度为3(Load Unit 的数量),释放宽度为 4。同时,Free List 可以反馈 Load Replay Queue 的空余项数量以及是否已满的信息。除了FreeList,LoadQueueReplay还包含两个子模块:AgeDetector 和 LqVAddrModule,其中 AgeDetector 用于寻找一系列load replay queue项中最早入队的一项。
|
||||
|
||||
例如昆明湖V1的Load宽度为2,则会将load replay queue分为两半,从偶数项和奇数项中分别挑选一项最老的进行重发。LqVAddrModule 用于保存load replay queue项数个虚拟地址,读口和写口的数量均为Load的宽度(LoadUnit的数量)。
|
||||
|
||||
## LoadQueueReplay 存储信息
|
||||
| Field | Description |
|
||||
|-----------------------|-------------|
|
||||
| allocated | 是否已经被分配,也代表是否该项是否有效。 |
|
||||
| scheduled | 是否已经被调度,代表该项已经被选出,已经或即将被发送至LoadUnit进行重发。 |
|
||||
| uop | load指令执行包括的uop信息。 |
|
||||
| vecReplay | 向量load指令相关信息。 |
|
||||
| vaddrModule | Load指令的虚拟地址。 |
|
||||
| cause | 某load replay queue项对应load指令重发的原因,包括:<br>- C_MA(位0): store-load预测违例<br>- C_TM(位1): tlb miss<br>- C_FF(位2): store-to-load-forwarding store数据为准备好,导致失败<br>- C_DR(位3): 出现DCache miss,但是无法分配MSHR<br>- C_DM(位4): 出现DCache miss<br>- C_WF(位5): 路预测器预测错误<br>- C_BC(位6): Bank冲突<br>- C_RAR(位7): LoadQueueRAR没有空间接受指令<br>- C_RAR(位8): LoadQueueRAW没有空间接受指令<br>- C_NK(位9): LoadUnit监测到store-to-load-forwarding违例<br>- C_MF(位10): LoadMisalignBuffer没用空间接受指令 |
|
||||
| blocking | Load指令正在被阻塞。 |
|
||||
| strict | 访存依赖预测器判断指令是否需要等待它之前的所有store指令执行完毕进入调度阶段。 |
|
||||
| blockSqIdx | 与load指令有相关性的store指令的StoreQueue Index。 |
|
||||
| missMSHRId | load指令的dcache miss请求接受ID。 |
|
||||
| tlbHintId | load指令的tlb miss请求接受ID。 |
|
||||
| replacementUpdated | DCache的替换算法是否已经更新。 |
|
||||
| replayCarry | DCache的路预测器预测信息。 |
|
||||
| missDbUpdated | ChiselDB中Miss相关情况更新。 |
|
||||
| dataInLastBeatReg | Load指令需要的数据在两笔回填请求的最后一笔。 |
|
||||
|
||||
# 功能简介
|
||||
|
||||
<mrs-functions>
|
||||
|
||||
## 模块功能说明
|
||||
|
||||
### 功能1:需要重发的指令请求入队
|
||||
|
||||
根据是否满足以下条件以及freelist是否可以分配的空闲槽位决定能否直接入队:
|
||||
|
||||
1. `enq_X.valid` 信号有效。
|
||||
2. 即将入队的项不需要重定向。
|
||||
3. 该项被标记为需要重发(`enq.bits.rep_info.need_rep`)。
|
||||
4. 没有异常。在入队时,必须确保没有异常发生。如果当前指令处于异常状态,入队操作应被禁止,以防止无效指令的执行。
|
||||
|
||||
### 功能2:指令重发解锁
|
||||
|
||||
LoadQueueReplay 中的指令出队分三拍:
|
||||
|
||||
在重发过程中,根据重发的原因和当前条件解锁相应项。在不满足解锁条件时,将会被阻塞,无法参与重发仲裁。其中C_BC(Dcache 块冲突)、C_NK(oad_unit 在 S1、S2 阶段发生 store-load 违例)、C_DR(Dcache miss 且 MSHR 满)、C_WF(路径预测失败)**无需条件即可立即重发**。
|
||||
|
||||
其他重发原因和对应的解锁条件如下:
|
||||
|
||||
1. C_MA(store_load 预测违例):已经被分配入队并且store准备好了相应的地址,具体如下:
|
||||
1. store unit 的地址信号有效,且 `sqIdx` 与被阻塞的 `Idx` 相同,store 地址未发生 TLB miss。
|
||||
2. 被阻塞的 `SeqIdx` 在 store_queue 发送的 `stAddrReadySqPtr` 之前。
|
||||
3. 非严格阻塞,且阻塞的 `SeqIdx` 在 `stAddrReadyVec` 的向量组内。
|
||||
4. store queue 为空,无未处理项。
|
||||
2. C_TM(TLB Miss):resp信号有效,并且输入的id号等于Tlb Hint的id号,或者replay_all信号有效。
|
||||
3. C_FF(store_load 数据前递失败):因为数据前递失败导致指令重发的释放条件有下面四条:
|
||||
1. store unit 的数据信号有效,且 `sqIdx` 与被阻塞的 `Idx` 相同。
|
||||
2. 被阻塞的 `SqIdx` 在 store_queue 发送的 `stDataReadySqPtr` 之前。
|
||||
3. 阻塞的 `SeqIdx` 在 `stDataReadyVec` 的向量组内。
|
||||
4. store queue 为空,无未处理项。
|
||||
4. C_DM(Dcache Miss):Dcache 的信号有效,且 `tl_d_channel.mshrid` 与阻塞的 `missMSHRId` 相同。
|
||||
5. C_RAR(RAR queue 没有回应):RAR 未满,或 `lqIdx` 在 `ldWbPtr` 之前。
|
||||
6. C_RAW(RAW 没有回应):RAW 未满,或 `lqIdx` 在 `stAddrReadySqPtr` 之前。
|
||||
|
||||
### 功能3:指令重发优先级
|
||||
LoadQueueReplay有3种选择调度方式:
|
||||
|
||||
1. 根据入队年龄
|
||||
|
||||
LoadQueueReplay使用3个年龄矩阵(每一个Bank对应一个年龄矩阵),来记录入队的时间。年龄矩阵会从已经准备好可以重发的指令中,选择一个入队时间最长的指令调度重发。
|
||||
|
||||
2. 根据Load指令的年龄
|
||||
|
||||
LoadQueuReplay可以根据LqPtr判断靠近最老的load指令重发,判断宽度为OldestSelectStride=4。
|
||||
|
||||
3. DCache数据相关的load指令优先调度
|
||||
|
||||
- LoadQueueReply首先调度因L2 Hint调度的重发(当dcache miss后,需要继续查询下级缓存L2 Cache。在L2 Cache回填前的2或3拍,L2 Cache会提前给LoadQueueReplay唤醒信号,称为L2 Hint)当收到L2 Hint后,LoadQueueReplay可以更早地唤醒这条因dcache miss而阻塞的Load指令进行重发。
|
||||
|
||||
- 如果不存在L2 Hint情况,会将其余Load Replay的原因分为高优先级和低优先级。高优先级包括因dcache缺失或st-ld forward导致的重发,而将其他原因归纳为低优先级。如果能够从LoadQueueReplay中找出一条满足重发条件的Load指令(有效、未被调度、且不被阻塞等待唤醒),则选择该Load指令重发,否则按照入队顺序,通过AgeDetector模块寻找一系列load replay queue项中最早入队的一项进行重发。
|
||||
|
||||
### 功能4:指令重发逻辑
|
||||
|
||||
1. Load_unit s3过来的请求根据enq.bits.isLoadReplay判断是否是已经从replay_queue出队的序列,如果是已经出队的序列,根据是否needReplay和有异常做下一步的判断,如果有异常或者不需要重发则释放这个槽位,并从agedetector里面把该项出队,如果需要重发则将这个项对应的scheduled位置为false来参与后续的出队仲裁竞争。
|
||||
|
||||
2. 从freelist中选出发给load unit的有效项,项数为load unit的宽度(即有几条load unit的流水线),根据优先级来进行出队。
|
||||
|
||||
3. 第0拍将数据传递给第1拍由s0_can_go控制,当s0_can_go为1时才能将0拍得到的数据发给第一拍,s0_can_go有效的条件是s0被重定向或者s1_can_go为1。
|
||||
|
||||
4. 第一拍从vaddr内部取出需要的虚拟地址,发给下一拍流水线。 ColdCouter的值在0到12之间,上一拍没有被阻塞并且整个过程没有发生重定向的时候,向load unit发送请求。
|
||||
|
||||
5. 发送给下一拍流水线的数据受s1_can_go控制,s1_can_go为1的条件是:
|
||||
|
||||
- ColdCouter的值在0到12之间 且 上一拍完成操作(未被阻塞)或者不需要发送数据两者之一。
|
||||
|
||||
- 发生数据的重定向。
|
||||
|
||||
6. 第二拍将收到第一拍的数据发送给对应的load unit,获取仲裁权限,完成重发指令的任务。
|
||||
|
||||
</mrs-functions>
|
||||
|
||||
## 接口说明
|
||||
|
||||
| name | I/O | description |
|
||||
| ----------------------- | ------ | ------------------------------------------------------------ |
|
||||
| redirect | input | 后端重定向相关信息 |
|
||||
| vecFeedback | input | 来自两条流水线的向量反馈信息 |
|
||||
| enq | input | 表示外部模块希望将 load 指令传递给当前模块,来自 load 指令流水线的 s3 级 |
|
||||
| storeAddrIn | input | 在一个时钟周期内接收多条 store 指令的地址信息,用于判断指令存储的地址是否已经准备好 |
|
||||
| storeDataIn | input | 在一个时钟周期内接收多条 store 指令的数据信息,用于判断指令存储的数据是否已准备好 |
|
||||
| replay | output | 用于处理 load 指令的重发请求,每个元素对应一个重发接口 |
|
||||
| tl_d_channel | input | 用于接收来自数据缓存(Dcache)的信息,在处理 load 指令时会使用该端口进行数据转发 |
|
||||
| stAddrReadySqPtr | input | 指向当前准备好地址的 store 指令 |
|
||||
| stAddrReadyVec | input | 向量中对应 store 指令的地址是否已经准备好 |
|
||||
| stDataReadySqPtr | input | 指向当前准备好数据的 store 指令 |
|
||||
| stDataReadyVec | input | 向量中对应 store 指令的数据是否已经准备好 |
|
||||
| sqEmpty | input | 当前 store 队列是否为空 |
|
||||
| lqFull | output | 当前 load 队列是否已满 |
|
||||
| ldWbPtr | input | 指向当前写回的load指令 |
|
||||
| rarFull | input | rar 队列是否已满 |
|
||||
| rawFull | input | raw 队列是否已满 |
|
||||
| l2_hint | input | 当 dcache miss 后,需要继续查询下级缓存 L2 Cache。在 L2 Cache 回填前的 2 或 3 拍,L2 Cache 会提前给 LoadQueueReplay 唤醒信号,称为 L2 Hint |
|
||||
| tlb_hint | input | 作用类似于 l2_hint,接收当前的 TLB 提示信息 |
|
||||
| tlbReplayDelayCycleCtrl | input | 控制 TLB 重发的延迟周期 |
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
---
|
||||
title: LoadQueueUncache
|
||||
linkTitle: LoadQueueUncache
|
||||
weight: 12
|
||||
---
|
||||
|
||||
**本文档参考[香山LSQ设计文档](https://github.com/OpenXiangShan/XiangShan-Design-Doc/tree/master/docs/memblock/LSU/LSQ)写成**
|
||||
|
||||
本文档撰写的内容截至[66e9b546]
|
||||
|
||||
请注意,本文档撰写的测试点仅供参考,如能补充更多测试点,最终获得的奖励可能更高!
|
||||
|
||||
# LoadQueueUncache 简介
|
||||
|
||||
LoadQueueUncache 和 Uncache 模块,对于 uncache load 访问请求来说,起到一个从 LoadUnit 流水线到总线访问的中间站作用。其中 Uncache 模块,作为靠近总线的一方,主要用于处理 uncache 访问到总线的请求和响应。LoadQueueUncache 作为靠近流水线的一方,需要承担以下责任:
|
||||
|
||||
1. 接收 LoadUnit 流水线传过来的 uncache load 请求。
|
||||
|
||||
2. 选择已准备好 uncache 访问的 uncache load 请求 发送到 Uncache Buffer。
|
||||
|
||||
3. 接收来自 Uncache Buffer 的处理完的 uncache load 请求。
|
||||
|
||||
4. 将处理完的 uncache load 请求 返回给 LoadUnit。
|
||||
|
||||
LoadQueueUncache 结构上,目前有 4 项(项数可配)UncacheEntry,每一项独立负责一个请求并利用一组状态寄存器控制其具体处理流程;有一个 FreeList,管理各项分配和回收的情况。而 LoadQueueUncache 主要是协同 4 项的新项分配、请求选择、响应分派、出队等统筹逻辑。
|
||||
|
||||
## 整体框图
|
||||
|
||||
<div>
|
||||
<center>
|
||||
<img src="../LoadQueueUncache_structure.svg"
|
||||
alt="LoadQueueUncache结构示意图"
|
||||
style="zoom:100%"/>
|
||||
<br>
|
||||
图1:LoadQueueUncache结构示意图
|
||||
</center>
|
||||
</div>
|
||||
|
||||
UnCacheBuffer 最多存放4条指令,除了 FreeList 之外,另一个重要的子模块是 UncacheEntry,管理每个Uncahce请求,负责发起Uncache,写回Uncache数据。每个Entry内维护一个用于发起Uncache请求的状态机,状态机的状态转换图如下:
|
||||
|
||||
<div>
|
||||
<center>
|
||||
<img src="../UncacheEntry.png"
|
||||
alt="UncacheEntry结构示意图"
|
||||
style="zoom:100%"/>
|
||||
<br>
|
||||
图2:UncacheEntry状态转换图
|
||||
</center>
|
||||
</div>
|
||||
|
||||
- s_idl:该项还未发起一个MMIO请求。
|
||||
|
||||
- s_req:向uncache模块发起MMIO请求,等待请求被接收。
|
||||
|
||||
- s_resp:等待uncache模块的MMIO响应。
|
||||
|
||||
- s_wait:等待将MMIO结果写回流水线。
|
||||
|
||||
# 功能简介
|
||||
|
||||
<mrs-functions>
|
||||
|
||||
## 模块功能说明
|
||||
|
||||
### 功能1:Uncache指令请求入队
|
||||
|
||||
LoadQueueUncache 负责接收来自 LoadUnit 0、1、2 三个模块的请求,这些请求可以是 MMIO 请求,也可以是 NC 请求。
|
||||
|
||||
1. 首先,系统会根据请求的 robIdx 按照时间顺序(从最老到最新)对请求进行排序,以确保最早的请求能优先分配到空闲项,避免特殊情况下因老项回滚(rollback)而导致死锁。
|
||||
|
||||
2. 进入入队处理的条件是:请求没有重发、没有异常,并且系统会根据 FreeList 中可分配的空闲项依次为请求分配项。
|
||||
|
||||
3. 当 LoadQueueUncache 达到容量上限,且仍有请求未分配到项时,系统会从这些未分配的请求中选择最早的请求进行 rollback。
|
||||
|
||||
UncacheBuffer 的入队分为 s1 和 s2 两个阶段:
|
||||
|
||||
s1:
|
||||
|
||||
- **请求收集**:通过 `io.req.map(_.bits)` 收集所有请求的内容,形成 `s1_req` 向量。
|
||||
|
||||
- **有效性标记**:通过 `io.req.map(_.valid)` 收集所有请求的有效性,形成 `s1_valid` 向量。
|
||||
|
||||
s2:
|
||||
|
||||
执行入队操作,主要分为以下几步:
|
||||
|
||||
- 使用 `RegEnable` 将 **s1** 阶段的请求 `s1_req` 注册到 `s2_req`,确保在请求有效时保持其状态。
|
||||
|
||||
- 通过以下条件生成`s2_valid`向量,判断每个请求是否有效:
|
||||
|
||||
1. `RegNext(s1_valid(i))`:确保请求在 **s1** 阶段有效。
|
||||
|
||||
2. `!s2_req(i).uop.robIdx.needFlush(RegNext(io.redirect))`:确保请求的 ROB 索引不需要因重定向而被刷新。
|
||||
|
||||
3. `!s2_req(i).uop.robIdx.needFlush(io.redirect)`:确保请求的 ROB 索引不需要因当前重定向而被刷新。
|
||||
|
||||
- 检查每个请求是否需要重发,结果存储在 `s2_need_replay` 向量中。
|
||||
|
||||
- 在 **s2** 阶段,使用 `s2_enqueue` 向量来决定哪些请求成功入队。入队条件包括:
|
||||
|
||||
- `s2_valid(w)`:请求在 **s2** 阶段有效。
|
||||
|
||||
- `!s2_has_exception(w)`:请求没有异常。
|
||||
|
||||
- `!s2_need_replay(w)`:请求不需要重发。
|
||||
|
||||
- `s2_req(w).mmio`:请求是一个内存映射 IO(MMIO)请求。
|
||||
|
||||
- 通过 `enqValidVec` 和 `enqIndexVec` 的有效管理,确保每个加载请求在满足有效性和可分配条件时能够正确地申请和分配FreeList槽位。
|
||||
|
||||
### 功能2:Uncache指令的出队
|
||||
|
||||
1. 当一个项完成 Uncache 访问操作并返回给 LoadUnit ,或被 redirect 刷新时,则该项出队并释放 FreeList 中该项的标志。
|
||||
|
||||
具体流程如下:
|
||||
|
||||
- 计算`freeMaskVec`掩码,用于标记每个槽位的释放状态,指示相应槽位是否可用。
|
||||
|
||||
- 如果当前条目被选择 (`e.io.select`) 且其输出信号有效 (`e.io.ldout.fire`),则对应槽位的释放状态被标记为 `true`,表示该槽位可用。
|
||||
|
||||
- 如果接收到刷新信号 (`e.io.flush`),同样将对应槽位的释放状态设置为 `true`。
|
||||
|
||||
2. 同一拍可能有多个项出队。返回给 LoadUnit 的请求,会在第一拍中选出,第二拍返回。
|
||||
|
||||
3. 其中,可供处理 uncache 返回请求的 LoadUnit 端口是预先设定的。当前,MMIO 只返回到 LoadUnit 2;NC 可返回到 LoadUnit 1\2。在多个端口返回的情况下,利用 uncache entry id 与端口数的余数,来指定每个项可以返回到的 LoadUnit 端口,并从该端口的候选项中选择一个项进行返回。
|
||||
|
||||
### 功能3:Uncache交互逻辑
|
||||
|
||||
1. 发送 req
|
||||
|
||||
第一拍先从当前已准备好 uncache 访问中选择一个,第二拍将其发送给 Uncache Buffer。发送的请求中,会标记选中项的 id,称为 mid 。其中是否被成功接收,可根据 req.ready 判断。
|
||||
|
||||
2. 接收 idResp
|
||||
|
||||
如果发送的请求被 Uncache Buffer 接收,那么会在接收的下一拍收到 Uncache 的 idResp。该响应中,包含 mid 和 Uncache Buffer 为该请求分配 entry id(称为 sid)。LoadQueueUncache 利用 mid 找到内部对应的项,并将 sid 存储在该项中。
|
||||
|
||||
3. 接收 resp
|
||||
|
||||
待 Uncache Buffer 完成该请求的总线访问后,会将访问结果返回给 LoadQueueUncache。该响应中,包含 sid。考虑到 Uncache Buffer 的合并特性(详细入队合并逻辑见 Uncache),一个 sid 可能对应 LoadQueueUncache 的多个项。LoadQueueUncache 利用 sid 找到内部所有相关项,并将访问结果传递给这些项。
|
||||
|
||||
### 功能4:Uncache回滚检测
|
||||
|
||||
freelist 没有空闲表现导致 MMIO Load 进入 UncacheBuffer 失败时需要进行 rollback,此
|
||||
时需要根据 robidx 选择不能入队的 MMIO 中最老的指令进行 rollback。整个流程分为以下几个周期:
|
||||
|
||||
- Cycle 0:进行 uncache 请求入队。
|
||||
|
||||
- Cycle 1:选择最旧的 uncache 加载请求。
|
||||
|
||||
- Cycle 2:发出重定向请求。
|
||||
|
||||
- 从 load 流水线中选择最旧的 load 请求。
|
||||
|
||||
- 根据检测到的拒绝情况准备重定向请求。
|
||||
|
||||
- 如果重定向请求有效,则发出请求。
|
||||
|
||||
使用 `selectOldestRedirect` 函数来选择最旧的重定向请求,具体步骤如下:
|
||||
|
||||
- 比较向量生成:
|
||||
|
||||
- 创建一个比较向量 `compareVec`,用于判断请求的顺序,比较每个请求的 ROB 索引。
|
||||
|
||||
- 生成独热编码结果:
|
||||
|
||||
- `resultOnehot` 向量根据有效性和比较结果生成,标记出最旧的可重定向请求。
|
||||
|
||||
</mrs-functions>
|
||||
|
||||
## 接口说明
|
||||
|
||||
| name | I/O | description |
|
||||
| ----------- | ------ | ------------------------------------------------------------ |
|
||||
| redirect | input | 后端重定向相关信息 |
|
||||
| req | input | 接收写入请求 |
|
||||
| ldout | output | 写回 MMIO 数据接口,输出 MemExuOutput 类型的数据,处理与 MMIO 的写回操作 |
|
||||
| ld_raw_data | output | 读取原始数据输出接口 |
|
||||
| rob | input | 接收来自 ROB 的信号或数据 |
|
||||
| uncache | output | 发送数据或信号给 uncache 模块 |
|
||||
| rollback | output | 当 uncache 缓存满时,从前端进行回滚 |
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
---
|
||||
title: VirtualLoadQueue
|
||||
linkTitle: VirtualLoadQueue
|
||||
weight: 12
|
||||
---
|
||||
|
||||
**本文档参考[香山LSQ设计文档](https://github.com/OpenXiangShan/XiangShan-Design-Doc/tree/master/docs/memblock/LSU/LSQ)写成**
|
||||
|
||||
本文档撰写的内容截至[ca892e73]
|
||||
|
||||
请注意,本文档撰写的测试点仅供参考,如能补充更多测试点,最终获得的奖励可能更高!
|
||||
|
||||
# VirtualLoadQueue 简介
|
||||
|
||||
Virtualloadqueue是一个队列,用于存储所有load指令的微操作(MicroOp),并维护这些load指令之间的顺序,它的功能类似于重排序缓冲区(Reorder Buffer, ROB),但专注于load指令的管理。其主要功能是跟踪Load指令执行状态,以确保在并发执行的环境中,加载操作能够正确、有序地完成。
|
||||
|
||||
## 整体框图
|
||||
|
||||
<div>
|
||||
<center>
|
||||
<img src="../VirtualLoadQueue_structure.png"
|
||||
alt="VirtualLoadQueue结构示意图"
|
||||
style="zoom:100%"/>
|
||||
<br>
|
||||
图1:VirtualLoadQueue结构示意图
|
||||
</center>
|
||||
</div>
|
||||
|
||||
Virtualloadqueue最多可以存放72条指令,dispatch阶段最多支持6条指令同时入队,最多支持8条指令出队。Virtualloadqueue对于每一个 entry 中的 load 指令都有若干状态位来标识这个 load 处于什么状态:
|
||||
|
||||
**allocated**:该项是否分配了load,用于确定load指令的生命周期。
|
||||
|
||||
**isvec**:该指令是否是向量load指令。
|
||||
|
||||
**committed**: 该项是否提交。
|
||||
|
||||
# 功能简介
|
||||
|
||||
<mrs-functions>
|
||||
|
||||
## 模块功能说明
|
||||
|
||||
### 功能1:load指令请求入队
|
||||
|
||||
在调度阶段,保留站通过入队(enq)总线向VirtualLoadQueue发起入队请求,最多支持六组并发请求。成功入队的条件包括以下几点:
|
||||
|
||||
1. StoreQueue 和 LoadQueue 有预留空间:确保LoadQueue有足够的容量来接收新的加载指令,以避免队列溢出。确保StoreQueu有预留空间则是基于数据一致性和避免指令阻塞的考虑,因为store指令入队阻塞可能会导致load指令无法正确读取或forward到数据。
|
||||
2. 入队请求有效:入队请求必须是合法的,确保指令在调度过程中可以被正确处理。
|
||||
3. 指令未被冲刷:确保指令在入队时没有被系统标记为无效或被撤销。
|
||||
|
||||
成功入队之后,系统会执行以下操作:
|
||||
|
||||
1. 将指令的lqidx作为索引,将对应的allocated寄存器置1,bits信息写入uop寄存器。
|
||||
2. 计算新的lqidx值,作为enq_resp传送给保留站。
|
||||
|
||||
### 功能2:接收load流水线写回的数据
|
||||
|
||||
在 load 流水线的s3阶段,load unit会将指令执行的信息通过总线 ldin 写回到 VirtualLoadQueue。具体写回信息包括:
|
||||
|
||||
1. 是否发生了异常以及异常类型
|
||||
2. dcache是否命中
|
||||
3. tlb是否命中
|
||||
4. 是否为mmio指令
|
||||
5. 是否为软件预取或者硬件预取
|
||||
6. 是否需要重发以及重发的原因
|
||||
7. 写uop的使能信号
|
||||
|
||||
写回需要满足的条件如下:
|
||||
|
||||
1. ldin 总线的 valid 信号需要拉高,表明当前正在进行有效的数据传输。
|
||||
2. 指令不应需要重发(即 `need_rep` 信号为 0),否则将影响写回的正常进行。
|
||||
|
||||
在满足写回条件后,系统将生成相应的写回响应,具体包括以下几个方面:
|
||||
|
||||
1. 如果在执行过程中发生了异常、TLB命中或软件预取操作,`addrvalid` 信号将被置为 1,表示地址信息有效。
|
||||
2. 如果在执行过程中发生了异常、MMIO操作、DCACHE命中并且不需要重发,或是软件预取操作,`datavalid` 信号将被置为 1,表示数据有效。
|
||||
3. 指令在流水线的 S3 阶段有效(注意:不能是硬件预取指令)。当 `ldin` 总线的写使能信号 `data_wen_dup` 拉高时,将更新队列中的uop信息,以确保指令的状态及时反映。
|
||||
|
||||
系统将`addrvalid`和`datavalid`分开进行处理是考虑到在一些情况下,地址可以被重用,而数据可能需要重新请求(如dcache miss/mmio/软件预取等)。分开标识可以减少流水线停顿,允许处理器在地址有效时继续执行其他指令,而不必等待数据有效性确认,从而优化整体性能。
|
||||
|
||||
### 功能3:load指令的出队(提交)
|
||||
|
||||
1. 出队时机:当被分配的entries(allocated为高)到达队头,同时allocated与committed都为1时,表示可以出队,如果是向量load,需要每个元素都committed。
|
||||
|
||||
</mrs-functions>
|
||||
|
||||
## 接口说明
|
||||
|
||||
| | name | I/O | width | description |
|
||||
| ----------- | -------------------------------------------------- | ------ | ----- | ------------------------------------------------------------ |
|
||||
| redirect | io.redirect.valid | input | 1 | 后端重定向有效位 |
|
||||
| | io.redirect.bits.robIdx.flag | input | 1 | 后端重定向相关信息 |
|
||||
| | io.redirect.bits.robIdx.value | input | 8 | 后端重定向相关信息 |
|
||||
| | io.redirect.bits.level | input | 1 | 后端重定向相关信息 |
|
||||
| enq | io.enq.canAccept | output | 1 | Lq能否接收派遣指令 |
|
||||
| | io.enq.sqcanAccept | input | 1 | sq能否接收派遣至零 |
|
||||
| | io.enq.needAlloc_0~5 | input | 1 | |
|
||||
| | io.enq.req_0~5.valid | input | 1 | 入队请求的有效信号 |
|
||||
| | io.enq.req_0~5.bits.robIdx.flag | input | 1 | 入队请求ROB指针的flag |
|
||||
| | io.enq.req_0~5.bits.robIdx.value | input | 8 | 入队请求ROB指针的value |
|
||||
| | io.enq.req_0~5.bits.lqIdx.value | input | 7 | 入队请求lqidx的value |
|
||||
| | io.enq.req_0~5.bits.numLsElem | input | 5 | 1. 向量寄存器的总位宽为128位,每个向量元素的大小为8位,因此每个向量寄存器可以存储16个,numLsElem表示向量寄存器中元素的个数,因此位宽为5。 2. 如果是标量值零,numLsElem的值恒为5‘b1 3. 如果是向量指令,每个端口的numLsElem的最大值为[16 2 2 2 2 2] |
|
||||
| ldin | io.ldin_0/1/2.valid | input | 1 | load写回到loadqueue的信息有效 |
|
||||
| | io.ldin_0/1/2.bits.uop.cf.exceptionVec_3/4/5/13/21 | input | 1 | Load写回到流水线的指令发生异常 |
|
||||
| | io.ldin_0/1/2.bits.uop.robIdx_flag | input | 1 | load写回lq指令的rob指针的flag |
|
||||
| | io.ldin_0/1/2.bits.uop.robIdx_value | input | 8 | load写回lq指令的rob指针的value |
|
||||
| | io.ldin_0/1/2.bits.uop.lqIdx.value | input | 7 | load写回lq指令的lq指针的value |
|
||||
| | io.ldin_0/1/2.bits.miss | input | 1 | Load写回到Lq的指令发生cacheMiss |
|
||||
| | io.ldin_0/1.bits.tlbMiss | input | 1 | Load写回到Lq的指令发生tlbMiss |
|
||||
| | io.ldin_0/1/2.bits.mmio | input | 1 | Load写回到Lq的指令是MMIO指令 |
|
||||
| | io.ldin_0/1/2.bits.isPrefetch | input | 1 | 指令为预取操作,预取分为软件预取和硬件预取 |
|
||||
| | io.ldin_0/1/2.bits.isHWPrefetch | input | 1 | 指令为硬件预取 |
|
||||
| | io.ldin_0/1/2.bits.dcacheRequireReplay | input | 1 | Load写回到Lq的指令需要replay |
|
||||
| | io.ldin_0/1/2.bits.rep.info.cause_0~9 | input | 1 | Load写回到Lq的指令需要replay的原因: =0:st-ld violention predirect =1:tlb miss =2:st-ld forward =3:dcache replay =4:dcache miss =5:wpu predict fail =6:dcache bank conflict =7:RAR queue nack =8:RAW queue nack =9:st-ld violention |
|
||||
| | io.ldin_0/1/2.bits.data_wen_dup_1 | input | 1 | uop信息的写入使能信号 |
|
||||
| ldWbPtr | io.ldWbPtr.flag | output | 1 | writeback指针的flag |
|
||||
| | io.ldWbPtr.value | output | 7 | writeback指针的value |
|
||||
| lqEmpty | io.lqEmpty | output | 1 | Lq是否空 |
|
||||
| lqDeq | io.lqDeq | output | 3 | 出队表项数量 |
|
||||
| lqCancelCnt | io.lqCancelCnt | output | 7 | 后端发生重定向时取消的load数量 |
|
||||
|
|
@ -0,0 +1,314 @@
|
|||
---
|
||||
title: StoreQueue
|
||||
linkTitle: StoreQueue
|
||||
weight: 12
|
||||
---
|
||||
|
||||
**本文档参考[香山LSQ设计文档](https://github.com/OpenXiangShan/XiangShan-Design-Doc/tree/master/docs/memblock/LSU/LSQ)写成**
|
||||
|
||||
本文档撰写的内容截至[ca892e73]
|
||||
|
||||
请注意,本文档撰写的测试点仅供参考,如能补充更多测试点,最终获得的奖励可能更高!
|
||||
|
||||
# StoreQueue 简介
|
||||
|
||||
StoreQueue是一个队列,用来装所有的 store 指令,功能如下:
|
||||
|
||||
- 在跟踪 store 指令的执行状态
|
||||
|
||||
- 存储 store 的数据,跟踪数据的状态(是否到达)
|
||||
|
||||
- 为load提供查询接口,让load可以forward相同地址的store
|
||||
|
||||
- 负责 MMIO store和NonCacheable store的执行
|
||||
|
||||
- 将被 ROB 提交的 store 写到 sbuffer 中
|
||||
|
||||
- 维护地址和数据就绪指针,用于LoadQueueRAW的释放和LoadQueueReplay的唤醒
|
||||
|
||||
store进行了地址与数据分离发射的优化,即 StoreUnit 是 store 的地址发射出来走的流水线,StdExeUnit 是 store 的数据发射出来走的流水线,是两个不同的保留站,store 的数据就绪了就可以发射到 StdExeUnit,store 的地址就绪了就可以发射到 StoreUnit。
|
||||
|
||||
## 整体框图
|
||||
|
||||
<div>
|
||||
<center>
|
||||
<img src="../StoreQueue_structure.svg"
|
||||
alt="StoreQueue结构示意图"
|
||||
style="zoom:100%"/>
|
||||
<br>
|
||||
图1:StoreQueue结构示意图<br><br>
|
||||
</center>
|
||||
</div>
|
||||
|
||||
StoreQueue最多可以存放64条指令,**store queue 中重要的状态位有:**
|
||||
|
||||
- allocated:RS在storeQueue队列有空闲时,会设置这个entry的allocated状态,开始记录这条store 的生命周期。同时发射到StoreUnit/ StdExeUnit 2条流水。当这条store指令被提交到Sbuffer时,allocated状态被清除。
|
||||
- addrvalid:在StoreUnit的S1更新,表示是否已经经过了地址转换得到物理地址,用于 load forward 检查时的 cam 比较。
|
||||
- datavalid:在StdExeUnit 的S1更新,表示store 的数据是否已经被发射出来,是否已经可用
|
||||
- committed:在store 是否已经被 ROB commit 了
|
||||
- pending:在StoreUnit的S2更新,在这条 store 是否是 MMIO 空间的 store,主要是用于控制 MMIO 的状态机
|
||||
- mmio:在StoreUnit的S2更新,这条 store 是否是 MMIO 空间的 store,主要是用于控制对 sbuffer 的写
|
||||
|
||||
## 非对齐store指令
|
||||
|
||||
StoreQueue支持处理非对齐的Store指令,每一个非对齐的Store指令占用一项,并在写入dataBuffer对地址和数据对齐后写入。
|
||||
|
||||
## 向量store指令
|
||||
|
||||
如图2所示,StoreQueue会给向量store指令预分配一些项。
|
||||
|
||||
StoreQueue通过vecMbCommit控制向量store的提交:
|
||||
|
||||
1. 针对每个 store,从反馈向量 fbk 中获取相应的信息。
|
||||
|
||||
2. 判断该 store 是否符合提交条件(valid 且标记为 commit 或 flush),并且检查该 store 是否与 uop(i) 对应的指令匹配(通过 robIdx 和 uopIdx)。只有当满足所有条件时,才会将该 store 标记为提交。判断VecStorePipelineWidth内是否有指令满足条件,满足则判断该向量store提交,否则不提交。
|
||||
|
||||
3. 特殊情况处理(跨页 store 指令):
|
||||
|
||||
在特殊情况下(当 store 跨页且 storeMisalignBuffer 中有相同的 uop),如果该 store 符合条件`io.maControl.toStoreQueue.withSameUop`,会强制将 vecMbCommit设置为 true,表示该 store 无论如何都已提交。
|
||||
|
||||
<div>
|
||||
<center>
|
||||
<img src="../StoreQueue_Vector.svg"
|
||||
alt="向量store指令示意图"
|
||||
style="zoom:100%"/>
|
||||
<br>
|
||||
图2:向量store指令<br><br>
|
||||
</center>
|
||||
</div>
|
||||
|
||||
|
||||
# 功能简介
|
||||
|
||||
<mrs-functions>
|
||||
|
||||
## 模块功能说明
|
||||
|
||||
### 功能1:store指令请求入队
|
||||
|
||||
1. StoreQueue 每次最多会有 2 个 entry 入队,通过入队指针 enqPtrExt 控制。在 dispatch 阶段最多可以分配2个 entry,指针每次右移 1 位或 2 位。
|
||||
|
||||
2. 通过比较入队指针 enqPtrExt 和出队指针 deqPtrExt 得出已经在队列中有效 entry。只有空闲的 entry 大于需要请求入队的指令时才会分配 entry 入队。
|
||||
|
||||
3. 入队时设置 entry 的状态位 allocated 为 true,其他状态位都为 false。
|
||||
|
||||
### 功能2:指令的出队
|
||||
|
||||
1. StoreQueue 每次最多会有2个 entry 出队释放,通过输出指针 deqPtrExt 控制,每次指针右移一位或 2 位。
|
||||
|
||||
2. STQ 出队的触发信号是isbuffer(i).fire延后一拍的信号,因为 sbuffer 的写动作要用 2 拍完成,在 sbuffer 写完成之前 entry 不释放可以继续 forward 数据。
|
||||
|
||||
### 功能3:从store的地址流水线写回结果
|
||||
|
||||
store 的地址从保留站发出来后会经过 StoreUnit 流水线,通过lsq/lsq_replenish总线接口在S1/S2把地址信息更新到store queue 中:
|
||||
|
||||
1. 在store流水线s1阶段,获得 DTLB hit/miss 的信息, 以及指令的虚拟地址vaddr和物理地址paddr
|
||||
|
||||
2. 在store流水线s2阶段,获得 mmio/pmp 信息,以及是否是mmio地址空间操作等信息
|
||||
|
||||
### 功能4:接收 store 的数据到STQ 的Datamodule
|
||||
|
||||
store 的数据是从与地址不同的保留站发出来的后经过`StdExeUnit`流水线,通过`storeDataIn`接口在S0/S1把数据写到对应的entry的`datamodule`里:
|
||||
|
||||
1. S0:给`datamodule`发写请求
|
||||
|
||||
2. S1:写入数据到`datamodule`同时更新 entry 的`datavalid`属性为True,接收 store 的mask到STQ 的`Datamodule`
|
||||
|
||||
store 的地址从保留站发出来之后会经过`StoreUnit`流水线,`s0_mask_out`在S0把地址中的mask信息更新到对应entry的`datamodule`里。
|
||||
|
||||
### 功能5:为 load 提供 forward 查询
|
||||
|
||||
1. load 需要查询 store queue 来找到在它之前相同地址的与它最近的那个 store 的数据。
|
||||
- 查询总线(`io.forwrd.sqIdx`) 和 StoreQueue 的出栈指针比较,找出所有比 load 指令老的 storeQueue 中的 entry。以 flag 相同或不同分为2种情况:
|
||||
|
||||
(1)same flag-> older Store范围是 (tail, sqIdx),如图3(a)所示
|
||||
|
||||
(2)different flags-> older Store范围是(tail, VirtualLoadQueueSize) +(0, sqIdx),如图3(b)所示
|
||||
|
||||
<div>
|
||||
<center>
|
||||
<img src="../StoreQueue_Forward_Mask.svg"
|
||||
alt="StoreQueue前递范围生成"
|
||||
style="zoom:100%"/>
|
||||
<br>
|
||||
图3:StoreQueue前递范围生成<br><br>
|
||||
</center>
|
||||
</div>
|
||||
|
||||
2. 查询总线用va 和pa同时查询,如果发现物理地址匹配但是虚拟地址不匹配;或者虚拟地址匹配但是物理地址不匹配的情况就需要将那条 load 设置为 replayInst,等 load 到 ROB head 后replay。
|
||||
|
||||
3. 如果只发现一笔 entry 匹配且数据准备好,则直接 forward
|
||||
|
||||
4. 如果只发现一笔 entry 匹配且数据没有准备好,就需要让保留站负责重发
|
||||
|
||||
5. 如果发现多笔匹配,则选择最老的一笔 store forward,StoreQueue以1字节为单位,采用树形数据选择逻辑,如图4
|
||||
|
||||
<div>
|
||||
<center>
|
||||
<img src="../StoreQueue_Forward.svg"
|
||||
alt="StoreQueue前递数据选择"
|
||||
style="zoom:100%"/>
|
||||
<br>
|
||||
图4:StoreQueue前递数据选择<br><br>
|
||||
</center>
|
||||
</div>
|
||||
|
||||
6. store 指令能被 load forward的条件:
|
||||
- allocated:这条 store 还在 store queue 内,还没有写到 sbuffer
|
||||
- datavalid:这条 store 的数据已经就绪
|
||||
- addrvalid:这条 store 已经完成了虚实地址转换,得到了物理地址
|
||||
|
||||
7. SSID (Store-Set-ID) 标记了之前 load 预测执行失败历史信息,如果当前 load 命中之前历史中的SSID,会等之前所有 older 的 store 都执行完;如果没有命中就只会等pa相同的 older Store 执行完成。
|
||||
|
||||
### 功能6:MMIO与NonCacheable Store指令
|
||||
|
||||
- **MMIO Store指令执行**:
|
||||
|
||||
1. MMIO 空间的 store 也只能等它到达 ROB 的 head 时才能执行,但是跟 load 稍微有些不同,store 到达 ROB 的 head 时,它不一定位于 store queue 的尾部,有可能有的 store 已经提交,但是还在 store queue 中没有写入到 sbuffer,需要等待这些 store 写到 sbuffer 之后,才能让这条 MMIO 的 store 去执行。
|
||||
|
||||
2. 利用一个状态机去控制MMIO的store执行
|
||||
|
||||
- s_idle:空闲状态,接收到MMIO的store请求后进入到s_req状态;
|
||||
|
||||
- s_req:给MMIO通道发请求,请求被MMIO通道接受后进入s_resp状态;
|
||||
|
||||
- s_resp:MMIO通道返回响应,接收后记录是否产生异常,并进入到 s_wb 状态
|
||||
|
||||
- s_wb:将结果转化为内部信号,写回给 ROB,成功后,如果有异常,则进入s_idle, 否则进入到 s_wait 状态
|
||||
|
||||
- s_wait:等待 ROB 将这条 store 指令提交,提交后重新回到 s_idle 状态
|
||||
|
||||
- **NonCacheable Store指令执行**:
|
||||
|
||||
1. NonCacheable空间的store指令,需要等待上一个NonCacheable Store指令提交之后,才能从StoreQueue按序发送请求
|
||||
|
||||
2. 利用一个状态机去控制NonCacheable的store执行
|
||||
|
||||
- nc_idle:空闲状态,接收到NonCacheable的store请求后进入到nc_req状态;
|
||||
|
||||
- nc_req:给NonCacheable通道发请求,请求被NonCachable通道接受后, 如果启用uncacheOutstanding功能,则进入nc_idle,否则进入nc_resp状态;
|
||||
|
||||
- nc_resp:接受NonCacheable通道返回响应,并进入到nc_idle状态
|
||||
|
||||
### 功能7:store指令提交以及写入SBuffer
|
||||
|
||||
StoreQueue采用提前提交的方式进行提交。
|
||||
|
||||
- **提前提交规则**:
|
||||
|
||||
1. 检查进入提交阶段的条件
|
||||
|
||||
(1)指令有效。
|
||||
|
||||
(2)指令的ROB对头指针不超过待提交指针。
|
||||
|
||||
(3)指令不需要取消。
|
||||
|
||||
(4)指令不等待Store操作完成,或者是向量指令
|
||||
|
||||
2. 如果是CommitGroup的第一条指令, 则
|
||||
|
||||
(1)检查MMIO状态: 没有MMIO操作或者有MMIO操作并且MMIO store以及提交。
|
||||
|
||||
(2)如果是向量指令,需满足vecMbCommit条件。
|
||||
|
||||
3. 如果不是CommitGroup的第一条指令,则:
|
||||
|
||||
(1)提交状态依赖于前一条指令的提交状态。
|
||||
|
||||
(2)如果是向量指令,需满足vecMbCommit条件。
|
||||
|
||||
提交之后可以按顺序写到 sbuffer, 先将这些 store 写到 dataBuffer 中,dataBuffer 是一个两项的缓冲区(0,1通道),用来处理从大项数 store queue 中的读出延迟。只有0通道可以编写未对齐的指令,同时为了简化设计,即使两个端口出现异常,但仍然只有一个未对齐出队。
|
||||
|
||||
- **写入sbuffer的过程**:
|
||||
|
||||
1. 写入有效信号生成
|
||||
|
||||
2. 0通道指令存在非对齐且跨越16字节边界时:
|
||||
|
||||
(1) 0通道的指令已分配和提交
|
||||
|
||||
(2) dataBuffer的0,1通道能同时接受指令,
|
||||
|
||||
(3) 0通道的指令不是向量指令,并且地址和数据有效;或者是向量且vsMergeBuffer以及提交。
|
||||
|
||||
(4) 没有跨越4K页表;或者跨越4K页表但是可以被出队,并且1)如果是0通道:允许有异常的数据写入; 2)如果是1通道:不允许有异常的数据写入。
|
||||
|
||||
(5) 之前的指令没有NonCacheable指令,如果是第一条指令,自身不能是Noncacheable指令
|
||||
3. 否则,需要满足:
|
||||
|
||||
(1) 指令已分配和提交。
|
||||
|
||||
(2) 不是向量且地址和数据有效,或者是向量且vsMergeBuffer以及提交。
|
||||
|
||||
(3) 之前的指令没有NonCacheable和MMIO指令,如果是第一条指令,自身不能是Noncacheable和MMIO指令。
|
||||
|
||||
(4) 如果未对齐store,则不能跨越16字节边界,且地址和数据有效或有异常
|
||||
|
||||
- **地址和数据生成**:
|
||||
|
||||
1. 地址拆分为高低两部分:
|
||||
|
||||
(1) 低位地址:8字节对齐地址
|
||||
|
||||
(2) 高位地址:低位地址加上8偏移量
|
||||
|
||||
2. 数据拆分为高低两部分:
|
||||
|
||||
(1) 跨16字节边界数据:原始数据左移地址低4位偏移量包含的字节数
|
||||
|
||||
(2) 低位数据:跨16字节边界数据的低128位;
|
||||
|
||||
(3) 高位数据:跨16字节边界数据的高128位;
|
||||
|
||||
3. 写入选择逻辑:
|
||||
|
||||
如果dataBuffer能接受非对齐指令写入,通道0的指令是非对齐并且跨越了16字节边界,则检查:
|
||||
|
||||
(1) 是否跨4K页表同时跨4K页表且可以出队: 通道0使用低位地址和低位数据写入dataBuffer; 通道1使用StoreMisaligBuffer的物理地址和高位数据写入dataBuffer
|
||||
|
||||
(2) 否则: 通道0使用低位地址和低位数据写入dataBuffer; 通道1使用高位地址和高位数据写入dataBuffer
|
||||
|
||||
(3) 如果通道指令没有跨越16字节并且非对齐,则使用16字节对齐地址和对齐数据写入dataBuffer
|
||||
|
||||
(4) 否则,将原始数据和地址写给dataBuffer
|
||||
|
||||
### 功能8:强制刷新sbuffer
|
||||
|
||||
StoreQueue采用双阈值的方法控制强制刷新Sbuffer:上阈值和下阈值。
|
||||
|
||||
1. 当StoreQueue的有效项数大于上阈值时, StoreQueue强制刷新Sbuffer
|
||||
2. 直到StoreQueue的有效项数小于下阈值时,停止刷新Sbuffer。
|
||||
|
||||
</mrs-functions>
|
||||
|
||||
## 接口说明
|
||||
|
||||
| name | description |
|
||||
| ------------------ | ------------------------------------------------------------ |
|
||||
| enq | 接收来自外部模块的信息,包含入队请求、控制信号等 |
|
||||
| brqRedirect | 分支重定向信号 |
|
||||
| vecFeedback | 向量反馈信息 |
|
||||
| storeAddrIn | store指令的地址 |
|
||||
| storeAddrInRe | store指令的地址,用于处理MMIO 和异常情况 |
|
||||
| storeDataIn | store指令的数据 |
|
||||
| storeMaskIn | 传递store掩码,从保留站(RS)发送到 Store Queue(SQ)。store掩码通常用于指示哪些字节在store操作中是有效的。 |
|
||||
| sbuffer | 存储已提交的 Store 请求到sbuffer |
|
||||
| uncacheOutstanding | 指示是否有未完成的uncached请求 |
|
||||
| cmoOpReg | 发送缓存管理操作请求 |
|
||||
| cmoOpResp | 接收缓存管理操作的响应 |
|
||||
| mmioStout | 写回uncache的存储操作的结果 |
|
||||
| forward | 查询forwarding信息 |
|
||||
| rob | 接收来自 ROB 的信号或数据 |
|
||||
| uncache | 发送数据或信号给 uncache 模块 |
|
||||
| flushSbuffer | 冲刷sbuffer缓冲区 |
|
||||
| sqEmpty | 标识store queue为空 |
|
||||
| stAddrReadySqPtr | 指向当前准备好地址的 store 指令 |
|
||||
| stAddrReadyVec | 向量中对应 store 指令的地址是否已经准备好 |
|
||||
| stDataReadySqPtr | 指向当前准备好数据的 store 指令 |
|
||||
| stDataReadyVec | 向量中对应 store 指令的数据是否已经准备好 |
|
||||
| stIssuePtr | 跟踪当前发出的store请求 |
|
||||
| sqCancelCnt | 指示在store queue中可以被取消的请求数量 |
|
||||
| sqDeq | 当前store queue中出队的请求位置 |
|
||||
| force_write | 是否强制写入存储操作 |
|
||||
| maControl | 与存储管理缓冲区(MA)进行控制信号的交互 |
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
title: LSQ
|
||||
linkTitle: LSQ
|
||||
weight: 12
|
||||
---
|
||||
|
||||
**本文档参考[香山LSQ设计文档](https://github.com/OpenXiangShan/XiangShan-Design-Doc/tree/master/docs/memblock/LSU/LSQ)写成**
|
||||
|
||||
本文档撰写的内容截至[ca892e73]
|
||||
|
||||
请注意,本文档撰写的测试点仅供参考,如能补充更多测试点,最终获得的奖励可能更高!
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
title: Shield-XS:基于Bitmap的安全隔离机制
|
||||
linkTitle: 香山bitmap 隔离模块
|
||||
#menu: {main: {weight: 20}}
|
||||
weight: 20
|
||||
---
|
||||
|
||||
{{% pageinfo %}}
|
||||
在香山中,什么是香山bitmap 隔离模块,如何验证?
|
||||
|
||||
Location: MMU-L2TLB-Bitmap
|
||||
{{% /pageinfo %}}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
title: 背景描述
|
||||
description: 解释下为什么需要bitmap 进行隔离
|
||||
categories: [香山 Bitmap 基础设计]
|
||||
tags: [香山, Bitmap, 安全隔离]
|
||||
weight: 1
|
||||
---
|
||||
|
||||
## 背景描述
|
||||
|
||||
自计算机问世以来,数据安全始终是至关重要的议题。在个人计算机时代,数据安全主要集中在单机硬盘等存储设备的安全性上。随着互联网时代的到来,数据安全的范畴扩展到了计算机之间数据传输的安全性,促使了安全传输协议的设计与发展。如今,在云计算时代,用户的大量数据被存储和处理在云端。在云环境中存储、共享和计算的数据面临着多重安全威胁。在云计算环境下,不同类型的负载面临各自的威胁模型包括但不限于:
|
||||
|
||||
计算型负载:恶意软件可能侵入操作系统或固件,攻击 CPU 和内存资源,导致数据泄露或系统性能下降。
|
||||
|
||||
内存型负载:恶意软件或进程可能试图访问内存型负载使用的内存区域,导致数据泄露或篡改。
|
||||
|
||||
存储型负载:存储设备及其 DMA 功能可能被攻击者利用,直接访问或篡改存储设备内存,绕过传统安全检查。
|
||||
|
||||
网络型负载:网络攻击可能利用网络接口控制器(NIC)等网络设备漏洞入侵系统,通过 DMA 功能访问或篡改内存中的敏感数据。
|
||||
|
||||
#### 不同工作负载面临的安全威胁
|
||||
|
||||

|
||||
|
||||
## 威胁模型
|
||||
|
||||
---
|
||||
|
||||
| **类别** | **安全准则** | **描述** |
|
||||
| :------------- | :------------------------------ | :------------------------------------------------------------- |
|
||||
| **内存分配** | 动态分配安全/非安全内存的能力 | 安全内存应根据需求动态分配或释放。 |
|
||||
| **内存机密性** | 内存隔离 | 防止非可信计算基(non-TCB)组件读取可信执行环境(TEE)的内存。 |
|
||||
| | 抵御软件攻击的内存完整性 | 防止软件攻击(如内存重映射、别名攻击、重放攻击、数据破坏等)。 |
|
||||
| **共享内存** | TEE控制与非TCB组件的数据共享 | 防止非TCB代码在未经TEE同意的情况下泄露信息。 |
|
||||
| | TEE控制与其他TEE的数据共DUT 享 | 支持TEE之间安全共享内存的能力。 |
|
||||
| **I/O保护** | 防止非TCB设备通过DMA访问TEE内存 | 禁止未被TCB接纳的外设设备访问TEE内存。 |
|
||||
| | 来自TCB内设备的可信I/O | 通过准入控制将设备绑定到TEE。 |
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
---
|
||||
title: 香山 bitmap 防御原理
|
||||
description: 本节介绍了bitmap 的基础思想和工作原理
|
||||
categories: [香山 bitmap 基础设计]
|
||||
tags: [香山, Bitmap, 基础]
|
||||
weight: 2
|
||||
---
|
||||
|
||||
## 防御原理
|
||||
|
||||
针对日益复杂的负载的安全威胁,香山轻量动态隔离模型为经典负载机密虚拟机和容器设计了一种动态隔离机制,用来保护正在使用中的数据的机密性和完整性。依据不同的应用场景,将负载从安全的维度进行划分,分为安全敏感型负载和非安全敏感型负载。
|
||||
|
||||
安全敏感型负载需要运行在可信执行环境下,用来保护正在使用中数据和代码的机密性和完整性。非安全敏感型负载对数据没有安全性保护需求,执行在开放的执行环境下,保证执行的效率。
|
||||
|
||||
基于bitmap的安全隔离的防御机制,其核心原理是通过可信根在软件层面的标记(Shield-bit)来隔离和保护敏感工作负载(Sensitive Workloads)与普通工作负载(Normal Workloads),从而增强系统的安全性。以下是其防御原理的详细描述:
|
||||
|
||||
### 1. 分类与标记
|
||||
|
||||
**工作负载分类**:系统将工作负载分为两类:
|
||||
|
||||
- **敏感工作负载(Sensitive Workloads)**:需要高安全级别的任务,如机密虚拟机、安全容器的任务(隐私数据的访问、处理)等。
|
||||
|
||||
- **普通工作负载(Normal Workloads)**:常规任务,代表系统中普通的应用程序或进程,安全要求较低。
|
||||
|
||||
**Shield-bit 标记**:通过 “Shield-bit”(**每比特代表 4KB**)对敏感工作负载使用的资源进行标记,依据需此标记动态调整资源访问权限:
|
||||
|
||||
- `Shield-bit = 1` 表示**安全敏感资源**
|
||||
|
||||
- `Shield-bit = 0` 表示**非安全敏感资源**
|
||||
|
||||
### 2. 资源隔离
|
||||
|
||||
**资源池划分**:[图中](#light_model_img)显示资源池中包含混合的敏感和普通工作负载,但通过 Shield-bit 实现逻辑隔离。
|
||||
|
||||
**资源类型保护**:敏感工作负载访问特定资源(如内存、IO设备、中断等)时,Shield-bit 会触发保护机制,防止普通工作负载或恶意程序越权访问。例如:
|
||||
|
||||
1. **内存隔离**:敏感数据的内存区域仅对标记为敏感的工作负载可访问。
|
||||
|
||||
2. **设备与中断保护**:关键外设或中断仅允许敏感工作负载调用。
|
||||
|
||||
### 3. Bypass 机制
|
||||
|
||||
**Bypass Shield-bit**:在单向隔离的情况下(仅对普通负载的资源访问作限制),安全敏感负载可绕过 Shield-bit 的机制直接访问资源提升性能。
|
||||
|
||||
### 4. 防御目标
|
||||
|
||||
**防止横向渗透**:攻击者通过普通工作负载漏洞无法访问敏感资源。
|
||||
|
||||
**最小权限原则**:每个工作负载仅能访问其必需的资源,降低攻击面。
|
||||
|
||||
**动态安全调整**:根据工作负载的敏感程度动态切换保护状态。
|
||||
|
||||
### 5. 技术实现
|
||||
|
||||
**硬件支持**:依赖内存管理单元(MMU)的扩展功能。
|
||||
|
||||
**软件协同**:由可信计算基负责标记管理和资源调度。
|
||||
|
||||
<span id="light_model_img"/>
|
||||
|
||||
#### 香山Shield-XS 轻量隔离模型
|
||||
|
||||

|
||||
|
||||
### 6. 核心工作流程
|
||||
|
||||
#### Shield-XS 隔离模型工作流程
|
||||
|
||||

|
||||
|
||||
**A. 启动**
|
||||
|
||||
系统初始化,加载固件(Firmware TCB)和可信执行环境(TEE TCB)。
|
||||
|
||||
开启Bitmap安全检测功能。
|
||||
|
||||
**B. 配置**
|
||||
|
||||
设定安全策略,例如定义受保护资源(内存、IO、中断等)访问权限。
|
||||
|
||||
TEE-TCB 依据应用需求配置不同资源的安全/非安全属性。
|
||||
|
||||
**C. 检测**
|
||||
|
||||
硬件监控访问行为,识别异常或非法访问请求。
|
||||
|
||||
通过 **Bitmap** 或其他机制标记不信任的访问行为。
|
||||
|
||||
**D. 故障处理**
|
||||
|
||||
当检测到违规操作(如未授权的内存访问)时,触发安全机制。
|
||||
|
||||
### 7. 关键安全机制
|
||||
|
||||
支持对所有地址映射类资源(内存、I/O、中断)的动态隔离。
|
||||
|
||||
资源隔离支持按需分配的,而非静态划分(区别于PMP)。
|
||||
|
||||
使用位图(Bitmap)标记资源的信任状态,快速过滤不信任的访问请求。
|
||||
|
||||
### 8. 技术特点
|
||||
|
||||
**灵活性**
|
||||
动态隔离允许根据运行时需求调整安全策略,适应不同场景。
|
||||
|
||||
**细粒度控制**
|
||||
支持多种资源类型(内存、I/O、中断)的精细化权限管理。
|
||||
|
||||
**轻量级检测**
|
||||
通过Bitmap等高效数据结构实现低开销的安全监控。
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
---
|
||||
title: bitmap的工作流程
|
||||
description: bitmap的工作流程是怎样的?
|
||||
categories: [香山 Bitmap 基础设计]
|
||||
tags: [香山, Bitmap, 基础]
|
||||
weight: 3
|
||||
---
|
||||
|
||||
## bitmap的工作流程是怎样的?
|
||||
|
||||

|
||||
|
||||
### 1. 识别负载类型
|
||||
|
||||
根据应用场景和安全需求,将负载分为安全敏感型负载和非安全敏感型负载:
|
||||
|
||||
- **安全敏感型负载**:需要在可信执行环境中运行,以保护数据和代码的机密性和完整性。
|
||||
|
||||
- **非安全敏感型负载**:可以在开放的执行环境中运行,主要关注执行效率。
|
||||
|
||||
### 2. 设置隔离属性
|
||||
|
||||
**定义隔离属性**:为不同类型负载设置隔离属性,包括单向隔离和双向隔离。
|
||||
|
||||
- **单向隔离**:允许安全敏感型负载访问非安全敏感型负载的资源,但不允许反向访问。
|
||||
|
||||
- **双向隔离**:安全敏感型负载和非安全敏感型负载之间互相不能访问对方的资源。
|
||||
|
||||
### 3. 划分资源池
|
||||
|
||||
**划分物理内存资源**:将物理内存划分为不同的资源池以支持不同负载的隔离需求。
|
||||
|
||||
- **安全资源池**:分配给安全敏感型负载。
|
||||
|
||||
- **非安全资源池**:分配给非安全敏感型负载。
|
||||
|
||||
### 4. 配置安全属性
|
||||
|
||||
**设置内存页安全状态**:使用Bitmap数据结构标记每个物理内存页的安全状态。
|
||||
|
||||
- **安全页**:标记为1,表示该页属于安全资源池。
|
||||
|
||||
- **非安全页**:标记为0,表示该页属于非安全资源池。
|
||||
|
||||
### 5. 动态分配资源
|
||||
|
||||
**按需分配内存**:根据负载的启动和运行时需求,动态分配相应的内存资源。
|
||||
|
||||
- **安全敏感型负载**:从安全资源池分配内存,并确保其访问受限于安全页。
|
||||
|
||||
- **非安全敏感型负载**:从非安全资源池分配内存,允许其访问非安全页。
|
||||
|
||||
### 6. 隔离检测判断
|
||||
|
||||
**判断隔离类型**:判断当前负载是否需要单向隔离或者双向隔离。
|
||||
|
||||
- **单向隔离**:仅对非安全敏感型负载进行安全性检测,确保其不访问安全资源。
|
||||
|
||||
- **双向隔离**:对所有负载类型进行隔离检测,确保不同负载之间互不干扰。
|
||||
|
||||
### 7. 安全性检测
|
||||
|
||||
**执行访问控制检查**:
|
||||
|
||||
- 对于单向隔离情况,检查非安全敏感型负载是否试图访问安全资源。
|
||||
|
||||
- 对于双向隔离情况,检查所有负载类型是否试图越界访问其他类型的资源。
|
||||
|
||||
### 8. 动态回收资源
|
||||
|
||||
**释放不再使用的资源**:负载执行完毕后,其占用的内存资源被回收并返回到相应的资源池中,以便后续其他负载的使用。
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
title: 香山bitmap 应用场景
|
||||
description: 香山bitmap 应用场景
|
||||
categories: [香山 Bitmap 基础设计]
|
||||
tags: [香山, Bitmap, 基础]
|
||||
weight: 4
|
||||
---
|
||||
|
||||
## 应用场景
|
||||
|
||||
### 1. 机密虚拟机
|
||||
|
||||

|
||||
|
||||
Shield-XS用于普通虚拟机和机密虚拟机的隔离。在机密虚拟机(Confidential VM, CVM)环境中,bitmap 用于标记和隔离安全内存区域。
|
||||
|
||||
普通虚拟机在访问内存时,硬件通过 bitmap 检查机制确保其只能访问被授权的内存区域,从而实现虚拟机之间的内存隔离。
|
||||
|
||||
注:机密虚拟机之间的隔离通过MMU 完成,不需要经过Bitmap 的检查机制。
|
||||
|
||||
CVM Bitmap 的资源的标记和分配由C-SDSM (Confidential Supervisor Domain Security Manager) 完成,Hypervisor 和 CVM 通过TEECall 和 TEEResume 进行安全世界的交互。
|
||||
|
||||

|
||||
|
||||
**TEECall**
|
||||
|
||||
- 从普通世界(Normal World)切换到安全世界(TEE),进入敏感工作负载执行流程。
|
||||
|
||||
**TEEResume**
|
||||
|
||||
- 从 TEE 返回普通世界,恢复 Normal Workloads 的执行。
|
||||
|
||||
### 2. 安全增强容器
|
||||
|
||||

|
||||
|
||||
上图显示了bitmap在容器中的应用场景。安全容器和普通容器都位于可信计算基的保护范围内。在安全容器中运行的应用程序可能包含敏感数据或关键业务逻辑。为了防止这些数据被普通容器或恶意软件访问,采用通过 **bitmap 机制**进行隔离。
|
||||
|
||||
Bitmap 数据结构用于标记哪些物理页属于安全容器的存储空间,通过将对应该容器内存区域的位图数据写为1,确保普通容器无法访问这些存储空间。只有安全容器内的应用程可以访问这些被标记为安全的内存页。
|
||||
|
||||
普通容器中的应用程序可能来自不可信的来源。通过 bitmap 机制,普通容器的内存访问被限制在非安全内存区域。任何试图访问安全内存的请求都会被硬件拦截,并触发Access Fault。
|
||||
|
||||
在上述场景的具体的实施细节中,**<font style="color:#ee0000;">软件模块负责Bitmap管理和配置 , 硬件机制负责Bitmap检查和同步。<font>**
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
title: Shield-XS Bitmap 基础知识
|
||||
description: 本文档介绍了bitmap 安全隔离的基础设计思想,通过阅读本文档,你可以知道为什么需要bitmap, 以及其应用场景
|
||||
categories: [香山 Bitmap 基础设计]
|
||||
tags: [香山, Bitmap, 基础]
|
||||
weight: 1
|
||||
---
|
||||
|
||||
本节将介绍 bitmap 的一些基础知识,有助于理解为什么我们需要 bitmap,本节包含:
|
||||
|
||||
- **背景描述**
|
||||
- **威胁模型**
|
||||
- **防御原理**
|
||||
- **工作流程**
|
||||
- **应用场景**
|
||||
|
||||
#### 术语描述
|
||||
|
||||
| 缩写 | 全名 | 含义 |
|
||||
| ------ | ----------------------------------------------- | -------------------------------------- |
|
||||
| TCB | Trusted Computing Base | 可信计算基,负责底层硬件的安全可信操作 |
|
||||
| TEE | Trusted Execution Environment | 可信执行环境 |
|
||||
| MMU | Memory Management Unit | 内存管理单元 |
|
||||
| RDSM | Root Domain Security Mananger | 根域权限管理器 |
|
||||
| C-SDSM | Confidencial Supervisor Domain Security Manager | 可信监督域 权限管理器 |
|
||||
| APLIC | advanced platform level interrupt controller | 平台级中断控制器 |
|
||||
| LLC | Last Lavel Cache | 末级缓存 |
|
||||
| DMA | Direct Memory Access | 直接内存访问 |
|
||||
| CVM | Confidencial Virtual Machine | 可信虚拟机 |
|
||||
| TLB | Translation Lookaside Buffer | 页表缓存 |
|
||||
| MBMC | machine level bitmap check | bitmap CSR 特殊状态寄存器 |
|
||||
| BMA | Bitmap Adress | Shield Bitmap 专属内存区域基地址 |
|
||||
| CMODE | Confidencial Mode | 开启bitmap后当前模式是否是可信 |
|
||||
| BME | Bitmap Enable | 是否开启 Bitmap |
|
||||
| PTW | Page Table Walker | 页表遍历器 |
|
||||
| HPTW | Hypervisor PTW | 监督域页表遍历器 |
|
||||
| LLPTW | Last Level PTW | 末级页表遍历器 |
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
title: 机器模式Bitmap控制状态寄存器
|
||||
categories: [bitmap 技术细节]
|
||||
tags: [香山, Bitmap, 技术细节]
|
||||
weight: 1
|
||||
---
|
||||
|
||||
## 机器模式 Bitmap控制状态寄存器
|
||||
|
||||
### 基本信息
|
||||
|
||||
| 寄存器名称 | MBMC(Machine BitMap Control) |
|
||||
| ---------- | ------------------------------------------------------------- |
|
||||
| 特权模式 | 机器模式 |
|
||||
| 寄存器编号 | 0xBC2 |
|
||||
| 读写权限 | 可读可写 |
|
||||
| 功能描述 | 用来控制Shield-bitmap 的使能、同步、Shield-XS Bitmap 基地址等 |
|
||||
|
||||
### 字段描述
|
||||
|
||||
| 地址 | 字段 | 描述 |
|
||||
| ---------- | ------------------------ | ------------------------------------------------------------------------------------------------------------ |
|
||||
| **[61:3]** | **BMA (Bitmap Address)** | Shield-XS Bitmap 基地址指定 Bitmap 数据结构在物理内存中的起始地址。 |
|
||||
| 2 | CMODE (Current Mode) | 表明当前执行模式CMODE = 1,表示安全模式CMODE = 0, 表示非安全模式。 |
|
||||
| 1 | BCLEAR (Bitmap Clear) | Shield-XS Bitmap 同步位BCLEAR = 1,表示刷新所有Shield-bit 副本。<br/>**注:实际使用过程,可结合HFence 指令** |
|
||||
| 0 | BME (Bitmap Enable) | Shield-XS 使能位当 BME 置为 1 时,启用 Bitmap 功能。一旦启用,无法关闭,也无法修改 Bitmap 的基地址。 |
|
||||
|
||||
## 数据结构
|
||||
|
||||

|
||||
|
||||
#### Shield-XS Bitmap数据结构
|
||||
|
||||
上图为Shield-XS Bitmap的数据结构,所有的Shield-XS Bitmap权限数据都被存放在一块连续的物理内存区域中。其中基地址是存放Shield-XS Bitmap 数据结构的内存区域的起始物理地址。这个地址可以通过MBMC寄存器中的 BMA字段进行配置。
|
||||
|
||||
Shield-XS Bitmap 数据结构的大小取决于系统内存的大小。每个4k物理页对应一个比特位。一个4k页对应的权限根据其物理地址存放在Shield-XS Bitmap 数据结构中,其位置可以通过基地址加上该4k页物理地址的偏移量计算得出。
|
||||
|
||||
**当BME =1 (开启bitmap) 且CMODE= 0 (当前模式处于 非安全模式)时,会进行bitmap检查。当bitmap 检查 当前4k页 bitmap属性 为1( 安全页面)时,会触发访问错误(access fault)。**
|
||||
|
||||
**当BME =1 (开启bitmap) 且CMODE= 1 (当前模式处于 安全模式)。无论安全还是非安全页面,当前状态都允许访问,因此无需进行bitmap检查。**
|
||||
|
||||
**当BME =0 (关闭bitmap)无需进行bitmap检查。**
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
title: Shield-Bitmap Cache
|
||||
categories: [bitmap 技术细节]
|
||||
tags: [香山, Bitmap, 技术细节]
|
||||
weight: 2
|
||||
---
|
||||
|
||||
## Shield-Bitmap Cache 性能加速
|
||||
|
||||
当访问一个物理地址 (PA) 时,硬件通过查找 Shield-XS Bitmap 数据结构中对应的位置来确定该页是否具有安全属性。
|
||||
|
||||
查找安全属性的物理地址计算公式如下:
|
||||
|
||||
即 **Shield-Bitmap 访问地址 = MBMC.BMA + PA[ XLEN-1 : 12 + log<sub>2</sub>(XLEN/8) ]**
|
||||
|
||||
随后用选出对应该 4KB 页的权限。如果对应比特位为 1 ,则表示该页具有安全属性,只能被安全敏感型负载访问;如果为 0 ,则表示该页不具有安全属性,可以被非安全敏感型负载访问。
|
||||
|
||||
##### Shield-XS 直接从内存中读取安全属性
|
||||
|
||||

|
||||
|
||||
开启Shield-XS 隔离之后,CPU 内部发起的任何一笔访存操作,都需要对标记安全属性的存储空间发起访问,从内存中获取安全属性的延迟过长,使得系统的性能变差。
|
||||
|
||||
为了Shield-XS 隔离带来的性能损失,利用了程序的局部性,增加了Shield-Bitmap Cache,
|
||||
当任何一笔访存需要获取安全属性时,优先从Shield-Bitmap Cache 中读取,只有Shield-Bitmap Cache Miss时,才会发起访存请求。
|
||||
|
||||
##### Shield-XS优先从Shield-BitMap Cache中读取安全属性
|
||||
|
||||

|
||||
|
|
@ -0,0 +1,195 @@
|
|||
---
|
||||
title: Shield-Bitmap 地址翻译
|
||||
categories: [bitmap 技术细节]
|
||||
tags: [香山, Bitmap, 技术细节]
|
||||
weight: 4
|
||||
---
|
||||
|
||||
# 虚拟化两阶段地址翻译流程
|
||||
|
||||
bitmap和MMU-L2TLB耦合,会在虚拟化两阶段地址走表翻译完成后才进行隔离检查。如果bitmap检查没有通过,就向MMU-L1TLB 发送af。因此对L1TLB以及其上面的模块来说,bitmap是透明的。所以,在进一步介绍前,需要先了解MMU处理虚拟化两阶段地址的流程。
|
||||
|
||||
## 基本概念
|
||||
|
||||
- **两阶段翻译**:Guest Virtual Address (GVA) → Guest Physical Address (GPA) → Host Physical Address (HPA)
|
||||
- **关键寄存器** :
|
||||
- **hgatp** :控制G-stage(客户机阶段)页表根地址
|
||||
- **vsatp** :控制VS-stage(虚拟化监督模式阶段)页表根地址
|
||||
|
||||
**1. VAPT (Virtual Address Protection and Translation)**
|
||||
|
||||
**作用** :管理客户机虚拟地址(GVA)到客户机物理地址(GPA)的第一阶段翻译(VS-stage)。
|
||||
|
||||
| **字段名** | **位宽** | **描述** |
|
||||
| ------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **MODE** | 4 | 页表模式:<br/> <ul><li>`0`: 关闭翻译</li> <li>`1`: Sv32</li> <li>`8`: Sv39</li> <li>`9`: Sv48</li> <li>`10`: Sv57</li></ul> |
|
||||
| **ASID** | 16 | 地址空间标识符(Address Space ID),隔离不同客户机的地址空间 |
|
||||
| **PPN** | 44 | 物理页号(Physical Page Number),指向VS-stage页表的根页表地址 |
|
||||
| **RESERVED** | 8 | 保留位,必须写0 |
|
||||
| **G** | 1 | 全局映射标志(Global bit),若为1则忽略ASID匹配 |
|
||||
|
||||
---
|
||||
|
||||
**2. SVAPT (Supervisor Virtual Address Protection and Translation)**
|
||||
|
||||
**作用** :在Hypervisor模式下管理宿主机虚拟地址(HVA)到宿主机物理地址(HPA)的翻译(HS-stage)。
|
||||
|
||||
| **字段名** | **位宽** | **描述** |
|
||||
| ------------ | -------- | -------------------------------------------------------------------------------------------------------- |
|
||||
| **MODE** | 4 | 页表模式:<br/> <ul><li>`0`: 关闭翻译</li> <li>`1`: Sv32</li> <li>`8`: Sv39</li> <li>`9`: Sv48</li></ul> |
|
||||
| **ASID** | 16 | 宿主机地址空间标识符 |
|
||||
| **PPN** | 44 | 指向HS-stage页表的根页表地址 |
|
||||
| **V** | 1 | 虚拟化启用标志: <br/> <ul><li>`1`: 启用两阶段翻译(需配合H-extension)</li></ul> |
|
||||
| **RESERVED** | 7 | 保留位 |
|
||||
|
||||
---
|
||||
|
||||
**3. HGAPT (Hypervisor Guest Address Protection and Translation)**
|
||||
|
||||
**作用** :控制客户机物理地址(GPA)到宿主机物理地址(HPA)的第二阶段翻译(G-stage)。
|
||||
|
||||
| **字段名** | **位宽** | **描述** |
|
||||
| ------------ | -------- | ------------------------------------------------------------------------------------------------ |
|
||||
| **MODE** | 4 | G-stage页表模式:<br/> <ul><li>`0`: 关闭翻译</li> <li>`3`: Sv32x4</li> <li>`4`: Sv48x4</li></ul> |
|
||||
| **VMID** | 14 | 虚拟机标识符(Virtual Machine ID),隔离不同虚拟机的G-stage页表 |
|
||||
| **PPN** | 44 | 指向G-stage页表的根页表地址 |
|
||||
| **GST** | 1 | 客户机软件TLB失效指令使能: <br/> <ul><li>`1`: **允许客户机执行 sfence.vma**</li></ul> |
|
||||
| **RESERVED** | 5 | 保留位 |
|
||||
|
||||
---
|
||||
|
||||
**关键差异总结**
|
||||
|
||||
| **寄存器** | **控制阶段** | **核心功能** | **特权级** |
|
||||
| ---------- | ------------ | ----------------------------- | -------------- |
|
||||
| VAPT | VS-stage | GVA→GPA翻译(客户机视角) | VS-mode |
|
||||
| SVAPT | HS-stage | HVA→HPA翻译(宿主机视角) | HS-mode |
|
||||
| HGAPT | G-stage | GPA→HPA翻译(硬件辅助虚拟化) | M-mode/HS-mode |
|
||||
|
||||
- **特权级** :
|
||||
- **VS-stage** :由Hypervisor管理,处理客户机虚拟地址
|
||||
- **G-stage** :由客户机OS管理,处理客户机物理地址
|
||||
|
||||
## **翻译过程**
|
||||
|
||||
**第一阶段:VS-stage (GVA → GPA)**
|
||||
|
||||
| **VPN部分** | **VS-stage页表** | **依赖的G-stage页表** | **说明** |
|
||||
| ----------- | ---------------- | ------------------------------- | ----------------------------- |
|
||||
| VPN[3] | VS-L3 | 通过**hgatp**访问G-L3 | 最高级页表,需G-stage辅助查询 |
|
||||
| VPN[2] | VS-L2 | 通过**hgatp**访问G-L2/G-L1/G-L0 | 中间级页表,需G-stage多级支持 |
|
||||
| VPN[1] | VS-L1 | 通过**hgatp**访问G-L1/G-L0 | 次末级页表 |
|
||||
| VPN[0] | VS-L0 | 通过**hgatp**访问G-L0 | 最后级页表,直接指向GPA |
|
||||
|
||||
---
|
||||
|
||||
**第二阶段:G-stage (GPA → HPA)**
|
||||
|
||||
| **页表层级** | **作用** |
|
||||
| ------------ | ------------------------------- |
|
||||
| G-L3 | 顶级页表,由**hgatp**寄存器指向 |
|
||||
| G-L2 | 中间级页表 |
|
||||
| G-L1 | 次末级页表 |
|
||||
| G-L0 | 最后级页表,与offset拼接生成HPA |
|
||||
|
||||
- 任一阶段页表访问失败会触发异常:
|
||||
- VS-stage异常 → Hypervisor处理
|
||||
- G-stage异常 → 客户机Page Fault
|
||||
|
||||

|
||||
|
||||
#### L2TLB table walker 查表流程说明:
|
||||
|
||||
1. 请求首先进入PageCache查询第一阶段页表
|
||||
2. 若第一阶段命中:
|
||||
- 直接由PageTableWalker处理第二阶段
|
||||
3. 若第一阶段未命中:
|
||||
- 根据命中级别选择PageTableWalker或LastLevelPageTableWalker
|
||||
4. 第二阶段处理:
|
||||
- 先在PageCache中查询
|
||||
- 未命中时转交HypervisorPageTableWalker
|
||||
- 翻译结果返回PageCache后完成流程
|
||||
|
||||
#### 关键路径:
|
||||
|
||||
- 快路径:PageCache(阶段1)→PageTableWalker→PageCache(阶段2)
|
||||
- 慢路径:PageCache(阶段1)→LastLevelPTW/PTW→PageCache(阶段2)→HypervisorPTW
|
||||
|
||||
## MMU-L2TLB 地址翻译流程
|
||||
|
||||

|
||||
|
||||
**1. L1TLB向L2TLB发送请求**
|
||||
|
||||
- **非两阶段翻译请求**:
|
||||
- 首先访问PageCache。
|
||||
- 若命中叶子节点,直接返回结果给L1TLB。
|
||||
- 若未命中叶子节点:
|
||||
- 根据PageCache命中的页表等级,结合PageTableWalker (PTW) 和 LastLevelPageTableWalker (LLPTW) 的空闲情况:
|
||||
- 进入PTW、LLPTW 或 Miss Queue。
|
||||
- **两阶段地址翻译请求**:
|
||||
- PageCache每次只能处理一个查询请求。
|
||||
- 首先查询第一阶段的页表:
|
||||
- 若第一阶段命中,请求发送给PTW进行第二阶段的翻译。
|
||||
- 若第一阶段未命中:
|
||||
- 根据命中的页表级别,发送给PTW或LLPTW,在这两个模块中完成第二阶段的翻译。
|
||||
|
||||
- **page Cache 访问流程:**
|
||||
|
||||

|
||||
|
||||
**2. PTW和LLPTW的第二阶段翻译**
|
||||
|
||||
- PTW和LLPTW发送的第二阶段翻译请求会先发送到PageCache查询:
|
||||
- 若命中,PageCache直接返回结果给对应模块(PTW或LLPTW)。
|
||||
- 若未命中,发送给HypervisorPageTableWalker (HPTW) 进行翻译,结果直接返回给PTW或LLPTW
|
||||
|
||||
**3. PageTableWalker (PTW) 处理**
|
||||
|
||||
- PTW同时只能处理一个请求,进行HardwarePageTableWalk:
|
||||
- 访问内存中前两级页表(不访问4KB页表)。
|
||||
- 可能的结果:
|
||||
- 访问到2MB或1GB的叶子节点。
|
||||
- 发生Pagefault或Access fault。
|
||||
- 以上情况直接返回给L1TLB。
|
||||
- 否则,请求送往LLPTW访问最后一级(4KB)页表。
|
||||
|
||||
PTW 处理流程
|
||||
|
||||

|
||||
|
||||
**4. HypervisorPageTableWalker (HPTW) 处理**
|
||||
|
||||
- HPTW每次只能处理一个请求:
|
||||
- LLPTW中第二阶段翻译的请求串行发送给HPTW。
|
||||
- HPTW访问可能触发Pagefault或Access fault,返回给PTW或LLPTW,最终返回给L1TLB。
|
||||
|
||||

|
||||
|
||||
**5. 内存访问流程**
|
||||
|
||||
- PTW、LLPTW、HPTW均可向内存发送请求访问页表内容。
|
||||
- 物理地址访问内存前需通过PMP和PMA模块检查:
|
||||
- 若发生Access fault,不向内存发送请求。
|
||||
- 请求经过仲裁后,通过TileLink总线向L2Cache发送:
|
||||
- L2Cache访存宽度为512bits,每次返回8项页表。
|
||||
|
||||

|
||||
|
||||
**6. 页表压缩机制**
|
||||
|
||||
- **L2TLB**:
|
||||
- 命中4KB页时,返回至多8项连续的页表项(虚拟页号高位相同且物理页号高位和属性相同)。
|
||||
- 在H拓展中,与虚拟化相关的页表仍采用压缩机制。
|
||||
- **L1TLB**:
|
||||
- 在H拓展中,与虚拟化相关的页表压缩机制无效,视为单个页表。
|
||||
|
||||
**7.异常处理**
|
||||
|
||||
- 各级Walker(PTW、LLPTW、HPTW)访问中可能触发:
|
||||
- Pagefault或Access fault,逐级返回至L1TLB。
|
||||
|
||||
**8. 关键限制**
|
||||
|
||||
- PTW和HPTW均单请求处理,串行化。
|
||||
- PageCache单查询请求处理,两阶段翻译需分步完成。
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
---
|
||||
title: Shield-Bitmap 地址翻译2
|
||||
categories: [bitmap 技术细节]
|
||||
tags: [香山, Bitmap, 技术细节]
|
||||
weight: 5
|
||||
---
|
||||
|
||||
# 加入了Shield-XS安全检查后的内存地址转换流程
|
||||
|
||||
如前文所述, bitmap和MMU耦合。现在将介绍增加了bitmap后的MMU地址翻译流程。
|
||||
|
||||

|
||||
|
||||
从 **客户虚拟地址** 到 **主机物理地址** 的转换过程,同时涉及 **安全属性检查** 和 Shield-Bitmap**高速缓存访问**。以下是详细步骤:
|
||||
|
||||
1. **客户虚拟地址(Guest Virtual Address)**: 流程开始于计算单元(如CPU)使用客户虚拟地址发出内存访问请求。
|
||||
|
||||
2. **访问一级快表(L1TLB Lookup)**: 首先查询一级快表(**L1TLB**),检查是否已缓存该地址的映射。只有经历过bitmap检查才会出现在L1TLB Cache中。因此和L2TLB Cache不同,L1TLB Cache 不会存储bitmap检查位。
|
||||
|
||||
3. **一级快表缓存命中?**
|
||||
|
||||
* **是** → 直接从L1TLB Cache获取主机物理地址(Host Physical Address)。
|
||||
|
||||
* **否** → 进入下一级查询(访问二级快表)。
|
||||
|
||||
4. **访问二级快表(L2TLB Lookup)**: 如果一级快表未命中,继续查询二级快表(L2TLB),检查二级快表是否有对应的映射。L2TLB Cache 存储安全允许位(cf)表示 是否经过bitmap 检查。
|
||||
|
||||
* **二级快表缓存命中?**
|
||||
|
||||
* **cf 和 L2TLB 页表项均命中** → 获取主机物理地址,并回填一级快表(更新L1TLB)。
|
||||
|
||||
* **L2TLB 页表项命中,bitmap cf 未设**→ 发送请求至bitmap,进行权限检查。
|
||||
|
||||
* **否** → 触发 **页表遍历(Page Table Walker)**,从内存中加载页表映射关系,随后进行bitmap检查。
|
||||
|
||||
5. **进行页表遍历**:从内存中加载页表映射关系
|
||||
|
||||
6. **主机虚拟地址 → 主机物理地址转换**:通过页表遍历获取主机物理地址(Host Physical Address)。
|
||||
|
||||
7. **安全性检查允许访问(Security Check)**: 对物理地址进行 **安全属性检查**。如果检查失败,可能触发访问错误(Access Fault)。
|
||||
|
||||
8. **访问(安全属性高速缓存)Shield-Bitmap Cache**:查询**Shield-Bitmap** Cache是否已缓存目标数据。
|
||||
|
||||
- **Shield-Bitmap Cache 命中?**
|
||||
|
||||
- **是** → 返回数据。
|
||||
|
||||
- **否** → 继续访问主存**Shield-Bitmap 专属内存区域**,读取权限。
|
||||
|
||||
9. **访存请求合并(Shield-Bitmap Memory Request Merging)**:如果多个请求访问同一地址,可能合并访存请求以提高效率。
|
||||
|
||||
10. **访问错误**:如果访问不被允许,触发访问错误。
|
||||
|
||||
## 异常处理描述
|
||||
|
||||
RISC-V 特权手册规定的同步异常处理优先级
|
||||
|
||||

|
||||
|
||||
优先级处理如下:
|
||||
|
||||
| 序号 | 描述 | 触发异常 |
|
||||
| ---- | ---------------------------------------------------------------------- | ------------ |
|
||||
| 1 | PTE.V == Invalid | Page fault |
|
||||
| 2 | PTE.V == Invalid &&<br/>Page Table Walker (PMP \|\| Bitmap) Check Fail | Access Fault |
|
||||
| 3 | PTE.V valid && PTE.Permission (R/W/X) Check Fail | Page fault |
|
||||
| 4 | PTE.V valid && PMP Check Fail | Access Fault |
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
title: Shield-XS Bitmap总体设计
|
||||
description: 本节将更近一步介绍Shield-XS Bitmap的技术细节
|
||||
categories: [bitmap 技术细节]
|
||||
tags: [香山, Bitmap, 技术细节]
|
||||
weight: 2
|
||||
---
|
||||
本节介绍bitmap的总体设计,不包括具体的硬件实现,只解释概念,包含:
|
||||
+ **机器模式 Bitmap控制状态寄存器**
|
||||
+ **Shield-Bitmap Cache 加速查表**
|
||||
+ **虚拟化两阶段内存地址翻译转换原理**
|
||||
+ **加入了Shield-XS安全检查后的内存地址转换流程**
|
||||
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
---
|
||||
title: Bitmap Checker
|
||||
categories: [bitmap 硬件实现]
|
||||
tags: [香山, bitmap, 硬件]
|
||||
weight: 1
|
||||
---
|
||||
|
||||
# Bitmap Checker硬件模块
|
||||
|
||||
## Bitmap checker简介
|
||||
|
||||
Bitmap checker 的作用是将来自外部(ptw/lptw/hptw)的请求发送至cache,并根据是否命中进行内存访问查权限。最后将cache返回的或者内存访问得到的权限发送回请求源。
|
||||
|
||||
此外,bitmap(walker)支持non blocking 特性,每一个请求来源都有FSM负责录入请求进行处理。但是一次只能有一个fsm进行cache访问。
|
||||
|
||||
## 状态机描述
|
||||
|
||||
为了保持non blocking,有8个独立的状态机(entries)并行运行。每个 entry 维护独立的状态和数据处理。当有请求进入时,从下到上依次将fsm填满,由于总共就8个请求来源,因此不会出现无空闲fsm可用的情况。
|
||||
|
||||
当entry的PA重复时,仅有一个fsm会进行一次查cache 或者访问memory,其余重复fsm项的状态会被部分跳过。重复表示PA的tag位[47:18] 一致。
|
||||
|
||||
| PA | | | |
|
||||
| :-: | :-----: | :-----------: | :---------: |
|
||||
| 段 | tag | Bitmap offset | Page offset |
|
||||
| 位 | [47:18] | [17:12] | [11:0] |
|
||||
|
||||
#### Bitmap checker 模块状态机
|
||||
|
||||

|
||||
|
||||
#### 状态机状态
|
||||
|
||||
| 状态 | 说明 |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| state_idle | 标明该fsm状态为空,可以录入新请求<br/>当io.req.fire时,切换到新状态<br/>转换条件:<br/><ul><li>io.req.fire → state_addr_check (无重复请求)</li><li>io.req.fire && to_wait → state_mem_waiting (检测到重复请求在等待) 同时写入重复项的id到fsm</li><li>io.req.fire && to_mem_out → state_mem_out (重复请求已完成) 同时写入重复项的id到fsm</li></ul> |
|
||||
| state_addr_check | 进行pmp检查<br/>转换条件:<br/><ul><li>accessFault=true → state_mem_out (检查失败)</li><li>accessFault=false → state_cache_req (检查通过)</li></ul> |
|
||||
| state_cache_req | 将cachereq拉高,fire后→ state_cache_resp |
|
||||
| state_cache_resp | Cache resp fire后更新:<ul><li>hit=true → state_mem_out (缓存命中)</li><li>hit=false && cm_to_mem_out → state_mem_out (重复请求已完成) 同时写入重复项的id到fsm</li><li>hit=false && cm_to_wait → state_mem_waiting (检测到重复请求) 同时写入重复项的id到fsm</li><li>hit=false → state_mem_req (无重复请求) </li></ul> |
|
||||
| state_mem_req | 拉高valid 并等待,mem req fire时,将所有重复项目的id跟新为本fsm id,并将所有重复和本机 state 设置为mem wait |
|
||||
| state_mem_waiting | Fire时→state_mem_out,并将所有的符合id项目内值全部跟新为mem返回值 |
|
||||
| state_mem_out | 拉高 resp valid ,fire时→ state_idle |
|
||||
|
||||
## 接口信号
|
||||
|
||||
| 信号 | 位宽 | 描述 |
|
||||
| ----------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| Io.mem | | 内存访问相关信号 |
|
||||
| io.mem.resp.bits.id | 4 | memory 响应返回的 ID(需为bitmap编号) |
|
||||
| io.mem.resp.bits.value | 512 | memory 返回的 bitmap 数据块 |
|
||||
| io.mem.req_mask | 20 | Memory 请求屏蔽位 |
|
||||
| io.mem.req.bits.addr | 56 | memory 请求的 bitmap 数据地址 |
|
||||
| io.mem.req.bits.id | 4 | memory 请求的编号(恒定为bitmap编号) |
|
||||
| io.mem.req.bits.hptw_bypassed | 1 | (和</font>bitmap </font>模块内部无关) |
|
||||
| Io.Req | | 请求信号 |
|
||||
| io.req.bits.bmppn | 27 | 被检查的物理页号 PPN |
|
||||
| io.req.bits.id | 4 | 请求编号,用于标识请求来源(和</font>bitmap </font>模块内部无关) |
|
||||
| io.req.bits.vpn | 27 | 对应虚拟页号VPN</font>,用于唤醒</font>pagecache</font>(和</font>bitmap </font>模块内部无关) |
|
||||
| io.req.bits.level | 2 | 所查询页表的级别信息(</font>0/1/2</font>),用于唤醒</font>pagecache</font>(和</font>bitmap </font>模块内部无关) |
|
||||
| io.req.bits.way_info | 8 | TLB way </font>编号用于唤醒</font>pagecache</font>(和</font>bitmap </font>模块内部无关) |
|
||||
| io.req.bits.hptw_bypassed | 1 | 用于唤醒</font>pagecache</font>(和</font>bitmap </font>模块内部无关) |
|
||||
| Io.resp | | 返回结果 |
|
||||
| io.resp.bits.cf | 1 | 检查权限是否允许访问 |
|
||||
| io.resp.bits.cfs | 8 | 相邻8个(3bit地址空间)的权限 |
|
||||
| io.resp.bits.id | 4 | 响应对应的请求id</font>(和</font>bitmap </font>模块内部无关) |
|
||||
| Io.pmp | | Pmp</font>查 |
|
||||
| io.pmp.req.bits.addr | 56 | 进行PMP检查的物理地址 |
|
||||
| io.pmp.req.bits.cmd | 2 | 读/写权限请求类型(恒定为读) |
|
||||
| io.pmp.req.bits.size | 3 | 请求访问大小(恒定) |
|
||||
| io.pmp.resp.ld | 1 | PMP Load 权限检查结果 |
|
||||
| io.pmp.resp.mmio | 1 | PMP MMIO 检查结果 |
|
||||
| Io.wakeup | | Resp</font>时且非</font>hptw bypassed </font>进行重填pagecache |
|
||||
| io.wakeup.bits.setIndex | 4 | 唤醒用的setIndex</font>(和</font>bitmap </font>模块内部无关) |
|
||||
| io.wakeup.bits.tag | 4 | 唤醒tag</font>(</font>VPN</font>高位)(和</font>bitmap </font>模块内部无关) |
|
||||
| io.wakeup.bits.isSp | 1 | 是否为</font>superpage</font>(和</font>bitmap </font>模块内部无关) |
|
||||
| io.wakeup.bits.way_info | 8 | TLB</font>对应的way </font>信息(和</font>bitmap </font>模块内部无关) |
|
||||
| io.wakeup.bits.pte_index | 6 | PTE </font>在段页表中的索引位置(和</font>bitmap </font>模块内部无关) |
|
||||
| io.wakeup.bits.check_success | 1 | 是否 bitmap 检查通过 |
|
||||
| Refill | | |
|
||||
| io.refill.bits.data | 64 | 要写入cache </font>的bitmap </font>数据 |
|
||||
| CSR | | |
|
||||
| io_sfence_valid | 1 | SFENCE </font>操作有效信号(为高刷新</font>fsm</font>) |
|
||||
| io_csr_satp_changed | 1 | SATP </font>寄存器变更标志(为高刷新</font>fsm</font>) |
|
||||
| io_csr_vsatp_changed | 1 | VSATP </font>寄存器变更标志(为高刷新</font>fsm</font>) |
|
||||
| io_csr_hgatp_changed | 1 | HGATP </font>寄存器变更标志(为高刷新</font>fsm</font>) |
|
||||
| io_csr_mbmc_BMA | 58 | Bitmap </font>基址寄存器值 |
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
title: Bitmap Cache
|
||||
categories: [bitmap 硬件实现]
|
||||
tags: [香山, bitmap, 硬件]
|
||||
weight: 2
|
||||
---
|
||||
|
||||
### Bitmap Cache硬件模块
|
||||
|
||||
#### Bitmap cache简介
|
||||
|
||||
Bitmap cache用于缓存 bitmap 数据块以减少 memory 访问延迟,存储最近访问的 bitmap 数据,**共16个entry。每个 entry 存储一个 64-bit 数据段**。使用plru替换策略。
|
||||
|
||||
#### Bitmap模块结构
|
||||
|
||||
Cache 一回合出结果,不需要pipeline。此外,refill也只需要一回合。Refill使用plru进行充填。
|
||||
|
||||
#### Bitmap cache接口
|
||||
|
||||
| io_req | 位宽 | Bm 发起请求 |
|
||||
| -------------------- | ---- | ------------------------------------------- |
|
||||
| io_req_bits_tag | 36 | Tag for cache lookup ([35:6] = tag) |
|
||||
| io_req_bits_order | 8 | 发起请求的Fsm编号 |
|
||||
| Io resp | | 返回bm请求 |
|
||||
| io_resp_bits_hit | 1 | 是否hit cache |
|
||||
| io_resp_bits_order | 8 | 发起请求的Fsm编号 |
|
||||
| io_resp_bits_cfs | 8 | 相邻8个的权限 |
|
||||
| Io refill | | Refill接口,来自bm,bm resp valid时发起重填 |
|
||||
| io_refill_bits_tag | 36 | Tag for cache refill ([35:6] = tag) |
|
||||
| io_refill_bits_data | 64 | Data to refill into cache |
|
||||
| io_resp_bits_hit | 1 | 是否hit cache |
|
||||
| CSR | | |
|
||||
| io_sfence_valid | 1 | 同步刷新请求有效(触发缓存刷新) |
|
||||
| io_csr_satp_changed | 1 | SATP CSR 变更标志(触发缓存刷新) |
|
||||
| io_csr_vsatp_changed | 1 | VSATP CSR 变更标志(触发缓存刷新) |
|
||||
| io_csr_hgatp_changed | 1 | HGATP CSR 变更标志(触发缓存刷新) |
|
||||
| io_csr_mbmc_BCLEAR | 1 | 缓存清除信号(触发缓存刷新) |
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
---
|
||||
title: Bitmap 与L2TLB交互
|
||||
categories: [bitmap 硬件实现]
|
||||
tags: [香山, bitmap, 硬件]
|
||||
weight: 3
|
||||
---
|
||||
|
||||
### Bitmap 与L2TLB内的交互
|
||||
|
||||

|
||||
|
||||
#### Page Cache 与 Bitmap 检测机制的交互
|
||||
|
||||
##### 新增信号
|
||||
|
||||
发向 PTW 的 bitmap check 信号:用于触发 PTW 进行 bitmap 检测。
|
||||
|
||||
发向 HPTW 的 bitmap check 信号:用于触发 HPTW 进行 bitmap 检测。
|
||||
|
||||
接收来自 bitmap 的重填信号(bitmap wakeup):用于接收 bitmap 检测结果并更新缓存。
|
||||
|
||||
##### Bitmap Wakeup接口: refill bitmap
|
||||
|
||||
功能描述:当接收到 wake up valid 信号时,将 check_success 结果写入对应的 sp 或 l0 的 cache bitmap reg 中。
|
||||
|
||||
工作原理:cache bitmap reg 用于标识缓存项是否通过 bitmap 检测。值为 1 表示已通过检测;值为 0 表示检测未通过或尚未检测。如果发现 PtwCache 命中的表项未通过检测,则触发 Bitmap 检测流程,并通过 bitmap wakeup 更新缓存项。此外,在走表过程中所有bitmap返回的项都会回填page cache。
|
||||
|
||||
<center><img src=../../module02_2.jpeg width=30% /></center>
|
||||
|
||||
|
||||
##### Ptw/llptw接口:refill data 后第一次伪hit发起bitmap请求
|
||||
|
||||
功能描述:当缓存命中且 bitmap valid = 0 时,首次命中不直接返回 L1TLB,而是返回响应请求源并发起 bitmap 请求。
|
||||
|
||||
工作原理:使用 is_hptw 判断请求源。请求源在获取 bitmap 权限后,将结果重新填充到 Page Cache 中。
|
||||
|
||||
#### Page Table walker 交互
|
||||
|
||||
##### 状态机更新
|
||||
|
||||
新增状态:PTW、LLPTW 和 HPTW 的状态机中新增了 state_bitmap_check。
|
||||
|
||||
工作流程:在 PTW、LLPTW 和 HPTW 的状态机中,于 state_mem_resp 阶段进行 bitmap 检测,并将 bitmap 检测的使能信号传递给这些部件。如果满足 bitmap 检测条件,则进入 state_bitmap_check 并获取检测结果。如果检测失败,则触发访问故障(Access Fault)并将结果返回。
|
||||
|
||||
##### 触发条件
|
||||
|
||||
PTW:仅在未开启虚拟化且检测到巨页(hugepage)时进行 bitmap 检测。
|
||||
|
||||
LLPTW:仅在请求未开启虚拟化(即进行 VA 到 PA 的地址转换时)进行 bitmap 检测。如果请求通过 HPTW,则 HPTW 已在工作过程中进行了 bitmap 检测。
|
||||
|
||||
HPTW:在遍历到最后一级页表时,于 mem_resp 阶段进行 bitmap 检测。
|
||||
|
||||
##### 新接口
|
||||
|
||||
Req_bitmapcheck 接口:用于在 Page Cache 首次命中时发起 bitmap 检测。仅在 PTW 和 LLPTW 上实现。如果有效,则直接接收一个 PTE 并检查权限。状态机直接跳转到 state_bitmap_check,获取权限后直接返回 pagecache。
|
||||
|
||||
Bitmap 接口:用于在 state_bitmap_check 阶段发送 bitmap 请求,并检查权限是否通过。如果检测失败,则触发访问故障。HPTW 和 LLPTW 均具备此接口。
|
||||
|
||||
##### 刷新
|
||||
|
||||
bitmap 依赖软件辅助刷新,硬件刷新不完整。在刷新前,需依次sfence 和 hfence L1 和L2TLB内所有项目,然后才可以拉高 CSR_MBMC_BCLEAR 进行bitmap cache刷新。
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
title: Bitmap接口时序图
|
||||
categories: [bitmap 硬件实现]
|
||||
tags: [香山, bitmap, 硬件]
|
||||
weight: 4
|
||||
---
|
||||
|
||||
## Bitmap接口时序图
|
||||
|
||||

|
||||
|
||||
| 信号 | 描述 |
|
||||
| ------------------- | ---------------------------------------------------------------------- |
|
||||
| io_req_ready | 8个fsm中有至少一个idle时为高,可以视为常态高 |
|
||||
| io_req_valid | 新请求进入时高,平时为低 |
|
||||
| io_resp_ready | 当请求源(ptw hptw llptw)发送请求,等待返回时会拉高,平时无请求时为低 |
|
||||
| io_resp_valid | 当返回查询结果时拉高,平时为低 |
|
||||
| io_mem_req_ready | 有其它mem请求时(ptw llptw hptw)为低,平时为高 |
|
||||
| io_mem_req_valid | cache miss时发起mem请求拉高,平时为低 |
|
||||
| io_mem_resp_valid | mem 返回结果拉高,平时为低 |
|
||||
| io_cache_req_valid | bimap fsm 发起 cache 请求拉高,平时为低 |
|
||||
| io_cache_req_ready | 常态高 |
|
||||
| io_cache_resp_valid | io_cache_req_valid下一clk 拉高平时低 |
|
||||
| io_cache_resp_ready | io_cache_req_valid 下一clk 拉高平时低 |
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
title: 开销评估
|
||||
categories: [bitmap 硬件实现]
|
||||
tags: [香山, bitmap, 硬件]
|
||||
weight: 5
|
||||
---
|
||||
|
||||
## 开销评估
|
||||
|
||||
**1. 基本配置**
|
||||
|
||||
| **类别** | **配置项** | **参数\*\***/\***\*设置** |
|
||||
| :------------------- | :---------------------- | :---------------------------- |
|
||||
| **Shield-Bit 配置** | 有效 Shield-XS 隔离模型 | - |
|
||||
| | 设置 Shield-Bitmap | \_ |
|
||||
| | Shield-Bitmap缓存大小 | 128 × 8 Bytes |
|
||||
| **KunminghuV2 配置** | TileLink Prototype | - |
|
||||
| **缓存层级配置** | L1 指令/数据缓存大小 | 64KB |
|
||||
| | L1 指令/数据 TLB | 48-全关联(Full Association) |
|
||||
| | L2 缓存大小 | 1MB |
|
||||
| | L3 缓存大小 | 16MB |
|
||||
|
||||
**2. SPEC2006 性能数据**
|
||||
|
||||
SPECInt2006 Simpoint est.@**3GHz** **GEOMEAN 44.62 **->** 44.29 (0.72% )**
|
||||
|
||||

|
||||
|
||||
图 9.1 SPEC2006 性能开销
|
||||
|
||||
性能开销与DTLB Miss-rate 呈正比。有效的减少 DTLB 和 Shield-bitmap Cache 的miss-rate, 可以进一步提升性能。例如将缓存从 16 项扩展到 128 项,可使 GemsFDTD 的性能开销从 6.51% 降低至 2.36%。
|
||||
|
||||
**3. 硬件开销**
|
||||
|
||||
采用7纳米工艺制程,硬件面积开销仅为0.2%。
|
||||
|
||||
<!-- | **模块** | **Cell ****面积****(平方微米)** |
|
||||
| --- | --- |
|
||||
| Shield-Bitmap Cache | 5,075 |
|
||||
| Shield-Bitmap Checker | 1,088 |
|
||||
| MMU Area | 50,843 |
|
||||
| KMH V2 Core Area | 2,000,000 |
|
||||
| Area Percentage | **<font style="color:#c00000;"><font style="color:#c00000;">0.3%** | -->
|
||||
|
||||
| 工艺 | 子模块前 (单位: μm2) | 子模块后 (单位: μm2) | 百分比 |
|
||||
| ---- | ------------------------------------ | --------------------------------- | --------- |
|
||||
| T7 | Memblock.withoutBitmap:462415.887238 | Memblock.withBitmap:471410.993566 | +1.94524% |
|
||||
| T7 | L2TLB.withoutBitmap: 41538.554989 | L2TLB.withBitmap : 50843.978450 | +22.4% |
|
||||
|
||||
## 时序违例
|
||||
|
||||
| 模块路径 | clock period | clock uncertainty | data arrival time | setup time | slack |
|
||||
| ----------------------------------- | ------------ | ----------------- | ----------------- | ---------- | ---------- |
|
||||
| bitmap FSM -> bitmap Cache Data Reg | 0.333 ns | 0.1 ns | 0.2724 ns | 0.0107 ns | -0.0501 ns |
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
title: Shield-XS Bitmap 硬件设计
|
||||
description: 本部分文档将会详细介绍香山 bitmap 硬件设计
|
||||
categories: [bitmap 硬件实现]
|
||||
tags: [香山, bitmap, 硬件]
|
||||
weight: 3
|
||||
---
|
||||
|
||||
## Shield-XS Bitmap 硬件设计
|
||||
|
||||
在硬件实现层面,Bitmap 机制由两个关键组件构成,即 Bitmap Checker 和 Bitmap Cache。其中,Checker 的职责是读取内存中的权限信息,以确保内存访问的安全性;而 Cache 则旨在加速查找过程,提升整体性能。需要指出的是,当前实现仅支持单向隔离功能。
|
||||
这意味着在实际应用场景中,它能够有效地防止非安全敏感型负载对安全内存区域的非法访问,但尚未支持更高阶的双向隔离功能,即安全与非安全负载之间的互相访问限制。
|
||||
|
||||
#### Shield-XS Bitmap 硬件示意图
|
||||
|
||||

|
||||
|
||||
上图展示了一次虚拟地址到物理地址转换过程中如何结合**Shield-Bitmap安全机制**进行访问权限检查。
|
||||
以及bitmap cache hit 和miss的不同处理。在L1TLB hit时,无需进行bitmap检查,因为L1TLB只会存储bitmap 检查为 allow的项。
|
||||
如果miss,在L2TLB的page cache中查找,如果页表项和对应的bitmap 均未命中,则先进行查表,后进行bitmap检查并返回结果。如果页表项命中但未进行过bitmap 检查,则只进行bitmap检查。 如果都命中,则直接返回。
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
---
|
||||
title: 单元验证
|
||||
description: 验证需求
|
||||
categories: [香山bitmap验证]
|
||||
tags: [香山, bitmap, 验证]
|
||||
weight: 1
|
||||
---
|
||||
|
||||
## 单元验证
|
||||
|
||||
### TLB 相关功能验证
|
||||
|
||||
| 验证功能编号 | 验证描述 | 覆盖方法 | 排列组合示例 |
|
||||
| ------------ | ---------------------------------------------------------------- | ------------------------------------------- | ----------------------------- |
|
||||
| TLB_001 | ITLB hit/miss 场景验证 | 设计测试用例覆盖 ITLB 命中和未命中 | 4K/2M/1G/512G 页大小 |
|
||||
| TLB_002 | DTLB hit/miss 场景验证 | 设计测试用例覆盖 DTLB 命中和未命中 | 4K/2M/1G/512G 页大小 |
|
||||
| TLB_003 | L2TLB hit/miss 场景验证 | 设计测试用例覆盖 L2TLB 命中和替换 | 4K/2M/1G/512G 页大小 |
|
||||
| TLB_004 | L2TLB 压缩功能验证<br/>注:仅支持一阶段地址转换压缩,最多压缩8项 | 测试TLB 压缩场景下,Bitmap 查询结果是否正确 | TLB 压缩启用 + 4K 页大小<br/> |
|
||||
|
||||
### Bitmap Cache 相关功能验证
|
||||
|
||||
| 验证功能编号 | 验证描述 | 覆盖方法 | 排列组合示例 |
|
||||
| ------------ | --------------------------------- | -------------------------------------------- | -------------------------------------------- |
|
||||
| BITMAP_001 | Bitmap Cache hit/miss 场景验证 | 设计测试用例覆盖命中和未命中 | Bitmap Y/N + 跨bitmap cache line |
|
||||
| BITMAP_002 | Bitmap check 功能验证 | 验证 bitmap check 的正确性 | Bitmap check 启用 + 4K 页大小 |
|
||||
| BITMAP_003 | 跨bitmap cache line 场景验证 | 测试跨 cache line 的访问行为 | 跨bitmap cache line + 2M 页大小 |
|
||||
| BITMAP_004 | NAPOT 拓展开启,验证Bitmap 的行为 | 开启NAPOT,设置PTE.n 位,验证Bitmap 检测流程 | 跨bitmap cache line + 64K 页大小切换PTE.n 位 |
|
||||
|
||||
### Bitmap Cache 和 TLB 组合相关功能验证
|
||||
|
||||
| 验证功能编号 | 验证描述 | 覆盖方法 | 排列组合示例 |
|
||||
| ------------------ | --------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
||||
| **TLB_BITMAP_001** | Bitmap与TLB混合命中场景验证 | 组合Bitmap命中与各级TLB命中,验证多级缓存协同 | Bitmap hit + ITLB/DTLB/L2TLB全命中<br/><br/> |
|
||||
| **TLB_BITMAP_002** | 全未命中极端场景验证 | 设计冷启动或冲刷缓存后首次访问的全未命中用例 | Bitmap miss + ITLB/DTLB/L2TLB全miss + 触发页表遍历 |
|
||||
| **TLB_BITMAP_003** | Bitmap跨行与TLB替换场景验证 | 强制Bitmap跨cache line访问并触发TLB替换(如duplicate access或页表更新) | Bitmap跨行 + DTLB miss + L2TLB替换 |
|
||||
| **TLB_BITMAP_004** | 重复访问与Cache/TLB交互验证 | 通过相同地址重复访问验证Bitmap/TLB的重复访问优化 | Bitmap重复命中 + ITLB重复命中 + 无替换<br/>Bitmap重复Miss + ITLB重复Miss + 替换 |
|
||||
|
||||
---
|
||||
|
||||
### 页表遍历(PTW)相关功能验证
|
||||
|
||||
| 验证功能编号 | 验证描述 | 覆盖方法 | 排列组合示例 |
|
||||
| ------------ | -------------------------------- | ------------------------ | ---------------------- |
|
||||
| PTW_001 | PTW/HPTW/LLPTW 优先级验证 | 测试多级页表遍历的优先级 | PTW + HPTW 并发 |
|
||||
| PTW_002 | PTW 返回异常(access fault)验证 | 模拟 PTW 返回异常的场景 | PTW return af + 跨Page |
|
||||
| PTW_003 | PTE 合法性验证 | 测试非法 PTE 的处理 | 非法 PTE + PMP check |
|
||||
|
||||
---
|
||||
|
||||
### 异常与重放(Replay)功能验证
|
||||
|
||||
| 验证功能编号 | 验证描述 | 覆盖方法 | 排列组合示例 |
|
||||
| ------------ | ---------------------------------------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
||||
| REPLAY_001 | 标量 replay 场景验证 | 测试标量指令重放行为 | 标量 replay + access fault |
|
||||
| REPLAY_002 | 向量访存 replay 场景验证 | 测试向量指令重放行为 | 向量 replay + 跨MMO |
|
||||
| EXCEPT_001 | 异常优先级验证(access/page fault/guest page fault) | 验证异常触发的优先级 | page fault/guest page fault<br/>page table walker 过程中的PMP/bitmap 检测失败<br/>转换之后的物理地址PMP 检测失败 |
|
||||
|
||||
---
|
||||
|
||||
### 特权级与扩展功能验证
|
||||
|
||||
| 验证功能编号 | 验证描述 | 覆盖方法 | 排列组合示例 |
|
||||
| ------------ | -------------------- | ------------------------------- | ------------------------- |
|
||||
| PRIV_001 | U/S/M 特权级切换验证 | 测试不同特权级下的访问权限 | U/S/M + 4K/2M 页大小 |
|
||||
| EXT_001 | H-extension 功能验证 | 测试 H-extension 启用和禁用场景 | H-extension + 跨tlb entry |
|
||||
| PMP_001 | PMP check 功能验证 | 测试bitmap 内PMP 权限检查 | PMP Y/N + 跨Page |
|
||||
|
||||
---
|
||||
|
||||
### Fense验证
|
||||
|
||||
| 验证功能编号 | 验证描述 | 覆盖方法 | 排列组合示例 |
|
||||
| ------------ | -------------- | ------------------------- | ----------------- |
|
||||
| MISC_001 | Fence 操作验证 | 测试 fence 指令的同步效果 | Fence + 切换Cmode |
|
||||
|
||||
### 验证方法说明
|
||||
|
||||
**覆盖方法**:通过随机测试、定向测试和边界值测试覆盖功能点。
|
||||
|
||||
**排列组合**:优先覆盖高频场景,再逐步覆盖低频组合(如 512G 页)。
|
||||
|
||||
### 备注
|
||||
|
||||
- 需根据实际硬件行为调整测试用例的输入和预期输出。
|
||||
|
||||
- 动态检查(如 PMP check)需结合具体权限配置。
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
title: Shield-XS_Bitmap 单元验证
|
||||
description: 本文档将会对需要验证的点进行说明
|
||||
categories: [香山bitmap验证]
|
||||
tags: [香山, bitmap, 验证]
|
||||
weight: 4
|
||||
---
|
||||
|
||||
本文档将会对需要验证的点进行说明
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
title: 微报告模板
|
||||
linkTitle: 微报告模板
|
||||
weight: 12
|
||||
---
|
||||
|
||||
|
||||
***SCALAR_DISPATCH 测试点***
|
||||
### 功能描述
|
||||
|
||||
本测试点验证标量Store指令(如sb、sh、sw、sd)从发射队列派发到StoreUnit时,是否正确分配一个StoreQueue(SQ)条目。测试确保指令派发机制正常工作,包括SQ索引分配、状态标记和信号交互。这是StoreUnit最基本的功能,涉及指令派发流水线的初始阶段。
|
||||
|
||||
### 输入参数
|
||||
- 虚拟地址(vaddr):0x1000
|
||||
- 存储数据(data):0x12345678
|
||||
- 存储大小(size):1、2、4、8字节
|
||||
- 首次派发标志(is_first_issue):true或false
|
||||
- 指令类型:通过fuOpType设置
|
||||
### 输出验证
|
||||
- 派发成功信号:io_stin_ready和io_stin_valid同时为高,表示指令被接收。
|
||||
- SQ条目分配:io_lsq_valid为高,且io_lsq_bits_updateAddrValid为高,表示SQ地址更新有效。
|
||||
- SQ索引范围:io_lsq_bits_uop_sqIdx_value在0到SQ_SIZE-1之间(如0-63)。
|
||||
- 状态标记:SQ条目中的valid位被设置为1,robIdx正确记录。
|
||||
- 后续准备:io_stin_ready保持为高,表示可接收新指令。
|
||||
### 边界条件分析
|
||||
1. 不同存储大小:测试了1字节(sb)、2字节(sh)、4字节(sw)和8字节(sd)指令,确保所有大小都能正确分配SQ条目。
|
||||
2. 地址对齐:测试对齐地址(如0x1000)和不对齐地址(如0x1001),验证地址计算不影响派发。
|
||||
3. 首次派发与重发:设置is_first_issue为true和false,模拟首次派发和重发场景,确保重发机制不干扰SQ分配。
|
||||
4. SQ容量边界:通过多次派发指令,测试SQ接近满时的行为,确保索引不溢出。
|
||||
5. 信号时序:验证在时钟上升沿信号稳定,避免时序问题导致派发失败。
|
||||
### 结果总结
|
||||
测试通过,所有验证点均符合预期。标量Store指令派发功能正常,SQ条目分配正确,边界条件处理稳健。覆盖了标量存储的所有大小类型和常见地址场景,为后续流水线阶段提供了可靠基础。无异常或错误发生,测试完整性高。
|
||||
|
|
@ -0,0 +1,752 @@
|
|||
---
|
||||
title: StoreUnit
|
||||
linkTitle: StoreUnit
|
||||
weight: 12
|
||||
---
|
||||
|
||||
#### 本文档参考[香山StoreUnit设计文档](https://github.com/OpenXiangShan/XiangShan-Design-Doc/blob/master/docs/zh/memblock/LSU/index.md)写成
|
||||
请注意,本文档撰写的测试点仅供参考,如能补充更多测试点,最终获得的奖励可能更高!
|
||||
# StoreUnit说明文档
|
||||
## 文档概述
|
||||
本文档描述了StoreUnit的结构与功能,并根据功能给出测试点参考,方便测试的参与者理解测试需求,编写相关测试用例。
|
||||
StoreUnit 是存储指令执行单元 ,由多个协同工作的子模块组成,包括其五级流水线处理流程、支持的三种类型store指令(标量、向量、非对齐)、接口设计与信号交互逻辑。该模块用于执行Store类指令的地址生成与处理,是Load/Store流水线中的关键组成部分。
|
||||
|
||||
|
||||
## 术语说明
|
||||
|
||||
| 名称 | 定义 |
|
||||
| ------- | ---|
|
||||
| TLB(Translation Lookaside Buffer) | 地址转换旁路缓冲器,用于虚拟地址到物理地址的快速转换 |
|
||||
| PMP(Physical Memory Protection) | 物理内存访问权限检查机制 |
|
||||
| RAW(Read After Write)违例 | 写后读违例,表示一个load指令读取尚未写入的store数据 |
|
||||
| MMIO(Memory-Mapped Input/Output) | 内存映射IO |
|
||||
|
||||
### 存储指令执行
|
||||
|
||||
内存单元里包含2条Store地址流水线与2条Store数据流水线。各流水线独立接收并执行对应发射队列派发的指令。
|
||||
|
||||
#### 存储地址流水线
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
由4级结构组成:
|
||||
|
||||
Store地址流水线(4级结构)
|
||||
|
||||
* stage 0:
|
||||
|
||||
* 计算VA地址
|
||||
|
||||
* 非对齐 > 向量 > 标量请求的优先级仲裁
|
||||
|
||||
* 地址非对齐检查更新到uop.exceptionVec
|
||||
|
||||
* 对地址的16字节边界检测
|
||||
|
||||
* 发出DTLB读请求到tlb
|
||||
|
||||
* 更新指令的掩码信息到s0_mask_out发送到StoreQueue
|
||||
|
||||
* 判断是否为数据宽度为128bits的store指令。
|
||||
|
||||
* stage 1:
|
||||
|
||||
* 接收TLB地址转换响应,TLB若缺失则重试
|
||||
|
||||
* 查询LoadQueueRAW以进行违规检查
|
||||
|
||||
* 与加载单元s1和s2阶段的Load指令进行RAW冒险检测
|
||||
|
||||
* 如果DTLB hit,将store issue信息发送到后端
|
||||
|
||||
* stage 2:
|
||||
|
||||
* mmio/PMP检查、检查原子操作权限
|
||||
|
||||
* 更新storeQueue中标记为地址就绪状态
|
||||
|
||||
* 更新DTLB结果通过feedback_slow更新到后端
|
||||
|
||||
* stage 3:
|
||||
|
||||
* 标量store发起写回,通过stout发送给后端
|
||||
|
||||
* StoreQueue条目释放与状态清理
|
||||
|
||||
#### 存储数据流水线
|
||||
|
||||
- 从发射队列(issue queue)接收数据
|
||||
- 将数据写回StoreQueue
|
||||
- 标记发射队列接收的数据为就绪状态
|
||||
|
||||
### 向量存储指令执行
|
||||
对于除 SEG指令外的向量内存访问指令,VSSplit 负责接收向量内存访问指令发射队列发送的微操作(uop),并将该微操作拆分为多个元素。随后VSSplit 将这些元素发送至StoreUnit 执行,执行流程与标量内存访问指令相同。执行完成后,元素会被写回至 VSMerge,其中 Merge 模块会将这些元素收集并组合成微操作,最终写回向量寄存器文件。
|
||||
SEG 指令则由独立的 VSegmentUnit 模块处理。
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
StoreUnit处理非对齐Store指令流程和标量类似,特别的:
|
||||
|
||||
* stage 0:
|
||||
|
||||
* 接受vsSplit的执行请求,优先级高于标量请求,并且不需要计算虚拟地址
|
||||
|
||||
* stage 1:
|
||||
|
||||
* 计算vecVaddrOffset和vecTriggerMask
|
||||
|
||||
* stage 2:
|
||||
|
||||
* 不需要向后端发送feedback_slow响应
|
||||
|
||||
* stage 3:
|
||||
|
||||
* 向量store发起Writeback,通过vecstout发送给后端
|
||||
|
||||
### 非对齐存储指令执行
|
||||
|
||||
香山核支持标量与向量内存指令对存储空间进行非对齐访问。
|
||||
|
||||
标量非对齐访问未跨越16B边界时可正常执行,无需特殊处理,而跨越16B边界的标量非对齐内存访问在MisalignBuffer中拆分为两次对齐内存操作,完成后由MisalignBuffer处理拼接与写回。
|
||||
|
||||
向量非Segment的Unit-stride指令访问连续地址空间,合并元素后一次访问16B,因此无需特殊处理。
|
||||
|
||||
非Unit-stride的非Segment向量指令由VSplit模块完成元素拆分与地址计算后发送至流水线。若元素非对齐则发送至MisalignBuffer,后续过程与非对齐标量相同,区别在于MisalignBuffer最终写回至VMerge而非直接后端
|
||||
|
||||
向量Segment指令的非对齐处理由VSegmentUnit独立完成,不复用标量内存访问路径,通过独立状态机完成
|
||||
|
||||
原子指令、MMIO与NC地址空间均不支持非对齐访问,这些情况将触发AccessFault异常。
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
StoreUnit处理非对齐Store指令流程和标量类似,特别的:
|
||||
|
||||
* stage 0:
|
||||
|
||||
* 接受来自StoreMisalignBuffer的请求,优先级高于向量和标量请求,并且不需要计算虚拟地址
|
||||
|
||||
* stage 2:
|
||||
|
||||
* 不需要向后端发送feedback响应
|
||||
|
||||
* 如果不是来自于StoreMisalignBuffer的请求并且没有跨越16字节边界的非对齐请求,那么需要进入StoreMisalignBuffer处理
|
||||
|
||||
* 通过io_misalign_buf接口,向StoreMisalignBuffer发送入队请求
|
||||
|
||||
* 如果是来自与StoreMisalignBuffer的请求并且没有跨越16字节边界请求,则需要向StoreMisliagnBuffer发送重发或者写回响应
|
||||
|
||||
* 通过io_misalign_sout接口,向StoreMisalignBuffer发送响应
|
||||
|
||||
* 如果出现TLB miss,则需要重发,否则写回
|
||||
|
||||
### RAW违例
|
||||
RAW内存违例:处理器核执行的Load指令的结果应来源于当前处理器核观察到的全局内存顺序中最新的写操作。具体而言,若最新的写操作来自当前核的Store指令,则Load应获取该Store写入的数据。为优化Load指令性能,超标量乱序处理器会投机执行Loads。因此,Load指令可能早于地址相同的更早Store执行,从而获取到该Store之前的旧值,即构成RAW内存违例。
|
||||
|
||||
## 整体框图及流水级
|
||||
|
||||
Store指令地址流水线分为S0/S1/S2/S3四级,如图所示:
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
接收store地址发射队列发来的请求,处理完成之后需要给后端和向量部分响应,处理过程中需要给发射队列反馈信息,给StoreQueue反馈信息,最后写回, 如果中间出现异常则从发射队列重新发射。
|
||||
|
||||
<mrs-functions>
|
||||
|
||||
## StoreUnit功能说明
|
||||
StoreUnit是存储指令的逻辑,功能被解构并集成到了 Memory Dispatch Queue、STA、STD、Integer ALUs、StoreQueue、SBuffer、LoadQueueRAW/RAR以及 DTLB 等一系列子模块中。
|
||||
|
||||
|
||||
### 内存指令派发
|
||||
|
||||
Store指令存在复杂的控制机制(如顺序、转发、违例等),因此需要队列来保存Store指令的先进先出顺序以进行相关控制,该队列即StoreQueue。Store指令在完成译码、重命名等操作后,需要派发至ROB与LSQ,分配对应的robIdx、lqIdx与sqIdx,随后进入各自发射队列。在所有源操作数就绪后发射至MemBlock中的流水线。在MemBlock中的执行生命周期内,Store指令会携带lqIdx与sqIdx,用于内存违例检测与数据转发时的顺序维护。
|
||||
|
||||
对于标量内存访问指令,一条指令分配一个StoreQueue表项。
|
||||
|
||||
对于向量内存访问指令,一条指令在译码阶段会被拆分为多个uop,每个uop包含若干元素,相当于一次内存访问操作。在派发时,一个uop会分配与其包含元素数量相等的多个LSQ表项。
|
||||
|
||||
| 序号 | 功能名称 | 测试点名称 | 描述 |
|
||||
| ----- |-----------------|---------------------|------------------------------------|
|
||||
| 1.1 | SU_DISPATCH | SCALAR_DISPATCH | 验证标量Store指令派发时分配一个StoreQueue条目。|
|
||||
| 1.2 |SU_DISPATCH | VECTOR_DISPATCH | 验证向量Store指令的一个uop分配多个LSQ条目(根据元素数量)。|
|
||||
|
||||
### 地址流水线
|
||||
|
||||
|
||||
| 序号 | 功能名称 | 测试点名称 | 描述 |
|
||||
| ----- |-----------------|---------------------|------------------------------------|
|
||||
| 2.1 | SU_STORE | S0_ADDRESS_CALC |验证s0阶段地址计算和仲裁。|
|
||||
| 2.2 | SU_STORE | S1_RAW_CHECK|验证s1阶段RAW冒险检测。|
|
||||
| 2.3 | SU_STORE | S2_SQ_MARK_READY |验证s2阶段StoreQueue地址就绪标记。|
|
||||
|
||||
|
||||
### 向量内存指令执行
|
||||
|
||||
|
||||
| 序号 | 功能名称 | 测试点名称 | 描述 |
|
||||
| ----- |-----------------|---------------------|------------------------------------|
|
||||
| 3.1 | SU_VECTOR | SPLIT |验证向量指令拆分正确性。|
|
||||
| 3.2 | SU_VECTOR | OFFSET |元素偏移计算|
|
||||
|
||||
|
||||
|
||||
### 重执行
|
||||
|
||||
存储指令会被发射队列重新传输,在一个存储指令被发射队列发射后,队列不会立即清除该指令,直到StoreUnit返回信号。StoreUnit根据TLB是否命中发送相应反馈。如果TLB未命中,则由指令发射队列负责重新发送该指令。
|
||||
|
||||
### RAW处理
|
||||
|
||||
RAW内存访问违例检测:LoadQueue中的LoadQueueRAW模块通过FreeList结构记录所有可能地址相同但尚未执行更早Store的Load指令。当Load指令在LoadUnit执行到s2阶段(此时地址转换与PMA/PMP检查已完成)时,会分配LoadQueueRAW表项。当StoreQueue中所有Store地址就绪后,LoadQueueRAW中的所有Load可释放;或当程序顺序早于它的所有Store地址就绪后,该Load可从LoadQueueRAW释放。若Store指令在查询LoadQueueRAW时发现存在地址相同的更晚Load,则发生RAW内存访问违例,需要回滚。
|
||||
|
||||
RAW内存访问违例恢复:检测到RAW违例时,由LoadQueueRAW发起回滚,从造成违例的Store指令的下一条指令开始清空流水线。
|
||||
|
||||
| 序号 | 功能名称 | 测试点名称 | 描述 |
|
||||
| ----- |-----------------|---------------------|------------------------------------|
|
||||
| 5.1 | SU_RAW | VIOLATION |验证RAW违例检测。|
|
||||
| 5.2 | SU_RAW | RECOVERY_MECH |验证检测到RAW违例后的恢复(流水线清空)。|
|
||||
|
||||
|
||||
### SBuffer优化
|
||||
|
||||
根据RVWMO模型,在多核场景下(无FENCE等栅栏语义指令时),一个核的Store指令可以比地址不同的更晚Load指令更晚对其他核可见。该内存模型规则主要优化Store指令性能。RVWMO等弱一致性模型允许处理器核包含SBuffer,暂时保存已提交的Store写操作,合并这些写操作后再写入DCache,从而减少Store指令对DCache SRAM端口的争用,提高Load指令执行带宽。
|
||||
|
||||
SBuffer为16×512B的全相联结构。当多个Store地址落在同一缓存块时,SBuffer会合并这些Store。
|
||||
|
||||
SBuffer每周期最多可写入2条Store指令,每条Store指令写数据位宽为16B(例外:cbo.zero指令按缓存块操作,一次操作一个缓存块)。
|
||||
|
||||
SBuffer换出:
|
||||
|
||||
当SBuffer容量超过一定阈值时,执行换出操作,按PLRU替换算法选择被替换块写入DCache
|
||||
|
||||
SBuffer支持被动清空机制;FENCE/atomic/向量Segment等指令执行时会清空SBuffer
|
||||
|
||||
SBuffer支持超时清空机制;超过2^20周期未被换出的数据块将被驱逐
|
||||
|
||||
| 序号 | 功能名称 | 测试点名称 | 描述 |
|
||||
| ----- |-----------------|---------------------|------------------------------------|
|
||||
| 6.1 | SU_SBUFFER | WRITE_MERGE|验证同一缓存块的多个Store在SBuffer中合并。|
|
||||
| 6.2 | SU_SBUFFER | PLRU_REPLACE |验证SBuffer满时按PLRU策略替换。|
|
||||
|
||||
|
||||
### MMIO处理
|
||||
|
||||
香山核仅允许标量内存访问指令访问MMIO地址空间。MMIO访问与任何其他内存操作强顺序。因此,MMIO指令必须等待成为ROB头(即所有前序指令均完成)时才能执行。对于MMIO Store指令,需完成虚地址到实地址转换,通过实地址检查,且写数据就绪。随后LSQ将内存请求发送至Uncache模块,通过总线访问外设。结果返回LSQ后写回至ROB。
|
||||
|
||||
原子指令与向量指令不支持MMIO访问。若此类指令访问MMIO地址空间,将触发对应AccessFault异常。
|
||||
| 序号 | 功能名称 | 测试点名称 | 描述 |
|
||||
| ----- |-----------------|---------------------|------------------------------------|
|
||||
| 7.1 | SU_MMIO | ORDER |验证MMIO指令强顺序执行(成为ROB头)。|
|
||||
| 7.2 | SU_MMIO | EXCEPTION |验证原子/向量指令访问MMIO触发异常。|
|
||||
|
||||
### Uncache指令执行
|
||||
|
||||
香山核除支持访问非幂等、强顺序的MMIO地址空间外,还支持访问幂等、弱一致性(RVWMO)的Non-cacheable地址空间,简称NC。软件通过页表PBMT字段配置为NC以覆盖原有PMA属性。与MMIO访问不同,NC访问允许乱序内存操作。
|
||||
|
||||
在StoreUnit流水线中被识别为NC地址(PBMT = NC)的内存访问指令会在LSQ中标记。LSQ负责将NC访问发送至Uncache模块。Uncache支持同时处理多个NC请求,支持请求合并,并负责向正在LoadUnit执行的NC Load转发Stores。
|
||||
|
||||
| 序号 | 功能名称 | 测试点名称 | 描述|
|
||||
| ----- |-----------------|---------------------|------------------------------------|
|
||||
| 8.1 | SU_NC | EXEC |验证NC访问允许乱序执行。|
|
||||
| 8.2 | SU_NC | FORWARD |验证Uncache模块的Store到Load转发。|
|
||||
|
||||
|
||||
### 非对齐内存访问
|
||||
|
||||
|
||||
| 序号 | 功能名称 | 测试点名称 | 描述 |
|
||||
| ----- |-----------------|---------------------|------------------------------------|
|
||||
| 9.1 | SU_MISALIGN | SCALAR_SPLIT |验证标量非对齐访问跨越16B边界时拆分为两个对齐访问。|
|
||||
| 9.2 | SU_MISALIGN | EXCEPTION |验证原子指令、MMIO、NC空间非对齐访问触发异常。|
|
||||
|
||||
|
||||
</mrs-functions>
|
||||
|
||||
|
||||
## 常量说明
|
||||
|
||||
|
||||
| 常量名 | 常量值 |
|
||||
| ------ | ------ |
|
||||
| VAddrBits | (Sv39) 39, (Sv48) 48 |
|
||||
| GPAddr Bits | (Sv39x4) 41, (Sv48x4) 50 |
|
||||
| StoreExeUnit | 2 |
|
||||
| StoreUnit | 2 x 8B/16B |
|
||||
| StoreQueue | 56 |
|
||||
| StoreBuffer | 16 x 64B |
|
||||
| VSMergeBuffer | 16 |
|
||||
| Store TLB | 48项全相联 |
|
||||
|
||||
|
||||
## 接口说明
|
||||
|
||||
### 输入接口
|
||||
| 信号名 | 方向 | 位宽 | 描述 |
|
||||
|--------|------|------|------|
|
||||
| clock | input | 1 | 时钟信号,用于同步时序 |
|
||||
| reset | input | 1 | 复位信号,用于重置模块状态 |
|
||||
| io_redirect_valid | input | 1 | 重定向有效信号,指示是否有重定向操作 |
|
||||
| io_redirect_bits_robIdx_flag | input | 1 | 重定向ROB索引标志,指示是否需要重定向 |
|
||||
| io_redirect_bits_robIdx_value | input | 8 | 重定向ROB索引值 |
|
||||
| io_redirect_bits_level | input | 1 | 重定向级别信号,表示重定向的优先级 |
|
||||
| io_csrCtrl_hd_misalign_st_enable | input | 1 | 控制Misalign Store启用信号,指示是否启用未对齐存储 |
|
||||
| io_stin_valid | input | 1 | 输入有效信号,指示是否有有效的存储指令 |
|
||||
| io_stin_bits_uop_ftqPtr_flag | input | 1 | 指令FTQ指针标志,标记指令在队列中的位置 |
|
||||
| io_stin_bits_uop_ftqPtr_value | input | 6 | 指令FTQ指针值,指示指令在队列中的位置 |
|
||||
| io_stin_bits_uop_ftqOffset | input | 4 | 指令FTQ偏移量,指示指令在队列中的偏移 |
|
||||
| io_stin_bits_uop_fuType | input | 35 | 功能单元类型,指示该指令使用的功能单元类型 |
|
||||
| io_stin_bits_uop_fuOpType | input | 9 | 功能单元操作类型,指示该指令执行的操作类型 |
|
||||
| io_stin_bits_uop_rfWen | input | 1 | 指令是否写入寄存器,表示是否更新寄存器值 |
|
||||
| io_stin_bits_uop_imm | input | 32 | 指令立即数,包含该指令中的常数值 |
|
||||
| io_stin_bits_uop_pdest | input | 8 | 指令目的寄存器编号,表示数据存储的目标寄存器 |
|
||||
| io_stin_bits_uop_robIdx_flag | input | 1 | ROB索引标志,表示是否使用ROB索引 |
|
||||
| io_stin_bits_uop_robIdx_value | input | 8 | ROB索引值,指示指令对应的ROB索引 |
|
||||
| io_stin_bits_uop_debugInfo_eliminatedMove | input | 1 | 指令优化消除的移动,标识指令是否被优化 |
|
||||
| io_stin_bits_uop_debugInfo_renameTime | input | 64 | 指令重命名时间,记录指令的重命名时间 |
|
||||
| io_stin_bits_uop_debugInfo_dispatchTime | input | 64 | 指令调度时间,记录指令进入调度队列的时间 |
|
||||
| io_stin_bits_uop_debugInfo_enqRsTime | input | 64 | 指令入队时间,记录指令进入执行队列的时间 |
|
||||
| io_stin_bits_uop_debugInfo_selectTime | input | 64 | 指令选择时间,记录指令被选中执行的时间 |
|
||||
| io_stin_bits_uop_debugInfo_issueTime | input | 64 | 指令发射时间,指示指令进入执行单元的时间 |
|
||||
| io_stin_bits_uop_debugInfo_writebackTime | input | 64 | 指令回写时间,表示指令将结果回写到寄存器的时间 |
|
||||
| io_stin_bits_uop_debugInfo_runahead_checkpoint_id | input | 64 | 预执行检查点ID,用于标记预执行阶段的检查点 |
|
||||
| io_stin_bits_uop_debugInfo_tlbFirstReqTime | input | 64 | TLB首次请求时间,记录指令发起首次TLB请求的时间 |
|
||||
| io_stin_bits_uop_debugInfo_tlbRespTime | input | 64 | TLB响应时间,记录指令收到TLB响应的时间 |
|
||||
| io_stin_bits_uop_lqIdx_flag | input | 1 | Load Queue索引标志,指示是否使用Load Queue |
|
||||
| io_stin_bits_uop_lqIdx_value | input | 7 | Load Queue索引值,标识该指令在Load Queue中的位置 |
|
||||
| io_stin_bits_uop_sqIdx_flag | input | 1 | Store Queue索引标志,指示是否使用Store Queue |
|
||||
| io_stin_bits_uop_sqIdx_value | input | 6 | Store Queue索引值,标识该指令在Store Queue中的位置 |
|
||||
| io_stin_bits_src_0 | input | 64 | 数据源0,提供给指令执行的数据源 |
|
||||
| io_stin_bits_isFirstIssue | input | 1 | 是否是第一次发射,表示指令是否首次被发射 |
|
||||
| io_misalign_stin_valid | input | 1 | Misalign Store输入有效信号,指示Misalign Store是否接收有效数据 |
|
||||
| io_misalign_stin_bits_uop_exceptionVec_0 | input | 1 | Misalign Store输入异常向量0,表示是否发生异常 |
|
||||
| io_misalign_stin_bits_uop_exceptionVec_1 | input | 1 | Misalign Store输入异常向量1,表示是否发生异常 |
|
||||
| io_misalign_stin_bits_uop_exceptionVec_2 | input | 1 | Misalign Store输入异常向量2,表示是否发生异常 |
|
||||
| io_misalign_stin_bits_uop_exceptionVec_4 | input | 1 | Misalign Store输入异常向量4,表示是否发生异常 |
|
||||
| io_misalign_stin_bits_uop_exceptionVec_5 | input | 1 | Misalign Store输入异常向量5,表示是否发生异常 |
|
||||
| io_misalign_stin_bits_uop_exceptionVec_8 | input | 1 | Misalign Store输入异常向量8,表示是否发生异常 |
|
||||
| io_misalign_stin_bits_uop_exceptionVec_9 | input | 1 | Misalign Store输入异常向量9,表示是否发生异常 |
|
||||
| io_misalign_stin_bits_uop_exceptionVec_10 | input | 1 | Misalign Store输入异常向量10,表示是否发生异常 |
|
||||
| io_misalign_stin_bits_uop_exceptionVec_11 | input | 1 | Misalign Store输入异常向量11,表示是否发生异常 |
|
||||
| io_misalign_stin_bits_uop_exceptionVec_12 | input | 1 | Misalign Store输入异常向量12,表示是否发生异常 |
|
||||
| io_misalign_stin_bits_uop_exceptionVec_13 | input | 1 | Misalign Store输入异常向量13,表示是否发生异常 |
|
||||
| io_misalign_stin_bits_uop_exceptionVec_14 | input | 1 | Misalign Store输入异常向量14,表示是否发生异常 |
|
||||
| io_misalign_stin_bits_uop_exceptionVec_16 | input | 1 | Misalign Store输入异常向量16,表示是否发生异常 |
|
||||
| io_misalign_stin_bits_uop_exceptionVec_17 | input | 1 | Misalign Store输入异常向量17,表示是否发生异常 |
|
||||
| io_misalign_stin_bits_uop_exceptionVec_18 | input | 1 | Misalign Store输入异常向量18,表示是否发生异常 |
|
||||
| io_misalign_stin_bits_uop_exceptionVec_19 | input | 1 | Misalign Store输入异常向量19,表示是否发生异常 |
|
||||
| io_misalign_stin_bits_uop_exceptionVec_20 | input | 1 | Misalign Store输入异常向量20,表示是否发生异常 |
|
||||
| io_misalign_stin_bits_uop_exceptionVec_21 | input | 1 | Misalign Store输入异常向量21,表示是否发生异常 |
|
||||
| io_misalign_stin_bits_uop_exceptionVec_22 | input | 1 | Misalign Store输入异常向量22,表示是否发生异常 |
|
||||
| io_misalign_stin_bits_uop_ftqPtr_flag | input | 1 | Misalign Store输入FTQ指针标志,表示指令位置 |
|
||||
| io_misalign_stin_bits_uop_ftqPtr_value | input | 6 | Misalign Store输入FTQ指针值,指示指令在队列中的位置 |
|
||||
| io_misalign_stin_bits_uop_ftqOffset | input | 4 | Misalign Store输入FTQ偏移量,表示指令在队列中的偏移 |
|
||||
| io_misalign_stin_bits_uop_fuType | input | 35 | Misalign Store输入功能单元类型,表示指令所需的功能单元 |
|
||||
| io_misalign_stin_bits_uop_fuOpType | input | 9 | Misalign Store输入功能单元操作类型,表示指令执行的操作类型 |
|
||||
| io_misalign_stin_bits_uop_rfWen | input | 1 | Misalign Store输入是否写入寄存器,标识是否会更新寄存器 |
|
||||
| io_misalign_stin_bits_uop_vpu_vstart | input | 8 | Misalign Store输入VPU向量起始位置,标识向量操作的起始位置 |
|
||||
| io_misalign_stin_bits_uop_vpu_nf | input | 3 | Misalign Store输入VPU向量元素数,表示该向量操作的元素数量 |
|
||||
| io_misalign_stin_bits_uop_vpu_veew | input | 2 | Misalign Store输入VPU向量元素宽度,表示向量的元素宽度 |
|
||||
| io_misalign_stin_bits_uop_uopIdx | input | 7 | Misalign Store输入操作索引,表示该指令的操作索引 |
|
||||
| io_misalign_stin_bits_uop_pdest | input | 8 | Misalign Store输入目的寄存器,表示存储的目标寄存器 |
|
||||
| io_misalign_stin_bits_uop_robIdx_flag | input | 1 | Misalign Store输入ROB索引标志,表示是否使用ROB索引 |
|
||||
| io_misalign_stin_bits_uop_robIdx_value | input | 8 | Misalign Store输入ROB索引值,表示该操作对应的ROB索引 |
|
||||
| io_misalign_stin_bits_uop_debugInfo_eliminatedMove | input | 1 | Misalign Store输入优化消除的移动,表示该操作是否被优化消除 |
|
||||
| io_misalign_stin_bits_uop_debugInfo_renameTime | input | 64 | Misalign Store输入指令重命名时间,记录指令的重命名时间 |
|
||||
| io_misalign_stin_bits_uop_debugInfo_dispatchTime | input | 64 | Misalign Store输入指令调度时间,表示指令进入调度队列的时间 |
|
||||
| io_misalign_stin_bits_uop_debugInfo_enqRsTime | input | 64 | Misalign Store输入指令入队时间,表示指令加入执行队列的时间 |
|
||||
| io_misalign_stin_bits_uop_debugInfo_selectTime | input | 64 | Misalign Store输入指令选择时间,表示指令被选中执行的时间 |
|
||||
| io_misalign_stin_bits_uop_debugInfo_issueTime | input | 64 | Misalign Store输入指令发射时间,表示指令发射到执行单元的时间 |
|
||||
| io_misalign_stin_bits_uop_debugInfo_writebackTime | input | 64 | Misalign Store输入指令回写时间,表示指令回写结果的时间 |
|
||||
| io_misalign_stin_bits_uop_debugInfo_runahead_checkpoint_id | input | 64 | Misalign Store输入指令预执行检查点ID,标记指令的预执行检查点 |
|
||||
| io_misalign_stin_bits_uop_debugInfo_tlbFirstReqTime | input | 64 | Misalign Store输入指令TLB首次请求时间,表示该指令发起首次TLB请求的时间 |
|
||||
| io_misalign_stin_bits_uop_debugInfo_tlbRespTime | input | 64 | Misalign Store输入指令TLB响应时间,表示该指令收到TLB响应的时间 |
|
||||
| io_misalign_stin_bits_uop_lqIdx_flag | input | 1 | Misalign Store输入Load Queue索引标志,表示是否使用Load Queue |
|
||||
| io_misalign_stin_bits_uop_lqIdx_value | input | 7 | Misalign Store输入Load Queue索引值,标识该指令在Load Queue中的位置 |
|
||||
| io_misalign_stin_bits_uop_sqIdx_flag | input | 1 | Misalign Store输入Store Queue索引标志,表示是否使用Store Queue |
|
||||
| io_misalign_stin_bits_uop_sqIdx_value | input | 6 | Misalign Store输入Store Queue索引值,标识该指令在Store Queue中的位置 |
|
||||
| io_misalign_stin_bits_vaddr | input | 50 | Misalign Store输入虚拟地址,表示存储请求的虚拟地址 |
|
||||
| io_misalign_stin_bits_mask | input | 16 | Misalign Store输入掩码,标识存储操作涉及的字节 |
|
||||
| io_misalign_stin_bits_isvec | input | 1 | Misalign Store输入是否为向量操作,指示是否为向量Store指令 |
|
||||
| io_misalign_stin_bits_is128bit | input | 1 | Misalign Store输入是否为128位操作,标识该操作是否为128位数据存储 |
|
||||
| io_misalign_stin_bits_isFinalSplit | input | 1 | Misalign Store输入是否为最终分割,指示该操作是否为最后一次存储操作 |
|
||||
| io_tlb_resp_valid | input | 1 | TLB响应有效信号,指示是否收到有效的TLB响应 |
|
||||
| io_tlb_resp_bits_paddr_0 | input | 48 | TLB响应物理地址0,表示TLB响应中的物理地址 |
|
||||
| io_tlb_resp_bits_gpaddr_0 | input | 64 | TLB响应通用物理地址0,提供对应的物理地址 |
|
||||
| io_tlb_resp_bits_fullva | input | 64 | TLB响应完整虚拟地址,表示该指令请求的完整虚拟地址 |
|
||||
| io_tlb_resp_bits_pbmt_0 | input | 2 | TLB响应页面映射类型0,指示TLB响应的页面映射类型 |
|
||||
| io_tlb_resp_bits_miss | input | 1 | TLB响应是否命中,指示TLB是否命中请求的地址 |
|
||||
| io_tlb_resp_bits_isForVSnonLeafPTE | input | 1 | TLB响应是否为非叶PTE,指示是否为虚拟化的非叶页面表项 |
|
||||
| io_tlb_resp_bits_excp_0_vaNeedExt | input | 1 | TLB响应是否需要扩展虚拟地址,表示是否需要扩展地址 |
|
||||
| io_tlb_resp_bits_excp_0_isHyper | input | 1 | TLB响应是否为Hypervisor模式的异常,表示TLB异常是否是超级管理模式 |
|
||||
| io_tlb_resp_bits_excp_0_gpf_st | input | 1 | TLB响应是否为一般保护异常,指示TLB请求是否发生一般保护错误 |
|
||||
| io_tlb_resp_bits_excp_0_pf_st | input | 1 | TLB响应是否为页面错误异常,指示TLB请求是否发生页面错误 |
|
||||
| io_tlb_resp_bits_excp_0_af_st | input | 1 | TLB响应是否为地址错误异常,指示TLB请求是否发生地址错误 |
|
||||
| io_tlb_resp_bits_ptwBack | input | 1 | TLB响应是否为物理地址翻译回写,表示TLB响应是否涉及物理地址回写 |
|
||||
| io_tlb_resp_bits_memidx_idx | input | 7 | TLB响应内存索引索引,指示TLB响应所在的内存块索引 |
|
||||
| io_pmp_st | input | 1 | PMP状态信号,表示是否为物理内存保护状态 |
|
||||
| io_pmp_mmio | input | 1 | PMP内存映射IO信号,表示是否涉及内存映射IO |
|
||||
| io_pmp_atomic | input | 1 | PMP原子操作信号,表示是否为原子操作请求 |
|
||||
| io_prefetch_req_bits_vaddr | input | 50 | 预取请求的虚拟地址,表示请求的虚拟地址 |
|
||||
| io_vecstin_valid | input | 1 | 向量Store输入有效信号,指示是否有有效的输入数据 |
|
||||
| io_vecstin_bits_vaddr | input | 64 | 向量Store输入虚拟地址,表示该操作的虚拟地址 |
|
||||
| io_vecstin_bits_basevaddr | input | 50 | 向量Store输入基准虚拟地址,表示向量操作的基准虚拟地址 |
|
||||
| io_vecstin_bits_mask | input | 16 | 向量Store输入掩码,表示该向量操作涉及的字节 |
|
||||
| io_vecstin_bits_alignedType | input | 3 | 向量Store输入对齐类型,表示向量操作的对齐类型 |
|
||||
| io_vecstin_bits_vecActive | input | 1 | 向量Store输入是否为激活的向量操作 |
|
||||
| io_vecstin_bits_uop_exceptionVec_4 | input | 1 | 向量Store输入异常向量4,表示是否发生异常 |
|
||||
| io_vecstin_bits_uop_exceptionVec_5 | input | 1 | 向量Store输入异常向量5,表示是否发生异常 |
|
||||
| io_vecstin_bits_uop_exceptionVec_6 | input | 1 | 向量Store输入异常向量6,表示是否发生异常 |
|
||||
| io_vecstin_bits_uop_exceptionVec_13 | input | 1 | 向量Store输入异常向量13,表示是否发生异常 |
|
||||
| io_vecstin_bits_uop_exceptionVec_21 | input | 1 | 向量Store输入异常向量21,表示是否发生异常 |
|
||||
| io_vecstin_bits_uop_ftqPtr_flag | input | 1 | 向量Store输入FTQ指针标志,表示指令在队列中的位置 |
|
||||
| io_vecstin_bits_uop_ftqPtr_value | input | 6 | 向量Store输入FTQ指针值,指示指令在队列中的位置 |
|
||||
| io_vecstin_bits_uop_ftqOffset | input | 4 | 向量Store输入FTQ偏移量,表示指令在队列中的偏移 |
|
||||
| io_vecstin_bits_uop_fuType | input | 35 | 向量Store输入功能单元类型,表示指令所需的功能单元 |
|
||||
| io_vecstin_bits_uop_fuOpType | input | 9 | 向量Store输入功能单元操作类型,表示指令执行的操作类型 |
|
||||
| io_vecstin_bits_uop_rfWen | input | 1 | 向量Store输入是否写入寄存器,表示该指令是否会更新寄存器值 |
|
||||
| io_vecstin_bits_uop_vpu_vstart | input | 8 | 向量Store输入VPU向量起始位置,标识向量操作的起始位置 |
|
||||
| io_vecstin_bits_uop_vpu_nf | input | 3 | 向量Store输入VPU向量元素数,表示该向量操作的元素数量 |
|
||||
| io_vecstin_bits_uop_vpu_veew | input | 2 | 向量Store输入VPU向量元素宽度,表示向量的元素宽度 |
|
||||
| io_vecstin_bits_uop_uopIdx | input | 7 | 向量Store输入操作索引,表示该指令的操作索引 |
|
||||
| io_vecstin_bits_uop_pdest | input | 8 | 向量Store输入目的寄存器,表示存储的目标寄存器 |
|
||||
| io_vecstin_bits_uop_robIdx_flag | input | 1 | 向量Store输入ROB索引标志,表示是否使用ROB索引 |
|
||||
| io_vecstin_bits_uop_robIdx_value | input | 8 | 向量Store输入ROB索引值,表示该操作对应的ROB索引 |
|
||||
| io_vecstin_bits_uop_debugInfo_eliminatedMove | input | 1 | 向量Store输入优化消除的移动,标识该操作是否被优化消除 |
|
||||
| io_vecstin_bits_uop_debugInfo_renameTime | input | 64 | 向量Store输入指令重命名时间,表示该Store指令的重命名时间 |
|
||||
| io_vecstin_bits_uop_debugInfo_dispatchTime | input | 64 | 向量Store输入指令调度时间,表示该Store指令的调度时间 |
|
||||
| io_vecstin_bits_uop_debugInfo_enqRsTime | input | 64 | 向量Store输入指令入队时间,表示该Store指令的入队时间 |
|
||||
| io_vecstin_bits_uop_debugInfo_selectTime | input | 64 | 向量Store输入指令选择时间,表示该Store指令的选择时间 |
|
||||
| io_vecstin_bits_uop_debugInfo_issueTime | input | 64 | 向量Store输入指令发射时间,表示该Store指令的发射时间 |
|
||||
| io_vecstin_bits_uop_debugInfo_writebackTime | input | 64 | 向量Store输入指令回写时间,表示该Store指令的回写时间 |
|
||||
| io_vecstin_bits_uop_debugInfo_runahead_checkpoint_id | input | 64 | 向量Store输入指令预执行检查点ID,标记该Store指令的预执行阶段 |
|
||||
| io_vecstin_bits_uop_debugInfo_tlbFirstReqTime | input | 64 | 向量Store输入指令TLB首次请求时间,表示该Store指令的首次TLB请求时间 |
|
||||
| io_vecstin_bits_uop_debugInfo_tlbRespTime | input | 64 | 向量Store输入指令TLB响应时间,表示该Store指令的TLB响应时间 |
|
||||
| io_vecstin_bits_uop_lqIdx_flag | input | 1 | 向量Store输入Load Queue索引标志,指示是否使用Load Queue |
|
||||
| io_vecstin_bits_uop_lqIdx_value | input | 7 | 向量Store输入Load Queue索引值,标识该指令在Load Queue中的位置 |
|
||||
| io_vecstin_bits_uop_sqIdx_flag | input | 1 | 向量Store输入Store Queue索引标志,指示是否使用Store Queue |
|
||||
| io_vecstin_bits_uop_sqIdx_value | input | 6 | 向量Store输入Store Queue索引值,标识该指令在Store Queue中的位置 |
|
||||
| io_vecstin_bits_mBIndex | input | 4 | 向量Store输入块索引,标识该向量操作的块索引 |
|
||||
| io_vecstin_bits_elemIdx | input | 8 | 向量Store输入元素索引,标识该向量操作的元素索引 |
|
||||
| io_misalign_buf_ready | input | 1 | Misalign缓冲区准备信号,指示Misalign操作是否准备好接收数据 |
|
||||
| io_fromCsrTrigger_tdataVec_0_matchType | input | 2 | 从CSR触发器接收的数据向量0匹配类型,指示匹配类型 |
|
||||
| io_fromCsrTrigger_tdataVec_0_select | input | 1 | 从CSR触发器接收的数据向量0选择标志,指示是否选择该向量 |
|
||||
| io_fromCsrTrigger_tdataVec_0_timing | input | 1 | 从CSR触发器接收的数据向量0定时标志,表示触发的定时信息 |
|
||||
| io_fromCsrTrigger_tdataVec_0_action | input | 4 | 从CSR触发器接收的数据向量0动作类型,表示触发动作 |
|
||||
| io_fromCsrTrigger_tdataVec_0_chain | input | 1 | 从CSR触发器接收的数据向量0链操作标志,指示是否为链操作 |
|
||||
| io_fromCsrTrigger_tdataVec_0_store | input | 1 | 从CSR触发器接收的数据向量0存储标志,指示是否为存储操作 |
|
||||
| io_fromCsrTrigger_tdataVec_0_tdata2 | input | 64 | 从CSR触发器接收的数据向量0附加数据2 |
|
||||
| io_fromCsrTrigger_tdataVec_1_matchType | input | 2 | 从CSR触发器接收的数据向量1匹配类型,指示匹配类型 |
|
||||
| io_fromCsrTrigger_tdataVec_1_select | input | 1 | 从CSR触发器接收的数据向量1选择标志,指示是否选择该向量 |
|
||||
| io_fromCsrTrigger_tdataVec_1_timing | input | 1 | 从CSR触发器接收的数据向量1定时标志,表示触发的定时信息 |
|
||||
| io_fromCsrTrigger_tdataVec_1_action | input | 4 | 从CSR触发器接收的数据向量1动作类型,表示触发动作 |
|
||||
| io_fromCsrTrigger_tdataVec_1_chain | input | 1 | 从CSR触发器接收的数据向量1链操作标志,指示是否为链操作 |
|
||||
| io_fromCsrTrigger_tdataVec_1_store | input | 1 | 从CSR触发器接收的数据向量1存储标志,指示是否为存储操作 |
|
||||
| io_fromCsrTrigger_tdataVec_1_tdata2 | input | 64 | 从CSR触发器接收的数据向量1附加数据2 |
|
||||
| io_fromCsrTrigger_tdataVec_2_matchType | input | 2 | 从CSR触发器接收的数据向量2匹配类型,指示匹配类型 |
|
||||
| io_fromCsrTrigger_tdataVec_2_select | input | 1 | 从CSR触发器接收的数据向量2选择标志,指示是否选择该向量 |
|
||||
| io_fromCsrTrigger_tdataVec_2_timing | input | 1 | 从CSR触发器接收的数据向量2定时标志,表示触发的定时信息 |
|
||||
| io_fromCsrTrigger_tdataVec_2_action | input | 4 | 从CSR触发器接收的数据向量2动作类型,表示触发动作 |
|
||||
| io_fromCsrTrigger_tdataVec_2_chain | input | 1 | 从CSR触发器接收的数据向量2链操作标志,指示是否为链操作 |
|
||||
| io_fromCsrTrigger_tdataVec_2_store | input | 1 | 从CSR触发器接收的数据向量2存储标志,指示是否为存储操作 |
|
||||
| io_fromCsrTrigger_tdataVec_2_tdata2 | input | 64 | 从CSR触发器接收的数据向量2附加数据2 |
|
||||
| io_fromCsrTrigger_tdataVec_3_matchType | input | 2 | 从CSR触发器接收的数据向量3匹配类型,指示匹配类型 |
|
||||
| io_fromCsrTrigger_tdataVec_3_select | input | 1 | 从CSR触发器接收的数据向量3选择标志,指示是否选择该向量 |
|
||||
| io_fromCsrTrigger_tdataVec_3_timing | input | 1 | 从CSR触发器接收的数据向量3定时标志,表示触发的定时信息 |
|
||||
| io_fromCsrTrigger_tdataVec_3_action | input | 4 | 从CSR触发器接收的数据向量3动作类型,表示触发动作 |
|
||||
| io_fromCsrTrigger_tdataVec_3_chain | input | 1 | 从CSR触发器接收的数据向量3链操作标志,指示是否为链操作 |
|
||||
| io_fromCsrTrigger_tdataVec_3_store | input | 1 | 从CSR触发器接收的数据向量3存储标志,指示是否为存储操作 |
|
||||
| io_fromCsrTrigger_tdataVec_3_tdata2 | input | 64 | 从CSR触发器接收的数据向量3附加数据2 |
|
||||
| io_fromCsrTrigger_tEnableVec_0 | input | 1 | 从CSR触发器接收的启用信号0,指示是否启用该向量 |
|
||||
| io_fromCsrTrigger_tEnableVec_1 | input | 1 | 从CSR触发器接收的启用信号1,指示是否启用该向量 |
|
||||
| io_fromCsrTrigger_tEnableVec_2 | input | 1 | 从CSR触发器接收的启用信号2,指示是否启用该向量 |
|
||||
| io_fromCsrTrigger_tEnableVec_3 | input | 1 | 从CSR触发器接收的启用信号3,指示是否启用该向量 |
|
||||
| io_fromCsrTrigger_debugMode | input | 1 | 从CSR触发器接收的调试模式标志,指示是否为调试模式 |
|
||||
| io_fromCsrTrigger_triggerCanRaiseBpExp | input | 1 | 从CSR触发器接收的触发是否可以引发BP异常标志 |
|
||||
|
||||
|
||||
### 输出接口
|
||||
| 信号名 | 方向 | 位宽 | 描述 |
|
||||
|--------|------|------|------|
|
||||
| io_stin_ready | output | 1 | 输入准备信号,指示是否准备好接收数据 |
|
||||
| io_issue_valid | output | 1 | 指令有效信号,指示该指令是否有效 |
|
||||
| io_issue_bits_uop_robIdx_flag | output | 1 | 指令ROB索引标志,标示该指令是否使用ROB索引 |
|
||||
| io_issue_bits_uop_robIdx_value | output | 8 | 指令ROB索引值,提供该指令的ROB索引 |
|
||||
| io_misalign_stin_ready | output | 1 | Misalign Store输入准备信号,指示Misalign Store是否准备好接收数据 |
|
||||
| io_misalign_stout_valid | output | 1 | Misalign Store输出有效信号,指示是否有有效的输出结果 |
|
||||
| io_misalign_stout_bits_uop_exceptionVec_3 | output | 1 | Misalign Store输出异常向量3,表示该异常是否发生 |
|
||||
| io_misalign_stout_bits_uop_exceptionVec_6 | output | 1 | Misalign Store输出异常向量6,表示该异常是否发生 |
|
||||
| io_misalign_stout_bits_uop_exceptionVec_7 | output | 1 | Misalign Store输出异常向量7,表示该异常是否发生 |
|
||||
| io_misalign_stout_bits_uop_exceptionVec_15 | output | 1 | Misalign Store输出异常向量15,表示该异常是否发生 |
|
||||
| io_misalign_stout_bits_uop_exceptionVec_23 | output | 1 | Misalign Store输出异常向量23,表示该异常是否发生 |
|
||||
| io_misalign_stout_bits_uop_trigger | output | 4 | Misalign Store输出触发器,标记触发的异常类型 |
|
||||
| io_misalign_stout_bits_paddr | output | 48 | Misalign Store输出物理地址,表示该操作的目标物理地址 |
|
||||
| io_misalign_stout_bits_mmio | output | 1 | Misalign Store输出是否为MMIO操作,标识是否为内存映射IO操作 |
|
||||
| io_misalign_stout_bits_vecActive | output | 1 | Misalign Store向量激活标志,指示是否为有效的向量存储操作 |
|
||||
| io_misalign_stout_bits_need_rep | output | 1 | Misalign Store输出是否需要重发标志,指示该操作是否需要重发 |
|
||||
| io_tlb_req_valid | output | 1 | TLB请求有效信号,指示是否有有效的TLB请求 |
|
||||
| io_tlb_req_bits_vaddr | output | 50 | TLB请求的虚拟地址,提供请求的虚拟地址 |
|
||||
| io_tlb_req_bits_fullva | output | 64 | TLB请求的完整虚拟地址,包含更多的地址信息 |
|
||||
| io_tlb_req_bits_checkfullva | output | 1 | TLB检查完整虚拟地址信号,指示是否检查完整地址 |
|
||||
| io_tlb_req_bits_hyperinst | output | 1 | TLB请求的超指令标志,表示是否为超指令请求 |
|
||||
| io_tlb_req_bits_memidx_idx | output | 7 | TLB请求的内存索引,标识内存请求的索引 |
|
||||
| io_tlb_req_bits_debug_robIdx_flag | output | 1 | TLB请求的ROB索引调试标志,调试用 |
|
||||
| io_tlb_req_bits_debug_robIdx_value | output | 8 | TLB请求的ROB索引值,调试用 |
|
||||
| io_tlb_req_bits_debug_isFirstIssue | output | 1 | TLB请求的首次发射标志,指示是否是第一次发射 |
|
||||
| io_dcache_req_valid | output | 1 | 数据缓存请求有效信号,表示是否有有效的数据缓存请求 |
|
||||
| io_lsq_valid | output | 1 | Load Store Queue有效信号,表示是否有有效的Load/Store指令 |
|
||||
| io_lsq_bits_uop_exceptionVec_0 | output | 1 | LSQ异常向量0,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_1 | output | 1 | LSQ异常向量1,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_2 | output | 1 | LSQ异常向量2,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_3 | output | 1 | LSQ异常向量3,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_4 | output | 1 | LSQ异常向量4,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_5 | output | 1 | LSQ异常向量5,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_6 | output | 1 | LSQ异常向量6,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_7 | output | 1 | LSQ异常向量7,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_8 | output | 1 | LSQ异常向量8,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_9 | output | 1 | LSQ异常向量9,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_10 | output | 1 | LSQ异常向量10,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_11 | output | 1 | LSQ异常向量11,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_12 | output | 1 | LSQ异常向量12,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_13 | output | 1 | LSQ异常向量13,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_14 | output | 1 | LSQ异常向量14,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_15 | output | 1 | LSQ异常向量15,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_16 | output | 1 | LSQ异常向量16,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_17 | output | 1 | LSQ异常向量17,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_18 | output | 1 | LSQ异常向量18,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_19 | output | 1 | LSQ异常向量19,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_20 | output | 1 | LSQ异常向量20,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_21 | output | 1 | LSQ异常向量21,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_22 | output | 1 | LSQ异常向量22,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_exceptionVec_23 | output | 1 | LSQ异常向量23,表示是否发生异常 |
|
||||
| io_lsq_bits_uop_trigger | output | 4 | Load Store Queue操作触发器,表示触发的异常类型 |
|
||||
| io_lsq_bits_uop_ftqPtr_flag | output | 1 | Load Store Queue FTQ指针标志,标记指令在队列中的位置 |
|
||||
| io_lsq_bits_uop_ftqPtr_value | output | 6 | Load Store Queue FTQ指针值,指示指令在队列中的位置 |
|
||||
| io_lsq_bits_uop_ftqOffset | output | 4 | Load Store Queue FTQ偏移量,表示指令在队列中的偏移 |
|
||||
| io_lsq_bits_uop_fuType | output | 35 | Load Store Queue功能单元类型,表示指令所需的功能单元 |
|
||||
| io_lsq_bits_uop_fuOpType | output | 9 | Load Store Queue功能单元操作类型,表示指令执行的操作类型 |
|
||||
| io_lsq_bits_uop_rfWen | output | 1 | Load Store Queue是否写入寄存器,表示该指令是否会更新寄存器值 |
|
||||
| io_lsq_bits_uop_vpu_nf | output | 3 | Load Store Queue VPU向量元素数,表示该向量操作的元素数量 |
|
||||
| io_lsq_bits_uop_vpu_veew | output | 2 | Load Store Queue VPU向量元素宽度,表示向量的元素宽度 |
|
||||
| io_lsq_bits_uop_uopIdx | output | 7 | Load Store Queue操作索引,表示该指令的操作索引 |
|
||||
| io_lsq_bits_uop_pdest | output | 8 | Load Store Queue目的寄存器,表示存储的目标寄存器 |
|
||||
| io_lsq_bits_uop_robIdx_flag | output | 1 | Load Store Queue ROB索引标志,表示是否使用ROB索引 |
|
||||
| io_lsq_bits_uop_robIdx_value | output | 8 | Load Store Queue ROB索引值,表示该Store操作对应的ROB索引 |
|
||||
| io_lsq_bits_uop_debugInfo_eliminatedMove | output | 1 | Load Store Queue优化消除的移动,标识该Store是否优化消除 |
|
||||
| io_lsq_bits_uop_debugInfo_renameTime | output | 64 | Load Store Queue重命名时间,表示该指令的重命名时间 |
|
||||
| io_lsq_bits_uop_debugInfo_dispatchTime | output | 64 | Load Store Queue调度时间,表示指令调度的时间 |
|
||||
| io_lsq_bits_uop_debugInfo_enqRsTime | output | 64 | Load Store Queue入队时间,表示指令入队的时间 |
|
||||
| io_lsq_bits_uop_debugInfo_selectTime | output | 64 | Load Store Queue选择时间,表示指令选择的时间 |
|
||||
| io_lsq_bits_uop_debugInfo_issueTime | output | 64 | Load Store Queue发射时间,表示指令发射的时间 |
|
||||
| io_lsq_bits_uop_debugInfo_writebackTime | output | 64 | Load Store Queue回写时间,表示指令回写的时间 |
|
||||
| io_lsq_bits_uop_debugInfo_runahead_checkpoint_id | output | 64 | Load Store Queue预执行检查点ID,标记该指令的预执行阶段 |
|
||||
| io_lsq_bits_uop_debugInfo_tlbFirstReqTime | output | 64 | Load Store Queue TLB首次请求时间,表示该指令发起TLB请求的时间 |
|
||||
| io_lsq_bits_uop_debugInfo_tlbRespTime | output | 64 | Load Store Queue TLB响应时间,表示该指令接收到TLB响应的时间 |
|
||||
| io_lsq_bits_uop_sqIdx_flag | output | 1 | Load Store Queue Store Queue索引标志,指示是否涉及Store Queue |
|
||||
| io_lsq_bits_uop_sqIdx_value | output | 6 | Load Store Queue Store Queue索引值,标识该指令在Store Queue中的位置 |
|
||||
| io_lsq_bits_vaddr | output | 50 | Load Store Queue虚拟地址,表示请求的虚拟地址 |
|
||||
| io_lsq_bits_fullva | output | 64 | Load Store Queue完整虚拟地址,表示请求的完整虚拟地址 |
|
||||
| io_lsq_bits_vaNeedExt | output | 1 | Load Store Queue虚拟地址是否需要扩展,指示是否需要扩展虚拟地址 |
|
||||
| io_lsq_bits_paddr | output | 48 | Load Store Queue物理地址,表示指令执行后的物理地址 |
|
||||
| io_lsq_bits_gpaddr | output | 64 | Load Store Queue通用物理地址,表示指令执行后的物理地址 |
|
||||
| io_lsq_bits_mask | output | 16 | Load Store Queue掩码,指示数据存储操作涉及的字节 |
|
||||
| io_lsq_bits_wlineflag | output | 1 | Load Store Queue是否为写回标志,表示是否写回数据 |
|
||||
| io_lsq_bits_miss | output | 1 | Load Store Queue未命中标志,表示该存储请求是否命中 |
|
||||
| io_lsq_bits_nc | output | 1 | Load Store Queue非一致性标志,表示该操作是否为非一致性操作 |
|
||||
| io_lsq_bits_isHyper | output | 1 | Load Store Queue是否为超级管理模式,指示该操作是否为超级管理模式 |
|
||||
| io_lsq_bits_isForVSnonLeafPTE | output | 1 | Load Store Queue是否为非叶PTE,指示是否为非叶页面表项操作 |
|
||||
| io_lsq_bits_isvec | output | 1 | Load Store Queue是否为向量操作,指示是否为向量Store指令 |
|
||||
| io_lsq_bits_isFrmMisAlignBuf | output | 1 | Load Store Queue是否为误对齐缓存操作,指示是否为误对齐操作 |
|
||||
| io_lsq_bits_isMisalign | output | 1 | Load Store Queue是否为误对齐操作,指示是否为误对齐存储操作 |
|
||||
| io_lsq_bits_misalignWith16Byte | output | 1 | Load Store Queue是否为16字节对齐的误对齐操作 |
|
||||
| io_lsq_bits_updateAddrValid | output | 1 | Load Store Queue地址更新有效信号,指示是否有有效的地址更新 |
|
||||
| io_lsq_replenish_uop_exceptionVec_3 | output | 1 | Load Store Queue重新补充操作异常向量3,表示该异常是否发生 |
|
||||
| io_lsq_replenish_uop_exceptionVec_6 | output | 1 | Load Store Queue重新补充操作异常向量6,表示该异常是否发生 |
|
||||
| io_lsq_replenish_uop_exceptionVec_15 | output | 1 | Load Store Queue重新补充操作异常向量15,表示该异常是否发生 |
|
||||
| io_lsq_replenish_uop_exceptionVec_23 | output | 1 | Load Store Queue重新补充操作异常向量23,表示该异常是否发生 |
|
||||
| io_lsq_replenish_uop_uopIdx | output | 7 | Load Store Queue重新补充操作索引,表示该指令的操作索引 |
|
||||
| io_lsq_replenish_uop_robIdx_flag | output | 1 | Load Store Queue重新补充操作ROB索引标志,指示是否使用ROB索引 |
|
||||
| io_lsq_replenish_uop_robIdx_value | output | 8 | Load Store Queue重新补充操作ROB索引值,表示该操作对应的ROB索引 |
|
||||
| io_lsq_replenish_fullva | output | 64 | Load Store Queue重新补充操作完整虚拟地址,表示该操作的完整虚拟地址 |
|
||||
| io_lsq_replenish_vaNeedExt | output | 1 | Load Store Queue重新补充操作是否需要扩展虚拟地址 |
|
||||
| io_lsq_replenish_gpaddr | output | 64 | Load Store Queue重新补充操作物理地址,表示该操作的目标物理地址 |
|
||||
| io_lsq_replenish_af | output | 1 | Load Store Queue重新补充操作是否为地址错误标志,表示是否发生地址错误 |
|
||||
| io_lsq_replenish_mmio | output | 1 | Load Store Queue重新补充操作是否为MMIO操作,标示是否为内存映射IO操作 |
|
||||
| io_lsq_replenish_memBackTypeMM | output | 1 | Load Store Queue重新补充操作是否为内存回写标志,表示是否为内存回写操作 |
|
||||
| io_lsq_replenish_atomic | output | 1 | Load Store Queue重新补充操作是否为原子操作,指示是否为原子操作 |
|
||||
| io_lsq_replenish_hasException | output | 1 | Load Store Queue重新补充操作是否包含异常,表示是否发生异常 |
|
||||
| io_lsq_replenish_isHyper | output | 1 | Load Store Queue重新补充操作是否为超级管理模式操作,表示是否为超级管理模式操作 |
|
||||
| io_lsq_replenish_isForVSnonLeafPTE | output | 1 | Load Store Queue重新补充操作是否为非叶PTE,指示是否为非叶页面表项操作 |
|
||||
| io_lsq_replenish_isvec | output | 1 | Load Store Queue重新补充操作是否为向量操作,表示是否为向量Store指令 |
|
||||
| io_lsq_replenish_updateAddrValid | output | 1 | Load Store Queue重新补充操作地址更新有效信号,指示是否有有效的地址更新 |
|
||||
| io_feedback_slow_valid | output | 1 | 反馈慢操作有效信号,表示是否有有效的反馈慢操作 |
|
||||
| io_feedback_slow_bits_robIdx_flag | output | 1 | 反馈慢操作ROB索引标志,指示是否涉及ROB索引 |
|
||||
| io_feedback_slow_bits_robIdx_value | output | 8 | 反馈慢操作ROB索引值,表示该操作对应的ROB索引 |
|
||||
| io_feedback_slow_bits_hit | output | 1 | 反馈慢操作命中标志,表示是否命中目标地址 |
|
||||
| io_feedback_slow_bits_flushState | output | 1 | 反馈慢操作刷新状态标志,表示是否需要刷新状态 |
|
||||
| io_feedback_slow_bits_sqIdx_flag | output | 1 | 反馈慢操作Store Queue索引标志,指示是否涉及Store Queue |
|
||||
| io_feedback_slow_bits_sqIdx_value | output | 6 | 反馈慢操作Store Queue索引值,标识该操作在Store Queue中的位置 |
|
||||
| io_feedback_slow_bits_lqIdx_flag | output | 1 | 反馈慢操作Load Queue索引标志,指示是否涉及Load Queue |
|
||||
| io_feedback_slow_bits_lqIdx_value | output | 7 | 反馈慢操作Load Queue索引值,标识该操作在Load Queue中的位置 |
|
||||
| io_stld_nuke_query_valid | output | 1 | Store Load查询有效信号,表示是否有有效的Store Load查询 |
|
||||
| io_stld_nuke_query_bits_robIdx_flag | output | 1 | Store Load查询ROB索引标志,指示是否涉及ROB |
|
||||
| io_stld_nuke_query_bits_robIdx_value | output | 8 | Store Load查询的ROB索引值 |
|
||||
| io_stld_nuke_query_bits_paddr | output | 48 | Store Load查询的物理地址 |
|
||||
| io_stld_nuke_query_bits_mask | output | 16 | Store Load查询的掩码 |
|
||||
| io_stld_nuke_query_bits_matchLine | output | 1 | Store Load查询的匹配行标志,表示是否匹配 |
|
||||
| io_stout_valid | output | 1 | Store操作有效信号,表示是否有有效的Store操作 |
|
||||
| io_stout_bits_uop_exceptionVec_3 | output | 1 | Store输出异常向量3,表示该异常是否发生 |
|
||||
| io_stout_bits_uop_exceptionVec_6 | output | 1 | Store输出异常向量6,表示该异常是否发生 |
|
||||
| io_stout_bits_uop_exceptionVec_7 | output | 1 | Store输出异常向量7,表示该异常是否发生 |
|
||||
| io_stout_bits_uop_exceptionVec_15 | output | 1 | Store输出异常向量15,表示该异常是否发生 |
|
||||
| io_stout_bits_uop_exceptionVec_23 | output | 1 | Store输出异常向量23,表示该异常是否发生 |
|
||||
| io_stout_bits_uop_trigger | output | 4 | Store操作触发器,标记触发的异常类型 |
|
||||
| io_stout_bits_uop_rfWen | output | 1 | Store是否写入寄存器,表示该指令是否会更新寄存器 |
|
||||
| io_stout_bits_uop_pdest | output | 8 | Store目的寄存器,表示该操作的目标寄存器 |
|
||||
| io_stout_bits_uop_robIdx_flag | output | 1 | Store ROB索引标志,指示是否使用ROB索引 |
|
||||
| io_stout_bits_uop_robIdx_value | output | 8 | Store ROB索引值,表示该操作对应的ROB索引 |
|
||||
| io_stout_bits_uop_debugInfo_eliminatedMove | output | 1 | Store输出优化消除的移动,表示该Store是否优化消除 |
|
||||
| io_stout_bits_uop_debugInfo_renameTime | output | 64 | Store输出指令重命名时间,表示该Store指令的重命名时间 |
|
||||
| io_stout_bits_uop_debugInfo_dispatchTime | output | 64 | Store输出指令调度时间,表示该Store指令的调度时间 |
|
||||
| io_stout_bits_uop_debugInfo_enqRsTime | output | 64 | Store输出指令入队时间,表示该Store指令的入队时间 |
|
||||
| io_stout_bits_uop_debugInfo_selectTime | output | 64 | Store输出指令选择时间,表示该Store指令的选择时间 |
|
||||
| io_stout_bits_uop_debugInfo_issueTime | output | 64 | Store输出指令发射时间,表示该Store指令的发射时间 |
|
||||
| io_stout_bits_uop_debugInfo_writebackTime | output | 64 | Store输出指令回写时间,表示该Store指令的回写时间 |
|
||||
| io_stout_bits_uop_debugInfo_runahead_checkpoint_id | output | 64 | Store输出指令预执行检查点ID,标记该Store指令的预执行检查点 |
|
||||
| io_stout_bits_uop_debugInfo_tlbFirstReqTime | output | 64 | Store输出指令TLB首次请求时间,表示该Store指令的首次TLB请求时间 |
|
||||
| io_stout_bits_uop_debugInfo_tlbRespTime | output | 64 | Store输出指令TLB响应时间,表示该Store指令的TLB响应时间 |
|
||||
| io_stout_bits_uop_sqIdx_flag | output | 1 | Store输出Store Queue索引标志,指示是否使用Store Queue |
|
||||
| io_stout_bits_uop_sqIdx_value | output | 6 | Store输出Store Queue索引值,标识该Store操作在Store Queue中的位置 |
|
||||
| io_stout_bits_debug_isMMIO | output | 1 | Store输出是否为MMIO操作,标示该Store操作是否为内存映射IO操作 |
|
||||
| io_stout_bits_debug_isNC | output | 1 | Store输出是否为非一致性操作,指示该操作是否为非一致性操作 |
|
||||
| io_stout_bits_debug_paddr | output | 48 | Store输出物理地址,表示该操作的目标物理地址 |
|
||||
| io_stout_bits_debug_vaddr | output | 50 | Store输出虚拟地址,表示该操作的目标虚拟地址 |
|
||||
| io_vecstout_valid | output | 1 | 向量Store结果有效信号,表示是否有有效的向量Store结果 |
|
||||
| io_vecstout_bits_mBIndex | output | 4 | 向量Store块索引,标识该向量Store操作的块索引 |
|
||||
| io_vecstout_bits_hit | output | 1 | 向量Store命中标志,表示该向量Store操作是否命中目标地址 |
|
||||
| io_vecstout_bits_trigger | output | 4 | 向量Store触发器,标识触发的异常类型 |
|
||||
| io_vecstout_bits_exceptionVec_3 | output | 1 | 向量Store异常向量3,表示该异常是否发生 |
|
||||
| io_vecstout_bits_exceptionVec_6 | output | 1 | 向量Store异常向量6,表示该异常是否发生 |
|
||||
| io_vecstout_bits_exceptionVec_7 | output | 1 | 向量Store异常向量7,表示该异常是否发生 |
|
||||
| io_vecstout_bits_exceptionVec_15 | output | 1 | 向量Store异常向量15,表示该异常是否发生 |
|
||||
| io_vecstout_bits_exceptionVec_23 | output | 1 | 向量Store异常向量23,表示该异常是否发生 |
|
||||
| io_vecstout_bits_hasException | output | 1 | 向量Store是否有异常,表示是否发生异常 |
|
||||
| io_vecstout_bits_vaddr | output | 64 | 向量Store虚拟地址,表示该操作的目标虚拟地址 |
|
||||
| io_vecstout_bits_vaNeedExt | output | 1 | 向量Store虚拟地址扩展标志,指示是否需要扩展虚拟地址 |
|
||||
| io_vecstout_bits_gpaddr | output | 64 | 向量Store物理地址,表示该操作的目标物理地址 |
|
||||
| io_vecstout_bits_isForVSnonLeafPTE | output | 1 | 向量Store是否为非叶PTE,表示是否为非叶页面表项操作 |
|
||||
| io_vecstout_bits_vstart | output | 8 | 向量Store起始位置,表示该向量操作的起始位置 |
|
||||
| io_vecstout_bits_elemIdx | output | 8 | 向量Store元素索引,标识该向量操作的元素索引 |
|
||||
| io_vecstout_bits_mask | output | 16 | 向量Store掩码,标识该向量操作的有效位掩码 |
|
||||
| io_st_mask_out_valid | output | 1 | Store Mask操作有效信号,指示是否有有效的存储掩码操作 |
|
||||
| io_st_mask_out_bits_sqIdx_value | output | 6 | Store Mask操作Store Queue索引值,表示该掩码操作在Store Queue中的位置 |
|
||||
| io_st_mask_out_bits_mask | output | 16 | Store Mask操作掩码,表示存储操作涉及的字节掩码 |
|
||||
| io_vecstin_ready | output | 1 | 向量Store输入准备信号,指示向量Store是否准备好接收数据 |
|
||||
| io_misalign_buf_valid | output | 1 | Misalign缓冲区有效信号,表示是否有有效的数据 |
|
||||
| io_misalign_buf_bits_uop_exceptionVec_0 | output | 1 | Misalign缓冲区异常向量0,表示是否发生异常 |
|
||||
| io_misalign_buf_bits_uop_exceptionVec_1 | output | 1 | Misalign缓冲区异常向量1,表示是否发生异常 |
|
||||
| io_misalign_buf_bits_uop_exceptionVec_2 | output | 1 | Misalign缓冲区异常向量2,表示是否发生异常 |
|
||||
| io_misalign_buf_bits_uop_exceptionVec_4 | output | 1 | Misalign缓冲区异常向量4,表示是否发生异常 |
|
||||
| io_misalign_buf_bits_uop_exceptionVec_5 | output | 1 | Misalign缓冲区异常向量5,表示是否发生异常 |
|
||||
| io_misalign_buf_bits_uop_exceptionVec_8 | output | 1 | Misalign缓冲区异常向量8,表示是否发生异常 |
|
||||
| io_misalign_buf_bits_uop_exceptionVec_9 | output | 1 | Misalign缓冲区异常向量9,表示是否发生异常 |
|
||||
| io_misalign_buf_bits_uop_exceptionVec_10 | output | 1 | Misalign缓冲区异常向量10,表示是否发生异常 |
|
||||
| io_misalign_buf_bits_uop_exceptionVec_11 | output | 1 | Misalign缓冲区异常向量11,表示是否发生异常 |
|
||||
| io_misalign_buf_bits_uop_exceptionVec_12 | output | 1 | Misalign缓冲区异常向量12,表示是否发生异常 |
|
||||
| io_misalign_buf_bits_uop_exceptionVec_13 | output | 1 | Misalign缓冲区异常向量13,表示是否发生异常 |
|
||||
| io_misalign_buf_bits_uop_exceptionVec_14 | output | 1 | Misalign缓冲区异常向量14,表示是否发生异常 |
|
||||
| io_misalign_buf_bits_uop_exceptionVec_16 | output | 1 | Misalign缓冲区异常向量16,表示是否发生异常 |
|
||||
| io_misalign_buf_bits_uop_exceptionVec_17 | output | 1 | Misalign缓冲区异常向量17,表示是否发生异常 |
|
||||
| io_misalign_buf_bits_uop_exceptionVec_18 | output | 1 | Misalign缓冲区异常向量18,表示是否发生异常 |
|
||||
| io_misalign_buf_bits_uop_exceptionVec_19 | output | 1 | Misalign缓冲区异常向量19,表示是否发生异常 |
|
||||
| io_misalign_buf_bits_uop_exceptionVec_20 | output | 1 | Misalign缓冲区异常向量20,表示是否发生异常 |
|
||||
| io_misalign_buf_bits_uop_exceptionVec_21 | output | 1 | Misalign缓冲区异常向量21,表示是否发生异常 |
|
||||
| io_misalign_buf_bits_uop_exceptionVec_22 | output | 1 | Misalign缓冲区异常向量22,表示是否发生异常 |
|
||||
| io_misalign_buf_bits_uop_trigger | output | 4 | Misalign缓冲区触发器,表示触发的异常类型 |
|
||||
| io_misalign_buf_bits_uop_ftqPtr_flag | output | 1 | Misalign缓冲区FTQ指针标志,表示指令位置 |
|
||||
| io_misalign_buf_bits_uop_ftqPtr_value | output | 6 | Misalign缓冲区FTQ指针值,指示指令在队列中的位置 |
|
||||
| io_misalign_buf_bits_uop_ftqOffset | output | 4 | Misalign缓冲区FTQ偏移量,表示指令在队列中的偏移 |
|
||||
| io_misalign_buf_bits_uop_fuType | output | 35 | Misalign缓冲区功能单元类型,表示指令所需的功能单元 |
|
||||
| io_misalign_buf_bits_uop_fuOpType | output | 9 | Misalign缓冲区功能单元操作类型,表示指令执行的操作类型 |
|
||||
| io_misalign_buf_bits_uop_rfWen | output | 1 | Misalign缓冲区是否写入寄存器,表示该指令是否会更新寄存器 |
|
||||
| io_misalign_buf_bits_uop_vpu_vstart | output | 8 | Misalign缓冲区VPU向量起始位置,表示向量操作的起始位置 |
|
||||
| io_misalign_buf_bits_uop_vpu_nf | output | 3 | Misalign缓冲区VPU向量元素数,表示该向量操作的元素数量 |
|
||||
| io_misalign_buf_bits_uop_vpu_veew | output | 2 | Misalign缓冲区VPU向量元素宽度,表示向量的元素宽度 |
|
||||
| io_misalign_buf_bits_uop_uopIdx | output | 7 | Misalign缓冲区操作索引,表示该指令的操作索引 |
|
||||
| io_misalign_buf_bits_uop_pdest | output | 8 | Misalign缓冲区目的寄存器,表示存储的目标寄存器 |
|
||||
| io_misalign_buf_bits_uop_robIdx_flag | output | 1 | Misalign缓冲区ROB索引标志,表示是否使用ROB索引 |
|
||||
| io_misalign_buf_bits_uop_robIdx_value | output | 8 | Misalign缓冲区ROB索引值,表示该操作对应的ROB索引 |
|
||||
| io_misalign_buf_bits_uop_debugInfo_eliminatedMove | output | 1 | Misalign缓冲区优化消除的移动,标识该操作是否被优化消除 |
|
||||
| io_misalign_buf_bits_uop_debugInfo_renameTime | output | 64 | Misalign缓冲区指令重命名时间,记录指令的重命名时间 |
|
||||
| io_misalign_buf_bits_uop_debugInfo_dispatchTime | output | 64 | Misalign缓冲区指令调度时间,记录指令调度的时间 |
|
||||
| io_misalign_buf_bits_uop_debugInfo_enqRsTime | output | 64 | Misalign缓冲区指令入队时间,表示指令加入执行队列的时间 |
|
||||
| io_misalign_buf_bits_uop_debugInfo_selectTime | output | 64 | Misalign缓冲区指令选择时间,表示指令被选中执行的时间 |
|
||||
| io_misalign_buf_bits_uop_debugInfo_issueTime | output | 64 | Misalign缓冲区指令发射时间,表示指令发射到执行单元的时间 |
|
||||
| io_misalign_buf_bits_uop_debugInfo_writebackTime | output | 64 | Misalign缓冲区指令回写时间,表示指令回写的时间 |
|
||||
| io_misalign_buf_bits_uop_debugInfo_runahead_checkpoint_id | output | 64 | Misalign缓冲区预执行检查点ID,标记指令的预执行阶段 |
|
||||
| io_misalign_buf_bits_uop_debugInfo_tlbFirstReqTime | output | 64 | Misalign缓冲区指令TLB首次请求时间,表示该指令发起TLB请求的时间 |
|
||||
| io_misalign_buf_bits_uop_debugInfo_tlbRespTime | output | 64 | Misalign缓冲区指令TLB响应时间,表示该指令收到TLB响应的时间 |
|
||||
| io_misalign_buf_bits_uop_lqIdx_flag | output | 1 | Misalign缓冲区Load Queue索引标志,指示是否使用Load Queue |
|
||||
| io_misalign_buf_bits_uop_lqIdx_value | output | 7 | Misalign缓冲区Load Queue索引值,标识该指令在Load Queue中的位置 |
|
||||
| io_misalign_buf_bits_uop_sqIdx_flag | output | 1 | Misalign缓冲区Store Queue索引标志,指示是否使用Store Queue |
|
||||
| io_misalign_buf_bits_uop_sqIdx_value | output | 6 | Misalign缓冲区Store Queue索引值,标识该指令在Store Queue中的位置 |
|
||||
| io_misalign_buf_bits_vaddr | output | 50 | Misalign缓冲区虚拟地址,表示存储请求的虚拟地址 |
|
||||
| io_misalign_buf_bits_vaNeedExt | output | 1 | Misalign缓冲区虚拟地址是否需要扩展,指示是否需要扩展虚拟地址 |
|
||||
| io_misalign_buf_bits_paddr | output | 48 | Misalign缓冲区物理地址,表示该操作的目标物理地址 |
|
||||
| io_misalign_buf_bits_gpaddr | output | 64 | Misalign缓冲区通用物理地址,表示该操作的目标物理地址 |
|
||||
| io_misalign_buf_bits_mask | output | 16 | Misalign缓冲区掩码,表示存储操作涉及的字节 |
|
||||
| io_misalign_buf_bits_hasException | output | 1 | Misalign缓冲区是否包含异常,表示是否发生异常 |
|
||||
| io_misalign_buf_bits_isForVSnonLeafPTE | output | 1 | Misalign缓冲区是否为非叶PTE操作,表示是否为非叶页面表项操作 |
|
||||
| io_misalign_buf_bits_isvec | output | 1 | Misalign缓冲区是否为向量操作,表示是否为向量Store指令 |
|
||||
| io_misalign_buf_bits_elemIdx | output | 8 | Misalign缓冲区元素索引,表示该向量操作的元素索引 |
|
||||
| io_misalign_buf_bits_alignedType | output | 3 | Misalign缓冲区对齐类型,表示向量操作的对齐类型 |
|
||||
| io_misalign_buf_bits_mbIndex | output | 4 | Misalign缓冲区块索引,表示该向量操作的块索引 |
|
||||
| io_s0_s1_valid | output | 1 | S0/S1有效信号,指示S0/S1阶段是否有效 |
|
||||
|
||||
## 接口时序
|
||||
|
||||
### 接口时序实例
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
## 测试点总表
|
||||
|
||||
<mrs-testpoints>
|
||||
|
||||
| 序号 | 功能名称 | 测试点名称 | 描述 |
|
||||
| ----- |-----------------|---------------------|------------------------------------|
|
||||
| 1.1 | SU_DISPATCH | SCALAR_DISPATCH | 验证标量Store指令派发时分配一个StoreQueue条目。|
|
||||
| 1.2 |SU_DISPATCH | VECTOR_DISPATCH | 验证向量Store指令的一个uop分配多个LSQ条目(根据元素数量)。|
|
||||
| 2.1 | SU_STORE | S0_ADDRESS_CALC |验证s0阶段地址计算和仲裁是否按三类指令优先级排序。|
|
||||
| 2.2 | SU_STORE | S1_RAW_CHECK|验证s1阶段RAW冒险检测发生。|
|
||||
| 2.3 | SU_STORE | S2_SQ_MARK_READY |验证s2阶段StoreQueue地址就绪标记。|
|
||||
| 3.1 | SU_VECTOR | SPLIT |验证向量指令拆分正确性。|
|
||||
| 3.2 | SU_VECTOR | OFFSET |验证向量元素偏移地址计算。|
|
||||
| 4 | SU_REPLAY | TLB_MISS | 验证TLB缺失时Store指令重发。 |
|
||||
| 5.1 | SU_RAW | VIOLATION |验证RAW违例检测。|
|
||||
| 5.2 | SU_RAW | RECOVERY_MECH |验证检测到RAW违例后的恢复(流水线清空)。|
|
||||
| 6.1 | SU_SBUFFER | WRITE_MERGE|验证同一缓存块的多个Store在SBuffer中合并。|
|
||||
| 6.2 | SU_SBUFFER | PLRU_REPLACE |验证SBuffer满时按PLRU策略替换。|
|
||||
| 7.1 | SU_MMIO | ORDER |验证MMIO指令强顺序执行(成为ROB头)。|
|
||||
| 7.2 | SU_MMIO | EXCEPTION |验证原子/向量指令访问MMIO触发异常。|
|
||||
| 8.1 | SU_NC | EXEC |验证NC访问允许乱序执行。|
|
||||
| 8.2 | SU_NC | FORWARD |验证Uncache模块的Store到Load转发。|
|
||||
| 9.1 | SU_MISALIGN | SCALAR_SPLIT |验证标量非对齐访问跨越16B边界时拆分为两个对齐访问。|
|
||||
| 9.2 | SU_MISALIGN | EXCEPTION |验证原子指令、MMIO、NC空间非对齐访问触发异常。|
|
||||
|
||||
</mrs-testpoints>
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: LSU
|
||||
linkTitle: LSU
|
||||
weight: 20
|
||||
---
|
||||
|
||||
**本文档参考[香山LSQ设计文档](https://github.com/OpenXiangShan/XiangShan-Design-Doc/blob/master/docs/zh/memblock/LSU/index.md)写成**
|
||||
|
||||
|
||||
请注意,本文档撰写的测试点仅供参考,如能补充更多测试点,最终获得的奖励可能更高!
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 236 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue