Step 1 - 参数与核心数据模型
This commit is contained in:
parent
fa7b04a880
commit
7eab504f4a
|
|
@ -87,4 +87,5 @@ crashlytics-build.properties
|
|||
~$*.xlsx
|
||||
Assets/GameMain/Configs/ResourceBuilder.xml
|
||||
/.dotnet
|
||||
/openspec/changes/archive
|
||||
/openspec/changes/archive
|
||||
/.dotnet-home
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d1f0f89f24ab6334f87c91090393ab8b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2170a834280c39d40826bf87a50a20b4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 278803579202bf144ad186a403dbaed2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 560da4f3bb13de24986a73b585c1ec1f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c143559c7269dd940b8cb1b43b2d07a5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5ab511454f082cd4987e3388c3eee16a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a14121217fe28e64f9ddd4fc9f89c32a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"name": "VMdemo"
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: baede126690de864e8b4ee808c6a2716
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 34f7f3b70dfcb49478f58b5b5af22019
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 760fef5586fd36f4f9004a24632450a7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 52e57f0a987345340859c62fc16b1c90
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e0e3ad2a355fe1d4e979eb4d2d33fc5a
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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<L1, Dictionary<L2, PTE>>`
|
||||
- [ ] 新建 `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 最小可运行)
|
||||
|
|
|
|||
Reference in New Issue