diff --git a/ut_backend/ctrl_block/decode/README.md b/ut_backend/ctrl_block/decode/README.md
index ff6c9bbc..5186e419 100644
--- a/ut_backend/ctrl_block/decode/README.md
+++ b/ut_backend/ctrl_block/decode/README.md
@@ -2,14 +2,14 @@
## 测试目标
-解码单元的功能是对输入的指令进行解码,最终转换为后端可识别的微指令。输出的指令有两种类型:16位的压缩指令(RVC) 和 32位的普通指令(RVI)。本单元测试的主要目的是**检查Dcode模块是否能识别所有非法指令**。
+解码单元的功能是对输入的指令进行解码,最终转换为后端可识别的微指令。输出的指令有两种类型:16位的压缩指令(RVC) 和 32位的普通指令(RVI)。本单元测试检查 Decode 模块能否识别非法指令,并验证输出反压期间的 ready/valid 协议及复杂指令的多 uop 排空行为。
测试基本流程为:
-1. 随机生成指令
-1. 把指令输给DUT,得到解码结果(结果种包含是否为异常指令)
-1. 把指令输给disasm,判断能否正常解析(disasm为RISC-V官方反汇编工具,可以认为 Golden)
-1. 对比DUT和disasm的结果判断是否一致
+1. 用固定编码验证合法/非法结果、反压和复杂指令排空
+1. 随机生成指令并输入 DUT,检查每条指令的握手、传输顺序和完成次数
+1. 用 disasm 提供随机指令的辅助诊断信息,但不把缺少架构状态的语法解析当作异常位 Golden
+1. 对固定用例的异常位和多 uop 属性做严格比较
## 测试环境 Env
@@ -23,7 +23,7 @@
预解码阶段,[TBD]
- **ut.decodestage**
-[TBD]
+提供 6 路输入和 6 路输出。当前 RTL 以 `io_out_0_ready` 计算全局输出容量,因此 Env 将 6 路 output ready 作为一个整体驱动,不支持部分 lane ready。
依赖:
@@ -39,7 +39,9 @@
|2|||检查是否能展开所有正常指令,
发现所有非法指令|RVC_EXPAND_ALL_16B|RANGE[`start~end`]: 16位压缩指令共有 2^16种可能,
通过不同的start-end指定输入指令的
遍历范围,遍历该范围内的输入是否
是合法或非法指令(start,end由用例指定)|
|3||常规指令展开|遍历所有32bit指令,
检查是否合法|RVC_EXPAND_ALL_32B|检查项同上|
|4|||随机生成N条32位指令,
检查是否合法|RVC_EXPAND_RANDOM_32B|POS_{i}:为了保证随机指令足够多,判断随机
生成的指令中第i位是否为1(0 <= i < 32)|
-|5|TBD|||||
+|5|decodestage|输出反压|检查全阻塞期间无握手且 payload 保持,恢复后仅传输一次|DECODE_OUTPUT_READY|ALL_STALLED、ALL_READY|
+|6|decodestage|复杂指令多 uop 解码|检查输入结束后继续运行直至最后一个 uop 握手|DECODE_COMPLEX_DRAIN|COMPLEX_LAST_UOP_FIRED|
+|7|decodestage|确定性非法指令|检查固定非法编码仅完成一次且置异常位|DECODE_KNOWN_ILLEGAL|KNOWN_ILLEGAL_COMPLETED|
## Env提供的验证接口(API)
@@ -73,7 +75,27 @@ class RVCExpander
def rvc_expander(request) -> RVCExpander:
```
-#### 2. TBD
+#### 2. decodestage
+
+```python
+# 同步驱动 6 路 output ready;不允许部分 lane ready
+decoder.Set_output_ready(ready)
+
+# 驱动并采样一个周期,在上升沿前返回 ready/valid 握手快照
+cycle = decoder.Run_cycle(insts=(), output_ready=True)
+
+# 按输入握手推进,并在所有指令的 lastUop 握手后结束
+success = decode_run(decoder, inst_list, need_log_file=False)
+
+# 调试或协议检查可请求统计信息
+success, stats = decode_run(
+ decoder, inst_list, need_log_file=False, return_stats=True
+)
+```
+
+`decode_run` 会在输入全部接收后清空 input valid,继续运行直至所有架构指令的 `lastUop` 完成;如果超过按输入数量计算的超时上限则失败。默认返回值仍为布尔值,以兼容已有用例。
+
+随机指令用例中的 syntax-only disasm 没有当前扩展、CSR、VType 状态,也可能不了解 DUT 的自定义编码,因此只用于辅助诊断;随机结果严格检查指令传输与顺序,不把 disasm 当作异常位 Golden。异常位由固定合法序列和固定非法编码用例做严格比较。固定的复杂指令排空用例会先执行 `vsetvli`,并逐条检查多 uop 的 `firstUop`、`lastUop` 和 `numUops`。
## 用例说明
diff --git a/ut_backend/ctrl_block/decode/env/decode_wrapper.py b/ut_backend/ctrl_block/decode/env/decode_wrapper.py
index 70709f44..17fa6941 100644
--- a/ut_backend/ctrl_block/decode/env/decode_wrapper.py
+++ b/ut_backend/ctrl_block/decode/env/decode_wrapper.py
@@ -125,25 +125,63 @@ 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)
+ self._input_pin_names = tuple(
+ name
+ for name in dir(dut)
+ if getattr(getattr(dut, name), "mIOType", None) == 0
+ and name not in {"clock", "reset"}
+ )
+ for name in self._input_pin_names:
+ getattr(dut, name).AsImmWrite()
+ payload_prefix = "io_out_0_bits_"
+ self._output_payload_fields = tuple(sorted(
+ name[len(payload_prefix):]
+ for name in dir(dut)
+ if name.startswith(payload_prefix)
+ and hasattr(getattr(dut, name), "value")
+ ))
+ required_payload_fields = {
+ "instr", "firstUop", "lastUop", "uopIdx", "numUops"
+ }
+ missing_payload_fields = required_payload_fields.difference(
+ self._output_payload_fields
+ )
+ if missing_payload_fields:
+ raise RuntimeError(
+ "DecodeStage output payload discovery missed: "
+ + ", ".join(sorted(missing_payload_fields))
+ )
+ self._last_cycle_state = {}
+ self._run_stats = {}
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
- })
+ """Drive every non-clock input to a deterministic default value."""
+ for name in self._input_pin_names:
+ getattr(self.dut, name).value = 0
+ self.Set_output_ready(True)
+
+ def Set_output_ready(self, ready):
+ """Drive the six output ready signals with the same value.
+
+ DecodeStage only uses lane 0 ready to calculate its global output
+ capacity. The integration contract therefore requires all six lanes
+ to be driven together.
+ """
+ if type(ready) not in (bool, int) or ready not in (0, 1):
+ raise ValueError("output ready must be boolean")
+ ready_value = int(bool(ready))
+ for lane in range(6):
+ getattr(self.dut, f"io_out_{lane}_ready").value = ready_value
+
+ def Get_output_ready_state(self):
+ """Return the output ready state and reject partial-ready driving."""
+ ready_state = tuple(
+ int(getattr(self.dut, f"io_out_{lane}_ready").value)
+ for lane in range(6)
+ )
+ assert len(set(ready_state)) == 1, \
+ "DecodeStage output ready must be identical on all lanes"
+ return ready_state
def Reset(self):
"""Directly operate the dut pins to reset"""
@@ -155,23 +193,36 @@ def Reset(self):
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
- if hasattr(self.input_inst[i], "bits_foldpc"):
- self.input_inst[i].bits_foldpc.value = 0
- self.input_inst[i].bits_exceptionVec_2.value = instr_ex
+ getattr(self.dut, f"io_in_{i}_valid").value = valid
+ getattr(self.dut, f"io_in_{i}_bits_instr").value = instr
+ foldpc = getattr(self.dut, f"io_in_{i}_bits_foldpc", None)
+ if foldpc is not None:
+ foldpc.value = 0
for j in range(24):
p = getattr(self.dut, f'io_in_{i}_bits_exceptionVec_{j}', None)
- if p:
+ if p is not None:
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
+ getattr(self.dut, f"io_in_{i}_bits_exceptionVec_2").value = instr_ex
+ getattr(self.dut, f"io_in_{i}_bits_trigger").value = 0
+ getattr(
+ self.dut, f"io_in_{i}_bits_preDecodeInfo_isRVC"
+ ).value = isRVC
+ getattr(
+ self.dut, f"io_in_{i}_bits_preDecodeInfo_brType"
+ ).value = brType
+ getattr(
+ self.dut, f"io_in_{i}_bits_preDecodeInfo_isCall"
+ ).value = isCall
+ getattr(
+ self.dut, f"io_in_{i}_bits_preDecodeInfo_isRet"
+ ).value = isRet
+ getattr(self.dut, f"io_in_{i}_bits_pred_taken").value = pred_taken
+ getattr(
+ self.dut, f"io_in_{i}_bits_crossPageIPFFix"
+ ).value = 0
+ getattr(self.dut, f"io_in_{i}_bits_ftqPtr_flag").value = 0
+ getattr(self.dut, f"io_in_{i}_bits_ftqPtr_value").value = 0
+ getattr(self.dut, f"io_in_{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
@@ -192,50 +243,232 @@ def FromCSR_virtualInst(self, sfenceVMA, sfencePart, hfence, hlsv, wfi):
self.dut.io_fromCSR_virtualInst_wfi.value = wfi
def Get_input_ready(self, i):
- return self.input_inst[i].ready.value
+ return getattr(self.dut, f"io_in_{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
+ """Return the contiguous prefix of inputs accepted this cycle."""
+ fire_mask = self.Get_input_fire_mask()
+ return next(
+ (lane for lane, fired in enumerate(fire_mask) if not fired),
+ len(fire_mask),
+ )
+
+ def Get_input_fire_mask(self):
+ """Return input handshakes for the current combinational cycle."""
+ return tuple(
+ int(
+ bool(getattr(self.dut, f"io_in_{lane}_valid").value)
+ and bool(getattr(self.dut, f"io_in_{lane}_ready").value)
+ )
+ for lane in range(6)
+ )
+
+ def Get_input_valid_mask(self):
+ """Return the current input valid state."""
+ return tuple(
+ int(bool(getattr(self.dut, f"io_in_{lane}_valid").value))
+ for lane in range(6)
+ )
+
+ def Get_input_ready_state(self):
+ """Return the current input ready state."""
+ return tuple(
+ int(bool(getattr(self.dut, f"io_in_{lane}_ready").value))
+ for lane in range(6)
+ )
+
+ def Get_output_fire_mask(self):
+ """Return output handshakes for the current combinational cycle."""
+ return tuple(
+ int(
+ bool(getattr(self.dut, f"io_out_{lane}_valid").value)
+ and bool(getattr(self.dut, f"io_out_{lane}_ready").value)
+ )
+ for lane in range(6)
+ )
+
+ def Get_output_valid_mask(self):
+ """Return the current output valid state."""
+ return tuple(
+ int(bool(getattr(self.dut, f"io_out_{lane}_valid").value))
+ for lane in range(6)
+ )
+
+ def Get_output_payloads(self):
+ """Copy every output payload field into ordinary Python values."""
+ output_payloads = []
+ for lane in range(6):
+ if getattr(self.dut, f"io_out_{lane}_valid").value != 1:
+ continue
+ fields = tuple(
+ (field, int(getattr(
+ self.dut,
+ f"io_out_{lane}_bits_{field}"
+ ).value))
+ for field in self._output_payload_fields
+ )
+ output_payloads.append((lane, fields))
+ return tuple(output_payloads)
+
+ def Get_fired_uops(self):
+ """Return the uops transferred on the current output handshake."""
+ fired_uops = []
+ for lane in range(6):
+ if getattr(self.dut, f"io_out_{lane}_valid").value != 1 or \
+ getattr(self.dut, f"io_out_{lane}_ready").value != 1:
+ continue
+ fired_uops.append({
+ "lane": lane,
+ "instr": int(getattr(
+ self.dut, f"io_out_{lane}_bits_instr"
+ ).value),
+ "first_uop": int(getattr(
+ self.dut, f"io_out_{lane}_bits_firstUop"
+ ).value),
+ "last_uop": int(getattr(
+ self.dut, f"io_out_{lane}_bits_lastUop"
+ ).value),
+ "uop_idx": int(getattr(
+ self.dut, f"io_out_{lane}_bits_uopIdx"
+ ).value),
+ "num_uops": int(getattr(
+ self.dut, f"io_out_{lane}_bits_numUops"
+ ).value),
+ })
+ return tuple(fired_uops)
+
+ def Get_last_cycle_state(self):
+ """Return the most recent Run_cycle handshake snapshot."""
+ return self._last_cycle_state
+
+ def Set_run_stats(self, stats):
+ """Store the most recent decode_run statistics for functional coverage."""
+ self._run_stats = dict(stats)
+
+ def Get_run_stats(self):
+ """Return statistics from the most recent decode_run call."""
+ return self._run_stats
def Input_instruction_list(self, insts, valid):
+ if len(insts) > 6:
+ raise ValueError("DecodeStage accepts at most six instructions")
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):
+ def Get_decode_result(self, fired_only=False):
+ """Return completed architectural instructions.
+
+ The default preserves the historical valid-only behavior. Protocol
+ scoreboards should pass ``fired_only=True`` so a stalled output is not
+ consumed more than once.
+ """
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))
+ output_valid = getattr(self.dut, f"io_out_{i}_valid").value == 1
+ output_ready = getattr(self.dut, f"io_out_{i}_ready").value == 1
+ output_fired = output_valid and output_ready
+ output_last_uop = getattr(
+ self.dut, f"io_out_{i}_bits_lastUop"
+ ).value
+ if output_valid and output_last_uop == 1 and (
+ not fired_only or output_fired):
+ output_instr = getattr(
+ self.dut, f"io_out_{i}_bits_instr"
+ ).value
+ output_exception = (
+ getattr(
+ self.dut, f"io_out_{i}_bits_exceptionVec_2"
+ ).value
+ or getattr(
+ self.dut, f"io_out_{i}_bits_exceptionVec_22"
+ ).value
+ )
+ output_first_uop = getattr(
+ self.dut, f"io_out_{i}_bits_firstUop"
+ ).value
+ insts_result.append((
+ output_instr,
+ output_exception,
+ output_first_uop,
+ ))
num = num + 1
return num, insts_result
+ def Run_cycle(self, insts=(), output_ready=True, cover_group=None):
+ """Drive and sample one ready/valid cycle before its rising edge."""
+ insts = list(insts)
+ if len(insts) > 6:
+ raise ValueError("Run_cycle accepts at most six instructions")
+ self.Set_output_ready(output_ready)
+ self.Input_instruction_list(insts, int(bool(insts)))
+ self.dut.RefreshComb()
+
+ input_fire_mask = self.Get_input_fire_mask()
+ seen_gap = False
+ for fired in input_fire_mask:
+ if not fired:
+ seen_gap = True
+ elif seen_gap:
+ raise AssertionError("DecodeStage input fire must be a prefix")
+
+ _, completed = self.Get_decode_result(fired_only=True)
+ fired_uops = self.Get_fired_uops()
+ self._last_cycle_state = {
+ "accepted": sum(input_fire_mask),
+ "input_valid_mask": self.Get_input_valid_mask(),
+ "input_ready_state": self.Get_input_ready_state(),
+ "input_instructions": tuple(
+ int(getattr(
+ self.dut, f"io_in_{lane}_bits_instr"
+ ).value)
+ for lane in range(6)
+ ),
+ "input_fire_mask": input_fire_mask,
+ "output_ready_state": self.Get_output_ready_state(),
+ "output_valid_mask": self.Get_output_valid_mask(),
+ "output_fire_mask": self.Get_output_fire_mask(),
+ "output_payloads": self.Get_output_payloads(),
+ "fired_uops": fired_uops,
+ "completed": tuple(completed),
+ "complex_last_uop_fired": any(
+ uop["last_uop"] and not uop["first_uop"]
+ for uop in fired_uops
+ ),
+ }
+ if cover_group is not None:
+ cover_group.sample()
+ self.dut.Step(1)
+ return self._last_cycle_state
+
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):
+ if getattr(self.dut, f"io_out_{i}_valid").value == 1 and \
+ getattr(self.dut, f"io_out_{i}_bits_lastUop").value == 1:
+ if getattr(
+ self.dut,
+ f"io_out_{i}_bits_exceptionVec_2"
+ ).value or getattr(
+ self.dut,
+ f"io_out_{i}_bits_exceptionVec_22"
+ ).value:
illegal = 1
+ if self._run_stats.get("saw_illegal_completion", False):
+ 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:
+ if getattr(self.dut, f"io_out_{i}_valid").value == 1 and \
+ getattr(self.dut, f"io_out_{i}_bits_lastUop").value == 1 and \
+ getattr(self.dut, f"io_out_{i}_bits_firstUop").value != 1:
complex = 1
+ if self._run_stats.get("saw_complex_last_uop", False):
+ complex = 1
return complex
@@ -259,7 +492,11 @@ def comapre_result(ref_value_list, dut_value_list, num):
return None
else:
for i in range(num):
- if ref_value_list[i][1] != dut_value_list[i][1]:
+ expected_exception = (
+ ref_value_list[i][1] or ref_value_list[i][3]
+ )
+ if ref_value_list[i][0] != dut_value_list[i][0] or \
+ expected_exception != dut_value_list[i][1]:
debug("================================")
debug(ref_value_list[i])
debug(dut_value_list[i])
@@ -323,9 +560,16 @@ def comapre_result_in_text(ref_value_list, dut_value_list, num):
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]):
+ disasm_text = ref_value_list[i][2]
+ instruction_matches = (
+ disasm_text == 0
+ or ref_value_list[i][0] == dut_value_list[i][0]
+ )
+ # Random syntax-only disassembly has no architectural extension,
+ # CSR or VType context, and may not know DUT-specific encodings.
+ # This path therefore checks transfer/order only; deterministic
+ # exception tests use comapre_result for an exact comparison.
+ if instruction_matches:
# 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)
@@ -333,8 +577,7 @@ def comapre_result_in_text(ref_value_list, dut_value_list, num):
# 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)
+ write_err_info_to_file(bad_info)
eq = False
return eq
@@ -392,36 +635,119 @@ def convert_reference_format(rvc_expander, ref_insts, need_expand, disasm_func,
# The main part of the test environment
-def decode_run(decoder, inst_list, need_log_file, log_file_name=None):
+def decode_run(decoder, inst_list, need_log_file, log_file_name=None,
+ timeout_cycles=None, return_stats=False, cover_group=None):
if need_log_file:
open_log_file(log_file_name)
decoder.SetDefaultValue()
decoder.Reset()
+ decoder.Set_run_stats({})
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:
+ if timeout_cycles is None:
+ timeout_cycles = max(64, 16 * insts_length + 16)
+ stats = {
+ "accepted": 0,
+ "completed": 0,
+ "cycles": 0,
+ "uops_fired": 0,
+ "uop_trace": [],
+ "last_input_fire_cycle": None,
+ "last_completion_cycle": None,
+ "timed_out": False,
+ "drained": False,
+ "saw_illegal_completion": False,
+ "saw_complex_last_uop": False,
+ "completed_instructions": [],
+ }
+
+ try:
+ while pos < insts_length or detect_pos < insts_length:
+ if stats["cycles"] >= timeout_cycles:
+ stats["timed_out"] = True
+ success = False
+ debug(f"Decode timeout after {timeout_cycles} cycles")
+ break
+
+ cycle = stats["cycles"]
+ cycle_state = decoder.Run_cycle(
+ inst_list[pos:pos + 6],
+ output_ready=True,
+ cover_group=cover_group,
+ )
+ accepted = cycle_state["accepted"]
+ old_pos = pos
+ pos = pos + accepted
+ stats["accepted"] = pos
+ if old_pos < insts_length and pos >= insts_length:
+ stats["last_input_fire_cycle"] = cycle
+
+ step_result_list = list(cycle_state["completed"])
+ num = len(step_result_list)
+ stats["uops_fired"] += len(cycle_state["fired_uops"])
+ stats["uop_trace"].extend(
+ dict(uop, cycle=cycle)
+ for uop in cycle_state["fired_uops"]
+ )
+ stats["saw_complex_last_uop"] = (
+ stats["saw_complex_last_uop"]
+ or cycle_state["complex_last_uop_fired"]
+ )
+ stats["saw_illegal_completion"] = (
+ stats["saw_illegal_completion"]
+ or any(bool(result[1]) for result in step_result_list)
+ )
+ if detect_pos + num > pos:
+ success = False
+ debug("Decode produced a completion before input handshake")
+ if detect_pos + num > insts_length:
+ success = False
+ num = insts_length - detect_pos
+ step_result_list = step_result_list[:num]
+ if num > 0:
+ ref_results = inst_list[detect_pos:detect_pos + num]
+ if need_log_file:
+ if comapre_result_in_text(
+ ref_results, step_result_list, num) is False:
+ success = False
+ elif comapre_result(
+ ref_results, step_result_list, num) is False:
success = False
- detect_pos = detect_pos + num
- if pos >= insts_length:
- break
- decoder.dut.Step(10)
- close_log_file()
+ detect_pos = detect_pos + num
+ stats["completed"] = detect_pos
+ stats["last_completion_cycle"] = cycle
+ stats["completed_instructions"].extend(
+ int(result[0]) for result in step_result_list
+ )
+ stats["cycles"] += 1
+
+ stats["drained"] = (
+ pos == insts_length and detect_pos == insts_length
+ )
+ if success and stats["drained"]:
+ quiet_state = decoder.Run_cycle(
+ (),
+ output_ready=True,
+ cover_group=cover_group,
+ )
+ stats["cycles"] += 1
+ stats["uops_fired"] += len(quiet_state["fired_uops"])
+ stats["uop_trace"].extend(
+ dict(uop, cycle=stats["cycles"] - 1)
+ for uop in quiet_state["fired_uops"]
+ )
+ if quiet_state["fired_uops"] or quiet_state["completed"]:
+ success = False
+ finally:
+ close_log_file()
+
+ stats["completed_instructions"] = tuple(
+ stats["completed_instructions"]
+ )
+ stats["uop_trace"] = tuple(stats["uop_trace"])
+ decoder.Set_run_stats(stats)
+ if return_stats:
+ return success, stats
return success
diff --git a/ut_backend/ctrl_block/decode/test_rv_decode.py b/ut_backend/ctrl_block/decode/test_rv_decode.py
index a457390b..38362652 100644
--- a/ut_backend/ctrl_block/decode/test_rv_decode.py
+++ b/ut_backend/ctrl_block/decode/test_rv_decode.py
@@ -18,6 +18,165 @@
from tools.disasm import disasmbly, libdisasm
+def _decode_references(instructions, expected_exception=0):
+ """Build deterministic references without invoking the random generator."""
+ return [
+ (instruction, expected_exception, "", 0, instruction)
+ for instruction in instructions
+ ]
+
+
+@toffee_test.testcase
+async def test_decode_backpressure_holds_and_releases(decoder):
+ """Stalled outputs stay stable and transfer exactly once after release."""
+ instructions = (
+ 0x00100093, # addi x1, x0, 1
+ 0x00200113, # addi x2, x0, 2
+ 0x00300193, # addi x3, x0, 3
+ 0x00400213, # addi x4, x0, 4
+ 0x00500293, # addi x5, x0, 5
+ 0x00600313, # addi x6, x0, 6
+ )
+ references = _decode_references(instructions)
+ g.add_cover_point(decoder, {
+ "ALL_STALLED": lambda env: env.Get_output_ready_state() == (0,) * 6,
+ "ALL_READY": lambda env: env.Get_output_ready_state() == (1,) * 6,
+ }, name="DECODE_OUTPUT_READY")
+
+ decoder.SetDefaultValue()
+ decoder.Reset()
+ stalled_first = decoder.Run_cycle(
+ references, output_ready=False, cover_group=g
+ )
+ stalled_second = decoder.Run_cycle(
+ references, output_ready=False, cover_group=g
+ )
+ released = decoder.Run_cycle(
+ references, output_ready=True, cover_group=g
+ )
+ quiet = decoder.Run_cycle((), output_ready=True, cover_group=g)
+
+ assert stalled_first["input_fire_mask"] == (0,) * 6
+ assert stalled_first["output_fire_mask"] == (0,) * 6
+ assert stalled_first["output_valid_mask"] == \
+ stalled_second["output_valid_mask"]
+ assert stalled_first["output_valid_mask"] == (1,) * 6
+ assert stalled_second["input_fire_mask"] == (0,) * 6
+ assert stalled_second["output_fire_mask"] == (0,) * 6
+ assert stalled_first["output_payloads"] == stalled_second["output_payloads"]
+ assert released["input_fire_mask"] == (1,) * 6
+ assert released["output_fire_mask"] == (1,) * 6
+ assert tuple(
+ int(result[0]) for result in released["completed"]
+ ) == instructions
+ assert quiet["fired_uops"] == ()
+ assert quiet["completed"] == ()
+
+ g.mark_function(
+ "DECODE_OUTPUT_READY",
+ test_decode_backpressure_holds_and_releases,
+ bin_name=["ALL_STALLED", "ALL_READY"],
+ )
+
+
+@toffee_test.testcase
+async def test_decode_drains_complex_tail(decoder):
+ """The runner waits for a complex instruction's last uop after input ends."""
+ instructions = (
+ 0x00100093, # addi x1, x0, 1
+ 0x0C007057, # vsetvli zero, zero, e8, m1, ta, ma
+ 0xC221A257, # vwaddu.vv v4, v2, v3
+ )
+ references = _decode_references(instructions)
+ g.add_cover_point(decoder, {
+ "COMPLEX_LAST_UOP_FIRED": lambda env: env.Get_last_cycle_state().get(
+ "complex_last_uop_fired", False
+ ),
+ }, name="DECODE_COMPLEX_DRAIN")
+
+ success, stats = decode_run(
+ decoder,
+ references,
+ need_log_file=False,
+ return_stats=True,
+ cover_group=g,
+ )
+
+ assert success is True, (
+ stats["completed_instructions"],
+ stats["uop_trace"],
+ )
+ assert stats["timed_out"] is False
+ assert stats["drained"] is True
+ assert stats["accepted"] == len(instructions)
+ assert stats["completed"] == len(instructions)
+ assert stats["completed_instructions"] == instructions
+ assert stats["uops_fired"] == 5
+ assert stats["saw_complex_last_uop"] is True
+ assert stats["last_completion_cycle"] > stats["last_input_fire_cycle"]
+ for instruction in instructions[1:]:
+ instruction_uops = tuple(
+ uop for uop in stats["uop_trace"]
+ if uop["instr"] == instruction
+ )
+ assert instruction_uops
+ assert instruction_uops[0]["first_uop"] == 1
+ assert all(
+ uop["first_uop"] == 0 for uop in instruction_uops[1:]
+ )
+ assert instruction_uops[-1]["last_uop"] == 1
+ assert all(
+ uop["last_uop"] == 0 for uop in instruction_uops[:-1]
+ )
+ assert all(
+ uop["num_uops"] == len(instruction_uops)
+ for uop in instruction_uops
+ )
+ assert all(
+ 0 <= uop["uop_idx"] < len(instruction_uops)
+ for uop in instruction_uops
+ )
+
+ g.mark_function(
+ "DECODE_COMPLEX_DRAIN",
+ test_decode_drains_complex_tail,
+ bin_name="COMPLEX_LAST_UOP_FIRED",
+ )
+
+
+@toffee_test.testcase
+async def test_decode_known_illegal_instructions(decoder):
+ """Known invalid encodings complete once with the illegal exception set."""
+ instructions = (0x00000000, 0xFFFFFFFF)
+ references = _decode_references(
+ instructions, expected_exception=1
+ )
+ g.add_cover_point(decoder, {
+ "KNOWN_ILLEGAL_COMPLETED": lambda env: any(
+ bool(result[1])
+ for result in env.Get_last_cycle_state().get("completed", ())
+ ),
+ }, name="DECODE_KNOWN_ILLEGAL")
+
+ success, stats = decode_run(
+ decoder,
+ references,
+ need_log_file=False,
+ return_stats=True,
+ cover_group=g,
+ )
+
+ assert success is True, stats["completed_instructions"]
+ assert stats["drained"] is True
+ assert stats["completed_instructions"] == instructions
+ assert stats["saw_illegal_completion"] is True
+ g.mark_function(
+ "DECODE_KNOWN_ILLEGAL",
+ test_decode_known_illegal_instructions,
+ bin_name="KNOWN_ILLEGAL_COMPLETED",
+ )
+
+
@toffee_test.testcase
async def test_rvc_inst(decoder, rvc_expander):
"""