Compare commits
23 Commits
fix_small_
...
main
| Author | SHA1 | Date |
|---|---|---|
|
|
091dec1502 | |
|
|
23b0e623f5 | |
|
|
b7c388616e | |
|
|
c1d672fd96 | |
|
|
e357515e02 | |
|
|
df5aeb317b | |
|
|
9af0faab74 | |
|
|
0f7c903f22 | |
|
|
2381940d63 | |
|
|
08c6f2574c | |
|
|
fe0726aed8 | |
|
|
0a8bf2e0e3 | |
|
|
a30b9eae7c | |
|
|
649faaed13 | |
|
|
04e95823c3 | |
|
|
0183b43290 | |
|
|
78582e48f0 | |
|
|
873b483d54 | |
|
|
4c25d694f7 | |
|
|
502b2c0f51 | |
|
|
d354b0077c | |
|
|
ba7c8a02b2 | |
|
|
fee7bc61cb |
|
|
@ -15,6 +15,9 @@ rtl/*
|
|||
documents/static/data/*
|
||||
!documents/static/data/README.txt
|
||||
|
||||
# Auto-generated mapping file
|
||||
.dirmap.autogen
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
|
|
|
|||
32
Makefile
|
|
@ -12,6 +12,12 @@ space:= $(empty) $(empty)
|
|||
|
||||
PROCESSED_DUTS := $(subst $(comma),$(space),$(strip $(DUTS)))
|
||||
|
||||
TIMESTAMP := $(shell date +'%Y-%m-%d %H:%M:%S,%3N')
|
||||
CURDIR := $(abspath .)
|
||||
|
||||
INFO_PREFIX := [$(TIMESTAMP),$(CURDIR)/Makefile,INFO]
|
||||
WARN_PREFIX := [$(TIMESTAMP),$(CURDIR)/Makefile,Warning]
|
||||
|
||||
all: rtl dut test_all
|
||||
|
||||
update_python_deps:
|
||||
|
|
@ -36,39 +42,41 @@ check_all_dut:
|
|||
test: check_dut
|
||||
@python3 run.py --config $(CFG) $(KV) -- $(REPORT) -vs $(target) $(args)
|
||||
|
||||
check_dut:
|
||||
check_dut: generate_dirmap
|
||||
@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 \
|
||||
grep ".* --> .* --> $$CLEANED_TARGET$$" .dirmap.autogen | while read -r MATCHED_LINE; do \
|
||||
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"; \
|
||||
echo "$(INFO_PREFIX) Building missing DUT for target $$t: $$DUT_NAME"; \
|
||||
$(MAKE) dut DUTS="$$DUT_NAME" NO_GEN_DIRMAP=1; \
|
||||
fi; \
|
||||
else \
|
||||
echo "No mapping found for target: $$t in dir_map.f, skipping check" >&2; \
|
||||
fi; \
|
||||
done; \
|
||||
done; \
|
||||
fi
|
||||
@rm -f .dirmap.autogen
|
||||
|
||||
dut: rtl
|
||||
dut: rtl $(if $(NO_GEN_DIRMAP),,generate_dirmap)
|
||||
@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); \
|
||||
dir=$$(awk -F' --> ' -v dut="$$d" '$$1 == dut {print $$2; exit}' .dirmap.autogen); \
|
||||
if [ -z "$$dir" ]; then \
|
||||
echo "No mapping found for DUT: $$d in dir_map.f, skipping deletion" >&2; \
|
||||
echo "$(WARN_PREFIX) No mapping found for DUT: $$d in .dirmap.autogen, skipping deletion" >&2; \
|
||||
continue; \
|
||||
fi; \
|
||||
echo "Cleaning dut/$$dir"; \
|
||||
echo "$(INFO_PREFIX) Cleaning dut/$$dir"; \
|
||||
rm -rf "dut/$$dir"; \
|
||||
done; \
|
||||
fi
|
||||
@python3 run.py --config $(CFG) --build $(DUTS) $(args)
|
||||
@if [ -z "$(NO_GEN_DIRMAP)" ]; then rm -f .dirmap.autogen; fi
|
||||
|
||||
generate_dirmap:
|
||||
@python3 -c "from comm.functions import generate_dirmap; generate_dirmap()"
|
||||
|
||||
rtl:
|
||||
@python3 run.py --config $(CFG) --download-rtl $(args)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
from .itlb_wrapper import *
|
||||
from .itlb_agent import *
|
||||
from .itlb_consts import *
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
#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 toffee.agent import *
|
||||
|
||||
class TLBRequestorAgent(Agent):
|
||||
def __init__(self, bundle):
|
||||
super().__init__(bundle.step)
|
||||
self.bundle = bundle
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
#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.
|
||||
#**************************************************************************************/
|
||||
|
||||
class consts():
|
||||
Width = 3
|
||||
nRespDups = 1
|
||||
|
|
@ -0,0 +1,435 @@
|
|||
#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 toffee
|
||||
import os
|
||||
import pytest
|
||||
import random
|
||||
from datetime import datetime
|
||||
import toffee.funcov as fc
|
||||
|
||||
from dut.TLB import *
|
||||
from .itlb_consts import *
|
||||
from queue import Queue
|
||||
from comm import get_version_checker, get_out_dir, UT_FCOV
|
||||
from toffee_test.reporter import set_func_coverage, set_line_coverage
|
||||
|
||||
# Set the toffee log level to ERROR
|
||||
toffee.setup_logging(toffee.ERROR)
|
||||
|
||||
# Version check
|
||||
version_check = get_version_checker("openxiangshan-kmh-*")
|
||||
|
||||
# Create a function coverage group
|
||||
g = fc.CovGroup(UT_FCOV("../../../CLASSIC"))
|
||||
|
||||
def init_itlb_funcov(tlb, g: fc.CovGroup):
|
||||
"""
|
||||
Add watch points to collect function coverage
|
||||
"""
|
||||
# TODO: add global watchpoint here
|
||||
g.add_watch_point(tlb.ctrl.io_ptw_req_0_valid, {
|
||||
"invalid": fc.Eq(0),
|
||||
"valid": fc.Eq(1),
|
||||
}, name = "PTW_REQ_0_VALID_GLOBAL")
|
||||
g.add_watch_point(tlb.ctrl.io_ptw_req_1_valid, {
|
||||
"invalid": fc.Eq(0),
|
||||
"valid": fc.Eq(1),
|
||||
}, name = "PTW_REQ_1_VALID_GLOBAL")
|
||||
g.add_watch_point(tlb.ptw_req_0.vpn, {
|
||||
"zero": fc.Eq(0),
|
||||
}, name = "PTW_REQ_0_VPN_IS_ZERO_GLOBAL")
|
||||
g.add_watch_point(tlb.ptw_req_0.vpn, {
|
||||
"max": fc.Eq(2 ** 38 - 1),
|
||||
}, name = "PTW_REQ_0_VPN_IS_MAX_GLOBAL")
|
||||
g.add_watch_point(tlb.ptw_req_1.vpn, {
|
||||
"zero": fc.Eq(0),
|
||||
}, name = "PTW_REQ_1_VPN_IS_ZERO_GLOBAL")
|
||||
g.add_watch_point(tlb.ptw_req_1.vpn, {
|
||||
"max": fc.Eq(2 ** 38 - 1),
|
||||
}, name = "PTW_REQ_1_VPN_IS_MAX_GLOBAL")
|
||||
|
||||
@pytest.fixture()
|
||||
def tlb_fixture(request):
|
||||
version_check()
|
||||
test_name = request.node.name
|
||||
wave_file = get_out_dir("TLB_%s.fst" % test_name)
|
||||
coverage_file = get_out_dir("TLB_%s.dat" % test_name)
|
||||
coverage_dir = os.path.dirname(coverage_file)
|
||||
os.makedirs(coverage_dir, exist_ok=True)
|
||||
random.seed(datetime.now().timestamp() * 10000)
|
||||
v = 1919810 + random.randint(24, 114514)
|
||||
dut = DUTTLB(
|
||||
[f"+verilator+seed+{v}", ],
|
||||
waveform_filename=wave_file,
|
||||
coverage_filename=coverage_file)
|
||||
tlb = TLBWrapper(dut)
|
||||
init_itlb_funcov(tlb, g)
|
||||
|
||||
yield tlb
|
||||
|
||||
tlb.dut.Finish()
|
||||
set_line_coverage(request, coverage_file)
|
||||
set_func_coverage(request, g)
|
||||
g.clear()
|
||||
|
||||
class ControlBundle(toffee.Bundle):
|
||||
signals = [
|
||||
"reset",
|
||||
"io_sfence_valid",
|
||||
"io_requestor_2_resp_ready",
|
||||
"io_requestor_2_resp_valid",
|
||||
"io_ptw_req_0_valid",
|
||||
"io_ptw_req_1_valid",
|
||||
"io_ptw_req_2_ready",
|
||||
"io_ptw_req_2_valid",
|
||||
"io_ptw_resp_valid",
|
||||
"io_ptw_resp_bits_s2xlate",
|
||||
"io_ptw_resp_bits_getGpa"
|
||||
]
|
||||
|
||||
class CsrBundle(toffee.Bundle):
|
||||
def __init__(self, dut):
|
||||
super().__init__()
|
||||
self.satp = toffee.Bundle.new_class_from_xport(dut.io_csr_satp ).from_prefix("io_csr_satp_" )
|
||||
self.vsatp = toffee.Bundle.new_class_from_xport(dut.io_csr_vsatp).from_prefix("io_csr_vsatp_")
|
||||
self.hgatp = toffee.Bundle.new_class_from_xport(dut.io_csr_hgatp).from_prefix("io_csr_hgatp_")
|
||||
self.priv = toffee.Bundle.new_class_from_xport(dut.io_csr_priv ).from_prefix("io_csr_priv_" )
|
||||
|
||||
class Requestor_0_Bundle(toffee.Bundle):
|
||||
def __init__(self, dut):
|
||||
super().__init__()
|
||||
self.req = toffee.Bundle.new_class_from_xport(dut.io_requestor_0_req).from_prefix("io_requestor_0_req_")
|
||||
self.resp = toffee.Bundle.new_class_from_xport(dut.io_requestor_0_resp_bits).from_prefix("io_requestor_0_resp_bits_")
|
||||
|
||||
class Requestor_1_Bundle(toffee.Bundle):
|
||||
def __init__(self, dut):
|
||||
super().__init__()
|
||||
self.req = toffee.Bundle.new_class_from_xport(dut.io_requestor_1_req).from_prefix("io_requestor_1_req_")
|
||||
self.resp = toffee.Bundle.new_class_from_xport(dut.io_requestor_1_resp_bits).from_prefix("io_requestor_1_resp_bits_")
|
||||
|
||||
class Requestor_2_Bundle(toffee.Bundle):
|
||||
def __init__(self, dut):
|
||||
super().__init__()
|
||||
self.req = toffee.Bundle.new_class_from_xport(dut.io_requestor_2_req).from_prefix("io_requestor_2_req_")
|
||||
self.resp = toffee.Bundle.new_class_from_xport(dut.io_requestor_2_resp_bits).from_prefix("io_requestor_2_resp_bits_")
|
||||
|
||||
class TLBWrapper(toffee.Bundle):
|
||||
"""
|
||||
Support full TLB I/O.
|
||||
"""
|
||||
def __init__(self, dut: DUTTLB):
|
||||
super().__init__()
|
||||
self.dut = dut
|
||||
self.dut.InitClock("clock")
|
||||
# control
|
||||
self.ctrl = ControlBundle()
|
||||
self.ctrl.set_write_mode(toffee.WriteMode.Imme)
|
||||
self.ctrl.set_write_mode(toffee.WriteMode.Fall)
|
||||
# sfence
|
||||
self.sfence = toffee.Bundle.new_class_from_xport(dut.io_sfence_bits).from_prefix("io_sfence_bits_")
|
||||
# csr
|
||||
self.csr = CsrBundle(dut)
|
||||
# requestor
|
||||
self.requestor_0 = Requestor_0_Bundle(dut)
|
||||
self.requestor_1 = Requestor_1_Bundle(dut)
|
||||
self.requestor_2 = Requestor_2_Bundle(dut)
|
||||
# flushPipe
|
||||
for i in range(consts.Width):
|
||||
setattr(self, f"flushPipe_{i}", toffee.Bundle.from_prefix(f"io_flushPipe_{i}" , dut))
|
||||
self.flushPipe = [getattr(self, f"flushPipe_{i}") for i in range(consts.Width)]
|
||||
# ptw
|
||||
self.ptw_req_0 = toffee.Bundle.new_class_from_xport(dut.io_ptw_req_0_bits).from_prefix("io_ptw_req_0_bits_")
|
||||
self.ptw_req_1 = toffee.Bundle.new_class_from_xport(dut.io_ptw_req_1_bits).from_prefix("io_ptw_req_1_bits_")
|
||||
self.ptw_req_2 = toffee.Bundle.new_class_from_xport(dut.io_ptw_req_2_bits).from_prefix("io_ptw_req_2_bits_")
|
||||
self.ptw_resp_s1 = toffee.Bundle.new_class_from_xport(dut.io_ptw_resp_bits_s1).from_prefix("io_ptw_resp_bits_s1_")
|
||||
self.ptw_resp_s2 = toffee.Bundle.new_class_from_xport(dut.io_ptw_resp_bits_s2).from_prefix("io_ptw_resp_bits_s2_")
|
||||
# data queue
|
||||
self.data_to_drive = Queue()
|
||||
|
||||
self.bind(self.dut)
|
||||
|
||||
def connect_check(self):
|
||||
"""
|
||||
Verify if the DUT interface signals are properly bound to the Python signal pins.
|
||||
"""
|
||||
print("----------------------------- CONNECT CHECK -----------------------------")
|
||||
print(">>> unconnected signals : ", self.detect_unconnected_signals(self.dut))
|
||||
print(">>> muticonnected signals: ", self.detect_multiple_connections(self.dut))
|
||||
specific_signal_name = "io_flushPipe_2"
|
||||
print(">>> specific connectivity check:", specific_signal_name, ":", self.detect_specific_connectivity(specific_signal_name, self.flushPipe[2]))
|
||||
print("-------------------------------------------------------------------------")
|
||||
|
||||
def set_default_value(self):
|
||||
"""
|
||||
To eliminate interference caused by randomly initialized signal values
|
||||
at simulation startup, you must first zeroize all signals (set to 0)
|
||||
before asserting the reset signal. This ensures clean initialization
|
||||
of the module's internal state.
|
||||
"""
|
||||
# sfence
|
||||
self.ctrl.io_sfence_valid.value = 0
|
||||
self.sfence.rs1.value = 0
|
||||
self.sfence.rs2.value = 0
|
||||
self.sfence.addr.value = 0
|
||||
self.sfence.id.value = 0
|
||||
self.sfence.flushPipe.value = 0
|
||||
self.sfence.hv.value = 0
|
||||
self.sfence.hg.value = 0
|
||||
# csr
|
||||
self.csr.satp.mode.value = 9
|
||||
self.csr.satp.asid.value = 0
|
||||
self.csr.satp.changed.value = 0
|
||||
self.csr.vsatp.mode.value = 0
|
||||
self.csr.vsatp.asid.value = 0
|
||||
self.csr.vsatp.changed.value = 0
|
||||
self.csr.hgatp.mode.value = 0
|
||||
self.csr.hgatp.vmid.value = 0
|
||||
self.csr.hgatp.changed.value = 0
|
||||
self.csr.priv.virt.value = 0
|
||||
self.csr.priv.imode.value = 0
|
||||
# requestor
|
||||
self.requestor_0.req.valid.value = 0
|
||||
self.requestor_0.req.bits_vaddr.value = 0
|
||||
self.requestor_1.req.valid.value = 0
|
||||
self.requestor_1.req.bits_vaddr.value = 0
|
||||
self.requestor_2.req.valid.value = 0
|
||||
self.requestor_2.req.bits_vaddr.value = 0
|
||||
self.ctrl.io_requestor_2_resp_ready.value = 1
|
||||
# flushPipe
|
||||
for i in range(consts.Width):
|
||||
self.flushPipe[i].value = 0
|
||||
# ptw
|
||||
self.ctrl.io_ptw_req_2_ready.value = 1
|
||||
self.ctrl.io_ptw_resp_valid.value = 0
|
||||
self.ctrl.io_ptw_resp_bits_s2xlate.value = 0
|
||||
self.ptw_resp_s1.entry_tag.value = 0
|
||||
self.ptw_resp_s1.entry_asid.value = 0
|
||||
self.ptw_resp_s1.entry_vmid.value = 0
|
||||
self.ptw_resp_s1.entry_perm_d.value = 0
|
||||
self.ptw_resp_s1.entry_perm_a.value = 0
|
||||
self.ptw_resp_s1.entry_perm_g.value = 0
|
||||
self.ptw_resp_s1.entry_perm_u.value = 0
|
||||
self.ptw_resp_s1.entry_perm_x.value = 0
|
||||
self.ptw_resp_s1.entry_perm_w.value = 0
|
||||
self.ptw_resp_s1.entry_perm_r.value = 0
|
||||
self.ptw_resp_s1.entry_level.value = 0
|
||||
self.ptw_resp_s1.entry_ppn.value = 0
|
||||
self.ptw_resp_s1.addr_low.value = 0
|
||||
self.ptw_resp_s1.ppn_low_0.value = 0
|
||||
self.ptw_resp_s1.ppn_low_1.value = 0
|
||||
self.ptw_resp_s1.ppn_low_2.value = 0
|
||||
self.ptw_resp_s1.ppn_low_3.value = 0
|
||||
self.ptw_resp_s1.ppn_low_4.value = 0
|
||||
self.ptw_resp_s1.ppn_low_5.value = 0
|
||||
self.ptw_resp_s1.ppn_low_6.value = 0
|
||||
self.ptw_resp_s1.ppn_low_7.value = 0
|
||||
self.ptw_resp_s1.valididx_0.value = 0
|
||||
self.ptw_resp_s1.valididx_1.value = 0
|
||||
self.ptw_resp_s1.valididx_2.value = 0
|
||||
self.ptw_resp_s1.valididx_3.value = 0
|
||||
self.ptw_resp_s1.valididx_4.value = 0
|
||||
self.ptw_resp_s1.valididx_5.value = 0
|
||||
self.ptw_resp_s1.valididx_6.value = 0
|
||||
self.ptw_resp_s1.valididx_7.value = 0
|
||||
self.ptw_resp_s1.pteidx_0.value = 0
|
||||
self.ptw_resp_s1.pteidx_1.value = 0
|
||||
self.ptw_resp_s1.pteidx_2.value = 0
|
||||
self.ptw_resp_s1.pteidx_3.value = 0
|
||||
self.ptw_resp_s1.pteidx_4.value = 0
|
||||
self.ptw_resp_s1.pteidx_5.value = 0
|
||||
self.ptw_resp_s1.pteidx_6.value = 0
|
||||
self.ptw_resp_s1.pteidx_7.value = 0
|
||||
self.ptw_resp_s1.pf.value = 0
|
||||
self.ptw_resp_s1.af.value = 0
|
||||
self.ptw_resp_s2.entry_tag.value = 0
|
||||
self.ptw_resp_s2.entry_vmid.value = 0
|
||||
self.ptw_resp_s2.entry_ppn.value = 0
|
||||
self.ptw_resp_s2.entry_perm_d.value = 0
|
||||
self.ptw_resp_s2.entry_perm_a.value = 0
|
||||
self.ptw_resp_s2.entry_perm_g.value = 0
|
||||
self.ptw_resp_s2.entry_perm_u.value = 0
|
||||
self.ptw_resp_s2.entry_perm_x.value = 0
|
||||
self.ptw_resp_s2.entry_perm_w.value = 0
|
||||
self.ptw_resp_s2.entry_perm_r.value = 0
|
||||
self.ptw_resp_s2.entry_level.value = 0
|
||||
self.ptw_resp_s2.gpf.value = 0
|
||||
self.ptw_resp_s2.gaf.value = 0
|
||||
self.ctrl.io_ptw_resp_bits_getGpa.value = 0
|
||||
self.dut.Step(2)
|
||||
|
||||
####################### TLB Basic Function Start From Here #######################
|
||||
def reset(self):
|
||||
"""
|
||||
reset
|
||||
"""
|
||||
self.dut.reset.value = 1
|
||||
self.dut.Step(10)
|
||||
self.dut.reset.value = 0
|
||||
# print(">>> RESET FINISHED !")
|
||||
|
||||
def gene_rand_TLBreq(self):
|
||||
"""
|
||||
generate random TLB request
|
||||
"""
|
||||
req_valid = random.choice([0, 1])
|
||||
req_vaddr = random.randint(0, 2 ** 50 - 1)
|
||||
return req_valid, req_vaddr
|
||||
|
||||
def rand_req0(self):
|
||||
"""
|
||||
send random TLB request from requestor0
|
||||
"""
|
||||
req_0_valid, req_0_vaddr = self.gene_rand_TLBreq()
|
||||
self.requestor_0.req.valid.value = req_0_valid
|
||||
self.requestor_0.req.bits_vaddr.value = req_0_vaddr
|
||||
return req_0_valid, req_0_vaddr
|
||||
|
||||
def rand_req1(self):
|
||||
"""
|
||||
send random TLB request from requestor1
|
||||
"""
|
||||
req_1_valid, req_1_vaddr = self.gene_rand_TLBreq()
|
||||
self.requestor_1.req.valid.value = req_1_valid
|
||||
self.requestor_1.req.bits_vaddr.value = req_1_vaddr
|
||||
return req_1_valid, req_1_vaddr
|
||||
|
||||
def rand_req2(self):
|
||||
"""
|
||||
send random TLB request from requestor2
|
||||
"""
|
||||
req_2_valid, req_2_vaddr = self.gene_rand_TLBreq()
|
||||
self.requestor_2.req.valid.value = req_2_valid
|
||||
self.requestor_2.req.bits_vaddr.value = req_2_vaddr
|
||||
return req_2_valid, req_2_vaddr
|
||||
|
||||
def rand_req(self):
|
||||
"""
|
||||
send random TLB request from all requestors
|
||||
"""
|
||||
req_0_valid, req_0_vaddr = self.rand_req0()
|
||||
req_1_valid, req_1_vaddr = self.rand_req1()
|
||||
req_2_valid, req_2_vaddr = self.rand_req2()
|
||||
return req_0_valid, req_0_vaddr, req_1_valid, req_1_vaddr, req_2_valid, req_2_vaddr
|
||||
|
||||
def gene_rand_ptw_resp(self, vpn, s2xlate):
|
||||
"""
|
||||
generate random PTW response by s2xlate
|
||||
"""
|
||||
self.ctrl.io_ptw_resp_valid = 1
|
||||
self.ctrl.io_ptw_resp_bits_s2xlate = s2xlate
|
||||
# nos2xlate (s2xlate == 0b00)
|
||||
self.ptw_resp_s1.entry_tag = vpn >> 12
|
||||
self.ptw_resp_s1.entry_asid
|
||||
|
||||
class TLBrwWrapper(toffee.Bundle):
|
||||
"""
|
||||
Support TLB read/write only.
|
||||
"""
|
||||
def __init__(self, dut: DUTTLB):
|
||||
super().__init__()
|
||||
self.dut = dut
|
||||
self.dut.InitClock("clock")
|
||||
# control
|
||||
self.ctrl = ControlBundle()
|
||||
self.ctrl.set_write_mode(toffee.WriteMode.Imme)
|
||||
self.ctrl.set_write_mode(toffee.WriteMode.Fall)
|
||||
# requestor
|
||||
self.requestor_0 = Requestor_0_Bundle(dut)
|
||||
self.requestor_1 = Requestor_1_Bundle(dut)
|
||||
self.requestor_2 = Requestor_2_Bundle(dut)
|
||||
# ptw
|
||||
self.ptw_req_0 = toffee.Bundle.new_class_from_xport(dut.io_ptw_req_0_bits).from_prefix("io_ptw_req_0_bits_")
|
||||
self.ptw_req_1 = toffee.Bundle.new_class_from_xport(dut.io_ptw_req_1_bits).from_prefix("io_ptw_req_1_bits_")
|
||||
self.ptw_req_2 = toffee.Bundle.new_class_from_xport(dut.io_ptw_req_2_bits).from_prefix("io_ptw_req_2_bits_")
|
||||
self.ptw_resp_s1 = toffee.Bundle.new_class_from_xport(dut.io_ptw_resp_bits_s1).from_prefix("io_ptw_resp_bits_s1_")
|
||||
self.ptw_resp_s2 = toffee.Bundle.new_class_from_xport(dut.io_ptw_resp_bits_s2).from_prefix("io_ptw_resp_bits_s2_")
|
||||
|
||||
self.bind(self.dut)
|
||||
|
||||
def set_default_value(self):
|
||||
# requestor
|
||||
self.requestor_0.req.valid.value = 0
|
||||
self.requestor_0.req.bits_vaddr.value = 0
|
||||
self.requestor_1.req.valid.value = 0
|
||||
self.requestor_1.req.bits_vaddr.value = 0
|
||||
self.requestor_2.req.valid.value = 0
|
||||
self.requestor_2.req.bits_vaddr.value = 0
|
||||
self.ctrl.io_requestor_2_resp_ready.value = 0
|
||||
# ptw
|
||||
self.ctrl.io_ptw_req_2_ready.value = 0
|
||||
self.ctrl.io_ptw_resp_valid.value = 0
|
||||
self.ctrl.io_ptw_resp_bits_s2xlate.value = 0
|
||||
self.ptw_resp_s1.entry_tag.value = 0
|
||||
self.ptw_resp_s1.entry_asid.value = 0
|
||||
self.ptw_resp_s1.entry_vmid.value = 0
|
||||
self.ptw_resp_s1.entry_perm_d.value = 0
|
||||
self.ptw_resp_s1.entry_perm_a.value = 0
|
||||
self.ptw_resp_s1.entry_perm_g.value = 0
|
||||
self.ptw_resp_s1.entry_perm_u.value = 0
|
||||
self.ptw_resp_s1.entry_perm_x.value = 0
|
||||
self.ptw_resp_s1.entry_perm_w.value = 0
|
||||
self.ptw_resp_s1.entry_perm_r.value = 0
|
||||
self.ptw_resp_s1.entry_level.value = 0
|
||||
self.ptw_resp_s1.entry_ppn.value = 0
|
||||
self.ptw_resp_s1.addr_low.value = 0
|
||||
self.ptw_resp_s1.ppn_low_0.value = 0
|
||||
self.ptw_resp_s1.ppn_low_1.value = 0
|
||||
self.ptw_resp_s1.ppn_low_2.value = 0
|
||||
self.ptw_resp_s1.ppn_low_3.value = 0
|
||||
self.ptw_resp_s1.ppn_low_4.value = 0
|
||||
self.ptw_resp_s1.ppn_low_5.value = 0
|
||||
self.ptw_resp_s1.ppn_low_6.value = 0
|
||||
self.ptw_resp_s1.ppn_low_7.value = 0
|
||||
self.ptw_resp_s1.valididx_0.value = 0
|
||||
self.ptw_resp_s1.valididx_1.value = 0
|
||||
self.ptw_resp_s1.valididx_2.value = 0
|
||||
self.ptw_resp_s1.valididx_3.value = 0
|
||||
self.ptw_resp_s1.valididx_4.value = 0
|
||||
self.ptw_resp_s1.valididx_5.value = 0
|
||||
self.ptw_resp_s1.valididx_6.value = 0
|
||||
self.ptw_resp_s1.valididx_7.value = 0
|
||||
self.ptw_resp_s1.pteidx_0.value = 0
|
||||
self.ptw_resp_s1.pteidx_1.value = 0
|
||||
self.ptw_resp_s1.pteidx_2.value = 0
|
||||
self.ptw_resp_s1.pteidx_3.value = 0
|
||||
self.ptw_resp_s1.pteidx_4.value = 0
|
||||
self.ptw_resp_s1.pteidx_5.value = 0
|
||||
self.ptw_resp_s1.pteidx_6.value = 0
|
||||
self.ptw_resp_s1.pteidx_7.value = 0
|
||||
self.ptw_resp_s1.pf.value = 0
|
||||
self.ptw_resp_s1.af.value = 0
|
||||
self.ptw_resp_s2.entry_tag.value = 0
|
||||
self.ptw_resp_s2.entry_vmid.value = 0
|
||||
self.ptw_resp_s2.entry_ppn.value = 0
|
||||
self.ptw_resp_s2.entry_perm_d.value = 0
|
||||
self.ptw_resp_s2.entry_perm_a.value = 0
|
||||
self.ptw_resp_s2.entry_perm_g.value = 0
|
||||
self.ptw_resp_s2.entry_perm_u.value = 0
|
||||
self.ptw_resp_s2.entry_perm_x.value = 0
|
||||
self.ptw_resp_s2.entry_perm_w.value = 0
|
||||
self.ptw_resp_s2.entry_perm_r.value = 0
|
||||
self.ptw_resp_s2.entry_level.value = 0
|
||||
self.ptw_resp_s2.gpf.value = 0
|
||||
self.ptw_resp_s2.gaf.value = 0
|
||||
self.ctrl.io_ptw_resp_bits_getGpa.value = 0
|
||||
|
||||
def reset(self):
|
||||
self.dut.reset.value = 0
|
||||
self.dut.Step(2)
|
||||
self.dut.reset.value = 1
|
||||
self.dut.Step(10)
|
||||
self.dut.reset.value = 0
|
||||
self.dut.Step(2)
|
||||
# print(">>> RESET FINISHED !")
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
#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 .env import *
|
||||
import inspect
|
||||
|
||||
### CASE EXAMPLE
|
||||
# Running the following test case will show a pass:
|
||||
def test_receive_ptw_resp_nonstage(tlb_fixture):
|
||||
"""
|
||||
Func: receive PTW response under nonstage condition and stored it into TLB entry
|
||||
subfunc1: TODO
|
||||
"""
|
||||
# connect to fixture
|
||||
tlb = tlb_fixture
|
||||
# add watch point
|
||||
# case_name = inspect.currentframe().f_back.f_code.co_name
|
||||
# g.add_watch_point(tlb.TODO, {
|
||||
# "TODO": fc.Eq(TODO),
|
||||
# "TODO": lambda TODO: TODO.value == TODO,
|
||||
# }, name = f"{case_name}: TODO")
|
||||
# set default value
|
||||
tlb.set_default_value()
|
||||
# reset
|
||||
tlb.reset()
|
||||
|
||||
# add clock
|
||||
tlb.dut.xclock.StepRis(lambda _: g.sample())
|
||||
# start
|
||||
for _ in range(1000):
|
||||
for _ in range(30):
|
||||
# add signal and assign to dut
|
||||
vaddr = random.randint(0, 2 ** 50 - 1)
|
||||
asid = random.randint(0, 2 ** 16 - 1)
|
||||
vpn = vaddr >> 12
|
||||
offset = vaddr & 0xfff
|
||||
s2xlate = 0b00
|
||||
ppn = tlb.rand_ptw_resp(vpn, asid, s2xlate)
|
||||
|
||||
tlb.csr.satp.asid.value = asid
|
||||
|
||||
# step to next cycle
|
||||
tlb.dut.Step()
|
||||
|
||||
# check whether PTW resp is stored
|
||||
tlb.requestor_0.req.valid.value = 1
|
||||
tlb.requestor_0.req.bits_vaddr.value = (vpn << 12) | offset
|
||||
|
||||
# step to next cycle
|
||||
tlb.dut.Step(2)
|
||||
|
||||
# assert result
|
||||
assert(tlb.requestor_0.resp.paddr_0.value == ((ppn << 12) | offset))
|
||||
assert(tlb.requestor_0.resp.miss.value == 0)
|
||||
# reset
|
||||
tlb.reset()
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
#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 .env import *
|
||||
import inspect
|
||||
|
||||
### CASE EXAMPLE
|
||||
# Running the following test case will show a pass:
|
||||
# In this test case, a single port (Requestor0) sends an address translation
|
||||
# request to TLB. TLB is expected to return a miss in the next cycle and initiate
|
||||
# a request to the PTW. At this point, you can simultaneously verify whether the
|
||||
# vpn in the PTW request is correct.
|
||||
def test_req_from_ifu_and_icache_rand_vaddr_rand_valid_single_port(tlb_fixture):
|
||||
"""
|
||||
Func: Compare the PTW request with the reference:
|
||||
subfunc1: rand vaddr
|
||||
subfunc2: rand valid
|
||||
subfunc3: single port 0 / 1 / 2
|
||||
"""
|
||||
# connect to fixture
|
||||
tlb = tlb_fixture
|
||||
# add watch point
|
||||
case_name = inspect.currentframe().f_back.f_code.co_name
|
||||
g.add_watch_point(tlb.requestor_0.resp.miss, {
|
||||
"miss": fc.Eq(1),
|
||||
"hit": fc.Eq(0),
|
||||
}, name = f"{case_name}: REQUESTOR_0_MISS")
|
||||
g.add_watch_point(tlb.requestor_1.resp.miss, {
|
||||
"miss": fc.Eq(1),
|
||||
"hit": fc.Eq(0),
|
||||
}, name = f"{case_name}: REQUESTOR_1_MISS")
|
||||
g.add_watch_point(tlb.ctrl.io_ptw_req_0_valid, {
|
||||
"valid": fc.Eq(1),
|
||||
"invalid": fc.Eq(0),
|
||||
}, name = f"{case_name}: PTW_REQ_0_VALID")
|
||||
g.add_watch_point(tlb.ctrl.io_ptw_req_1_valid, {
|
||||
"valid": fc.Eq(1),
|
||||
"invalid": fc.Eq(0),
|
||||
}, name = f"{case_name}: PTW_REQ_1_VALID")
|
||||
# set default value
|
||||
tlb.set_default_value()
|
||||
# reset
|
||||
tlb.reset()
|
||||
|
||||
# add clock
|
||||
tlb.dut.xclock.StepRis(lambda _: g.sample())
|
||||
# start
|
||||
for _ in range(10000):
|
||||
# add signal and assign to dut
|
||||
req_0_valid, req_0_vaddr = tlb.rand_req0()
|
||||
# step to next cycle
|
||||
tlb.dut.Step(2)
|
||||
|
||||
# assert result
|
||||
assert (tlb.ctrl.io_ptw_req_0_valid.value == req_0_valid)
|
||||
if (req_0_valid):
|
||||
assert (tlb.ptw_req_0.vpn.value == req_0_vaddr >> 12)
|
||||
|
||||
|
||||
#################################### TODO EXAMPLE ####################################
|
||||
# TODO: Add Requestor1 Verification
|
||||
# The existing test case provides verification methods for sending TLB requests
|
||||
# from requestor0. Now, we need to verify whether sending TLB requests from requestor1
|
||||
# also meets the expected behavior. Modify the test case such that in each cycle, a
|
||||
# TLB request is sent from either requestor0 or requestor1, with strict attention to
|
||||
# ensuring that both ports are not active simultaneously.
|
||||
|
||||
for _ in range(10000):
|
||||
# Randomly select between Requestor0 or Requestor1
|
||||
port = random.choice([0, 1])
|
||||
if port == 0:
|
||||
req_valid, vaddr = tlb.rand_req0()
|
||||
else:
|
||||
req_valid, vaddr = tlb.rand_req1()
|
||||
# step to next cycle
|
||||
tlb.dut.Step(2)
|
||||
|
||||
# assert result
|
||||
if port == 0:
|
||||
assert (tlb.ctrl.io_ptw_req_0_valid.value == req_valid)
|
||||
if req_valid:
|
||||
assert (tlb.ptw_req_0.vpn.value == vaddr >> 12)
|
||||
else:
|
||||
assert (tlb.ctrl.io_ptw_req_1_valid.value == req_valid)
|
||||
if req_valid:
|
||||
assert (tlb.ptw_req_1.vpn.value == vaddr >> 12)
|
||||
|
||||
######################################################################################
|
||||
######################################################################################
|
||||
# TODO: Add Requestor2 Verification (Critical Differences)
|
||||
# Requestor2 uses BLOCKING access protocol (vs. Non-blocking on 0/1)
|
||||
######################################################################################
|
||||
|
||||
### CASE EXAMPLE
|
||||
# Running the following test case will show a pass:
|
||||
def test_req_from_icache_rand_vaddr_rand_valid_muti_port(tlb_fixture):
|
||||
"""
|
||||
Func: compare the PTW request with the reference:
|
||||
subfunc1: rand vaddr
|
||||
subfunc2: rand valid
|
||||
subfunc3: muti port 0 & 1
|
||||
"""
|
||||
# connect to fixture
|
||||
tlb = tlb_fixture
|
||||
# add watch point
|
||||
case_name = inspect.currentframe().f_back.f_code.co_name
|
||||
g.add_watch_point(tlb.requestor_0.resp.miss, {
|
||||
"miss": fc.Eq(1),
|
||||
"hit": fc.Eq(0),
|
||||
}, name = f"{case_name}: REQUESTOR_0_MISS")
|
||||
g.add_watch_point(tlb.requestor_1.resp.miss, {
|
||||
"miss": fc.Eq(1),
|
||||
"hit": fc.Eq(0),
|
||||
}, name = f"{case_name}: REQUESTOR_1_MISS")
|
||||
g.add_watch_point(tlb.ctrl.io_ptw_req_0_valid, {
|
||||
"valid": fc.Eq(1),
|
||||
"invalid": fc.Eq(0),
|
||||
}, name = f"{case_name}: PTW_REQ_0_VALID")
|
||||
g.add_watch_point(tlb.ctrl.io_ptw_req_1_valid, {
|
||||
"valid": fc.Eq(1),
|
||||
"invalid": fc.Eq(0),
|
||||
}, name = f"{case_name}: PTW_REQ_1_VALID")
|
||||
# set default value
|
||||
tlb.set_default_value()
|
||||
# reset
|
||||
tlb.reset()
|
||||
|
||||
# add clock
|
||||
tlb.dut.xclock.StepRis(lambda _: g.sample())
|
||||
# start
|
||||
for _ in range(10000):
|
||||
# add signal and assign to dut
|
||||
req_0_valid, req_0_vaddr = tlb.rand_req0()
|
||||
req_1_valid, req_1_vaddr = tlb.rand_req1()
|
||||
# step to next cycle
|
||||
tlb.dut.Step(2)
|
||||
|
||||
# assert result
|
||||
assert (tlb.ctrl.io_ptw_req_0_valid.value == req_0_valid)
|
||||
assert (tlb.ctrl.io_ptw_req_1_valid.value == req_1_valid)
|
||||
if (req_0_valid):
|
||||
assert (tlb.ptw_req_0.vpn.value == req_0_vaddr >> 12)
|
||||
if (req_1_valid):
|
||||
assert (tlb.ptw_req_1.vpn.value == req_1_vaddr >> 12)
|
||||
|
||||
|
||||
def test_req_from_ifu_and_icache_rand_vaddr_rand_valid_single_port2(tlb_fixture):
|
||||
"""
|
||||
Func: Compare the PTW request with the reference for Requestor2:
|
||||
subfunc1: rand vaddr
|
||||
subfunc2: rand valid
|
||||
subfunc3: single port 2 (blocking access)
|
||||
"""
|
||||
tlb = tlb_fixture
|
||||
# Add watch points for Requestor2 and PTW request
|
||||
case_name = inspect.currentframe().f_back.f_code.co_name
|
||||
|
||||
g.add_watch_point(tlb.requestor_2.resp.miss, {
|
||||
"miss": fc.Eq(1),
|
||||
"hit": fc.Eq(0),
|
||||
}, name=f"{case_name}: REQUESTOR_2_MISS")
|
||||
g.add_watch_point(tlb.ctrl.io_ptw_req_2_valid, {
|
||||
"valid": fc.Eq(1),
|
||||
"invalid": fc.Eq(0),
|
||||
}, name=f"{case_name}: PTW_REQ_2_VALID")
|
||||
|
||||
g.add_watch_point(tlb.ctrl.io_ptw_req_2_ready, {
|
||||
"valid": fc.Eq(1),
|
||||
"invalid": fc.Eq(0),
|
||||
}, name=f"{case_name}: PTW_REQ_2_READY")
|
||||
|
||||
# Set default values
|
||||
tlb.set_default_value()
|
||||
# Reset DUT
|
||||
tlb.reset()
|
||||
|
||||
# Add clock and coverage sampling
|
||||
tlb.dut.xclock.StepRis(lambda _: g.sample())
|
||||
|
||||
for _ in range(10000):
|
||||
# Generate random request for Requestor2
|
||||
req_2_valid, req_2_vaddr = tlb.rand_req2()
|
||||
|
||||
# Step to next cycle to allow the request to propagate
|
||||
tlb.dut.Step(2)
|
||||
# Assert the PTW request validity and VPN
|
||||
if tlb.ctrl.io_ptw_req_2_ready.value == 1:
|
||||
assert (tlb.ctrl.io_ptw_req_2_valid.value == req_2_valid)
|
||||
if req_2_valid:
|
||||
assert (tlb.ptw_req_2.vpn.value == req_2_vaddr >> 12)
|
||||
tlb.reset()
|
||||
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
#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 .env import *
|
||||
import inspect
|
||||
|
||||
### CASE EXAMPLE
|
||||
# Running this test case will report a BUG:
|
||||
# The reset function fails. When a TLB request is initiated simultaneously
|
||||
# with a reset, it is observed that all signals are not properly reset. After
|
||||
# the reset ends, TLB will send a request to PTW while returning a miss to the
|
||||
# upper-level module.
|
||||
#
|
||||
# NOTE: This test case is solely intended to demonstrate the scenario where
|
||||
# the bug occurs. In practice, initiating a request (req) simultaneously with
|
||||
# a reset signal constitutes an invalid input and violates the protocol
|
||||
# specification.
|
||||
# def test_reset_when_request(tlb_fixture):
|
||||
# """
|
||||
# Check reset
|
||||
# Request & reset in the same cycle
|
||||
# """
|
||||
# # connect to fixture
|
||||
# tlb = tlb_fixture
|
||||
# # add watch point
|
||||
# case_name = inspect.currentframe().f_back.f_code.co_name
|
||||
# g.add_watch_point(tlb.ctrl.reset, {
|
||||
# "reset": fc.Eq(1),
|
||||
# "notreset": fc.Eq(0),
|
||||
# }, name = f"{case_name}: RESET")
|
||||
# # set default value
|
||||
# tlb.set_default_value()
|
||||
# # reset
|
||||
# tlb.reset()
|
||||
|
||||
# # add clock
|
||||
# tlb.dut.xclock.StepRis(lambda _: g.sample())
|
||||
# # start
|
||||
# for _ in range(10000):
|
||||
# # add signal and assign to dut
|
||||
# _, _, _, _, _, _ = tlb.rand_req()
|
||||
# tlb.ctrl.reset.value = 1
|
||||
|
||||
# # step to next cycle
|
||||
# tlb.dut.Step(10)
|
||||
|
||||
# # add signal and assign to dut
|
||||
# tlb.ctrl.reset.value = 0
|
||||
# # step to next cycle
|
||||
# tlb.dut.Step(5)
|
||||
|
||||
# # assert result
|
||||
# assert (tlb.requestor_0.resp.miss.value == 0)
|
||||
# assert (tlb.requestor_1.resp.miss.value == 0)
|
||||
# assert (tlb.ctrl.io_ptw_req_0_valid.value == 0)
|
||||
# assert (tlb.ctrl.io_ptw_req_1_valid.value == 0)
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import base64
|
||||
import re
|
||||
|
|
@ -487,3 +488,39 @@ def get_all_rtl_files(top_module, cfg):
|
|||
|
||||
get_rtl_helper(top_module)
|
||||
return list(module_path_map.values())
|
||||
|
||||
|
||||
def generate_dirmap(scripts_dir="../scripts", output_file="../.dirmap.autogen"):
|
||||
script_root = os.path.abspath(os.path.dirname(__file__))
|
||||
scripts_dir = os.path.join(script_root, scripts_dir)
|
||||
output_path = os.path.abspath(os.path.join(script_root, output_file))
|
||||
|
||||
script_files = [
|
||||
f for f in os.listdir(scripts_dir)
|
||||
if f.startswith("build_ut_") and f.endswith(".py")
|
||||
]
|
||||
|
||||
with open(output_path, "w") as f_out:
|
||||
for script_file in script_files:
|
||||
dut_name = re.search(r"build_ut_(.*)\.py", script_file).group(1)
|
||||
script_path = os.path.join(scripts_dir, script_file)
|
||||
module_name = f"build_ut_{dut_name}"
|
||||
spec = importlib.util.spec_from_file_location(module_name, script_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[module_name] = module
|
||||
spec.loader.exec_module(module)
|
||||
if not hasattr(module, "get_metadata"):
|
||||
warning(f"{script_file} has no get_metadata() function, skipping")
|
||||
continue
|
||||
try:
|
||||
metadata = module.get_metadata()
|
||||
dut_dir = metadata.get("dut_dir")
|
||||
test_targets = metadata.get("test_targets", [])
|
||||
except Exception as e:
|
||||
warning(f"Failed to get metadata from {script_file}: {str(e)}")
|
||||
continue
|
||||
if not dut_dir or not test_targets:
|
||||
warning(f"{script_file} has invalid metadata (missing dut_dir or test_targets)")
|
||||
continue
|
||||
for target in test_targets:
|
||||
f_out.write(f"{dut_name} --> {dut_dir} --> {target}\n")
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
23
dir_map.f
|
|
@ -1,23 +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 --> ut_frontend/ifu/ifu_top
|
||||
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
|
||||
frontend_icache_ctrlunit --> CtrlUnit --> ut_frontend/icache/ctrlunit
|
||||
frontend_icache_iprefetchpipe --> IPrefetchPipe --> ut_frontend/icache/iprefechpipe
|
||||
frontend_icache_icache --> ICache --> ut_frontend/icache/icache
|
||||
frontend_icache_mainpipe --> ICacheMainPipe --> ut_frontend/icache/mainpipe
|
||||
frontend_icache_missunit --> ICacheMissUnit --> ut_frontend/icache/missunit
|
||||
frontend_icache_waylookup --> WayLookup --> ut_frontend/icache/waylookup
|
||||
frontend_instruncache --> InstrUncache --> ut_frontend/instruncache
|
||||
|
||||
|
|
@ -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 %}}
|
||||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ linkTitle: ICache
|
|||
weight: 12
|
||||
---
|
||||
|
||||
**本文档参考[香山 IFU 设计文档](https://github.com/OpenXiangShan/XiangShan-Design-Doc/blob/master/docs/frontend/ICache/index.md)写成**
|
||||
**本文档参考[香山 ICache 设计文档](https://github.com/OpenXiangShan/XiangShan-Design-Doc/blob/master/docs/frontend/ICache/index.md)写成**
|
||||
|
||||
本次验证对象是昆明湖前端指令缓存(ICache)的模块。验证的代码版本为[XiangShan_20250307_4b2c87ba](https://github.com/OpenXiangShan/XiangShan/tree/4b2c87ba1d7965f6f2b0a396be707a6e2f6fb345)
|
||||
|
||||
|
|
@ -49,6 +49,7 @@ weight: 12
|
|||
| hartID | hardware thread ID | 硬件线程标识 | RISC-V 硬件线程 ID。在 RISC-V 架构中,每个处理器核心都有一个唯一的 hart ID,用于区分同一处理器中运行的多个硬件线程。 |
|
||||
| SFENCE.VMA | Supervisor Memory-Management Fence Instruction | 监管者内存管理屏障指令 | 同步对内存中内存管理数据结构的更新与当前执行的指令。 |
|
||||
| fence.i | fence.i | 屏障指令 | 用于同步指令流与数据流,确保在指令之前的存储操作对后续取指可见。 |
|
||||
|FDIP|Fetch-directed instruction prefetching |取指导向指令预取|通过在分支预测单元和取指单元之间引入一个取指目标队列(Fetch Target Queue,FTQ),将两者解耦。分支预测单元会预测未来的控制流,并将预测的分支目标地址存入FTQ。取指单元则根据FTQ中的地址信息,提前从更高级别的缓存或内存中获取指令块,并将其放入一个全相联的缓冲区中,以便与L1指令缓存并行访问。|
|
||||
|
||||
## ICache 和 DCache 区别
|
||||
|
||||
|
|
@ -73,6 +74,25 @@ DCache 数据一致性是一个重要的问题。因为数据可能会被多个
|
|||
|
||||
将数据和指令分开存储到 DCache 和 ICache,有利于提高命中率和减少冲突,提升性能(CPU 在执行程序时,可以同时获取指令和数据,做到硬件上的并行),简化设计(ICache 可以专注读指令,而 DCache 需要数据的读写操作,还需要考虑数据一致性问题)。
|
||||
|
||||
## 为什么需要预取
|
||||
|
||||
预取是 CPU 用来提高执行性能的一种技术,它在实际需要之前将指令或数据从原来存储在较慢内存中的位置取到较快的本地内存中(因此称为 "预取")。
|
||||
|
||||
<div>
|
||||
<center>
|
||||
<img src="difference between cpu and memory.png"
|
||||
alt="CPU 和内存的性能差异"
|
||||
style="zoom:100%"/>
|
||||
<br> <!--换行-->
|
||||
CPU 和内存的性能差异 <!--标题-->
|
||||
</center>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
由处理器和内存之前的性能差异越来大,所以我们需要预取。<br>
|
||||
从上图可以看出,1980年至2015年间,CPU的性能提升了将近一万倍,可是内存相关的性能只提升了十倍。如果等CPU需要执行相关指令或者需要修改数据的时候再从内存中去读取,那么大部分时间都会花费在等待数据上,这是不可容忍的。这时预取的重要性就体现了,把将要访问的内容提前从内存搬移到Cache中,CPU就可以即时拿到所需的内容,避免了等待。当然,如果预取做得不好,是有可能导致性能下降的,由于Cache的大小是很宝贵的,如果预取判断出错,预取的是无用的数据,然后反而把Cache中后续有可能还会用到的数据给Evict了,那么会增加系统的功耗,减低性能。
|
||||
|
||||
## 模块列表
|
||||
|
||||
| 子模块 | 描述 |
|
||||
|
|
@ -1181,7 +1201,7 @@ DataArray 主要存储了每个 Cache 行的标签和 ECC 校验码。
|
|||
预取请求来自 FTQ,在 S0 流水级传入。
|
||||
| 接口名 | 解释 |
|
||||
| ---------- | ------------------------------------------------------ |
|
||||
|ready||指示 s0 能否继续|
|
||||
|ready|指示 s0 能否继续|
|
||||
|valid|指示软件预取或者硬件预取是否有效。|
|
||||
|startAddr | 预测块起始地址。 |
|
||||
|nextlineStart | 预测块下一个缓存行的起始地址。 |
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
请注意,本文档撰写的测试点仅供参考,如能补充更多测试点,最终获得的奖励可能更高!
|
||||
|
After Width: | Height: | Size: 569 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 373 KiB |
|
After Width: | Height: | Size: 3.5 MiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 349 KiB |
|
After Width: | Height: | Size: 214 KiB |
|
After Width: | Height: | Size: 3.5 MiB |
|
|
@ -52,5 +52,17 @@ def build(cfg):
|
|||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "backend_ctrl_block_decode",
|
||||
"dut_dir": "DecodeStage",
|
||||
"test_targets": [
|
||||
"ut_backend/ctrl_block/decode",
|
||||
"ut_backend/ctrl_block",
|
||||
"ut_backend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["PreDecode.v", "DecodeStage.v"]
|
||||
|
|
|
|||
|
|
@ -20,5 +20,17 @@ def build(cfg):
|
|||
return False
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "backend_ctrl_block_rob",
|
||||
"dut_dir": "Rob",
|
||||
"test_targets": [
|
||||
"ut_backend/ctrl_block/rob",
|
||||
"ut_backend/ctrl_block",
|
||||
"ut_backend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return []
|
||||
|
|
|
|||
|
|
@ -20,5 +20,17 @@ def build(cfg):
|
|||
return False
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_bpu_ftb",
|
||||
"dut_dir": "FTB",
|
||||
"test_targets": [
|
||||
"ut_frontend/bpu/ftb",
|
||||
"ut_frontend/bpu",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return []
|
||||
|
|
|
|||
|
|
@ -46,5 +46,17 @@ def build(cfg):
|
|||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_bpu_ittage",
|
||||
"dut_dir": "ITTage",
|
||||
"test_targets": [
|
||||
"ut_frontend/bpu/ittage",
|
||||
"ut_frontend/bpu",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["ITTage.v"]
|
||||
|
|
|
|||
|
|
@ -20,5 +20,17 @@ def build(cfg):
|
|||
return False
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_bpu_ras",
|
||||
"dut_dir": "RAS",
|
||||
"test_targets": [
|
||||
"ut_frontend/bpu/ras",
|
||||
"ut_frontend/bpu",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return []
|
||||
|
|
|
|||
|
|
@ -45,5 +45,17 @@ def build(cfg):
|
|||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_bpu_tagesc",
|
||||
"dut_dir": "Tage_SC",
|
||||
"test_targets": [
|
||||
"ut_frontend/bpu/tagesc",
|
||||
"ut_frontend/bpu",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["Tage_SC.v"]
|
||||
|
|
|
|||
|
|
@ -20,5 +20,16 @@ def build(cfg):
|
|||
return False
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_bpu_top",
|
||||
"dut_dir": "BPU",
|
||||
"test_targets": [
|
||||
"ut_frontend/bpu",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return []
|
||||
|
|
|
|||
|
|
@ -20,5 +20,16 @@ def build(cfg):
|
|||
return False
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_bpu_uftb",
|
||||
"dut_dir": "UFTB",
|
||||
"test_targets": [
|
||||
"ut_frontend/bpu",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return []
|
||||
|
|
|
|||
|
|
@ -33,6 +33,19 @@ def build(cfg):
|
|||
|
||||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_ftb_entry_mem",
|
||||
"dut_dir": "FtbEntryMem",
|
||||
"test_targets": [
|
||||
"ut_frontend/ftq/ftb_entry_mem",
|
||||
"ut_frontend/ftq",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
## set coverage
|
||||
def line_coverage_files(cfg):
|
||||
return ["FtbEntryMem.v"]
|
||||
|
|
|
|||
|
|
@ -39,6 +39,19 @@ def build(cfg):
|
|||
|
||||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_ftq_meta_1r_sram",
|
||||
"dut_dir": "FtqMetairSram",
|
||||
"test_targets": [
|
||||
"ut_frontend/ftq/meta_1r_sram",
|
||||
"ut_frontend/ftq",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
## set coverage
|
||||
def line_coverage_files(cfg):
|
||||
return ["FtqMetairSram.v"]
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from comm import warning, info
|
|||
def build(cfg):
|
||||
# import base modules
|
||||
from toffee_test.markers import match_version
|
||||
from comm import is_all_file_exist, get_rtl_dir, exe_cmd, get_root_dir
|
||||
from comm import is_all_file_exist, get_rtl_dir, exe_cmd, get_root_dir, get_all_rtl_files
|
||||
# check version
|
||||
if not match_version(cfg.rtl.version, "openxiangshan-kmh-*"):
|
||||
warning("frontend_ftq_redirect_mem: %s" % f"Unsupported RTL version {cfg.rtl.version}")
|
||||
|
|
@ -12,28 +12,32 @@ def build(cfg):
|
|||
# check files
|
||||
module_name = "FtqPcMem"
|
||||
file_name ="FtqPcMemWrapper.sv"
|
||||
dp_file_names = [
|
||||
"SyncDataModuleTemplate_FtqPC_64entry.sv",
|
||||
"DataModule_FtqPC_16entry.sv"
|
||||
]
|
||||
dp_fpaths = [f"rtl/rtl/{dp_file_name}" for dp_file_name in dp_file_names]
|
||||
dp_fpaths_after_get_root = [get_root_dir(dp_fpath) for dp_fpath in dp_fpaths]
|
||||
fpath = f"rtl/{file_name}"
|
||||
all_fpaths = dp_fpaths + [fpath]
|
||||
## internal signals is now not determined
|
||||
rtl_files = get_all_rtl_files("FtqPcMemWrapper", cfg=cfg)
|
||||
internal_signals_path=""
|
||||
f = is_all_file_exist(all_fpaths, get_rtl_dir(cfg=cfg))
|
||||
#assert f is True, f"File {f} not found"
|
||||
|
||||
# build
|
||||
# export SyncDataModuleTemplate__64entry.sv
|
||||
if not os.path.exists(get_root_dir(f"dut/{module_name}")):
|
||||
info(f"Exporting {file_name}.sv")
|
||||
s,out,err = exe_cmd(f'picker export {get_rtl_dir(f"{fpath}",cfg = cfg)} --tname {module_name}\
|
||||
--lang python --tdir {get_root_dir("dut")}/ -w {module_name}.fst -c --fs ' + ' '.join(dp_fpaths_after_get_root))
|
||||
s,out,err = exe_cmd(f'picker export {rtl_files[0]} --tname {module_name}\
|
||||
--lang python --tdir {get_root_dir("dut")}/ -w {module_name}.fst -c --fs ' + ' '.join(rtl_files))
|
||||
assert s, f"Failed to export {file_name}.sv: %s\n%s" % (out, err)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_ftq_pc_mem",
|
||||
"dut_dir": "FtqPcMem",
|
||||
"test_targets": [
|
||||
"ut_frontend/ftq/ftq_pc_mem",
|
||||
"ut_frontend/ftq",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
## set coverage
|
||||
def line_coverage_files(cfg):
|
||||
return ["FtqPcMem.v"]
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from comm import warning, info
|
|||
def build(cfg):
|
||||
# import base modules
|
||||
from toffee_test.markers import match_version
|
||||
from comm import is_all_file_exist, get_rtl_dir, exe_cmd, get_root_dir
|
||||
from comm import is_all_file_exist, get_rtl_dir, exe_cmd, get_root_dir, get_all_rtl_files
|
||||
# check version
|
||||
if not match_version(cfg.rtl.version, "openxiangshan-kmh-*"):
|
||||
warning("frontend_ftq_pd_mem: %s" % f"Unsupported RTL version {cfg.rtl.version}")
|
||||
|
|
@ -12,27 +12,32 @@ def build(cfg):
|
|||
# check files
|
||||
module_name = "FtqPdMem"
|
||||
file_name ="SyncDataModuleTemplate__64entry_2.sv"
|
||||
dp_file_names = [
|
||||
"DataModule__16entry_8.sv",
|
||||
]
|
||||
dp_fpaths = [f"rtl/rtl/{dp_file_name}" for dp_file_name in dp_file_names]
|
||||
dp_fpaths_after_get_root = [get_root_dir(dp_fpath) for dp_fpath in dp_fpaths]
|
||||
fpath = f"rtl/{file_name}"
|
||||
all_fpaths = dp_fpaths + [fpath]
|
||||
## internal signals is now not determined
|
||||
rtl_files = get_all_rtl_files("SyncDataModuleTemplate__64entry_2", cfg=cfg)
|
||||
internal_signals_path=""
|
||||
f = is_all_file_exist(all_fpaths, get_rtl_dir(cfg=cfg))
|
||||
#assert f is True, f"File {f} not found" ##some problem here
|
||||
|
||||
# build
|
||||
# export SyncDataModuleTemplate__64_1entry.sv
|
||||
if not os.path.exists(get_root_dir(f"dut/{module_name}")):
|
||||
info(f"Exporting {file_name}.sv")
|
||||
s,out,err = exe_cmd(f'picker export {get_rtl_dir(f"{fpath}",cfg = cfg)} --tname {module_name}\
|
||||
--lang python --tdir {get_root_dir("dut")}/ -w {module_name}.fst -c --fs ' + ' '.join(dp_fpaths_after_get_root))
|
||||
s,out,err = exe_cmd(f'picker export {rtl_files[0]} --tname {module_name}\
|
||||
--lang python --tdir {get_root_dir("dut")}/ -w {module_name}.fst -c --fs ' + ' '.join(rtl_files))
|
||||
assert s, f"Failed to export {file_name}.sv: %s\n%s" % (out, err)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_ftq_pd_mem",
|
||||
"dut_dir": "FtqPdMem",
|
||||
"test_targets": [
|
||||
"ut_frontend/ftq/ftq_pd_mem",
|
||||
"ut_frontend/ftq",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
## set coverage
|
||||
def line_coverage_files(cfg):
|
||||
return ["FtqPdMem.v"]
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from comm import warning, info
|
|||
def build(cfg):
|
||||
# import base modules
|
||||
from toffee_test.markers import match_version
|
||||
from comm import is_all_file_exist, get_rtl_dir, exe_cmd, get_root_dir
|
||||
from comm import is_all_file_exist, get_rtl_dir, exe_cmd, get_root_dir, get_all_rtl_files
|
||||
# check version
|
||||
if not match_version(cfg.rtl.version, "openxiangshan-kmh-*"):
|
||||
warning("frontend_ftq_redirect_mem: %s" % f"Unsupported RTL version {cfg.rtl.version}")
|
||||
|
|
@ -12,27 +12,32 @@ def build(cfg):
|
|||
# check files
|
||||
module_name = "FtqRedirectMem"
|
||||
file_name ="SyncDataModuleTemplate__64entry.sv"
|
||||
dp_file_names = [
|
||||
"DataModule__16entry.sv",
|
||||
]
|
||||
dp_fpaths = [f"rtl/rtl/{dp_file_name}" for dp_file_name in dp_file_names]
|
||||
dp_fpaths_after_get_root = [get_root_dir(dp_fpath) for dp_fpath in dp_fpaths]
|
||||
fpath = f"rtl/{file_name}"
|
||||
all_fpaths = dp_fpaths + [fpath]
|
||||
## internal signals is now not determined
|
||||
rtl_files = get_all_rtl_files("SyncDataModuleTemplate__64entry", cfg=cfg)
|
||||
internal_signals_path=""
|
||||
f = is_all_file_exist(all_fpaths, get_rtl_dir(cfg=cfg))
|
||||
#assert f is True, f"File {f} not found"
|
||||
|
||||
# build
|
||||
# export SyncDataModuleTemplate__64entry.sv
|
||||
if not os.path.exists(get_root_dir(f"dut/{module_name}")):
|
||||
info(f"Exporting {file_name}.sv")
|
||||
s,out,err = exe_cmd(f'picker export {get_rtl_dir(f"{fpath}",cfg = cfg)} --tname {module_name}\
|
||||
--lang python --tdir {get_root_dir("dut")}/ -w {module_name}.fst -c --fs ' + ' '.join(dp_fpaths_after_get_root))
|
||||
s,out,err = exe_cmd(f'picker export {rtl_files[0]} --tname {module_name}\
|
||||
--lang python --tdir {get_root_dir("dut")}/ -w {module_name}.fst -c --fs ' + ' '.join(rtl_files))
|
||||
assert s, f"Failed to export {file_name}.sv: %s\n%s" % (out, err)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_ftq_redirect_mem",
|
||||
"dut_dir": "FtqRedirectMem",
|
||||
"test_targets": [
|
||||
"ut_frontend/ftq/ftq_redirect_mem",
|
||||
"ut_frontend/ftq",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
## set coverage
|
||||
def line_coverage_files(cfg):
|
||||
return ["FtqRedirectMem.v"]
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from comm import warning, info
|
|||
def build(cfg):
|
||||
# import base modules
|
||||
from toffee_test.markers import match_version
|
||||
from comm import is_all_file_exist, get_rtl_dir, exe_cmd, get_root_dir
|
||||
from comm import is_all_file_exist, get_rtl_dir, exe_cmd, get_root_dir, get_all_rtl_files
|
||||
# check version
|
||||
if not match_version(cfg.rtl.version, "openxiangshan-kmh-*"):
|
||||
warning("frontend_ftq_top: %s" % f"Unsupported RTL version {cfg.rtl.version}")
|
||||
|
|
@ -12,48 +12,32 @@ def build(cfg):
|
|||
# check files 1
|
||||
module_name = "FtqTop"
|
||||
file_name ="Ftq.sv"
|
||||
dp_file_names = [
|
||||
"SyncDataModuleTemplate__64entry.sv",
|
||||
"SyncDataModuleTemplate__64entry_1.sv",
|
||||
"SyncDataModuleTemplate__64entry_2.sv",
|
||||
"SyncDataModuleTemplate__64entry_3.sv",
|
||||
"DataModule__16entry.sv",
|
||||
"DataModule__16entry_4.sv",
|
||||
"DataModule__16entry_8.sv",
|
||||
"DataModule__16entry_12.sv",
|
||||
"FTBEntryGen.sv",
|
||||
"FtqNRSRAM.sv",
|
||||
"FtqPcMemWrapper.sv",
|
||||
"SRAMTemplate_65.sv",
|
||||
"SyncDataModuleTemplate_FtqPC_64entry.sv",
|
||||
"DataModule_FtqPC_16entry.sv",
|
||||
"array_0_0.sv",
|
||||
"array_0_0_ext.v",
|
||||
"ClockGate.sv",
|
||||
"MbistClockGateCell.sv",
|
||||
"sram_array_2p64x576m192s1h0l1b_ftq.sv",
|
||||
"array_8.sv",
|
||||
"array_8_ext.v",
|
||||
"MbistPipeFtq.sv"
|
||||
]
|
||||
dp_fpaths = [f"rtl/rtl/{dp_file_name}" for dp_file_name in dp_file_names]
|
||||
dp_fpaths_after_get_root = [get_root_dir(dp_fpath) for dp_fpath in dp_fpaths]
|
||||
fpath = f"rtl/{file_name}"
|
||||
all_fpaths = dp_fpaths + [fpath]
|
||||
## internal signals is now not determined
|
||||
rtl_files = get_all_rtl_files("Ftq", cfg=cfg)
|
||||
internal_signals_path=""
|
||||
f = is_all_file_exist(all_fpaths, get_rtl_dir(cfg=cfg))
|
||||
#assert f is True, f"File {f} not found"
|
||||
|
||||
# build
|
||||
# export ftq.sv
|
||||
if not os.path.exists(get_root_dir(f"dut/{module_name}")):
|
||||
info("Exporting Ftq.sv")
|
||||
s,out,err = exe_cmd(f'picker export --cp_lib false {get_rtl_dir(f"{fpath}",cfg = cfg)} \
|
||||
--lang python --tdir {get_root_dir("dut")}/ -w {module_name}.fst -c --fs ' + ' '.join(dp_fpaths_after_get_root))
|
||||
s,out,err = exe_cmd(f'picker export --cp_lib false {rtl_files[0]} --tname {module_name}\
|
||||
--lang python --tdir {get_root_dir("dut")}/ -w {module_name}.fst -c --fs ' + ' '.join(rtl_files))
|
||||
assert s, f"Failed to export Ftq.sv: %s\n%s" % (out, err)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_ftq_top",
|
||||
"dut_dir": "FtqTop",
|
||||
"test_targets": [
|
||||
"ut_frontend/ftq/ftq_top",
|
||||
"ut_frontend/ftq",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
## set coverage
|
||||
def line_coverage_files(cfg):
|
||||
return ["Ftq.v"]
|
||||
|
|
|
|||
|
|
@ -37,5 +37,17 @@ def build(cfg):
|
|||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_icache_ctrlunit",
|
||||
"dut_dir": "ICacheCtrlUnit",
|
||||
"test_targets": [
|
||||
"ut_frontend/icache/ctrlunit",
|
||||
"ut_frontend/icache",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["ICacheCtrlUnit.v"]
|
||||
|
|
|
|||
|
|
@ -37,5 +37,17 @@ def build(cfg):
|
|||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_icache_icache",
|
||||
"dut_dir": "ICache",
|
||||
"test_targets": [
|
||||
"ut_frontend/icache/icache",
|
||||
"ut_frontend/icache",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["ICache.v"]
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ from comm import warning, info, get_all_rtl_files
|
|||
def build(cfg):
|
||||
from tempfile import NamedTemporaryFile
|
||||
from toffee_test.markers import match_version
|
||||
from comm import error, info, get_root_dir, exe_cmd
|
||||
from comm import get_root_dir, exe_cmd
|
||||
|
||||
# check version
|
||||
if not match_version(cfg.rtl.version, "openxiangshan-kmh-*"):
|
||||
error(f"frontend_icache_iprefetchpipe: Unsupported RTL version {cfg.rtl.version}")
|
||||
warning(f"frontend_icache_iprefetchpipe: Unsupported RTL version {cfg.rtl.version}")
|
||||
return False
|
||||
|
||||
# find source files for IPrefetchPipe
|
||||
|
|
@ -21,6 +21,9 @@ def build(cfg):
|
|||
internal_signals_path = os.path.join(get_root_dir("scripts/icache_related/icache_iprefetchpipe_internals.yaml"))
|
||||
# assert os.path.exists(internal_signals_path), "Cannot find internal signal files"
|
||||
|
||||
# verilator arguments
|
||||
verilator_args = "'--x-initial;0'"
|
||||
|
||||
# export IPrefetchPipe.sv
|
||||
if not os.path.exists(get_root_dir("dut/IPrefetchPipe")):
|
||||
info("Exporting IPrefetchPipe.sv")
|
||||
|
|
@ -29,11 +32,24 @@ def build(cfg):
|
|||
filelist.flush()
|
||||
s, _, err = exe_cmd(
|
||||
f"picker export --cp_lib false {rtl_files[0]} --fs {filelist.name} --lang python --tdir "
|
||||
f"{get_root_dir('dut')}/ -w IPrefetchPipe.fst -c --internal={internal_signals_path}"
|
||||
f"{get_root_dir('dut')}/ -w IPrefetchPipe.fst -c --internal={internal_signals_path} "
|
||||
f"-V {verilator_args}"
|
||||
)
|
||||
assert s, err
|
||||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_icache_iprefetchpipe",
|
||||
"dut_dir": "IPrefetchPipe",
|
||||
"test_targets": [
|
||||
"ut_frontend/icache/iprefetchpipe",
|
||||
"ut_frontend/icache",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["IPrefetchPipe.v"]
|
||||
|
|
|
|||
|
|
@ -38,5 +38,17 @@ def build(cfg):
|
|||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_icache_mainpipe",
|
||||
"dut_dir": "ICacheMainPipe",
|
||||
"test_targets": [
|
||||
"ut_frontend/icache/mainpipe",
|
||||
"ut_frontend/icache",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["ICacheMainPipe.v"]
|
||||
|
|
|
|||
|
|
@ -37,5 +37,17 @@ def build(cfg):
|
|||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_icache_missunit",
|
||||
"dut_dir": "ICacheMissUnit",
|
||||
"test_targets": [
|
||||
"ut_frontend/icache/missunit",
|
||||
"ut_frontend/icache",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["ICacheMissUnit.v"]
|
||||
|
|
|
|||
|
|
@ -38,5 +38,17 @@ def build(cfg):
|
|||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_icache_waylookup",
|
||||
"dut_dir": "WayLookup",
|
||||
"test_targets": [
|
||||
"ut_frontend/icache/waylookup",
|
||||
"ut_frontend/icache",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["WayLookup.v"]
|
||||
|
|
|
|||
|
|
@ -28,5 +28,17 @@ def build(cfg):
|
|||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_ifu_f3predecoder",
|
||||
"dut_dir": "F3Predecoder",
|
||||
"test_targets": [
|
||||
"ut_frontend/ifu/f3predecoder",
|
||||
"ut_frontend/ifu",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["F3Predecoder.v"]
|
||||
|
|
|
|||
|
|
@ -28,5 +28,17 @@ def build(cfg):
|
|||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_ifu_frontend_trigger",
|
||||
"dut_dir": "FrontendTrigger",
|
||||
"test_targets": [
|
||||
"ut_frontend/ifu/frontend_trigger",
|
||||
"ut_frontend/ifu",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["FrontendTrigger.v"]
|
||||
|
|
|
|||
|
|
@ -43,5 +43,17 @@ def build(cfg):
|
|||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_ifu_pred_checker",
|
||||
"dut_dir": "PredChecker",
|
||||
"test_targets": [
|
||||
"ut_frontend/ifu/pred_checker",
|
||||
"ut_frontend/ifu",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["PredChecker.v"]
|
||||
|
|
|
|||
|
|
@ -28,5 +28,17 @@ def build(cfg):
|
|||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_ifu_predecode",
|
||||
"dut_dir": "PreDecode",
|
||||
"test_targets": [
|
||||
"ut_frontend/ifu/predecode",
|
||||
"ut_frontend/ifu",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["PreDecode.v"]
|
||||
|
|
|
|||
|
|
@ -27,5 +27,19 @@ def build(cfg):
|
|||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_ifu_rvc_expander",
|
||||
"dut_dir": "RVCExpander",
|
||||
"test_targets": [
|
||||
"ut_frontend/ifu/rvc_expander/classical_version",
|
||||
"ut_frontend/ifu/rvc_expander/toffee_version",
|
||||
"ut_frontend/ifu/rvc_expander",
|
||||
"ut_frontend/ifu",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["RVCExpander.v"]
|
||||
|
|
@ -30,5 +30,17 @@ def build(cfg):
|
|||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_ifu_top",
|
||||
"dut_dir": "NewIFU",
|
||||
"test_targets": [
|
||||
"ut_frontend/ifu/ifu_top",
|
||||
"ut_frontend/ifu",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["NewIFU.v"]
|
||||
|
|
@ -37,5 +37,18 @@ def build(cfg):
|
|||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_instruncache",
|
||||
"dut_dir": "InstrUncache",
|
||||
"test_targets": [
|
||||
"ut_frontend/instruncache/classical_version",
|
||||
"ut_frontend/instruncache/toffee_version",
|
||||
"ut_frontend/instruncache",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["InstrUncache.v"]
|
||||
|
|
|
|||
|
|
@ -48,5 +48,18 @@ def build(cfg):
|
|||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_itlb",
|
||||
"dut_dir": "TLB",
|
||||
"test_targets": [
|
||||
"ut_frontend/itlb/classical_version",
|
||||
"ut_frontend/itlb/toffee_version",
|
||||
"ut_frontend/itlb",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["TLB.v"]
|
||||
|
|
|
|||
|
|
@ -48,5 +48,18 @@ def build(cfg):
|
|||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_tlb_fa",
|
||||
"dut_dir": "TLBFA",
|
||||
"test_targets": [
|
||||
"ut_frontend/itlb/submodules/TLBFA",
|
||||
"ut_frontend/itlb/submodules",
|
||||
"ut_frontend/itlb",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["TLBFA.v"]
|
||||
|
|
|
|||
|
|
@ -48,5 +48,18 @@ def build(cfg):
|
|||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_tlb_nonblock",
|
||||
"dut_dir": "TLBNonBlock",
|
||||
"test_targets": [
|
||||
"ut_frontend/itlb/submodules/TLBNonBlock",
|
||||
"ut_frontend/itlb/submodules",
|
||||
"ut_frontend/itlb",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["TLBNonBlock.v"]
|
||||
|
|
|
|||
|
|
@ -48,5 +48,18 @@ def build(cfg):
|
|||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_tlb_storage_wrapper",
|
||||
"dut_dir": "TlbStorageWrapper",
|
||||
"test_targets": [
|
||||
"ut_frontend/itlb/submodules/TlbStorageWrapper",
|
||||
"ut_frontend/itlb/submodules",
|
||||
"ut_frontend/itlb",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["TlbStorageWrapper.v"]
|
||||
|
|
|
|||
|
|
@ -48,5 +48,18 @@ def build(cfg):
|
|||
return True
|
||||
|
||||
|
||||
def get_metadata():
|
||||
return {
|
||||
"dut_name": "frontend_tlbuffer",
|
||||
"dut_dir": "TLBuffer",
|
||||
"test_targets": [
|
||||
"ut_frontend/itlb/submodules/TLBuffer",
|
||||
"ut_frontend/itlb/submodules",
|
||||
"ut_frontend/itlb",
|
||||
"ut_frontend"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def line_coverage_files(cfg):
|
||||
return ["TLBuffer.v"]
|
||||
|
|
|
|||
|
|
@ -1,2 +1,16 @@
|
|||
IPrefetchPipe:
|
||||
- "wire s1_ready"
|
||||
- "wire s1_flush"
|
||||
- "wire from_bpu_s0_flush_probe"
|
||||
- "wire s0_can_go"
|
||||
- "wire s0_fire"
|
||||
- "reg s1_valid"
|
||||
- "reg [49:0] s1_req_vaddr_0"
|
||||
- "reg [49:0] s1_req_vaddr_1"
|
||||
- "reg s1_isSoftPrefetch"
|
||||
- "reg s1_doubleline"
|
||||
- "reg s1_req_ftqIdx_flag"
|
||||
- "reg [5:0] s1_req_ftqIdx_value"
|
||||
- "reg [1:0] s1_backendException_0"
|
||||
- "reg [1:0] s1_backendException_1"
|
||||
- "reg [2:0] state"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
void print_binary(unsigned long long n) {
|
||||
if (n == 0) {
|
||||
printf("0");
|
||||
return;
|
||||
}
|
||||
char binary[65]; // 64位加一个空字符
|
||||
int i = 0;
|
||||
while (n > 0) {
|
||||
binary[i++] = (n % 2) ? '1' : '0';
|
||||
n /= 2;
|
||||
}
|
||||
binary[i] = '\0';
|
||||
|
||||
// 反转字符串以正确打印
|
||||
for (int j = 0; j < i / 2; j++) {
|
||||
char temp = binary[j];
|
||||
binary[j] = binary[i - 1 - j];
|
||||
binary[i - 1 - j] = temp;
|
||||
}
|
||||
printf("%s", binary);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
// 检查参数数量是否正确
|
||||
if (argc != 4) {
|
||||
fprintf(stderr, "用法: %s <-l|-r> <value> <shift_bits>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *direction = argv[1];
|
||||
char *value_str = argv[2];
|
||||
char *shift_bits_str = argv[3];
|
||||
|
||||
unsigned long long value = 0;
|
||||
unsigned int shift_bits = 0;
|
||||
char prefix[3] = "";
|
||||
|
||||
// 尝试解析十六进制或十进制
|
||||
if (sscanf(value_str, "%2s", prefix) == 1 && strcmp(prefix, "0x") == 0) {
|
||||
if (sscanf(value_str, "%llx", &value) != 1) {
|
||||
fprintf(stderr, "错误: 无效的十六进制数值\n");
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
if (sscanf(value_str, "%llu", &value) != 1) {
|
||||
fprintf(stderr, "错误: 无效的十进制数值\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 解析移位位数
|
||||
if (sscanf(shift_bits_str, "%u", &shift_bits) != 1) {
|
||||
fprintf(stderr, "错误: 无效的移位位数\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 检查移位位数是否合法
|
||||
if (shift_bits >= 64) {
|
||||
fprintf(stderr, "错误: 移位位数必须小于64\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
unsigned long long result;
|
||||
|
||||
printf("原始值: 0x%llx (十进制: %llu)\n", value, value);
|
||||
printf("原始值 (二进制): ");
|
||||
print_binary(value);
|
||||
printf("\n");
|
||||
printf("移位位数: %u\n", shift_bits);
|
||||
|
||||
// 执行移位操作
|
||||
if (strcmp(direction, "-l") == 0) {
|
||||
// 左移操作 (无符号)
|
||||
result = value << shift_bits;
|
||||
printf("左移 %u 位后的结果: 0x%llx (十进制: %llu)\n", shift_bits, result, result);
|
||||
printf("结果 (二进制): ");
|
||||
print_binary(result);
|
||||
printf("\n");
|
||||
} else if (strcmp(direction, "-r") == 0) {
|
||||
// 右移操作 (无符号)
|
||||
result = value >> shift_bits;
|
||||
printf("右移 %u 位后的结果: 0x%llx (十进制: %llu)\n", shift_bits, result, result);
|
||||
printf("结果 (二进制): ");
|
||||
print_binary(result);
|
||||
printf("\n");
|
||||
} else {
|
||||
fprintf(stderr, "错误: 无效的移位方向,请使用 -l 或 -r\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
from toffee import Agent
|
||||
from ..bundle import IPrefetchPipeBundle
|
||||
import random
|
||||
|
||||
|
||||
class IPrefetchPipeAgent(Agent):
|
||||
def __inti__(self, bundle: IPrefetchPipeBundle):
|
||||
def __init__(self, bundle: IPrefetchPipeBundle):
|
||||
super().__init__(bundle)
|
||||
bundle.set_all(0)
|
||||
self.bundle = bundle
|
||||
|
||||
async def set_s1_flush(self):
|
||||
|
|
@ -15,8 +15,62 @@ class IPrefetchPipeAgent(Agent):
|
|||
await self.bundle.step()
|
||||
|
||||
print(
|
||||
"\nBefore setting, s1_flush is: ", self.bundle.IPrefetchPipe_s1_flush.value
|
||||
"\nBefore setting, s1_flush is: ",
|
||||
self.bundle.IPrefetchPipe._s1._flush.value,
|
||||
)
|
||||
self.bundle.io._flush.value = 1
|
||||
await self.bundle.step()
|
||||
print("After setting, s1_flush is: ", self.bundle.IPrefetchPipe_s1_flush.value)
|
||||
print(
|
||||
"After setting, s1_flush is: ", self.bundle.IPrefetchPipe._s1._flush.value
|
||||
)
|
||||
|
||||
|
||||
async def receive_prefetch(self):
|
||||
self.bundle.io._req._bits._startAddr.value = random.randint(0, (1<<49)-1)<<1
|
||||
self.bundle.io._req._bits._nextlineStart.value = random.randint(0, (1<<49)-1)<<1
|
||||
self.bundle.io._req._bits._isSoftPrefetch.value = random.getrandbits(1)
|
||||
self.bundle.io._req._bits._ftqIdx._flag.value = random.getrandbits(1)
|
||||
self.bundle.io._req._bits._ftqIdx._value.value = random.randint(0, (1<<6)-1)
|
||||
self.bundle.io._req._bits._backendException.value = random.randint(0, (1<<6)-1)
|
||||
|
||||
# set s0_fire
|
||||
self.bundle.io._req._valid.value = 1
|
||||
self.bundle.io._metaRead._toIMeta._ready.value = 1
|
||||
await self.bundle.step()
|
||||
self.bundle.io._flush.value = 0
|
||||
|
||||
await self.bundle.step()
|
||||
|
||||
assert (
|
||||
self.bundle.IPrefetchPipe._s1._req._vaddr._0.value
|
||||
== self.bundle.io._req._bits._startAddr.value
|
||||
), "vaddr_0 is not equal"
|
||||
assert (
|
||||
self.bundle.IPrefetchPipe._s1._req._vaddr._1.value
|
||||
== self.bundle.io._req._bits._nextlineStart.value
|
||||
), "vaddr_1 is not equal"
|
||||
assert (
|
||||
self.bundle.IPrefetchPipe._s1._isSoftPrefetch.value
|
||||
== self.bundle.io._req._bits._isSoftPrefetch.value
|
||||
), "isSoftPrefetch is not equal"
|
||||
assert self.bundle.IPrefetchPipe._s1._doubleline.value == int(
|
||||
(bin(self.bundle.io._req._bits._startAddr.value)[2:])[-6]
|
||||
), "doubleline is not equal"
|
||||
assert (
|
||||
self.bundle.IPrefetchPipe._s1._req._ftqIdx._flag.value
|
||||
== self.bundle.io._req._bits._ftqIdx._flag.value
|
||||
), "ftqIdx_flag is not equal"
|
||||
assert (
|
||||
self.bundle.IPrefetchPipe._s1._req._ftqIdx._value.value
|
||||
== self.bundle.io._req._bits._ftqIdx._value.value
|
||||
), "ftqIdx_value is not equal"
|
||||
assert (
|
||||
self.bundle.IPrefetchPipe._s1._backendException._0.value
|
||||
== self.bundle.io._req._bits._backendException.value
|
||||
), "backendException_0 is not equal"
|
||||
assert (
|
||||
self.bundle.IPrefetchPipe._s1._backendException._1.value
|
||||
== self.bundle.io._req._bits._backendException.value
|
||||
), "backendException_1 is not equal"
|
||||
|
||||
await self.bundle.step(2)
|
||||
|
|
|
|||
|
|
@ -1,185 +1,167 @@
|
|||
from toffee import Bundle, Signals, Signal
|
||||
|
||||
|
||||
class _0Bundle(Bundle):
|
||||
_vSetIdx, _blkPaddr = Signals(2)
|
||||
|
||||
_fire, _can_go = Signals(2)
|
||||
|
||||
class _1Bundle(Bundle):
|
||||
_bits = _0Bundle.from_prefix("_bits")
|
||||
_valid, _ready = Signals(2)
|
||||
|
||||
_1, _0 = Signals(2)
|
||||
|
||||
class _2Bundle(Bundle):
|
||||
_vSetIdx, _corrupt, _blkPaddr, _waymask = Signals(4)
|
||||
|
||||
_flag, _value = Signals(2)
|
||||
|
||||
class _3Bundle(Bundle):
|
||||
_bits = _2Bundle.from_prefix("_bits")
|
||||
_valid = Signal()
|
||||
|
||||
_ftqIdx = _2Bundle.from_prefix("_ftqIdx")
|
||||
_vaddr = _1Bundle.from_prefix("_vaddr")
|
||||
|
||||
class _4Bundle(Bundle):
|
||||
_flag, _value = Signals(2)
|
||||
|
||||
_backendException = _1Bundle.from_prefix("_backendException")
|
||||
_req = _3Bundle.from_prefix("_req")
|
||||
_isSoftPrefetch, _valid, _ready,_flush, _doubleline = Signals(5)
|
||||
|
||||
class _5Bundle(Bundle):
|
||||
_bits = _4Bundle.from_prefix("_bits")
|
||||
_valid = Signal()
|
||||
|
||||
_s1 = _4Bundle.from_prefix("_s1")
|
||||
_s0 = _0Bundle.from_prefix("_s0")
|
||||
_from_bpu_s0_flush_probe = Signal()
|
||||
|
||||
class _6Bundle(Bundle):
|
||||
_s3 = _5Bundle.from_prefix("_s3")
|
||||
_s2 = _5Bundle.from_prefix("_s2")
|
||||
|
||||
_blkPaddr, _vSetIdx = Signals(2)
|
||||
|
||||
class _7Bundle(Bundle):
|
||||
_bits_vaddr, _valid = Signals(2)
|
||||
|
||||
_bits = _6Bundle.from_prefix("_bits")
|
||||
_ready, _valid = Signals(2)
|
||||
|
||||
class _8Bundle(Bundle):
|
||||
_pf_instr, _af_instr, _gpf_instr = Signals(3)
|
||||
|
||||
_corrupt, _waymask, _blkPaddr, _vSetIdx = Signals(4)
|
||||
|
||||
class _9Bundle(Bundle):
|
||||
_0 = _8Bundle.from_prefix("_0")
|
||||
|
||||
_bits = _8Bundle.from_prefix("_bits")
|
||||
_valid = Signal()
|
||||
|
||||
class _10Bundle(Bundle):
|
||||
_0 = Signal()
|
||||
|
||||
_bits = _2Bundle.from_prefix("_bits")
|
||||
_valid = Signal()
|
||||
|
||||
class _11Bundle(Bundle):
|
||||
_pbmt = _10Bundle.from_prefix("_pbmt")
|
||||
_gpaddr = _10Bundle.from_prefix("_gpaddr")
|
||||
_paddr = _10Bundle.from_prefix("_paddr")
|
||||
_excp = _9Bundle.from_prefix("_excp")
|
||||
_isForVSnonLeafPTE, _miss = Signals(2)
|
||||
|
||||
_s3 = _10Bundle.from_prefix("_s3")
|
||||
_s2 = _10Bundle.from_prefix("_s2")
|
||||
|
||||
class _12Bundle(Bundle):
|
||||
_resp_bits = _11Bundle.from_prefix("_resp_bits")
|
||||
_req = _7Bundle.from_prefix("_req")
|
||||
|
||||
_bits_vaddr, _valid = Signals(2)
|
||||
|
||||
class _13Bundle(Bundle):
|
||||
_1 = _12Bundle.from_prefix("_1")
|
||||
_0 = _12Bundle.from_prefix("_0")
|
||||
|
||||
_af_instr, _pf_instr, _gpf_instr = Signals(3)
|
||||
|
||||
class _14Bundle(Bundle):
|
||||
_0, _1, _3, _2 = Signals(4)
|
||||
|
||||
_0 = _13Bundle.from_prefix("_0")
|
||||
|
||||
class _15Bundle(Bundle):
|
||||
_0 = _14Bundle.from_prefix("_0")
|
||||
_1 = _14Bundle.from_prefix("_1")
|
||||
|
||||
_0 = Signal()
|
||||
|
||||
class _16Bundle(Bundle):
|
||||
_tag = Signal()
|
||||
|
||||
_pbmt = _15Bundle.from_prefix("_pbmt")
|
||||
_gpaddr = _15Bundle.from_prefix("_gpaddr")
|
||||
_paddr = _15Bundle.from_prefix("_paddr")
|
||||
_excp = _14Bundle.from_prefix("_excp")
|
||||
_isForVSnonLeafPTE, _miss = Signals(2)
|
||||
|
||||
class _17Bundle(Bundle):
|
||||
_1 = _16Bundle.from_prefix("_1")
|
||||
_0 = _16Bundle.from_prefix("_0")
|
||||
_2 = _16Bundle.from_prefix("_2")
|
||||
_3 = _16Bundle.from_prefix("_3")
|
||||
|
||||
_req = _12Bundle.from_prefix("_req")
|
||||
_resp_bits = _16Bundle.from_prefix("_resp_bits")
|
||||
|
||||
class _18Bundle(Bundle):
|
||||
_1 = _17Bundle.from_prefix("_1")
|
||||
_0 = _17Bundle.from_prefix("_0")
|
||||
|
||||
_0 = _17Bundle.from_prefix("_0")
|
||||
_1 = _17Bundle.from_prefix("_1")
|
||||
|
||||
class _19Bundle(Bundle):
|
||||
_entryValid = _15Bundle.from_prefix("_entryValid")
|
||||
_codes = _15Bundle.from_prefix("_codes")
|
||||
_metas = _18Bundle.from_prefix("_metas")
|
||||
|
||||
_1, _2, _0, _3 = Signals(4)
|
||||
|
||||
class _20Bundle(Bundle):
|
||||
_0, _1 = Signals(2)
|
||||
|
||||
_1 = _19Bundle.from_prefix("_1")
|
||||
_0 = _19Bundle.from_prefix("_0")
|
||||
|
||||
class _21Bundle(Bundle):
|
||||
_vSetIdx = _20Bundle.from_prefix("_vSetIdx")
|
||||
_isDoubleLine = Signal()
|
||||
|
||||
_tag = Signal()
|
||||
|
||||
class _22Bundle(Bundle):
|
||||
_bits = _21Bundle.from_prefix("_bits")
|
||||
_valid, _ready = Signals(2)
|
||||
|
||||
_1 = _21Bundle.from_prefix("_1")
|
||||
_0 = _21Bundle.from_prefix("_0")
|
||||
_3 = _21Bundle.from_prefix("_3")
|
||||
_2 = _21Bundle.from_prefix("_2")
|
||||
|
||||
class _23Bundle(Bundle):
|
||||
_fromIMeta = _19Bundle.from_prefix("_fromIMeta")
|
||||
_toIMeta = _22Bundle.from_prefix("_toIMeta")
|
||||
|
||||
_1 = _22Bundle.from_prefix("_1")
|
||||
_0 = _22Bundle.from_prefix("_0")
|
||||
|
||||
class _24Bundle(Bundle):
|
||||
_mmio, _instr = Signals(2)
|
||||
|
||||
_metas = _23Bundle.from_prefix("_metas")
|
||||
_entryValid = _20Bundle.from_prefix("_entryValid")
|
||||
_codes = _20Bundle.from_prefix("_codes")
|
||||
|
||||
class _25Bundle(Bundle):
|
||||
_resp = _24Bundle.from_prefix("_resp")
|
||||
_req_bits_addr = Signal()
|
||||
|
||||
_vSetIdx = _1Bundle.from_prefix("_vSetIdx")
|
||||
_isDoubleLine = Signal()
|
||||
|
||||
class _26Bundle(Bundle):
|
||||
_1 = _25Bundle.from_prefix("_1")
|
||||
_0 = _25Bundle.from_prefix("_0")
|
||||
|
||||
_bits = _25Bundle.from_prefix("_bits")
|
||||
_ready, _valid = Signals(2)
|
||||
|
||||
class _27Bundle(Bundle):
|
||||
_ftqIdx = _4Bundle.from_prefix("_ftqIdx")
|
||||
_backendException, _startAddr, _isSoftPrefetch, _nextlineStart = Signals(4)
|
||||
|
||||
_toIMeta = _26Bundle.from_prefix("_toIMeta")
|
||||
_fromIMeta = _24Bundle.from_prefix("_fromIMeta")
|
||||
|
||||
class _28Bundle(Bundle):
|
||||
_bits = _27Bundle.from_prefix("_bits")
|
||||
_valid, _ready = Signals(2)
|
||||
|
||||
_mmio, _instr = Signals(2)
|
||||
|
||||
class _29Bundle(Bundle):
|
||||
_pbmt = _20Bundle.from_prefix("_pbmt")
|
||||
_exception = _20Bundle.from_prefix("_exception")
|
||||
|
||||
_resp = _28Bundle.from_prefix("_resp")
|
||||
_req_bits_addr = Signal()
|
||||
|
||||
class _30Bundle(Bundle):
|
||||
_waymask = _20Bundle.from_prefix("_waymask")
|
||||
_meta_codes = _20Bundle.from_prefix("_meta_codes")
|
||||
_vSetIdx = _20Bundle.from_prefix("_vSetIdx")
|
||||
_ptag = _20Bundle.from_prefix("_ptag")
|
||||
_itlb = _29Bundle.from_prefix("_itlb")
|
||||
|
||||
_1 = _29Bundle.from_prefix("_1")
|
||||
_0 = _29Bundle.from_prefix("_0")
|
||||
|
||||
class _31Bundle(Bundle):
|
||||
_gpaddr, _isForVSnonLeafPTE = Signals(2)
|
||||
|
||||
_ftqIdx = _2Bundle.from_prefix("_ftqIdx")
|
||||
_nextlineStart, _isSoftPrefetch, _backendException, _startAddr = Signals(4)
|
||||
|
||||
class _32Bundle(Bundle):
|
||||
_gpf = _31Bundle.from_prefix("_gpf")
|
||||
_entry = _30Bundle.from_prefix("_entry")
|
||||
|
||||
_bits = _31Bundle.from_prefix("_bits")
|
||||
_ready, _valid = Signals(2)
|
||||
|
||||
class _33Bundle(Bundle):
|
||||
_bits = _32Bundle.from_prefix("_bits")
|
||||
_valid, _ready = Signals(2)
|
||||
|
||||
_exception = _1Bundle.from_prefix("_exception")
|
||||
_pbmt = _1Bundle.from_prefix("_pbmt")
|
||||
|
||||
class _34Bundle(Bundle):
|
||||
_MSHRReq = _1Bundle.from_prefix("_MSHRReq")
|
||||
_itlb = _13Bundle.from_prefix("_itlb")
|
||||
_flushFromBpu = _6Bundle.from_prefix("_flushFromBpu")
|
||||
_metaRead = _23Bundle.from_prefix("_metaRead")
|
||||
_wayLookupWrite = _33Bundle.from_prefix("_wayLookupWrite")
|
||||
_req = _28Bundle.from_prefix("_req")
|
||||
_pmp = _26Bundle.from_prefix("_pmp")
|
||||
_MSHRResp = _3Bundle.from_prefix("_MSHRResp")
|
||||
_csr_pf_enable, _itlbFlushPipe, _flush = Signals(3)
|
||||
_meta_codes = _1Bundle.from_prefix("_meta_codes")
|
||||
_vSetIdx = _1Bundle.from_prefix("_vSetIdx")
|
||||
_ptag = _1Bundle.from_prefix("_ptag")
|
||||
_waymask = _1Bundle.from_prefix("_waymask")
|
||||
_itlb = _33Bundle.from_prefix("_itlb")
|
||||
|
||||
class _35Bundle(Bundle):
|
||||
_gpaddr, _isForVSnonLeafPTE = Signals(2)
|
||||
|
||||
class _36Bundle(Bundle):
|
||||
_entry = _34Bundle.from_prefix("_entry")
|
||||
_gpf = _35Bundle.from_prefix("_gpf")
|
||||
|
||||
class _37Bundle(Bundle):
|
||||
_bits = _36Bundle.from_prefix("_bits")
|
||||
_ready, _valid = Signals(2)
|
||||
|
||||
class _38Bundle(Bundle):
|
||||
_req = _32Bundle.from_prefix("_req")
|
||||
_MSHRResp = _9Bundle.from_prefix("_MSHRResp")
|
||||
_flushFromBpu = _11Bundle.from_prefix("_flushFromBpu")
|
||||
_MSHRReq = _7Bundle.from_prefix("_MSHRReq")
|
||||
_itlb = _18Bundle.from_prefix("_itlb")
|
||||
_metaRead = _27Bundle.from_prefix("_metaRead")
|
||||
_wayLookupWrite = _37Bundle.from_prefix("_wayLookupWrite")
|
||||
_pmp = _30Bundle.from_prefix("_pmp")
|
||||
_flush, _csr_pf_enable, _itlbFlushPipe, _state = Signals(4)
|
||||
|
||||
class IPrefetchPipeBundle(Bundle):
|
||||
io = _34Bundle.from_prefix("io")
|
||||
clock, IPrefetchPipe_s1_flush, reset = Signals(3)
|
||||
io = _38Bundle.from_prefix("io")
|
||||
IPrefetchPipe = _5Bundle.from_prefix("IPrefetchPipe")
|
||||
reset, clock = Signals(2)
|
||||
|
|
|
|||