|
- # E01-S05: link the memory-map skeleton against cfg/compiler.cfg.
- # build-map-skeleton.ps1 normal build, prints segment usage
- # build-map-skeleton.ps1 -Overflow ZP build that must fail in ld65
- [CmdletBinding()]
- param(
- [ValidateSet("", "ZP", "COMMON", "MAIN", "SWSTACK", "TABLES")]
- [string]$Overflow = ""
- )
-
- $ErrorActionPreference = "Stop"
- $project = Split-Path $PSScriptRoot -Parent
- $build = Join-Path $project "build"
- $config = Join-Path $project "cfg\compiler.cfg"
- $sources = @(
- (Join-Path $project "src\compiler\mmu.s"),
- (Join-Path $project "tests\platform\map-skeleton.s")
- )
- $suffix = if ($Overflow) { "-overflow-" + $Overflow.ToLowerInvariant() } else { "" }
- $stem = Join-Path $build ("map-skeleton" + $suffix)
- $program = "$stem.prg"
- $map = "$stem.map"
- $labels = "$stem.lbl"
-
- $ca65 = (Get-Command ca65 -ErrorAction Stop).Source
- $ld65 = (Get-Command ld65 -ErrorAction Stop).Source
- New-Item -ItemType Directory -Force -Path $build | Out-Null
- foreach ($path in @($program, $map, $labels)) {
- if (Test-Path -LiteralPath $path) { Remove-Item -LiteralPath $path -Force }
- }
-
- $objects = @()
- foreach ($source in $sources) {
- $object = Join-Path $build (([IO.Path]::GetFileNameWithoutExtension($source)) + $suffix + ".o")
- $defines = @()
- if ($Overflow) { $defines = @("-D", "OVERFLOW_$Overflow=1") }
- & $ca65 --cpu 6502 @defines -o $object $source
- if ($LASTEXITCODE -ne 0) { throw "ca65 failed on $source" }
- $objects += $object
- }
-
- $linkOutput = & $ld65 -C $config -o $program -m $map -Ln $labels @objects 2>&1
- $linkExit = $LASTEXITCODE
- if ($Overflow) {
- $text = ($linkOutput | Out-String)
- if ($linkExit -eq 0) { throw "OVERFLOW_$Overflow linked; capacity check failed to fire" }
- if ($text -notmatch "(?i)overflows memory area") {
- throw "OVERFLOW_$Overflow failed for an unexpected reason: $text"
- }
- $area = [regex]::Match($text, "(?i)overflows memory area\W+(\w+)").Groups[1].Value
- Write-Host ("overflow {0}: ld65 rejected it (memory area {1})" -f $Overflow, $area)
- return
- }
- if ($linkExit -ne 0) { throw ("ld65 failed:`n" + ($linkOutput | Out-String)) }
-
- # Segment usage from the map file: name, start, end, size.
- $segments = @{}
- $mapText = Get-Content -Raw -LiteralPath $map
- $block = [regex]::Match($mapText, "(?s)Segment list:.*?-{5,}\r?\n(.*?)\r?\n\r?\n").Groups[1].Value
- foreach ($line in ($block -split "\r?\n")) {
- $m = [regex]::Match($line, "^(\w+)\s+([0-9A-F]{6})\s+([0-9A-F]{6})\s+([0-9A-F]{6})")
- if ($m.Success) {
- $segments[$m.Groups[1].Value] = @{
- Start = [Convert]::ToInt32($m.Groups[2].Value, 16)
- End = [Convert]::ToInt32($m.Groups[3].Value, 16)
- Size = [Convert]::ToInt32($m.Groups[4].Value, 16)
- }
- }
- }
-
- # Contract checks that ld65 cannot express: fixed placements.
- function Require-Segment([string]$name, [int]$lo, [int]$hi) {
- if (-not $segments.ContainsKey($name)) { throw "segment $name missing from map" }
- $seg = $segments[$name]
- if ($seg.Size -gt 0 -and ($seg.Start -lt $lo -or $seg.End -gt $hi)) {
- throw ("segment {0} `${1:X4}-`${2:X4} outside `${3:X4}-`${4:X4}" -f
- $name, $seg.Start, $seg.End, $lo, $hi)
- }
- }
- Require-Segment "ZEROPAGE" 0x0002 0x00FF
- Require-Segment "COMMON" 0x1300 0x18FF
- Require-Segment "MAILBOX" 0x1BF0 0x1BFF
- Require-Segment "STARTUP" 0x1C0D 0xB7FF
- Require-Segment "CODE" 0x1C0D 0xB7FF
- if ($segments["STARTUP"].Start -ne 0x1C0D) { throw "STARTUP does not begin at `$1C0D" }
- $labelText = Get-Content -Raw -LiteralPath $labels
- if ($labelText -notmatch "(?im)^al\s+001C0D\s+\.skel_entry\s*$") {
- throw "skel_entry is not at `$1C0D (the SYS 7181 target)"
- }
- Require-Segment "SWSTACK" 0xB800 0xBFFF
- Require-Segment "TABLES" 0x2000 0xBFFF
- if ($segments["MAILBOX"].Start -ne 0x1BF0) { throw "mailbox is not at `$1BF0" }
-
- $bytes = [IO.File]::ReadAllBytes($program)
- if ($bytes[0] -ne 0x01 -or $bytes[1] -ne 0x1C) { throw "load address is not `$1C01" }
-
- Write-Host ("built {0} ({1} bytes)" -f $program, $bytes.Length)
- $limits = [ordered]@{
- ZEROPAGE = 0x00FE; COMMON = 0x0600; SWSTACK = 0x0800; TABLES = 0xA000
- }
- foreach ($name in ($segments.Keys | Sort-Object { $segments[$_].Start })) {
- $seg = $segments[$name]
- $limit = if ($limits.Contains($name)) { " of {0}" -f $limits[$name] } else { "" }
- Write-Host (" {0,-9} `${1:X4}-`${2:X4} {3,6} bytes{4}" -f
- $name, $seg.Start, $seg.End, $seg.Size, $limit)
- }
|