forked from XS-MLVP/UnityChipForXiangShan
444 lines
18 KiB
Python
444 lines
18 KiB
Python
#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 ctypes
|
|
import datetime
|
|
import toffee.funcov as fc
|
|
|
|
from dut.PreDecode import *
|
|
from dut.DecodeStage import *
|
|
|
|
from comm import get_out_dir, get_root_dir, debug, UT_FCOV, get_file_logger, get_version_checker, module_name_with
|
|
from dut.RVCExpander import *
|
|
|
|
from toffee_test.reporter import set_func_coverage
|
|
from toffee_test.reporter import set_line_coverage
|
|
|
|
# Set the toffe log level to ERROR
|
|
toffee.setup_logging(toffee.ERROR)
|
|
|
|
# Version check
|
|
version_check = get_version_checker("openxiangshan-kmh-*")
|
|
|
|
# Create a function coverage group: INT (Int instruction)
|
|
g = fc.CovGroup(UT_FCOV("../../INT"))
|
|
|
|
|
|
def init_rvc_expander_funcov(expander, g: fc.CovGroup):
|
|
"""Add watch points to the RVCExpander module to collect function 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")
|
|
|
|
# 2. Add point RVC_EXPAND_16B_RANGE to check expander input range
|
|
# - bin RANGE[start-end]. The instruction is in the range of the compressed instruction set
|
|
# This check point is added in case 'test_rv_decode.test_rvc_expand_16bit_full' dynamically, see the test case for details
|
|
|
|
# 3. Add point RVC_EXPAND_32B_RANGE to check expander input range
|
|
# - bin RANGE[start-end]. The instruction is in the range of the 32bit instruction set
|
|
# This check point is added in case 'test_rv_decode.test_rvc_expand_32bit_full' dynamically, see the test case for details
|
|
|
|
# 4. Add point RVC_EXPAND_32B_BITS to check expander function coverage
|
|
# - bin BITS[0-31]. The instruction is expanded to the corresponding 32-bit instruction
|
|
def _check_pos(i):
|
|
def check(expander):
|
|
return expander.stat()["instr"] & (1<<i) != 0
|
|
return check
|
|
g.add_watch_point(expander, {
|
|
"POS_%d"%i: _check_pos(i)
|
|
for i in range(32)
|
|
},
|
|
name="RVC_EXPAND_32B_BITS")
|
|
|
|
# 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"])
|
|
# - mark RVC_EXPAND_16B_RANGE
|
|
g.mark_function("RVC_EXPAND_32B_BITS", _M("test_rvc_expand_32bit_randomN"), bin_name=["POS_*"], raise_error=False)
|
|
|
|
# The End
|
|
return None
|
|
|
|
|
|
def init_rv_decoder_funcov(g: fc.CovGroup):
|
|
# TBD
|
|
pass
|
|
|
|
|
|
class RVCExpander(toffee.Bundle):
|
|
def __init__(self, cover_group, **kwargs):
|
|
super().__init__()
|
|
self.cover_group = cover_group
|
|
self.dut = DUTRVCExpander(**kwargs)
|
|
self.io = toffee.Bundle.from_prefix("io_", self.dut)
|
|
self.bind(self.dut)
|
|
|
|
def expand(self, instr):
|
|
self.io["in"].value = instr
|
|
self.io["fsIsOff"].value = False
|
|
self.dut.RefreshComb()
|
|
self.cover_group.sample()
|
|
return self.io["out_bits"].value, self.io["ill"].value
|
|
|
|
def stat(self):
|
|
return {
|
|
"instr": self.io["in"].value,
|
|
"decode": self.io["out_bits"].value,
|
|
"ilegal": self.io["ill"].value != 0,
|
|
}
|
|
|
|
|
|
@pytest.fixture()
|
|
def rvc_expander(request):
|
|
version_check()
|
|
fname = request.node.name
|
|
wave_file = get_out_dir("decoder/rvc_expander_%s.fst" % fname)
|
|
coverage_file = get_out_dir("decoder/rvc_expander_%s.dat" % fname)
|
|
coverage_dir = os.path.dirname(coverage_file)
|
|
os.makedirs(coverage_dir, exist_ok=True)
|
|
expander = RVCExpander(g, coverage_filename=coverage_file, waveform_filename=wave_file)
|
|
expander.dut.io_in.AsImmWrite()
|
|
init_rvc_expander_funcov(expander, g)
|
|
yield expander
|
|
expander.dut.Finish()
|
|
set_line_coverage(request, coverage_file)
|
|
set_func_coverage(request, g)
|
|
g.clear()
|
|
|
|
|
|
class Decode(toffee.Bundle):
|
|
def __init__(self, dut: DUTDecodeStage):
|
|
super().__init__()
|
|
self.dut = dut
|
|
for i in range(6):
|
|
setattr(self, f"in_data_{i}", toffee.Bundle.from_prefix(f"io_in_{i}_", dut))
|
|
setattr(self, f"out_data_{i}", toffee.Bundle.from_prefix(f"io_out_{i}_", dut))
|
|
self.input_inst = [getattr(self, f"in_data_{i}") for i in range(6)]
|
|
self.output_instrution = [getattr(self, f"out_data_{i}") for i in range(6)]
|
|
self.io = toffee.Bundle.from_prefix(f"io_", dut)
|
|
self.bind(dut)
|
|
|
|
def SetDefaultValue(self):
|
|
"""Use Boundle to set DUT pin value"""
|
|
self.io.assign({
|
|
"out_0_ready": 0b1,
|
|
"out_1_ready": 0b1,
|
|
"out_2_ready": 0b1,
|
|
"out_3_ready": 0b1,
|
|
"out_4_ready": 0b1,
|
|
"out_5_ready": 0b1,
|
|
"*": 0, # Set other pins to 0
|
|
})
|
|
|
|
def Reset(self):
|
|
"""Directly operate the dut pins to reset"""
|
|
self.dut.reset.value = 0
|
|
self.dut.Step(1)
|
|
self.dut.reset.value = 1
|
|
self.dut.Step(2)
|
|
self.dut.reset.value = 0
|
|
self.dut.Step(1)
|
|
|
|
def Input_instruction(self, i, valid, instr, isRVC, brType, isCall, isRet, pred_taken, instr_ex):
|
|
self.input_inst[i].valid.value = valid
|
|
self.input_inst[i].bits_instr.value = instr
|
|
self.input_inst[i].bits_foldpc.value = 0
|
|
self.input_inst[i].bits_exceptionVec_2.value = instr_ex
|
|
for j in range(24):
|
|
p = getattr(self.dut, f'io_in_{i}_bits_exceptionVec_{j}', None)
|
|
if p:
|
|
p.value = 0
|
|
self.input_inst[i].bits_trigger.value = 0
|
|
self.input_inst[i].bits_preDecodeInfo_isRVC.value = isRVC
|
|
self.input_inst[i].bits_preDecodeInfo_brType.value = brType
|
|
self.input_inst[i].bits_pred_taken.value = pred_taken
|
|
self.input_inst[i].bits_crossPageIPFFix.value = 0
|
|
self.input_inst[i].bits_ftqPtr_flag.value = 0
|
|
self.input_inst[i].bits_ftqPtr_value.value = 0
|
|
self.input_inst[i].bits_ftqOffset.value = 0
|
|
|
|
def FromCSR_illegalInst(self, sfenceVMA, sfencePart, hfenceGVMA, hfenceVVMA, hlsv, fsIsOff, vsIsOff, wfi, frm):
|
|
self.dut.io_fromCSR_illegalInst_sfenceVMA.value = sfenceVMA
|
|
self.dut.io_fromCSR_illegalInst_sfencePart.value = sfencePart
|
|
self.dut.io_fromCSR_illegalInst_hfenceGVMA.value = hfenceGVMA
|
|
self.dut.io_fromCSR_illegalInst_hfenceVVMA.value = hfenceVVMA
|
|
self.dut.io_fromCSR_illegalInst_hlsv.value = hlsv
|
|
self.dut.io_fromCSR_illegalInst_fsIsOff.value = fsIsOff
|
|
self.dut.io_fromCSR_illegalInst_vsIsOff.value = vsIsOff
|
|
self.dut.io_fromCSR_illegalInst_wfi.value = wfi
|
|
self.dut.io_fromCSR_illegalInst_frm.value = frm
|
|
|
|
def FromCSR_virtualInst(self, sfenceVMA, sfencePart, hfence, hlsv, wfi):
|
|
self.dut.io_fromCSR_virtualInst_sfenceVMA.value = sfenceVMA
|
|
self.dut.io_fromCSR_virtualInst_sfencePart.value = sfencePart
|
|
self.dut.io_fromCSR_virtualInst_hfence.value = hfence
|
|
self.dut.io_fromCSR_virtualInst_hlsv.value = hlsv
|
|
self.dut.io_fromCSR_virtualInst_wfi.value = wfi
|
|
|
|
def Get_input_ready(self, i):
|
|
return self.input_inst[i].ready.value
|
|
|
|
def Get_allow_input_number(self):
|
|
cnt = 0
|
|
for i in range(6):
|
|
if self.input_inst[i].ready.value == 1:
|
|
cnt = i + 1
|
|
else:
|
|
break
|
|
return cnt
|
|
|
|
def Input_instruction_list(self, insts, valid):
|
|
for i in range(6):
|
|
self.Input_instruction(i, 0, 0, 0, 0, 0, 0, 0,0)
|
|
for i, inst in enumerate(insts):
|
|
self.Input_instruction(i, valid, inst[0], 0, 0, 0, 0, 0,inst[3])
|
|
|
|
def Get_decode_result(self):
|
|
insts_result = []
|
|
num = 0
|
|
for i in range(6):
|
|
if self.output_instrution[i].valid.value == 1 and self.output_instrution[i].bits_lastUop.value == 1:
|
|
insts_result.append((self.output_instrution[i].bits_instr.value,
|
|
self.output_instrution[i].bits_exceptionVec_2.value or self.output_instrution[i].bits_exceptionVec_22.value,
|
|
self.output_instrution[i].bits_firstUop.value))
|
|
num = num + 1
|
|
return num, insts_result
|
|
|
|
def Get_decode_checkpoint_illeagl_inst(self):
|
|
illegal = 0
|
|
for i in range(6):
|
|
if self.output_instrution[i].valid.value == 1 and self.output_instrution[i].bits_lastUop.value == 1:
|
|
if (self.output_instrution[i].bits_exceptionVec_2.value or self.output_instrution[i].bits_exceptionVec_22.value):
|
|
illegal = 1
|
|
return illegal
|
|
|
|
def Get_decode_checkpoint_complex_inst(self):
|
|
complex = 0
|
|
for i in range(6):
|
|
if self.output_instrution[i].valid.value == 1 and self.output_instrution[i].bits_lastUop.value == 1 and self.output_instrution[i].bits_firstUop.value != 1:
|
|
complex = 1
|
|
return complex
|
|
|
|
|
|
@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()
|
|
|
|
|
|
def comapre_result(ref_value_list, dut_value_list, num):
|
|
eq = True
|
|
if num == 0:
|
|
return None
|
|
else:
|
|
for i in range(num):
|
|
if ref_value_list[i][1] != dut_value_list[i][1]:
|
|
debug("================================")
|
|
debug(ref_value_list[i])
|
|
debug(dut_value_list[i])
|
|
eq = False
|
|
return eq
|
|
|
|
|
|
log_all_info_file = None
|
|
log_err_info_file = None
|
|
|
|
|
|
def open_log_file(name):
|
|
global log_all_info_file
|
|
global log_err_info_file
|
|
# Obtain the current time and format it
|
|
current_time = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
|
output_dir = get_out_dir("decoder/log/log")
|
|
# Create a file name
|
|
if name is not None:
|
|
filename_all = output_dir + f"_all_{name}.txt"
|
|
filename_err = output_dir + f"_err_{name}.txt"
|
|
else:
|
|
filename_all = output_dir + f"_all_{current_time}.txt"
|
|
filename_err = output_dir + f"_err_{current_time}.txt"
|
|
log_all_info_file = get_file_logger(filename_all, format=None)
|
|
log_err_info_file = get_file_logger(filename_err, format=None)
|
|
|
|
|
|
def close_log_file():
|
|
global log_all_info_file
|
|
global log_err_info_file
|
|
if log_all_info_file is not None:
|
|
log_all_info_file = None
|
|
if log_err_info_file is not None:
|
|
log_err_info_file = None
|
|
|
|
|
|
def write_all_info_to_file(info):
|
|
if log_all_info_file is not None:
|
|
log_all_info_file.info(info)
|
|
else:
|
|
debug("remember open_log_file , close_log_file")
|
|
|
|
|
|
def write_err_info_to_file(info):
|
|
if log_err_info_file is not None:
|
|
log_err_info_file.info(info)
|
|
else:
|
|
debug("remember open_log_file , close_log_file")
|
|
|
|
|
|
# Write the comparison results to a file
|
|
# ref_value_list[i][0] = Decimal display of instructions, ref_value_list[i][1] = The reference results are used to determine whether the instruction is illegal,
|
|
# ref_value_list[i][2] = Disassembly result of the instruction, ref_value_list[i][3] = Preliminary screening for illegal instructions is conducted through the RVCExpander module
|
|
# dut_value_list[i][0] = Decimal representation of the instructions output by the Decoder module,
|
|
# dut_value_list[i][1] = The output results from the Decoder are used to determine whether anomalies exist,
|
|
# dut_value_list[i][2] = The output results from the Decoder are used to determine whether the instruction is a complex instruction
|
|
def comapre_result_in_text(ref_value_list, dut_value_list, num):
|
|
eq = True
|
|
if num == 0:
|
|
return None
|
|
else:
|
|
for i in range(num):
|
|
# Do not change to assert for now. First, focus on implementing the functionality. Assert directly stops execution.。
|
|
if (ref_value_list[i][2] == 0 or (ref_value_list[i][0] == dut_value_list[i][0])) and (ref_value_list[i][1] == dut_value_list[i][1]):
|
|
# print("Meets expectations:( ̄︶ ̄)↗")
|
|
good_info = f"good ----- ref: {ref_value_list[i][0]}, {ref_value_list[i][1]}, {ref_value_list[i][2]}, {ref_value_list[i][3]}"
|
|
write_all_info_to_file(good_info)
|
|
else:
|
|
# print("Not meeting expectations: <(_ _)>")
|
|
bad_info = f"bad ----- ref: {ref_value_list[i][0]}, {ref_value_list[i][1]}, {ref_value_list[i][2]}, {ref_value_list[i][3]}, old inst: {ref_value_list[i][4]}, dut: {dut_value_list[i][0]}, {dut_value_list[i][1]}, complex: {dut_value_list[i][2] == 0}"
|
|
write_all_info_to_file(bad_info)
|
|
if(ref_value_list[i][2][0] != 'v'):
|
|
write_err_info_to_file(bad_info)
|
|
eq = False
|
|
return eq
|
|
|
|
|
|
# Filter out certain cases where exception can be identified through instruction itself,
|
|
# Supplement with the exception detection situation of the reference model.
|
|
def instr_filter(insn_disasm_text):
|
|
instr_opcode = insn_disasm_text.split(' ')[0]
|
|
is_except = 0
|
|
if (instr_opcode == "c.lwsp" or instr_opcode == "c.ldsp" or instr_opcode == "c.addiw"):
|
|
dst = insn_disasm_text.split()[1].split(',')[0]
|
|
if dst == "zero":
|
|
is_except = 1
|
|
elif (instr_opcode == "c.addi4spn"):
|
|
imm = insn_disasm_text.split()[3]
|
|
if imm == "0":
|
|
is_except = 1
|
|
elif (instr_opcode == "c.addi16sp"):
|
|
imm = insn_disasm_text.split()[2]
|
|
if imm == "0":
|
|
is_except = 1
|
|
elif (instr_opcode == "c.lui"):
|
|
imm = insn_disasm_text.split()[2]
|
|
if imm == "0x0":
|
|
is_except = 1
|
|
elif (instr_opcode == "c.jr"):
|
|
rs1 = insn_disasm_text.split()[1]
|
|
if rs1 == "zero":
|
|
is_except = 1
|
|
elif (instr_opcode == "c.unimp"):
|
|
is_except = 1
|
|
return is_except
|
|
|
|
|
|
# Disassemble the instruction and send it to the rvc_expand module for decoding of compressed instructions
|
|
def convert_reference_format(rvc_expander, ref_insts, need_expand, disasm_func, disasm_free_func, disasm_arg = 0):
|
|
inst_list = []
|
|
for insn in ref_insts:
|
|
c_void_ptr = disasm_func(ctypes.c_uint64(insn), disasm_arg)
|
|
insn_disasm = ctypes.cast(c_void_ptr, ctypes.c_char_p).value.decode('utf-8')
|
|
disasm_free_func(c_void_ptr)
|
|
|
|
if need_expand == True:
|
|
instr_bits, instr_ex = rvc_expander.expand(insn)
|
|
else:
|
|
instr_ex = 0
|
|
instr_bits = insn
|
|
|
|
if insn_disasm == "unknown":
|
|
inst_list.append((instr_bits, 1, insn_disasm, instr_ex, insn))
|
|
else:
|
|
is_excpet = instr_filter(insn_disasm)
|
|
inst_list.append((instr_bits, is_excpet, insn_disasm, instr_ex, insn))
|
|
return inst_list
|
|
|
|
|
|
# The main part of the test environment
|
|
def decode_run(decoder, inst_list, need_log_file, log_file_name = None):
|
|
if need_log_file == True:
|
|
open_log_file(log_file_name)
|
|
decoder.SetDefaultValue()
|
|
decoder.Reset()
|
|
pos = 0
|
|
detect_pos = 0
|
|
insts_length = len(inst_list)
|
|
sub_valid = 1
|
|
sub_list = inst_list[pos:pos+6]
|
|
success = True
|
|
while True:
|
|
decoder.Input_instruction_list(sub_list,sub_valid)
|
|
decoder.dut.Step(1)
|
|
allow_number = decoder.Get_allow_input_number()
|
|
if allow_number > 0:
|
|
pos = pos + allow_number
|
|
sub_list = inst_list[pos:pos+allow_number]
|
|
sub_valid = 1
|
|
num, step_result_list = decoder.Get_decode_result()
|
|
if num > 0:
|
|
if need_log_file == True:
|
|
if comapre_result_in_text(inst_list[detect_pos:detect_pos+num], step_result_list, num) == False:
|
|
success = False
|
|
else:
|
|
if comapre_result(inst_list[detect_pos:detect_pos+num], step_result_list, num) == False:
|
|
success = False
|
|
detect_pos = detect_pos + num
|
|
if pos >= insts_length:
|
|
break
|
|
decoder.dut.Step(10)
|
|
close_log_file()
|
|
return success
|