Step 2 - 地址生成与拆分

This commit is contained in:
SepComet 2026-04-14 00:56:38 +08:00
parent 7eab504f4a
commit 4ab93d7806
7 changed files with 266 additions and 10 deletions

View File

@ -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;
}
}
}

View File

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

View File

@ -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);
}
}
}

View File

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

View File

@ -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<System.ArgumentOutOfRangeException>(() =>
AddressTranslatorUtils.SplitVirtualAddress(0x1_0000_0000UL, 32, 12));
}
}
}

View File

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

View File

@ -63,21 +63,21 @@
### TODO ### TODO
- [ ] 新建 `AddressGenerator.cs`:生成 `[0, 2^vaBits - 1]` 范围虚拟地址 - [x] 新建 `AddressGenerator.cs`:生成 `[0, 2^vaBits - 1]` 范围虚拟地址
- [ ] 新建 `AddressTranslatorUtils.cs`:拆分 `VPN + offset` - [x] 新建 `AddressTranslatorUtils.cs`:拆分 `VPN + offset`
- [ ] 实现二级页表索引位拆分 - [x] 实现二级页表索引位拆分
### 拆分规则 ### 拆分规则
- [ ] `vpnBits = vaBits - offsetBits` - [x] `vpnBits = vaBits - offsetBits`
- [ ] `l1Bits = ceil(vpnBits / 2)` - [x] `l1Bits = ceil(vpnBits / 2)`
- [ ] `l2Bits = floor(vpnBits / 2)` - [x] `l2Bits = floor(vpnBits / 2)`
- [ ] 从 `VPN` 拆出 `L1Index``L2Index` - [x] 从 `VPN` 拆出 `L1Index``L2Index`
### 完成标准 ### 完成标准
- [ ] 控制台可打印每次地址拆分结果 - [x] 控制台可打印每次地址拆分结果
- [ ] 32/48/64 三种位数下拆分结果均正确 - [x] 32/48/64 三种位数下拆分结果均正确
## 4. Step 3 - TLB全相联 + LRU ## 4. Step 3 - TLB全相联 + LRU
@ -206,5 +206,5 @@
## 11. 首日执行建议(直接开工) ## 11. 首日执行建议(直接开工)
- [x] 先完成 Step 1参数模型 + 校验) - [x] 先完成 Step 1参数模型 + 校验)
- [ ] 紧接 Step 2地址拆分 - [x] 紧接 Step 2地址拆分
- [ ] 当天收尾前完成 Step 3TLB LRU 最小可运行) - [ ] 当天收尾前完成 Step 3TLB LRU 最小可运行)