mirror of
https://github.com/fadden/6502bench.git
synced 2025-04-02 11:29:45 +00:00
Fix 64tass output for non-loadable files
64tass wants to place its output into a 64KB region of memory, starting at the address "*" is set to, and continuing without wrapping around the end of the bank. Some files aren't meant to be handled that way, so we need to generate the output differently. If the file's output fits nicely, it's considered "loadable", and is generated in the usual way. If it doesn't, it's treated as "streamable", and the initial "* = addr" directive is omitted (leaving "*" at zero), and we go straight to ".logical" directives. 65816 code with an initial address outside bank 0 is treated as "streamable" whether or not the contents fit nicely in the designated 64K area. This caused a minor change to a few of the 65816 tests. A new test, 20240-large-overlay, exercises "streamable" by creating a file with eight overlapping 8KB segments that load at $8000. While the file as a whole fits in 64KB, it wouldn't if loaded at the desired start address. Also, updated the regression test harness to report assembler failure independently of overall test failure. This makes it easier to confirm that (say) ACME v0.96.4 still works with the code we generate, even though it doesn't match the expected output (which was generated for v0.97). (problem was raised in issue #98)
This commit is contained in:
parent
752fa06ef5
commit
8db554c1cd
@ -112,6 +112,14 @@ namespace SourceGen.AsmGen {
|
||||
/// </summary>
|
||||
private CharEncoding.Encoding mCurrentEncoding;
|
||||
|
||||
/// <summary>
|
||||
/// Output mode; determines how ORG is handled.
|
||||
/// </summary>
|
||||
private enum TassOutputMode {
|
||||
Unknown = 0, Loadable = 1, Streamable = 2
|
||||
}
|
||||
private TassOutputMode mOutputMode;
|
||||
|
||||
/// <summary>
|
||||
/// Holds detected version of configured assembler.
|
||||
/// </summary>
|
||||
@ -120,7 +128,6 @@ namespace SourceGen.AsmGen {
|
||||
// Version we're coded against.
|
||||
private static CommonUtil.Version V1_53 = new CommonUtil.Version(1, 53, 1515);
|
||||
|
||||
|
||||
// Pseudo-op string constants.
|
||||
private static PseudoOp.PseudoOpNames sDataOpNames =
|
||||
new PseudoOp.PseudoOpNames(new Dictionary<string, string> {
|
||||
@ -189,7 +196,37 @@ namespace SourceGen.AsmGen {
|
||||
AssemblerInfo.Id.Tass64);
|
||||
mColumnWidths = (int[])config.ColumnWidths.Clone();
|
||||
|
||||
mHasPrgHeader = GenCommon.HasPrgHeader(project);
|
||||
// 64tass emulates a loader on a 64K system. The address you specify with
|
||||
// "* = <addr>" tells the loader where the code lives. If the project runs off the
|
||||
// end of memory, you get a warning message and an output file that has the last
|
||||
// part as the first part, because the loader wraps around.
|
||||
//
|
||||
// If (start_addr + total_len) doesn't fit without wrapping, we want to start
|
||||
// the code with "* = 0" (or omit it entirely) and use ".logical" for the first.
|
||||
// chunk. This allows us to generate the full 64K. Note that 65816 code that
|
||||
// starts outside bank 0 will always fail this test.
|
||||
//
|
||||
// Thus there are two modes: "loadable" and "streamable". We could output everything
|
||||
// as streamable but that's kind of ugly and prevents the PRG optimization.
|
||||
//
|
||||
// If the file has more than 64K of data in it, we need to add "--long-address" to
|
||||
// the command-line arguments.
|
||||
|
||||
// Get start address. If this is a PRG file, the start address is the address
|
||||
// of offset +000002.
|
||||
bool hasPrgHeader = GenCommon.HasPrgHeader(project);
|
||||
int offAdj = hasPrgHeader ? 2 : 0;
|
||||
int startAddr = project.AddrMap.Get(offAdj);
|
||||
if (startAddr + project.FileDataLength - offAdj > 65536) {
|
||||
// Does not fit into memory at load address.
|
||||
mOutputMode = TassOutputMode.Streamable;
|
||||
mHasPrgHeader = false;
|
||||
} else {
|
||||
mOutputMode = TassOutputMode.Loadable;
|
||||
mHasPrgHeader = hasPrgHeader;
|
||||
}
|
||||
//Debug.WriteLine("startAddr=$" + startAddr.ToString("x6") +
|
||||
// " outputMode=" + mOutputMode + " hasPrg=" + mHasPrgHeader);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -252,8 +289,9 @@ namespace SourceGen.AsmGen {
|
||||
Localizer.QuirkNoOpcodeMnemonics = true;
|
||||
Localizer.Analyze();
|
||||
|
||||
bool needLongAddress = Project.FileDataLength > 65536 + (mHasPrgHeader ? 2 : 0);
|
||||
string extraOptions = string.Empty +
|
||||
(Project.FileDataLength > 65536 ? AsmTass64.LONG_ADDRESS : string.Empty) +
|
||||
(needLongAddress ? AsmTass64.LONG_ADDRESS : string.Empty) +
|
||||
(mHasPrgHeader ? string.Empty : AsmTass64.NOSTART);
|
||||
|
||||
// Use UTF-8 encoding, without a byte-order mark.
|
||||
@ -612,14 +650,18 @@ namespace SourceGen.AsmGen {
|
||||
// 64tass separates the "compile offset", which determines where the output fits
|
||||
// into the generated binary, and "program counter", which determines the code
|
||||
// the assembler generates. Since we need to explicitly specify every byte in
|
||||
// the output file, the compile offset isn't very useful. We want to set it once
|
||||
// before the first line of code, then leave it alone.
|
||||
// the output file, having a distinct compile offset isn't very useful. We want
|
||||
// to set it once before the first line of code, then leave it alone.
|
||||
//
|
||||
// Any subsequent ORG changes are made to the program counter, and take the form
|
||||
// of a pair of ops (.logical <addr> to open, .here to end). Omitting the .here
|
||||
// causes an error.
|
||||
//
|
||||
// If this is a "streamable" file, meaning it won't actually load into 64K of RAM
|
||||
// without wrapping around, then we skip the "* = addr" (same as "* = 0") and just
|
||||
// start with ".logical" segments.
|
||||
Debug.Assert(offset >= StartOffset);
|
||||
if (offset == StartOffset) {
|
||||
if (offset == StartOffset && mOutputMode == TassOutputMode.Loadable) {
|
||||
// Set the "compile offset" to the initial address.
|
||||
OutputLine("*", "=",
|
||||
SourceFormatter.FormatHexValue(Project.AddrMap.Get(StartOffset), 4),
|
||||
|
@ -507,7 +507,9 @@ namespace SourceGen.AsmGen {
|
||||
/// <param name="project">Project to check.</param>
|
||||
/// <returns>True if we think we found a PRG header.</returns>
|
||||
public static bool HasPrgHeader(DisasmProject project) {
|
||||
if (project.FileDataLength < 3 || project.FileDataLength > 65536) {
|
||||
if (project.FileDataLength < 3 || project.FileDataLength > 65536+2) {
|
||||
// Must fit in 64KB of memory. A 65538-byte file will work if the
|
||||
// first two bytes are the PRG header (and it starts at address zero).
|
||||
//Debug.WriteLine("PRG test: incompatible file length");
|
||||
return false;
|
||||
}
|
||||
@ -539,6 +541,8 @@ namespace SourceGen.AsmGen {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO? confirm project fits in 64K of memory
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
BIN
SourceGen/SGTestData/20240-large-overlay
Normal file
BIN
SourceGen/SGTestData/20240-large-overlay
Normal file
Binary file not shown.
88
SourceGen/SGTestData/20240-large-overlay.dis65
Normal file
88
SourceGen/SGTestData/20240-large-overlay.dis65
Normal file
@ -0,0 +1,88 @@
|
||||
### 6502bench SourceGen dis65 v1.0 ###
|
||||
{
|
||||
"_ContentVersion":4,
|
||||
"FileDataLength":65536,
|
||||
"FileDataCrc32":239105661,
|
||||
"ProjectProps":{
|
||||
"CpuName":"6502",
|
||||
"IncludeUndocumentedInstr":false,
|
||||
"TwoByteBrk":false,
|
||||
"EntryFlags":32702671,
|
||||
"AutoLabelStyle":"Simple",
|
||||
"AnalysisParams":{
|
||||
"AnalyzeUncategorizedData":true,
|
||||
"DefaultTextScanMode":"LowHighAscii",
|
||||
"MinCharsForString":4,
|
||||
"SeekNearbyTargets":true,
|
||||
"UseRelocData":false,
|
||||
"SmartPlpHandling":false,
|
||||
"SmartPlbHandling":true},
|
||||
|
||||
"PlatformSymbolFileIdentifiers":[],
|
||||
"ExtensionScriptFileIdentifiers":[],
|
||||
"ProjectSyms":{
|
||||
}},
|
||||
|
||||
"AddressMap":[{
|
||||
"Offset":0,
|
||||
"Addr":32768},
|
||||
|
||||
{
|
||||
"Offset":8192,
|
||||
"Addr":32768},
|
||||
|
||||
{
|
||||
"Offset":16384,
|
||||
"Addr":32768},
|
||||
|
||||
{
|
||||
"Offset":24576,
|
||||
"Addr":32768},
|
||||
|
||||
{
|
||||
"Offset":32768,
|
||||
"Addr":32768},
|
||||
|
||||
{
|
||||
"Offset":40960,
|
||||
"Addr":32768},
|
||||
|
||||
{
|
||||
"Offset":49152,
|
||||
"Addr":32768},
|
||||
|
||||
{
|
||||
"Offset":57344,
|
||||
"Addr":32768}],
|
||||
"TypeHints":[],
|
||||
"StatusFlagOverrides":{
|
||||
},
|
||||
|
||||
"Comments":{
|
||||
},
|
||||
|
||||
"LongComments":{
|
||||
},
|
||||
|
||||
"Notes":{
|
||||
},
|
||||
|
||||
"UserLabels":{
|
||||
},
|
||||
|
||||
"OperandFormats":{
|
||||
},
|
||||
|
||||
"LvTables":{
|
||||
},
|
||||
|
||||
"Visualizations":[],
|
||||
"VisualizationAnimations":[],
|
||||
"VisualizationSets":{
|
||||
},
|
||||
|
||||
"RelocList":{
|
||||
},
|
||||
|
||||
"DbrValues":{
|
||||
}}
|
@ -13,7 +13,7 @@ thirty2 = $12345678 ;32-bit constant test
|
||||
plataddr = $3000 ;address only in platform file
|
||||
projalsa = $3200 ;same val as projalso
|
||||
|
||||
* = $012345
|
||||
.logical $012345
|
||||
.as
|
||||
.xs
|
||||
start clc
|
||||
@ -127,6 +127,7 @@ _skipdata lda #(biggie >> 16)-1
|
||||
|
||||
_nextchunk jml _L1000_1
|
||||
|
||||
.here
|
||||
.logical $1000
|
||||
_L1000_1 nop
|
||||
_L1000 nop
|
||||
|
@ -1,5 +1,5 @@
|
||||
.cpu "65816"
|
||||
* = $021000
|
||||
.logical $021000
|
||||
.as
|
||||
.xs
|
||||
clc
|
||||
@ -9,6 +9,7 @@
|
||||
jsr L21107
|
||||
jmp L22000
|
||||
|
||||
.here
|
||||
.logical $021100
|
||||
L21100 bit L21100 & $ffff
|
||||
L21103 lda #$11
|
||||
|
@ -1,7 +1,7 @@
|
||||
.cpu "65816"
|
||||
.enc "sg_ascii"
|
||||
.cdef $20,$7e,$20
|
||||
* = $030000
|
||||
.logical $030000
|
||||
.al
|
||||
.xl
|
||||
L30000 clc
|
||||
@ -77,6 +77,7 @@ _L30088 .byte $03
|
||||
|
||||
_L3008E rts
|
||||
|
||||
.here
|
||||
.logical $04ffe0
|
||||
_L4FFE0 .long _L4FFE0
|
||||
.byte $00
|
||||
|
26
SourceGen/SGTestData/Expected/20240-large-overlay_64tass.S
Normal file
26
SourceGen/SGTestData/Expected/20240-large-overlay_64tass.S
Normal file
@ -0,0 +1,26 @@
|
||||
.cpu "6502"
|
||||
.logical $8000
|
||||
.byte $ea
|
||||
.fill 8191,$00
|
||||
.here
|
||||
.logical $8000
|
||||
.fill 8192,$01
|
||||
.here
|
||||
.logical $8000
|
||||
.fill 8192,$02
|
||||
.here
|
||||
.logical $8000
|
||||
.fill 8192,$03
|
||||
.here
|
||||
.logical $8000
|
||||
.fill 8192,$04
|
||||
.here
|
||||
.logical $8000
|
||||
.fill 8192,$05
|
||||
.here
|
||||
.logical $8000
|
||||
.fill 8192,$06
|
||||
.here
|
||||
.logical $8000
|
||||
.fill 8192,$07
|
||||
.here
|
27
SourceGen/SGTestData/Expected/20240-large-overlay_acme.S
Normal file
27
SourceGen/SGTestData/Expected/20240-large-overlay_acme.S
Normal file
@ -0,0 +1,27 @@
|
||||
!cpu 6502
|
||||
* = $0000
|
||||
!pseudopc $8000 {
|
||||
!byte $ea
|
||||
!fill 8191,$00
|
||||
} ;!pseudopc
|
||||
!pseudopc $8000 {
|
||||
!fill 8192,$01
|
||||
} ;!pseudopc
|
||||
!pseudopc $8000 {
|
||||
!fill 8192,$02
|
||||
} ;!pseudopc
|
||||
!pseudopc $8000 {
|
||||
!fill 8192,$03
|
||||
} ;!pseudopc
|
||||
!pseudopc $8000 {
|
||||
!fill 8192,$04
|
||||
} ;!pseudopc
|
||||
!pseudopc $8000 {
|
||||
!fill 8192,$05
|
||||
} ;!pseudopc
|
||||
!pseudopc $8000 {
|
||||
!fill 8192,$06
|
||||
} ;!pseudopc
|
||||
!pseudopc $8000 {
|
||||
!fill 8192,$07
|
||||
} ;!pseudopc
|
26
SourceGen/SGTestData/Expected/20240-large-overlay_cc65.S
Normal file
26
SourceGen/SGTestData/Expected/20240-large-overlay_cc65.S
Normal file
@ -0,0 +1,26 @@
|
||||
.setcpu "6502"
|
||||
; .segment "SEG000"
|
||||
.org $8000
|
||||
.byte $ea
|
||||
.res 8191,$00
|
||||
; .segment "SEG001"
|
||||
.org $8000
|
||||
.res 8192,$01
|
||||
; .segment "SEG002"
|
||||
.org $8000
|
||||
.res 8192,$02
|
||||
; .segment "SEG003"
|
||||
.org $8000
|
||||
.res 8192,$03
|
||||
; .segment "SEG004"
|
||||
.org $8000
|
||||
.res 8192,$04
|
||||
; .segment "SEG005"
|
||||
.org $8000
|
||||
.res 8192,$05
|
||||
; .segment "SEG006"
|
||||
.org $8000
|
||||
.res 8192,$06
|
||||
; .segment "SEG007"
|
||||
.org $8000
|
||||
.res 8192,$07
|
25
SourceGen/SGTestData/Expected/20240-large-overlay_cc65.cfg
Normal file
25
SourceGen/SGTestData/Expected/20240-large-overlay_cc65.cfg
Normal file
@ -0,0 +1,25 @@
|
||||
# 6502bench SourceGen generated linker script for 20240-large-overlay
|
||||
MEMORY {
|
||||
MAIN: file=%O, start=%S, size=65536;
|
||||
# MEM000: file=%O, start=$8000, size=8192;
|
||||
# MEM001: file=%O, start=$8000, size=8192;
|
||||
# MEM002: file=%O, start=$8000, size=8192;
|
||||
# MEM003: file=%O, start=$8000, size=8192;
|
||||
# MEM004: file=%O, start=$8000, size=8192;
|
||||
# MEM005: file=%O, start=$8000, size=8192;
|
||||
# MEM006: file=%O, start=$8000, size=8192;
|
||||
# MEM007: file=%O, start=$8000, size=8192;
|
||||
}
|
||||
SEGMENTS {
|
||||
CODE: load=MAIN, type=rw;
|
||||
# SEG000: load=MEM000, type=rw;
|
||||
# SEG001: load=MEM001, type=rw;
|
||||
# SEG002: load=MEM002, type=rw;
|
||||
# SEG003: load=MEM003, type=rw;
|
||||
# SEG004: load=MEM004, type=rw;
|
||||
# SEG005: load=MEM005, type=rw;
|
||||
# SEG006: load=MEM006, type=rw;
|
||||
# SEG007: load=MEM007, type=rw;
|
||||
}
|
||||
FEATURES {}
|
||||
SYMBOLS {}
|
17
SourceGen/SGTestData/Expected/20240-large-overlay_merlin32.S
Normal file
17
SourceGen/SGTestData/Expected/20240-large-overlay_merlin32.S
Normal file
@ -0,0 +1,17 @@
|
||||
org $8000
|
||||
dfb $ea
|
||||
ds 8191
|
||||
org $8000
|
||||
ds 8192,$01
|
||||
org $8000
|
||||
ds 8192,$02
|
||||
org $8000
|
||||
ds 8192,$03
|
||||
org $8000
|
||||
ds 8192,$04
|
||||
org $8000
|
||||
ds 8192,$05
|
||||
org $8000
|
||||
ds 8192,$06
|
||||
org $8000
|
||||
ds 8192,$07
|
18
SourceGen/SGTestData/Source/20240-large-overlay.S
Normal file
18
SourceGen/SGTestData/Source/20240-large-overlay.S
Normal file
@ -0,0 +1,18 @@
|
||||
; Copyright 2021 faddenSoft. All Rights Reserved.
|
||||
; See the LICENSE.txt file for distribution terms (Apache 2.0).
|
||||
;
|
||||
; Assembler: Merlin 32
|
||||
|
||||
org $0000
|
||||
|
||||
; EDIT: change each 8KB segment to start at $8000, so they overlap
|
||||
|
||||
nop ;work around Merlin 1.0 bug
|
||||
ds $1fff,$00
|
||||
ds $2000,$01
|
||||
ds $2000,$02
|
||||
ds $2000,$03
|
||||
ds $2000,$04
|
||||
ds $2000,$05
|
||||
ds $2000,$06
|
||||
ds $2000,$07
|
@ -134,10 +134,14 @@ namespace SourceGen.Tests {
|
||||
DateTime startWhen = DateTime.Now;
|
||||
|
||||
int successCount = 0;
|
||||
int asmFailCount = 0;
|
||||
foreach (string pathName in testCases) {
|
||||
if (GenerateAndAssemble(pathName)) {
|
||||
if (GenerateAndAssemble(pathName, out bool someAsmFailed)) {
|
||||
successCount++;
|
||||
}
|
||||
if (someAsmFailed) {
|
||||
asmFailCount++;
|
||||
}
|
||||
|
||||
if (worker.CancellationPending) {
|
||||
ReportProgress("\r\nCancelled.\r\n", Colors.Red);
|
||||
@ -152,7 +156,9 @@ namespace SourceGen.Tests {
|
||||
" tests passed in {0:N3} sec\r\n",
|
||||
(endWhen - startWhen).TotalSeconds), Colors.Green);
|
||||
} else {
|
||||
ReportProgress(successCount + " of " + testCases.Count + " tests passed\r\n");
|
||||
ReportProgress(successCount + " of " + testCases.Count + " tests passed\r\n",
|
||||
Colors.OrangeRed);
|
||||
ReportProgress(asmFailCount + " tests reported assembler failures\r\n");
|
||||
}
|
||||
|
||||
PrintAsmVersions();
|
||||
@ -215,8 +221,14 @@ namespace SourceGen.Tests {
|
||||
/// does not count as a failure.
|
||||
/// </summary>
|
||||
/// <param name="pathName">Full path to test case.</param>
|
||||
/// <param name="someAsmFailed">Set to true if one or more assemblers reported an error,
|
||||
/// or the assembled binary did not match. Useful when running the tests against an
|
||||
/// older version of an assembler for which the generated code does not match the
|
||||
/// expected text, but we can still evaluate correctness.</param>
|
||||
/// <returns>True if all assemblers worked as expected.</returns>
|
||||
private bool GenerateAndAssemble(string pathName) {
|
||||
private bool GenerateAndAssemble(string pathName, out bool someAsmFailed) {
|
||||
someAsmFailed = false;
|
||||
|
||||
ReportProgress(Path.GetFileName(pathName) + "...\r\n");
|
||||
|
||||
// Create DisasmProject object, either as a new project for a plain data file,
|
||||
@ -243,6 +255,7 @@ namespace SourceGen.Tests {
|
||||
|
||||
// Iterate through all known assemblers.
|
||||
bool didFail = false;
|
||||
int numAsmFailures = 0;
|
||||
foreach (AssemblerInfo.Id asmId in
|
||||
(AssemblerInfo.Id[])Enum.GetValues(typeof(AssemblerInfo.Id))) {
|
||||
if (asmId == AssemblerInfo.Id.Unknown) {
|
||||
@ -315,16 +328,15 @@ namespace SourceGen.Tests {
|
||||
didFail = true;
|
||||
continue;
|
||||
}
|
||||
results.AsmResults = asmResults;
|
||||
numAsmFailures++; // assume failure until the end
|
||||
if (asmResults.ExitCode != 0) {
|
||||
ReportErrMsg("assembler returned code=" + asmResults.ExitCode);
|
||||
ReportFailure();
|
||||
didFail = true;
|
||||
results.AsmResults = asmResults;
|
||||
continue;
|
||||
}
|
||||
|
||||
results.AsmResults = asmResults;
|
||||
|
||||
ReportProgress(" verify...");
|
||||
timer.StartTask("Compare Binary to Expected");
|
||||
FileInfo fi = new FileInfo(asmResults.OutputPathName);
|
||||
@ -354,6 +366,7 @@ namespace SourceGen.Tests {
|
||||
|
||||
// Victory!
|
||||
results.AssembleOkay = true;
|
||||
numAsmFailures--;
|
||||
ReportSuccess();
|
||||
|
||||
timer.EndTask("Full Test Duration");
|
||||
@ -373,6 +386,8 @@ namespace SourceGen.Tests {
|
||||
}
|
||||
|
||||
project.Cleanup();
|
||||
|
||||
someAsmFailed = (numAsmFailures != 0);
|
||||
return !didFail;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user