From 7eab504f4a03c210e85174b0b7b677d2f6481ac4 Mon Sep 17 00:00:00 2001 From: SepComet <2428390463@qq.com> Date: Tue, 14 Apr 2026 00:40:16 +0800 Subject: [PATCH] =?UTF-8?q?Step=201=20-=20=E5=8F=82=E6=95=B0=E4=B8=8E?= =?UTF-8?q?=E6=A0=B8=E5=BF=83=E6=95=B0=E6=8D=AE=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- Assets/Scripts/Core.meta | 8 + Assets/Scripts/Core/AddressParts.cs | 28 ++++ Assets/Scripts/Core/AddressParts.cs.meta | 11 ++ Assets/Scripts/Core/ConfigValidator.cs | 141 ++++++++++++++++++ Assets/Scripts/Core/ConfigValidator.cs.meta | 11 ++ Assets/Scripts/Core/SimulationConfig.cs | 68 +++++++++ Assets/Scripts/Core/SimulationConfig.cs.meta | 11 ++ Assets/Scripts/Simulation.meta | 8 + Assets/Scripts/Simulation/SimulationState.cs | 28 ++++ .../Simulation/SimulationState.cs.meta | 11 ++ Assets/Scripts/UI.meta | 8 + Assets/Scripts/VMdemo.asmdef | 3 + Assets/Scripts/VMdemo.asmdef.meta | 7 + Assets/Tests.meta | 8 + Assets/Tests/EditMode.meta | 8 + Assets/Tests/EditMode/Step1CoreModelTests.cs | 137 +++++++++++++++++ .../EditMode/Step1CoreModelTests.cs.meta | 11 ++ .../EditMode/VMdemo.EditModeTests.asmdef | 25 ++++ .../EditMode/VMdemo.EditModeTests.asmdef.meta | 7 + doc/MVP-TODO.md | 72 ++++++--- 21 files changed, 595 insertions(+), 19 deletions(-) create mode 100644 Assets/Scripts/Core.meta create mode 100644 Assets/Scripts/Core/AddressParts.cs create mode 100644 Assets/Scripts/Core/AddressParts.cs.meta create mode 100644 Assets/Scripts/Core/ConfigValidator.cs create mode 100644 Assets/Scripts/Core/ConfigValidator.cs.meta create mode 100644 Assets/Scripts/Core/SimulationConfig.cs create mode 100644 Assets/Scripts/Core/SimulationConfig.cs.meta create mode 100644 Assets/Scripts/Simulation.meta create mode 100644 Assets/Scripts/Simulation/SimulationState.cs create mode 100644 Assets/Scripts/Simulation/SimulationState.cs.meta create mode 100644 Assets/Scripts/UI.meta create mode 100644 Assets/Scripts/VMdemo.asmdef create mode 100644 Assets/Scripts/VMdemo.asmdef.meta create mode 100644 Assets/Tests.meta create mode 100644 Assets/Tests/EditMode.meta create mode 100644 Assets/Tests/EditMode/Step1CoreModelTests.cs create mode 100644 Assets/Tests/EditMode/Step1CoreModelTests.cs.meta create mode 100644 Assets/Tests/EditMode/VMdemo.EditModeTests.asmdef create mode 100644 Assets/Tests/EditMode/VMdemo.EditModeTests.asmdef.meta diff --git a/.gitignore b/.gitignore index 218365d..bc3a0a9 100644 --- a/.gitignore +++ b/.gitignore @@ -87,4 +87,5 @@ crashlytics-build.properties ~$*.xlsx Assets/GameMain/Configs/ResourceBuilder.xml /.dotnet -/openspec/changes/archive \ No newline at end of file +/openspec/changes/archive +/.dotnet-home \ No newline at end of file diff --git a/Assets/Scripts/Core.meta b/Assets/Scripts/Core.meta new file mode 100644 index 0000000..a99f5a1 --- /dev/null +++ b/Assets/Scripts/Core.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d1f0f89f24ab6334f87c91090393ab8b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Core/AddressParts.cs b/Assets/Scripts/Core/AddressParts.cs new file mode 100644 index 0000000..2972ed3 --- /dev/null +++ b/Assets/Scripts/Core/AddressParts.cs @@ -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}"; + } + } +} diff --git a/Assets/Scripts/Core/AddressParts.cs.meta b/Assets/Scripts/Core/AddressParts.cs.meta new file mode 100644 index 0000000..2825c50 --- /dev/null +++ b/Assets/Scripts/Core/AddressParts.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2170a834280c39d40826bf87a50a20b4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Core/ConfigValidator.cs b/Assets/Scripts/Core/ConfigValidator.cs new file mode 100644 index 0000000..ed93abd --- /dev/null +++ b/Assets/Scripts/Core/ConfigValidator.cs @@ -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; + } + } +} diff --git a/Assets/Scripts/Core/ConfigValidator.cs.meta b/Assets/Scripts/Core/ConfigValidator.cs.meta new file mode 100644 index 0000000..713d973 --- /dev/null +++ b/Assets/Scripts/Core/ConfigValidator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 278803579202bf144ad186a403dbaed2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Core/SimulationConfig.cs b/Assets/Scripts/Core/SimulationConfig.cs new file mode 100644 index 0000000..9a37aef --- /dev/null +++ b/Assets/Scripts/Core/SimulationConfig.cs @@ -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; + } + } +} diff --git a/Assets/Scripts/Core/SimulationConfig.cs.meta b/Assets/Scripts/Core/SimulationConfig.cs.meta new file mode 100644 index 0000000..274fe26 --- /dev/null +++ b/Assets/Scripts/Core/SimulationConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 560da4f3bb13de24986a73b585c1ec1f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Simulation.meta b/Assets/Scripts/Simulation.meta new file mode 100644 index 0000000..5ff53f1 --- /dev/null +++ b/Assets/Scripts/Simulation.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c143559c7269dd940b8cb1b43b2d07a5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Simulation/SimulationState.cs b/Assets/Scripts/Simulation/SimulationState.cs new file mode 100644 index 0000000..7d0eb22 --- /dev/null +++ b/Assets/Scripts/Simulation/SimulationState.cs @@ -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; + } + } +} diff --git a/Assets/Scripts/Simulation/SimulationState.cs.meta b/Assets/Scripts/Simulation/SimulationState.cs.meta new file mode 100644 index 0000000..2eb436d --- /dev/null +++ b/Assets/Scripts/Simulation/SimulationState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5ab511454f082cd4987e3388c3eee16a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/UI.meta b/Assets/Scripts/UI.meta new file mode 100644 index 0000000..fb04b6d --- /dev/null +++ b/Assets/Scripts/UI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a14121217fe28e64f9ddd4fc9f89c32a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/VMdemo.asmdef b/Assets/Scripts/VMdemo.asmdef new file mode 100644 index 0000000..8bb1fa6 --- /dev/null +++ b/Assets/Scripts/VMdemo.asmdef @@ -0,0 +1,3 @@ +{ + "name": "VMdemo" +} diff --git a/Assets/Scripts/VMdemo.asmdef.meta b/Assets/Scripts/VMdemo.asmdef.meta new file mode 100644 index 0000000..3b0851c --- /dev/null +++ b/Assets/Scripts/VMdemo.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: baede126690de864e8b4ee808c6a2716 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests.meta b/Assets/Tests.meta new file mode 100644 index 0000000..c9d93b8 --- /dev/null +++ b/Assets/Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 34f7f3b70dfcb49478f58b5b5af22019 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/EditMode.meta b/Assets/Tests/EditMode.meta new file mode 100644 index 0000000..d59d1fa --- /dev/null +++ b/Assets/Tests/EditMode.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 760fef5586fd36f4f9004a24632450a7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/EditMode/Step1CoreModelTests.cs b/Assets/Tests/EditMode/Step1CoreModelTests.cs new file mode 100644 index 0000000..93caa89 --- /dev/null +++ b/Assets/Tests/EditMode/Step1CoreModelTests.cs @@ -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)); + } + } +} diff --git a/Assets/Tests/EditMode/Step1CoreModelTests.cs.meta b/Assets/Tests/EditMode/Step1CoreModelTests.cs.meta new file mode 100644 index 0000000..191ebfe --- /dev/null +++ b/Assets/Tests/EditMode/Step1CoreModelTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 52e57f0a987345340859c62fc16b1c90 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/EditMode/VMdemo.EditModeTests.asmdef b/Assets/Tests/EditMode/VMdemo.EditModeTests.asmdef new file mode 100644 index 0000000..2a77dd9 --- /dev/null +++ b/Assets/Tests/EditMode/VMdemo.EditModeTests.asmdef @@ -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 +} \ No newline at end of file diff --git a/Assets/Tests/EditMode/VMdemo.EditModeTests.asmdef.meta b/Assets/Tests/EditMode/VMdemo.EditModeTests.asmdef.meta new file mode 100644 index 0000000..546b206 --- /dev/null +++ b/Assets/Tests/EditMode/VMdemo.EditModeTests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e0e3ad2a355fe1d4e979eb4d2d33fc5a +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/doc/MVP-TODO.md b/doc/MVP-TODO.md index 4df4749..cc25df1 100644 --- a/doc/MVP-TODO.md +++ b/doc/MVP-TODO.md @@ -1,6 +1,9 @@ # MVP TODO - Unity 虚拟地址寻址模拟 +此为 MVP 实现,时间要求紧,不需要太考虑代码结构,需要跨脚本访问的内容全用单例也没问题 + ## 0. 需求冻结(已确认) + - [x] 模式:单次地址翻译可视化 + 批量访问统计 - [x] 系统位数:仅支持 `32 / 48 / 64` - [x] 页大小输入:仅支持 `2^n KB` @@ -15,6 +18,7 @@ - [x] 技术栈:Unity 2022 + C# ## 1. 总体分步计划 + 1. Step 1:参数与核心数据模型 2. Step 2:地址生成与拆分 3. Step 3:TLB(LRU)实现 @@ -25,64 +29,79 @@ 8. Step 8:联调、测试、验收 ## 2. Step 1 - 参数与核心数据模型 + ### TODO -- [ ] 创建脚本目录:`Assets/Scripts/Core`、`Assets/Scripts/Simulation`、`Assets/Scripts/UI` -- [ ] 新建 `SimulationConfig.cs`:定义可输入参数 -- [ ] 新建 `ConfigValidator.cs`:参数合法性校验 -- [ ] 新建 `AddressParts.cs`:保存 `VPN / offset / L1Index / L2Index` -- [ ] 新建 `SimulationState.cs`:保存当前轮次、当前虚拟地址、是否缺页等 + +- [x] 创建脚本目录:`Assets/Scripts/Core`、`Assets/Scripts/Simulation`、`Assets/Scripts/UI` +- [x] 新建 `SimulationConfig.cs`:定义可输入参数 +- [x] 新建 `ConfigValidator.cs`:参数合法性校验 +- [x] 新建 `AddressParts.cs`:保存 `VPN / offset / L1Index / L2Index` +- [x] 新建 `SimulationState.cs`:保存当前轮次、当前虚拟地址、是否缺页等 ### 输入字段(MVP) -- [ ] `vaBits`:`32|48|64` -- [ ] `pageSizeKB`:`2^n` -- [ ] `physicalMemoryMB`:正整数 -- [ ] `tlbEntries`:正整数,默认 `16` -- [ ] `accessCount`:正整数 -- [ ] `pageFaultPenalty`:默认 `100`(抽象开销单位) + +- [x] `vaBits`:`32|48|64` +- [x] `pageSizeKB`:`2^n` +- [x] `physicalMemoryMB`:正整数 +- [x] `tlbEntries`:正整数,默认 `16` +- [x] `accessCount`:正整数 +- [x] `pageFaultPenalty`:默认 `100`(抽象开销单位) ### 校验规则 -- [ ] `pageSizeKB` 必须是 2 的幂 -- [ ] `offsetBits = log2(pageSizeBytes)` 且 `offsetBits < vaBits` -- [ ] `frameCount = floor(physicalMemoryBytes / pageSizeBytes)` 且 `frameCount >= 1` -- [ ] 非法输入返回明确错误文本(用于 UI 提示) + +- [x] `pageSizeKB` 必须是 2 的幂 +- [x] `offsetBits = log2(pageSizeBytes)` 且 `offsetBits < vaBits` +- [x] `frameCount = floor(physicalMemoryBytes / pageSizeBytes)` 且 `frameCount >= 1` +- [x] 非法输入返回明确错误文本(用于 UI 提示) ### 完成标准 -- [ ] 给定任意合法参数,能计算全部派生参数并通过校验 -- [ ] 任意非法输入都能得到可读错误信息 + +- [x] 给定任意合法参数,能计算全部派生参数并通过校验 +- [x] 任意非法输入都能得到可读错误信息 ## 3. Step 2 - 地址生成与拆分 + ### TODO + - [ ] 新建 `AddressGenerator.cs`:生成 `[0, 2^vaBits - 1]` 范围虚拟地址 - [ ] 新建 `AddressTranslatorUtils.cs`:拆分 `VPN + offset` - [ ] 实现二级页表索引位拆分 ### 拆分规则 + - [ ] `vpnBits = vaBits - offsetBits` - [ ] `l1Bits = ceil(vpnBits / 2)` - [ ] `l2Bits = floor(vpnBits / 2)` - [ ] 从 `VPN` 拆出 `L1Index` 和 `L2Index` ### 完成标准 + - [ ] 控制台可打印每次地址拆分结果 - [ ] 32/48/64 三种位数下拆分结果均正确 ## 4. Step 3 - TLB(全相联 + LRU) + ### TODO + - [ ] 新建 `TlbEntry.cs`:`VPN -> PFN` 映射及有效位 - [ ] 新建 `TlbCache.cs`:查询、更新、淘汰 - [ ] 使用 `Dictionary + LinkedList` 实现 O(1) 级 LRU ### 行为要求 + - [ ] `Lookup(vpn)`:命中返回 PFN,未命中返回失败 - [ ] `InsertOrUpdate(vpn, pfn)`:已存在则更新并置为最近使用 - [ ] 超出容量时淘汰最久未使用项 ### 完成标准 + - [ ] 构造用例可验证 LRU 淘汰顺序正确 - [ ] 连续访问时命中统计准确 ## 5. Step 4 - 二级页表 + 缺页 + FIFO + ### TODO + - [ ] 新建 `TwoLevelPageTable.cs`:`Dictionary>` - [ ] 新建 `PageTableEntry.cs`:`PFN / present / dirty(可选)` - [ ] 新建 `PhysicalMemoryManager.cs`:空闲帧管理 + FIFO 队列 @@ -90,16 +109,20 @@ - [ ] 淘汰后同步页表项状态(`present = false`) ### 行为要求 + - [ ] 页表命中:TLB 未命中后,页表中有 `present = true` 映射 - [ ] 缺页:页表不存在或 `present = false` - [ ] 装入页面后写回页表并更新 TLB ### 完成标准 + - [ ] 能稳定运行大量访问(例如 `N=10000`)无崩溃 - [ ] FIFO 次序可从日志中验证 ## 6. Step 5 - 翻译引擎(单步状态机) + ### TODO + - [ ] 新建 `TranslatorEngine.cs` - [ ] 定义步骤枚举: - [ ] `GenerateVA` @@ -112,26 +135,33 @@ - [ ] 提供 `StepOnce()` 与 `RunOneAccess()` 两种执行接口 ### 完成标准 + - [ ] 单步执行能暂停在每个阶段并暴露当前状态 - [ ] 一次完整访问流程结果与预期一致 ## 7. Step 6 - 统计模块 + ### TODO + - [ ] 新建 `StatsCollector.cs` - [ ] 累计:`totalAccess`、`tlbHit`、`pageTableHit`、`pageFault` - [ ] 计算:`tlbHitRate`、`pageTableHitRate`、`avgCost` ### 开销模型(MVP) + - [ ] TLB 命中:`+1` - [ ] TLB miss + 页表命中:`+4` - [ ] 缺页:`+4 + pageFaultPenalty` ### 完成标准 + - [ ] 指标在单步和批量模式下都持续更新 - [ ] 批量完成后给出最终汇总 ## 8. Step 7 - UI 绑定与流程动画 + ### TODO + - [ ] 新建主场景 `Main.unity` - [ ] 参数面板:位数/页大小/物理内存/TLB条目/N/开销参数 - [ ] 控制按钮:`单步`、`连续播放`、`暂停`、`重置` @@ -141,11 +171,14 @@ - [ ] 指标区:四项核心指标实时显示 ### 完成标准 + - [ ] UI 上可完整观察一次访问的每个阶段 - [ ] 连续播放期间 UI 不冻结,可暂停/恢复 ## 9. Step 8 - 联调、测试、验收 + ### 测试清单 + - [ ] 参数边界:最小/最大可接受输入 - [ ] 位数覆盖:32/48/64 各跑一轮 - [ ] 页大小覆盖:多个 `2^n KB` 值 @@ -155,12 +188,14 @@ - [ ] 物理内存极小场景:高缺页率验证 FIFO ### 验收标准 + - [ ] 功能覆盖需求冻结清单 - [ ] 四项指标数值可解释且趋势合理 - [ ] 单步、连续播放、重置均可稳定使用 - [ ] 控制台无持续异常错误 ## 10. 预计工时(MVP) + - [ ] Step 1-2:4-6 小时 - [ ] Step 3-4:6-10 小时 - [ ] Step 5-6:3-5 小时 @@ -169,6 +204,7 @@ - [ ] 合计:约 21-33 小时(约 3-4.5 天) ## 11. 首日执行建议(直接开工) -- [ ] 先完成 Step 1(参数模型 + 校验) + +- [x] 先完成 Step 1(参数模型 + 校验) - [ ] 紧接 Step 2(地址拆分) - [ ] 当天收尾前完成 Step 3(TLB LRU 最小可运行)