Step 1 - 参数与核心数据模型

This commit is contained in:
SepComet 2026-04-14 00:40:16 +08:00
parent fa7b04a880
commit 7eab504f4a
21 changed files with 595 additions and 19 deletions

1
.gitignore vendored
View File

@ -88,3 +88,4 @@ crashlytics-build.properties
Assets/GameMain/Configs/ResourceBuilder.xml Assets/GameMain/Configs/ResourceBuilder.xml
/.dotnet /.dotnet
/openspec/changes/archive /openspec/changes/archive
/.dotnet-home

8
Assets/Scripts/Core.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d1f0f89f24ab6334f87c91090393ab8b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,28 @@
using System;
namespace VMdemo.Core
{
[Serializable]
public struct AddressParts
{
public AddressParts(ulong vpn, ulong offset, ulong l1Index, ulong l2Index)
{
Vpn = vpn;
Offset = offset;
L1Index = l1Index;
L2Index = l2Index;
}
public static AddressParts Empty => new AddressParts(0UL, 0UL, 0UL, 0UL);
public ulong Vpn;
public ulong Offset;
public ulong L1Index;
public ulong L2Index;
public override string ToString()
{
return $"VPN={Vpn}, Offset={Offset}, L1={L1Index}, L2={L2Index}";
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2170a834280c39d40826bf87a50a20b4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,141 @@
using System;
namespace VMdemo.Core
{
public static class ConfigValidator
{
public readonly struct DerivedConfig
{
public DerivedConfig(ulong pageSizeBytes, ulong physicalMemoryBytes, int offsetBits, int vpnBits, ulong frameCount)
{
PageSizeBytes = pageSizeBytes;
PhysicalMemoryBytes = physicalMemoryBytes;
OffsetBits = offsetBits;
VpnBits = vpnBits;
FrameCount = frameCount;
}
public ulong PageSizeBytes { get; }
public ulong PhysicalMemoryBytes { get; }
public int OffsetBits { get; }
public int VpnBits { get; }
public ulong FrameCount { get; }
}
public static bool TryValidate(SimulationConfig config, out string errorMessage)
{
return TryValidateAndBuildDerived(config, out _, out errorMessage);
}
public static bool TryValidateAndBuildDerived(
SimulationConfig config,
out DerivedConfig derived,
out string errorMessage)
{
derived = default;
if (config == null)
{
errorMessage = "配置不能为空。";
return false;
}
if (config.vaBits != 32 && config.vaBits != 48 && config.vaBits != 64)
{
errorMessage = "vaBits 仅支持 32、48、64。";
return false;
}
if (config.pageSizeKB <= 0)
{
errorMessage = "pageSizeKB 必须是正整数。";
return false;
}
if (!IsPowerOfTwo((ulong)config.pageSizeKB))
{
errorMessage = "pageSizeKB 必须是 2 的幂2^n KB。";
return false;
}
if (config.physicalMemoryMB <= 0)
{
errorMessage = "physicalMemoryMB 必须是正整数。";
return false;
}
if (config.tlbEntries <= 0)
{
errorMessage = "tlbEntries 必须是正整数。";
return false;
}
if (config.accessCount <= 0)
{
errorMessage = "accessCount 必须是正整数。";
return false;
}
if (config.pageFaultPenalty <= 0)
{
errorMessage = "pageFaultPenalty 必须是正整数。";
return false;
}
var pageSizeBytes = config.PageSizeBytes;
if (!TryGetLog2(pageSizeBytes, out var offsetBits))
{
errorMessage = "pageSizeKB 必须对应可计算的 2 的幂字节数。";
return false;
}
if (offsetBits >= config.vaBits)
{
errorMessage =
$"offsetBits 必须小于 vaBits当前 offsetBits={offsetBits}vaBits={config.vaBits}。";
return false;
}
var frameCount = config.FrameCount;
if (frameCount < 1UL)
{
errorMessage =
$"frameCount 必须 >= 1当前 frameCount={frameCount}。请增大 physicalMemoryMB 或减小 pageSizeKB。";
return false;
}
derived = new DerivedConfig(
pageSizeBytes,
config.PhysicalMemoryBytes,
offsetBits,
config.vaBits - offsetBits,
frameCount);
errorMessage = string.Empty;
return true;
}
private static bool IsPowerOfTwo(ulong value)
{
return value != 0UL && (value & (value - 1UL)) == 0UL;
}
private static bool TryGetLog2(ulong value, out int bits)
{
bits = -1;
if (!IsPowerOfTwo(value))
{
return false;
}
bits = 0;
while (value > 1UL)
{
value >>= 1;
bits++;
}
return true;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 278803579202bf144ad186a403dbaed2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,68 @@
using System;
namespace VMdemo.Core
{
[Serializable]
public class SimulationConfig
{
public int vaBits = 32;
public int pageSizeKB = 4;
public int physicalMemoryMB = 64;
public int tlbEntries = 16;
public int accessCount = 100;
public int pageFaultPenalty = 100;
public ulong PageSizeBytes => pageSizeKB > 0 ? (ulong)pageSizeKB * 1024UL : 0UL;
public ulong PhysicalMemoryBytes => physicalMemoryMB > 0 ? (ulong)physicalMemoryMB * 1024UL * 1024UL : 0UL;
public int OffsetBits
{
get
{
return TryGetLog2(PageSizeBytes, out var bits) ? bits : -1;
}
}
public int VpnBits
{
get
{
var offsetBits = OffsetBits;
return offsetBits >= 0 ? vaBits - offsetBits : -1;
}
}
public ulong FrameCount
{
get
{
var pageSizeBytes = PageSizeBytes;
if (pageSizeBytes == 0UL)
{
return 0UL;
}
return PhysicalMemoryBytes / pageSizeBytes;
}
}
private static bool TryGetLog2(ulong value, out int bits)
{
bits = -1;
if (value == 0UL || (value & (value - 1UL)) != 0UL)
{
return false;
}
bits = 0;
while (value > 1UL)
{
value >>= 1;
bits++;
}
return true;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 560da4f3bb13de24986a73b585c1ec1f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c143559c7269dd940b8cb1b43b2d07a5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,28 @@
using System;
using VMdemo.Core;
namespace VMdemo.Simulation
{
[Serializable]
public class SimulationState
{
public int CurrentRound { get; set; }
public ulong CurrentVirtualAddress { get; set; }
public AddressParts CurrentAddressParts { get; set; }
public bool IsPageFault { get; set; }
public bool IsTlbHit { get; set; }
public bool IsPageTableHit { get; set; }
public int CurrentCost { get; set; }
public void Reset()
{
CurrentRound = 0;
CurrentVirtualAddress = 0UL;
CurrentAddressParts = AddressParts.Empty;
IsPageFault = false;
IsTlbHit = false;
IsPageTableHit = false;
CurrentCost = 0;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5ab511454f082cd4987e3388c3eee16a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

8
Assets/Scripts/UI.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a14121217fe28e64f9ddd4fc9f89c32a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,3 @@
{
"name": "VMdemo"
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: baede126690de864e8b4ee808c6a2716
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

8
Assets/Tests.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 34f7f3b70dfcb49478f58b5b5af22019
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 760fef5586fd36f4f9004a24632450a7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,137 @@
using NUnit.Framework;
using VMdemo.Core;
using VMdemo.Simulation;
namespace VMdemo.Tests.EditMode
{
public class Step1CoreModelTests
{
[Test]
public void TryValidateAndBuildDerived_ValidConfig_ReturnsExpectedDerivedValues()
{
var config = new SimulationConfig
{
vaBits = 48,
pageSizeKB = 4,
physicalMemoryMB = 64,
tlbEntries = 16,
accessCount = 1000,
pageFaultPenalty = 100
};
var ok = ConfigValidator.TryValidateAndBuildDerived(config, out var derived, out var error);
Assert.IsTrue(ok, error);
Assert.That(error, Is.Empty);
Assert.That(derived.PageSizeBytes, Is.EqualTo(4096UL));
Assert.That(derived.PhysicalMemoryBytes, Is.EqualTo(67108864UL));
Assert.That(derived.OffsetBits, Is.EqualTo(12));
Assert.That(derived.VpnBits, Is.EqualTo(36));
Assert.That(derived.FrameCount, Is.EqualTo(16384UL));
}
[Test]
public void TryValidate_InvalidVaBits_FailsWithReadableMessage()
{
var config = new SimulationConfig
{
vaBits = 40,
pageSizeKB = 4,
physicalMemoryMB = 64,
tlbEntries = 16,
accessCount = 10,
pageFaultPenalty = 100
};
var ok = ConfigValidator.TryValidate(config, out var error);
Assert.IsFalse(ok);
Assert.That(error, Does.Contain("vaBits"));
}
[Test]
public void TryValidate_PageSizeNotPowerOfTwo_FailsWithReadableMessage()
{
var config = new SimulationConfig
{
vaBits = 32,
pageSizeKB = 6,
physicalMemoryMB = 64,
tlbEntries = 16,
accessCount = 10,
pageFaultPenalty = 100
};
var ok = ConfigValidator.TryValidate(config, out var error);
Assert.IsFalse(ok);
Assert.That(error, Does.Contain("pageSizeKB"));
}
[Test]
public void TryValidate_OffsetBitsGreaterOrEqualVaBits_Fails()
{
var config = new SimulationConfig
{
vaBits = 32,
pageSizeKB = 4 * 1024 * 1024,
physicalMemoryMB = 8192,
tlbEntries = 16,
accessCount = 10,
pageFaultPenalty = 100
};
var ok = ConfigValidator.TryValidate(config, out var error);
Assert.IsFalse(ok);
Assert.That(error, Does.Contain("offsetBits"));
}
[Test]
public void TryValidate_FrameCountLessThanOne_Fails()
{
var config = new SimulationConfig
{
vaBits = 32,
pageSizeKB = 2048,
physicalMemoryMB = 1,
tlbEntries = 16,
accessCount = 10,
pageFaultPenalty = 100
};
var ok = ConfigValidator.TryValidate(config, out var error);
Assert.IsFalse(ok);
Assert.That(error, Does.Contain("frameCount"));
}
[Test]
public void SimulationState_Reset_ClearsAllStepState()
{
var state = new SimulationState
{
CurrentRound = 7,
CurrentVirtualAddress = 123456UL,
CurrentAddressParts = new AddressParts(10UL, 20UL, 3UL, 7UL),
IsPageFault = true,
IsTlbHit = true,
IsPageTableHit = true,
CurrentCost = 999
};
state.Reset();
Assert.That(state.CurrentRound, Is.EqualTo(0));
Assert.That(state.CurrentVirtualAddress, Is.EqualTo(0UL));
Assert.That(state.CurrentAddressParts.Vpn, Is.EqualTo(0UL));
Assert.That(state.CurrentAddressParts.Offset, Is.EqualTo(0UL));
Assert.That(state.CurrentAddressParts.L1Index, Is.EqualTo(0UL));
Assert.That(state.CurrentAddressParts.L2Index, Is.EqualTo(0UL));
Assert.IsFalse(state.IsPageFault);
Assert.IsFalse(state.IsTlbHit);
Assert.IsFalse(state.IsPageTableHit);
Assert.That(state.CurrentCost, Is.EqualTo(0));
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 52e57f0a987345340859c62fc16b1c90
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,25 @@
{
"name": "VMdemo.EditModeTests",
"rootNamespace": "",
"references": [
"Assembly-CSharp",
"UnityEngine.TestRunner",
"UnityEditor.TestRunner",
"VMdemo"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll"
],
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [],
"noEngineReferences": false
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e0e3ad2a355fe1d4e979eb4d2d33fc5a
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,6 +1,9 @@
# MVP TODO - Unity 虚拟地址寻址模拟 # MVP TODO - Unity 虚拟地址寻址模拟
此为 MVP 实现,时间要求紧,不需要太考虑代码结构,需要跨脚本访问的内容全用单例也没问题
## 0. 需求冻结(已确认) ## 0. 需求冻结(已确认)
- [x] 模式:单次地址翻译可视化 + 批量访问统计 - [x] 模式:单次地址翻译可视化 + 批量访问统计
- [x] 系统位数:仅支持 `32 / 48 / 64` - [x] 系统位数:仅支持 `32 / 48 / 64`
- [x] 页大小输入:仅支持 `2^n KB` - [x] 页大小输入:仅支持 `2^n KB`
@ -15,6 +18,7 @@
- [x] 技术栈Unity 2022 + C# - [x] 技术栈Unity 2022 + C#
## 1. 总体分步计划 ## 1. 总体分步计划
1. Step 1参数与核心数据模型 1. Step 1参数与核心数据模型
2. Step 2地址生成与拆分 2. Step 2地址生成与拆分
3. Step 3TLBLRU实现 3. Step 3TLBLRU实现
@ -25,64 +29,79 @@
8. Step 8联调、测试、验收 8. Step 8联调、测试、验收
## 2. Step 1 - 参数与核心数据模型 ## 2. Step 1 - 参数与核心数据模型
### TODO ### TODO
- [ ] 创建脚本目录:`Assets/Scripts/Core`、`Assets/Scripts/Simulation`、`Assets/Scripts/UI`
- [ ] 新建 `SimulationConfig.cs`:定义可输入参数 - [x] 创建脚本目录:`Assets/Scripts/Core`、`Assets/Scripts/Simulation`、`Assets/Scripts/UI`
- [ ] 新建 `ConfigValidator.cs`:参数合法性校验 - [x] 新建 `SimulationConfig.cs`:定义可输入参数
- [ ] 新建 `AddressParts.cs`:保存 `VPN / offset / L1Index / L2Index` - [x] 新建 `ConfigValidator.cs`:参数合法性校验
- [ ] 新建 `SimulationState.cs`:保存当前轮次、当前虚拟地址、是否缺页等 - [x] 新建 `AddressParts.cs`:保存 `VPN / offset / L1Index / L2Index`
- [x] 新建 `SimulationState.cs`:保存当前轮次、当前虚拟地址、是否缺页等
### 输入字段MVP ### 输入字段MVP
- [ ] `vaBits``32|48|64`
- [ ] `pageSizeKB``2^n` - [x] `vaBits``32|48|64`
- [ ] `physicalMemoryMB`:正整数 - [x] `pageSizeKB``2^n`
- [ ] `tlbEntries`:正整数,默认 `16` - [x] `physicalMemoryMB`:正整数
- [ ] `accessCount`:正整数 - [x] `tlbEntries`:正整数,默认 `16`
- [ ] `pageFaultPenalty`:默认 `100`(抽象开销单位) - [x] `accessCount`:正整数
- [x] `pageFaultPenalty`:默认 `100`(抽象开销单位)
### 校验规则 ### 校验规则
- [ ] `pageSizeKB` 必须是 2 的幂
- [ ] `offsetBits = log2(pageSizeBytes)``offsetBits < vaBits` - [x] `pageSizeKB` 必须是 2 的幂
- [ ] `frameCount = floor(physicalMemoryBytes / pageSizeBytes)``frameCount >= 1` - [x] `offsetBits = log2(pageSizeBytes)``offsetBits < vaBits`
- [ ] 非法输入返回明确错误文本(用于 UI 提示) - [x] `frameCount = floor(physicalMemoryBytes / pageSizeBytes)``frameCount >= 1`
- [x] 非法输入返回明确错误文本(用于 UI 提示)
### 完成标准 ### 完成标准
- [ ] 给定任意合法参数,能计算全部派生参数并通过校验
- [ ] 任意非法输入都能得到可读错误信息 - [x] 给定任意合法参数,能计算全部派生参数并通过校验
- [x] 任意非法输入都能得到可读错误信息
## 3. Step 2 - 地址生成与拆分 ## 3. Step 2 - 地址生成与拆分
### TODO ### TODO
- [ ] 新建 `AddressGenerator.cs`:生成 `[0, 2^vaBits - 1]` 范围虚拟地址 - [ ] 新建 `AddressGenerator.cs`:生成 `[0, 2^vaBits - 1]` 范围虚拟地址
- [ ] 新建 `AddressTranslatorUtils.cs`:拆分 `VPN + offset` - [ ] 新建 `AddressTranslatorUtils.cs`:拆分 `VPN + offset`
- [ ] 实现二级页表索引位拆分 - [ ] 实现二级页表索引位拆分
### 拆分规则 ### 拆分规则
- [ ] `vpnBits = vaBits - offsetBits` - [ ] `vpnBits = vaBits - offsetBits`
- [ ] `l1Bits = ceil(vpnBits / 2)` - [ ] `l1Bits = ceil(vpnBits / 2)`
- [ ] `l2Bits = floor(vpnBits / 2)` - [ ] `l2Bits = floor(vpnBits / 2)`
- [ ] 从 `VPN` 拆出 `L1Index``L2Index` - [ ] 从 `VPN` 拆出 `L1Index``L2Index`
### 完成标准 ### 完成标准
- [ ] 控制台可打印每次地址拆分结果 - [ ] 控制台可打印每次地址拆分结果
- [ ] 32/48/64 三种位数下拆分结果均正确 - [ ] 32/48/64 三种位数下拆分结果均正确
## 4. Step 3 - TLB全相联 + LRU ## 4. Step 3 - TLB全相联 + LRU
### TODO ### TODO
- [ ] 新建 `TlbEntry.cs``VPN -> PFN` 映射及有效位 - [ ] 新建 `TlbEntry.cs``VPN -> PFN` 映射及有效位
- [ ] 新建 `TlbCache.cs`:查询、更新、淘汰 - [ ] 新建 `TlbCache.cs`:查询、更新、淘汰
- [ ] 使用 `Dictionary + LinkedList` 实现 O(1) 级 LRU - [ ] 使用 `Dictionary + LinkedList` 实现 O(1) 级 LRU
### 行为要求 ### 行为要求
- [ ] `Lookup(vpn)`:命中返回 PFN未命中返回失败 - [ ] `Lookup(vpn)`:命中返回 PFN未命中返回失败
- [ ] `InsertOrUpdate(vpn, pfn)`:已存在则更新并置为最近使用 - [ ] `InsertOrUpdate(vpn, pfn)`:已存在则更新并置为最近使用
- [ ] 超出容量时淘汰最久未使用项 - [ ] 超出容量时淘汰最久未使用项
### 完成标准 ### 完成标准
- [ ] 构造用例可验证 LRU 淘汰顺序正确 - [ ] 构造用例可验证 LRU 淘汰顺序正确
- [ ] 连续访问时命中统计准确 - [ ] 连续访问时命中统计准确
## 5. Step 4 - 二级页表 + 缺页 + FIFO ## 5. Step 4 - 二级页表 + 缺页 + FIFO
### TODO ### TODO
- [ ] 新建 `TwoLevelPageTable.cs``Dictionary<L1, Dictionary<L2, PTE>>` - [ ] 新建 `TwoLevelPageTable.cs``Dictionary<L1, Dictionary<L2, PTE>>`
- [ ] 新建 `PageTableEntry.cs``PFN / present / dirty(可选)` - [ ] 新建 `PageTableEntry.cs``PFN / present / dirty(可选)`
- [ ] 新建 `PhysicalMemoryManager.cs`:空闲帧管理 + FIFO 队列 - [ ] 新建 `PhysicalMemoryManager.cs`:空闲帧管理 + FIFO 队列
@ -90,16 +109,20 @@
- [ ] 淘汰后同步页表项状态(`present = false` - [ ] 淘汰后同步页表项状态(`present = false`
### 行为要求 ### 行为要求
- [ ] 页表命中TLB 未命中后,页表中有 `present = true` 映射 - [ ] 页表命中TLB 未命中后,页表中有 `present = true` 映射
- [ ] 缺页:页表不存在或 `present = false` - [ ] 缺页:页表不存在或 `present = false`
- [ ] 装入页面后写回页表并更新 TLB - [ ] 装入页面后写回页表并更新 TLB
### 完成标准 ### 完成标准
- [ ] 能稳定运行大量访问(例如 `N=10000`)无崩溃 - [ ] 能稳定运行大量访问(例如 `N=10000`)无崩溃
- [ ] FIFO 次序可从日志中验证 - [ ] FIFO 次序可从日志中验证
## 6. Step 5 - 翻译引擎(单步状态机) ## 6. Step 5 - 翻译引擎(单步状态机)
### TODO ### TODO
- [ ] 新建 `TranslatorEngine.cs` - [ ] 新建 `TranslatorEngine.cs`
- [ ] 定义步骤枚举: - [ ] 定义步骤枚举:
- [ ] `GenerateVA` - [ ] `GenerateVA`
@ -112,26 +135,33 @@
- [ ] 提供 `StepOnce()``RunOneAccess()` 两种执行接口 - [ ] 提供 `StepOnce()``RunOneAccess()` 两种执行接口
### 完成标准 ### 完成标准
- [ ] 单步执行能暂停在每个阶段并暴露当前状态 - [ ] 单步执行能暂停在每个阶段并暴露当前状态
- [ ] 一次完整访问流程结果与预期一致 - [ ] 一次完整访问流程结果与预期一致
## 7. Step 6 - 统计模块 ## 7. Step 6 - 统计模块
### TODO ### TODO
- [ ] 新建 `StatsCollector.cs` - [ ] 新建 `StatsCollector.cs`
- [ ] 累计:`totalAccess`、`tlbHit`、`pageTableHit`、`pageFault` - [ ] 累计:`totalAccess`、`tlbHit`、`pageTableHit`、`pageFault`
- [ ] 计算:`tlbHitRate`、`pageTableHitRate`、`avgCost` - [ ] 计算:`tlbHitRate`、`pageTableHitRate`、`avgCost`
### 开销模型MVP ### 开销模型MVP
- [ ] TLB 命中:`+1` - [ ] TLB 命中:`+1`
- [ ] TLB miss + 页表命中:`+4` - [ ] TLB miss + 页表命中:`+4`
- [ ] 缺页:`+4 + pageFaultPenalty` - [ ] 缺页:`+4 + pageFaultPenalty`
### 完成标准 ### 完成标准
- [ ] 指标在单步和批量模式下都持续更新 - [ ] 指标在单步和批量模式下都持续更新
- [ ] 批量完成后给出最终汇总 - [ ] 批量完成后给出最终汇总
## 8. Step 7 - UI 绑定与流程动画 ## 8. Step 7 - UI 绑定与流程动画
### TODO ### TODO
- [ ] 新建主场景 `Main.unity` - [ ] 新建主场景 `Main.unity`
- [ ] 参数面板:位数/页大小/物理内存/TLB条目/N/开销参数 - [ ] 参数面板:位数/页大小/物理内存/TLB条目/N/开销参数
- [ ] 控制按钮:`单步`、`连续播放`、`暂停`、`重置` - [ ] 控制按钮:`单步`、`连续播放`、`暂停`、`重置`
@ -141,11 +171,14 @@
- [ ] 指标区:四项核心指标实时显示 - [ ] 指标区:四项核心指标实时显示
### 完成标准 ### 完成标准
- [ ] UI 上可完整观察一次访问的每个阶段 - [ ] UI 上可完整观察一次访问的每个阶段
- [ ] 连续播放期间 UI 不冻结,可暂停/恢复 - [ ] 连续播放期间 UI 不冻结,可暂停/恢复
## 9. Step 8 - 联调、测试、验收 ## 9. Step 8 - 联调、测试、验收
### 测试清单 ### 测试清单
- [ ] 参数边界:最小/最大可接受输入 - [ ] 参数边界:最小/最大可接受输入
- [ ] 位数覆盖32/48/64 各跑一轮 - [ ] 位数覆盖32/48/64 各跑一轮
- [ ] 页大小覆盖:多个 `2^n KB` - [ ] 页大小覆盖:多个 `2^n KB`
@ -155,12 +188,14 @@
- [ ] 物理内存极小场景:高缺页率验证 FIFO - [ ] 物理内存极小场景:高缺页率验证 FIFO
### 验收标准 ### 验收标准
- [ ] 功能覆盖需求冻结清单 - [ ] 功能覆盖需求冻结清单
- [ ] 四项指标数值可解释且趋势合理 - [ ] 四项指标数值可解释且趋势合理
- [ ] 单步、连续播放、重置均可稳定使用 - [ ] 单步、连续播放、重置均可稳定使用
- [ ] 控制台无持续异常错误 - [ ] 控制台无持续异常错误
## 10. 预计工时MVP ## 10. 预计工时MVP
- [ ] Step 1-24-6 小时 - [ ] Step 1-24-6 小时
- [ ] Step 3-46-10 小时 - [ ] Step 3-46-10 小时
- [ ] Step 5-63-5 小时 - [ ] Step 5-63-5 小时
@ -169,6 +204,7 @@
- [ ] 合计:约 21-33 小时(约 3-4.5 天) - [ ] 合计:约 21-33 小时(约 3-4.5 天)
## 11. 首日执行建议(直接开工) ## 11. 首日执行建议(直接开工)
- [ ] 先完成 Step 1参数模型 + 校验)
- [x] 先完成 Step 1参数模型 + 校验)
- [ ] 紧接 Step 2地址拆分 - [ ] 紧接 Step 2地址拆分
- [ ] 当天收尾前完成 Step 3TLB LRU 最小可运行) - [ ] 当天收尾前完成 Step 3TLB LRU 最小可运行)