1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-07-06 00:28:58 +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:
Andy McFadden 2021-08-01 17:09:52 -07:00
parent 752fa06ef5
commit 8db554c1cd
14 changed files with 307 additions and 16 deletions

View File

@ -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),

View File

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

Binary file not shown.

View 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":{
}}

View File

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

View File

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

View File

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

View 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

View 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

View 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

View 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 {}

View 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

View 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

View File

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