# E01-S02: execute the MMU probe and verify its machine-readable result block. [CmdletBinding()] param([int]$RemotePort = 6511) $ErrorActionPreference = "Stop" $project = Split-Path $PSScriptRoot -Parent $build = Join-Path $project "build" $program = Join-Path $build "mmu-probe.prg" $labelsPath = Join-Path $build "mmu-probe.lbl" $traceCommands = Join-Path $project "monitor\mmu-probe-trace.txt" $monitorLog = Join-Path $build "mmu-probe-monitor.log" $x128 = "C:\Program Files\GTK3VICE-3.10-win64\bin\x128.exe" & (Join-Path $PSScriptRoot "build-mmu-probe.ps1") if ($LASTEXITCODE -ne 0) { throw "MMU probe build failed" } if (Test-Path -LiteralPath $monitorLog) { Remove-Item -LiteralPath $monitorLog -Force } $labelText = Get-Content -Raw -LiteralPath $labelsPath function Get-LabelAddress([string]$name) { $match = [regex]::Match($labelText, "(?im)^al\s+([0-9a-f]+)\s+\." + [regex]::Escape($name) + "\s*$") if (-not $match.Success) { throw "missing label $name" } return [Convert]::ToInt32($match.Groups[1].Value, 16) } $resultAddress = Get-LabelAddress "mmu_probe_result" $completeAddress = Get-LabelAddress "mmu_probe_complete" $bank0Address = Get-LabelAddress "mmu_probe_observed_bank0" $bank1Address = Get-LabelAddress "mmu_probe_observed_bank1" $p0Address = Get-LabelAddress "mmu_probe_observed_p0" $p1Address = Get-LabelAddress "mmu_probe_observed_p1" $p0cBank0Address = Get-LabelAddress "mmu_probe_obs_p0c_b0" $p1cBank0Address = Get-LabelAddress "mmu_probe_obs_p1c_b0" $stateEndAddress = Get-LabelAddress "mmu_probe_common_rcr" $entryAddress = Get-LabelAddress "mmu_probe_entry" $doneAddress = Get-LabelAddress "mmu_probe_done" $codeEndAddress = Get-LabelAddress "mmu_probe_code_end" # The checked-in trace file has the fixed entry point; the done address moves # with the code, so append it from the fresh label file. $generatedTrace = Join-Path $build "mmu-probe-trace.gen.txt" $traceText = (Get-Content -Raw -LiteralPath $traceCommands) + ("trace exec `${0:X4}`ncommand 4 `"disable 2`"`n" -f $doneAddress) [IO.File]::WriteAllText($generatedTrace, $traceText, [Text.Encoding]::ASCII) $traceCommands = $generatedTrace $arguments = @( "-autostart", ('"' + $program + '"'), "-autostart-warp", "-warp", "-sounddev", "dummy", "-moncommands", ('"' + $traceCommands + '"'), "-monlog", "-monlogname", ('"' + $monitorLog + '"'), "-remotemonitor", "-remotemonitoraddress", ("ip4://127.0.0.1:{0}" -f $RemotePort) ) -join ' ' $psi = [Diagnostics.ProcessStartInfo]::new() $psi.FileName = $x128 $psi.Arguments = $arguments $psi.UseShellExecute = $true $psi.WindowStyle = [Diagnostics.ProcessWindowStyle]::Hidden $process = [Diagnostics.Process]::Start($psi) $client = $null try { $deadline = [DateTime]::UtcNow.AddSeconds(10) while ([DateTime]::UtcNow -lt $deadline) { if ($process.HasExited) { throw "x128 exited before remote verification" } try { $client = [Net.Sockets.TcpClient]::new() $client.Connect("127.0.0.1", $RemotePort) break } catch { if ($client) { $client.Dispose(); $client = $null } Start-Sleep -Milliseconds 200 } } if (-not $client -or -not $client.Connected) { throw "could not connect to VICE remote monitor" } Start-Sleep -Seconds 3 $stream = $client.GetStream() $stream.ReadTimeout = 2000 $writer = [IO.StreamWriter]::new($stream, [Text.Encoding]::ASCII, 1024, $true) $writer.NewLine = "`n" $writer.AutoFlush = $true $writer.WriteLine(("m `${0:X4} `${1:X4}" -f $resultAddress, $stateEndAddress)) Start-Sleep -Milliseconds 400 $buffer = New-Object byte[] 16384 $response = "" while ($stream.DataAvailable) { $count = $stream.Read($buffer, 0, $buffer.Length) if ($count -le 0) { break } $response += [Text.Encoding]::ASCII.GetString($buffer, 0, $count) Start-Sleep -Milliseconds 50 } $expected = @( @{ Address = $resultAddress; Value = 0xFF; Name = "result" }, @{ Address = $completeAddress; Value = 0xA5; Name = "complete" }, @{ Address = $bank0Address; Value = 0x11; Name = "bank0" }, @{ Address = $bank1Address; Value = 0x22; Name = "bank1" }, @{ Address = $p0Address; Value = 0x77; Name = "page0" }, @{ Address = $p1Address; Value = 0x33; Name = "page1" }, @{ Address = $p0cBank0Address; Value = 0x66; Name = "page0-common-bank0" }, @{ Address = $p1cBank0Address; Value = 0x5A; Name = "page1-common-bank0" } ) # Parse every monitor dump row into address -> byte so checks do not # depend on where a 16-byte row happens to start. $memory = @{} $rows = [regex]::Matches($response, "(?i)C:([0-9a-f]{4})((?:[ ]{1,2}[0-9a-f]{2}){1,16})") foreach ($row in $rows) { $address = [Convert]::ToInt32($row.Groups[1].Value, 16) foreach ($hex in ($row.Groups[2].Value.Trim() -split '\s+')) { $memory[$address] = [Convert]::ToInt32($hex, 16) $address++ } } foreach ($item in $expected) { if (-not $memory.ContainsKey($item.Address) -or $memory[$item.Address] -ne $item.Value) { throw ("{0} at `${1:X4} was not `${2:X2}; monitor response: {3}" -f $item.Name, $item.Address, $item.Value, $response) } } $writer.WriteLine("q") $writer.Dispose() $client.Dispose() $client = $null if (-not $process.WaitForExit(5000)) { throw "x128 did not exit after monitor quit" } if (-not (Test-Path -LiteralPath $monitorLog)) { throw "monitor log not created" } # Trace hits go to the log before the remote client connects and to the # remote stream afterwards; examine both. $trace = (Get-Content -Raw -LiteralPath $monitorLog) + "`n" + $response [IO.File]::WriteAllText((Join-Path $build "mmu-probe-trace-dump.txt"), $trace) # Every hit is "#n (Trace ) ..." followed by ".C: ...". $hits = [regex]::Matches($trace, "(?im)^#\d+ \(Trace\s+(exec|store) ([0-9a-f]{4})\)[^\r\n]*\r?\n\.C:([0-9a-f]{4})") $inWindow = $false $windowClosed = $false $storeCount = 0 $written = @{} foreach ($hit in $hits) { $kind = $hit.Groups[1].Value.ToLowerInvariant() $target = [Convert]::ToInt32($hit.Groups[2].Value, 16) $pc = [Convert]::ToInt32($hit.Groups[3].Value, 16) if ($kind -eq 'exec') { if ($target -eq $entryAddress -and -not $windowClosed) { $inWindow = $true } elseif ($target -eq $doneAddress -and $inWindow) { $inWindow = $false; $windowClosed = $true; break } continue } if (-not $inWindow) { continue } if ($pc -lt $entryAddress -or $pc -ge $codeEndAddress) { throw ("MMU store to `${0:X4} from `${1:X4}, outside probe code" -f $target, $pc) } $written[$target] = $true $storeCount++ } if (-not $windowClosed) { throw "trace has no complete entry..done probe window" } foreach ($register in @(0xFF00, 0xD506, 0xD507, 0xD508, 0xD509, 0xD50A)) { if (-not $written.ContainsKey($register)) { throw ("probe window has no store to `${0:X4}" -f $register) } } foreach ($register in @(0xD500, 0xD501, 0xD502, 0xD503, 0xD504, 0xD505, 0xD50B)) { if ($written.ContainsKey($register)) { throw ("probe wrote unintended MMU register `${0:X4}" -f $register) } } Write-Host ("verified {0} MMU stores in the probe window, all from probe code" -f $storeCount) Write-Host ("verified MMU result `$FF, completion `$A5, banks `$11/`$22, " + "pages `$77/`$33, common-priority bank0 `$66/`$5A") Write-Host ("monitor log: {0}" -f $monitorLog) } finally { if ($client) { $client.Dispose() } if ($process -and -not $process.HasExited) { $process.Kill() $process.WaitForExit() } }