diff --git a/Assets/Scripts/Core/AddressTranslatorUtils.cs b/Assets/Scripts/Core/AddressTranslatorUtils.cs new file mode 100644 index 0000000..68b17a2 --- /dev/null +++ b/Assets/Scripts/Core/AddressTranslatorUtils.cs @@ -0,0 +1,71 @@ +using System; + +namespace VMdemo.Core +{ + public static class AddressTranslatorUtils + { + public static AddressParts SplitVirtualAddress(ulong virtualAddress, int vaBits, int offsetBits) + { + if (vaBits <= 0 || vaBits > 64) + { + throw new ArgumentOutOfRangeException(nameof(vaBits), "vaBits 必须在 1 到 64 之间。"); + } + + if (offsetBits < 0 || offsetBits >= vaBits) + { + throw new ArgumentOutOfRangeException(nameof(offsetBits), "offsetBits 必须满足 0 <= offsetBits < vaBits。"); + } + + var vaMask = GetBitMask(vaBits); + if (vaBits < 64 && (virtualAddress & ~vaMask) != 0UL) + { + throw new ArgumentOutOfRangeException(nameof(virtualAddress), "virtualAddress 超出 vaBits 可表示范围。"); + } + + var vpnBits = vaBits - offsetBits; + GetPageTableBitLayout(vpnBits, out _, out var l2Bits); + + var offset = virtualAddress & GetBitMask(offsetBits); + var vpn = virtualAddress >> offsetBits; + var l2Index = vpn & GetBitMask(l2Bits); + var l1Index = vpn >> l2Bits; + + return new AddressParts(vpn, offset, l1Index, l2Index); + } + + public static void GetPageTableBitLayout(int vpnBits, out int l1Bits, out int l2Bits) + { + if (vpnBits <= 0) + { + throw new ArgumentOutOfRangeException(nameof(vpnBits), "vpnBits 必须是正整数。"); + } + + l1Bits = (vpnBits + 1) / 2; + l2Bits = vpnBits / 2; + } + + public static string FormatSplitResult( + ulong virtualAddress, + int vaBits, + int offsetBits, + AddressParts parts) + { + return $"VA={virtualAddress}, VPN={parts.Vpn}, Offset={parts.Offset}, L1={parts.L1Index}, L2={parts.L2Index}, vaBits={vaBits}, offsetBits={offsetBits}"; + } + + private static ulong GetBitMask(int bits) + { + if (bits <= 0) + { + return 0UL; + } + + if (bits >= 64) + { + return ulong.MaxValue; + } + + return (1UL << bits) - 1UL; + } + } +} diff --git a/Assets/Scripts/Core/AddressTranslatorUtils.cs.meta b/Assets/Scripts/Core/AddressTranslatorUtils.cs.meta new file mode 100644 index 0000000..5b7ef4f --- /dev/null +++ b/Assets/Scripts/Core/AddressTranslatorUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1b0967dfe5b0eb64993e8c207604d159 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Simulation/AddressGenerator.cs b/Assets/Scripts/Simulation/AddressGenerator.cs new file mode 100644 index 0000000..a66d387 --- /dev/null +++ b/Assets/Scripts/Simulation/AddressGenerator.cs @@ -0,0 +1,39 @@ +using System; + +namespace VMdemo.Simulation +{ + public class AddressGenerator + { + public const int DefaultSeed = 12345; + + private readonly int _vaBits; + private readonly ulong _vaMask; + private readonly Random _random; + private readonly byte[] _buffer = new byte[8]; + + public AddressGenerator(int vaBits, int? seed = null) + { + if (vaBits != 32 && vaBits != 48 && vaBits != 64) + { + throw new ArgumentOutOfRangeException(nameof(vaBits), "vaBits 仅支持 32、48、64。"); + } + + _vaBits = vaBits; + _vaMask = vaBits == 64 ? ulong.MaxValue : (1UL << vaBits) - 1UL; + _random = new Random(seed ?? DefaultSeed); + } + + public ulong MaxVirtualAddress => _vaMask; + + public ulong NextVirtualAddress() + { + return NextRawUlong() & _vaMask; + } + + private ulong NextRawUlong() + { + _random.NextBytes(_buffer); + return BitConverter.ToUInt64(_buffer, 0); + } + } +} diff --git a/Assets/Scripts/Simulation/AddressGenerator.cs.meta b/Assets/Scripts/Simulation/AddressGenerator.cs.meta new file mode 100644 index 0000000..2ae3048 --- /dev/null +++ b/Assets/Scripts/Simulation/AddressGenerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a3e8a9161af494c43a623da1945f4373 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/EditMode/Step2AddressSplitTests.cs b/Assets/Tests/EditMode/Step2AddressSplitTests.cs new file mode 100644 index 0000000..e545a72 --- /dev/null +++ b/Assets/Tests/EditMode/Step2AddressSplitTests.cs @@ -0,0 +1,113 @@ +using NUnit.Framework; +using VMdemo.Core; +using VMdemo.Simulation; + +namespace VMdemo.Tests.EditMode +{ + public class Step2AddressSplitTests + { + [TestCase(32)] + [TestCase(48)] + [TestCase(64)] + public void AddressGenerator_NextVirtualAddress_StaysWithinRange(int vaBits) + { + var generator = new AddressGenerator(vaBits, seed: 7); + + for (var i = 0; i < 1000; i++) + { + var va = generator.NextVirtualAddress(); + Assert.That(va, Is.LessThanOrEqualTo(generator.MaxVirtualAddress)); + } + } + + [Test] + public void AddressGenerator_SameSeed_GeneratesDeterministicSequence() + { + var a = new AddressGenerator(48, seed: 99); + var b = new AddressGenerator(48, seed: 99); + + for (var i = 0; i < 20; i++) + { + Assert.That(a.NextVirtualAddress(), Is.EqualTo(b.NextVirtualAddress())); + } + } + + [Test] + public void AddressGenerator_DifferentSeed_GeneratesDifferentSequence() + { + var a = new AddressGenerator(48, seed: 99); + var b = new AddressGenerator(48, seed: 100); + var hasDifference = false; + + for (var i = 0; i < 20; i++) + { + if (a.NextVirtualAddress() != b.NextVirtualAddress()) + { + hasDifference = true; + break; + } + } + + Assert.IsTrue(hasDifference); + } + + [TestCase(0x12345ABCUL, 32, 12, 0x12345UL, 0xABCUL, 0x48UL, 0x345UL)] + [TestCase(0x0000ABCDEF123456UL, 48, 12, 0xABCDEF123UL, 0x456UL, 0x2AF37UL, 0x2F123UL)] + [TestCase(0xFEDCBA9876543210UL, 64, 16, 0xFEDCBA987654UL, 0x3210UL, 0xFEDCBAUL, 0x987654UL)] + public void SplitVirtualAddress_SupportedVaBits_ReturnsExpectedParts( + ulong va, + int vaBits, + int offsetBits, + ulong expectedVpn, + ulong expectedOffset, + ulong expectedL1, + ulong expectedL2) + { + var parts = AddressTranslatorUtils.SplitVirtualAddress(va, vaBits, offsetBits); + TestContext.WriteLine(AddressTranslatorUtils.FormatSplitResult(va, vaBits, offsetBits, parts)); + + Assert.That(parts.Vpn, Is.EqualTo(expectedVpn)); + Assert.That(parts.Offset, Is.EqualTo(expectedOffset)); + Assert.That(parts.L1Index, Is.EqualTo(expectedL1)); + Assert.That(parts.L2Index, Is.EqualTo(expectedL2)); + } + + [Test] + public void SplitVirtualAddress_OddVpnBits_UsesCeilFloorBitSplit() + { + const int vaBits = 32; + const int offsetBits = 13; + const ulong va = 0xDEADBEEFUL; + + var parts = AddressTranslatorUtils.SplitVirtualAddress(va, vaBits, offsetBits); + AddressTranslatorUtils.GetPageTableBitLayout(vaBits - offsetBits, out var l1Bits, out var l2Bits); + + Assert.That(l1Bits, Is.EqualTo(10)); + Assert.That(l2Bits, Is.EqualTo(9)); + Assert.That(parts.Vpn, Is.EqualTo(0x6F56DUL)); + Assert.That(parts.Offset, Is.EqualTo(0x1EEFUL)); + Assert.That(parts.L1Index, Is.EqualTo(0x37AUL)); + Assert.That(parts.L2Index, Is.EqualTo(0x16DUL)); + } + + [Test] + public void GetPageTableBitLayout_AlwaysSumsToVpnBits() + { + for (var vpnBits = 1; vpnBits <= 63; vpnBits++) + { + AddressTranslatorUtils.GetPageTableBitLayout(vpnBits, out var l1Bits, out var l2Bits); + + Assert.That(l1Bits + l2Bits, Is.EqualTo(vpnBits)); + Assert.That(l1Bits, Is.GreaterThanOrEqualTo(l2Bits)); + Assert.That(l1Bits - l2Bits, Is.LessThanOrEqualTo(1)); + } + } + + [Test] + public void SplitVirtualAddress_AddressOutOfRange_Throws() + { + Assert.Throws(() => + AddressTranslatorUtils.SplitVirtualAddress(0x1_0000_0000UL, 32, 12)); + } + } +} diff --git a/Assets/Tests/EditMode/Step2AddressSplitTests.cs.meta b/Assets/Tests/EditMode/Step2AddressSplitTests.cs.meta new file mode 100644 index 0000000..07e2c6d --- /dev/null +++ b/Assets/Tests/EditMode/Step2AddressSplitTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 53e95a021ddfb5b48bbc5194223d8736 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/doc/MVP-TODO.md b/doc/MVP-TODO.md index cc25df1..d00abb2 100644 --- a/doc/MVP-TODO.md +++ b/doc/MVP-TODO.md @@ -63,21 +63,21 @@ ### TODO -- [ ] 新建 `AddressGenerator.cs`:生成 `[0, 2^vaBits - 1]` 范围虚拟地址 -- [ ] 新建 `AddressTranslatorUtils.cs`:拆分 `VPN + offset` -- [ ] 实现二级页表索引位拆分 +- [x] 新建 `AddressGenerator.cs`:生成 `[0, 2^vaBits - 1]` 范围虚拟地址 +- [x] 新建 `AddressTranslatorUtils.cs`:拆分 `VPN + offset` +- [x] 实现二级页表索引位拆分 ### 拆分规则 -- [ ] `vpnBits = vaBits - offsetBits` -- [ ] `l1Bits = ceil(vpnBits / 2)` -- [ ] `l2Bits = floor(vpnBits / 2)` -- [ ] 从 `VPN` 拆出 `L1Index` 和 `L2Index` +- [x] `vpnBits = vaBits - offsetBits` +- [x] `l1Bits = ceil(vpnBits / 2)` +- [x] `l2Bits = floor(vpnBits / 2)` +- [x] 从 `VPN` 拆出 `L1Index` 和 `L2Index` ### 完成标准 -- [ ] 控制台可打印每次地址拆分结果 -- [ ] 32/48/64 三种位数下拆分结果均正确 +- [x] 控制台可打印每次地址拆分结果 +- [x] 32/48/64 三种位数下拆分结果均正确 ## 4. Step 3 - TLB(全相联 + LRU) @@ -206,5 +206,5 @@ ## 11. 首日执行建议(直接开工) - [x] 先完成 Step 1(参数模型 + 校验) -- [ ] 紧接 Step 2(地址拆分) +- [x] 紧接 Step 2(地址拆分) - [ ] 当天收尾前完成 Step 3(TLB LRU 最小可运行)