mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f5706bbeb | ||
|
|
25c9b2fea4 | ||
|
|
154f9b300f | ||
|
|
d78ce77536 | ||
|
|
b4fb43bc80 | ||
|
|
e0e01f794e | ||
|
|
08865dbb4e | ||
|
|
b9ad7e0e55 | ||
|
|
07158a6f1a | ||
|
|
957c42bc1d | ||
|
|
f784da2da6 | ||
|
|
c080fbe59a | ||
|
|
d70b8303b1 | ||
|
|
1d38c3582a | ||
|
|
9438e996d7 | ||
|
|
3b4a5e27f7 | ||
|
|
648d9fc269 | ||
|
|
54fccec7d7 | ||
|
|
4f9693055e | ||
|
|
555c50ee10 | ||
|
|
bf98ceca2c | ||
|
|
573cecb087 | ||
|
|
1b528491c2 | ||
|
|
4bdabe1961 | ||
|
|
a3fa527378 | ||
|
|
84f5ffa426 | ||
|
|
25d2b42283 | ||
|
|
300d1a871c | ||
|
|
2fcb83a39f | ||
|
|
3ba1d00a7c | ||
|
|
64164c1c72 | ||
|
|
3ee6058524 | ||
|
|
93a0a41e73 | ||
|
|
e7ab7b6d7a | ||
|
|
7d4dc3c063 | ||
|
|
a50400b7d1 | ||
|
|
f89f1a84d0 | ||
|
|
688dce6145 | ||
|
|
b88f550c5b | ||
|
|
9864abd393 | ||
|
|
c702c4a6df | ||
|
|
77e376f6bf | ||
|
|
491e5dbcfb | ||
|
|
a5c7393561 | ||
|
|
7fd3e9bb7d | ||
|
|
459e9f8f3b | ||
|
|
5b1143bcb3 | ||
|
|
fddd390d31 | ||
|
|
e514eeba17 | ||
|
|
c11a52b278 | ||
|
|
85e87dfe2e | ||
|
|
cb47e2c149 | ||
|
|
0fc9aa6b2d | ||
|
|
155896c4c7 | ||
|
|
178e60bba0 | ||
|
|
9f84aa5fb2 | ||
|
|
66fc109ce5 | ||
|
|
a231872821 | ||
|
|
7cfb33a448 | ||
|
|
3b798097b9 | ||
|
|
6fb05bdefc | ||
|
|
64ea72ed4d | ||
|
|
89425088ce | ||
|
|
925b9d845d | ||
|
|
ad074076c2 | ||
|
|
a2194c43a6 | ||
|
|
4b23b1dc86 | ||
|
|
9005c7994a | ||
|
|
4a47e15b1c | ||
|
|
09cbdf410a | ||
|
|
df6a43c7f0 | ||
|
|
4ce130dc8b | ||
|
|
94d76aa82c | ||
|
|
73609636c5 | ||
|
|
66b06d6c40 | ||
|
|
eeeb8d81f4 | ||
|
|
6f727aff88 | ||
|
|
518e5a30c2 | ||
|
|
bbba4b3d60 | ||
|
|
967adb9a87 | ||
|
|
040a6c62de | ||
|
|
483d193ced | ||
|
|
62458216c9 | ||
|
|
76b05cb5fd | ||
|
|
570b574b93 | ||
|
|
a82f211f9a | ||
|
|
504c80cddf | ||
|
|
4b4af9b527 | ||
|
|
28b383f888 | ||
|
|
40ce7725a1 | ||
|
|
1f2d46628e | ||
|
|
c9535049c8 | ||
|
|
9317cf8a35 | ||
|
|
1cd754f05d | ||
|
|
97b8cb748d | ||
|
|
84d9040b57 | ||
|
|
fdd18c615c | ||
|
|
c14f6cfc2b | ||
|
|
326eab3dd1 | ||
|
|
6da1f7eb4c | ||
|
|
1e82483152 | ||
|
|
6e2fd41a8b | ||
|
|
9927af1095 | ||
|
|
7585b6ef6f | ||
|
|
a6159702da | ||
|
|
0247fb0d84 | ||
|
|
6de760885f | ||
|
|
9851d14fb9 | ||
|
|
d5fc69d3e4 | ||
|
|
a40d120f2a | ||
|
|
fcdd9414d9 | ||
|
|
272a1001a8 | ||
|
|
2a52241f1c | ||
|
|
d8f1822c12 | ||
|
|
ce7d094adb | ||
|
|
a0cf1889a3 | ||
|
|
38ef394e15 | ||
|
|
abbf7c7cb0 | ||
|
|
ca5f7ae32f | ||
|
|
cbc4b75e50 | ||
|
|
65ddcf91d0 | ||
|
|
5280e1b449 | ||
|
|
b6ffb81909 | ||
|
|
e9edffa9f0 | ||
|
|
aef211e5f3 | ||
|
|
66829203d8 | ||
|
|
7a0eaf3148 |
No files matched your search
+4
-2
@@ -1,9 +1,12 @@
|
||||
.idea/workspace.xml
|
||||
.idea/discord.xml
|
||||
.idea/developer-tools.xml
|
||||
.idea/usage.statistics.xml
|
||||
.idea/shelf/
|
||||
build/
|
||||
dist/
|
||||
output/
|
||||
out/
|
||||
.*cache/
|
||||
*.directory
|
||||
*.prg
|
||||
@@ -12,7 +15,6 @@ output/
|
||||
*.vm.txt
|
||||
*.vice-mon-list
|
||||
docs/build
|
||||
out/
|
||||
parser/**/*.interp
|
||||
parser/**/*.tokens
|
||||
parser/**/*.java
|
||||
@@ -23,6 +25,7 @@ compiler/src/prog8/buildversion/*
|
||||
.eggs/
|
||||
/MANIFEST
|
||||
.tox/
|
||||
.kotlin/
|
||||
__pycache__/
|
||||
parser.out
|
||||
parsetab.py
|
||||
@@ -31,7 +34,6 @@ parsetab.py
|
||||
compiler/lib/
|
||||
|
||||
.gradle
|
||||
/prog8compiler.jar
|
||||
sd*.img
|
||||
*.d64
|
||||
|
||||
Generated
+1
-1
@@ -4,6 +4,6 @@
|
||||
<option name="jvmTarget" value="11" />
|
||||
</component>
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="1.9.24" />
|
||||
<option name="version" value="2.0.21" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+10
-10
@@ -1,23 +1,23 @@
|
||||
<component name="libraryTable">
|
||||
<library name="KotlinJavaRuntime" type="repository">
|
||||
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.20" />
|
||||
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.21" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.21/kotlin-stdlib-jdk8-2.0.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.20/kotlin-stdlib-jdk7-2.0.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.21/kotlin-stdlib-jdk7-2.0.21.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.21/kotlin-stdlib-jdk8-2.0.21-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.20/kotlin-stdlib-jdk7-2.0.20-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.21/kotlin-stdlib-jdk7-2.0.21-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.21/kotlin-stdlib-jdk8-2.0.21-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.20/kotlin-stdlib-jdk7-2.0.20-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.21/kotlin-stdlib-jdk7-2.0.21-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
Generated
+13
@@ -0,0 +1,13 @@
|
||||
<component name="libraryTable">
|
||||
<library name="eclipse.lsp4j" type="repository">
|
||||
<properties maven-id="org.eclipse.lsp4j:org.eclipse.lsp4j:0.23.1" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/0.23.1/org.eclipse.lsp4j-0.23.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/0.23.1/org.eclipse.lsp4j.jsonrpc-0.23.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.11.0/gson-2.11.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.27.0/error_prone_annotations-2.27.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
+4
-2
@@ -2,6 +2,8 @@
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/beanshell/beanshell.iml" filepath="$PROJECT_DIR$/beanshell/beanshell.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/benchmark-program/benchmark-program.iml" filepath="$PROJECT_DIR$/benchmark-program/benchmark-program.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeCore/codeCore.iml" filepath="$PROJECT_DIR$/codeCore/codeCore.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" filepath="$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" filepath="$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" />
|
||||
@@ -12,10 +14,10 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/benchmark-program/benchmark-program.iml" filepath="$PROJECT_DIR$/benchmark-program/benchmark-program.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/prog8.iml" filepath="$PROJECT_DIR$/.idea/modules/prog8.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
all:
|
||||
gradle installdist installshadowdist
|
||||
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/p8compile"
|
||||
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c"
|
||||
|
||||
test:
|
||||
gradle build
|
||||
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/p8compile"
|
||||
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c"
|
||||
|
||||
@@ -66,11 +66,14 @@ What does Prog8 provide?
|
||||
- conditional branches that map 1:1 to cpu status flags
|
||||
- ``when`` statement to provide a concise jump table alternative to if/elseif chains
|
||||
- ``in`` expression for concise and efficient multi-value/containment check
|
||||
- ``defer`` statement to help write concise and robust subroutine cleanup logic
|
||||
- several specialized built-in functions such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
|
||||
- various powerful built-in libraries to do I/O, number conversions, graphics and more
|
||||
- inline assembly allows you to have full control when every cycle or byte matters
|
||||
- supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16 (also available on other targets)
|
||||
- encode strings and characters into petscii or screencodes or even other encodings
|
||||
- Automatic ROM/RAM bank switching on certain compiler targets when calling routines in other banks
|
||||
- 50 Kb of available program RAM size on the C64 by default; because Basic ROM is banked out altogether
|
||||
|
||||
*Rapid edit-compile-run-debug cycle:*
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" name="jetbrains.kotlinx.cli.jvm" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/lib/bsh-3.0.0-SNAPSHOT.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,91 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("application")
|
||||
}
|
||||
|
||||
val serverMainClassName = "prog8lsp.MainKt"
|
||||
val applicationName = "prog8-beanshell"
|
||||
val javaVersion: String by project
|
||||
|
||||
application {
|
||||
mainClass.set(serverMainClassName)
|
||||
description = "Code completions, diagnostics and more for Prog8"
|
||||
// applicationDefaultJvmArgs = listOf("-DkotlinLanguageServer.version=$version")
|
||||
applicationDistribution.into("bin") {
|
||||
filePermissions {
|
||||
user {
|
||||
read=true
|
||||
execute=true
|
||||
write=true
|
||||
}
|
||||
other.execute = true
|
||||
group.execute = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(files("lib/bsh-3.0.0-SNAPSHOT.jar"))
|
||||
}
|
||||
|
||||
configurations.forEach { config ->
|
||||
config.resolutionStrategy {
|
||||
preferProjectModules()
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets.main {
|
||||
java.srcDir("src")
|
||||
resources.srcDir("resources")
|
||||
}
|
||||
|
||||
sourceSets.test {
|
||||
java.srcDir("src")
|
||||
resources.srcDir("resources")
|
||||
}
|
||||
|
||||
tasks.startScripts {
|
||||
applicationName = "prog8-beanshell"
|
||||
}
|
||||
|
||||
tasks.register<Exec>("fixFilePermissions") {
|
||||
// When running on macOS or Linux the start script
|
||||
// needs executable permissions to run.
|
||||
|
||||
onlyIf { !System.getProperty("os.name").lowercase().contains("windows") }
|
||||
commandLine("chmod", "+x", "${tasks.installDist.get().destinationDir}/bin/prog8-beanshell")
|
||||
}
|
||||
|
||||
tasks.withType<Test>() {
|
||||
testLogging {
|
||||
events("failed")
|
||||
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
|
||||
}
|
||||
}
|
||||
|
||||
tasks.installDist {
|
||||
finalizedBy("fixFilePermissions")
|
||||
}
|
||||
|
||||
tasks.build {
|
||||
finalizedBy("installDist")
|
||||
}
|
||||
|
||||
java {
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions.jvmTarget = JvmTarget.JVM_11
|
||||
// jvmToolchain {
|
||||
// languageVersion = JavaLanguageVersion.of(javaVersion.toInt())
|
||||
// }
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,48 @@
|
||||
package prog8beanshell
|
||||
|
||||
import java.io.FilterReader
|
||||
import java.io.Reader
|
||||
|
||||
|
||||
class CommandLineReader(val input: Reader): FilterReader(input) {
|
||||
private val normal = 0
|
||||
private val lastCharNL = 1
|
||||
private val sentSemi = 2
|
||||
private var state = lastCharNL
|
||||
|
||||
override fun read(): Int {
|
||||
if (state == sentSemi) {
|
||||
this.state = lastCharNL
|
||||
return 10
|
||||
} else {
|
||||
var b = input.read()
|
||||
while(b==13) b = input.read()
|
||||
|
||||
if (b == 10) {
|
||||
if (this.state == lastCharNL) {
|
||||
b = 59
|
||||
this.state = sentSemi
|
||||
} else {
|
||||
this.state = lastCharNL
|
||||
}
|
||||
} else {
|
||||
this.state = normal
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun read(buff: CharArray, off: Int, len: Int): Int {
|
||||
val b = read()
|
||||
if (b == -1) {
|
||||
return -1
|
||||
} else {
|
||||
buff[off] = b.toChar()
|
||||
return 1
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package prog8beanshell
|
||||
|
||||
import bsh.FileReader
|
||||
import bsh.Interpreter
|
||||
|
||||
|
||||
class BeanshellInterpreter {
|
||||
|
||||
fun run(symbols: Map<String, Any>) {
|
||||
val interpreter = Interpreter(CommandLineReader(FileReader(System.`in`)), System.out, System.err, true)
|
||||
interpreter.setExitOnEOF(false)
|
||||
symbols.forEach { (name, value) -> interpreter.set(name, value) }
|
||||
interpreter.run()
|
||||
}
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val i = BeanshellInterpreter()
|
||||
i.run(mapOf(
|
||||
"env" to System.getenv(),
|
||||
"args" to args
|
||||
))
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
.PHONY: all clean emu
|
||||
.PHONY: clean run
|
||||
|
||||
all: benchmark.prg
|
||||
run:
|
||||
prog8c -target cx16 benchmark.p8
|
||||
x16emu -run -prg benchmark.prg -warp
|
||||
|
||||
clean:
|
||||
rm -f *.prg *.PRG *.asm *.vice-* *.BIN *.PAL *.zip *.7z
|
||||
|
||||
emu: benchmark.prg
|
||||
x16emu -run -prg $< -warp
|
||||
|
||||
benchmark.prg: benchmark.p8 b_3d.p8 b_adpcm.p8 b_circles.p8 b_life.p8 b_mandelbrot.p8 b_maze.p8 b_queens.p8 b_textelite.p8
|
||||
p8compile $< -target cx16
|
||||
@@ -1,4 +1,4 @@
|
||||
%import gfx2
|
||||
%import gfx_lores
|
||||
%import math
|
||||
|
||||
circles {
|
||||
@@ -14,7 +14,7 @@ circles {
|
||||
if use_kernal
|
||||
void cx16.set_screen_mode(128)
|
||||
else
|
||||
gfx2.screen_mode(1)
|
||||
gfx_lores.graphics_mode()
|
||||
|
||||
math.rndseed(12345,6789)
|
||||
cbm.SETTIM(0,0,0)
|
||||
@@ -28,14 +28,14 @@ circles {
|
||||
cx16.GRAPH_clear()
|
||||
}
|
||||
else
|
||||
gfx2.clear_screen(0)
|
||||
gfx_lores.clear_screen(0)
|
||||
total_num_circles += draw_circles(use_kernal, max_time)
|
||||
}
|
||||
|
||||
if use_kernal
|
||||
void cx16.set_screen_mode(3)
|
||||
else {
|
||||
gfx2.screen_mode(0)
|
||||
gfx_lores.text_mode()
|
||||
}
|
||||
|
||||
return total_num_circles
|
||||
@@ -66,7 +66,7 @@ circles {
|
||||
cx16.GRAPH_draw_oval(x-radius, y-radius, radius*2, radius*2, true)
|
||||
}
|
||||
else
|
||||
gfx2.disc(x, y as ubyte, radius, color)
|
||||
gfx_lores.disc(x, y as ubyte, radius, color)
|
||||
circle_x[num_circles] = x
|
||||
circle_y[num_circles] = y
|
||||
circle_radius[num_circles] = radius
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/compiled" />
|
||||
</content>
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -62,7 +62,7 @@ main {
|
||||
benchmark_score[benchmark_number] = adpcm.decode_benchmark(300)
|
||||
benchmark_number++
|
||||
|
||||
announce_benchmark("circles with gfx2")
|
||||
announce_benchmark("circles with gfx_lores")
|
||||
benchmark_score[benchmark_number] = circles.draw(false, 300)
|
||||
benchmark_number++
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package prog8.code
|
||||
|
||||
import prog8.code.ast.PtAsmSub
|
||||
import prog8.code.ast.PtNode
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.code.core.*
|
||||
@@ -97,7 +98,7 @@ enum class StNodeType {
|
||||
// MODULE, // not used with current scoping rules
|
||||
BLOCK,
|
||||
SUBROUTINE,
|
||||
ROMSUB,
|
||||
EXTSUB,
|
||||
LABEL,
|
||||
STATICVAR,
|
||||
MEMVAR,
|
||||
@@ -178,31 +179,48 @@ open class StNode(val name: String,
|
||||
|
||||
class StStaticVariable(name: String,
|
||||
val dt: DataType,
|
||||
val onetimeInitializationNumericValue: Double?, // regular (every-run-time) initialization is done via regular assignments
|
||||
val onetimeInitializationStringValue: StString?,
|
||||
val onetimeInitializationArrayValue: StArray?,
|
||||
val initializationStringValue: StString?,
|
||||
val initializationArrayValue: StArray?,
|
||||
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
|
||||
val zpwish: ZeropageWish, // used in the variable allocator
|
||||
val align: Int,
|
||||
astNode: PtNode) : StNode(name, StNodeType.STATICVAR, astNode) {
|
||||
|
||||
val uninitialized = onetimeInitializationArrayValue==null && onetimeInitializationStringValue==null && onetimeInitializationNumericValue==null
|
||||
var initializationNumericValue: Double? = null
|
||||
private set
|
||||
|
||||
fun setOnetimeInitNumeric(number: Double) {
|
||||
// In certain cases the init value of an existing var should be updated,
|
||||
// so we can't ask this as a constructor parameter.
|
||||
// This has to do with the way Prog8 does the (re)initialization of such variables: via code assignment statements.
|
||||
// Certain codegens might want to put them back into the variable directly.
|
||||
// For strings and arrays this doesn't occur - these are always already specced at creation time.
|
||||
initializationNumericValue = number
|
||||
}
|
||||
|
||||
val uninitialized: Boolean
|
||||
get() = initializationArrayValue==null && initializationStringValue==null && initializationNumericValue==null
|
||||
|
||||
init {
|
||||
if(length!=null) {
|
||||
require(onetimeInitializationNumericValue == null)
|
||||
if(onetimeInitializationArrayValue!=null)
|
||||
require(onetimeInitializationArrayValue.isEmpty() ||onetimeInitializationArrayValue.size==length)
|
||||
require(initializationNumericValue == null)
|
||||
if(initializationArrayValue!=null)
|
||||
require(initializationArrayValue.isEmpty() ||initializationArrayValue.size==length)
|
||||
}
|
||||
if(onetimeInitializationNumericValue!=null) {
|
||||
if(initializationNumericValue!=null) {
|
||||
require(dt in NumericDatatypes || dt==DataType.BOOL)
|
||||
}
|
||||
if(onetimeInitializationArrayValue!=null) {
|
||||
if(initializationArrayValue!=null) {
|
||||
require(dt in ArrayDatatypes)
|
||||
require(length==onetimeInitializationArrayValue.size)
|
||||
require(length==initializationArrayValue.size)
|
||||
}
|
||||
if(onetimeInitializationStringValue!=null) {
|
||||
if(initializationStringValue!=null) {
|
||||
require(dt == DataType.STR)
|
||||
require(length == onetimeInitializationStringValue.first.length+1)
|
||||
require(length == initializationStringValue.first.length+1)
|
||||
}
|
||||
if(align > 0) {
|
||||
require(dt == DataType.STR || dt in ArrayDatatypes)
|
||||
require(zpwish != ZeropageWish.REQUIRE_ZEROPAGE && zpwish != ZeropageWish.PREFER_ZEROPAGE)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,18 +257,24 @@ class StSub(name: String, val parameters: List<StSubroutineParameter>, val retur
|
||||
StNode(name, StNodeType.SUBROUTINE, astNode)
|
||||
|
||||
|
||||
class StRomSub(name: String,
|
||||
val address: UInt?, // null in case of asmsub, specified in case of romsub
|
||||
val parameters: List<StRomSubParameter>,
|
||||
val returns: List<StRomSubParameter>,
|
||||
class StExtSub(name: String,
|
||||
val address: PtAsmSub.Address?, // null in case of asmsub, specified in case of extsub.
|
||||
val parameters: List<StExtSubParameter>,
|
||||
val returns: List<StExtSubParameter>,
|
||||
astNode: PtNode) :
|
||||
StNode(name, StNodeType.ROMSUB, astNode)
|
||||
StNode(name, StNodeType.EXTSUB, astNode)
|
||||
|
||||
|
||||
|
||||
class StSubroutineParameter(val name: String, val type: DataType)
|
||||
class StRomSubParameter(val register: RegisterOrStatusflag, val type: DataType)
|
||||
class StArrayElement(val number: Double?, val addressOfSymbol: String?, val boolean: Boolean?)
|
||||
class StExtSubParameter(val register: RegisterOrStatusflag, val type: DataType)
|
||||
class StArrayElement(val number: Double?, val addressOfSymbol: String?, val boolean: Boolean?) {
|
||||
init {
|
||||
if(number!=null) require(addressOfSymbol==null && boolean==null)
|
||||
if(addressOfSymbol!=null) require(number==null && boolean==null)
|
||||
if(boolean!=null) require(addressOfSymbol==null && number==null)
|
||||
}
|
||||
}
|
||||
|
||||
typealias StString = Pair<String, Encoding>
|
||||
typealias StArray = List<StArrayElement>
|
||||
@@ -3,7 +3,6 @@ package prog8.code
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
import kotlin.collections.ArrayDeque
|
||||
|
||||
|
||||
class SymbolTableMaker(private val program: PtProgram, private val options: CompilationOptions) {
|
||||
@@ -39,9 +38,9 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
|
||||
private fun addToSt(node: PtNode, scope: ArrayDeque<StNode>) {
|
||||
val stNode = when(node) {
|
||||
is PtAsmSub -> {
|
||||
val parameters = node.parameters.map { StRomSubParameter(it.first, it.second.type) }
|
||||
val returns = node.returns.map { StRomSubParameter(it.first, it.second) }
|
||||
StRomSub(node.name, node.address, parameters, returns, node)
|
||||
val parameters = node.parameters.map { StExtSubParameter(it.first, it.second.type) }
|
||||
val returns = node.returns.map { StExtSubParameter(it.first, it.second) }
|
||||
StExtSub(node.name, node.address, parameters, returns, node)
|
||||
}
|
||||
is PtBlock -> {
|
||||
StNode(node.name, StNodeType.BLOCK, node)
|
||||
@@ -66,23 +65,26 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
|
||||
val numElements: Int?
|
||||
val value = node.value
|
||||
if(value!=null) {
|
||||
val number = (value as? PtNumber)?.number
|
||||
initialNumeric = if(number==0.0) null else number // 0 as init value -> just uninitialized
|
||||
when (value) {
|
||||
is PtString -> {
|
||||
initialString = StString(value.value, value.encoding)
|
||||
initialArray = null
|
||||
initialNumeric = null
|
||||
numElements = value.value.length + 1 // include the terminating 0-byte
|
||||
}
|
||||
is PtArray -> {
|
||||
initialArray = makeInitialArray(value)
|
||||
initialString = null
|
||||
initialNumeric = null
|
||||
numElements = initialArray.size
|
||||
require(node.arraySize?.toInt()==numElements)
|
||||
}
|
||||
else -> {
|
||||
require(value is PtNumber)
|
||||
initialString = null
|
||||
initialArray = null
|
||||
val number = value.number
|
||||
initialNumeric = number
|
||||
numElements = node.arraySize?.toInt()
|
||||
}
|
||||
}
|
||||
@@ -95,7 +97,10 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
|
||||
// if(node.type in SplitWordArrayTypes) {
|
||||
// ... split array also add _lsb and _msb to symboltable?
|
||||
// }
|
||||
StStaticVariable(node.name, node.type, initialNumeric, initialString, initialArray, numElements, node.zeropage, node)
|
||||
val stVar = StStaticVariable(node.name, node.type, initialString, initialArray, numElements, node.zeropage, node.align.toInt(), node)
|
||||
if(initialNumeric!=null)
|
||||
stVar.setOnetimeInitNumeric(initialNumeric)
|
||||
stVar
|
||||
}
|
||||
is PtBuiltinFunctionCall -> {
|
||||
if(node.name=="memory") {
|
||||
@@ -131,7 +136,6 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
|
||||
TODO("address-of array element $it in initial array value")
|
||||
StArrayElement(null, it.identifier.name, null)
|
||||
}
|
||||
is PtIdentifier -> StArrayElement(null, it.name, null)
|
||||
is PtNumber -> StArrayElement(it.number, null, null)
|
||||
is PtBool -> StArrayElement(null, null, it.value)
|
||||
else -> throw AssemblyError("invalid array element $it")
|
||||
|
||||
@@ -80,18 +80,11 @@ class PtBlock(name: String,
|
||||
val options: Options,
|
||||
position: Position
|
||||
) : PtNamedNode(name, position), IPtStatementContainer {
|
||||
enum class BlockAlignment {
|
||||
NONE,
|
||||
WORD,
|
||||
PAGE
|
||||
}
|
||||
|
||||
class Options(val address: UInt? = null,
|
||||
val forceOutput: Boolean = false,
|
||||
val noSymbolPrefixing: Boolean = false,
|
||||
val veraFxMuls: Boolean = false,
|
||||
val ignoreUnused: Boolean = false,
|
||||
val alignment: BlockAlignment = BlockAlignment.NONE)
|
||||
val ignoreUnused: Boolean = false)
|
||||
}
|
||||
|
||||
|
||||
@@ -103,12 +96,20 @@ class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Positi
|
||||
}
|
||||
|
||||
|
||||
class PtLabel(name: String, position: Position) : PtNamedNode(name, position)
|
||||
class PtLabel(name: String, position: Position) : PtNamedNode(name, position) {
|
||||
companion object {
|
||||
// all automatically generated labels everywhere need to have the same label name prefix:
|
||||
const val GeneratedLabelPrefix = "p8_label_gen_"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PtBreakpoint(position: Position): PtNode(position)
|
||||
|
||||
|
||||
class PtAlign(val align: UInt, position: Position): PtNode(position)
|
||||
|
||||
|
||||
class PtIncludeBinary(val file: Path, val offset: UInt?, val length: UInt?, position: Position) : PtNode(position)
|
||||
|
||||
|
||||
|
||||
@@ -82,10 +82,11 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
|
||||
}
|
||||
|
||||
fun asConstInteger(): Int? = (this as? PtNumber)?.number?.toInt() ?: (this as? PtBool)?.asInt()
|
||||
fun asConstValue(): Double? = (this as? PtNumber)?.number ?: (this as? PtBool)?.asInt()?.toDouble()
|
||||
|
||||
fun isSimple(): Boolean {
|
||||
return when(this) {
|
||||
is PtAddressOf -> true
|
||||
is PtAddressOf -> this.arrayIndexExpr==null || this.arrayIndexExpr?.isSimple()==true
|
||||
is PtArray -> true
|
||||
is PtArrayIndexer -> index is PtNumber || index is PtIdentifier
|
||||
is PtBinaryExpression -> false
|
||||
@@ -106,6 +107,7 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
|
||||
is PtRange -> true
|
||||
is PtString -> true
|
||||
is PtTypeCast -> value.isSimple()
|
||||
is PtIfExpression -> condition.isSimple() && truevalue.isSimple() && falsevalue.isSimple()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +165,7 @@ class PtArrayIndexer(elementType: DataType, position: Position): PtExpression(el
|
||||
|
||||
|
||||
class PtArray(type: DataType, position: Position): PtExpression(type, position) {
|
||||
// children are always one of 3 types: PtBool, PtNumber or PtAddressOf.
|
||||
override fun hashCode(): Int = Objects.hash(children, type)
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if(other==null || other !is PtArray)
|
||||
@@ -205,6 +208,16 @@ class PtBinaryExpression(val operator: String, type: DataType, position: Positio
|
||||
}
|
||||
|
||||
|
||||
class PtIfExpression(type: DataType, position: Position): PtExpression(type, position) {
|
||||
val condition: PtExpression
|
||||
get() = children[0] as PtExpression
|
||||
val truevalue: PtExpression
|
||||
get() = children[1] as PtExpression
|
||||
val falsevalue: PtExpression
|
||||
get() = children[2] as PtExpression
|
||||
}
|
||||
|
||||
|
||||
class PtContainmentCheck(position: Position): PtExpression(DataType.BOOL, position) {
|
||||
val needle: PtExpression
|
||||
get() = children[0] as PtExpression
|
||||
@@ -226,6 +239,13 @@ class PtFunctionCall(val name: String,
|
||||
position: Position) : PtExpression(type, position) {
|
||||
val args: List<PtExpression>
|
||||
get() = children.map { it as PtExpression }
|
||||
|
||||
init {
|
||||
if(void) require(type==DataType.UNDEFINED) {
|
||||
"void fcall should have undefined datatype"
|
||||
}
|
||||
// note: non-void calls can have UNDEFINED type: is if they return more than 1 value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -330,12 +350,11 @@ class PtRange(type: DataType, position: Position) : PtExpression(type, position)
|
||||
|
||||
val fromLv = from as? PtNumber
|
||||
val toLv = to as? PtNumber
|
||||
val stepLv = step as? PtNumber
|
||||
if(fromLv==null || toLv==null || stepLv==null)
|
||||
if(fromLv==null || toLv==null)
|
||||
return null
|
||||
val fromVal = fromLv.number.toInt()
|
||||
val toVal = toLv.number.toInt()
|
||||
val stepVal = stepLv.number.toInt()
|
||||
val stepVal = step.number.toInt()
|
||||
return makeRange(fromVal, toVal, stepVal)
|
||||
}
|
||||
}
|
||||
@@ -359,7 +378,3 @@ class PtTypeCast(type: DataType, position: Position) : PtExpression(type, positi
|
||||
|
||||
// special node that isn't created from compiling user code, but used internally in the Intermediate Code
|
||||
class PtIrRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position)
|
||||
|
||||
|
||||
fun constValue(expr: PtExpression): Double? = if(expr is PtNumber) expr.number else if(expr is PtBool) expr.asInt().toDouble() else null
|
||||
fun constIntValue(expr: PtExpression): Int? = if(expr is PtNumber) expr.number.toInt() else if(expr is PtBool) expr.asInt() else null
|
||||
@@ -10,6 +10,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
|
||||
fun type(dt: DataType) = "!${dt.name.lowercase()}!"
|
||||
fun txt(node: PtNode): String {
|
||||
return when(node) {
|
||||
is PtAlign -> "%align ${node.align}"
|
||||
is PtAssignTarget -> if(node.void) "<void>" else "<target>"
|
||||
is PtAssignment -> "<assign>"
|
||||
is PtAugmentedAssign -> "<inplace-assign> ${node.operator}"
|
||||
@@ -24,6 +25,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
|
||||
is PtArray -> {
|
||||
val valuelist = node.children.map {
|
||||
when (it) {
|
||||
is PtBool -> it.toString()
|
||||
is PtNumber -> it.number.toString()
|
||||
is PtIdentifier -> it.name
|
||||
else -> "?"
|
||||
@@ -87,16 +89,18 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
|
||||
}"
|
||||
}
|
||||
val str = if (node.inline) "inline " else ""
|
||||
if(node.address==null) {
|
||||
if(node.address == null) {
|
||||
str + "asmsub ${node.name}($params) $clobbers $returns"
|
||||
} else {
|
||||
str + "romsub ${node.address.toHex()} = ${node.name}($params) $clobbers $returns"
|
||||
val bank = if(node.address.constbank!=null) "@bank ${node.address.constbank}"
|
||||
else if(node.address.varbank!=null) "@bank ${node.address.varbank?.name}"
|
||||
else ""
|
||||
str + "extsub $bank ${node.address.address.toHex()} = ${node.name}($params) $clobbers $returns"
|
||||
}
|
||||
}
|
||||
is PtBlock -> {
|
||||
val addr = if(node.options.address==null) "" else "@${node.options.address.toHex()}"
|
||||
val align = if(node.options.alignment==PtBlock.BlockAlignment.NONE) "" else "align=${node.options.alignment}"
|
||||
"\nblock '${node.name}' $addr $align"
|
||||
"\nblock '${node.name}' $addr"
|
||||
}
|
||||
is PtConstant -> {
|
||||
val value = when(node.type) {
|
||||
@@ -125,13 +129,20 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
|
||||
}
|
||||
is PtVariable -> {
|
||||
val split = if(node.type in SplitWordArrayTypes) "@split" else ""
|
||||
val align = when(node.align) {
|
||||
0u -> ""
|
||||
2u -> "@alignword"
|
||||
64u -> "@align64"
|
||||
256u -> "@alignpage"
|
||||
else -> throw IllegalArgumentException("invalid alignment size")
|
||||
}
|
||||
val str = if(node.arraySize!=null) {
|
||||
val eltType = ArrayToElementTypes.getValue(node.type)
|
||||
"${eltType.name.lowercase()}[${node.arraySize}] $split ${node.name}"
|
||||
"${eltType.name.lowercase()}[${node.arraySize}] $split $align ${node.name}"
|
||||
}
|
||||
else if(node.type in ArrayDatatypes) {
|
||||
val eltType = ArrayToElementTypes.getValue(node.type)
|
||||
"${eltType.name.lowercase()}[] $split ${node.name}"
|
||||
"${eltType.name.lowercase()}[] $split $align ${node.name}"
|
||||
}
|
||||
else
|
||||
"${node.type.name.lowercase()} ${node.name}"
|
||||
@@ -153,6 +164,8 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
|
||||
else
|
||||
"->"
|
||||
}
|
||||
is PtDefer -> "<defer>"
|
||||
is PtIfExpression -> "<ifexpr>"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,13 +10,16 @@ sealed interface IPtSubroutine {
|
||||
|
||||
class PtAsmSub(
|
||||
name: String,
|
||||
val address: UInt?,
|
||||
val address: Address?,
|
||||
val clobbers: Set<CpuRegister>,
|
||||
val parameters: List<Pair<RegisterOrStatusflag, PtSubroutineParameter>>,
|
||||
val returns: List<Pair<RegisterOrStatusflag, DataType>>,
|
||||
val inline: Boolean,
|
||||
position: Position
|
||||
) : PtNamedNode(name, position), IPtSubroutine
|
||||
) : PtNamedNode(name, position), IPtSubroutine {
|
||||
|
||||
class Address(val constbank: UByte?, var varbank: PtIdentifier?, val address: UInt)
|
||||
}
|
||||
|
||||
|
||||
class PtSub(
|
||||
@@ -131,7 +134,8 @@ class PtRepeatLoop(position: Position) : PtNode(position) {
|
||||
|
||||
|
||||
class PtReturn(position: Position) : PtNode(position) {
|
||||
val hasValue = children.any()
|
||||
val hasValue: Boolean
|
||||
get() = children.any()
|
||||
val value: PtExpression?
|
||||
get() {
|
||||
return if(children.any())
|
||||
@@ -148,7 +152,15 @@ sealed interface IPtVariable {
|
||||
}
|
||||
|
||||
|
||||
class PtVariable(name: String, override val type: DataType, val zeropage: ZeropageWish, val value: PtExpression?, val arraySize: UInt?, position: Position) : PtNamedNode(name, position), IPtVariable {
|
||||
class PtVariable(
|
||||
name: String,
|
||||
override val type: DataType,
|
||||
val zeropage: ZeropageWish,
|
||||
val align: UInt,
|
||||
val value: PtExpression?,
|
||||
val arraySize: UInt?,
|
||||
position: Position
|
||||
) : PtNamedNode(name, position), IPtVariable {
|
||||
init {
|
||||
value?.let {it.parent=this}
|
||||
}
|
||||
@@ -179,3 +191,6 @@ class PtWhenChoice(val isElse: Boolean, position: Position) : PtNode(position) {
|
||||
val statements: PtNodeGroup
|
||||
get() = children[1] as PtNodeGroup
|
||||
}
|
||||
|
||||
|
||||
class PtDefer(position: Position): PtNode(position), IPtStatementContainer
|
||||
@@ -0,0 +1,16 @@
|
||||
package prog8.code.ast
|
||||
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.core.*
|
||||
|
||||
fun verifyFinalAstBeforeAsmGen(program: PtProgram, options: CompilationOptions, st: SymbolTable, errors: IErrorReporter) {
|
||||
/*
|
||||
walkAst(program) { node, _ ->
|
||||
if(node is PtVariable) {
|
||||
if(node.value!=null) {
|
||||
// require(node.type in ArrayDatatypes || node.type==DataType.STR) { "final check before asmgen: only string and array variables can still have an init value ${node.name} ${node.type} ${node.position}"}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -49,11 +49,14 @@ class FSignature(val pure: Boolean, // does it have side effects?
|
||||
return when {
|
||||
actualParamTypes.isEmpty() -> CallConvention(emptyList(), returns)
|
||||
actualParamTypes.size==1 -> {
|
||||
// one parameter goes via register/registerpair
|
||||
// One parameter goes via register/registerpair.
|
||||
// this avoids repeated code for every caller to store the value in the subroutine's argument variable.
|
||||
// (that store is still done, but only coded once at the start at the subroutine itself rather than at every call site).
|
||||
// TODO can we start using the X register as well for a third byte or word+byte combo
|
||||
val paramConv = when(val paramType = actualParamTypes[0]) {
|
||||
DataType.UBYTE, DataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false)
|
||||
DataType.UWORD, DataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||
DataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||
DataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false) // TODO is this correct? shouldn't it be FAC1?
|
||||
in PassByReferenceDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||
else -> ParamConvention(paramType, null, false)
|
||||
}
|
||||
@@ -79,7 +82,6 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
// cmp returns a status in the carry flag, but not a proper return value
|
||||
"cmp" to FSignature(false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), null),
|
||||
"prog8_lib_stringcompare" to FSignature(true, listOf(FParam("str1", arrayOf(DataType.STR)), FParam("str2", arrayOf(DataType.STR))), DataType.BYTE),
|
||||
"prog8_lib_arraycopy" to FSignature(false, listOf(FParam("source", ArrayDatatypes), FParam("target", ArrayDatatypes)), null),
|
||||
"prog8_lib_square_byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), DataType.UBYTE),
|
||||
"prog8_lib_square_word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD, DataType.UWORD))), DataType.UWORD),
|
||||
"prog8_ifelse_bittest_set" to FSignature(true, listOf(FParam("variable", ByteDatatypes), FParam("bitnumber", arrayOf(DataType.UBYTE))), DataType.BOOL),
|
||||
|
||||
@@ -14,6 +14,7 @@ class CompilationOptions(val output: OutputType,
|
||||
val compTarget: ICompilationTarget,
|
||||
// these are set later, based on command line arguments or options in the source code:
|
||||
var loadAddress: UInt,
|
||||
var memtopAddress: UInt,
|
||||
var warnSymbolShadowing: Boolean = false,
|
||||
var optimize: Boolean = false,
|
||||
var asmQuiet: Boolean = false,
|
||||
@@ -27,6 +28,7 @@ class CompilationOptions(val output: OutputType,
|
||||
var slabsHighBank: Int? = null,
|
||||
var slabsGolden: Boolean = false,
|
||||
var splitWordArrays: Boolean = false,
|
||||
var addMissingRts: Boolean = false, // deprecated, will likely go way in future version
|
||||
var breakpointCpuInstruction: String? = null,
|
||||
var outputDir: Path = Path(""),
|
||||
var symbolDefs: Map<String, String> = emptyMap()
|
||||
|
||||
@@ -13,7 +13,9 @@ interface IMachineDefinition {
|
||||
val FLOAT_MAX_NEGATIVE: Double
|
||||
val FLOAT_MAX_POSITIVE: Double
|
||||
val FLOAT_MEM_SIZE: Int
|
||||
val STARTUP_CODE_RESERVED_SIZE: UInt // this is here, so that certain compiler targets are able to tune this
|
||||
val PROGRAM_LOAD_ADDRESS : UInt
|
||||
val PROGRAM_MEMTOP_ADDRESS: UInt
|
||||
val BSSHIGHRAM_START: UInt
|
||||
val BSSHIGHRAM_END: UInt
|
||||
val BSSGOLDENRAM_START: UInt
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package prog8.code.optimize
|
||||
|
||||
import prog8.code.StRomSub
|
||||
import prog8.code.StExtSub
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
@@ -39,7 +39,7 @@ private fun optimizeAssignTargets(program: PtProgram, st: SymbolTable, errors: I
|
||||
}
|
||||
if(functionName!=null) {
|
||||
val stNode = st.lookup(functionName)
|
||||
if (stNode is StRomSub) {
|
||||
if (stNode is StExtSub) {
|
||||
require(node.children.size==stNode.returns.size+1) {
|
||||
"number of targets must match return values"
|
||||
}
|
||||
@@ -60,7 +60,7 @@ private fun optimizeAssignTargets(program: PtProgram, st: SymbolTable, errors: I
|
||||
if(node.children.dropLast(1).all { (it as PtAssignTarget).void }) {
|
||||
// all targets are now void, the whole assignment can be discarded and replaced by just a (void) call to the subroutine
|
||||
val index = node.parent.children.indexOf(node)
|
||||
val voidCall = PtFunctionCall(functionName, true, value.type, value.position)
|
||||
val voidCall = PtFunctionCall(functionName, true, DataType.UNDEFINED, value.position)
|
||||
value.children.forEach { voidCall.add(it) }
|
||||
node.parent.children[index] = voidCall
|
||||
voidCall.parent = node.parent
|
||||
@@ -78,38 +78,73 @@ private fun optimizeBitTest(program: PtProgram, options: CompilationOptions): In
|
||||
if(options.compTarget.machine.cpu == CpuType.VIRTUAL)
|
||||
return 0 // the special bittest optimization is not yet valid for the IR
|
||||
|
||||
fun makeBittestCall(condition: PtBinaryExpression, and: PtBinaryExpression, variable: PtIdentifier, bitmask: Int): PtBuiltinFunctionCall {
|
||||
require(bitmask==128 || bitmask==64)
|
||||
val setOrNot = if(condition.operator=="!=") "set" else "notset"
|
||||
val bittestCall = PtBuiltinFunctionCall("prog8_ifelse_bittest_$setOrNot", false, true, DataType.BOOL, condition.position)
|
||||
bittestCall.add(variable)
|
||||
if(bitmask==128)
|
||||
bittestCall.add(PtNumber(DataType.UBYTE, 7.0, and.right.position))
|
||||
else
|
||||
bittestCall.add(PtNumber(DataType.UBYTE, 6.0, and.right.position))
|
||||
return bittestCall
|
||||
}
|
||||
|
||||
fun isAndByteCondition(condition: PtBinaryExpression?): Triple<PtBinaryExpression, PtIdentifier, Int>? {
|
||||
if(condition!=null && (condition.operator=="==" || condition.operator=="!=")) {
|
||||
if (condition.right.asConstInteger() == 0) {
|
||||
val and = condition.left as? PtBinaryExpression
|
||||
if (and != null && and.operator == "&" && and.type == DataType.UBYTE) {
|
||||
val bitmask = and.right.asConstInteger()
|
||||
if(bitmask==128 || bitmask==64) {
|
||||
val variable = and.left as? PtIdentifier
|
||||
if (variable != null && variable.type in ByteDatatypes) {
|
||||
return Triple(and, variable, bitmask)
|
||||
}
|
||||
val typecast = and.left as? PtTypeCast
|
||||
if (typecast != null && typecast.type == DataType.UBYTE) {
|
||||
val castedVariable = typecast.value as? PtIdentifier
|
||||
if(castedVariable!=null && castedVariable.type in ByteDatatypes)
|
||||
return Triple(and, castedVariable, bitmask)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
var changes = 0
|
||||
var recurse = true
|
||||
walkAst(program) { node: PtNode, depth: Int ->
|
||||
if(node is PtIfElse) {
|
||||
val condition = node.condition as? PtBinaryExpression
|
||||
if(condition!=null && (condition.operator=="==" || condition.operator=="!=")) {
|
||||
if(condition.right.asConstInteger()==0) {
|
||||
val and = condition.left as? PtBinaryExpression
|
||||
if(and != null && and.operator=="&" && and.type == DataType.UBYTE) {
|
||||
val variable = and.left as? PtIdentifier
|
||||
val bitmask = and.right.asConstInteger()
|
||||
if(variable!=null && variable.type in ByteDatatypes && (bitmask==128 || bitmask==64)) {
|
||||
val setOrNot = if(condition.operator=="!=") "set" else "notset"
|
||||
val index = node.parent.children.indexOf(node)
|
||||
val bittestCall = PtBuiltinFunctionCall("prog8_ifelse_bittest_$setOrNot", false, true, DataType.BOOL, node.condition.position)
|
||||
bittestCall.add(variable)
|
||||
if(bitmask==128)
|
||||
bittestCall.add(PtNumber(DataType.UBYTE, 7.0, and.right.position))
|
||||
else
|
||||
bittestCall.add(PtNumber(DataType.UBYTE, 6.0, and.right.position))
|
||||
val ifElse = PtIfElse(node.position)
|
||||
ifElse.add(bittestCall)
|
||||
ifElse.add(node.ifScope)
|
||||
if(node.hasElse())
|
||||
ifElse.add(node.elseScope)
|
||||
node.parent.children[index] = ifElse
|
||||
ifElse.parent = node.parent
|
||||
changes++
|
||||
recurse = false
|
||||
}
|
||||
}
|
||||
}
|
||||
val check = isAndByteCondition(condition)
|
||||
if(check!=null) {
|
||||
val (and, variable, bitmask) = check
|
||||
val bittestCall = makeBittestCall(condition!!, and, variable, bitmask)
|
||||
val ifElse = PtIfElse(node.position)
|
||||
ifElse.add(bittestCall)
|
||||
ifElse.add(node.ifScope)
|
||||
if (node.hasElse())
|
||||
ifElse.add(node.elseScope)
|
||||
val index = node.parent.children.indexOf(node)
|
||||
node.parent.children[index] = ifElse
|
||||
ifElse.parent = node.parent
|
||||
changes++
|
||||
recurse = false
|
||||
}
|
||||
}
|
||||
if (node is PtIfExpression) {
|
||||
val condition = node.condition as? PtBinaryExpression
|
||||
val check = isAndByteCondition(condition)
|
||||
if(check!=null) {
|
||||
val (and, variable, bitmask) = check
|
||||
val bittestCall = makeBittestCall(condition!!, and, variable, bitmask)
|
||||
node.children[0] = bittestCall
|
||||
bittestCall.parent = node
|
||||
changes++
|
||||
recurse = false
|
||||
}
|
||||
}
|
||||
recurse
|
||||
|
||||
@@ -27,6 +27,7 @@ val CompilationTargets = listOf(
|
||||
Cx16Target.NAME,
|
||||
PETTarget.NAME,
|
||||
AtariTarget.NAME,
|
||||
Neo6502Target.NAME,
|
||||
VMTarget.NAME
|
||||
)
|
||||
|
||||
@@ -37,5 +38,6 @@ fun getCompilationTargetByName(name: String) = when(name.lowercase()) {
|
||||
PETTarget.NAME -> PETTarget()
|
||||
AtariTarget.NAME -> AtariTarget()
|
||||
VMTarget.NAME -> VMTarget()
|
||||
Neo6502Target.NAME -> Neo6502Target()
|
||||
else -> throw IllegalArgumentException("invalid compilation target")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package prog8.code.target
|
||||
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.neo6502.Neo6502MachineDefinition
|
||||
|
||||
|
||||
class Neo6502Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
|
||||
override val name = NAME
|
||||
override val machine = Neo6502MachineDefinition()
|
||||
override val defaultEncoding = Encoding.ISO
|
||||
|
||||
companion object {
|
||||
const val NAME = "neo"
|
||||
}
|
||||
|
||||
override fun memorySize(dt: DataType): Int {
|
||||
return when(dt) {
|
||||
in ByteDatatypesWithBoolean -> 1
|
||||
in WordDatatypes, in PassByReferenceDatatypes -> 2
|
||||
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||
else -> throw IllegalArgumentException("invalid datatype")
|
||||
}
|
||||
}
|
||||
|
||||
override fun memorySize(arrayDt: DataType, numElements: Int) =
|
||||
if(arrayDt== DataType.UWORD)
|
||||
numElements // pointer to bytes.
|
||||
else
|
||||
memorySize(ArrayToElementTypes.getValue(arrayDt)) * numElements
|
||||
}
|
||||
@@ -11,7 +11,9 @@ class AtariMachineDefinition: IMachineDefinition {
|
||||
override val FLOAT_MAX_POSITIVE = 9.999999999e97
|
||||
override val FLOAT_MAX_NEGATIVE = -9.999999999e97
|
||||
override val FLOAT_MEM_SIZE = 6
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x2000u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xffffu // TODO what's memtop?
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // TODO
|
||||
override val BSSHIGHRAM_END = 0u // TODO
|
||||
|
||||
@@ -13,7 +13,9 @@ class C128MachineDefinition: IMachineDefinition {
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xff00u
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // TODO
|
||||
override val BSSHIGHRAM_END = 0u // TODO
|
||||
|
||||
@@ -14,7 +14,10 @@ class C64MachineDefinition: IMachineDefinition {
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xcfe0u // $a000 if floats are used
|
||||
// note that at $cfe0-$cfff are the 16 'virtual registers' R0-R15
|
||||
|
||||
override val BSSHIGHRAM_START = 0xc000u
|
||||
override val BSSHIGHRAM_END = 0xcfffu
|
||||
|
||||
@@ -13,7 +13,9 @@ class CX16MachineDefinition: IMachineDefinition {
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0x9f00u
|
||||
|
||||
override val BSSHIGHRAM_START = 0xa000u // hiram bank 1, 8Kb, assumed to be active
|
||||
override val BSSHIGHRAM_END = 0xbfffu // Rom starts at $c000
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package prog8.code.target.neo6502
|
||||
|
||||
import prog8.code.core.*
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
class Neo6502MachineDefinition: IMachineDefinition {
|
||||
|
||||
override val cpu = CpuType.CPU65c02
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = 9.999999999e97
|
||||
override val FLOAT_MAX_NEGATIVE = -9.999999999e97
|
||||
override val FLOAT_MEM_SIZE = 6
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0800u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xfc00u // kernal starts here
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // TODO
|
||||
override val BSSHIGHRAM_END = 0u // TODO
|
||||
override val BSSGOLDENRAM_START = 0u // TODO
|
||||
override val BSSGOLDENRAM_END = 0u // TODO
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
override lateinit var golden: GoldenRam
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = TODO("atari float asm bytes from number")
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> = TODO("atari float to bytes")
|
||||
override fun convertBytesToFloat(bytes: List<UByte>): Double = TODO("atari bytes to float")
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
|
||||
if(selectedEmulator!=1) {
|
||||
System.err.println("The neo target only supports the main emulator (neo).")
|
||||
return
|
||||
}
|
||||
|
||||
val cmdline = listOf("neo", "${programNameWithPath}.bin@800", "cold")
|
||||
|
||||
println("\nStarting Neo6502 emulator...")
|
||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||
val process: Process = processb.start()
|
||||
process.waitFor()
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = address in 0xff00u..0xff0fu
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = Neo6502Zeropage(compilerOptions)
|
||||
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package prog8.code.target.neo6502
|
||||
|
||||
import prog8.code.core.*
|
||||
|
||||
class Neo6502Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
override val SCRATCH_B1 = 0xfau // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0xfbu // temp storage for a register, must be B1+1
|
||||
override val SCRATCH_W1 = 0xfcu // temp storage 1 for a word $fc+$fd
|
||||
override val SCRATCH_W2 = 0xfeu // temp storage 2 for a word $fe+$ff
|
||||
|
||||
init {
|
||||
if (options.floats) {
|
||||
throw InternalCompilerException("Neo6502 target doesn't support floating point routines")
|
||||
}
|
||||
|
||||
when (options.zeropage) {
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
}
|
||||
else -> {
|
||||
free.addAll(0x22u..0xffu)
|
||||
}
|
||||
}
|
||||
|
||||
val distinctFree = free.distinct()
|
||||
free.clear()
|
||||
free.addAll(distinctFree)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
allocateCx16VirtualRegisters()
|
||||
retainAllowed()
|
||||
}
|
||||
|
||||
override fun allocateCx16VirtualRegisters() {
|
||||
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
|
||||
// However, to be able for the compiler to "see" them as zero page variables, we have to register them here as well.
|
||||
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
||||
for(reg in 0..15) {
|
||||
allocatedVariables["cx16.r${reg}"] = VarAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||
allocatedVariables["cx16.r${reg}s"] = VarAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||
allocatedVariables["cx16.r${reg}L"] = VarAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
||||
allocatedVariables["cx16.r${reg}H"] = VarAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,9 @@ class PETMachineDefinition: IMachineDefinition {
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0401u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0x8000u
|
||||
|
||||
override val BSSHIGHRAM_START = 0u
|
||||
override val BSSHIGHRAM_END = 0u
|
||||
|
||||
@@ -13,7 +13,9 @@ class VirtualMachineDefinition: IMachineDefinition {
|
||||
override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MEM_SIZE = 8 // 64-bits double
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 0u // not actually used
|
||||
override val PROGRAM_LOAD_ADDRESS = 0u // not actually used
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xffffu // not actually used
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // not actually used
|
||||
override val BSSHIGHRAM_END = 0u // not actually used
|
||||
|
||||
@@ -9,7 +9,6 @@ import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.codegen.cpu6502.assignment.*
|
||||
import kotlin.collections.ArrayDeque
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.writeLines
|
||||
|
||||
@@ -17,15 +16,15 @@ import kotlin.io.path.writeLines
|
||||
internal const val subroutineFloatEvalResultVar1 = "prog8_float_eval_result1"
|
||||
internal const val subroutineFloatEvalResultVar2 = "prog8_float_eval_result2"
|
||||
|
||||
class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
|
||||
class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSequenceNr: Int): ICodeGeneratorBackend {
|
||||
override fun generate(
|
||||
program: PtProgram,
|
||||
symbolTable: SymbolTable,
|
||||
options: CompilationOptions,
|
||||
errors: IErrorReporter
|
||||
errors: IErrorReporter,
|
||||
): IAssemblyProgram? {
|
||||
val st = if(prefixSymbols) prefixSymbols(program, options, symbolTable) else symbolTable
|
||||
val asmgen = AsmGen6502Internal(program, st, options, errors)
|
||||
val asmgen = AsmGen6502Internal(program, st, options, errors, lastGeneratedLabelSequenceNr)
|
||||
return asmgen.compileToAssembly()
|
||||
}
|
||||
|
||||
@@ -37,10 +36,9 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
|
||||
when(node) {
|
||||
is PtAsmSub, is PtSub -> node.name = "p8s_${node.name}"
|
||||
is PtBlock -> node.name = "p8b_${node.name}"
|
||||
is PtLabel -> node.name = "p8l_${node.name}"
|
||||
is PtLabel -> if(!node.name.startsWith(PtLabel.GeneratedLabelPrefix)) node.name = "p8l_${node.name}" // don't prefix autogenerated labels
|
||||
is PtConstant -> node.name = "p8c_${node.name}"
|
||||
is PtVariable, is PtMemMapped, is PtSubroutineParameter -> node.name = "p8v_${node.name}"
|
||||
else -> node.name = "p8_${node.name}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +47,9 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
|
||||
is PtAsmSub -> {
|
||||
prefixNamedNode(node)
|
||||
node.parameters.forEach { (_, param) -> prefixNamedNode(param) }
|
||||
if(node.address?.varbank!=null) {
|
||||
node.address!!.varbank = node.address!!.varbank!!.prefix(node, st)
|
||||
}
|
||||
}
|
||||
is PtSub -> {
|
||||
prefixNamedNode(node)
|
||||
@@ -124,11 +125,15 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
|
||||
}
|
||||
|
||||
private fun prefixScopedName(name: String, type: Char): String {
|
||||
if('.' !in name)
|
||||
if('.' !in name) {
|
||||
if(name.startsWith(PtLabel.GeneratedLabelPrefix))
|
||||
return name
|
||||
return "p8${type}_$name"
|
||||
}
|
||||
val parts = name.split('.')
|
||||
val firstPrefixed = "p8b_${parts[0]}"
|
||||
val lastPrefixed = "p8${type}_${parts.last()}"
|
||||
val lastPart = parts.last()
|
||||
val lastPrefixed = if(lastPart.startsWith(PtLabel.GeneratedLabelPrefix)) lastPart else "p8${type}_$lastPart"
|
||||
// the parts in between are assumed to be subroutine scopes.
|
||||
val inbetweenPrefixed = parts.drop(1).dropLast(1).map{ "p8s_$it" }
|
||||
val prefixed = listOf(firstPrefixed) + inbetweenPrefixed + listOf(lastPrefixed)
|
||||
@@ -145,7 +150,6 @@ private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
|
||||
val newValue = PtArray(arrayValue.type, arrayValue.position)
|
||||
arrayValue.children.forEach { elt ->
|
||||
when(elt) {
|
||||
is PtIdentifier -> newValue.add(elt.prefix(arrayValue, st))
|
||||
is PtBool -> newValue.add(elt)
|
||||
is PtNumber -> newValue.add(elt)
|
||||
is PtAddressOf -> {
|
||||
@@ -163,7 +167,7 @@ private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
|
||||
else -> throw AssemblyError("weird array value element $elt")
|
||||
}
|
||||
}
|
||||
val result = PtVariable(name, type, zeropage, newValue, arraySize, position)
|
||||
val result = PtVariable(name, type, zeropage, align, newValue, arraySize, position)
|
||||
result.parent = parent
|
||||
result
|
||||
}
|
||||
@@ -201,7 +205,7 @@ private fun PtIdentifier.prefix(parent: PtNode, st: SymbolTable): PtIdentifier {
|
||||
|
||||
val prefixType = when(target!!.type) {
|
||||
StNodeType.BLOCK -> 'b'
|
||||
StNodeType.SUBROUTINE, StNodeType.ROMSUB -> 's'
|
||||
StNodeType.SUBROUTINE, StNodeType.EXTSUB -> 's'
|
||||
StNodeType.LABEL -> 'l'
|
||||
StNodeType.STATICVAR, StNodeType.MEMVAR -> 'v'
|
||||
StNodeType.CONSTANT -> 'c'
|
||||
@@ -220,7 +224,8 @@ class AsmGen6502Internal (
|
||||
val program: PtProgram,
|
||||
internal val symbolTable: SymbolTable,
|
||||
internal val options: CompilationOptions,
|
||||
internal val errors: IErrorReporter
|
||||
internal val errors: IErrorReporter,
|
||||
private var generatedLabelSequenceNumber: Int
|
||||
) {
|
||||
|
||||
internal val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40,50,80,100)
|
||||
@@ -582,6 +587,7 @@ class AsmGen6502Internal (
|
||||
internal fun translate(stmt: PtNode) {
|
||||
outputSourceLine(stmt)
|
||||
when(stmt) {
|
||||
is PtAlign -> if(stmt.align > 1u) out(" .align ${stmt.align.toHex()}")
|
||||
is PtReturn -> translate(stmt)
|
||||
is PtSub -> programGen.translateSubroutine(stmt)
|
||||
is PtAsmSub -> programGen.translateAsmSubroutine(stmt)
|
||||
@@ -604,9 +610,10 @@ class AsmGen6502Internal (
|
||||
is PtRepeatLoop -> translate(stmt)
|
||||
is PtWhen -> translate(stmt)
|
||||
is PtIncludeBinary -> translate(stmt)
|
||||
is PtBreakpoint -> translate(stmt)
|
||||
is PtBreakpoint -> translateBrk()
|
||||
is PtVariable, is PtConstant, is PtMemMapped -> { /* do nothing; variables are handled elsewhere */ }
|
||||
is PtBlock -> throw AssemblyError("block should have been handled elsewhere")
|
||||
is PtDefer -> throw AssemblyError("defer should have been transformed")
|
||||
is PtNodeGroup -> stmt.children.forEach { translate(it) }
|
||||
is PtNop -> {}
|
||||
else -> throw AssemblyError("missing asm translation for $stmt")
|
||||
@@ -782,7 +789,7 @@ class AsmGen6502Internal (
|
||||
when {
|
||||
iterations == 0 -> {}
|
||||
iterations == 1 -> translate(stmt.statements)
|
||||
iterations<0 || iterations>65535 -> throw AssemblyError("invalid number of iterations")
|
||||
iterations<0 || iterations>65536 -> throw AssemblyError("invalid number of iterations")
|
||||
iterations <= 256 -> repeatByteCount(iterations, stmt)
|
||||
else -> repeatWordCount(iterations, stmt)
|
||||
}
|
||||
@@ -822,10 +829,10 @@ class AsmGen6502Internal (
|
||||
}
|
||||
|
||||
private fun repeatWordCount(iterations: Int, stmt: PtRepeatLoop) {
|
||||
require(iterations in 257..65535) { "invalid repeat count ${stmt.position}" }
|
||||
require(iterations in 257..65536) { "invalid repeat count ${stmt.position}" }
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
val counterVar = createRepeatCounterVar(DataType.UWORD, isTargetCpu(CpuType.CPU65c02), stmt)
|
||||
val loopcount = if(iterations and 0x00ff == 0) iterations else iterations + 0x0100 // so that the loop can simply use a double-dec
|
||||
val loopcount = if(iterations==65536) 0 else if(iterations and 0x00ff == 0) iterations else iterations + 0x0100 // so that the loop can simply use a double-dec
|
||||
out("""
|
||||
ldy #>$loopcount
|
||||
lda #<$loopcount
|
||||
@@ -1040,7 +1047,7 @@ $repeatLabel""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translate(ret: PtReturn, withRts: Boolean=true) {
|
||||
private fun translate(ret: PtReturn) {
|
||||
ret.value?.let { returnvalue ->
|
||||
val sub = ret.definingSub()!!
|
||||
val returnReg = sub.returnRegister()!!
|
||||
@@ -1057,9 +1064,7 @@ $repeatLabel""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(withRts)
|
||||
out(" rts")
|
||||
out(" rts")
|
||||
}
|
||||
|
||||
private fun translate(asm: PtInlineAssembly) {
|
||||
@@ -1084,7 +1089,7 @@ $repeatLabel""")
|
||||
out(" .binary \"$pathForAssembler\" $offset $length")
|
||||
}
|
||||
|
||||
private fun translate(brk: PtBreakpoint) {
|
||||
private fun translateBrk() {
|
||||
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
|
||||
breakpointLabels.add(label)
|
||||
out(label)
|
||||
@@ -1363,11 +1368,9 @@ $repeatLabel""")
|
||||
extra
|
||||
}
|
||||
|
||||
private var generatedLabelSequenceNumber: Int = 0
|
||||
|
||||
internal fun makeLabel(postfix: String): String {
|
||||
generatedLabelSequenceNumber++
|
||||
return "label_asm_${generatedLabelSequenceNumber}_$postfix"
|
||||
return "${PtLabel.GeneratedLabelPrefix}${generatedLabelSequenceNumber}_$postfix"
|
||||
}
|
||||
|
||||
fun assignConstFloatToPointerAY(number: PtNumber) {
|
||||
@@ -1381,6 +1384,10 @@ $repeatLabel""")
|
||||
pla
|
||||
jsr floats.copy_float""")
|
||||
}
|
||||
|
||||
internal fun assignIfExpression(target: AsmAssignTarget, value: PtIfExpression) {
|
||||
ifElseAsmgen.assignIfExpression(target, value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@ package prog8.codegen.cpu6502
|
||||
import prog8.code.StConstant
|
||||
import prog8.code.StMemVar
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.PtLabel
|
||||
import prog8.code.core.IMachineDefinition
|
||||
|
||||
|
||||
@@ -361,15 +362,16 @@ or *_afterif labels.
|
||||
This gets generated after certain if conditions, and only the branch instruction is needed in these cases.
|
||||
*/
|
||||
|
||||
val autoLabelPrefix = PtLabel.GeneratedLabelPrefix
|
||||
if(first=="beq +" && second=="lda #1" && third=="+") {
|
||||
if((fourth.startsWith("beq label_") || fourth.startsWith("bne label_")) &&
|
||||
if((fourth.startsWith("beq $autoLabelPrefix") || fourth.startsWith("bne $autoLabelPrefix")) &&
|
||||
(fourth.endsWith("_shortcut") || fourth.endsWith("_afterif") || fourth.endsWith("_shortcut:") || fourth.endsWith("_afterif:"))) {
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
}
|
||||
else if(fourth.startsWith("label_") && (fourth.endsWith("_shortcut") || fourth.endsWith("_shortcut:"))) {
|
||||
if((fifth.startsWith("beq label_") || fifth.startsWith("bne label_")) &&
|
||||
else if(fourth.startsWith(autoLabelPrefix) && (fourth.endsWith("_shortcut") || fourth.endsWith("_shortcut:"))) {
|
||||
if((fifth.startsWith("beq $autoLabelPrefix") || fifth.startsWith("bne $autoLabelPrefix")) &&
|
||||
(fifth.endsWith("_shortcut") || fifth.endsWith("_afterif") || fifth.endsWith("_shortcut:") || fifth.endsWith("_afterif:"))) {
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
@@ -519,8 +521,10 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
|
||||
|
||||
if(!haslabel(second)) {
|
||||
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
|
||||
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
if("floats.pushFAC" !in first && "floats.popFAC" !in first) { // these 2 routines depend on being called with JSR!!
|
||||
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
}
|
||||
}
|
||||
else if (" rts" in first || "\trts" in first) {
|
||||
if (" jmp" in second || "\tjmp" in second)
|
||||
|
||||
@@ -11,18 +11,22 @@ fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
|
||||
// 1) cx16 virtual word registers,
|
||||
// 2) paired CPU registers,
|
||||
// 3) single CPU registers (order Y,X,A),
|
||||
// 4) CPU Carry status flag
|
||||
// 4) floating point registers (FAC1, FAC2),
|
||||
// 5) CPU Carry status flag
|
||||
val args = sub.parameters.withIndex()
|
||||
val (cx16regs, args2) = args.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
|
||||
val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)
|
||||
val (pairedRegs , args3) = args2.partition { it.value.first.registerOrPair in pairedRegisters }
|
||||
val (singleRegs, rest) = args3.partition { it.value.first.registerOrPair != null }
|
||||
val (singleRegsMixed, rest) = args3.partition { it.value.first.registerOrPair != null }
|
||||
val (singleCpuRegs, floatRegs) = singleRegsMixed.partition {it.value.first.registerOrPair != RegisterOrPair.FAC1 && it.value.first.registerOrPair != RegisterOrPair.FAC2 }
|
||||
|
||||
cx16regs.forEach { order += it.index }
|
||||
pairedRegs.forEach { order += it.index }
|
||||
singleRegs.sortedBy { it.value.first.registerOrPair!!.asCpuRegister() }.asReversed().forEach { order += it.index }
|
||||
singleCpuRegs.sortedBy { it.value.first.registerOrPair!!.asCpuRegister() }.asReversed().forEach { order += it.index }
|
||||
require(rest.all { it.value.first.registerOrPair==null && it.value.first.statusflag!=null})
|
||||
floatRegs.forEach { order += it.index }
|
||||
rest.forEach { order += it.index }
|
||||
require(order.size==sub.parameters.size)
|
||||
|
||||
return order
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.code.ast.PtLabel
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.AtariTarget
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Neo6502Target
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
internal class AssemblyProgram(
|
||||
override val name: String,
|
||||
outputDir: Path,
|
||||
private val outputDir: Path,
|
||||
private val compTarget: ICompilationTarget) : IAssemblyProgram {
|
||||
|
||||
private val assemblyFile = outputDir.resolve("$name.asm")
|
||||
@@ -16,6 +19,7 @@ internal class AssemblyProgram(
|
||||
private val binFile = outputDir.resolve("$name.bin")
|
||||
private val viceMonListFile = outputDir.resolve(C64Target.viceMonListName(name))
|
||||
private val listFile = outputDir.resolve("$name.list")
|
||||
private val targetWithoutBreakpointsForEmulator = setOf(AtariTarget.NAME, Neo6502Target.NAME)
|
||||
|
||||
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
|
||||
|
||||
@@ -96,12 +100,47 @@ internal class AssemblyProgram(
|
||||
command.addAll(listOf("--output", outFile.toString(), assemblyFile.toString()))
|
||||
assemblerCommand = command
|
||||
}
|
||||
"neo" -> {
|
||||
// Neo6502 raw program generation.
|
||||
|
||||
if(options.output!=OutputType.RAW || options.loadAddress!=0x0800u || options.launcher!=CbmPrgLauncherType.NONE) {
|
||||
throw AssemblyError("invalid program compilation options. Neo6502 requires %output raw, %launcher none, %address $0800")
|
||||
}
|
||||
|
||||
// TODO are these options okay for neo?
|
||||
val command = mutableListOf("64tass", "--case-sensitive", "--long-branch",
|
||||
"-Wall", // "-Werror", "-Wno-strict-bool"
|
||||
"--no-monitor"
|
||||
)
|
||||
|
||||
if(options.warnSymbolShadowing)
|
||||
command.add("-Wshadow")
|
||||
else
|
||||
command.add("-Wno-shadow")
|
||||
|
||||
if(options.asmQuiet)
|
||||
command.add("--quiet")
|
||||
|
||||
if(options.asmListfile)
|
||||
command.add("--list=$listFile")
|
||||
|
||||
val outFile = when (options.output) {
|
||||
OutputType.RAW -> {
|
||||
command.add("--nostart")
|
||||
println("\nCreating raw binary for target ${compTarget.name}.")
|
||||
binFile
|
||||
}
|
||||
else -> throw AssemblyError("invalid output type, need 'raw'")
|
||||
}
|
||||
command.addAll(listOf("--output", outFile.toString(), assemblyFile.toString()))
|
||||
assemblerCommand = command
|
||||
}
|
||||
else -> throw AssemblyError("invalid compilation target")
|
||||
}
|
||||
|
||||
val proc = ProcessBuilder(assemblerCommand).inheritIO().start()
|
||||
val result = proc.waitFor()
|
||||
if (result == 0 && compTarget.name!="atari") {
|
||||
if (result == 0 && compTarget.name !in targetWithoutBreakpointsForEmulator) {
|
||||
removeGeneratedLabelsFromMonlist()
|
||||
generateBreakpointList()
|
||||
}
|
||||
@@ -109,7 +148,7 @@ internal class AssemblyProgram(
|
||||
}
|
||||
|
||||
private fun removeGeneratedLabelsFromMonlist() {
|
||||
val pattern = Regex("""al (\w+) \S+prog8_label_.+?""")
|
||||
val pattern = Regex("""al (\w+) \S+${PtLabel.GeneratedLabelPrefix}.+?""")
|
||||
val lines = viceMonListFile.toFile().readLines()
|
||||
viceMonListFile.toFile().outputStream().bufferedWriter().use {
|
||||
for (line in lines) {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.code.StMemVar
|
||||
import prog8.code.StStaticVariable
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.codegen.cpu6502.assignment.*
|
||||
@@ -71,114 +69,25 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
"prog8_lib_stringcompare" -> funcStringCompare(fcall, resultRegister)
|
||||
"prog8_lib_square_byte" -> funcSquare(fcall, DataType.UBYTE, resultRegister)
|
||||
"prog8_lib_square_word" -> funcSquare(fcall, DataType.UWORD, resultRegister)
|
||||
"prog8_lib_arraycopy" -> funcArrayCopy(fcall)
|
||||
else -> throw AssemblyError("missing asmgen for builtin func ${fcall.name}")
|
||||
}
|
||||
|
||||
return BuiltinFunctions.getValue(fcall.name).returnType
|
||||
}
|
||||
|
||||
private fun funcArrayCopy(fcall: PtBuiltinFunctionCall) {
|
||||
val source = fcall.args[0] as PtIdentifier
|
||||
val target = fcall.args[1] as PtIdentifier
|
||||
|
||||
val numElements = when(val sourceSymbol = asmgen.symbolTable.lookup(source.name)) {
|
||||
is StStaticVariable -> sourceSymbol.length!!
|
||||
is StMemVar -> sourceSymbol.length!!
|
||||
else -> 0
|
||||
}
|
||||
val sourceAsm = asmgen.asmVariableName(source)
|
||||
val targetAsm = asmgen.asmVariableName(target)
|
||||
|
||||
if(source.type in SplitWordArrayTypes && target.type in SplitWordArrayTypes) {
|
||||
// split -> split words (copy lsb and msb arrays separately)
|
||||
asmgen.out("""
|
||||
lda #<${sourceAsm}_lsb
|
||||
ldy #>${sourceAsm}_lsb
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${targetAsm}_lsb
|
||||
ldy #>${targetAsm}_lsb
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #${numElements and 255}
|
||||
jsr prog8_lib.memcopy_small
|
||||
lda #<${sourceAsm}_msb
|
||||
ldy #>${sourceAsm}_msb
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${targetAsm}_msb
|
||||
ldy #>${targetAsm}_msb
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #${numElements and 255}
|
||||
jsr prog8_lib.memcopy_small""")
|
||||
}
|
||||
else if(source.type in SplitWordArrayTypes) {
|
||||
// split word array to normal word array (copy lsb and msb arrays separately)
|
||||
require(target.type==DataType.ARRAY_UW || target.type==DataType.ARRAY_W)
|
||||
asmgen.out("""
|
||||
lda #<${sourceAsm}_lsb
|
||||
ldy #>${sourceAsm}_lsb
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${sourceAsm}_msb
|
||||
ldy #>${sourceAsm}_msb
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
lda #<${targetAsm}
|
||||
ldy #>${targetAsm}
|
||||
ldx #${numElements and 255}
|
||||
jsr prog8_lib.arraycopy_split_to_normal_words""")
|
||||
}
|
||||
else if(target.type in SplitWordArrayTypes) {
|
||||
// normal word array to split array
|
||||
require(source.type==DataType.ARRAY_UW || source.type==DataType.ARRAY_W)
|
||||
asmgen.out("""
|
||||
lda #<${targetAsm}_lsb
|
||||
ldy #>${targetAsm}_lsb
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${targetAsm}_msb
|
||||
ldy #>${targetAsm}_msb
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
lda #<${sourceAsm}
|
||||
ldy #>${sourceAsm}
|
||||
ldx #${numElements and 255}
|
||||
jsr prog8_lib.arraycopy_normal_to_split_words""")
|
||||
}
|
||||
else {
|
||||
// normal array to array copy, various element types
|
||||
val eltsize = asmgen.options.compTarget.memorySize(ArrayToElementTypes.getValue(source.type))
|
||||
val numBytes = numElements * eltsize
|
||||
asmgen.out("""
|
||||
lda #<${sourceAsm}
|
||||
ldy #>${sourceAsm}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${targetAsm}
|
||||
ldy #>${targetAsm}
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #${numBytes and 255}
|
||||
jsr prog8_lib.memcopy_small""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcSquare(fcall: PtBuiltinFunctionCall, resultType: DataType, resultRegister: RegisterOrPair?) {
|
||||
// square of word value is faster with dedicated routine, square of byte just use the regular multiplication routine.
|
||||
when (resultType) {
|
||||
DataType.UBYTE -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A)
|
||||
asmgen.out(" tay | jsr math.multiply_bytes")
|
||||
asmgen.out(" tay | jsr prog8_math.multiply_bytes")
|
||||
if(resultRegister!=null) {
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), CpuRegister.A, false, false)
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
|
||||
asmgen.out(" jsr math.square")
|
||||
asmgen.out(" jsr prog8_math.square")
|
||||
if(resultRegister!=null) {
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
@@ -195,7 +104,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.Y, false)
|
||||
asmgen.restoreRegisterStack(CpuRegister.A ,false)
|
||||
// math.divmod_ub_asm: -- divide A by Y, result quotient in Y, remainder in A (unsigned)
|
||||
asmgen.out(" jsr math.divmod_ub_asm")
|
||||
asmgen.out(" jsr prog8_math.divmod_ub_asm")
|
||||
val var2name = asmgen.asmVariableName(fcall.args[2] as PtIdentifier)
|
||||
val var3name = asmgen.asmVariableName(fcall.args[3] as PtIdentifier)
|
||||
val divisionTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, fcall.definingISub(), fcall.args[2].position, var2name)
|
||||
@@ -209,7 +118,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
// math.divmod_uw_asm: -- divide two unsigned words (16 bit each) into 16 bit results
|
||||
// input: P8ZP_SCRATCH_W1 in ZP: 16 bit number, A/Y: 16 bit divisor
|
||||
// output: P8ZP_SCRATCH_W2 in ZP: 16 bit remainder, A/Y: 16 bit division result
|
||||
asmgen.out(" jsr math.divmod_uw_asm")
|
||||
asmgen.out(" jsr prog8_math.divmod_uw_asm")
|
||||
val var2name = asmgen.asmVariableName(fcall.args[2] as PtIdentifier)
|
||||
val divisionTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, fcall.definingISub(), fcall.args[2].position, var2name)
|
||||
val remainderVar = asmgen.asmVariableName(fcall.args[3] as PtIdentifier)
|
||||
@@ -299,15 +208,22 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
}
|
||||
|
||||
private fun funcCallFar(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
if(asmgen.options.compTarget.name != "cx16")
|
||||
throw AssemblyError("callfar only works on cx16 target at this time")
|
||||
val targetName = asmgen.options.compTarget.name
|
||||
if(targetName !in arrayOf("cx16", "c64", "c128"))
|
||||
throw AssemblyError("callfar only works on cx16, c64 and c128 targets at this time")
|
||||
|
||||
val jsrfar = when(targetName) {
|
||||
"cx16" -> "cx16.JSRFAR"
|
||||
"c64" -> "c64.x16jsrfar"
|
||||
"c128" -> "c128.x16jsrfar"
|
||||
else -> TODO("jsrfar routine")
|
||||
}
|
||||
val constBank = fcall.args[0].asConstInteger()
|
||||
val constAddress = fcall.args[1].asConstInteger()
|
||||
if(constBank!=null && constAddress!=null) {
|
||||
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.AY) // uword argument
|
||||
asmgen.out("""
|
||||
jsr cx16.JSRFAR
|
||||
jsr $jsrfar
|
||||
.word ${constAddress.toHex()}
|
||||
.byte $constBank""")
|
||||
} else {
|
||||
@@ -317,7 +233,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" sta (+)+0 | sty (+)+1")
|
||||
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.AY) // uword argument
|
||||
asmgen.out("""
|
||||
jsr cx16.JSRFAR
|
||||
jsr $jsrfar
|
||||
+ .word 0
|
||||
+ .byte 0""")
|
||||
}
|
||||
@@ -329,8 +245,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
}
|
||||
|
||||
private fun funcCallFar2(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
if(asmgen.options.compTarget.name != "cx16")
|
||||
throw AssemblyError("callfar2 only works on cx16 target at this time")
|
||||
val targetName = asmgen.options.compTarget.name
|
||||
if(targetName !in arrayOf("cx16", "c64", "c128"))
|
||||
throw AssemblyError("callfar2 only works on cx16, c64 and c128 targets at this time")
|
||||
|
||||
fun assignArgs() {
|
||||
fun assign(value: PtExpression, register: Char) {
|
||||
@@ -351,12 +268,18 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
TODO("callfar2: support non-const argument values")
|
||||
}
|
||||
|
||||
val jsrfar = when(targetName) {
|
||||
"cx16" -> "cx16.JSRFAR"
|
||||
"c64" -> "c64.x16jsrfar"
|
||||
"c128" -> "c128.x16jsrfar"
|
||||
else -> TODO("jsrfar routine")
|
||||
}
|
||||
val constBank = fcall.args[0].asConstInteger()
|
||||
val constAddress = fcall.args[1].asConstInteger()
|
||||
if(constBank!=null && constAddress!=null) {
|
||||
assignArgs()
|
||||
asmgen.out("""
|
||||
jsr cx16.JSRFAR
|
||||
jsr $jsrfar
|
||||
.word ${constAddress.toHex()}
|
||||
.byte $constBank""")
|
||||
} else {
|
||||
@@ -366,7 +289,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" sta (+)+0 | sty (+)+1")
|
||||
assignArgs()
|
||||
asmgen.out("""
|
||||
jsr cx16.JSRFAR
|
||||
jsr $jsrfar
|
||||
+ .word 0
|
||||
+ .byte 0""")
|
||||
}
|
||||
@@ -1357,22 +1280,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
private fun outputAddressAndLengthOfArray(arg: PtIdentifier) {
|
||||
// address goes in P8ZP_SCRATCH_W1, number of elements in A
|
||||
val numElements = when(val symbol = asmgen.symbolTable.lookup(arg.name)) {
|
||||
is StStaticVariable -> symbol.length!!
|
||||
is StMemVar -> symbol.length!!
|
||||
else -> 0
|
||||
}
|
||||
val identifierName = asmgen.asmVariableName(arg)
|
||||
asmgen.out("""
|
||||
lda #<$identifierName
|
||||
ldy #>$identifierName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #${numElements and 255}""")
|
||||
}
|
||||
|
||||
private fun translateArguments(call: PtBuiltinFunctionCall, scope: IPtSubroutine?) {
|
||||
val signature = BuiltinFunctions.getValue(call.name)
|
||||
val callConv = signature.callConvention(call.args.map { it.type})
|
||||
|
||||
@@ -15,6 +15,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
// just ignore any result values from the function call.
|
||||
}
|
||||
|
||||
// TODO tweak subroutine call convention to also make use of X register to pass a third byte?
|
||||
internal fun optimizeIntArgsViaRegisters(sub: PtSub) =
|
||||
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypesWithBoolean)
|
||||
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypesWithBoolean && sub.parameters[1].type in ByteDatatypesWithBoolean)
|
||||
@@ -40,7 +41,84 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
sub.children.forEach { asmgen.translate(it as PtInlineAssembly) }
|
||||
asmgen.out(" \t; inlined routine end: ${sub.name}")
|
||||
} else {
|
||||
asmgen.out(" jsr $subAsmName")
|
||||
val bank = sub.address?.constbank?.toString()
|
||||
if(bank==null) {
|
||||
val varbank = if(sub.address?.varbank==null) null else asmgen.asmVariableName(sub.address!!.varbank!!)
|
||||
if(varbank!=null) {
|
||||
when(asmgen.options.compTarget.name) {
|
||||
"cx16" -> {
|
||||
// JSRFAR can jump to a banked RAM address as well!
|
||||
asmgen.out("""
|
||||
php
|
||||
pha
|
||||
lda $varbank
|
||||
sta +
|
||||
pla
|
||||
plp
|
||||
jsr cx16.JSRFAR
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
+ .byte 0 ; modified"""
|
||||
)
|
||||
}
|
||||
"c64" -> {
|
||||
asmgen.out("""
|
||||
php
|
||||
pha
|
||||
lda $varbank
|
||||
sta +
|
||||
pla
|
||||
plp
|
||||
jsr c64.x16jsrfar
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
+ .byte 0 ; modified"""
|
||||
)
|
||||
}
|
||||
"c128" -> {
|
||||
asmgen.out("""
|
||||
php
|
||||
pha
|
||||
lda $varbank
|
||||
sta +
|
||||
pla
|
||||
plp
|
||||
jsr c128.x16jsrfar
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
+ .byte 0 ; modified"""
|
||||
)
|
||||
}
|
||||
else -> throw AssemblyError("callfar is not supported on the selected compilation target")
|
||||
}
|
||||
} else {
|
||||
asmgen.out(" jsr $subAsmName")
|
||||
}
|
||||
}
|
||||
else {
|
||||
when(asmgen.options.compTarget.name) {
|
||||
"cx16" -> {
|
||||
// JSRFAR can jump to a banked RAM address as well!
|
||||
asmgen.out("""
|
||||
jsr cx16.JSRFAR
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
.byte $bank"""
|
||||
)
|
||||
}
|
||||
"c64" -> {
|
||||
asmgen.out("""
|
||||
jsr c64.x16jsrfar
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
.byte $bank"""
|
||||
)
|
||||
}
|
||||
"c128" -> {
|
||||
asmgen.out("""
|
||||
jsr c128.x16jsrfar
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
.byte $bank"""
|
||||
)
|
||||
}
|
||||
else -> throw AssemblyError("callfar is not supported on the selected compilation target")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(sub is PtSub) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.code.StRomSub
|
||||
import prog8.code.StExtSub
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
@@ -17,7 +17,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
|
||||
fun translate(stmt: PtIfElse) {
|
||||
require(stmt.condition.type== DataType.BOOL)
|
||||
checkNotRomsubReturnsStatusReg(stmt.condition)
|
||||
checkNotExtsubReturnsStatusReg(stmt.condition)
|
||||
|
||||
val jumpAfterIf = stmt.ifScope.children.singleOrNull() as? PtJump
|
||||
|
||||
@@ -43,23 +43,105 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
|
||||
val prefixCond = stmt.condition as? PtPrefix
|
||||
if(prefixCond?.operator=="not") {
|
||||
checkNotRomsubReturnsStatusReg(prefixCond.value)
|
||||
assignConditionValueToRegisterAndTest(prefixCond.value)
|
||||
return if(jumpAfterIf!=null)
|
||||
translateJumpElseBodies("beq", "bne", jumpAfterIf, stmt.elseScope)
|
||||
else
|
||||
translateIfElseBodies("bne", stmt)
|
||||
if(stmt.hasElse())
|
||||
throw AssemblyError("not prefix in ifelse should have been replaced by swapped if-else blocks")
|
||||
else {
|
||||
checkNotExtsubReturnsStatusReg(prefixCond.value)
|
||||
assignConditionValueToRegisterAndTest(prefixCond.value)
|
||||
return if (jumpAfterIf != null)
|
||||
translateJumpElseBodies("beq", "bne", jumpAfterIf, stmt.elseScope)
|
||||
else
|
||||
translateIfElseBodies("bne", stmt)
|
||||
}
|
||||
}
|
||||
|
||||
throw AssemblyError("weird non-boolean condition node type ${stmt.condition} at ${stmt.condition.position}")
|
||||
}
|
||||
|
||||
private fun checkNotRomsubReturnsStatusReg(condition: PtExpression) {
|
||||
internal fun assignIfExpression(target: AsmAssignTarget, expr: PtIfExpression) {
|
||||
// this is NOT for the if-else STATEMENT, but this is code for the IF-EXPRESSION.
|
||||
require(target.datatype==expr.type)
|
||||
val falseLabel = asmgen.makeLabel("ifexpr_false")
|
||||
val endLabel = asmgen.makeLabel("ifexpr_end")
|
||||
evalConditonAndBranchWhenFalse(expr.condition, falseLabel)
|
||||
when(expr.type) {
|
||||
in ByteDatatypesWithBoolean -> {
|
||||
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.A, false)
|
||||
asmgen.jmp(endLabel)
|
||||
asmgen.out(falseLabel)
|
||||
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.A, false)
|
||||
asmgen.out(endLabel)
|
||||
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.AY, false)
|
||||
asmgen.jmp(endLabel)
|
||||
asmgen.out(falseLabel)
|
||||
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.AY, false)
|
||||
asmgen.out(endLabel)
|
||||
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.FAC1, true)
|
||||
asmgen.jmp(endLabel)
|
||||
asmgen.out(falseLabel)
|
||||
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.FAC1, true)
|
||||
asmgen.out(endLabel)
|
||||
asmgen.assignRegister(RegisterOrPair.FAC1, target)
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
|
||||
private fun evalConditonAndBranchWhenFalse(condition: PtExpression, falseLabel: String) {
|
||||
if (condition is PtBinaryExpression) {
|
||||
return when(condition.right.type) {
|
||||
in ByteDatatypesWithBoolean -> translateIfByteConditionBranch(condition, falseLabel)
|
||||
in WordDatatypes -> translateIfWordConditionBranch(condition, falseLabel)
|
||||
DataType.FLOAT -> translateFloatConditionBranch(condition, falseLabel)
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
else if(condition is PtPrefix && condition.operator=="not") {
|
||||
throw AssemblyError("not prefix in ifexpression should have been replaced by swapped values")
|
||||
} else {
|
||||
// 'simple' condition, check if it is a byte bittest
|
||||
val bittest = condition as? PtBuiltinFunctionCall
|
||||
if(bittest!=null && bittest.name.startsWith("prog8_ifelse_bittest_")) {
|
||||
val variable = bittest.args[0] as PtIdentifier
|
||||
val bitnumber = (bittest.args[1] as PtNumber).number.toInt()
|
||||
val testForBitSet = bittest.name.endsWith("_set")
|
||||
when (bitnumber) {
|
||||
7 -> {
|
||||
// test via bit + N flag
|
||||
asmgen.out(" bit ${variable.name}")
|
||||
if(testForBitSet) asmgen.out(" bpl $falseLabel")
|
||||
else asmgen.out(" bmi $falseLabel")
|
||||
return
|
||||
}
|
||||
6 -> {
|
||||
// test via bit + V flag
|
||||
asmgen.out(" bit ${variable.name}")
|
||||
if(testForBitSet) asmgen.out(" bvc $falseLabel")
|
||||
else asmgen.out(" bvs $falseLabel")
|
||||
return
|
||||
}
|
||||
else -> throw AssemblyError("prog8_ifelse_bittest can only work on bits 7 and 6")
|
||||
}
|
||||
}
|
||||
|
||||
// the condition is "simple" enough to just assign its 0/1 value to a register and branch on that
|
||||
assignConditionValueToRegisterAndTest(condition)
|
||||
asmgen.out(" beq $falseLabel")
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkNotExtsubReturnsStatusReg(condition: PtExpression) {
|
||||
val fcall = condition as? PtFunctionCall
|
||||
if(fcall!=null && fcall.type==DataType.BOOL) {
|
||||
val romsub = st.lookup(fcall.name) as? StRomSub
|
||||
if(romsub!=null && romsub.returns.any { it.register.statusflag!=null }) {
|
||||
throw AssemblyError("if romsub() that returns a status register boolean should have been changed into a Conditional branch such as if_cc")
|
||||
val extsub = st.lookup(fcall.name) as? StExtSub
|
||||
if(extsub!=null && extsub.returns.any { it.register.statusflag!=null }) {
|
||||
throw AssemblyError("if extsub() returning a status register boolean should have been changed into a Conditional branch such as if_cc")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,6 +155,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
is PtIrRegister,
|
||||
is PtArrayIndexer,
|
||||
is PtPrefix,
|
||||
is PtIfExpression,
|
||||
is PtBinaryExpression -> { /* no cmp necessary the lda has been done just prior */ }
|
||||
is PtTypeCast -> {
|
||||
if(condition.value.type !in ByteDatatypes && condition.value.type !in WordDatatypes)
|
||||
@@ -276,6 +359,230 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateIfByteConditionBranch(condition: PtBinaryExpression, falseLabel: String) {
|
||||
val signed = condition.left.type in SignedDatatypes
|
||||
val constValue = condition.right.asConstInteger()
|
||||
if(constValue==0) {
|
||||
return translateIfCompareWithZeroByteBranch(condition, signed, falseLabel)
|
||||
}
|
||||
|
||||
when(condition.operator) {
|
||||
"==" -> {
|
||||
// if X==value
|
||||
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
|
||||
cmpAwithByteValue(condition.right, false)
|
||||
asmgen.out(" bne $falseLabel")
|
||||
}
|
||||
"!=" -> {
|
||||
// if X!=value
|
||||
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
|
||||
cmpAwithByteValue(condition.right, false)
|
||||
asmgen.out(" beq $falseLabel")
|
||||
}
|
||||
in LogicalOperators -> {
|
||||
val regAtarget = AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.BOOL, condition.definingISub(), condition.position, register=RegisterOrPair.A)
|
||||
if (assignmentAsmGen.optimizedLogicalExpr(condition, regAtarget)) {
|
||||
asmgen.out(" beq $falseLabel")
|
||||
} else {
|
||||
errors.warn("SLOW FALLBACK FOR 'IFEXPR' CODEGEN - ask for support", condition.position) // should not occur ;-)
|
||||
assignConditionValueToRegisterAndTest(condition)
|
||||
asmgen.out(" beq $falseLabel")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// TODO don't store condition as expression result but just use the flags, like a normal PtIfElse translation does
|
||||
// TODO: special cases for <, <=, >, >= above.
|
||||
assignConditionValueToRegisterAndTest(condition)
|
||||
asmgen.out(" beq $falseLabel")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateIfWordConditionBranch(condition: PtBinaryExpression, falseLabel: String) {
|
||||
// TODO can we reuse this whole thing from IfElse ?
|
||||
val constValue = condition.right.asConstInteger()
|
||||
if(constValue!=null) {
|
||||
if (constValue == 0) {
|
||||
when (condition.operator) {
|
||||
"==" -> return translateWordExprIsZero(condition.left, falseLabel)
|
||||
"!=" -> return translateWordExprIsNotZero(condition.left, falseLabel)
|
||||
}
|
||||
}
|
||||
if (constValue != 0) {
|
||||
when (condition.operator) {
|
||||
"==" -> return translateWordExprEqualsNumber(condition.left, constValue, falseLabel)
|
||||
"!=" -> return translateWordExprNotEqualsNumber(condition.left, constValue, falseLabel)
|
||||
}
|
||||
}
|
||||
}
|
||||
val variable = condition.right as? PtIdentifier
|
||||
if(variable!=null) {
|
||||
when (condition.operator) {
|
||||
"==" -> return translateWordExprEqualsVariable(condition.left, variable, falseLabel)
|
||||
"!=" -> return translateWordExprNotEqualsVariable(condition.left, variable, falseLabel)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO don't store condition as expression result but just use the flags, like a normal PtIfElse translation does
|
||||
assignConditionValueToRegisterAndTest(condition)
|
||||
asmgen.out(" beq $falseLabel")
|
||||
}
|
||||
|
||||
private fun translateWordExprNotEqualsVariable(expr: PtExpression, variable: PtIdentifier, falseLabel: String) {
|
||||
// if w!=variable
|
||||
// TODO reuse code from ifElse?
|
||||
val varRight = asmgen.asmVariableName(variable)
|
||||
if(expr is PtIdentifier) {
|
||||
val varLeft = asmgen.asmVariableName(expr)
|
||||
asmgen.out("""
|
||||
lda $varLeft
|
||||
cmp $varRight
|
||||
bne +
|
||||
lda $varLeft+1
|
||||
cmp $varRight+1
|
||||
beq $falseLabel
|
||||
+""")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
|
||||
asmgen.out("""
|
||||
cmp $varRight
|
||||
bne +
|
||||
cpy $varRight+1
|
||||
beq $falseLabel
|
||||
+""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateWordExprEqualsVariable(expr: PtExpression, variable: PtIdentifier, falseLabel: String) {
|
||||
// if w==variable
|
||||
// TODO reuse code from ifElse?
|
||||
val varRight = asmgen.asmVariableName(variable)
|
||||
if(expr is PtIdentifier) {
|
||||
val varLeft = asmgen.asmVariableName(expr)
|
||||
asmgen.out("""
|
||||
lda $varLeft
|
||||
cmp $varRight
|
||||
bne $falseLabel
|
||||
lda $varLeft+1
|
||||
cmp $varRight+1
|
||||
bne $falseLabel""")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
|
||||
asmgen.out("""
|
||||
cmp $varRight
|
||||
bne $falseLabel
|
||||
cpy $varRight+1
|
||||
bne $falseLabel""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateWordExprNotEqualsNumber(expr: PtExpression, number: Int, falseLabel: String) {
|
||||
// if w!=number
|
||||
// TODO reuse code from ifElse?
|
||||
if(expr is PtIdentifier) {
|
||||
val varname = asmgen.asmVariableName(expr)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp #<$number
|
||||
bne +
|
||||
lda $varname+1
|
||||
cmp #>$number
|
||||
beq $falseLabel
|
||||
+""")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
|
||||
asmgen.out("""
|
||||
cmp #<$number
|
||||
bne +
|
||||
cpy #>$number
|
||||
beq $falseLabel
|
||||
+""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateWordExprEqualsNumber(expr: PtExpression, number: Int, falseLabel: String) {
|
||||
// if w==number
|
||||
// TODO reuse code from ifElse?
|
||||
if(expr is PtIdentifier) {
|
||||
val varname = asmgen.asmVariableName(expr)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp #<$number
|
||||
bne $falseLabel
|
||||
lda $varname+1
|
||||
cmp #>$number
|
||||
bne $falseLabel""")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
|
||||
asmgen.out( """
|
||||
cmp #<$number
|
||||
bne $falseLabel
|
||||
cpy #>$number
|
||||
bne $falseLabel""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateWordExprIsNotZero(expr: PtExpression, falseLabel: String) {
|
||||
// if w!=0
|
||||
// TODO reuse code from ifElse?
|
||||
if(expr is PtIdentifier) {
|
||||
val varname = asmgen.asmVariableName(expr)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
ora $varname+1
|
||||
beq $falseLabel""")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
|
||||
asmgen.out(" sty P8ZP_SCRATCH_REG | ora P8ZP_SCRATCH_REG | beq $falseLabel")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateWordExprIsZero(expr: PtExpression, falseLabel: String) {
|
||||
// if w==0
|
||||
// TODO reuse code from ifElse?
|
||||
if(expr is PtIdentifier) {
|
||||
val varname = asmgen.asmVariableName(expr)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
ora $varname+1
|
||||
bne $falseLabel""")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
|
||||
asmgen.out(" sty P8ZP_SCRATCH_REG | ora P8ZP_SCRATCH_REG | bne $falseLabel")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateIfCompareWithZeroByteBranch(condition: PtBinaryExpression, signed: Boolean, falseLabel: String) {
|
||||
// optimized code for byte comparisons with 0
|
||||
assignConditionValueToRegisterAndTest(condition.left)
|
||||
when (condition.operator) {
|
||||
"==" -> asmgen.out(" bne $falseLabel")
|
||||
"!=" -> asmgen.out(" beq $falseLabel")
|
||||
">" -> {
|
||||
if(signed) asmgen.out(" bmi $falseLabel | beq $falseLabel")
|
||||
else asmgen.out(" beq $falseLabel")
|
||||
}
|
||||
">=" -> {
|
||||
if(signed) asmgen.out(" bmi $falseLabel")
|
||||
else { /* always true for unsigned */ }
|
||||
}
|
||||
"<" -> {
|
||||
if(signed) asmgen.out(" bpl $falseLabel")
|
||||
else asmgen.jmp(falseLabel)
|
||||
}
|
||||
"<=" -> {
|
||||
if(signed) {
|
||||
// inverted '>'
|
||||
asmgen.out("""
|
||||
beq +
|
||||
bpl $falseLabel
|
||||
+""")
|
||||
} else asmgen.out(" bne $falseLabel")
|
||||
}
|
||||
else -> throw AssemblyError("expected comparison operator")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateIfCompareWithZeroByte(stmt: PtIfElse, signed: Boolean, jumpAfterIf: PtJump?) {
|
||||
// optimized code for byte comparisons with 0
|
||||
val condition = stmt.condition as PtBinaryExpression
|
||||
@@ -1741,6 +2048,51 @@ _jump jmp ($asmLabel)
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateFloatConditionBranch(condition: PtBinaryExpression, elseLabel: String) {
|
||||
val constValue = (condition.right as? PtNumber)?.number
|
||||
if(constValue==0.0) {
|
||||
if (condition.operator == "==") {
|
||||
// if FL==0.0
|
||||
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.FAC1, true)
|
||||
asmgen.out(" jsr floats.SIGN | cmp #0 | bne $elseLabel")
|
||||
return
|
||||
} else if(condition.operator=="!=") {
|
||||
// if FL!=0.0
|
||||
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.FAC1, true)
|
||||
asmgen.out(" jsr floats.SIGN | cmp #0 | beq $elseLabel")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
when(condition.operator) {
|
||||
"==" -> {
|
||||
translateFloatsEqualsConditionIntoA(condition.left, condition.right)
|
||||
asmgen.out(" beq $elseLabel")
|
||||
}
|
||||
"!=" -> {
|
||||
translateFloatsEqualsConditionIntoA(condition.left, condition.right)
|
||||
asmgen.out(" bne $elseLabel")
|
||||
}
|
||||
"<" -> {
|
||||
translateFloatsLessConditionIntoA(condition.left, condition.right, false)
|
||||
asmgen.out(" beq $elseLabel")
|
||||
}
|
||||
"<=" -> {
|
||||
translateFloatsLessConditionIntoA(condition.left, condition.right, true)
|
||||
asmgen.out(" beq $elseLabel")
|
||||
}
|
||||
">" -> {
|
||||
translateFloatsLessConditionIntoA(condition.left, condition.right, true)
|
||||
asmgen.out(" bne $elseLabel")
|
||||
}
|
||||
">=" -> {
|
||||
translateFloatsLessConditionIntoA(condition.left, condition.right, false)
|
||||
asmgen.out(" bne $elseLabel")
|
||||
}
|
||||
else -> throw AssemblyError("expected comparison operator")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateFloatsEqualsConditionIntoA(left: PtExpression, right: PtExpression) {
|
||||
fun equalf(leftName: String, rightName: String) {
|
||||
asmgen.out("""
|
||||
|
||||
@@ -32,10 +32,6 @@ internal class ProgramAndVarsGen(
|
||||
|
||||
internal fun generate() {
|
||||
header()
|
||||
val allBlocks = program.allBlocks()
|
||||
|
||||
if(allBlocks.first().name != "p8b_main" && allBlocks.first().name != "main")
|
||||
throw AssemblyError("first block should be 'main' or 'p8b_main'")
|
||||
|
||||
if(errors.noErrors()) {
|
||||
program.allBlocks().forEach { block2asm(it) }
|
||||
@@ -75,6 +71,12 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out("P8ZP_SCRATCH_REG = ${zp.SCRATCH_REG}")
|
||||
asmgen.out("P8ZP_SCRATCH_W1 = ${zp.SCRATCH_W1} ; word")
|
||||
asmgen.out("P8ZP_SCRATCH_W2 = ${zp.SCRATCH_W2} ; word")
|
||||
if(compTarget.name=="c64") {
|
||||
if(options.floats)
|
||||
asmgen.out("PROG8_C64_BANK_CONFIG=31 ; basic+IO+kernal")
|
||||
else
|
||||
asmgen.out("PROG8_C64_BANK_CONFIG=30 ; IO+kernal, no basic")
|
||||
}
|
||||
asmgen.out(".weak") // hack to allow user to override the following two with command line redefinition (however, just use '-esa' command line option instead!)
|
||||
asmgen.out(".endweak")
|
||||
|
||||
@@ -89,12 +91,13 @@ internal class ProgramAndVarsGen(
|
||||
OutputType.RAW -> {
|
||||
asmgen.out("; ---- raw assembler program ----")
|
||||
asmgen.out("* = ${options.loadAddress.toHex()}")
|
||||
asmgen.out("prog8_program_start\t; start of program label")
|
||||
asmgen.out(" cld")
|
||||
asmgen.out(" tsx ; save stackpointer for sys.exit()")
|
||||
asmgen.out(" stx prog8_lib.orig_stackpointer")
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jsr sys.init_system")
|
||||
asmgen.out(" jsr sys.init_system_phase2")
|
||||
asmgen.out(" jsr p8_sys_startup.init_system")
|
||||
asmgen.out(" jsr p8_sys_startup.init_system_phase2")
|
||||
}
|
||||
OutputType.PRG -> {
|
||||
when(options.launcher) {
|
||||
@@ -104,40 +107,43 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
asmgen.out("; ---- basic program with sys call ----")
|
||||
asmgen.out("* = ${options.loadAddress.toHex()}")
|
||||
asmgen.out("prog8_program_start\t; start of program label")
|
||||
val year = LocalDate.now().year
|
||||
asmgen.out(" .word (+), $year")
|
||||
asmgen.out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'")
|
||||
asmgen.out("+\t.word 0")
|
||||
asmgen.out("prog8_entrypoint\t; assembly code starts here")
|
||||
asmgen.out("prog8_entrypoint")
|
||||
asmgen.out(" cld")
|
||||
asmgen.out(" tsx ; save stackpointer for sys.exit()")
|
||||
asmgen.out(" stx prog8_lib.orig_stackpointer")
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jsr sys.init_system")
|
||||
asmgen.out(" jsr sys.init_system_phase2")
|
||||
asmgen.out(" jsr p8_sys_startup.init_system")
|
||||
asmgen.out(" jsr p8_sys_startup.init_system_phase2")
|
||||
}
|
||||
CbmPrgLauncherType.NONE -> {
|
||||
// this is the same as RAW
|
||||
asmgen.out("; ---- program without basic sys call ----")
|
||||
asmgen.out("* = ${options.loadAddress.toHex()}")
|
||||
asmgen.out("prog8_program_start\t; start of program label")
|
||||
asmgen.out(" cld")
|
||||
asmgen.out(" tsx ; save stackpointer for sys.exit()")
|
||||
asmgen.out(" stx prog8_lib.orig_stackpointer")
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jsr sys.init_system")
|
||||
asmgen.out(" jsr sys.init_system_phase2")
|
||||
asmgen.out(" jsr p8_sys_startup.init_system")
|
||||
asmgen.out(" jsr p8_sys_startup.init_system_phase2")
|
||||
}
|
||||
}
|
||||
}
|
||||
OutputType.XEX -> {
|
||||
asmgen.out("; ---- atari xex program ----")
|
||||
asmgen.out("* = ${options.loadAddress.toHex()}")
|
||||
asmgen.out("prog8_program_start\t; start of program label")
|
||||
asmgen.out(" cld")
|
||||
asmgen.out(" tsx ; save stackpointer for sys.exit()")
|
||||
asmgen.out(" stx prog8_lib.orig_stackpointer")
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jsr sys.init_system")
|
||||
asmgen.out(" jsr sys.init_system_phase2")
|
||||
asmgen.out(" jsr p8_sys_startup.init_system")
|
||||
asmgen.out(" jsr p8_sys_startup.init_system_phase2")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,19 +161,19 @@ internal class ProgramAndVarsGen(
|
||||
if(options.floats)
|
||||
asmgen.out(" lda #4 | sta $01") // to use floats, make sure Basic rom is banked in
|
||||
asmgen.out(" jsr p8b_main.p8s_start")
|
||||
asmgen.out(" jmp sys.cleanup_at_exit")
|
||||
asmgen.out(" jmp p8_sys_startup.cleanup_at_exit")
|
||||
}
|
||||
"c64" -> {
|
||||
asmgen.out(" jsr p8b_main.p8s_start | lda #31 | sta $01")
|
||||
asmgen.out(" jmp sys.cleanup_at_exit")
|
||||
asmgen.out(" jsr p8b_main.p8s_start")
|
||||
asmgen.out(" jmp p8_sys_startup.cleanup_at_exit")
|
||||
}
|
||||
"c128" -> {
|
||||
asmgen.out(" jsr p8b_main.p8s_start | lda #0 | sta ${"$"}ff00")
|
||||
asmgen.out(" jmp sys.cleanup_at_exit")
|
||||
asmgen.out(" jsr p8b_main.p8s_start")
|
||||
asmgen.out(" jmp p8_sys_startup.cleanup_at_exit")
|
||||
}
|
||||
else -> {
|
||||
asmgen.out(" jsr p8b_main.p8s_start")
|
||||
asmgen.out(" jmp sys.cleanup_at_exit")
|
||||
asmgen.out(" jmp p8_sys_startup.cleanup_at_exit")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,6 +290,8 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out(" .cerror * > ${relocatedBssEnd.toHex()}, \"too many data for slabs_BSS section\"")
|
||||
}
|
||||
}
|
||||
asmgen.out(" ; memtop check")
|
||||
asmgen.out(" .cerror * >= ${options.memtopAddress.toHex()}, \"Program too long by \", * - ${(options.memtopAddress-1u).toHex()}, \" bytes, memtop=${options.memtopAddress.toHex()}\"")
|
||||
}
|
||||
|
||||
private fun block2asm(block: PtBlock) {
|
||||
@@ -291,12 +299,6 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out("; ---- block: '${block.name}' ----")
|
||||
if(block.options.address!=null)
|
||||
asmgen.out("* = ${block.options.address!!.toHex()}")
|
||||
else {
|
||||
if(block.options.alignment==PtBlock.BlockAlignment.WORD)
|
||||
asmgen.out("\t.align 2")
|
||||
else if(block.options.alignment==PtBlock.BlockAlignment.PAGE)
|
||||
asmgen.out("\t.align $100")
|
||||
}
|
||||
|
||||
asmgen.out("${block.name}\t" + (if(block.options.forceOutput) ".block" else ".proc"))
|
||||
asmgen.outputSourceLine(block)
|
||||
@@ -471,6 +473,7 @@ internal class ProgramAndVarsGen(
|
||||
|
||||
private fun entrypointInitialization() {
|
||||
// zero out the BSS area first, before setting the variable init values
|
||||
// this is mainly to make sure the arrays are all zero'd out at program startup
|
||||
asmgen.out(" jsr prog8_lib.program_startup_clear_bss")
|
||||
|
||||
// initialize block-level (global) variables at program start
|
||||
@@ -516,12 +519,12 @@ internal class ProgramAndVarsGen(
|
||||
|
||||
stringVarsWithInitInZp.forEach {
|
||||
val varname = asmgen.asmVariableName(it.name)+"_init_value"
|
||||
outputStringvar(varname, it.value.second, it.value.first)
|
||||
outputStringvar(varname, 0, it.value.second, it.value.first)
|
||||
}
|
||||
|
||||
arrayVarsWithInitInZp.forEach {
|
||||
val varname = asmgen.asmVariableName(it.name)+"_init_value"
|
||||
arrayVariable2asm(varname, it.alloc.dt, it.value, null)
|
||||
arrayVariable2asm(varname, it.alloc.dt, 0, it.value, null)
|
||||
}
|
||||
|
||||
asmgen.out("+")
|
||||
@@ -545,8 +548,8 @@ internal class ProgramAndVarsGen(
|
||||
for (variable in vars) {
|
||||
val scopedName = variable.key
|
||||
val svar = symboltable.lookup(scopedName) as? StStaticVariable
|
||||
if(svar?.onetimeInitializationStringValue!=null)
|
||||
result.add(ZpStringWithInitial(scopedName, variable.value, svar.onetimeInitializationStringValue!!))
|
||||
if(svar?.initializationStringValue!=null)
|
||||
result.add(ZpStringWithInitial(scopedName, variable.value, svar.initializationStringValue!!))
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -557,8 +560,8 @@ internal class ProgramAndVarsGen(
|
||||
for (variable in vars) {
|
||||
val scopedName = variable.key
|
||||
val svar = symboltable.lookup(scopedName) as? StStaticVariable
|
||||
if(svar?.onetimeInitializationArrayValue!=null)
|
||||
result.add(ZpArrayWithInitial(scopedName, variable.value, svar.onetimeInitializationArrayValue!!))
|
||||
if(svar?.initializationArrayValue!=null)
|
||||
result.add(ZpArrayWithInitial(scopedName, variable.value, svar.initializationArrayValue!!))
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -586,7 +589,11 @@ internal class ProgramAndVarsGen(
|
||||
if(varsNoInit.isNotEmpty()) {
|
||||
asmgen.out("; non-zeropage variables")
|
||||
asmgen.out(" .section BSS")
|
||||
varsNoInit.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt }).forEach {
|
||||
val (notAligned, aligned) = varsNoInit.partition { it.align==0 }
|
||||
notAligned.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt }).forEach {
|
||||
uninitializedVariable2asm(it)
|
||||
}
|
||||
aligned.sortedWith(compareBy<StStaticVariable> { it.align }.thenBy { it.name }.thenBy { it.dt }).forEach {
|
||||
uninitializedVariable2asm(it)
|
||||
}
|
||||
asmgen.out(" .send BSS")
|
||||
@@ -595,14 +602,29 @@ internal class ProgramAndVarsGen(
|
||||
if(varsWithInit.isNotEmpty()) {
|
||||
asmgen.out("; non-zeropage variables with init value")
|
||||
val (stringvars, othervars) = varsWithInit.sortedBy { it.name }.partition { it.dt == DataType.STR }
|
||||
stringvars.forEach {
|
||||
val (notAlignedStrings, alignedStrings) = stringvars.partition { it.align==0 }
|
||||
val (notAlignedOther, alignedOther) = othervars.partition { it.align==0 }
|
||||
notAlignedStrings.forEach {
|
||||
outputStringvar(
|
||||
it.name,
|
||||
it.onetimeInitializationStringValue!!.second,
|
||||
it.onetimeInitializationStringValue!!.first
|
||||
it.align,
|
||||
it.initializationStringValue!!.second,
|
||||
it.initializationStringValue!!.first
|
||||
)
|
||||
}
|
||||
othervars.sortedBy { it.type }.forEach {
|
||||
alignedStrings.sortedBy { it.align }.forEach {
|
||||
outputStringvar(
|
||||
it.name,
|
||||
it.align,
|
||||
it.initializationStringValue!!.second,
|
||||
it.initializationStringValue!!.first
|
||||
)
|
||||
}
|
||||
|
||||
notAlignedOther.sortedBy { it.type }.forEach {
|
||||
staticVariable2asm(it)
|
||||
}
|
||||
alignedOther.sortedBy { it.align }.sortedBy { it.type }.forEach {
|
||||
staticVariable2asm(it)
|
||||
}
|
||||
}
|
||||
@@ -616,11 +638,13 @@ internal class ProgramAndVarsGen(
|
||||
DataType.WORD -> asmgen.out("${variable.name}\t.sint ?")
|
||||
DataType.FLOAT -> asmgen.out("${variable.name}\t.fill ${compTarget.machine.FLOAT_MEM_SIZE}")
|
||||
in SplitWordArrayTypes -> {
|
||||
alignVar(variable.align)
|
||||
val numbytesPerHalf = compTarget.memorySize(variable.dt, variable.length!!) / 2
|
||||
asmgen.out("${variable.name}_lsb\t.fill $numbytesPerHalf")
|
||||
asmgen.out("${variable.name}_msb\t.fill $numbytesPerHalf")
|
||||
}
|
||||
in ArrayDatatypes -> {
|
||||
alignVar(variable.align)
|
||||
val numbytes = compTarget.memorySize(variable.dt, variable.length!!)
|
||||
asmgen.out("${variable.name}\t.fill $numbytes")
|
||||
}
|
||||
@@ -630,13 +654,18 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
}
|
||||
|
||||
private fun alignVar(align: Int) {
|
||||
if(align > 1)
|
||||
asmgen.out(" .align ${align.toHex()}")
|
||||
}
|
||||
|
||||
private fun staticVariable2asm(variable: StStaticVariable) {
|
||||
val initialValue: Number =
|
||||
if(variable.onetimeInitializationNumericValue!=null) {
|
||||
if(variable.initializationNumericValue!=null) {
|
||||
if(variable.dt== DataType.FLOAT)
|
||||
variable.onetimeInitializationNumericValue!!
|
||||
variable.initializationNumericValue!!
|
||||
else
|
||||
variable.onetimeInitializationNumericValue!!.toInt()
|
||||
variable.initializationNumericValue!!.toInt()
|
||||
} else 0
|
||||
|
||||
when (variable.dt) {
|
||||
@@ -655,14 +684,17 @@ internal class ProgramAndVarsGen(
|
||||
DataType.STR -> {
|
||||
throw AssemblyError("all string vars should have been interned into prog")
|
||||
}
|
||||
in ArrayDatatypes -> arrayVariable2asm(variable.name, variable.dt, variable.onetimeInitializationArrayValue, variable.length)
|
||||
in ArrayDatatypes -> {
|
||||
arrayVariable2asm(variable.name, variable.dt, variable.align, variable.initializationArrayValue, variable.length)
|
||||
}
|
||||
else -> {
|
||||
throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun arrayVariable2asm(varname: String, dt: DataType, value: StArray?, orNumberOfZeros: Int?) {
|
||||
private fun arrayVariable2asm(varname: String, dt: DataType, align: Int, value: StArray?, orNumberOfZeros: Int?) {
|
||||
alignVar(align)
|
||||
when(dt) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_BOOL -> {
|
||||
val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
|
||||
@@ -754,11 +786,16 @@ internal class ProgramAndVarsGen(
|
||||
.filter { it is PtAsmSub && it.address!=null }
|
||||
.forEach { asmsub ->
|
||||
asmsub as PtAsmSub
|
||||
asmgen.out(" ${asmsub.name} = ${asmsub.address!!.toHex()}")
|
||||
val address = asmsub.address!!
|
||||
val bank = if(address.constbank!=null) "; @bank ${address.constbank}"
|
||||
else if(address.varbank!=null) "; @bank ${address.varbank?.name}"
|
||||
else ""
|
||||
asmgen.out(" ${asmsub.name} = ${address.address.toHex()} $bank")
|
||||
}
|
||||
}
|
||||
|
||||
private fun outputStringvar(varname: String, encoding: Encoding, value: String) {
|
||||
private fun outputStringvar(varname: String, align: Int, encoding: Encoding, value: String) {
|
||||
alignVar(align)
|
||||
asmgen.out("$varname\t; $encoding:\"${value.escape().replace("\u0000", "<NULL>")}\"", false)
|
||||
val bytes = compTarget.encodeString(value, encoding).plus(0.toUByte())
|
||||
val outputBytes = bytes.map { "$" + it.toString(16).padStart(2, '0') }
|
||||
|
||||
@@ -49,8 +49,8 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
val varsRequiringZp = allVariables.filter { it.zpwish == ZeropageWish.REQUIRE_ZEROPAGE }
|
||||
val varsPreferringZp = allVariables.filter { it.zpwish == ZeropageWish.PREFER_ZEROPAGE }
|
||||
val varsNotZp = allVariables.filter { it.zpwish == ZeropageWish.NOT_IN_ZEROPAGE }
|
||||
val varsDontCare = allVariables.filter { it.zpwish == ZeropageWish.DONTCARE }
|
||||
require(varsDontCare.size + varsRequiringZp.size + varsPreferringZp.size + varsNotZp.size == numberOfAllocatableVariables)
|
||||
val (varsDontCareWithoutAlignment, varsDontCareWithAlignment) = allVariables.filter { it.zpwish == ZeropageWish.DONTCARE }.partition { it.align == 0 }
|
||||
require(varsDontCareWithAlignment.size + varsDontCareWithoutAlignment.size + varsRequiringZp.size + varsPreferringZp.size + varsNotZp.size == numberOfAllocatableVariables)
|
||||
|
||||
var numVariablesAllocatedInZP = 0
|
||||
var numberOfNonIntegerVariables = 0
|
||||
@@ -89,7 +89,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
// try to allocate the "don't care" interger variables into the zeropage until it is full.
|
||||
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...?
|
||||
if(errors.noErrors()) {
|
||||
val sortedList = varsDontCare.sortedByDescending { it.scopedName }
|
||||
val sortedList = varsDontCareWithoutAlignment.sortedByDescending { it.scopedName }
|
||||
for (variable in sortedList) {
|
||||
if(variable.dt in IntegerDatatypesWithBoolean) {
|
||||
if(zeropage.free.isEmpty()) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package prog8.codegen.cpu6502.assignment
|
||||
|
||||
import prog8.code.StMemVar
|
||||
import prog8.code.StRomSub
|
||||
import prog8.code.StRomSubParameter
|
||||
import prog8.code.StExtSub
|
||||
import prog8.code.StExtSubParameter
|
||||
import prog8.code.StStaticVariable
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
@@ -39,7 +39,7 @@ internal class AssignmentAsmGen(
|
||||
val values = assignment.value as? PtFunctionCall
|
||||
?: throw AssemblyError("only function calls can return multiple values in a multi-assign")
|
||||
|
||||
val sub = asmgen.symbolTable.lookup(values.name) as? StRomSub
|
||||
val sub = asmgen.symbolTable.lookup(values.name) as? StExtSub
|
||||
?: throw AssemblyError("only asmsubs can return multiple values")
|
||||
|
||||
require(sub.returns.size>=2)
|
||||
@@ -64,11 +64,11 @@ internal class AssignmentAsmGen(
|
||||
}
|
||||
|
||||
private fun assignStatusFlagsAndRegistersResults(
|
||||
statusFlagResults: List<Pair<StRomSubParameter, PtNode>>,
|
||||
registersResults: List<Pair<StRomSubParameter, PtNode>>
|
||||
statusFlagResults: List<Pair<StExtSubParameter, PtNode>>,
|
||||
registersResults: List<Pair<StExtSubParameter, PtNode>>
|
||||
) {
|
||||
|
||||
fun needsToSaveA(registersResults: List<Pair<StRomSubParameter, PtNode>>): Boolean =
|
||||
fun needsToSaveA(registersResults: List<Pair<StExtSubParameter, PtNode>>): Boolean =
|
||||
if(registersResults.isEmpty())
|
||||
false
|
||||
else if(registersResults.all { (it.second as PtAssignTarget).identifier!=null})
|
||||
@@ -91,12 +91,12 @@ internal class AssignmentAsmGen(
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignOnlyTheStatusFlagsResults(saveA: Boolean, statusFlagResults: List<Pair<StRomSubParameter, PtNode>>) {
|
||||
private fun assignOnlyTheStatusFlagsResults(saveA: Boolean, statusFlagResults: List<Pair<StExtSubParameter, PtNode>>) {
|
||||
// assigning flags to their variables targets requires load-instructions that destroy flags
|
||||
// so if there's more than 1, we need to save and restore the flags
|
||||
val saveFlags = statusFlagResults.size>1
|
||||
|
||||
fun hasFlag(statusFlagResults: List<Pair<StRomSubParameter, PtNode>>, flag: Statusflag): PtAssignTarget? {
|
||||
fun hasFlag(statusFlagResults: List<Pair<StExtSubParameter, PtNode>>, flag: Statusflag): PtAssignTarget? {
|
||||
for ((returns, target) in statusFlagResults) {
|
||||
if(returns.register.statusflag!! == flag)
|
||||
return target as PtAssignTarget
|
||||
@@ -121,7 +121,7 @@ internal class AssignmentAsmGen(
|
||||
if(saveA) asmgen.out(" pla")
|
||||
}
|
||||
|
||||
private fun assignRegisterResults(registersResults: List<Pair<StRomSubParameter, PtNode>>) {
|
||||
private fun assignRegisterResults(registersResults: List<Pair<StExtSubParameter, PtNode>>) {
|
||||
registersResults.forEach { (returns, target) ->
|
||||
target as PtAssignTarget
|
||||
if(!target.void) {
|
||||
@@ -639,6 +639,7 @@ internal class AssignmentAsmGen(
|
||||
throw AssemblyError("Expression is too complex to translate into assembly. Split it up into several separate statements, introduce a temporary variable, or otherwise rewrite it. Location: $pos")
|
||||
}
|
||||
}
|
||||
is PtIfExpression -> asmgen.assignIfExpression(assign.target, value)
|
||||
else -> throw AssemblyError("weird assignment value type $value")
|
||||
}
|
||||
}
|
||||
@@ -935,7 +936,7 @@ internal class AssignmentAsmGen(
|
||||
if(!directIntoY(expr.right)) asmgen.out(" pha")
|
||||
assignExpressionToRegister(expr.right, RegisterOrPair.Y, false)
|
||||
if(!directIntoY(expr.right)) asmgen.out(" pla")
|
||||
asmgen.out(" jsr math.divmod_ub_asm")
|
||||
asmgen.out(" jsr prog8_math.divmod_ub_asm")
|
||||
if(target.register==RegisterOrPair.A)
|
||||
asmgen.out(" cmp #0") // fix the status register
|
||||
else
|
||||
@@ -944,7 +945,7 @@ internal class AssignmentAsmGen(
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out(" jsr math.divmod_uw_asm")
|
||||
asmgen.out(" jsr prog8_math.divmod_uw_asm")
|
||||
assignVariableWord(target, "P8ZP_SCRATCH_W2", DataType.UWORD)
|
||||
return true
|
||||
}
|
||||
@@ -960,7 +961,7 @@ internal class AssignmentAsmGen(
|
||||
if(!directIntoY(expr.right)) asmgen.out(" pha")
|
||||
assignExpressionToRegister(expr.right, RegisterOrPair.Y, false)
|
||||
if(!directIntoY(expr.right)) asmgen.out(" pla")
|
||||
asmgen.out(" jsr math.divmod_ub_asm")
|
||||
asmgen.out(" jsr prog8_math.divmod_ub_asm")
|
||||
assignRegisterByte(target, CpuRegister.Y, false, true)
|
||||
return true
|
||||
}
|
||||
@@ -969,19 +970,19 @@ internal class AssignmentAsmGen(
|
||||
if(!directIntoY(expr.right)) asmgen.out(" pha")
|
||||
assignExpressionToRegister(expr.right, RegisterOrPair.Y, true)
|
||||
if(!directIntoY(expr.right)) asmgen.out(" pla")
|
||||
asmgen.out(" jsr math.divmod_b_asm")
|
||||
asmgen.out(" jsr prog8_math.divmod_b_asm")
|
||||
assignRegisterByte(target, CpuRegister.Y, true, true)
|
||||
return true
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out(" jsr math.divmod_uw_asm")
|
||||
asmgen.out(" jsr prog8_math.divmod_uw_asm")
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return true
|
||||
}
|
||||
DataType.WORD -> {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out(" jsr math.divmod_w_asm")
|
||||
asmgen.out(" jsr prog8_math.divmod_w_asm")
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return true
|
||||
}
|
||||
@@ -998,7 +999,7 @@ internal class AssignmentAsmGen(
|
||||
if(!directIntoY(expr.right)) asmgen.out(" pha")
|
||||
assignExpressionToRegister(expr.right, RegisterOrPair.Y, expr.type in SignedDatatypes)
|
||||
if(!directIntoY(expr.right)) asmgen.out(" pla")
|
||||
asmgen.out(" jsr math.multiply_bytes")
|
||||
asmgen.out(" jsr prog8_math.multiply_bytes")
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
return true
|
||||
}
|
||||
@@ -1021,8 +1022,8 @@ internal class AssignmentAsmGen(
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return true
|
||||
} else {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "math.multiply_words.multiplier")
|
||||
asmgen.out(" jsr math.multiply_words")
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "prog8_math.multiply_words.multiplier")
|
||||
asmgen.out(" jsr prog8_math.multiply_words")
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
}
|
||||
return true
|
||||
@@ -1034,16 +1035,16 @@ internal class AssignmentAsmGen(
|
||||
in ByteDatatypes -> {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.A, expr.type in SignedDatatypes)
|
||||
if (value in asmgen.optimizedByteMultiplications)
|
||||
asmgen.out(" jsr math.mul_byte_${value}")
|
||||
asmgen.out(" jsr prog8_math.mul_byte_${value}")
|
||||
else
|
||||
asmgen.out(" ldy #$value | jsr math.multiply_bytes")
|
||||
asmgen.out(" ldy #$value | jsr prog8_math.multiply_bytes")
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
return true
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
if (value in asmgen.optimizedWordMultiplications) {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.type in SignedDatatypes)
|
||||
asmgen.out(" jsr math.mul_word_${value}")
|
||||
asmgen.out(" jsr prog8_math.mul_word_${value}")
|
||||
}
|
||||
else {
|
||||
if(expr.definingBlock()!!.options.veraFxMuls){
|
||||
@@ -1054,8 +1055,8 @@ internal class AssignmentAsmGen(
|
||||
sty cx16.r0+1
|
||||
jsr verafx.muls""")
|
||||
} else {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "math.multiply_words.multiplier")
|
||||
asmgen.out(" jsr math.multiply_words")
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "prog8_math.multiply_words.multiplier")
|
||||
asmgen.out(" jsr prog8_math.multiply_words")
|
||||
}
|
||||
}
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
@@ -1092,11 +1093,11 @@ internal class AssignmentAsmGen(
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
if(expr.operator==">>")
|
||||
if(signed)
|
||||
asmgen.out(" jsr math.lsr_byte_A")
|
||||
asmgen.out(" jsr prog8_math.lsr_byte_A")
|
||||
else
|
||||
asmgen.out(" jsr math.lsr_ubyte_A")
|
||||
asmgen.out(" jsr prog8_math.lsr_ubyte_A")
|
||||
else
|
||||
asmgen.out(" jsr math.asl_byte_A")
|
||||
asmgen.out(" jsr prog8_math.asl_byte_A")
|
||||
assignRegisterByte(target, CpuRegister.A, signed, true)
|
||||
return true
|
||||
} else {
|
||||
@@ -1104,11 +1105,11 @@ internal class AssignmentAsmGen(
|
||||
asmgen.restoreRegisterStack(CpuRegister.X, true)
|
||||
if(expr.operator==">>")
|
||||
if(signed)
|
||||
asmgen.out(" jsr math.lsr_word_AY")
|
||||
asmgen.out(" jsr prog8_math.lsr_word_AY")
|
||||
else
|
||||
asmgen.out(" jsr math.lsr_uword_AY")
|
||||
asmgen.out(" jsr prog8_math.lsr_uword_AY")
|
||||
else
|
||||
asmgen.out(" jsr math.asl_word_AY")
|
||||
asmgen.out(" jsr prog8_math.asl_word_AY")
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return true
|
||||
}
|
||||
@@ -1128,7 +1129,7 @@ internal class AssignmentAsmGen(
|
||||
if(shifts==1)
|
||||
asmgen.out(" cmp #$80 | ror a")
|
||||
else
|
||||
asmgen.out(" ldy #$shifts | jsr math.lsr_byte_A")
|
||||
asmgen.out(" ldy #$shifts | jsr prog8_math.lsr_byte_A")
|
||||
} else {
|
||||
repeat(shifts) {
|
||||
asmgen.out(" lsr a")
|
||||
@@ -1140,7 +1141,7 @@ internal class AssignmentAsmGen(
|
||||
}
|
||||
else -> {
|
||||
if(signed && expr.operator==">>") {
|
||||
asmgen.out(" ldy #$shifts | jsr math.lsr_byte_A")
|
||||
asmgen.out(" ldy #$shifts | jsr prog8_math.lsr_byte_A")
|
||||
} else {
|
||||
asmgen.out(" lda #0")
|
||||
}
|
||||
@@ -1173,7 +1174,7 @@ internal class AssignmentAsmGen(
|
||||
ror a""")
|
||||
}
|
||||
else
|
||||
asmgen.out(" ldx #$shifts | jsr math.lsr_word_AY")
|
||||
asmgen.out(" ldx #$shifts | jsr prog8_math.lsr_word_AY")
|
||||
} else {
|
||||
if(shifts>0) {
|
||||
asmgen.out(" sty P8ZP_SCRATCH_B1")
|
||||
@@ -1197,16 +1198,16 @@ internal class AssignmentAsmGen(
|
||||
} else {
|
||||
asmgen.out(" ldx #$shifts")
|
||||
if(signed)
|
||||
asmgen.out(" jsr math.lsr_word_AY")
|
||||
asmgen.out(" jsr prog8_math.lsr_word_AY")
|
||||
else
|
||||
asmgen.out(" jsr math.lsr_uword_AY")
|
||||
asmgen.out(" jsr prog8_math.lsr_uword_AY")
|
||||
}
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return true
|
||||
}
|
||||
else -> {
|
||||
if(signed && expr.operator==">>") {
|
||||
asmgen.out(" ldx #$shifts | jsr math.lsr_word_AY")
|
||||
asmgen.out(" ldx #$shifts | jsr prog8_math.lsr_word_AY")
|
||||
} else {
|
||||
asmgen.out(" lda #0 | ldy #0")
|
||||
}
|
||||
|
||||
+47
-47
@@ -904,9 +904,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
when (operator) {
|
||||
"+" -> asmgen.out(" clc | adc $otherName")
|
||||
"-" -> asmgen.out(" sec | sbc $otherName")
|
||||
"*" -> asmgen.out(" ldy $otherName | jsr math.multiply_bytes")
|
||||
"/" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm | tya")
|
||||
"%" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm")
|
||||
"*" -> asmgen.out(" ldy $otherName | jsr prog8_math.multiply_bytes")
|
||||
"/" -> asmgen.out(" ldy $otherName | jsr prog8_math.divmod_ub_asm | tya")
|
||||
"%" -> asmgen.out(" ldy $otherName | jsr prog8_math.divmod_ub_asm")
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
ldy $otherName
|
||||
@@ -981,23 +981,23 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
"*" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
if(value in asmgen.optimizedByteMultiplications)
|
||||
asmgen.out(" jsr math.mul_byte_${value}")
|
||||
asmgen.out(" jsr prog8_math.mul_byte_${value}")
|
||||
else
|
||||
asmgen.out(" ldy #$value | jsr math.multiply_bytes")
|
||||
asmgen.out(" ldy #$value | jsr prog8_math.multiply_bytes")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName, false)
|
||||
}
|
||||
"/" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
if(value==0)
|
||||
throw AssemblyError("division by zero")
|
||||
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | tya")
|
||||
asmgen.out(" ldy #$value | jsr prog8_math.divmod_ub_asm | tya")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName, false)
|
||||
}
|
||||
"%" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
if(value==0)
|
||||
throw AssemblyError("division by zero")
|
||||
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm")
|
||||
asmgen.out(" ldy #$value | jsr prog8_math.divmod_ub_asm")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName, false)
|
||||
}
|
||||
"<<" -> {
|
||||
@@ -1143,17 +1143,17 @@ $shortcutLabel:""")
|
||||
when (operator) {
|
||||
"+" -> asmgen.out(" clc | adc $variable")
|
||||
"-" -> asmgen.out(" sec | sbc $variable")
|
||||
"*" -> asmgen.out(" ldy $variable | jsr math.multiply_bytes")
|
||||
"*" -> asmgen.out(" ldy $variable | jsr prog8_math.multiply_bytes")
|
||||
"/" -> {
|
||||
if(signed)
|
||||
asmgen.out(" ldy $variable | jsr math.divmod_b_asm | tya")
|
||||
asmgen.out(" ldy $variable | jsr prog8_math.divmod_b_asm | tya")
|
||||
else
|
||||
asmgen.out(" ldy $variable | jsr math.divmod_ub_asm | tya")
|
||||
asmgen.out(" ldy $variable | jsr prog8_math.divmod_ub_asm | tya")
|
||||
}
|
||||
"%" -> {
|
||||
if(signed)
|
||||
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
||||
asmgen.out(" ldy $variable | jsr math.divmod_ub_asm")
|
||||
asmgen.out(" ldy $variable | jsr prog8_math.divmod_ub_asm")
|
||||
}
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
@@ -1318,14 +1318,14 @@ $shortcutLabel:""")
|
||||
}
|
||||
"/" -> {
|
||||
if(signed)
|
||||
asmgen.out(" tay | lda $variable | jsr math.divmod_b_asm | tya")
|
||||
asmgen.out(" tay | lda $variable | jsr prog8_math.divmod_b_asm | tya")
|
||||
else
|
||||
asmgen.out(" tay | lda $variable | jsr math.divmod_ub_asm | tya")
|
||||
asmgen.out(" tay | lda $variable | jsr prog8_math.divmod_ub_asm | tya")
|
||||
}
|
||||
"%" -> {
|
||||
if(signed)
|
||||
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
||||
asmgen.out(" tay | lda $variable | jsr math.divmod_ub_asm")
|
||||
asmgen.out(" tay | lda $variable | jsr prog8_math.divmod_ub_asm")
|
||||
}
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
@@ -1478,16 +1478,16 @@ $shortcutLabel:""")
|
||||
}
|
||||
"*" -> {
|
||||
if(value in asmgen.optimizedByteMultiplications)
|
||||
asmgen.out(" lda $name | jsr math.mul_byte_$value | sta $name")
|
||||
asmgen.out(" lda $name | jsr prog8_math.mul_byte_$value | sta $name")
|
||||
else
|
||||
asmgen.out(" lda $name | ldy #$value | jsr math.multiply_bytes | sta $name")
|
||||
asmgen.out(" lda $name | ldy #$value | jsr prog8_math.multiply_bytes | sta $name")
|
||||
}
|
||||
"/" -> {
|
||||
// replacing division by shifting is done in an optimizer step.
|
||||
if (dt == DataType.UBYTE)
|
||||
asmgen.out(" lda $name | ldy #$value | jsr math.divmod_ub_asm | sty $name")
|
||||
asmgen.out(" lda $name | ldy #$value | jsr prog8_math.divmod_ub_asm | sty $name")
|
||||
else
|
||||
asmgen.out(" lda $name | ldy #$value | jsr math.divmod_b_asm | sty $name")
|
||||
asmgen.out(" lda $name | ldy #$value | jsr prog8_math.divmod_b_asm | sty $name")
|
||||
}
|
||||
"%" -> {
|
||||
if(dt==DataType.BYTE)
|
||||
@@ -1495,7 +1495,7 @@ $shortcutLabel:""")
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
ldy #$value
|
||||
jsr math.divmod_ub_asm
|
||||
jsr prog8_math.divmod_ub_asm
|
||||
sta $name""")
|
||||
}
|
||||
"<<" -> {
|
||||
@@ -1529,7 +1529,7 @@ $shortcutLabel:""")
|
||||
value>3 -> asmgen.out("""
|
||||
lda $name
|
||||
ldy #$value
|
||||
jsr math.lsr_byte_A
|
||||
jsr prog8_math.lsr_byte_A
|
||||
sta $name""")
|
||||
else -> repeat(value) { asmgen.out(" lda $name | asl a | ror $name") }
|
||||
}
|
||||
@@ -1802,7 +1802,7 @@ $shortcutLabel:""")
|
||||
"*" -> {
|
||||
// the mul code works for both signed and unsigned
|
||||
if(value in asmgen.optimizedWordMultiplications) {
|
||||
asmgen.out(" lda $lsb | ldy $msb | jsr math.mul_word_$value | sta $lsb | sty $msb")
|
||||
asmgen.out(" lda $lsb | ldy $msb | jsr prog8_math.mul_word_$value | sta $lsb | sty $msb")
|
||||
} else {
|
||||
if(block?.options?.veraFxMuls==true)
|
||||
// cx16 verafx hardware mul
|
||||
@@ -1821,12 +1821,12 @@ $shortcutLabel:""")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $lsb
|
||||
sta math.multiply_words.multiplier
|
||||
sta prog8_math.multiply_words.multiplier
|
||||
lda $msb
|
||||
sta math.multiply_words.multiplier+1
|
||||
sta prog8_math.multiply_words.multiplier+1
|
||||
lda #<$value
|
||||
ldy #>$value
|
||||
jsr math.multiply_words
|
||||
jsr prog8_math.multiply_words
|
||||
sta $lsb
|
||||
sty $msb""")
|
||||
}
|
||||
@@ -1844,7 +1844,7 @@ $shortcutLabel:""")
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<$value
|
||||
ldy #>$value
|
||||
jsr math.divmod_w_asm
|
||||
jsr prog8_math.divmod_w_asm
|
||||
sta $lsb
|
||||
sty $msb
|
||||
""")
|
||||
@@ -1857,7 +1857,7 @@ $shortcutLabel:""")
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<$value
|
||||
ldy #>$value
|
||||
jsr math.divmod_uw_asm
|
||||
jsr prog8_math.divmod_uw_asm
|
||||
sta $lsb
|
||||
sty $msb
|
||||
""")
|
||||
@@ -1876,7 +1876,7 @@ $shortcutLabel:""")
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<$value
|
||||
ldy #>$value
|
||||
jsr math.divmod_uw_asm
|
||||
jsr prog8_math.divmod_uw_asm
|
||||
lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
sta $lsb
|
||||
@@ -2297,20 +2297,20 @@ $shortcutLabel:""")
|
||||
sty $name+1""")
|
||||
} else {
|
||||
if(valueDt==DataType.UBYTE) {
|
||||
asmgen.out(" lda $otherName | sta math.multiply_words.multiplier")
|
||||
asmgen.out(" lda $otherName | sta prog8_math.multiply_words.multiplier")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz math.multiply_words.multiplier+1")
|
||||
asmgen.out(" stz prog8_math.multiply_words.multiplier+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta math.multiply_words.multiplier+1")
|
||||
asmgen.out(" lda #0 | sta prog8_math.multiply_words.multiplier+1")
|
||||
} else {
|
||||
asmgen.out(" lda $otherName")
|
||||
asmgen.signExtendAYlsb(valueDt)
|
||||
asmgen.out(" sta math.multiply_words.multiplier | sty math.multiply_words.multiplier+1")
|
||||
asmgen.out(" sta prog8_math.multiply_words.multiplier | sty prog8_math.multiply_words.multiplier+1")
|
||||
}
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
ldy $name+1
|
||||
jsr math.multiply_words
|
||||
jsr prog8_math.multiply_words
|
||||
sta $name
|
||||
sty $name+1""")
|
||||
}
|
||||
@@ -2324,7 +2324,7 @@ $shortcutLabel:""")
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda $otherName
|
||||
ldy #0
|
||||
jsr math.divmod_uw_asm
|
||||
jsr prog8_math.divmod_uw_asm
|
||||
sta $name
|
||||
sty $name+1
|
||||
""")
|
||||
@@ -2336,7 +2336,7 @@ $shortcutLabel:""")
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda $otherName
|
||||
ldy #0
|
||||
jsr math.divmod_w_asm
|
||||
jsr prog8_math.divmod_w_asm
|
||||
sta $name
|
||||
sty $name+1
|
||||
""")
|
||||
@@ -2352,7 +2352,7 @@ $shortcutLabel:""")
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda $otherName
|
||||
ldy #0
|
||||
jsr math.divmod_uw_asm
|
||||
jsr prog8_math.divmod_uw_asm
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta $name
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
@@ -2460,11 +2460,11 @@ $shortcutLabel:""")
|
||||
asmgen.out("""
|
||||
lda $otherName
|
||||
ldy $otherName+1
|
||||
sta math.multiply_words.multiplier
|
||||
sty math.multiply_words.multiplier+1
|
||||
sta prog8_math.multiply_words.multiplier
|
||||
sty prog8_math.multiply_words.multiplier+1
|
||||
lda $name
|
||||
ldy $name+1
|
||||
jsr math.multiply_words
|
||||
jsr prog8_math.multiply_words
|
||||
sta $name
|
||||
sty $name+1""")
|
||||
}
|
||||
@@ -2477,7 +2477,7 @@ $shortcutLabel:""")
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda $otherName
|
||||
ldy $otherName+1
|
||||
jsr math.divmod_w_asm
|
||||
jsr prog8_math.divmod_w_asm
|
||||
sta $name
|
||||
sty $name+1""")
|
||||
}
|
||||
@@ -2489,7 +2489,7 @@ $shortcutLabel:""")
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda $otherName
|
||||
ldy $otherName+1
|
||||
jsr math.divmod_uw_asm
|
||||
jsr prog8_math.divmod_uw_asm
|
||||
sta $name
|
||||
sty $name+1""")
|
||||
}
|
||||
@@ -2504,7 +2504,7 @@ $shortcutLabel:""")
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda $otherName
|
||||
ldy $otherName+1
|
||||
jsr math.divmod_uw_asm
|
||||
jsr prog8_math.divmod_uw_asm
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta $name
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
@@ -2661,11 +2661,11 @@ $shortcutLabel:""")
|
||||
""")
|
||||
else
|
||||
asmgen.out("""
|
||||
sta math.multiply_words.multiplier
|
||||
sty math.multiply_words.multiplier+1
|
||||
sta prog8_math.multiply_words.multiplier
|
||||
sty prog8_math.multiply_words.multiplier+1
|
||||
lda $name
|
||||
ldy $name+1
|
||||
jsr math.multiply_words
|
||||
jsr prog8_math.multiply_words
|
||||
sta $name
|
||||
sty $name+1
|
||||
""")
|
||||
@@ -2680,9 +2680,9 @@ $shortcutLabel:""")
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
txa""")
|
||||
if (dt == DataType.WORD)
|
||||
asmgen.out(" jsr math.divmod_w_asm")
|
||||
asmgen.out(" jsr prog8_math.divmod_w_asm")
|
||||
else
|
||||
asmgen.out(" jsr math.divmod_uw_asm")
|
||||
asmgen.out(" jsr prog8_math.divmod_uw_asm")
|
||||
asmgen.out(" sta $name | sty $name+1")
|
||||
}
|
||||
|
||||
@@ -2696,7 +2696,7 @@ $shortcutLabel:""")
|
||||
lda $name+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
txa
|
||||
jsr math.divmod_uw_asm
|
||||
jsr prog8_math.divmod_uw_asm
|
||||
lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
sta $name
|
||||
|
||||
@@ -26,7 +26,8 @@ class TestCodegen: FunSpec({
|
||||
floats = true,
|
||||
noSysInit = false,
|
||||
compTarget = target,
|
||||
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
|
||||
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS,
|
||||
memtopAddress = 0xffffu
|
||||
)
|
||||
}
|
||||
|
||||
@@ -43,14 +44,46 @@ class TestCodegen: FunSpec({
|
||||
// xx += cx16.r0
|
||||
// }
|
||||
//}
|
||||
val codegen = AsmGen6502(prefixSymbols = false)
|
||||
val codegen = AsmGen6502(prefixSymbols = false, 0)
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main",false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
|
||||
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
|
||||
sub.add(PtVariable("particleDX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
|
||||
sub.add(PtVariable("xx", DataType.WORD, ZeropageWish.DONTCARE, PtNumber(DataType.WORD, 1.0, Position.DUMMY), null, Position.DUMMY))
|
||||
sub.add(PtVariable(
|
||||
"pi",
|
||||
DataType.UBYTE,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
PtNumber(DataType.UBYTE, 0.0, Position.DUMMY),
|
||||
null,
|
||||
Position.DUMMY
|
||||
))
|
||||
sub.add(PtVariable(
|
||||
"particleX",
|
||||
DataType.ARRAY_UB,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
null,
|
||||
3u,
|
||||
Position.DUMMY
|
||||
))
|
||||
sub.add(PtVariable(
|
||||
"particleDX",
|
||||
DataType.ARRAY_UB,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
null,
|
||||
3u,
|
||||
Position.DUMMY
|
||||
))
|
||||
sub.add(PtVariable(
|
||||
"xx",
|
||||
DataType.WORD,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
PtNumber(DataType.WORD, 1.0, Position.DUMMY),
|
||||
null,
|
||||
Position.DUMMY
|
||||
))
|
||||
|
||||
val assign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||
val target = PtAssignTarget(false, Position.DUMMY).also {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.StRomSub
|
||||
import prog8.code.StRomSubParameter
|
||||
import prog8.code.StExtSub
|
||||
import prog8.code.StExtSubParameter
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.intermediate.*
|
||||
@@ -14,7 +14,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
val values = assignment.value as? PtFunctionCall
|
||||
?: throw AssemblyError("only function calls can return multiple values in a multi-assign")
|
||||
|
||||
val sub = codeGen.symbolTable.lookup(values.name) as? StRomSub
|
||||
val sub = codeGen.symbolTable.lookup(values.name) as? StExtSub
|
||||
?: throw AssemblyError("only asmsubs can return multiple values")
|
||||
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
@@ -46,7 +46,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignCpuRegister(returns: StRomSubParameter, regNum: Int, target: PtAssignTarget): IRCodeChunks {
|
||||
private fun assignCpuRegister(returns: StExtSubParameter, regNum: Int, target: PtAssignTarget): IRCodeChunks {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val loadCpuRegInstr = when(returns.register.registerOrPair) {
|
||||
RegisterOrPair.A -> IRInstruction(Opcode.LOADHA, IRDataType.BYTE, reg1=regNum)
|
||||
@@ -278,7 +278,6 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
}
|
||||
}
|
||||
"not" -> {
|
||||
// TODO: in boolean branch, is 'not' handled ok like this?
|
||||
val register = codeGen.registers.nextFree()
|
||||
if(constIndex!=null) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
@@ -360,7 +359,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
val variable = targetArray.variable.name
|
||||
val itemsize = codeGen.program.memsizer.memorySize(targetArray.type)
|
||||
|
||||
val fixedIndex = constIntValue(targetArray.index)
|
||||
val fixedIndex = targetArray.index.asConstInteger()
|
||||
val arrayLength = codeGen.symbolTable.getLength(targetArray.variable.name)
|
||||
if(zero) {
|
||||
if(fixedIndex!=null) {
|
||||
@@ -884,7 +883,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
}
|
||||
|
||||
private fun operatorMultiplyInplaceSplitArray(array: PtArrayIndexer, operand: PtExpression): IRCodeChunks? {
|
||||
return null // TODO("inplace split word array *")
|
||||
return null // TODO("inplace split word array *")
|
||||
}
|
||||
|
||||
private fun operatorMinusInplaceSplitArray(array: PtArrayIndexer, operand: PtExpression): IRCodeChunks? {
|
||||
@@ -1371,7 +1370,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
|
||||
private fun createInplaceArrayComparison(array: PtArrayIndexer, value: PtExpression, comparisonOperator: String): IRCodeChunks? {
|
||||
if(array.type==DataType.FLOAT)
|
||||
return null // TODO("optimized in-place compare on float arrays")) // TODO?
|
||||
return null // TODO("optimized in-place compare on float arrays"))
|
||||
|
||||
val eltSize = codeGen.program.memsizer.memorySize(array.type)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
@@ -1390,10 +1389,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
it += when(comparisonOperator) {
|
||||
"==" -> IRInstruction(Opcode.SZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
|
||||
"!=" -> IRInstruction(Opcode.SNZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
|
||||
"<" -> return null // TODO("array <0 inplace")) // TODO?
|
||||
"<=" -> return null // TODO("array <=0 inplace")) // TODO?
|
||||
">" -> return null // TODO("array >0 inplace")) // TODO?
|
||||
">=" -> return null // TODO("array >=0 inplace")) // TODO?
|
||||
"<" -> return null // TODO("array <0 inplace"))
|
||||
"<=" -> return null // TODO("array <=0 inplace"))
|
||||
">" -> return null // TODO("array >0 inplace"))
|
||||
">=" -> return null // TODO("array >=0 inplace"))
|
||||
else -> throw AssemblyError("invalid operator")
|
||||
}
|
||||
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = cmpResultReg, labelSymbol = array.variable.name, symbolOffset = constIndex*eltSize)
|
||||
@@ -1413,10 +1412,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
it += when(comparisonOperator) {
|
||||
"==" -> IRInstruction(Opcode.SZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
|
||||
"!=" -> IRInstruction(Opcode.SNZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
|
||||
"<" -> return null // TODO("array <0 inplace")) // TODO?
|
||||
"<=" -> return null // TODO("array <=0 inplace")) // TODO?
|
||||
">" -> return null // TODO("array >0 inplace")) // TODO?
|
||||
">=" -> return null // TODO("array >=0 inplace")) // TODO?
|
||||
"<" -> return null // TODO("array <0 inplace"))
|
||||
"<=" -> return null // TODO("array <=0 inplace"))
|
||||
">" -> return null // TODO("array >0 inplace"))
|
||||
">=" -> return null // TODO("array >=0 inplace"))
|
||||
else -> throw AssemblyError("invalid operator")
|
||||
}
|
||||
it += IRInstruction(Opcode.STOREX, vmDt, reg1=valueReg, reg2=indexTr.resultReg, labelSymbol = array.variable.name)
|
||||
|
||||
@@ -39,77 +39,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
"ror" -> funcRolRor(call)
|
||||
"rol2" -> funcRolRor(call)
|
||||
"ror2" -> funcRolRor(call)
|
||||
"prog8_ifelse_bittest_set" -> throw AssemblyError("prog8_ifelse_bittest_set() should have been translated as part of an ifElse statement")
|
||||
"prog8_ifelse_bittest_notset" -> throw AssemblyError("prog8_ifelse_bittest_notset() should have been translated as part of an ifElse statement")
|
||||
"prog8_ifelse_bittest_set" -> throw AssemblyError("prog8_ifelse_bittest_set() should have been translated as part of an ifElse/ifExpression statement")
|
||||
"prog8_ifelse_bittest_notset" -> throw AssemblyError("prog8_ifelse_bittest_notset() should have been translated as part of an ifElse/ifExpression statement")
|
||||
"prog8_lib_stringcompare" -> funcStringCompare(call)
|
||||
"prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE)
|
||||
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
|
||||
"prog8_lib_arraycopy" -> funcArrayCopy(call)
|
||||
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcArrayCopy(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val source = call.args[0] as PtIdentifier
|
||||
val target = call.args[1] as PtIdentifier
|
||||
val sourceLength = codeGen.symbolTable.getLength(source.name)!!
|
||||
val targetLength = codeGen.symbolTable.getLength(target.name)!!
|
||||
require(sourceLength==targetLength)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val fromReg = codeGen.registers.nextFree()
|
||||
val toReg = codeGen.registers.nextFree()
|
||||
val countReg = codeGen.registers.nextFree()
|
||||
if(source.type in SplitWordArrayTypes && target.type in SplitWordArrayTypes) {
|
||||
// split words -> split words, copy lsb and msb arrays separately
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name+"_lsb")
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name+"_lsb")
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=countReg, immediate = sourceLength)
|
||||
it += codeGen.makeSyscall(IMSyscall.MEMCOPY_SMALL, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to toReg, IRDataType.BYTE to (countReg and 255)), returns = null)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name+"_msb")
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name+"_msb")
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=countReg, immediate = sourceLength)
|
||||
it += codeGen.makeSyscall(IMSyscall.MEMCOPY_SMALL, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to toReg, IRDataType.BYTE to (countReg and 255)), returns = null)
|
||||
}
|
||||
}
|
||||
else if(source.type in SplitWordArrayTypes) {
|
||||
// split -> normal words
|
||||
require(target.type==DataType.ARRAY_UW || target.type==DataType.ARRAY_W)
|
||||
val fromRegMsb = codeGen.registers.nextFree()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name+"_lsb")
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromRegMsb, labelSymbol = source.name+"_msb")
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=countReg, immediate = sourceLength)
|
||||
}
|
||||
result += codeGen.makeSyscall(IMSyscall.ARRAYCOPY_SPLITW_TO_NORMAL, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to fromRegMsb, IRDataType.WORD to toReg, IRDataType.BYTE to countReg), returns = null)
|
||||
}
|
||||
else if(target.type in SplitWordArrayTypes) {
|
||||
// normal -> split words
|
||||
require(source.type==DataType.ARRAY_UW || source.type==DataType.ARRAY_W)
|
||||
val toRegMsb = codeGen.registers.nextFree()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name+"_lsb")
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toRegMsb, labelSymbol = target.name+"_msb")
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=countReg, immediate = sourceLength)
|
||||
}
|
||||
result += codeGen.makeSyscall(IMSyscall.ARRAYCOPY_NORMAL_TO_SPLITW, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to toReg, IRDataType.WORD to toRegMsb, IRDataType.BYTE to countReg), returns = null)
|
||||
}
|
||||
else {
|
||||
// normal array to array copy (various element types)
|
||||
val eltsize = codeGen.options.compTarget.memorySize(ArrayToElementTypes.getValue(source.type))
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=countReg, immediate = sourceLength * eltsize)
|
||||
}
|
||||
result += codeGen.makeSyscall(IMSyscall.MEMCOPY_SMALL, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to toReg, IRDataType.BYTE to (countReg and 255)), returns = null)
|
||||
}
|
||||
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcSquare(call: PtBuiltinFunctionCall, resultType: IRDataType): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val valueTr = exprGen.translateExpression(call.args[0])
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.StNode
|
||||
import prog8.code.StRomSub
|
||||
import prog8.code.StExtSub
|
||||
import prog8.code.StSub
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
@@ -79,6 +79,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
is PtPrefix -> translate(expr)
|
||||
is PtArrayIndexer -> translate(expr)
|
||||
is PtBinaryExpression -> translate(expr)
|
||||
is PtIfExpression -> translate(expr)
|
||||
is PtBuiltinFunctionCall -> codeGen.translateBuiltinFunc(expr)
|
||||
is PtFunctionCall -> translate(expr)
|
||||
is PtContainmentCheck -> translate(expr)
|
||||
@@ -89,6 +90,41 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun translate(ifExpr: PtIfExpression): ExpressionCodeResult {
|
||||
|
||||
if((ifExpr.condition as? PtPrefix)?.operator=="not")
|
||||
throw AssemblyError("not prefix in ifexpression should have been replaced by swapped values")
|
||||
|
||||
// TODO don't store condition as expression result but just use the flags, like a normal PtIfElse translation does
|
||||
val condTr = translateExpression(ifExpr.condition)
|
||||
val trueTr = translateExpression(ifExpr.truevalue)
|
||||
val falseTr = translateExpression(ifExpr.falsevalue)
|
||||
val irDt = irType(ifExpr.type)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val falseLabel = codeGen.createLabelName()
|
||||
val endLabel = codeGen.createLabelName()
|
||||
|
||||
addToResult(result, condTr, condTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=condTr.resultReg, immediate = 0), null)
|
||||
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = falseLabel), null)
|
||||
|
||||
if (irDt != IRDataType.FLOAT) {
|
||||
addToResult(result, trueTr, trueTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
|
||||
result += IRCodeChunk(falseLabel, null)
|
||||
addToResult(result, falseTr, trueTr.resultReg, -1)
|
||||
result += IRCodeChunk(endLabel, null)
|
||||
return ExpressionCodeResult(result, irDt, trueTr.resultReg, -1)
|
||||
} else {
|
||||
addToResult(result, trueTr, -1, trueTr.resultFpReg)
|
||||
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
|
||||
result += IRCodeChunk(falseLabel, null)
|
||||
addToResult(result, falseTr, -1, trueTr.resultFpReg)
|
||||
result += IRCodeChunk(endLabel, null)
|
||||
return ExpressionCodeResult(result, irDt, -1, trueTr.resultFpReg)
|
||||
}
|
||||
}
|
||||
|
||||
private fun translate(expr: PtAddressOf): ExpressionCodeResult {
|
||||
val vmDt = irType(expr.type)
|
||||
val symbol = expr.identifier.name
|
||||
@@ -492,7 +528,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
fun translate(fcall: PtFunctionCall): ExpressionCodeResult {
|
||||
val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)
|
||||
|
||||
if(callTarget.scopedName in listOf("sys.push", "sys.pushw", "sys.pop", "sys.popw")) {
|
||||
if(callTarget.scopedName in listOf("sys.push", "sys.pushw", "sys.pop", "sys.popw", "floats.push", "floats.pop")) {
|
||||
// special case, these should be inlined, or even use specialized instructions. Instead of doing a normal subroutine call.
|
||||
return translateStackFunctions(fcall, callTarget)
|
||||
}
|
||||
@@ -552,7 +588,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
else
|
||||
ExpressionCodeResult(result, returnRegSpec!!.dt, returnRegSpec.registerNum, -1)
|
||||
}
|
||||
is StRomSub -> {
|
||||
is StExtSub -> {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
|
||||
// assign the arguments
|
||||
@@ -572,6 +608,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
RegisterOrPair.AX -> addInstr(result, IRInstruction(Opcode.STOREHAX, IRDataType.WORD, reg1=tr.resultReg), null)
|
||||
RegisterOrPair.AY -> addInstr(result, IRInstruction(Opcode.STOREHAY, IRDataType.WORD, reg1=tr.resultReg), null)
|
||||
RegisterOrPair.XY -> addInstr(result, IRInstruction(Opcode.STOREHXY, IRDataType.WORD, reg1=tr.resultReg), null)
|
||||
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> TODO("floating point register parameters not supported")
|
||||
in Cx16VirtualRegisters -> {
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, paramDt, reg1=tr.resultReg, labelSymbol = "cx16.${parameter.register.registerOrPair.toString().lowercase()}"), null)
|
||||
}
|
||||
@@ -585,7 +622,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
|
||||
if(callTarget.returns.size>1)
|
||||
return callRomSubWithMultipleReturnValues(callTarget, fcall, argRegisters, result)
|
||||
return callExtSubWithMultipleReturnValues(callTarget, fcall, argRegisters, result)
|
||||
|
||||
// return a single value (or nothing)
|
||||
val returnRegSpec = if(fcall.void) null else {
|
||||
@@ -607,8 +644,18 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val call =
|
||||
if(callTarget.address==null)
|
||||
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
|
||||
else
|
||||
IRInstruction(Opcode.CALL, address = callTarget.address!!.toInt(), fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
|
||||
else {
|
||||
val address = callTarget.address!!
|
||||
if(address.constbank==null && address.varbank==null) {
|
||||
IRInstruction(
|
||||
Opcode.CALL,
|
||||
address = address.address.toInt(),
|
||||
fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
|
||||
}
|
||||
else {
|
||||
TODO("callfar into another bank is not implemented for the selected compilation target")
|
||||
}
|
||||
}
|
||||
addInstr(result, call, null)
|
||||
var finalReturnRegister = returnRegSpec?.registerNum ?: -1
|
||||
|
||||
@@ -687,12 +734,25 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
addInstr(chunk, IRInstruction(Opcode.POP, IRDataType.WORD, reg1=popReg), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.WORD, popReg, -1)
|
||||
}
|
||||
"floats.push" -> {
|
||||
// push float
|
||||
val tr = translateExpression(fcall.args.single())
|
||||
chunk += tr.chunks
|
||||
addInstr(chunk, IRInstruction(Opcode.PUSH, IRDataType.FLOAT, fpReg1 = tr.resultFpReg), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.FLOAT, -1, -1)
|
||||
}
|
||||
"floats.pop" -> {
|
||||
// pop float
|
||||
val popReg = codeGen.registers.nextFreeFloat()
|
||||
addInstr(chunk, IRInstruction(Opcode.POP, IRDataType.FLOAT, fpReg1 = popReg), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.FLOAT, -1, resultFpReg = popReg)
|
||||
}
|
||||
else -> throw AssemblyError("unknown stack subroutine called")
|
||||
}
|
||||
}
|
||||
|
||||
private fun callRomSubWithMultipleReturnValues(
|
||||
callTarget: StRomSub,
|
||||
private fun callExtSubWithMultipleReturnValues(
|
||||
callTarget: StExtSub,
|
||||
fcall: PtFunctionCall,
|
||||
argRegisters: MutableList<FunctionCallArgs.ArgumentSpec>,
|
||||
result: MutableList<IRCodeChunkBase>
|
||||
@@ -706,8 +766,17 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val call =
|
||||
if(callTarget.address==null)
|
||||
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegisters))
|
||||
else
|
||||
IRInstruction(Opcode.CALL, address = callTarget.address!!.toInt(), fcallArgs = FunctionCallArgs(argRegisters, returnRegisters))
|
||||
else {
|
||||
val address = callTarget.address!!
|
||||
if(address.constbank==null && address.varbank==null) {
|
||||
IRInstruction(
|
||||
Opcode.CALL,
|
||||
address = address.address.toInt(),
|
||||
fcallArgs = FunctionCallArgs(argRegisters, returnRegisters)
|
||||
)
|
||||
}
|
||||
else TODO("extsub with banked address got called ${callTarget.name}")
|
||||
}
|
||||
addInstr(result, call, null)
|
||||
val resultRegs = returnRegisters.filter{it.dt!=IRDataType.FLOAT}.map{it.registerNum}
|
||||
val resultFpRegs = returnRegisters.filter{it.dt==IRDataType.FLOAT}.map{it.registerNum}
|
||||
@@ -826,7 +895,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
if(binExpr.left.type==DataType.STR || binExpr.right.type==DataType.STR) {
|
||||
throw AssemblyError("str compares should have been replaced with builtin function call to do the compare")
|
||||
} else {
|
||||
return if(constValue(binExpr.right)==0.0) {
|
||||
return if(binExpr.right.asConstValue()==0.0) {
|
||||
val tr = translateExpression(binExpr.left)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val opcode = if (notEquals) Opcode.SNZ else Opcode.SZ
|
||||
|
||||
@@ -23,6 +23,7 @@ class IRCodeGen(
|
||||
makeAllNodenamesScoped(program)
|
||||
moveAllNestedSubroutinesToBlockScope(program)
|
||||
verifyNameScoping(program, symbolTable)
|
||||
changeGlobalVarInits(symbolTable)
|
||||
|
||||
val irSymbolTable = IRSymbolTable.fromStDuringCodegen(symbolTable)
|
||||
val irProg = IRProgram(program.name, irSymbolTable, options, program.encoding)
|
||||
@@ -54,12 +55,43 @@ class IRCodeGen(
|
||||
return irProg
|
||||
}
|
||||
|
||||
private fun changeGlobalVarInits(symbolTable: SymbolTable) {
|
||||
// Normally, block level (global) variables that have a numeric initialization value
|
||||
// are initialized via an assignment statement.
|
||||
val initsToRemove = mutableListOf<Pair<PtBlock, PtAssignment>>()
|
||||
|
||||
symbolTable.allVariables.forEach { variable ->
|
||||
if(variable.uninitialized && variable.parent.type==StNodeType.BLOCK) {
|
||||
val block = variable.parent.astNode as PtBlock
|
||||
val initialization = (block.children.firstOrNull {
|
||||
it is PtAssignment && it.target.identifier?.name==variable.scopedName
|
||||
} as PtAssignment?)
|
||||
val initValue = initialization?.value
|
||||
when(initValue){
|
||||
is PtBool -> {
|
||||
variable.setOnetimeInitNumeric(initValue.asInt().toDouble())
|
||||
initsToRemove += block to initialization
|
||||
}
|
||||
is PtNumber -> {
|
||||
variable.setOnetimeInitNumeric(initValue.number)
|
||||
initsToRemove += block to initialization
|
||||
}
|
||||
is PtArray, is PtString -> throw AssemblyError("array or string initialization values should already be part of the vardecl, not a separate assignment")
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for((block, assign) in initsToRemove) {
|
||||
block.children.remove(assign)
|
||||
}
|
||||
}
|
||||
|
||||
private fun verifyNameScoping(program: PtProgram, symbolTable: SymbolTable) {
|
||||
fun verifyPtNode(node: PtNode) {
|
||||
when (node) {
|
||||
is PtBuiltinFunctionCall -> require('.' !in node.name) { "builtin function call name should not be scoped: ${node.name}" }
|
||||
is PtFunctionCall -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
|
||||
is PtIdentifier -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
|
||||
is PtAsmSub -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
|
||||
is PtBlock -> require('.' !in node.name) { "block name should not be scoped: ${node.name}" }
|
||||
is PtConstant -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
|
||||
@@ -69,8 +101,7 @@ class IRCodeGen(
|
||||
is PtVariable -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
|
||||
is PtProgram -> require('.' !in node.name) { "program name should not be scoped: ${node.name}" }
|
||||
is PtSubroutineParameter -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
|
||||
else -> { /* node has no name */
|
||||
}
|
||||
else -> { /* node has no name or is ok to have no dots in the name */ }
|
||||
}
|
||||
node.children.forEach { verifyPtNode(it) }
|
||||
}
|
||||
@@ -201,6 +232,7 @@ class IRCodeGen(
|
||||
chunk += IRInstruction(Opcode.BREAKPOINT)
|
||||
listOf(chunk)
|
||||
}
|
||||
is PtAlign -> TODO("ir support for inline %align")
|
||||
is PtConditionalBranch -> translate(node)
|
||||
is PtInlineAssembly -> listOf(IRInlineAsmChunk(null, node.assembly, node.isIR, null))
|
||||
is PtIncludeBinary -> listOf(IRInlineBinaryChunk(null, readBinaryData(node), null))
|
||||
@@ -221,6 +253,7 @@ class IRCodeGen(
|
||||
is PtBool,
|
||||
is PtArray,
|
||||
is PtBlock,
|
||||
is PtDefer -> throw AssemblyError("should have been transformed")
|
||||
is PtString -> throw AssemblyError("should not occur as separate statement node ${node.position}")
|
||||
is PtSub -> throw AssemblyError("nested subroutines should have been flattened ${node.position}")
|
||||
else -> TODO("missing codegen for $node")
|
||||
@@ -1295,6 +1328,9 @@ class IRCodeGen(
|
||||
}
|
||||
|
||||
private fun translateIfElse(ifElse: PtIfElse): IRCodeChunks {
|
||||
if((ifElse.condition as? PtPrefix)?.operator=="not" && ifElse.hasElse())
|
||||
throw AssemblyError("not prefix in ifelse should have been replaced by swapped if-else blocks")
|
||||
|
||||
val condition = ifElse.condition as? PtBinaryExpression
|
||||
if(condition==null || condition.left.type != DataType.FLOAT) {
|
||||
return ifWithElse_IntegerCond(ifElse)
|
||||
@@ -1360,12 +1396,14 @@ class IRCodeGen(
|
||||
private fun ifWithElse_IntegerCond(ifElse: PtIfElse): List<IRCodeChunkBase> {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
|
||||
fun translateSimple(condition: PtExpression, jumpFalseOpcode: Opcode) {
|
||||
fun translateSimple(condition: PtExpression, jumpFalseOpcode: Opcode, addCmpiZero: Boolean) {
|
||||
|
||||
if(condition is PtBuiltinFunctionCall && condition.name.startsWith("prog8_ifelse_bittest_"))
|
||||
throw AssemblyError("IR codegen doesn't have special instructions for dedicated BIT tests and should just still use normal AND")
|
||||
|
||||
val tr = expressionEval.translateExpression(condition)
|
||||
if(addCmpiZero)
|
||||
tr.chunks.last().instructions.add(IRInstruction(Opcode.CMPI, tr.dt, reg1 = tr.resultReg, immediate = 0))
|
||||
result += tr.chunks
|
||||
if(ifElse.hasElse()) {
|
||||
val elseLabel = createLabelName()
|
||||
@@ -1385,7 +1423,7 @@ class IRCodeGen(
|
||||
|
||||
fun translateBinExpr(condition: PtBinaryExpression) {
|
||||
if(condition.operator in LogicalOperators)
|
||||
return translateSimple(condition, Opcode.BSTEQ)
|
||||
return translateSimple(condition, Opcode.BSTEQ, false)
|
||||
|
||||
val signed = condition.left.type in SignedDatatypes
|
||||
val elseBranchFirstReg: Int
|
||||
@@ -1524,18 +1562,21 @@ class IRCodeGen(
|
||||
when(val cond=ifElse.condition) {
|
||||
is PtBool -> {
|
||||
// normally this will be optimized away, but not with -noopt
|
||||
translateSimple(cond, Opcode.BSTEQ)
|
||||
translateSimple(cond, Opcode.BSTEQ, false)
|
||||
}
|
||||
is PtTypeCast -> {
|
||||
require(cond.type==DataType.BOOL && cond.value.type in NumericDatatypes)
|
||||
translateSimple(cond, Opcode.BSTEQ)
|
||||
translateSimple(cond, Opcode.BSTEQ, false)
|
||||
}
|
||||
is PtIdentifier, is PtArrayIndexer, is PtBuiltinFunctionCall, is PtFunctionCall, is PtContainmentCheck -> {
|
||||
translateSimple(cond, Opcode.BSTEQ)
|
||||
is PtIdentifier, is PtArrayIndexer, is PtContainmentCheck -> {
|
||||
translateSimple(cond, Opcode.BSTEQ, false)
|
||||
}
|
||||
is PtBuiltinFunctionCall, is PtFunctionCall -> {
|
||||
translateSimple(cond, Opcode.BSTEQ, true)
|
||||
}
|
||||
is PtPrefix -> {
|
||||
require(cond.operator=="not")
|
||||
translateSimple(cond.value, Opcode.BSTNE)
|
||||
translateSimple(cond.value, Opcode.BSTNE, false)
|
||||
}
|
||||
is PtBinaryExpression -> {
|
||||
translateBinExpr(cond)
|
||||
@@ -1546,7 +1587,7 @@ class IRCodeGen(
|
||||
}
|
||||
|
||||
private fun translate(repeat: PtRepeatLoop): IRCodeChunks {
|
||||
when (constIntValue(repeat.count)) {
|
||||
when (repeat.count.asConstInteger()) {
|
||||
0 -> return emptyList()
|
||||
1 -> return translateGroup(repeat.children)
|
||||
256 -> {
|
||||
@@ -1557,18 +1598,30 @@ class IRCodeGen(
|
||||
|
||||
val repeatLabel = createLabelName()
|
||||
val skipRepeatLabel = createLabelName()
|
||||
val irDt = irType(repeat.count.type)
|
||||
val constRepeats = repeat.count.asConstInteger()
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val countTr = expressionEval.translateExpression(repeat.count)
|
||||
addToResult(result, countTr, countTr.resultReg, -1)
|
||||
if(constIntValue(repeat.count)==null) {
|
||||
// check if the counter is already zero
|
||||
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = skipRepeatLabel), null)
|
||||
}
|
||||
result += labelFirstChunk(translateNode(repeat.statements), repeatLabel)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.DEC, irDt, reg1 = countTr.resultReg) // sets status bits
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = repeatLabel)
|
||||
if(constRepeats==65536) {
|
||||
// make use of the word wrap around to count to 65536
|
||||
val resultRegister = registers.nextFree()
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultRegister, immediate = 0), null)
|
||||
result += labelFirstChunk(translateNode(repeat.statements), repeatLabel)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.DEC, IRDataType.WORD, reg1 = resultRegister) // sets status bits
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = repeatLabel)
|
||||
}
|
||||
} else {
|
||||
val irDt = irType(repeat.count.type)
|
||||
val countTr = expressionEval.translateExpression(repeat.count)
|
||||
addToResult(result, countTr, countTr.resultReg, -1)
|
||||
if (repeat.count.asConstValue() == null) {
|
||||
// check if the counter is already zero
|
||||
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = skipRepeatLabel), null)
|
||||
}
|
||||
result += labelFirstChunk(translateNode(repeat.statements), repeatLabel)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.DEC, irDt, reg1 = countTr.resultReg) // sets status bits
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = repeatLabel)
|
||||
}
|
||||
}
|
||||
result += IRCodeChunk(skipRepeatLabel, null)
|
||||
return result
|
||||
@@ -1613,14 +1666,22 @@ class IRCodeGen(
|
||||
addInstr(result, IRInstruction(Opcode.RETURN), null)
|
||||
} else {
|
||||
if(value.type==DataType.FLOAT) {
|
||||
val tr = expressionEval.translateExpression(value)
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
addInstr(result, IRInstruction(Opcode.RETURNR, IRDataType.FLOAT, fpReg1 = tr.resultFpReg), null)
|
||||
if(value is PtNumber) {
|
||||
addInstr(result, IRInstruction(Opcode.RETURNI, IRDataType.FLOAT, immediateFp = value.number), null)
|
||||
} else {
|
||||
val tr = expressionEval.translateExpression(value)
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
addInstr(result, IRInstruction(Opcode.RETURNR, IRDataType.FLOAT, fpReg1 = tr.resultFpReg), null)
|
||||
}
|
||||
}
|
||||
else {
|
||||
val tr = expressionEval.translateExpression(value)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.RETURNR, irType(value.type) , reg1=tr.resultReg), null)
|
||||
if(value.asConstInteger()!=null) {
|
||||
addInstr(result, IRInstruction(Opcode.RETURNI, irType(value.type), immediate = value.asConstInteger()), null)
|
||||
} else {
|
||||
val tr = expressionEval.translateExpression(value)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.RETURNR, irType(value.type), reg1 = tr.resultReg), null)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
@@ -1633,14 +1694,14 @@ class IRCodeGen(
|
||||
block.options.forceOutput,
|
||||
block.options.noSymbolPrefixing,
|
||||
block.options.veraFxMuls,
|
||||
block.options.ignoreUnused,
|
||||
translate(block.options.alignment)
|
||||
block.options.ignoreUnused
|
||||
), block.position)
|
||||
for (child in block.children) {
|
||||
when(child) {
|
||||
is PtNop -> { /* nothing */ }
|
||||
is PtAssignment, is PtAugmentedAssign -> { /* global variable initialization is done elsewhere */ }
|
||||
is PtVariable, is PtConstant, is PtMemMapped -> { /* vars should be looked up via symbol table */ }
|
||||
is PtAlign -> TODO("ir support for inline %align")
|
||||
is PtSub -> {
|
||||
val sub = IRSubroutine(child.name, translate(child.parameters), child.returntype, child.position)
|
||||
for (subchild in child.children) {
|
||||
@@ -1650,9 +1711,9 @@ class IRCodeGen(
|
||||
}
|
||||
is PtAsmSub -> {
|
||||
if(child.address!=null) {
|
||||
// romsub. No codegen needed: calls to this are jumping straight to the address.
|
||||
// extmsub. No codegen needed: calls to this are jumping straight to the address.
|
||||
require(child.children.isEmpty()) {
|
||||
"romsub should be empty at ${child.position}"
|
||||
"extsub should be empty at ${child.position}"
|
||||
}
|
||||
} else {
|
||||
// regular asmsub
|
||||
@@ -1665,7 +1726,7 @@ class IRCodeGen(
|
||||
)
|
||||
irBlock += IRAsmSubroutine(
|
||||
child.name,
|
||||
child.address,
|
||||
null,
|
||||
child.clobbers,
|
||||
child.parameters.map { IRAsmSubroutine.IRAsmParam(it.first, it.second.type) }, // note: the name of the asmsub param is not used here anymore
|
||||
child.returns.map { IRAsmSubroutine.IRAsmParam(it.first, it.second)},
|
||||
@@ -1698,18 +1759,10 @@ class IRCodeGen(
|
||||
IRSubroutine.IRParam(flattenedName, orig.dt)
|
||||
}
|
||||
|
||||
private fun translate(alignment: PtBlock.BlockAlignment): IRBlock.BlockAlignment {
|
||||
return when(alignment) {
|
||||
PtBlock.BlockAlignment.NONE -> IRBlock.BlockAlignment.NONE
|
||||
PtBlock.BlockAlignment.WORD -> IRBlock.BlockAlignment.WORD
|
||||
PtBlock.BlockAlignment.PAGE -> IRBlock.BlockAlignment.PAGE
|
||||
}
|
||||
}
|
||||
|
||||
private var labelSequenceNumber = 0
|
||||
internal fun createLabelName(): String {
|
||||
labelSequenceNumber++
|
||||
return "label_gen_$labelSequenceNumber"
|
||||
return "${PtLabel.GeneratedLabelPrefix}$labelSequenceNumber"
|
||||
}
|
||||
|
||||
internal fun translateBuiltinFunc(call: PtBuiltinFunctionCall): ExpressionCodeResult
|
||||
|
||||
@@ -64,7 +64,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
private fun replaceConcatZeroMsbWithExt(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
if (ins.opcode == Opcode.CONCAT) {
|
||||
if (ins.opcode == Opcode.CONCAT && idx>0) {
|
||||
// if the previous instruction loads a zero in the msb, this can be turned into EXT.B instead
|
||||
val prev = indexedInstructions[idx-1].value
|
||||
if(prev.opcode==Opcode.LOAD && prev.immediate==0 && prev.reg1==ins.reg2) {
|
||||
@@ -299,7 +299,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
}
|
||||
|
||||
// remove useless RETURN
|
||||
if(idx>0 && (ins.opcode == Opcode.RETURN || ins.opcode==Opcode.RETURNR)) {
|
||||
if(idx>0 && (ins.opcode == Opcode.RETURN || ins.opcode==Opcode.RETURNR || ins.opcode==Opcode.RETURNI)) {
|
||||
val previous = chunk.instructions[idx-1]
|
||||
if(previous.opcode in OpcodesThatJump) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
@@ -449,7 +449,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
private fun removeDoubleLoadsAndStores(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
var changed = false
|
||||
indexedInstructions.forEach { (idx, ins) ->
|
||||
if(ins.opcode==Opcode.STOREM) {
|
||||
if(ins.opcode==Opcode.STOREM && idx>0) {
|
||||
val prev = indexedInstructions[idx-1].value
|
||||
if(prev.opcode==Opcode.LOADM) {
|
||||
// loadm.X rX,something | storem.X rX,something ?? -> get rid of the store.
|
||||
|
||||
@@ -22,7 +22,8 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
floats = false,
|
||||
noSysInit = true,
|
||||
compTarget = target,
|
||||
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
|
||||
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS,
|
||||
memtopAddress = 0xffffu
|
||||
)
|
||||
val prog = IRProgram("test", IRSymbolTable(), options, target)
|
||||
prog.addBlock(block)
|
||||
|
||||
@@ -23,7 +23,8 @@ class TestVmCodeGen: FunSpec({
|
||||
floats = true,
|
||||
noSysInit = false,
|
||||
compTarget = target,
|
||||
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
|
||||
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS,
|
||||
memtopAddress = 0xffffu
|
||||
)
|
||||
}
|
||||
|
||||
@@ -44,10 +45,42 @@ class TestVmCodeGen: FunSpec({
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
|
||||
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
|
||||
sub.add(PtVariable("particleDX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
|
||||
sub.add(PtVariable("xx", DataType.WORD, ZeropageWish.DONTCARE, PtNumber(DataType.WORD, 1.0, Position.DUMMY), null, Position.DUMMY))
|
||||
sub.add(PtVariable(
|
||||
"pi",
|
||||
DataType.UBYTE,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
PtNumber(DataType.UBYTE, 0.0, Position.DUMMY),
|
||||
null,
|
||||
Position.DUMMY
|
||||
))
|
||||
sub.add(PtVariable(
|
||||
"particleX",
|
||||
DataType.ARRAY_UB,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
null,
|
||||
3u,
|
||||
Position.DUMMY
|
||||
))
|
||||
sub.add(PtVariable(
|
||||
"particleDX",
|
||||
DataType.ARRAY_UB,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
null,
|
||||
3u,
|
||||
Position.DUMMY
|
||||
))
|
||||
sub.add(PtVariable(
|
||||
"xx",
|
||||
DataType.WORD,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
PtNumber(DataType.WORD, 1.0, Position.DUMMY),
|
||||
null,
|
||||
Position.DUMMY
|
||||
))
|
||||
|
||||
val assign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||
val target = PtAssignTarget(false, Position.DUMMY).also {
|
||||
@@ -123,7 +156,15 @@ class TestVmCodeGen: FunSpec({
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||
sub.add(PtVariable(
|
||||
"f1",
|
||||
DataType.FLOAT,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
))
|
||||
val if1 = PtIfElse(Position.DUMMY)
|
||||
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
|
||||
cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||
@@ -186,7 +227,15 @@ class TestVmCodeGen: FunSpec({
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||
sub.add(PtVariable(
|
||||
"f1",
|
||||
DataType.FLOAT,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
))
|
||||
val if1 = PtIfElse(Position.DUMMY)
|
||||
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
|
||||
cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||
@@ -245,7 +294,15 @@ class TestVmCodeGen: FunSpec({
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||
sub.add(PtVariable(
|
||||
"f1",
|
||||
DataType.FLOAT,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
))
|
||||
val if1 = PtIfElse(Position.DUMMY)
|
||||
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
|
||||
cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||
@@ -292,7 +349,15 @@ class TestVmCodeGen: FunSpec({
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||
sub.add(PtVariable(
|
||||
"sb1",
|
||||
DataType.BYTE,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
))
|
||||
val if1 = PtIfElse(Position.DUMMY)
|
||||
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
|
||||
cmp1.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
|
||||
@@ -355,7 +420,15 @@ class TestVmCodeGen: FunSpec({
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||
sub.add(PtVariable(
|
||||
"sb1",
|
||||
DataType.BYTE,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
))
|
||||
val if1 = PtIfElse(Position.DUMMY)
|
||||
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
|
||||
cmp1.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
|
||||
@@ -414,7 +487,15 @@ class TestVmCodeGen: FunSpec({
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("ub1", DataType.UBYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||
sub.add(PtVariable(
|
||||
"ub1",
|
||||
DataType.UBYTE,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
))
|
||||
val if1 = PtIfElse(Position.DUMMY)
|
||||
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
|
||||
cmp1.add(PtIdentifier("main.start.ub1", DataType.UBYTE, Position.DUMMY))
|
||||
@@ -442,9 +523,9 @@ class TestVmCodeGen: FunSpec({
|
||||
irChunks.size shouldBe 1
|
||||
}
|
||||
|
||||
test("romsub allowed in ir-codegen") {
|
||||
test("extsub allowed in ir-codegen") {
|
||||
//main {
|
||||
// romsub $5000 = routine()
|
||||
// extsub $5000 = routine()
|
||||
//
|
||||
// sub start() {
|
||||
// routine()
|
||||
@@ -453,8 +534,8 @@ class TestVmCodeGen: FunSpec({
|
||||
val codegen = VmCodeGen()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val romsub = PtAsmSub("routine", 0x5000u, setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY)
|
||||
block.add(romsub)
|
||||
val extsub = PtAsmSub("routine", PtAsmSub.Address(null, null, 0x5000u), setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY)
|
||||
block.add(extsub)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
val call = PtFunctionCall("main.routine", true, DataType.UNDEFINED, Position.DUMMY)
|
||||
sub.add(call)
|
||||
|
||||
@@ -2,6 +2,7 @@ package prog8.optimizer
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.maySwapOperandOrder
|
||||
import prog8.ast.statements.*
|
||||
@@ -112,6 +113,28 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.left.inferType(program).isArray) {
|
||||
if (expr.operator=="*" && rightconst!=null) {
|
||||
if (expr.left is ArrayLiteral) {
|
||||
// concatenate array literal.
|
||||
val part = expr.left as ArrayLiteral
|
||||
if(part.value.isEmpty())
|
||||
errors.warn("resulting array has length zero", part.position)
|
||||
val tmp = mutableListOf<Expression>()
|
||||
repeat(rightconst.number.toInt()) {
|
||||
part.value.forEach { tmp += it.copy() }
|
||||
}
|
||||
val newArray = ArrayLiteral(part.type, tmp.toTypedArray(), part.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newArray, parent))
|
||||
}
|
||||
else {
|
||||
val leftTarget = (expr.left as? IdentifierReference)?.targetVarDecl(program)
|
||||
if(leftTarget!=null && leftTarget.origin==VarDeclOrigin.ARRAYLITERAL)
|
||||
throw FatalAstException("shouldn't see an array literal converted to an autovar here")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator=="==" && rightconst!=null) {
|
||||
val leftExpr = expr.left as? BinaryExpression
|
||||
// only do this shuffling when the LHS is not a constant itself (otherwise problematic nested replacements)
|
||||
@@ -298,6 +321,31 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(parent is VarDecl && parent.parent is Block) {
|
||||
// only block level (global) initializers are considered here, because they're run just once at program startup
|
||||
|
||||
val const = arrayIndexedExpression.constValue(program)
|
||||
if (const != null)
|
||||
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, const, parent))
|
||||
|
||||
val constIndex = arrayIndexedExpression.indexer.constIndex()
|
||||
if (constIndex != null) {
|
||||
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
|
||||
if(arrayVar!=null) {
|
||||
val array =arrayVar.value as? ArrayLiteral
|
||||
if(array!=null) {
|
||||
val value = array.value[constIndex].constValue(program)
|
||||
if(value!=null) {
|
||||
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, value, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
val constvalue = functionCallExpr.constValue(program)
|
||||
return if(constvalue!=null)
|
||||
|
||||
@@ -76,7 +76,7 @@ class VarConstantValueTypeAdjuster(
|
||||
if (declValue != null) {
|
||||
// variable is never written to, so it can be replaced with a constant, IF the value is a constant
|
||||
errors.info("variable '${decl.name}' is never written to and was replaced by a constant", decl.position)
|
||||
val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.arraysize, decl.name, decl.names, declValue, decl.sharedWithAsm, decl.splitArray, decl.position)
|
||||
val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.arraysize, decl.name, decl.names, declValue, decl.sharedWithAsm, decl.splitArray, decl.alignment, decl.dirty, decl.position)
|
||||
decl.value = null
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(decl, const, parent)
|
||||
@@ -96,7 +96,7 @@ class VarConstantValueTypeAdjuster(
|
||||
}
|
||||
// variable only has a single write and it is the initialization value, so it can be replaced with a constant, IF the value is a constant
|
||||
errors.info("variable '${decl.name}' is never written to and was replaced by a constant", decl.position)
|
||||
val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.arraysize, decl.name, decl.names, singleAssignment.value, decl.sharedWithAsm, decl.splitArray, decl.position)
|
||||
val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.arraysize, decl.name, decl.names, singleAssignment.value, decl.sharedWithAsm, decl.splitArray, decl.alignment, decl.dirty, decl.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(decl, const, parent),
|
||||
IAstModification.Remove(singleAssignment, singleAssignment.parent as IStatementContainer)
|
||||
@@ -268,11 +268,9 @@ class VarConstantValueTypeAdjuster(
|
||||
|
||||
|
||||
// Replace all constant identifiers with their actual value,
|
||||
// and the array var initializer values and sizes.
|
||||
// This is needed because further constant optimizations depend on those.
|
||||
internal class ConstantIdentifierReplacer(
|
||||
private val program: Program,
|
||||
private val options: CompilationOptions,
|
||||
private val errors: IErrorReporter
|
||||
) : AstWalker() {
|
||||
|
||||
@@ -287,7 +285,7 @@ internal class ConstantIdentifierReplacer(
|
||||
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
|
||||
// replace identifiers that refer to const value, with the value itself
|
||||
// if it's a simple type and if it's not a left hand side variable
|
||||
if(identifier.parent is AssignTarget)
|
||||
if(identifier.parent is AssignTarget || identifier.parent is Alias)
|
||||
return noModifications
|
||||
var forloop = identifier.parent as? ForLoop
|
||||
if(forloop==null)
|
||||
@@ -400,7 +398,7 @@ internal class ConstantIdentifierReplacer(
|
||||
if(targetDatatype.isArray) {
|
||||
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.ARRAYLITERAL, targetDatatype.getOr(DataType.UNDEFINED),
|
||||
ZeropageWish.DONTCARE, null, "dummy", emptyList(),
|
||||
assignment.value, false, false, Position.DUMMY)
|
||||
assignment.value, false, false, 0u, false, Position.DUMMY)
|
||||
val replaceValue = createConstArrayInitializerValue(decl)
|
||||
if(replaceValue!=null) {
|
||||
return listOf(IAstModification.ReplaceNode(assignment.value, replaceValue, assignment))
|
||||
@@ -421,104 +419,41 @@ internal class ConstantIdentifierReplacer(
|
||||
return null
|
||||
}
|
||||
|
||||
// convert the initializer range expression from a range or int, to an actual array.
|
||||
val rangeExpr = decl.value as? RangeExpression ?: return null
|
||||
|
||||
// convert the initializer range expression from a range, to an actual array literal.
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange?.isEmpty()==true) {
|
||||
if(constRange.first>constRange.last && constRange.step>=0)
|
||||
errors.err("descending range with positive step", decl.value?.position!!)
|
||||
else if(constRange.first<constRange.last && constRange.step<=0)
|
||||
errors.err("ascending range with negative step", decl.value?.position!!)
|
||||
}
|
||||
when(decl.datatype) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_UW_SPLIT -> {
|
||||
val rangeExpr = decl.value as? RangeExpression
|
||||
if(rangeExpr!=null) {
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange?.isEmpty()==true) {
|
||||
if(constRange.first>constRange.last && constRange.step>=0)
|
||||
errors.err("descending range with positive step", decl.value?.position!!)
|
||||
else if(constRange.first<constRange.last && constRange.step<=0)
|
||||
errors.err("ascending range with negative step", decl.value?.position!!)
|
||||
}
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||
if(constRange!=null) {
|
||||
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
|
||||
return if(eltType in ByteDatatypes) {
|
||||
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
} else {
|
||||
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
val numericLv = decl.value as? NumericLiteral
|
||||
if(numericLv!=null && numericLv.type== DataType.FLOAT)
|
||||
errors.err("arraysize requires only integers here", numericLv.position)
|
||||
val size = decl.arraysize?.constIndex() ?: return null
|
||||
if (rangeExpr==null && numericLv!=null) {
|
||||
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
|
||||
val fillvalue = numericLv.number.toInt()
|
||||
when(decl.datatype){
|
||||
DataType.ARRAY_UB -> {
|
||||
if(fillvalue !in 0..255)
|
||||
errors.err("ubyte value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_B -> {
|
||||
if(fillvalue !in -128..127)
|
||||
errors.err("byte value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_UW -> {
|
||||
if(fillvalue !in 0..65535)
|
||||
errors.err("uword value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_W -> {
|
||||
if(fillvalue !in -32768..32767)
|
||||
errors.err("word value overflow", numericLv.position)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteral(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
|
||||
return ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
val rangeExpr = decl.value as? RangeExpression
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array of floats
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
||||
constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||
if(constRange!=null) {
|
||||
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
|
||||
return if(eltType in ByteDatatypes) {
|
||||
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
} else {
|
||||
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
}
|
||||
}
|
||||
|
||||
val numericLv = decl.value as? NumericLiteral
|
||||
val size = decl.arraysize?.constIndex() ?: return null
|
||||
if(rangeExpr==null && numericLv!=null) {
|
||||
// arraysize initializer is a single int, and we know the array size.
|
||||
val fillvalue = numericLv.number
|
||||
if (fillvalue < options.compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > options.compTarget.machine.FLOAT_MAX_POSITIVE)
|
||||
errors.err("float value overflow", numericLv.position)
|
||||
else {
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
|
||||
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_BOOL -> {
|
||||
val numericLv = decl.value as? NumericLiteral
|
||||
val size = decl.arraysize?.constIndex() ?: return null
|
||||
if(numericLv!=null) {
|
||||
// arraysize initializer is a single value, and we know the array size.
|
||||
if(numericLv.type!=DataType.BOOL) {
|
||||
errors.err("initializer value is not a boolean", numericLv.position)
|
||||
return null
|
||||
}
|
||||
val array = Array(size) {numericLv.number}.map { NumericLiteral(DataType.BOOL, it, numericLv.position) }.toTypedArray<Expression>()
|
||||
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_BOOL), array, position = numericLv.position)
|
||||
DataType.ARRAY_F -> {
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||
if(constRange!=null) {
|
||||
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
||||
constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
}
|
||||
}
|
||||
else -> return null
|
||||
|
||||
@@ -238,11 +238,8 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if(expr.operator=="==") {
|
||||
if(rightDt==DataType.BOOL && leftDt==DataType.BOOL) {
|
||||
val rightConstBool = rightVal?.asBooleanValue
|
||||
if(rightConstBool!=null) {
|
||||
return if(rightConstBool)
|
||||
listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
|
||||
else
|
||||
listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
|
||||
if(rightConstBool==true) {
|
||||
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
|
||||
}
|
||||
}
|
||||
if (rightVal?.number == 1.0) {
|
||||
@@ -261,11 +258,8 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if (expr.operator=="!=") {
|
||||
if(rightDt==DataType.BOOL && leftDt==DataType.BOOL) {
|
||||
val rightConstBool = rightVal?.asBooleanValue
|
||||
if(rightConstBool!=null) {
|
||||
return if (rightConstBool)
|
||||
listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
|
||||
else
|
||||
listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
|
||||
if(rightConstBool==false) {
|
||||
listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
|
||||
}
|
||||
}
|
||||
if (rightVal?.number == 1.0) {
|
||||
@@ -416,21 +410,6 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator=="not") {
|
||||
// not X <compare> Y -> X <invertedcompare> Y
|
||||
val binExpr = expr.expression as? BinaryExpression
|
||||
if(binExpr!=null) {
|
||||
val invertedOperator = invertedComparisonOperator(binExpr.operator)
|
||||
if(invertedOperator!=null) {
|
||||
val inverted = BinaryExpression(binExpr.left, invertedOperator, binExpr.right, binExpr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, inverted, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun applyAbsorptionLaws(expr: BinaryExpression): Expression? {
|
||||
// NOTE: only when the terms are not function calls!!!
|
||||
if(expr.left is IFunctionCall || expr.right is IFunctionCall)
|
||||
|
||||
@@ -12,7 +12,7 @@ fun Program.constantFold(errors: IErrorReporter, options: CompilationOptions) {
|
||||
if(errors.noErrors()) {
|
||||
valuetypefixer.applyModifications()
|
||||
|
||||
val replacer = ConstantIdentifierReplacer(this, options, errors)
|
||||
val replacer = ConstantIdentifierReplacer(this, errors)
|
||||
replacer.visit(this)
|
||||
if (errors.noErrors()) {
|
||||
replacer.applyModifications()
|
||||
|
||||
@@ -127,19 +127,6 @@ class StatementOptimizer(private val program: Program,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// switch if/else around if the condition is a not
|
||||
val prefixCond = ifElse.condition as? PrefixExpression
|
||||
if(prefixCond?.operator=="not") {
|
||||
errors.info("invert conditon and swap if/else blocks", ifElse.condition.position)
|
||||
val newTruePart = AnonymousScope(ifElse.elsepart.statements, ifElse.elsepart.position)
|
||||
val newElsePart = AnonymousScope(ifElse.truepart.statements, ifElse.truepart.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(ifElse.elsepart, newElsePart, ifElse),
|
||||
IAstModification.ReplaceNode(ifElse.truepart, newTruePart, ifElse),
|
||||
IAstModification.ReplaceNode(ifElse.condition, prefixCond.expression, ifElse)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
|
||||
@@ -21,6 +21,31 @@ class UnusedCodeRemover(private val program: Program,
|
||||
): AstWalker() {
|
||||
|
||||
private lateinit var callgraph: CallGraph
|
||||
private val neverRemoveSubroutines = mutableListOf<Subroutine>()
|
||||
|
||||
init {
|
||||
neverRemoveSubroutines.add(program.entrypoint)
|
||||
|
||||
program.allBlocks.singleOrNull { it.name=="sys" } ?.let {
|
||||
val subroutines = it.statements.filterIsInstance<Subroutine>()
|
||||
val push = subroutines.single { it.name == "push" }
|
||||
val pushw = subroutines.single { it.name == "pushw" }
|
||||
val pop = subroutines.single { it.name == "pop" }
|
||||
val popw = subroutines.single { it.name == "popw" }
|
||||
neverRemoveSubroutines.add(push)
|
||||
neverRemoveSubroutines.add(pushw)
|
||||
neverRemoveSubroutines.add(pop)
|
||||
neverRemoveSubroutines.add(popw)
|
||||
}
|
||||
|
||||
program.allBlocks.singleOrNull { it.name=="floats" } ?.let {
|
||||
val subroutines = it.statements.filterIsInstance<Subroutine>()
|
||||
val push = subroutines.single { it.name == "push" }
|
||||
val pop = subroutines.single { it.name == "pop" }
|
||||
neverRemoveSubroutines.add(push)
|
||||
neverRemoveSubroutines.add(pop)
|
||||
}
|
||||
}
|
||||
|
||||
override fun before(program: Program): Iterable<IAstModification> {
|
||||
callgraph = CallGraph(program)
|
||||
@@ -92,7 +117,7 @@ class UnusedCodeRemover(private val program: Program,
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
val forceOutput = "force_output" in subroutine.definingBlock.options()
|
||||
if (subroutine !== program.entrypoint && !forceOutput && !subroutine.isAsmSubroutine) {
|
||||
if (subroutine !in neverRemoveSubroutines && !forceOutput && !subroutine.isAsmSubroutine) {
|
||||
if(callgraph.unused(subroutine)) {
|
||||
if(subroutine.containsNoCodeNorVars) {
|
||||
if("ignore_unused" !in subroutine.definingBlock.options())
|
||||
|
||||
@@ -32,6 +32,7 @@ dependencies {
|
||||
implementation project(':codeGenIntermediate')
|
||||
implementation project(':codeGenExperimental')
|
||||
implementation project(':virtualmachine')
|
||||
// implementation project(':beanshell')
|
||||
implementation "org.antlr:antlr4-runtime:4.13.2"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
@@ -80,11 +81,11 @@ startScripts.enabled = true
|
||||
|
||||
application {
|
||||
mainClass = 'prog8.CompilerMainKt'
|
||||
applicationName = 'p8compile'
|
||||
applicationName = 'prog8c'
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
archiveBaseName = 'prog8compiler'
|
||||
archiveBaseName = 'prog8c'
|
||||
archiveVersion = version
|
||||
// minimize()
|
||||
}
|
||||
|
||||
@@ -12,16 +12,16 @@ atari {
|
||||
&uword COLCRS = 85
|
||||
&ubyte ROWCRS = 84
|
||||
|
||||
romsub $F24A = getchar() -> ubyte @A
|
||||
romsub $F2B0 = outchar(ubyte character @ A)
|
||||
romsub $F2FD = waitkey() -> ubyte @A
|
||||
extsub $F24A = getchar() -> ubyte @A
|
||||
extsub $F2B0 = outchar(ubyte character @ A)
|
||||
extsub $F2FD = waitkey() -> ubyte @A
|
||||
|
||||
}
|
||||
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
const ubyte target = 8 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16, 8 = atari800XL
|
||||
const ubyte target = 8 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
|
||||
|
||||
const ubyte sizeof_bool = 1
|
||||
const ubyte sizeof_byte = 1
|
||||
@@ -30,43 +30,6 @@ sys {
|
||||
const ubyte sizeof_uword = 2
|
||||
const ubyte sizeof_float = 0 ; undefined, no float support
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
; TODO
|
||||
%asm {{
|
||||
sei
|
||||
; TODO reset screen mode etc etc
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
cld
|
||||
clc
|
||||
clv
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
%asm {{
|
||||
_exitcodeCarry = *+1
|
||||
lda #0
|
||||
lsr a
|
||||
_exitcode = *+1
|
||||
lda #0 ; exit code possibly modified in exit()
|
||||
_exitcodeX = *+1
|
||||
ldx #0
|
||||
_exitcodeY = *+1
|
||||
ldy #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub reset_system() {
|
||||
; Soft-reset the system back to initial power-on Basic prompt.
|
||||
; TODO
|
||||
@@ -177,6 +140,65 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcmp(uword address1 @R0, uword address2 @R1, uword size @AY) -> byte @A {
|
||||
; Compares two blocks of memory
|
||||
; Returns -1 (255), 0 or 1, meaning: block 1 sorts before, equal or after block 2.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_REG ; lsb(size)
|
||||
sty P8ZP_SCRATCH_B1 ; msb(size)
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r1
|
||||
ldy cx16.r1+1
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
|
||||
ldx P8ZP_SCRATCH_B1
|
||||
beq _no_msb_size
|
||||
|
||||
_loop_msb_size
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bcs +
|
||||
lda #-1
|
||||
rts
|
||||
+ beq +
|
||||
lda #1
|
||||
rts
|
||||
+ iny
|
||||
bne -
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
dec P8ZP_SCRATCH_B1 ; msb(size) -= 1
|
||||
dex
|
||||
bne _loop_msb_size
|
||||
|
||||
_no_msb_size
|
||||
lda P8ZP_SCRATCH_REG ; lsb(size)
|
||||
bne +
|
||||
rts
|
||||
|
||||
+ ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bcs +
|
||||
lda #-1
|
||||
rts
|
||||
+ beq +
|
||||
lda #1
|
||||
rts
|
||||
+ iny
|
||||
cpy P8ZP_SCRATCH_REG ; lsb(size)
|
||||
bne -
|
||||
|
||||
lda #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub read_flags() -> ubyte @A {
|
||||
%asm {{
|
||||
php
|
||||
@@ -248,6 +270,7 @@ save_SCRATCH_ZPB1 .byte 0
|
||||
save_SCRATCH_ZPREG .byte 0
|
||||
save_SCRATCH_ZPWORD1 .word 0
|
||||
save_SCRATCH_ZPWORD2 .word 0
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -272,37 +295,37 @@ save_SCRATCH_ZPWORD2 .word 0
|
||||
asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
sta cleanup_at_exit._exitcode
|
||||
sta p8_sys_startup.cleanup_at_exit._exitcode
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
jmp cleanup_at_exit
|
||||
jmp p8_sys_startup.cleanup_at_exit
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub exit2(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y) {
|
||||
; -- immediately exit the program with result values in the A, X and Y registers.
|
||||
%asm {{
|
||||
sta cleanup_at_exit._exitcode
|
||||
stx cleanup_at_exit._exitcodeX
|
||||
sty cleanup_at_exit._exitcodeY
|
||||
sta p8_sys_startup.cleanup_at_exit._exitcode
|
||||
stx p8_sys_startup.cleanup_at_exit._exitcodeX
|
||||
sty p8_sys_startup.cleanup_at_exit._exitcodeY
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
jmp cleanup_at_exit
|
||||
jmp p8_sys_startup.cleanup_at_exit
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub exit3(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y, bool carry @Pc) {
|
||||
; -- immediately exit the program with result values in the A, X and Y registers, and the Carry flag in the status register.
|
||||
%asm {{
|
||||
sta cleanup_at_exit._exitcode
|
||||
sta p8_sys_startup.cleanup_at_exit._exitcode
|
||||
lda #0
|
||||
rol a
|
||||
sta cleanup_at_exit._exitcodeCarry
|
||||
stx cleanup_at_exit._exitcodeX
|
||||
sty cleanup_at_exit._exitcodeY
|
||||
sta p8_sys_startup.cleanup_at_exit._exitcodeCarry
|
||||
stx p8_sys_startup.cleanup_at_exit._exitcodeX
|
||||
sty p8_sys_startup.cleanup_at_exit._exitcodeY
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
jmp cleanup_at_exit
|
||||
jmp p8_sys_startup.cleanup_at_exit
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -313,6 +336,13 @@ save_SCRATCH_ZPWORD2 .word 0
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub progstart() -> uword @AY {
|
||||
%asm {{
|
||||
lda #<prog8_program_start
|
||||
ldy #>prog8_program_start
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub push(ubyte value @A) {
|
||||
%asm {{
|
||||
pha
|
||||
@@ -461,6 +491,7 @@ cx16 {
|
||||
_cx16_vreg_storage
|
||||
.word 0,0,0,0,0,0,0,0
|
||||
.word 0,0,0,0,0,0,0,0
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -481,3 +512,44 @@ cx16 {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
p8_sys_startup {
|
||||
; program startup and shutdown machinery. Needs to reside in normal system ram.
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
; TODO
|
||||
%asm {{
|
||||
sei
|
||||
; TODO reset screen mode etc etc
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
cld
|
||||
clc
|
||||
clv
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
%asm {{
|
||||
_exitcodeCarry = *+1
|
||||
lda #0
|
||||
lsr a
|
||||
_exitcode = *+1
|
||||
lda #0 ; exit code possibly modified in exit()
|
||||
_exitcodeX = *+1
|
||||
ldx #0
|
||||
_exitcodeY = *+1
|
||||
ldy #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
%import shared_cbm_diskio
|
||||
|
||||
; No alterations here; everything is taken from the shared module.
|
||||
|
||||
@@ -56,51 +56,49 @@ cbm {
|
||||
; CLEARSCR -> use txt.clear_screen
|
||||
; HOMECRSR -> use txt.home or txt.plot
|
||||
|
||||
romsub $FA65 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
|
||||
romsub $FF33 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
|
||||
extsub $FA65 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
|
||||
extsub $FF33 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
|
||||
|
||||
; TODO c128 a bunch of kernal routines are missing here that are specific to the c128
|
||||
|
||||
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
|
||||
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||
romsub $FF8D = VECTOR(uword userptr @ XY, bool dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set top of memory pointer
|
||||
romsub $FF9C = MEMBOT(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
|
||||
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
|
||||
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
|
||||
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
|
||||
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word (use CLEARST to reset it to 0)
|
||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
|
||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
romsub $FFC0 = OPEN() clobbers(X,Y) -> bool @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFD2 = CHROUT(ubyte character @ A) ; (via 806 ($326)) output a character
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
|
||||
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A) also see STOP2
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character also see GETIN2
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; get current window dimensions into X (columns) and Y (rows) NOTE: changed behavior compared to VIC/C64/PET SCREEN() routine!
|
||||
romsub $FFED = SCRORG() -> ubyte @ X, ubyte @ Y ; get current window dimensions into X (columns) and Y (rows) NOTE: changed behavior compared to VIC/C64/PET SCREEN() routine!
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
extsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||
extsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||
extsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
|
||||
extsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||
extsub $FF8D = VECTOR(uword userptr @ XY, bool dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||
extsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||
extsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||
extsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||
extsub $FF99 = MEMTOP(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set top of memory pointer
|
||||
extsub $FF9C = MEMBOT(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||
extsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||
extsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||
extsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
|
||||
extsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
|
||||
extsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
|
||||
extsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
|
||||
extsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||
extsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||
extsub $FFB7 = READST() -> ubyte @ A ; read I/O status word (use CLEARST to reset it to 0)
|
||||
extsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
|
||||
extsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
extsub $FFC0 = OPEN() clobbers(X,Y) -> bool @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||
extsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
extsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; (via 798 ($31E)) define an input channel
|
||||
extsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||
extsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
extsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
extsub $FFD2 = CHROUT(ubyte character @ A) ; (via 806 ($326)) output a character
|
||||
extsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||
extsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
extsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
extsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
|
||||
extsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A) also see STOP2
|
||||
extsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character also see GETIN2
|
||||
extsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
extsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
extsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; get current window dimensions into X (columns) and Y (rows) NOTE: changed behavior compared to VIC/C64/PET SCREEN() routine!
|
||||
extsub $FFED = SCRORG() -> ubyte @ X, ubyte @ Y ; get current window dimensions into X (columns) and Y (rows) NOTE: changed behavior compared to VIC/C64/PET SCREEN() routine!
|
||||
extsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||
extsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
|
||||
; ---- end of C64 compatible ROM kernal routines ----
|
||||
|
||||
@@ -319,8 +317,27 @@ c128 {
|
||||
&ubyte VM4 = $0A2F ; starting page for VDC attribute mem
|
||||
|
||||
|
||||
; TODO c128 a bunch of kernal routines are missing here that are specific to the c128
|
||||
|
||||
extsub $FF6E = JSRFAR()
|
||||
|
||||
|
||||
; ---- C128 specific system utility routines: ----
|
||||
|
||||
inline asmsub banks(ubyte banks @A) {
|
||||
; -- set the memory bank configuration MMU register
|
||||
%asm {{
|
||||
sta $FF00
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub getbanks() -> ubyte @A {
|
||||
; -- get the current memory bank configuration from the MMU register
|
||||
%asm {{
|
||||
lda $FF00
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub disable_basic() clobbers(A) {
|
||||
%asm {{
|
||||
lda $0a04 ; disable BASIC shadow registers
|
||||
@@ -342,6 +359,56 @@ asmsub disable_basic() clobbers(A) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub x16jsrfar() {
|
||||
%asm {{
|
||||
; setup a JSRFAR call (using X16 call convention)
|
||||
; see https://cx16.dk/c128-kernal-routines/jsrfar.html
|
||||
sty $08 ; save registers
|
||||
stx $07
|
||||
sta $06
|
||||
php ; including PSR
|
||||
pla
|
||||
sta $05
|
||||
|
||||
pla ; get original return address
|
||||
sta $fa ; and store it in a temp ZP pointer
|
||||
pla
|
||||
sta $fb
|
||||
|
||||
ldy #$01
|
||||
lda ($fa),y ; grab low byte of target address
|
||||
sta $04
|
||||
iny
|
||||
lda ($fa),y ; now the high byte
|
||||
sta $03
|
||||
iny
|
||||
lda ($fa),y ; then the target bank
|
||||
sta $02
|
||||
|
||||
; replace the original return address by it + 3 to skip the data bytes
|
||||
clc
|
||||
lda $fa
|
||||
adc #3
|
||||
sta $fa
|
||||
lda $fb
|
||||
adc #0
|
||||
pha
|
||||
lda $fa
|
||||
pha
|
||||
|
||||
jsr c128.JSRFAR ; call kernal's jsrfar routine
|
||||
|
||||
lda $05 ; populate registers
|
||||
pha
|
||||
lda $06
|
||||
ldx $07
|
||||
ldy $08
|
||||
plp
|
||||
|
||||
rts ; and return
|
||||
}}
|
||||
}
|
||||
|
||||
; ---- end of C128 specific system utility routines ----
|
||||
|
||||
}
|
||||
@@ -349,7 +416,7 @@ asmsub disable_basic() clobbers(A) {
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
const ubyte target = 128 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
|
||||
const ubyte target = 128 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
|
||||
|
||||
const ubyte sizeof_bool = 1
|
||||
const ubyte sizeof_byte = 1
|
||||
@@ -359,80 +426,13 @@ sys {
|
||||
const ubyte sizeof_float = 5
|
||||
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in,
|
||||
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
|
||||
; Also a different color scheme is chosen to identify ourselves a little.
|
||||
; Uppercase charset is activated.
|
||||
%asm {{
|
||||
sei
|
||||
lda #0
|
||||
sta $ff00 ; select default bank 15
|
||||
jsr cbm.IOINIT
|
||||
jsr cbm.RESTOR
|
||||
jsr cbm.CINT
|
||||
lda #6
|
||||
sta c64.EXTCOL
|
||||
lda #7
|
||||
sta cbm.COLOR
|
||||
lda #0
|
||||
sta c64.BGCOL0
|
||||
jsr disable_runstop_and_charsetswitch
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
sub disable_runstop_and_charsetswitch() {
|
||||
p8_sys_startup.disable_runstop_and_charsetswitch()
|
||||
}
|
||||
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
cld
|
||||
clc
|
||||
clv
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
%asm {{
|
||||
lda #0
|
||||
sta $ff00 ; default bank 15
|
||||
jsr cbm.CLRCHN ; reset i/o channels
|
||||
jsr enable_runstop_and_charsetswitch
|
||||
_exitcodeCarry = *+1
|
||||
lda #0
|
||||
lsr a
|
||||
_exitcode = *+1
|
||||
lda #0 ; exit code possibly modified in exit()
|
||||
_exitcodeX = *+1
|
||||
ldx #0
|
||||
_exitcodeY = *+1
|
||||
ldy #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub disable_runstop_and_charsetswitch() clobbers(A) {
|
||||
%asm {{
|
||||
lda #$80
|
||||
sta 247 ; disable charset switching
|
||||
lda #112
|
||||
sta 808 ; disable run/stop key
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub enable_runstop_and_charsetswitch() clobbers(A) {
|
||||
%asm {{
|
||||
lda #0
|
||||
sta 247 ; enable charset switching
|
||||
lda #110
|
||||
sta 808 ; enable run/stop key
|
||||
rts
|
||||
}}
|
||||
}
|
||||
sub enable_runstop_and_charsetswitch() {
|
||||
p8_sys_startup.enable_runstop_and_charsetswitch()
|
||||
}
|
||||
|
||||
asmsub save_prog8_internals() {
|
||||
%asm {{
|
||||
@@ -453,6 +453,7 @@ save_SCRATCH_ZPB1 .byte 0
|
||||
save_SCRATCH_ZPREG .byte 0
|
||||
save_SCRATCH_ZPWORD1 .word 0
|
||||
save_SCRATCH_ZPWORD2 .word 0
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -504,8 +505,6 @@ _modified
|
||||
tax
|
||||
pla
|
||||
rti
|
||||
|
||||
_use_kernal .byte 0
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -727,6 +726,65 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcmp(uword address1 @R0, uword address2 @R1, uword size @AY) -> byte @A {
|
||||
; Compares two blocks of memory
|
||||
; Returns -1 (255), 0 or 1, meaning: block 1 sorts before, equal or after block 2.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_REG ; lsb(size)
|
||||
sty P8ZP_SCRATCH_B1 ; msb(size)
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r1
|
||||
ldy cx16.r1+1
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
|
||||
ldx P8ZP_SCRATCH_B1
|
||||
beq _no_msb_size
|
||||
|
||||
_loop_msb_size
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bcs +
|
||||
lda #-1
|
||||
rts
|
||||
+ beq +
|
||||
lda #1
|
||||
rts
|
||||
+ iny
|
||||
bne -
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
dec P8ZP_SCRATCH_B1 ; msb(size) -= 1
|
||||
dex
|
||||
bne _loop_msb_size
|
||||
|
||||
_no_msb_size
|
||||
lda P8ZP_SCRATCH_REG ; lsb(size)
|
||||
bne +
|
||||
rts
|
||||
|
||||
+ ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bcs +
|
||||
lda #-1
|
||||
rts
|
||||
+ beq +
|
||||
lda #1
|
||||
rts
|
||||
+ iny
|
||||
cpy P8ZP_SCRATCH_REG ; lsb(size)
|
||||
bne -
|
||||
|
||||
lda #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub read_flags() -> ubyte @A {
|
||||
%asm {{
|
||||
php
|
||||
@@ -788,37 +846,37 @@ _longcopy
|
||||
asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
sta cleanup_at_exit._exitcode
|
||||
sta p8_sys_startup.cleanup_at_exit._exitcode
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
jmp cleanup_at_exit
|
||||
jmp p8_sys_startup.cleanup_at_exit
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub exit2(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y) {
|
||||
; -- immediately exit the program with result values in the A, X and Y registers.
|
||||
%asm {{
|
||||
sta cleanup_at_exit._exitcode
|
||||
stx cleanup_at_exit._exitcodeX
|
||||
sty cleanup_at_exit._exitcodeY
|
||||
sta p8_sys_startup.cleanup_at_exit._exitcode
|
||||
stx p8_sys_startup.cleanup_at_exit._exitcodeX
|
||||
sty p8_sys_startup.cleanup_at_exit._exitcodeY
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
jmp cleanup_at_exit
|
||||
jmp p8_sys_startup.cleanup_at_exit
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub exit3(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y, bool carry @Pc) {
|
||||
; -- immediately exit the program with result values in the A, X and Y registers, and the Carry flag in the status register.
|
||||
%asm {{
|
||||
sta cleanup_at_exit._exitcode
|
||||
sta p8_sys_startup.cleanup_at_exit._exitcode
|
||||
lda #0
|
||||
rol a
|
||||
sta cleanup_at_exit._exitcodeCarry
|
||||
stx cleanup_at_exit._exitcodeX
|
||||
sty cleanup_at_exit._exitcodeY
|
||||
sta p8_sys_startup.cleanup_at_exit._exitcodeCarry
|
||||
stx p8_sys_startup.cleanup_at_exit._exitcodeX
|
||||
sty p8_sys_startup.cleanup_at_exit._exitcodeY
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
jmp cleanup_at_exit
|
||||
jmp p8_sys_startup.cleanup_at_exit
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -829,6 +887,13 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub progstart() -> uword @AY {
|
||||
%asm {{
|
||||
lda #<prog8_program_start
|
||||
ldy #>prog8_program_start
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub push(ubyte value @A) {
|
||||
%asm {{
|
||||
pha
|
||||
@@ -978,6 +1043,7 @@ cx16 {
|
||||
_cx16_vreg_storage
|
||||
.word 0,0,0,0,0,0,0,0
|
||||
.word 0,0,0,0,0,0,0,0
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -998,3 +1064,85 @@ cx16 {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
p8_sys_startup {
|
||||
; program startup and shutdown machinery. Needs to reside in normal system ram.
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in,
|
||||
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
|
||||
; Also a different color scheme is chosen to identify ourselves a little.
|
||||
; Uppercase charset is activated.
|
||||
%asm {{
|
||||
sei
|
||||
lda #0
|
||||
sta $ff00 ; select default bank 15
|
||||
jsr cbm.IOINIT
|
||||
jsr cbm.RESTOR
|
||||
jsr cbm.CINT
|
||||
lda #%00001110
|
||||
sta $ff00 ; bank out basic rom so we have ram from $1c00-$bfff
|
||||
lda #6
|
||||
sta c64.EXTCOL
|
||||
lda #7
|
||||
sta cbm.COLOR
|
||||
lda #0
|
||||
sta c64.BGCOL0
|
||||
jsr disable_runstop_and_charsetswitch
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
cld
|
||||
clc
|
||||
clv
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
%asm {{
|
||||
lda #0
|
||||
sta $ff00 ; default bank 15
|
||||
jsr cbm.CLRCHN ; reset i/o channels
|
||||
jsr enable_runstop_and_charsetswitch
|
||||
_exitcodeCarry = *+1
|
||||
lda #0
|
||||
lsr a
|
||||
_exitcode = *+1
|
||||
lda #0 ; exit code possibly modified in exit()
|
||||
_exitcodeX = *+1
|
||||
ldx #0
|
||||
_exitcodeY = *+1
|
||||
ldy #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub disable_runstop_and_charsetswitch() clobbers(A) {
|
||||
%asm {{
|
||||
lda #$80
|
||||
sta 247 ; disable charset switching
|
||||
lda #112
|
||||
sta 808 ; disable run/stop key
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub enable_runstop_and_charsetswitch() clobbers(A) {
|
||||
%asm {{
|
||||
lda #0
|
||||
sta 247 ; enable charset switching
|
||||
lda #110
|
||||
sta 808 ; enable run/stop key
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,7 +13,7 @@ txt {
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
|
||||
|
||||
sub clear_screen() {
|
||||
@@ -314,6 +314,7 @@ _mod sta $ffff ; modified
|
||||
rts
|
||||
|
||||
_screenrows .word cbm.Screen + range(0, 1000, 40)
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -357,6 +358,7 @@ _mod sta $ffff ; modified
|
||||
rts
|
||||
|
||||
_colorrows .word $d800 + range(0, 1000, 40)
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
%import shared_cbm_diskio
|
||||
|
||||
; No alterations here; everything is taken from the shared module.
|
||||
|
||||
@@ -120,12 +120,8 @@ cast_FAC1_as_uw_into_ya .proc ; also used for float 2 ub
|
||||
.pend
|
||||
|
||||
cast_FAC1_as_w_into_ay .proc ; also used for float 2 b
|
||||
; -- cast fac1 to word into A/Y
|
||||
; clobbers X
|
||||
jsr AYINT
|
||||
ldy floats.AYINT_facmo
|
||||
lda floats.AYINT_facmo+1
|
||||
rts
|
||||
; -- cast fac1 to word into A/Y. clobbers X
|
||||
jmp AYINT2
|
||||
.pend
|
||||
|
||||
|
||||
|
||||
@@ -15,21 +15,21 @@ floats {
|
||||
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
|
||||
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
|
||||
|
||||
romsub $bba2 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
|
||||
romsub $bba6 = FREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac1
|
||||
romsub $ba8c = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
|
||||
romsub $ba90 = FAREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac2
|
||||
romsub $bbfc = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
||||
romsub $bc0c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded the least significant bit)
|
||||
romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||
extsub $bba2 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
|
||||
extsub $bba6 = FREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac1
|
||||
extsub $ba8c = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
|
||||
extsub $ba90 = FAREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac2
|
||||
extsub $bbfc = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
||||
extsub $bc0c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded the least significant bit)
|
||||
extsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||
extsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||
|
||||
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
||||
; (tip: use floats.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||
romsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
||||
extsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
||||
|
||||
romsub $bc9b = QINT() clobbers(A,X,Y) ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
|
||||
romsub $b1bf = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
|
||||
extsub $bc9b = QINT() clobbers(A,X,Y) ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
|
||||
extsub $b1bf = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY) DON'T USE THIS, USE WRAPPER 'AYINT2' INSTEAD.
|
||||
|
||||
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
|
||||
; (tip: use floats.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
|
||||
@@ -37,49 +37,63 @@ romsub $b1bf = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101
|
||||
; there is also floats.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
|
||||
; there is also floats.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
|
||||
; there is also floats.FREAD{S,U}24AXY that read (un)signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
|
||||
romsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
||||
extsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
||||
|
||||
romsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
|
||||
romsub $bc3c = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
|
||||
romsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $22/23 must point to string, A=string length. Also see parse()
|
||||
romsub $aabc = FPRINTLN() clobbers(A,X,Y) ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
|
||||
romsub $bddd = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY ($0100)
|
||||
extsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
|
||||
extsub $bc3c = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
|
||||
extsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $22/23 must point to string, A=string length. Also see parse()
|
||||
extsub $aabc = FPRINTLN() clobbers(A,X,Y) ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
|
||||
extsub $bddd = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY ($0100)
|
||||
|
||||
romsub $b849 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for integer rounding- call this before INT
|
||||
romsub $bae2 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||
romsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! You have to fix sign manually!
|
||||
romsub $bc5b = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||
extsub $b849 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for integer rounding- call this before INT
|
||||
extsub $bae2 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||
extsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! You have to fix sign manually!
|
||||
extsub $bc5b = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||
|
||||
romsub $b86a = FADDT() clobbers(A,X,Y) ; fac1 += fac2
|
||||
romsub $b867 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
|
||||
romsub $b853 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
|
||||
romsub $b850 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
|
||||
romsub $ba2b = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
|
||||
romsub $ba28 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
|
||||
romsub $bb12 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 mind the order of the operands
|
||||
romsub $bb0f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1
|
||||
romsub $bf7b = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||
romsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
|
||||
romsub $bd7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||
extsub $b86a = FADDT() clobbers(A,X,Y) ; fac1 += fac2
|
||||
extsub $b867 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
|
||||
extsub $b853 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
|
||||
extsub $b850 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
|
||||
extsub $ba2b = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
|
||||
extsub $ba28 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
|
||||
extsub $bb12 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 mind the order of the operands
|
||||
extsub $bb0f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1
|
||||
extsub $bf7b = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||
extsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
|
||||
extsub $bd7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||
|
||||
romsub $aed4 = NOTOP() clobbers(A,X,Y) ; fac1 = NOT(fac1)
|
||||
romsub $bccc = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to integer round instead of trunc
|
||||
romsub $b9ea = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
|
||||
romsub $bc39 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
||||
romsub $bc2b = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||
romsub $bc58 = ABS() ; fac1 = ABS(fac1)
|
||||
romsub $bf71 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||
romsub $bf74 = SQRA() clobbers(A,X,Y) ; fac1 = SQRT(fac2)
|
||||
romsub $bfed = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
||||
romsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
|
||||
romsub $b8d7 = NORMAL() clobbers(A) ; normalize FAC1
|
||||
romsub $e097 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
||||
romsub $e264 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
||||
romsub $e26b = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
||||
romsub $e2b4 = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
|
||||
romsub $e30e = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
|
||||
extsub $aed4 = NOTOP() clobbers(A,X,Y) ; fac1 = NOT(fac1)
|
||||
extsub $bccc = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to integer round instead of trunc
|
||||
extsub $b9ea = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
|
||||
extsub $bc39 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
||||
extsub $bc2b = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||
extsub $bc58 = ABS() ; fac1 = ABS(fac1)
|
||||
extsub $bf71 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||
extsub $bf74 = SQRA() clobbers(A,X,Y) ; fac1 = SQRT(fac2)
|
||||
extsub $bfed = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
||||
extsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
|
||||
extsub $b8d7 = NORMAL() clobbers(A) ; normalize FAC1
|
||||
extsub $e097 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
||||
extsub $e264 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
||||
extsub $e26b = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
||||
extsub $e2b4 = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
|
||||
extsub $e30e = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
|
||||
|
||||
|
||||
asmsub AYINT2() clobbers(X) -> word @AY {
|
||||
; fac1-> signed word in AY. Safe wrapper around the AYINT kernal routine (not reading internal memory locations)
|
||||
; (might throw ILLEGAL QUANTITY)
|
||||
%asm {{
|
||||
jsr AYINT
|
||||
ldx #<floats_temp_var
|
||||
ldy #>floats_temp_var
|
||||
jsr MOVMF
|
||||
lda floats_temp_var+4
|
||||
ldy floats_temp_var+3
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub FREADS32() clobbers(A,X,Y) {
|
||||
; ---- fac1 = signed int32 from $62-$65 big endian (MSB FIRST)
|
||||
%asm {{
|
||||
@@ -154,7 +168,7 @@ asmsub FREADU24AXY(ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A, X, Y)
|
||||
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
|
||||
%asm {{
|
||||
sty $62
|
||||
sty $62 ; facmo
|
||||
sta $63
|
||||
ldx #$90
|
||||
sec
|
||||
@@ -183,7 +197,6 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
||||
}}
|
||||
}
|
||||
|
||||
&uword AYINT_facmo = $64 ; $64/$65 contain result of AYINT
|
||||
|
||||
sub rnd() -> float {
|
||||
%asm {{
|
||||
|
||||
@@ -347,12 +347,11 @@ hline_zero2
|
||||
|
||||
uword @zp internal_plotx ; 0..319 ; separate 'parameter' for internal_plot()
|
||||
|
||||
asmsub internal_plot(ubyte ploty @A) clobbers (A, X, Y) { ; internal_plotx is 16 bits 0 to 319... doesn't fit in a register
|
||||
asmsub internal_plot(ubyte ploty @Y) clobbers (A, X, Y) { ; internal_plotx is 16 bits 0 to 319... doesn't fit in a register
|
||||
%asm {{
|
||||
tay
|
||||
lda p8v_internal_plotx+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
lsr a ; 0
|
||||
lsr a ; make a=0
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda p8v_internal_plotx
|
||||
pha
|
||||
@@ -377,7 +376,7 @@ hline_zero2
|
||||
|
||||
_ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
|
||||
|
||||
; note: this can be even faster if we also have a 256 byte x-lookup table, but hey.
|
||||
; note: this can be even faster if we also have a 320 word x-lookup table, but hey, that's a lot of memory.
|
||||
; see http://codebase64.org/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics
|
||||
; the y lookup tables encodes this formula: BITMAP_ADDRESS + 320*(py>>3) + (py & 7) (y from 0..199)
|
||||
; We use the 64tass syntax for range expressions to calculate this table on assembly time.
|
||||
@@ -386,6 +385,7 @@ _plot_y_values := p8c_BITMAP_ADDRESS + 320*(range(200)>>3) + (range(200) & 7)
|
||||
|
||||
_y_lookup_lo .byte <_plot_y_values
|
||||
_y_lookup_hi .byte >_plot_y_values
|
||||
; !notreached!
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
+315
-132
@@ -57,50 +57,50 @@ cbm {
|
||||
|
||||
; ---- CBM ROM kernal routines (C64 addresses) ----
|
||||
|
||||
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use txt.print instead)
|
||||
romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
|
||||
romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
|
||||
romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
|
||||
romsub $EA81 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
|
||||
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
|
||||
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||
romsub $FF8D = VECTOR(uword userptr @ XY, bool dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set top of memory pointer
|
||||
romsub $FF9C = MEMBOT(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
|
||||
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
|
||||
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
|
||||
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
|
||||
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word (use CLEARST to reset it to 0)
|
||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
|
||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
romsub $FFC0 = OPEN() clobbers(X,Y) -> bool @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFD2 = CHROUT(ubyte character @ A) ; (via 806 ($326)) output a character
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
|
||||
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A) also see STOP2
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character also see GETIN2
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; get size of text screen into X (columns) and Y (rows)
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
extsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use txt.print instead)
|
||||
extsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
|
||||
extsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
|
||||
extsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
|
||||
extsub $EA81 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
|
||||
extsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||
extsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||
extsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
|
||||
extsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||
extsub $FF8D = VECTOR(uword userptr @ XY, bool dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||
extsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||
extsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||
extsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||
extsub $FF99 = MEMTOP(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set top of memory pointer
|
||||
extsub $FF9C = MEMBOT(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||
extsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||
extsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||
extsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
|
||||
extsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
|
||||
extsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
|
||||
extsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
|
||||
extsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||
extsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||
extsub $FFB7 = READST() -> ubyte @ A ; read I/O status word (use CLEARST to reset it to 0)
|
||||
extsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
|
||||
extsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
extsub $FFC0 = OPEN() clobbers(X,Y) -> bool @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||
extsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
extsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; (via 798 ($31E)) define an input channel
|
||||
extsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||
extsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
extsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
extsub $FFD2 = CHROUT(ubyte character @ A) ; (via 806 ($326)) output a character
|
||||
extsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||
extsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
extsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
extsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
|
||||
extsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A) also see STOP2
|
||||
extsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character also see GETIN2
|
||||
extsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
extsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
extsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; get size of text screen into X (columns) and Y (rows)
|
||||
extsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||
extsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
|
||||
|
||||
inline asmsub STOP2() clobbers(X,A) -> bool @Pz {
|
||||
@@ -307,6 +307,93 @@ c64 {
|
||||
|
||||
; ---- end of SID registers ----
|
||||
|
||||
asmsub banks(ubyte banks @A) {
|
||||
; -- set the memory bank configuration
|
||||
; see https://www.c64-wiki.com/wiki/Bank_Switching
|
||||
%asm {{
|
||||
and #%00000111
|
||||
sta P8ZP_SCRATCH_REG
|
||||
sei
|
||||
lda $01
|
||||
and #%11111000
|
||||
ora P8ZP_SCRATCH_REG
|
||||
sta $01
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub getbanks() -> ubyte @A {
|
||||
; -- get the current memory bank configuration
|
||||
; see https://www.c64-wiki.com/wiki/Bank_Switching
|
||||
%asm {{
|
||||
lda $01
|
||||
and #%00000111
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub x16jsrfar() {
|
||||
%asm {{
|
||||
; setup a JSRFAR call (using X16 call convention)
|
||||
sta P8ZP_SCRATCH_W2 ; save A
|
||||
sty P8ZP_SCRATCH_W2+1 ; save Y
|
||||
php
|
||||
pla
|
||||
sta P8ZP_SCRATCH_REG ; save Status
|
||||
|
||||
pla
|
||||
sta P8ZP_SCRATCH_W1
|
||||
pla
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
|
||||
; retrieve arguments
|
||||
ldy #$01
|
||||
lda (P8ZP_SCRATCH_W1),y ; grab low byte of target address
|
||||
sta _jmpfar+1
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y ; now the high byte
|
||||
sta _jmpfar+2
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y ; then the target bank
|
||||
sta P8ZP_SCRATCH_B1
|
||||
|
||||
; adjust return address to skip over the arguments
|
||||
clc
|
||||
lda P8ZP_SCRATCH_W1
|
||||
adc #3
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
adc #0
|
||||
pha
|
||||
lda P8ZP_SCRATCH_W1
|
||||
pha
|
||||
lda $01 ; save old ram banks
|
||||
pha
|
||||
; set target bank, restore A, Y and flags
|
||||
lda P8ZP_SCRATCH_REG
|
||||
pha
|
||||
lda P8ZP_SCRATCH_B1
|
||||
jsr banks
|
||||
lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
plp
|
||||
jsr _jmpfar ; do the actual call
|
||||
; restore bank without clobbering status flags and A register
|
||||
sta P8ZP_SCRATCH_W1
|
||||
php
|
||||
pla
|
||||
sta P8ZP_SCRATCH_B1
|
||||
pla
|
||||
jsr banks
|
||||
lda P8ZP_SCRATCH_B1
|
||||
pha
|
||||
lda P8ZP_SCRATCH_W1
|
||||
plp
|
||||
rts
|
||||
|
||||
_jmpfar jmp $0000 ; modified
|
||||
}}
|
||||
}
|
||||
|
||||
sub get_vic_memory_base() -> uword {
|
||||
; one of the 4 possible banks. $0000/$4000/$8000/$c000.
|
||||
@@ -345,7 +432,7 @@ c64 {
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
const ubyte target = 64 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
|
||||
const ubyte target = 64 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
|
||||
|
||||
const ubyte sizeof_bool = 1
|
||||
const ubyte sizeof_byte = 1
|
||||
@@ -354,83 +441,13 @@ sys {
|
||||
const ubyte sizeof_uword = 2
|
||||
const ubyte sizeof_float = 5
|
||||
|
||||
sub disable_runstop_and_charsetswitch() {
|
||||
p8_sys_startup.disable_runstop_and_charsetswitch()
|
||||
}
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in,
|
||||
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
|
||||
; Also a different color scheme is chosen to identify ourselves a little.
|
||||
; Uppercase charset is activated.
|
||||
%asm {{
|
||||
sei
|
||||
lda #%00101111
|
||||
sta $00
|
||||
lda #%00100111
|
||||
sta $01
|
||||
jsr cbm.IOINIT
|
||||
jsr cbm.RESTOR
|
||||
jsr cbm.CINT
|
||||
lda #6
|
||||
sta c64.EXTCOL
|
||||
lda #7
|
||||
sta cbm.COLOR
|
||||
lda #0
|
||||
sta c64.BGCOL0
|
||||
jsr disable_runstop_and_charsetswitch
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
cld
|
||||
clc
|
||||
clv
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
%asm {{
|
||||
lda #31
|
||||
sta $01 ; bank the kernal in
|
||||
jsr cbm.CLRCHN ; reset i/o channels
|
||||
jsr enable_runstop_and_charsetswitch
|
||||
_exitcodeCarry = *+1
|
||||
lda #0
|
||||
lsr a
|
||||
_exitcode = *+1
|
||||
lda #0 ; exit code possibly modified in exit()
|
||||
_exitcodeX = *+1
|
||||
ldx #0
|
||||
_exitcodeY = *+1
|
||||
ldy #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub disable_runstop_and_charsetswitch() clobbers(A) {
|
||||
%asm {{
|
||||
lda #$80
|
||||
sta 657 ; disable charset switching
|
||||
lda #239
|
||||
sta 808 ; disable run/stop key
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub enable_runstop_and_charsetswitch() clobbers(A) {
|
||||
%asm {{
|
||||
lda #0
|
||||
sta 657 ; enable charset switching
|
||||
lda #237
|
||||
sta 808 ; enable run/stop key
|
||||
rts
|
||||
}}
|
||||
}
|
||||
sub enable_runstop_and_charsetswitch() {
|
||||
p8_sys_startup.enable_runstop_and_charsetswitch()
|
||||
}
|
||||
|
||||
asmsub save_prog8_internals() {
|
||||
%asm {{
|
||||
@@ -451,6 +468,7 @@ save_SCRATCH_ZPB1 .byte 0
|
||||
save_SCRATCH_ZPREG .byte 0
|
||||
save_SCRATCH_ZPWORD1 .word 0
|
||||
save_SCRATCH_ZPWORD2 .word 0
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -724,6 +742,65 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcmp(uword address1 @R0, uword address2 @R1, uword size @AY) -> byte @A {
|
||||
; Compares two blocks of memory
|
||||
; Returns -1 (255), 0 or 1, meaning: block 1 sorts before, equal or after block 2.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_REG ; lsb(size)
|
||||
sty P8ZP_SCRATCH_B1 ; msb(size)
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r1
|
||||
ldy cx16.r1+1
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
|
||||
ldx P8ZP_SCRATCH_B1
|
||||
beq _no_msb_size
|
||||
|
||||
_loop_msb_size
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bcs +
|
||||
lda #-1
|
||||
rts
|
||||
+ beq +
|
||||
lda #1
|
||||
rts
|
||||
+ iny
|
||||
bne -
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
dec P8ZP_SCRATCH_B1 ; msb(size) -= 1
|
||||
dex
|
||||
bne _loop_msb_size
|
||||
|
||||
_no_msb_size
|
||||
lda P8ZP_SCRATCH_REG ; lsb(size)
|
||||
bne +
|
||||
rts
|
||||
|
||||
+ ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bcs +
|
||||
lda #-1
|
||||
rts
|
||||
+ beq +
|
||||
lda #1
|
||||
rts
|
||||
+ iny
|
||||
cpy P8ZP_SCRATCH_REG ; lsb(size)
|
||||
bne -
|
||||
|
||||
lda #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub read_flags() -> ubyte @A {
|
||||
%asm {{
|
||||
php
|
||||
@@ -785,37 +862,37 @@ _longcopy
|
||||
asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
sta cleanup_at_exit._exitcode
|
||||
sta p8_sys_startup.cleanup_at_exit._exitcode
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
jmp cleanup_at_exit
|
||||
jmp p8_sys_startup.cleanup_at_exit
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub exit2(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y) {
|
||||
; -- immediately exit the program with result values in the A, X and Y registers.
|
||||
%asm {{
|
||||
sta cleanup_at_exit._exitcode
|
||||
stx cleanup_at_exit._exitcodeX
|
||||
sty cleanup_at_exit._exitcodeY
|
||||
sta p8_sys_startup.cleanup_at_exit._exitcode
|
||||
stx p8_sys_startup.cleanup_at_exit._exitcodeX
|
||||
sty p8_sys_startup.cleanup_at_exit._exitcodeY
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
jmp cleanup_at_exit
|
||||
jmp p8_sys_startup.cleanup_at_exit
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub exit3(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y, bool carry @Pc) {
|
||||
; -- immediately exit the program with result values in the A, X and Y registers, and the Carry flag in the status register.
|
||||
%asm {{
|
||||
sta cleanup_at_exit._exitcode
|
||||
sta p8_sys_startup.cleanup_at_exit._exitcode
|
||||
lda #0
|
||||
rol a
|
||||
sta cleanup_at_exit._exitcodeCarry
|
||||
stx cleanup_at_exit._exitcodeX
|
||||
sty cleanup_at_exit._exitcodeY
|
||||
sta p8_sys_startup.cleanup_at_exit._exitcodeCarry
|
||||
stx p8_sys_startup.cleanup_at_exit._exitcodeX
|
||||
sty p8_sys_startup.cleanup_at_exit._exitcodeY
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
jmp cleanup_at_exit
|
||||
jmp p8_sys_startup.cleanup_at_exit
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -826,6 +903,13 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub progstart() -> uword @AY {
|
||||
%asm {{
|
||||
lda #<prog8_program_start
|
||||
ldy #>prog8_program_start
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub push(ubyte value @A) {
|
||||
%asm {{
|
||||
pha
|
||||
@@ -975,6 +1059,7 @@ cx16 {
|
||||
_cx16_vreg_storage
|
||||
.word 0,0,0,0,0,0,0,0
|
||||
.word 0,0,0,0,0,0,0,0
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -995,3 +1080,101 @@ cx16 {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
p8_sys_startup {
|
||||
; program startup and shutdown machinery. Needs to reside in normal system ram.
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
; This means that the KERNAL and CHARGEN ROMs are banked in,
|
||||
; BASIC ROM is NOT banked in (so we have another 8Kb of RAM at our disposal),
|
||||
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
|
||||
; Also a different color scheme is chosen to identify ourselves a little.
|
||||
; Uppercase charset is activated.
|
||||
%asm {{
|
||||
sei
|
||||
lda #%00101111
|
||||
sta $00
|
||||
lda #%00100110 ; kernal and i/o banked in, basic off
|
||||
sta $01
|
||||
jsr cbm.IOINIT
|
||||
jsr cbm.RESTOR
|
||||
jsr cbm.CINT
|
||||
lda #6
|
||||
sta c64.EXTCOL
|
||||
lda #7
|
||||
sta cbm.COLOR
|
||||
lda #0
|
||||
sta c64.BGCOL0
|
||||
jsr disable_runstop_and_charsetswitch
|
||||
lda #PROG8_C64_BANK_CONFIG ; apply bank config
|
||||
sta $01
|
||||
and #1
|
||||
bne +
|
||||
; basic is not banked in, adjust MEMTOP
|
||||
ldx #<$d000
|
||||
ldy #>$d000
|
||||
clc
|
||||
jsr cbm.MEMTOP
|
||||
+ cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
cld
|
||||
clc
|
||||
clv
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
%asm {{
|
||||
lda #%00101111
|
||||
sta $00
|
||||
lda #31
|
||||
sta $01 ; bank the kernal and basic in
|
||||
ldx #<$a000
|
||||
ldy #>$a000
|
||||
clc
|
||||
jsr cbm.MEMTOP ; adjust MEMTOP down again
|
||||
jsr cbm.CLRCHN ; reset i/o channels
|
||||
jsr enable_runstop_and_charsetswitch
|
||||
_exitcodeCarry = *+1
|
||||
lda #0
|
||||
lsr a
|
||||
_exitcode = *+1
|
||||
lda #0 ; exit code possibly modified in exit()
|
||||
_exitcodeX = *+1
|
||||
ldx #0
|
||||
_exitcodeY = *+1
|
||||
ldy #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub disable_runstop_and_charsetswitch() clobbers(A) {
|
||||
%asm {{
|
||||
lda #$80
|
||||
sta 657 ; disable charset switching
|
||||
lda #239
|
||||
sta 808 ; disable run/stop key
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub enable_runstop_and_charsetswitch() clobbers(A) {
|
||||
%asm {{
|
||||
lda #0
|
||||
sta 657 ; enable charset switching
|
||||
lda #237
|
||||
sta 808 ; enable run/stop key
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,7 +13,7 @@ txt {
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
|
||||
sub clear_screen() {
|
||||
chrout(147)
|
||||
@@ -317,6 +317,7 @@ _mod sta $ffff ; modified
|
||||
rts
|
||||
|
||||
_screenrows .word cbm.Screen + range(0, 1000, 40)
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -360,6 +361,7 @@ _mod sta $ffff ; modified
|
||||
rts
|
||||
|
||||
_colorrows .word $d800 + range(0, 1000, 40)
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -200,6 +200,7 @@ _allzero lda #'0'
|
||||
sta string_out,x
|
||||
inx
|
||||
bne _end
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -402,6 +403,7 @@ _digit
|
||||
bne _parse
|
||||
; never reached
|
||||
_negative .byte 0
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -472,6 +474,7 @@ _try_iso
|
||||
bcs _stop
|
||||
and #63
|
||||
bne _add_letter
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -702,7 +705,7 @@ decHundreds .byte 0
|
||||
decTens .byte 0
|
||||
decOnes .byte 0
|
||||
.byte 0 ; zero-terminate the decimal output string
|
||||
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -736,6 +739,7 @@ asmsub internal_ubyte2hex (ubyte value @A) clobbers(X) -> ubyte @A, ubyte @Y
|
||||
rts
|
||||
|
||||
_hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -753,6 +757,7 @@ asmsub internal_uword2hex (uword value @AY) clobbers(A,Y) {
|
||||
sty output+3
|
||||
rts
|
||||
output .text "0000", $00 ; 0-terminated output buffer (to make printing easier)
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,459 +0,0 @@
|
||||
%import syslib
|
||||
|
||||
; Stubs for the audio routines in rom bank 10
|
||||
; They use JSRFAR to select the correct bank, so user code doesn't have to bother.
|
||||
;
|
||||
; The stubs here still use the romsub definitions of the routines from syslib, but those are
|
||||
; just the jump addresses and are unaware of the kernal bank. Wrapping in JSRFAR fixes this.
|
||||
|
||||
audio {
|
||||
const ubyte rom_bank = $0a
|
||||
asmsub audio_init() clobbers(A,X,Y) -> bool @Pc { ; (re)initialize both vera PSG and YM audio chips
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.audio_init
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub bas_fmfreq(ubyte channel @A, uword freq @XY, bool noretrigger @Pc) clobbers(A,X,Y) -> bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.bas_fmfreq
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub bas_fmnote(ubyte channel @A, ubyte note @X, ubyte fracsemitone @Y, bool noretrigger @Pc) clobbers(A,X,Y) -> bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.bas_fmnote
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub bas_fmplaystring(ubyte length @A, str string @XY) clobbers(A,X,Y){
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.bas_fmplaystring
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub bas_fmvib(ubyte speed @A, ubyte depth @X) clobbers(A,X,Y) -> bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.bas_fmvib
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub bas_playstringvoice(ubyte channel @A) clobbers(Y){
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.bas_playstringvoice
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub bas_psgfreq(ubyte voice @A, uword freq @XY) clobbers(A,X,Y) -> bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.bas_psgfreq
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub bas_psgnote(ubyte voice @A, ubyte note @X, ubyte fracsemitone @Y) clobbers(A,X,Y) -> bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.bas_psgnote
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub bas_psgwav(ubyte voice @A, ubyte waveform @X) clobbers(A,X,Y) -> bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.bas_psgwav
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub bas_psgplaystring(ubyte length @A, str string @XY) clobbers(A,X,Y){
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.bas_psgplaystring
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub bas_fmchordstring(ubyte length @A, str string @XY) clobbers(A,X,Y){
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.bas_fmchordstring
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub bas_psgchordstring(ubyte length @A, str string @XY) clobbers(A,X,Y){
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.bas_psgchordstring
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub notecon_bas2fm(ubyte note @X) clobbers(A) -> ubyte @X, bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.notecon_bas2fm
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub notecon_bas2midi(ubyte note @X) clobbers(A) -> ubyte @X, bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.notecon_bas2midi
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub notecon_bas2psg(ubyte note @X, ubyte fracsemitone @Y) clobbers(A) -> uword @XY, bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.notecon_bas2psg
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub notecon_fm2bas(ubyte note @X) clobbers(A) -> ubyte @X, bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.notecon_fm2bas
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub notecon_fm2midi(ubyte note @X) clobbers(A) -> ubyte @X, bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.notecon_fm2midi
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub notecon_fm2psg(ubyte note @X, ubyte fracsemitone @Y) clobbers(A) -> uword @XY, bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.notecon_fm2psg
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub notecon_freq2bas(uword freqHz @XY) clobbers(A) -> ubyte @X, ubyte @Y, bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.notecon_freq2bas
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub notecon_freq2fm(uword freqHz @XY) clobbers(A) -> ubyte @X, ubyte @Y, bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.notecon_freq2fm
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub notecon_freq2midi(uword freqHz @XY) clobbers(A) -> ubyte @X, ubyte @Y, bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.notecon_freq2midi
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub notecon_freq2psg(uword freqHz @XY) clobbers(A) -> uword @XY, bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.notecon_freq2psg
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub notecon_midi2bas(ubyte note @X) clobbers(A) -> ubyte @X, bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.notecon_midi2bas
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub notecon_midi2fm(ubyte note @X) clobbers(A) -> ubyte @X, bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.notecon_midi2fm
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub notecon_midi2psg(ubyte note @X, ubyte fracsemitone @Y) clobbers(A) -> uword @XY, bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.notecon_midi2psg
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub notecon_psg2bas(uword freq @XY) clobbers(A) -> ubyte @X, ubyte @Y, bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.notecon_psg2bas
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub notecon_psg2fm(uword freq @XY) clobbers(A) -> ubyte @X, ubyte @Y, bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.notecon_psg2fm
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub notecon_psg2midi(uword freq @XY) clobbers(A) -> ubyte @X, ubyte @Y, bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.notecon_psg2midi
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub psg_init() clobbers(A,X,Y) { ; (re)init Vera PSG
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.psg_init
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub psg_playfreq(ubyte voice @A, uword freq @XY) clobbers(A,X,Y){
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.psg_playfreq
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub psg_read(ubyte offset @X, bool cookedVol @Pc) clobbers(Y) -> ubyte @A{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.psg_read
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub psg_setatten(ubyte voice @A, ubyte attenuation @X) clobbers(A,X,Y){
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.psg_setatten
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub psg_setfreq(ubyte voice @A, uword freq @XY) clobbers(A,X,Y){
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.psg_setfreq
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub psg_setpan(ubyte voice @A, ubyte panning @X) clobbers(A,X,Y){
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.psg_setpan
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub psg_setvol(ubyte voice @A, ubyte volume @X) clobbers(A,X,Y){
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.psg_setvol
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub psg_write(ubyte value @A, ubyte offset @X) clobbers(Y){
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.psg_write
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub psg_write_fast(ubyte value @A, ubyte offset @X) clobbers(Y){
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.psg_write_fast
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub psg_getatten(ubyte voice @A) clobbers(Y) -> ubyte @X{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.psg_getatten
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub psg_getpan(ubyte voice @A) clobbers(Y) -> ubyte @X{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.psg_getpan
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub ym_init() clobbers(A,X,Y) -> bool @Pc { ; (re)init YM chip
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.ym_init
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub ym_loaddefpatches() clobbers(A,X,Y) -> bool @Pc { ; load default YM patches
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.ym_loaddefpatches
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub ym_loadpatch(ubyte channel @A, uword patchOrAddress @XY, bool what @Pc) clobbers(A,X,Y){
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.ym_loadpatch
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub ym_loadpatchlfn(ubyte channel @A, ubyte lfn @X) clobbers(X,Y) -> ubyte @A, bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.ym_loadpatchlfn
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub ym_playdrum(ubyte channel @A, ubyte note @X) clobbers(A,X,Y) -> bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.ym_playdrum
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub ym_playnote(ubyte channel @A, ubyte kc @X, ubyte kf @Y, bool notrigger @Pc) clobbers(A,X,Y) -> bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.ym_playnote
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub ym_setatten(ubyte channel @A, ubyte attenuation @X) clobbers(Y) -> bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.ym_setatten
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub ym_setdrum(ubyte channel @A, ubyte note @X) clobbers(A,X,Y) -> bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.ym_setdrum
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub ym_setnote(ubyte channel @A, ubyte kc @X, ubyte kf @Y) clobbers(A,X,Y) -> bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.ym_setnote
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub ym_setpan(ubyte channel @A, ubyte panning @X) clobbers(A,X,Y) -> bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.ym_setpan
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub ym_read(ubyte register @X, bool cooked @Pc) clobbers(Y) -> ubyte @A, bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.ym_read
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub ym_release(ubyte channel @A) clobbers(A,X,Y) -> bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.ym_release
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub ym_trigger(ubyte channel @A, bool noRelease @Pc) clobbers(A,X,Y) -> bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.ym_trigger
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub ym_write(ubyte value @A, ubyte register @X) clobbers(Y) -> bool @Pc{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.ym_write
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub ym_getatten(ubyte channel @A) clobbers(Y) -> ubyte @X{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.ym_getatten
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub ym_getpan(ubyte channel @A) clobbers(Y) -> ubyte @X{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.ym_getpan
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub ym_get_chip_type() clobbers(X) -> ubyte @A{
|
||||
%asm{{
|
||||
jsr cx16.JSRFAR
|
||||
.word cx16.ym_get_chip_type
|
||||
.byte p8c_rom_bank
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
; Routines to load and save "BMX" files (commander X16 bitmap format) Version 1.
|
||||
; Bitmap data is loaded directly into VRAM without intermediary buffering.
|
||||
; Only uncompressed images are supported for now.
|
||||
; BMX Specification: https://cx16forum.com/forum/viewtopic.php?t=6945
|
||||
|
||||
|
||||
@@ -145,6 +145,7 @@ io_error:
|
||||
uword list_pattern
|
||||
uword list_blocks
|
||||
bool iteration_in_progress = false
|
||||
bool write_iteration_in_progress = false
|
||||
str list_filetype = "???" ; prg, seq, dir
|
||||
str list_filename = "?" * 50
|
||||
|
||||
@@ -423,6 +424,7 @@ _end jsr cbm.READST
|
||||
|
||||
sub f_close() {
|
||||
; -- end an iterative file loading session (close channels).
|
||||
; it is safe to call this multiple times, or when no file is open for reading.
|
||||
if iteration_in_progress {
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
@@ -460,7 +462,10 @@ _end jsr cbm.READST
|
||||
cbm.SETLFS(WRITE_IO_CHANNEL, drivenumber, WRITE_IO_CHANNEL)
|
||||
void cbm.OPEN() ; open 13,8,13,"filename"
|
||||
if_cc {
|
||||
return cbm.READST()==0
|
||||
if cbm.READST()==0 {
|
||||
write_iteration_in_progress = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
cbm.CLOSE(WRITE_IO_CHANNEL)
|
||||
f_close_w()
|
||||
@@ -498,8 +503,12 @@ no_mciout:
|
||||
|
||||
sub f_close_w() {
|
||||
; -- end an iterative file writing session (close channels).
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(WRITE_IO_CHANNEL)
|
||||
; it is safe to call this multiple times, or when no file is open for reading.
|
||||
if write_iteration_in_progress {
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(WRITE_IO_CHANNEL)
|
||||
write_iteration_in_progress = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -703,7 +712,7 @@ io_error:
|
||||
; CommanderX16 extensions over the basic C64/C128 diskio routines:
|
||||
|
||||
; For use directly after a load or load_raw call (don't mess with the ram bank yet):
|
||||
; Calculates the number of bytes loaded (files > 64Kb ar truncated to 16 bits)
|
||||
; Calculates the number of bytes loaded (files > 64Kb are truncated to 16 bits)
|
||||
sub load_size(ubyte startbank, uword startaddress, uword endaddress) -> uword {
|
||||
return $2000 * (cx16.getrambank() - startbank) + endaddress - startaddress
|
||||
}
|
||||
@@ -869,6 +878,15 @@ io_error:
|
||||
send_command(list_filename)
|
||||
}
|
||||
|
||||
sub exists(str filename) -> bool {
|
||||
; -- returns true if the given file exists on the disk, otherwise false
|
||||
if f_open(filename) {
|
||||
f_close()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
sub f_seek(uword pos_hiword, uword pos_loword) {
|
||||
; -- seek in the reading file opened with f_open, to the given 32-bits position
|
||||
; Note: this will not work if you have already read the last byte of the file! Then you must close and reopen the file first.
|
||||
|
||||
@@ -14,69 +14,82 @@ floats {
|
||||
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
|
||||
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
|
||||
|
||||
romsub $fe00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 'facmo' and 'faclo', MSB FIRST. (might throw ILLEGAL QUANTITY) See "basic.sym" kernal symbol file for their memory locations.
|
||||
extsub $fe00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 'facmo' and 'faclo', MSB FIRST. DON'T USE THIS, USE WRAPPER 'AYINT2' INSTEAD. (might throw ILLEGAL QUANTITY)
|
||||
|
||||
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
|
||||
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
|
||||
; (tip: use GIVAYFAY to use A/Y input; lo/hi switched to normal order)
|
||||
romsub $fe03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
||||
extsub $fe03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
||||
|
||||
romsub $fe06 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
|
||||
romsub $fe09 = VAL_1(uword string @XY, ubyte length @A) clobbers(A,X,Y) -> float @FAC1 ; convert ASCII string in XY and length in A, to floating point in FAC1. WARNING: only implemented in ROM 47+. Safer to use floats.parse() instead.
|
||||
extsub $fe06 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
|
||||
extsub $fe09 = VAL_1(uword string @XY, ubyte length @A) clobbers(A,X,Y) -> float @FAC1 ; convert ASCII string in XY and length in A, to floating point in FAC1. WARNING: only implemented in ROM 47+. Safer to use floats.parse() instead.
|
||||
|
||||
; GETADR: fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
||||
; (tip: use GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||
romsub $fe0c = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
||||
romsub $fe0f = FLOATC() clobbers(A,X,Y) ; convert address to floating point
|
||||
extsub $fe0c = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
||||
extsub $fe0f = FLOATC() clobbers(A,X,Y) ; convert address to floating point
|
||||
|
||||
romsub $fe12 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
|
||||
romsub $fe15 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
|
||||
romsub $fe18 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
|
||||
romsub $fe1b = FADDT() clobbers(A,X,Y) ; fac1 += fac2
|
||||
romsub $fe1e = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
|
||||
romsub $fe21 = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
|
||||
romsub $fe24 = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1
|
||||
romsub $fe27 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 mind the order of the operands
|
||||
romsub $fe2a = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
|
||||
romsub $fe2d = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to integer round instead of trunc
|
||||
romsub $fe30 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||
romsub $fe33 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
|
||||
romsub $fe36 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** float in A/Y
|
||||
romsub $fe39 = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||
romsub $fe3c = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
||||
romsub $fe3f = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
||||
romsub $fe42 = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
||||
romsub $fe45 = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
|
||||
romsub $fe48 = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
|
||||
romsub $fe4b = ROUND() clobbers(A,X,Y) ; round least significant bit of fac1
|
||||
romsub $fe4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
|
||||
romsub $fe51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||
romsub $fe54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||
romsub $fe57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator NOTE: incompatible with C64's RND routine
|
||||
romsub $fe57 = RND() clobbers(A,X,Y) ; alias for RND_0
|
||||
romsub $fe5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
|
||||
romsub $fe5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2
|
||||
romsub $fe60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1 (use MOVFM instead)
|
||||
romsub $fe63 = MOVFM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1
|
||||
romsub $fe66 = MOVMF(uword mflpt @ XY) clobbers(A,X,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||
romsub $fe69 = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
||||
romsub $fe6c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded the least significant bit)
|
||||
extsub $fe12 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
|
||||
extsub $fe15 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
|
||||
extsub $fe18 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
|
||||
extsub $fe1b = FADDT() clobbers(A,X,Y) ; fac1 += fac2
|
||||
extsub $fe1e = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
|
||||
extsub $fe21 = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
|
||||
extsub $fe24 = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1
|
||||
extsub $fe27 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 mind the order of the operands
|
||||
extsub $fe2a = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
|
||||
extsub $fe2d = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to integer round instead of trunc
|
||||
extsub $fe30 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||
extsub $fe33 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
|
||||
extsub $fe36 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** float in A/Y
|
||||
extsub $fe39 = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||
extsub $fe3c = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
||||
extsub $fe3f = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
||||
extsub $fe42 = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
||||
extsub $fe45 = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
|
||||
extsub $fe48 = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
|
||||
extsub $fe4b = ROUND() clobbers(A,X,Y) ; round least significant bit of fac1
|
||||
extsub $fe4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
|
||||
extsub $fe51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||
extsub $fe54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||
extsub $fe57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator NOTE: incompatible with C64's RND routine
|
||||
extsub $fe57 = RND() clobbers(A,X,Y) ; alias for RND_0
|
||||
extsub $fe5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
|
||||
extsub $fe5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2
|
||||
extsub $fe60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1 (use MOVFM instead)
|
||||
extsub $fe63 = MOVFM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1
|
||||
extsub $fe66 = MOVMF(uword mflpt @ XY) clobbers(A,X,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||
extsub $fe69 = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
||||
extsub $fe6c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded the least significant bit)
|
||||
|
||||
; X16 additions
|
||||
romsub $fe6f = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for integer rounding- call this before INT
|
||||
romsub $fe72 = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
|
||||
romsub $fe75 = NORMAL() clobbers(A,X,Y) ; normalize fac1
|
||||
romsub $fe78 = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (doesn't work, juse use NEGOP() instead!)
|
||||
romsub $fe7b = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||
romsub $fe7e = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! Have to restore sign manually!
|
||||
romsub $fe81 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||
romsub $fe84 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
||||
romsub $fe87 = FLOAT() clobbers(A,X,Y) ; FAC = (s8).A
|
||||
romsub $fe8a = FLOATS() clobbers(A,X,Y) ; FAC = (s16)facho+1:facho
|
||||
romsub $fe8d = QINT() clobbers(A,X,Y) ; facho:facho+1:facho+2:facho+3 = u32(FAC)
|
||||
romsub $fe90 = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||
extsub $fe6f = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for integer rounding- call this before INT
|
||||
extsub $fe72 = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
|
||||
extsub $fe75 = NORMAL() clobbers(A,X,Y) ; normalize fac1
|
||||
extsub $fe78 = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (doesn't work, juse use NEGOP() instead!)
|
||||
extsub $fe7b = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||
extsub $fe7e = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! Have to restore sign manually!
|
||||
extsub $fe81 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||
extsub $fe84 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
||||
extsub $fe87 = FLOAT() clobbers(A,X,Y) ; FAC = (s8).A
|
||||
extsub $fe8a = FLOATS() clobbers(A,X,Y) ; FAC = (s16)facho+1:facho
|
||||
extsub $fe8d = QINT() clobbers(A,X,Y) ; facho:facho+1:facho+2:facho+3 = u32(FAC)
|
||||
extsub $fe90 = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||
|
||||
|
||||
asmsub AYINT2() clobbers(X) -> word @AY {
|
||||
; fac1-> signed word in AY. Safe wrapper around the AYINT kernal routine (not reading internal memory locations)
|
||||
; (might throw ILLEGAL QUANTITY)
|
||||
%asm {{
|
||||
jsr AYINT
|
||||
ldx #<floats_temp_var
|
||||
ldy #>floats_temp_var
|
||||
jsr MOVMF
|
||||
lda floats_temp_var+4
|
||||
ldy floats_temp_var+3
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub FREADSA (byte value @A) clobbers(A,X,Y) {
|
||||
; ---- 8 bit signed A -> float in fac1
|
||||
@@ -124,6 +137,8 @@ asmsub FREADU24AXY(ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A, X, Y)
|
||||
|
||||
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
|
||||
; See "basic.sym" kernal symbol file for the facmo memory location.
|
||||
; TODO find a way to not depend on that internal fac memory location. MOVFM doesn't work.
|
||||
%asm {{
|
||||
sty $c4 ; facmo
|
||||
sta $c5 ; facmo+1
|
||||
@@ -186,10 +201,10 @@ _borked
|
||||
+ jmp sys.exit
|
||||
|
||||
_msg .text 13,"?rom 47+ required for val1",13,0
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
&uword AYINT_facmo = $c6 ; $c6/$c7 contain result of AYINT (See "basic.sym" kernal symbol file)
|
||||
|
||||
sub rnd() -> float {
|
||||
%asm {{
|
||||
|
||||
@@ -1,117 +1,49 @@
|
||||
; Bitmap pixel graphics routines for the CommanderX16
|
||||
; Custom routines to use the full-screen 640x480 and 320x240 screen modes.
|
||||
; Custom routines to use the full-screen 640x480 in 4 color mode.
|
||||
; (monochrome mode can be used via the monogfx module)
|
||||
; (These modes are not supported by the documented GRAPH_xxxx kernal routines)
|
||||
;
|
||||
; No text layer is currently shown, text can be drawn as part of the bitmap itself.
|
||||
; Note: for similar graphics routines that also work on the C-64, use the "graphics" module instead.
|
||||
; Note: for identical routines for a monochrome 1 bpp screen, use the "monogfx" module instead.
|
||||
; Note: for color palette manipulation, use the "palette" module or write Vera registers yourself.
|
||||
; Note: this library implements code for various resolutions and color depths. This takes up memory.
|
||||
; If you're memory constrained you should probably not use this built-in library,
|
||||
; but make a copy in your project only containing the code for the required resolution.
|
||||
;
|
||||
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
|
||||
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
|
||||
; NOTE: the bitmap screen data is positioned in vram at $00000
|
||||
; This is compatible with the CX16's screen mode 128. (void cx16.set_screen_mode(128))
|
||||
;
|
||||
; NOTE: the bitmap screen data is positioned in vram at $0:0000
|
||||
;
|
||||
; NOTE: In the future, these routines might be split out to separate modules, 1 for each screen mode,
|
||||
; so they can be optimized a lot better. There's already a "gfx_lores" module with a few routines for lores 256C mode.
|
||||
;
|
||||
; SCREEN MODE LIST:
|
||||
; mode 0 = reset back to default text mode
|
||||
; mode 1 = bitmap 320 x 240 x 256c (8 bpp)
|
||||
; mode 2 = bitmap 640 x 480 x 4c (2 bpp)
|
||||
; mode 3 = bitmap 320 x 240 x 4c (not yet implemented: just use 256c, there's enough vram for that)
|
||||
; mode 4 = bitmap 320 x 240 x 16c (not yet implemented: just use 256c, there's enough vram for that)
|
||||
; mode 5 = bitmap 640 x 400 x 16c (not yet implemented)
|
||||
|
||||
gfx2 {
|
||||
gfx_hires {
|
||||
|
||||
%option ignore_unused
|
||||
|
||||
; read-only control variables:
|
||||
ubyte active_mode = 0
|
||||
uword width = 0
|
||||
uword height = 0
|
||||
ubyte bpp = 0
|
||||
const uword WIDTH = 640
|
||||
const uword HEIGHT = 480
|
||||
|
||||
sub screen_mode(ubyte mode) {
|
||||
sub graphics_mode() {
|
||||
; set hires 4c mode
|
||||
cx16.VERA_CTRL=0
|
||||
when mode {
|
||||
1 -> {
|
||||
; lores 256c
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
|
||||
cx16.VERA_DC_HSCALE = 64
|
||||
cx16.VERA_DC_VSCALE = 64
|
||||
cx16.VERA_L1_CONFIG = %00000111
|
||||
cx16.VERA_L1_MAPBASE = 0
|
||||
cx16.VERA_L1_TILEBASE = 0
|
||||
}
|
||||
2 -> {
|
||||
; highres 4c
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
|
||||
cx16.VERA_DC_HSCALE = 128
|
||||
cx16.VERA_DC_VSCALE = 128
|
||||
cx16.VERA_L1_CONFIG = %00000101
|
||||
cx16.VERA_L1_MAPBASE = 0
|
||||
cx16.VERA_L1_TILEBASE = %00000001
|
||||
}
|
||||
else -> {
|
||||
; back to default text mode
|
||||
if active_mode!=0 {
|
||||
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
|
||||
cbm.CINT()
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init_mode(mode)
|
||||
if active_mode!=0
|
||||
clear_screen(0)
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
|
||||
cx16.VERA_DC_HSCALE = 128
|
||||
cx16.VERA_DC_VSCALE = 128
|
||||
cx16.VERA_L1_CONFIG = %00000101
|
||||
cx16.VERA_L1_MAPBASE = 0
|
||||
cx16.VERA_L1_TILEBASE = %00000001
|
||||
clear_screen(0)
|
||||
}
|
||||
|
||||
sub init_mode(ubyte mode) {
|
||||
; set the internal configuration variables corresponding to the given screenmode
|
||||
; doesn't manipulate Vera / the actual display mode
|
||||
active_mode = mode
|
||||
when mode {
|
||||
1 -> {
|
||||
width = 320
|
||||
height = 240
|
||||
bpp = 8
|
||||
}
|
||||
2 -> {
|
||||
width = 640
|
||||
height = 480
|
||||
bpp = 2
|
||||
}
|
||||
else -> {
|
||||
width = 0
|
||||
height = 0
|
||||
bpp = 0
|
||||
active_mode = 0
|
||||
}
|
||||
}
|
||||
sub text_mode() {
|
||||
; back to normal text mode
|
||||
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
|
||||
cbm.CINT()
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
|
||||
}
|
||||
|
||||
sub clear_screen(ubyte color) {
|
||||
position(0, 0)
|
||||
when active_mode {
|
||||
1 -> {
|
||||
; lores 256c
|
||||
repeat 240/2
|
||||
cs_innerloop640(color)
|
||||
}
|
||||
2 -> {
|
||||
; highres 4c
|
||||
ubyte[] colors = [%00000000, %01010101, %10101010, %11111111]
|
||||
color = colors[color&3]
|
||||
repeat 480/4
|
||||
cs_innerloop640(color)
|
||||
}
|
||||
}
|
||||
ubyte[] colors = [%00000000, %01010101, %10101010, %11111111]
|
||||
color = colors[color&3]
|
||||
repeat 480/4
|
||||
cs_innerloop640(color)
|
||||
position(0, 0)
|
||||
}
|
||||
|
||||
@@ -128,6 +60,18 @@ gfx2 {
|
||||
vertical_line(xx+rwidth-1, yy+1, rheight-2, color)
|
||||
}
|
||||
|
||||
sub safe_rect(uword xx, uword yy, uword rwidth, uword rheight, ubyte color) {
|
||||
; does bounds checking and clipping
|
||||
safe_horizontal_line(xx, yy, rwidth, color)
|
||||
if rheight==1
|
||||
return
|
||||
safe_horizontal_line(xx, yy+rheight-1, rwidth, color)
|
||||
safe_vertical_line(xx, yy+1, rheight-2, color)
|
||||
if rwidth==1
|
||||
return
|
||||
safe_vertical_line(xx+rwidth-1, yy+1, rheight-2, color)
|
||||
}
|
||||
|
||||
sub fillrect(uword xx, uword yy, uword rwidth, uword rheight, ubyte color) {
|
||||
; Draw a filled rectangle of the given size and color.
|
||||
; To fill the whole screen, use clear_screen(color) instead - it is much faster.
|
||||
@@ -139,140 +83,124 @@ gfx2 {
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_fillrect(uword xx, uword yy, uword rwidth, uword rheight, ubyte color) {
|
||||
; Draw a filled rectangle of the given size and color.
|
||||
; To fill the whole screen, use clear_screen(color) instead - it is much faster.
|
||||
; This safe version does bounds checking and clipping.
|
||||
if xx>=WIDTH or yy>=WIDTH
|
||||
return
|
||||
if msb(xx)&$80!=0 {
|
||||
rwidth += xx
|
||||
xx = 0
|
||||
}
|
||||
if xx>=WIDTH
|
||||
return
|
||||
if xx+rwidth>WIDTH
|
||||
rwidth = WIDTH-xx
|
||||
if rwidth>WIDTH
|
||||
return
|
||||
|
||||
if yy + rheight > HEIGHT
|
||||
rheight = HEIGHT-yy
|
||||
if rheight>HEIGHT
|
||||
return
|
||||
|
||||
repeat rheight {
|
||||
horizontal_line(xx, yy, rwidth, color)
|
||||
yy++
|
||||
}
|
||||
}
|
||||
|
||||
sub horizontal_line(uword xx, uword yy, uword length, ubyte color) {
|
||||
if length==0
|
||||
return
|
||||
when active_mode {
|
||||
1 -> {
|
||||
; lores 256c
|
||||
position(xx, yy)
|
||||
%asm {{
|
||||
lda p8v_color
|
||||
ldx p8v_length+1
|
||||
beq +
|
||||
ldy #0
|
||||
- sta cx16.VERA_DATA0
|
||||
iny
|
||||
bne -
|
||||
dex
|
||||
bne -
|
||||
+ ldy p8v_length ; remaining
|
||||
beq +
|
||||
- sta cx16.VERA_DATA0
|
||||
dey
|
||||
bne -
|
||||
+
|
||||
}}
|
||||
}
|
||||
2 -> {
|
||||
; highres 4c ....also mostly usable for lores 4c?
|
||||
color &= 3
|
||||
ubyte[4] colorbits
|
||||
ubyte ii
|
||||
for ii in 3 downto 0 {
|
||||
colorbits[ii] = color
|
||||
color <<= 2
|
||||
}
|
||||
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
%asm {{
|
||||
lda cx16.VERA_ADDR_H
|
||||
and #%00000111 ; no auto advance
|
||||
sta cx16.VERA_ADDR_H
|
||||
stz cx16.VERA_CTRL ; setup vera addr 0
|
||||
lda cx16.r1
|
||||
and #1
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.r0+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
ldx p8v_xx
|
||||
}}
|
||||
; highres 4c ....also mostly usable for lores 4c?
|
||||
color &= 3
|
||||
ubyte[4] colorbits
|
||||
ubyte ii
|
||||
for ii in 3 downto 0 {
|
||||
colorbits[ii] = color
|
||||
color <<= 2
|
||||
}
|
||||
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
%asm {{
|
||||
lda cx16.VERA_ADDR_H
|
||||
and #%00000111 ; no auto advance
|
||||
sta cx16.VERA_ADDR_H
|
||||
stz cx16.VERA_CTRL ; setup vera addr 0
|
||||
lda cx16.r1
|
||||
and #1
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.r0+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
ldx p8v_xx
|
||||
}}
|
||||
|
||||
repeat length {
|
||||
%asm {{
|
||||
txa
|
||||
and #3
|
||||
tay
|
||||
lda cx16.VERA_DATA0
|
||||
and p8b_gfx2.p8s_plot.p8v_mask4c,y
|
||||
ora p8v_colorbits,y
|
||||
sta cx16.VERA_DATA0
|
||||
cpy #%00000011 ; next vera byte?
|
||||
bne ++
|
||||
inc cx16.VERA_ADDR_L
|
||||
bne ++
|
||||
inc cx16.VERA_ADDR_M
|
||||
repeat length {
|
||||
%asm {{
|
||||
txa
|
||||
and #3
|
||||
tay
|
||||
lda cx16.VERA_DATA0
|
||||
and p8b_gfx_hires.p8s_plot.p8v_mask4c,y
|
||||
ora p8v_colorbits,y
|
||||
sta cx16.VERA_DATA0
|
||||
cpy #%00000011 ; next vera byte?
|
||||
bne ++
|
||||
inc cx16.VERA_ADDR_L
|
||||
bne ++
|
||||
inc cx16.VERA_ADDR_M
|
||||
+ bne +
|
||||
inc cx16.VERA_ADDR_H
|
||||
inc cx16.VERA_ADDR_H
|
||||
+ inx ; next pixel
|
||||
}}
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_horizontal_line(uword xx, uword yy, uword length, ubyte color) {
|
||||
; does bounds checking and clipping
|
||||
if msb(yy)&$80!=0 or yy>=height
|
||||
if msb(yy)&$80!=0 or yy>=HEIGHT
|
||||
return
|
||||
if msb(xx)&$80!=0 {
|
||||
length += xx
|
||||
xx = 0
|
||||
}
|
||||
if xx>=width
|
||||
if xx>=WIDTH
|
||||
return
|
||||
if xx+length>width
|
||||
length = width-xx
|
||||
if length>width
|
||||
if xx+length>WIDTH
|
||||
length = WIDTH-xx
|
||||
if length>WIDTH
|
||||
return
|
||||
|
||||
horizontal_line(xx, yy, length, color)
|
||||
}
|
||||
|
||||
sub vertical_line(uword xx, uword yy, uword lheight, ubyte color) {
|
||||
when active_mode {
|
||||
1 -> {
|
||||
; lores 256c
|
||||
; set vera auto-increment to 320 pixel increment (=next line)
|
||||
position(xx,yy)
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (14<<4)
|
||||
%asm {{
|
||||
ldy p8v_lheight
|
||||
beq +
|
||||
lda p8v_color
|
||||
- sta cx16.VERA_DATA0
|
||||
dey
|
||||
bne -
|
||||
+
|
||||
}}
|
||||
}
|
||||
2 -> {
|
||||
; highres 4c
|
||||
; use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment is possible
|
||||
if lheight==0
|
||||
return
|
||||
position2(xx,yy,true)
|
||||
set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode
|
||||
;; color &= 3
|
||||
;; color <<= gfx2.plot.shift4c[lsb(xx) & 3]
|
||||
cx16.r2L = lsb(xx) & 3
|
||||
when color & 3 {
|
||||
0 -> color = 0
|
||||
1 -> color = gfx2.plot.shiftedleft_4c_1[cx16.r2L]
|
||||
2 -> color = gfx2.plot.shiftedleft_4c_2[cx16.r2L]
|
||||
3 -> color = gfx2.plot.shiftedleft_4c_3[cx16.r2L]
|
||||
}
|
||||
ubyte @shared mask = gfx2.plot.mask4c[lsb(xx) & 3]
|
||||
repeat lheight {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
and p8v_mask
|
||||
ora p8v_color
|
||||
sta cx16.VERA_DATA1
|
||||
}}
|
||||
}
|
||||
}
|
||||
; highres 4c
|
||||
; use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment is possible
|
||||
if lheight==0
|
||||
return
|
||||
position2(xx,yy,true)
|
||||
set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode
|
||||
;; color &= 3
|
||||
;; color <<= plot.shift4c[lsb(xx) & 3]
|
||||
cx16.r2L = lsb(xx) & 3
|
||||
when color & 3 {
|
||||
0 -> color = 0
|
||||
1 -> color = gfx_hires.plot.shiftedleft_4c_1[cx16.r2L]
|
||||
2 -> color = gfx_hires.plot.shiftedleft_4c_2[cx16.r2L]
|
||||
3 -> color = gfx_hires.plot.shiftedleft_4c_3[cx16.r2L]
|
||||
}
|
||||
ubyte @shared mask = gfx_hires.plot.mask4c[lsb(xx) & 3]
|
||||
repeat lheight {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
and p8v_mask
|
||||
ora p8v_color
|
||||
sta cx16.VERA_DATA1
|
||||
}}
|
||||
}
|
||||
|
||||
sub set_both_strides(ubyte stride) {
|
||||
@@ -285,6 +213,24 @@ gfx2 {
|
||||
|
||||
}
|
||||
|
||||
sub safe_vertical_line(uword xx, uword yy, uword lheight, ubyte color) {
|
||||
; does bounds checking and clipping
|
||||
if msb(yy)&$80!=0 or yy>=HEIGHT
|
||||
return
|
||||
if msb(xx)&$80!=0 or xx>=WIDTH
|
||||
return
|
||||
if msb(yy)&$80!=0 {
|
||||
lheight += yy
|
||||
yy = 0
|
||||
}
|
||||
if yy+lheight>HEIGHT
|
||||
lheight = HEIGHT-yy
|
||||
if lheight>HEIGHT
|
||||
return
|
||||
|
||||
vertical_line(xx, yy, lheight, color)
|
||||
}
|
||||
|
||||
sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, ubyte color) {
|
||||
; Bresenham algorithm.
|
||||
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
|
||||
@@ -564,107 +510,68 @@ gfx2 {
|
||||
ubyte[4] shiftedleft_4c_2 = [2<<6, 2<<4, 2<<2, 2<<0]
|
||||
ubyte[4] shiftedleft_4c_3 = [3<<6, 3<<4, 3<<2, 3<<0]
|
||||
|
||||
when active_mode {
|
||||
1 -> {
|
||||
; lores 256c
|
||||
void addr_mul_24_for_lores_256c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
lda cx16.r1
|
||||
ora #%00010000 ; enable auto-increment so next_pixel() can be used after this
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda cx16.r0
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda p8v_color
|
||||
sta cx16.VERA_DATA0
|
||||
}}
|
||||
}
|
||||
2 -> {
|
||||
; highres 4c ....also mostly usable for lores 4c?
|
||||
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
cx16.r2L = lsb(xx) & 3 ; xbits
|
||||
; color &= 3
|
||||
; color <<= shift4c[cx16.r2L]
|
||||
when color & 3 {
|
||||
0 -> color = 0
|
||||
1 -> color = shiftedleft_4c_1[cx16.r2L]
|
||||
2 -> color = shiftedleft_4c_2[cx16.r2L]
|
||||
3 -> color = shiftedleft_4c_3[cx16.r2L]
|
||||
}
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
lda cx16.r1L
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0H
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda cx16.r0L
|
||||
sta cx16.VERA_ADDR_L
|
||||
ldy cx16.r2L ; xbits
|
||||
lda p8v_mask4c,y
|
||||
and cx16.VERA_DATA0
|
||||
ora p8v_color
|
||||
sta cx16.VERA_DATA0
|
||||
}}
|
||||
}
|
||||
; highres 4c ....also mostly usable for lores 4c?
|
||||
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
cx16.r2L = lsb(xx) & 3 ; xbits
|
||||
; color &= 3
|
||||
; color <<= shift4c[cx16.r2L]
|
||||
when color & 3 {
|
||||
0 -> color = 0
|
||||
1 -> color = shiftedleft_4c_1[cx16.r2L]
|
||||
2 -> color = shiftedleft_4c_2[cx16.r2L]
|
||||
3 -> color = shiftedleft_4c_3[cx16.r2L]
|
||||
}
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
lda cx16.r1L
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0H
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda cx16.r0L
|
||||
sta cx16.VERA_ADDR_L
|
||||
ldy cx16.r2L ; xbits
|
||||
lda p8v_mask4c,y
|
||||
and cx16.VERA_DATA0
|
||||
ora p8v_color
|
||||
sta cx16.VERA_DATA0
|
||||
}}
|
||||
}
|
||||
|
||||
sub safe_plot(uword xx, uword yy, ubyte color) {
|
||||
; A plot that does bounds checks to see if the pixel is inside the screen.
|
||||
if msb(xx)&$80!=0 or msb(yy)&$80!=0
|
||||
return
|
||||
if xx >= width or yy >= height
|
||||
if xx >= WIDTH or yy >= HEIGHT
|
||||
return
|
||||
plot(xx, yy, color)
|
||||
}
|
||||
|
||||
sub pget(uword @zp xx, uword yy) -> ubyte {
|
||||
when active_mode {
|
||||
1 -> {
|
||||
; lores 256c
|
||||
void addr_mul_24_for_lores_256c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
lda cx16.r1
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda cx16.r0
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.VERA_DATA0
|
||||
}}
|
||||
}
|
||||
2 -> {
|
||||
; hires 4c
|
||||
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
lda cx16.r1L
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0H
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda cx16.r0L
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.VERA_DATA0
|
||||
pha
|
||||
lda p8v_xx
|
||||
and #3
|
||||
tay
|
||||
lda p8b_gfx2.p8s_plot.p8v_shift4c,y
|
||||
tay
|
||||
pla
|
||||
cpy #0
|
||||
beq +
|
||||
- lsr a
|
||||
dey
|
||||
bne -
|
||||
+ and #3
|
||||
}}
|
||||
}
|
||||
else -> return 0
|
||||
}
|
||||
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
lda cx16.r1L
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0H
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda cx16.r0L
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.VERA_DATA0
|
||||
pha
|
||||
lda p8v_xx
|
||||
and #3
|
||||
tay
|
||||
lda p8b_gfx_hires.p8s_plot.p8v_shift4c,y
|
||||
tay
|
||||
pla
|
||||
cpy #0
|
||||
beq +
|
||||
- lsr a
|
||||
dey
|
||||
bne -
|
||||
+ and #3
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub fill(uword x, uword y, ubyte new_color) {
|
||||
@@ -696,7 +603,7 @@ gfx2 {
|
||||
if cx16.r12L==MAXDEPTH
|
||||
return
|
||||
cx16.r0s = sy+sdy
|
||||
if cx16.r0s>=0 and cx16.r0s<=height-1 {
|
||||
if cx16.r0s>=0 and cx16.r0s<=HEIGHT-1 {
|
||||
;; stack_xl[cx16.r12L] = sxl
|
||||
;; stack_xr[cx16.r12L] = sxr
|
||||
;; stack_y[cx16.r12L] = sy
|
||||
@@ -753,29 +660,23 @@ gfx2 {
|
||||
cx16.r11L = pget(xx as uword, yy as uword) ; old_color
|
||||
if cx16.r11L == cx16.r10L
|
||||
return
|
||||
if xx<0 or xx>width-1 or yy<0 or yy>height-1
|
||||
if xx<0 or xx>WIDTH-1 or yy<0 or yy>HEIGHT-1
|
||||
return
|
||||
if gfx2.active_mode == 2 set_color_masks()
|
||||
set_color_masks()
|
||||
push_stack(xx, xx, yy, 1)
|
||||
push_stack(xx, xx, yy + 1, -1)
|
||||
word left = 0
|
||||
while cx16.r12L!=0 {
|
||||
pop_stack()
|
||||
xx = x1
|
||||
when active_mode {
|
||||
1 -> if fill_scanline_left_8bpp() goto skip
|
||||
2 -> if fill_scanline_left_2bpp() goto skip
|
||||
}
|
||||
if fill_scanline_left_2bpp() goto skip
|
||||
left = xx + 1
|
||||
if left < x1
|
||||
push_stack(left, x1 - 1, yy, -dy)
|
||||
xx = x1 + 1
|
||||
|
||||
do {
|
||||
when active_mode {
|
||||
1 -> fill_scanline_right_8bpp()
|
||||
2 -> fill_scanline_right_2bpp()
|
||||
}
|
||||
fill_scanline_right_2bpp()
|
||||
push_stack(left, xx - 1, yy, dy)
|
||||
if xx > x2 + 1
|
||||
push_stack(x2 + 1, xx - 1, yy, -dy)
|
||||
@@ -801,35 +702,9 @@ skip:
|
||||
cx16.VERA_CTRL = 0
|
||||
}
|
||||
|
||||
sub fill_scanline_left_8bpp() -> bool {
|
||||
void addr_mul_24_for_lores_256c(yy as uword, xx as uword) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
cx16.r1L |= %00011000 ; auto decrement enabled
|
||||
set_vera_address()
|
||||
cx16.r9s = xx
|
||||
while xx >= 0 {
|
||||
if cx16.VERA_DATA0 != cx16.r11L
|
||||
break
|
||||
cx16.VERA_DATA1 = cx16.r10L
|
||||
xx--
|
||||
}
|
||||
return xx==cx16.r9s
|
||||
}
|
||||
|
||||
sub fill_scanline_right_8bpp() {
|
||||
void addr_mul_24_for_lores_256c(yy as uword, xx as uword) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
cx16.r1L |= %00010000 ; auto increment enabled
|
||||
set_vera_address()
|
||||
while xx <= width-1 {
|
||||
if cx16.VERA_DATA0 != cx16.r11L
|
||||
break
|
||||
cx16.VERA_DATA1 = cx16.r10L
|
||||
xx++
|
||||
}
|
||||
}
|
||||
|
||||
sub fill_scanline_left_2bpp() -> bool {
|
||||
uword vx = xx as uword
|
||||
void gfx2.addr_mul_24_for_highres_4c(yy as uword,vx)
|
||||
void gfx_hires.addr_mul_24_for_highres_4c(yy as uword,vx)
|
||||
cx16.r1L |= %0001_1000 ; auto decrement
|
||||
set_vera_address()
|
||||
cmask = amask[lsb(vx) & 3] ; set the color mask for the first color pel
|
||||
@@ -873,7 +748,7 @@ set_byte:
|
||||
}
|
||||
|
||||
sub fill_scanline_right_2bpp() {
|
||||
void gfx2.addr_mul_24_for_highres_4c(yy as uword,xx as uword)
|
||||
void gfx_hires.addr_mul_24_for_highres_4c(yy as uword,xx as uword)
|
||||
cx16.r1L |= %00010000 ; auto increment
|
||||
set_vera_address()
|
||||
cmask = amask[lsb(xx) & 3] ; set the color mask for the first color pel
|
||||
@@ -907,7 +782,7 @@ set_byte:
|
||||
}
|
||||
set_byte:
|
||||
cx16.VERA_DATA1 = nvub
|
||||
if xx >= gfx2.width-1 break
|
||||
if xx >= WIDTH-1 break
|
||||
cmask = $C0
|
||||
}
|
||||
}
|
||||
@@ -919,16 +794,7 @@ set_byte:
|
||||
}
|
||||
|
||||
sub position(uword @zp xx, uword yy) {
|
||||
when active_mode {
|
||||
1 -> {
|
||||
; lores 256c
|
||||
void addr_mul_24_for_lores_256c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
}
|
||||
2 -> {
|
||||
; highres 4c
|
||||
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
}
|
||||
}
|
||||
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
cx16.r2L = cx16.r1L
|
||||
cx16.vaddr(cx16.r2L, cx16.r0, 0, 1)
|
||||
}
|
||||
@@ -1010,83 +876,36 @@ set_byte:
|
||||
ubyte[8] @shared char_bitmap_bytes_left
|
||||
ubyte[8] @shared char_bitmap_bytes_right
|
||||
|
||||
when active_mode {
|
||||
1 -> {
|
||||
; lores 256c
|
||||
while @(sctextptr)!=0 {
|
||||
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
||||
cx16.vaddr(charset_bank, chardataptr, 1, 1)
|
||||
repeat 8 {
|
||||
position(xx,yy)
|
||||
yy++
|
||||
%asm {{
|
||||
ldx p8v_color
|
||||
lda cx16.VERA_DATA1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
ldy #8
|
||||
- asl P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
stx cx16.VERA_DATA0 ; write a pixel
|
||||
bra ++
|
||||
+ lda cx16.VERA_DATA0 ; don't write a pixel, but do advance to the next address
|
||||
+ dey
|
||||
bne -
|
||||
}}
|
||||
}
|
||||
xx+=8
|
||||
yy-=8
|
||||
sctextptr++
|
||||
}
|
||||
}
|
||||
2 -> {
|
||||
; hires 4c
|
||||
; we're going to use a few cx16 registers to make sure every variable is in zeropage in the inner loop.
|
||||
cx16.r11L = color
|
||||
while @(sctextptr)!=0 {
|
||||
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
||||
cx16.vaddr(charset_bank, chardataptr, 1, 1) ; for reading the chardata from Vera data channel 1
|
||||
position(xx, yy) ; only calculated once, we update vera address in the loop instead
|
||||
cx16.VERA_ADDR_H &= $0f ; no auto increment
|
||||
repeat 8 {
|
||||
cx16.r10L = cx16.VERA_DATA1 ; get the next 8 horizontal character bits
|
||||
cx16.r7 = xx
|
||||
repeat 8 {
|
||||
cx16.r10L <<= 1
|
||||
if_cs {
|
||||
cx16.r2L = cx16.r7L & 3 ; xbits
|
||||
when cx16.r11L & 3 {
|
||||
1 -> cx16.r12L = gfx2.plot.shiftedleft_4c_1[cx16.r2L]
|
||||
2 -> cx16.r12L = gfx2.plot.shiftedleft_4c_2[cx16.r2L]
|
||||
3 -> cx16.r12L = gfx2.plot.shiftedleft_4c_3[cx16.r2L]
|
||||
else -> cx16.r12L = 0
|
||||
}
|
||||
cx16.VERA_DATA0 = cx16.VERA_DATA0 & gfx2.plot.mask4c[cx16.r2L] | cx16.r12L
|
||||
}
|
||||
cx16.r7++
|
||||
if (cx16.r7 & 3) == 0 {
|
||||
; increment the pixel address by one
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
clc
|
||||
lda cx16.VERA_ADDR_L
|
||||
adc #1
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.VERA_ADDR_M
|
||||
adc #0
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda cx16.VERA_ADDR_H
|
||||
adc #0
|
||||
sta cx16.VERA_ADDR_H
|
||||
}}
|
||||
}
|
||||
; we're going to use a few cx16 registers to make sure every variable is in zeropage in the inner loop.
|
||||
cx16.r11L = color
|
||||
while @(sctextptr)!=0 {
|
||||
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
||||
cx16.vaddr(charset_bank, chardataptr, 1, 1) ; for reading the chardata from Vera data channel 1
|
||||
position(xx, yy) ; only calculated once, we update vera address in the loop instead
|
||||
cx16.VERA_ADDR_H &= $0f ; no auto increment
|
||||
repeat 8 {
|
||||
cx16.r10L = cx16.VERA_DATA1 ; get the next 8 horizontal character bits
|
||||
cx16.r7 = xx
|
||||
repeat 8 {
|
||||
cx16.r10L <<= 1
|
||||
if_cs {
|
||||
cx16.r2L = cx16.r7L & 3 ; xbits
|
||||
when cx16.r11L & 3 {
|
||||
1 -> cx16.r12L = gfx_hires.plot.shiftedleft_4c_1[cx16.r2L]
|
||||
2 -> cx16.r12L = gfx_hires.plot.shiftedleft_4c_2[cx16.r2L]
|
||||
3 -> cx16.r12L = gfx_hires.plot.shiftedleft_4c_3[cx16.r2L]
|
||||
else -> cx16.r12L = 0
|
||||
}
|
||||
|
||||
; increment pixel address to the next line
|
||||
cx16.VERA_DATA0 = cx16.VERA_DATA0 & gfx_hires.plot.mask4c[cx16.r2L] | cx16.r12L
|
||||
}
|
||||
cx16.r7++
|
||||
if (cx16.r7 & 3) == 0 {
|
||||
; increment the pixel address by one
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
clc
|
||||
lda cx16.VERA_ADDR_L
|
||||
adc #(640-8)/4
|
||||
adc #1
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.VERA_ADDR_M
|
||||
adc #0
|
||||
@@ -1096,10 +915,25 @@ set_byte:
|
||||
sta cx16.VERA_ADDR_H
|
||||
}}
|
||||
}
|
||||
xx+=8
|
||||
sctextptr++
|
||||
}
|
||||
|
||||
; increment pixel address to the next line
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
clc
|
||||
lda cx16.VERA_ADDR_L
|
||||
adc #(640-8)/4
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.VERA_ADDR_M
|
||||
adc #0
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda cx16.VERA_ADDR_H
|
||||
adc #0
|
||||
sta cx16.VERA_ADDR_H
|
||||
}}
|
||||
}
|
||||
xx+=8
|
||||
sctextptr++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1170,50 +1004,4 @@ set_byte:
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub addr_mul_24_for_lores_256c(uword yy @R0, uword xx @AY) clobbers(A) -> uword @R0, ubyte @R1 {
|
||||
; yy * 320 + xx (24 bits calculation)
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r0
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda cx16.r0+1
|
||||
sta cx16.r1
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda cx16.r0
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
sta cx16.r0
|
||||
lda P8ZP_SCRATCH_B1
|
||||
clc
|
||||
adc P8ZP_SCRATCH_REG
|
||||
sta cx16.r0+1
|
||||
bcc +
|
||||
inc cx16.r1
|
||||
+ ; now add the value to this 24-bits number
|
||||
lda cx16.r0
|
||||
clc
|
||||
adc P8ZP_SCRATCH_W1
|
||||
sta cx16.r0
|
||||
lda cx16.r0+1
|
||||
adc P8ZP_SCRATCH_W1+1
|
||||
sta cx16.r0+1
|
||||
bcc +
|
||||
inc cx16.r1
|
||||
+ lda cx16.r1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,14 +2,18 @@
|
||||
; bitmap image needs to start at VRAM addres $00000.
|
||||
; This is compatible with the CX16's screen mode 128. (void cx16.set_screen_mode(128))
|
||||
|
||||
|
||||
%import syslib
|
||||
%import verafx
|
||||
|
||||
|
||||
gfx_lores {
|
||||
|
||||
%option ignore_unused
|
||||
|
||||
sub set_screen_mode() {
|
||||
const uword WIDTH = 320
|
||||
const ubyte HEIGHT = 240
|
||||
|
||||
sub graphics_mode() {
|
||||
; enable 320x240 256c bitmap graphics mode
|
||||
cx16.VERA_CTRL=0
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
|
||||
cx16.VERA_DC_HSCALE = 64
|
||||
@@ -18,8 +22,23 @@ gfx_lores {
|
||||
cx16.VERA_L1_MAPBASE = 0
|
||||
cx16.VERA_L1_TILEBASE = 0
|
||||
clear_screen(0)
|
||||
drawmode_eor(false)
|
||||
}
|
||||
|
||||
sub text_mode() {
|
||||
; back to normal text mode
|
||||
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
|
||||
cbm.CINT()
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
|
||||
}
|
||||
|
||||
sub drawmode_eor(bool enabled) {
|
||||
; with EOR drawing mode you can have non destructive drawing (2*EOR=restore original)
|
||||
eor_mode = enabled
|
||||
}
|
||||
|
||||
bool eor_mode
|
||||
|
||||
sub clear_screen(ubyte color) {
|
||||
if verafx.available() {
|
||||
; use verafx cache writes to quicly clear the screen
|
||||
@@ -56,10 +75,10 @@ gfx_lores {
|
||||
cx16.VERA_CTRL=0
|
||||
cx16.VERA_ADDR=0
|
||||
cx16.VERA_ADDR_H = 1<<4 ; 1 pixel auto increment
|
||||
repeat 240 {
|
||||
repeat HEIGHT {
|
||||
%asm {{
|
||||
lda p8v_color
|
||||
ldy #320/8
|
||||
ldy #p8c_WIDTH/8
|
||||
- .rept 8
|
||||
sta cx16.VERA_DATA0
|
||||
.endrept
|
||||
@@ -71,28 +90,186 @@ gfx_lores {
|
||||
cx16.VERA_ADDR_H = 0
|
||||
}
|
||||
|
||||
asmsub plot(uword x @AX, ubyte y @Y, ubyte color @R0) {
|
||||
; x in r0, y in r1, color.
|
||||
%asm {{
|
||||
clc
|
||||
adc times320_lo,y
|
||||
sta cx16.VERA_ADDR_L
|
||||
txa
|
||||
adc times320_mid,y
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda #0
|
||||
adc times320_hi,y
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0L
|
||||
sta cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
sub rect(uword xx, ubyte yy, uword rwidth, ubyte rheight, ubyte color) {
|
||||
if rwidth==0 or rheight==0
|
||||
return
|
||||
horizontal_line(xx, yy, rwidth, color)
|
||||
if rheight==1
|
||||
return
|
||||
horizontal_line(xx, yy+rheight-1, rwidth, color)
|
||||
vertical_line(xx, yy+1, rheight-2, color)
|
||||
if rwidth==1
|
||||
return
|
||||
vertical_line(xx+rwidth-1, yy+1, rheight-2, color)
|
||||
}
|
||||
|
||||
sub safe_rect(uword xx, ubyte yy, uword rwidth, ubyte rheight, ubyte color) {
|
||||
; does bounds checking and clipping
|
||||
safe_horizontal_line(xx, yy, rwidth, color)
|
||||
if rheight==1
|
||||
return
|
||||
uword bottomyy = yy as uword + rheight -1
|
||||
if bottomyy<HEIGHT
|
||||
safe_horizontal_line(xx, lsb(bottomyy), rwidth, color)
|
||||
safe_vertical_line(xx, yy+1, rheight-2, color)
|
||||
if rwidth==1
|
||||
return
|
||||
safe_vertical_line(xx+rwidth-1, yy+1, rheight-2, color)
|
||||
}
|
||||
|
||||
sub fillrect(uword xx, ubyte yy, uword rwidth, ubyte rheight, ubyte color) {
|
||||
; Draw a filled rectangle of the given size and color.
|
||||
; To fill the whole screen, use clear_screen(color) instead - it is much faster.
|
||||
if rwidth==0
|
||||
return
|
||||
repeat rheight {
|
||||
horizontal_line(xx, yy, rwidth, color)
|
||||
yy++
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_fillrect(uword xx, ubyte yy, uword rwidth, ubyte rheight, ubyte color) {
|
||||
; Draw a filled rectangle of the given size and color.
|
||||
; To fill the whole screen, use clear_screen(color) instead - it is much faster.
|
||||
; This safe version does bounds checking and clipping.
|
||||
if xx>=WIDTH or yy>=HEIGHT
|
||||
return
|
||||
if msb(xx)&$80!=0 {
|
||||
rwidth += xx
|
||||
xx = 0
|
||||
}
|
||||
if xx>=WIDTH
|
||||
return
|
||||
if xx+rwidth>WIDTH
|
||||
rwidth = WIDTH-xx
|
||||
if rwidth>WIDTH
|
||||
return
|
||||
|
||||
if yy as uword + rheight > HEIGHT
|
||||
rheight = HEIGHT-yy
|
||||
if rheight>HEIGHT
|
||||
return
|
||||
|
||||
repeat rheight {
|
||||
horizontal_line(xx, yy, rwidth, color)
|
||||
yy++
|
||||
}
|
||||
}
|
||||
|
||||
sub horizontal_line(uword xx, ubyte yy, uword length, ubyte color) {
|
||||
if length==0
|
||||
return
|
||||
position(xx, yy)
|
||||
; set vera auto-increment to 1 pixel
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (1<<4)
|
||||
if eor_mode {
|
||||
cx16.vaddr_clone(0) ; also setup port 1, for reading
|
||||
%asm {{
|
||||
ldx p8v_length+1
|
||||
beq +
|
||||
ldy #0
|
||||
- lda p8v_color
|
||||
eor cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
iny
|
||||
bne -
|
||||
dex
|
||||
bne -
|
||||
+ ldy p8v_length ; remaining
|
||||
beq +
|
||||
- lda p8v_color
|
||||
eor cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
dey
|
||||
bne -
|
||||
+
|
||||
}}
|
||||
} else {
|
||||
%asm {{
|
||||
lda p8v_color
|
||||
ldx p8v_length+1
|
||||
beq +
|
||||
ldy #0
|
||||
- sta cx16.VERA_DATA0
|
||||
iny
|
||||
bne -
|
||||
dex
|
||||
bne -
|
||||
+ ldy p8v_length ; remaining
|
||||
beq +
|
||||
- sta cx16.VERA_DATA0
|
||||
dey
|
||||
bne -
|
||||
+
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_horizontal_line(uword xx, ubyte yy, uword length, ubyte color) {
|
||||
; does bounds checking and clipping
|
||||
if yy>=HEIGHT
|
||||
return
|
||||
if msb(xx)&$80!=0 {
|
||||
length += xx
|
||||
xx = 0
|
||||
}
|
||||
if xx>=WIDTH
|
||||
return
|
||||
if xx+length>WIDTH
|
||||
length = WIDTH-xx
|
||||
if length>WIDTH
|
||||
return
|
||||
|
||||
horizontal_line(xx, yy, length, color)
|
||||
}
|
||||
|
||||
sub vertical_line(uword xx, ubyte yy, ubyte lheight, ubyte color) {
|
||||
if lheight==0
|
||||
return
|
||||
position(xx, yy)
|
||||
; set vera auto-increment to 320 pixel increment (=next line)
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (14<<4)
|
||||
if eor_mode {
|
||||
cx16.vaddr_clone(0) ; also setup port 1, for reading
|
||||
%asm {{
|
||||
ldy p8v_lheight
|
||||
beq +
|
||||
- lda p8v_color
|
||||
eor cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
dey
|
||||
bne -
|
||||
+
|
||||
}}
|
||||
} else {
|
||||
%asm {{
|
||||
ldy p8v_lheight
|
||||
lda p8v_color
|
||||
- sta cx16.VERA_DATA0
|
||||
dey
|
||||
bne -
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_vertical_line(uword xx, ubyte yy, ubyte lheight, ubyte color) {
|
||||
; does bounds checking and clipping
|
||||
if yy>=HEIGHT
|
||||
return
|
||||
if msb(xx)&$80!=0 or xx>=WIDTH
|
||||
return
|
||||
if yy as uword + lheight > HEIGHT
|
||||
lheight = HEIGHT-yy
|
||||
if lheight>HEIGHT
|
||||
return
|
||||
|
||||
vertical_line(xx, yy, lheight, color)
|
||||
}
|
||||
|
||||
sub line(uword x1, ubyte y1, uword x2, ubyte y2, ubyte color) {
|
||||
; Bresenham algorithm.
|
||||
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
|
||||
; NOTE: this is about twice as fast as the kernal routine GRAPH_draw_line, and ~3-4 times as fast as gfx2.line()
|
||||
; NOTE: this is about twice as fast as the kernal routine GRAPH_draw_line
|
||||
; it trades memory for speed (uses inline plot routine and multiplication lookup tables)
|
||||
;
|
||||
; NOTE: is currently still a regular 6502 routine, could likely be made much faster with the VeraFX line helper.
|
||||
@@ -207,55 +384,512 @@ gfx_lores {
|
||||
lda #0
|
||||
adc times320_hi,y
|
||||
sta cx16.VERA_ADDR_H
|
||||
|
||||
lda p8v_eor_mode
|
||||
bne +
|
||||
lda p8v_color
|
||||
sta cx16.VERA_DATA0
|
||||
rts
|
||||
+ lda p8v_color
|
||||
eor cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
sub horizontal_line(uword xx, ubyte yy, uword length, ubyte color) {
|
||||
if length==0
|
||||
sub circle(uword @zp xcenter, ubyte @zp ycenter, ubyte radius, ubyte color) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm.
|
||||
if radius==0
|
||||
return
|
||||
plot(xx, yy, color) ; set starting position by reusing plot routine
|
||||
; set vera auto-increment to 1 pixel
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (1<<4)
|
||||
|
||||
ubyte @zp xx = radius
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-xx
|
||||
; R14 = internal plot X
|
||||
; R15 = internal plot Y
|
||||
|
||||
while xx>=yy {
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter + yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter + xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
xx--
|
||||
decisionOver2 -= xx*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
|
||||
sub plotq() {
|
||||
; cx16.r14 = x, cx16.r15 = y, color=color.
|
||||
plot(cx16.r14, cx16.r15L, color)
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, ubyte color) {
|
||||
; This version does bounds checks and clipping, but is a lot slower.
|
||||
; Midpoint algorithm.
|
||||
if radius==0
|
||||
return
|
||||
|
||||
ubyte @zp xx = radius
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-xx
|
||||
; R14 = internal plot X
|
||||
; R15 = internal plot Y
|
||||
|
||||
while xx>=yy {
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter + yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter + xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
xx--
|
||||
decisionOver2 -= xx*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
|
||||
sub plotq() {
|
||||
; cx16.r14 = x, cx16.r15 = y, color=color.
|
||||
if cx16.r15 < HEIGHT
|
||||
safe_plot(cx16.r14, cx16.r15L, color)
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(uword @zp xcenter, ubyte @zp ycenter, ubyte @zp radius, ubyte color) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm, filled
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
ubyte last_y3 = ycenter+radius
|
||||
ubyte last_y4 = ycenter-radius
|
||||
ubyte new_y3, new_y4
|
||||
|
||||
while radius>=yy {
|
||||
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
|
||||
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
|
||||
|
||||
new_y3 = ycenter+radius
|
||||
if new_y3 != last_y3 {
|
||||
horizontal_line(xcenter-yy, last_y3, yy*$0002+1, color)
|
||||
last_y3 = new_y3
|
||||
}
|
||||
new_y4 = ycenter-radius
|
||||
if new_y4 != last_y4 {
|
||||
horizontal_line(xcenter-yy, last_y4, yy*$0002+1, color)
|
||||
last_y4 = new_y4
|
||||
}
|
||||
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
decisionOver2 -= radius*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
|
||||
; draw the final two spans
|
||||
yy--
|
||||
horizontal_line(xcenter-yy, last_y3, yy*$0002+1, color)
|
||||
horizontal_line(xcenter-yy, last_y4, yy*$0002+1, color)
|
||||
}
|
||||
|
||||
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
|
||||
; This version does bounds checks and clipping, but is a lot slower.
|
||||
; Midpoint algorithm, filled
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
uword last_y3 = ycenter+radius
|
||||
uword last_y4 = ycenter-radius
|
||||
uword new_y3, new_y4
|
||||
|
||||
while radius>=yy {
|
||||
uword liney = ycenter+yy
|
||||
if msb(liney)==0
|
||||
safe_horizontal_line(xcenter-radius, lsb(ycenter+yy), radius*$0002+1, color)
|
||||
liney = ycenter-yy
|
||||
if msb(liney)==0
|
||||
safe_horizontal_line(xcenter-radius, lsb(ycenter-yy), radius*$0002+1, color)
|
||||
new_y3 = ycenter+radius
|
||||
if new_y3 != last_y3 {
|
||||
if msb(last_y3)==0
|
||||
safe_horizontal_line(xcenter-yy, lsb(last_y3), yy*$0002+1, color)
|
||||
last_y3 = new_y3
|
||||
}
|
||||
new_y4 = ycenter-radius
|
||||
if new_y4 != last_y4 {
|
||||
if msb(last_y4)==0
|
||||
safe_horizontal_line(xcenter-yy, lsb(last_y4), yy*$0002+1, color)
|
||||
last_y4 = new_y4
|
||||
}
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
decisionOver2 -= radius*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
; draw the final two spans
|
||||
yy--
|
||||
if msb(last_y3)==0
|
||||
safe_horizontal_line(xcenter-yy, lsb(last_y3), yy*$0002+1, color)
|
||||
if msb(last_y4)==0
|
||||
safe_horizontal_line(xcenter-yy, lsb(last_y4), yy*$0002+1, color)
|
||||
}
|
||||
|
||||
asmsub plot(uword x @AX, ubyte y @Y, ubyte color @R0) {
|
||||
; x in r0, y in r1, color.
|
||||
%asm {{
|
||||
lda p8v_color
|
||||
ldx p8v_length+1
|
||||
clc
|
||||
adc times320_lo,y
|
||||
sta cx16.VERA_ADDR_L
|
||||
txa
|
||||
adc times320_mid,y
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda #0
|
||||
adc times320_hi,y
|
||||
sta cx16.VERA_ADDR_H
|
||||
|
||||
lda p8v_eor_mode
|
||||
bne +
|
||||
lda cx16.r0L
|
||||
sta cx16.VERA_DATA0
|
||||
rts
|
||||
+ lda cx16.r0L
|
||||
eor cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub safe_plot(uword xx, ubyte yy, ubyte color) {
|
||||
; A plot that does bounds checks to see if the pixel is inside the screen.
|
||||
if msb(xx)&$80!=0
|
||||
return
|
||||
if xx >= WIDTH or yy >= HEIGHT
|
||||
return
|
||||
plot(xx, yy, color)
|
||||
}
|
||||
|
||||
asmsub pget(uword x @AX, ubyte y @Y) -> ubyte @A {
|
||||
; returns the color of the pixel
|
||||
%asm {{
|
||||
jsr p8s_position
|
||||
lda cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub fill(uword x, ubyte y, ubyte new_color) {
|
||||
; reuse a few virtual registers in ZP for variables
|
||||
&ubyte fillm = &cx16.r7L
|
||||
&ubyte seedm = &cx16.r8L
|
||||
&ubyte cmask = &cx16.r8H
|
||||
&ubyte vub = &cx16.r13L
|
||||
&ubyte nvub = &cx16.r13H
|
||||
ubyte[4] amask = [$c0,$30,$0c,$03] ; array of cmask bytes
|
||||
|
||||
; Non-recursive scanline flood fill.
|
||||
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
|
||||
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
|
||||
const ubyte MAXDEPTH = 100
|
||||
word @zp xx = x as word
|
||||
word @zp yy = y as word
|
||||
word[MAXDEPTH] @split @shared stack_xl
|
||||
word[MAXDEPTH] @split @shared stack_xr
|
||||
word[MAXDEPTH] @split @shared stack_y
|
||||
byte[MAXDEPTH] @shared stack_dy
|
||||
cx16.r12L = 0 ; stack pointer
|
||||
word x1
|
||||
word x2
|
||||
byte dy
|
||||
cx16.r10L = new_color
|
||||
|
||||
sub push_stack(word sxl, word sxr, word sy, byte sdy) {
|
||||
if cx16.r12L==MAXDEPTH
|
||||
return
|
||||
cx16.r0s = sy+sdy
|
||||
if cx16.r0s>=0 and cx16.r0s<=HEIGHT-1 {
|
||||
;; stack_xl[cx16.r12L] = sxl
|
||||
;; stack_xr[cx16.r12L] = sxr
|
||||
;; stack_y[cx16.r12L] = sy
|
||||
;; stack_dy[cx16.r12L] = sdy
|
||||
;; cx16.r12L++
|
||||
%asm {{
|
||||
ldy cx16.r12L
|
||||
lda p8v_sxl
|
||||
sta p8v_stack_xl_lsb,y
|
||||
lda p8v_sxl+1
|
||||
sta p8v_stack_xl_msb,y
|
||||
lda p8v_sxr
|
||||
sta p8v_stack_xr_lsb,y
|
||||
lda p8v_sxr+1
|
||||
sta p8v_stack_xr_msb,y
|
||||
lda p8v_sy
|
||||
sta p8v_stack_y_lsb,y
|
||||
lda p8v_sy+1
|
||||
sta p8v_stack_y_msb,y
|
||||
ldy cx16.r12L
|
||||
lda p8v_sdy
|
||||
sta p8v_stack_dy,y
|
||||
inc cx16.r12L
|
||||
}}
|
||||
}
|
||||
}
|
||||
sub pop_stack() {
|
||||
;; cx16.r12L--
|
||||
;; x1 = stack_xl[cx16.r12L]
|
||||
;; x2 = stack_xr[cx16.r12L]
|
||||
;; y = stack_y[cx16.r12L]
|
||||
;; dy = stack_dy[cx16.r12L]
|
||||
%asm {{
|
||||
dec cx16.r12L
|
||||
ldy cx16.r12L
|
||||
lda p8v_stack_xl_lsb,y
|
||||
sta p8v_x1
|
||||
lda p8v_stack_xl_msb,y
|
||||
sta p8v_x1+1
|
||||
lda p8v_stack_xr_lsb,y
|
||||
sta p8v_x2
|
||||
lda p8v_stack_xr_msb,y
|
||||
sta p8v_x2+1
|
||||
lda p8v_stack_y_lsb,y
|
||||
sta p8v_yy
|
||||
lda p8v_stack_y_msb,y
|
||||
sta p8v_yy+1
|
||||
ldy cx16.r12L
|
||||
lda p8v_stack_dy,y
|
||||
sta p8v_dy
|
||||
}}
|
||||
yy+=dy
|
||||
}
|
||||
cx16.r11L = pget(xx as uword, lsb(yy)) ; old_color
|
||||
if cx16.r11L == cx16.r10L
|
||||
return
|
||||
if xx<0 or xx>WIDTH-1 or yy<0 or yy>HEIGHT-1
|
||||
return
|
||||
push_stack(xx, xx, yy, 1)
|
||||
push_stack(xx, xx, yy + 1, -1)
|
||||
word left = 0
|
||||
while cx16.r12L!=0 {
|
||||
pop_stack()
|
||||
xx = x1
|
||||
if fill_scanline_left_8bpp() goto skip
|
||||
left = xx + 1
|
||||
if left < x1
|
||||
push_stack(left, x1 - 1, yy, -dy)
|
||||
xx = x1 + 1
|
||||
|
||||
do {
|
||||
fill_scanline_right_8bpp()
|
||||
push_stack(left, xx - 1, yy, dy)
|
||||
if xx > x2 + 1
|
||||
push_stack(x2 + 1, xx - 1, yy, -dy)
|
||||
skip:
|
||||
xx++
|
||||
while xx <= x2 {
|
||||
if pget(xx as uword, lsb(yy)) == cx16.r11L
|
||||
break
|
||||
xx++
|
||||
}
|
||||
left = xx
|
||||
} until xx>x2
|
||||
}
|
||||
|
||||
sub set_vera_address(bool decr) {
|
||||
; set both data0 and data1 addresses
|
||||
position(xx as uword, lsb(yy))
|
||||
cx16.r0 = cx16.VERA_ADDR
|
||||
cx16.r1L = cx16.VERA_ADDR_H & 1 | if decr %00011000 else %00010000
|
||||
cx16.VERA_ADDR_H = cx16.r1L
|
||||
cx16.VERA_CTRL = 1
|
||||
cx16.VERA_ADDR = cx16.r0
|
||||
cx16.VERA_ADDR_H = cx16.r1L
|
||||
cx16.VERA_CTRL = 0
|
||||
}
|
||||
|
||||
sub fill_scanline_left_8bpp() -> bool {
|
||||
set_vera_address(true)
|
||||
cx16.r9s = xx
|
||||
while xx >= 0 {
|
||||
if cx16.VERA_DATA0 != cx16.r11L
|
||||
break
|
||||
cx16.VERA_DATA1 = cx16.r10L
|
||||
xx--
|
||||
}
|
||||
return xx==cx16.r9s
|
||||
}
|
||||
|
||||
sub fill_scanline_right_8bpp() {
|
||||
set_vera_address(false)
|
||||
while xx <= WIDTH-1 {
|
||||
if cx16.VERA_DATA0 != cx16.r11L
|
||||
break
|
||||
cx16.VERA_DATA1 = cx16.r10L
|
||||
xx++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub text_charset(ubyte charset) {
|
||||
; -- select the text charset to use with the text() routine
|
||||
; the charset number is the same as for the cx16.screen_set_charset() ROM function.
|
||||
; 1 = ISO charset, 2 = PETSCII uppercase+graphs, 3= PETSCII uppercase+lowercase etc. etc.
|
||||
cx16.screen_set_charset(charset, 0)
|
||||
}
|
||||
|
||||
const ubyte charset_bank = $1
|
||||
const uword charset_addr = $f000 ; in bank 1, so $1f000
|
||||
|
||||
sub text(uword @zp xx, uword yy, ubyte color, uword textptr) {
|
||||
; -- Write some text at the given pixel position. The text string must be in an encoding approprite for the charset.
|
||||
; You must also have called text_charset() first to select and prepare the character set to use.
|
||||
uword chardataptr
|
||||
ubyte[8] @shared char_bitmap_bytes_left
|
||||
ubyte[8] @shared char_bitmap_bytes_right
|
||||
|
||||
while @(textptr)!=0 {
|
||||
chardataptr = charset_addr + (@(textptr) as uword)*8
|
||||
cx16.vaddr(charset_bank, chardataptr, 1, 1)
|
||||
repeat 8 {
|
||||
position(xx,lsb(yy))
|
||||
yy++
|
||||
%asm {{
|
||||
ldx p8v_color
|
||||
lda cx16.VERA_DATA1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
ldy #8
|
||||
- asl P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
stx cx16.VERA_DATA0 ; write a pixel
|
||||
bra ++
|
||||
+ lda cx16.VERA_DATA0 ; don't write a pixel, but do advance to the next address
|
||||
+ dey
|
||||
bne -
|
||||
}}
|
||||
}
|
||||
xx+=8
|
||||
yy-=8
|
||||
textptr++
|
||||
}
|
||||
}
|
||||
|
||||
asmsub position(uword x @AX, ubyte y @Y) {
|
||||
%asm {{
|
||||
clc
|
||||
adc times320_lo,y
|
||||
sta cx16.VERA_ADDR_L
|
||||
txa
|
||||
adc times320_mid,y
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda #%00010000 ; auto increment on
|
||||
adc times320_hi,y
|
||||
sta cx16.VERA_ADDR_H
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub next_pixel(ubyte color @A) {
|
||||
; -- sets the next pixel byte to the graphics chip.
|
||||
; for 8 bpp screens this will plot 1 pixel.
|
||||
; for 2 bpp screens it will plot 4 pixels at once (color = bit pattern).
|
||||
%asm {{
|
||||
sta cx16.VERA_DATA0
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub next_pixels(uword pixels @AY, uword amount @R0) clobbers(A, X, Y) {
|
||||
; -- sets the next bunch of pixels from a prepared array of bytes.
|
||||
; for 8 bpp screens this will plot 1 pixel per byte.
|
||||
; for 2 bpp screens it will plot 4 pixels at once (colors are the bit patterns per byte).
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r0+1
|
||||
beq +
|
||||
ldy #0
|
||||
- sta cx16.VERA_DATA0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta cx16.VERA_DATA0
|
||||
iny
|
||||
bne -
|
||||
inc P8ZP_SCRATCH_W1+1 ; next page of 256 pixels
|
||||
dex
|
||||
bne -
|
||||
+ ldy p8v_length ; remaining
|
||||
|
||||
+ ldx cx16.r0 ; remaining pixels
|
||||
beq +
|
||||
- sta cx16.VERA_DATA0
|
||||
dey
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta cx16.VERA_DATA0
|
||||
iny
|
||||
dex
|
||||
bne -
|
||||
+
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub vertical_line(uword xx, ubyte yy, ubyte lheight, ubyte color) {
|
||||
if lheight==0
|
||||
return
|
||||
plot(xx, yy, color) ; set starting position by reusing plot routine
|
||||
; set vera auto-increment to 320 pixel increment (=next line)
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (14<<4)
|
||||
asmsub set_8_pixels_from_bits(ubyte bits @R0, ubyte oncolor @A, ubyte offcolor @Y) clobbers(X) {
|
||||
; this is only useful in 256 color mode where one pixel equals one byte value.
|
||||
%asm {{
|
||||
ldy p8v_lheight
|
||||
lda p8v_color
|
||||
- sta cx16.VERA_DATA0
|
||||
dey
|
||||
ldx #8
|
||||
- asl cx16.r0
|
||||
bcc +
|
||||
sta cx16.VERA_DATA0
|
||||
bra ++
|
||||
+ sty cx16.VERA_DATA0
|
||||
+ dex
|
||||
bne -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
%asm {{
|
||||
; multiplication by 320 lookup table
|
||||
times320 := 320*range(240)
|
||||
@@ -265,4 +899,3 @@ times320_mid .byte >times320
|
||||
times320_hi .byte `times320
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
; Wraps the graphics functions that are in ROM.
|
||||
; Only lo-res 320x240 256 color mode for now.
|
||||
; Unlike graphics module on the C64, you can use colors() to set new drawing colors for every draw operation.
|
||||
; For other resolutions or other color modes, use the "gfx2" or "monogfx" module instead. (which is Cx16-specific)
|
||||
; For other resolutions or other color modes, use the "gfx_lores", "gfx_hires", or "monogfx" module instead.
|
||||
; Note: there is no color palette manipulation here, you have to do that yourself or use the "palette" module.
|
||||
;
|
||||
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
|
||||
@@ -15,7 +15,7 @@
|
||||
graphics {
|
||||
%option ignore_unused
|
||||
|
||||
romsub $feff = FB_cursor_position2() clobbers(A,X,Y) ; alias for the normal FB_cursor_position() call but reuses existing r0 and r1
|
||||
extsub $feff = FB_cursor_position2() clobbers(A,X,Y) ; alias for the normal FB_cursor_position() call but reuses existing r0 and r1
|
||||
|
||||
const uword WIDTH = 320
|
||||
const ubyte HEIGHT = 240
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
; Using the full-screen 640x480 and 320x240 screen modes, in 1 bpp mode (black/white).
|
||||
;
|
||||
; No text layer is currently shown, but text can be drawn as part of the bitmap itself.
|
||||
; For color bitmap graphics, see the gfx2 library.
|
||||
; For color bitmap graphics, see the gfx_lores or gfx_hires libraries.
|
||||
;
|
||||
; NOTE: a lot of the code here is similar or the same to that in gfx2
|
||||
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
|
||||
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
|
||||
|
||||
|
||||
@@ -34,6 +34,22 @@ sprites {
|
||||
cx16.vpoke(1, sprite_reg+7, height_flag<<6 | width_flag<<4 | palette_offset&15) ; 64x64 pixels, palette offset
|
||||
}
|
||||
|
||||
sub reset(ubyte spritenum_start, ubyte count) {
|
||||
; resets all sprite attributes for the given sprite range
|
||||
; this removes these sprites from the screen completely
|
||||
; (without disabling sprites globally so the mouse cursor remains visible)
|
||||
if spritenum_start > 127
|
||||
return
|
||||
if count + spritenum_start > 128
|
||||
return
|
||||
cx16.VERA_CTRL = $00
|
||||
cx16.VERA_ADDR_H = $11
|
||||
cx16.VERA_ADDR = VERA_SPRITEREGS + spritenum_start * $0008
|
||||
repeat count {
|
||||
unroll 8 cx16.VERA_DATA0 = $00
|
||||
}
|
||||
}
|
||||
|
||||
sub data(ubyte spritenum, ubyte bank, uword addr) {
|
||||
addr >>= 5
|
||||
addr |= (bank as uword)<<11
|
||||
|
||||
@@ -44,45 +44,45 @@ cbm {
|
||||
; CLEARSCR -> use txt.clear_screen
|
||||
; HOMECRSR -> use txt.home or txt.plot
|
||||
|
||||
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip, including resetting to the default color palette. Note: also sets the video mode back to VGA
|
||||
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, IRQ, ...)
|
||||
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, screen
|
||||
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||
romsub $FF8D = VECTOR(uword userptr @ XY, bool dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, bool dir @ Pc) -> uword @ XY, ubyte @A ; read/set top of memory pointer. NOTE: on the Cx16 also returns the number of RAM memory banks in A! Also see cx16.numbanks()
|
||||
romsub $FF9C = MEMBOT(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard, also called kbd_scan
|
||||
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
|
||||
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
|
||||
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
|
||||
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
|
||||
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word (use CLEARST to reset it to 0)
|
||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
|
||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
romsub $FFC0 = OPEN() clobbers(X,Y) -> bool @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFD2 = CHROUT(ubyte character @ A) ; (via 806 ($326)) output a character
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) clobbers (X, Y) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device. See also BSAVE
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (in little endian order: A=lo,X=mid,Y=high) , however use RDTIM_safe() instead
|
||||
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A) also see STOP2
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character also see GETIN2
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; get size of text screen into X (columns) and Y (rows)
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Also see txt.plot
|
||||
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
extsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip, including resetting to the default color palette. Note: also sets the video mode back to VGA
|
||||
extsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, IRQ, ...)
|
||||
extsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, screen
|
||||
extsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||
extsub $FF8D = VECTOR(uword userptr @ XY, bool dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||
extsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||
extsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||
extsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||
extsub $FF99 = MEMTOP(uword address @ XY, bool dir @ Pc) -> uword @ XY, ubyte @A ; read/set top of memory pointer. NOTE: on the Cx16 also returns the number of RAM memory banks in A! Also see cx16.numbanks()
|
||||
extsub $FF9C = MEMBOT(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||
extsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard, also called kbd_scan
|
||||
extsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||
extsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
|
||||
extsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
|
||||
extsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
|
||||
extsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
|
||||
extsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||
extsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||
extsub $FFB7 = READST() -> ubyte @ A ; read I/O status word (use CLEARST to reset it to 0)
|
||||
extsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
|
||||
extsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
extsub $FFC0 = OPEN() clobbers(X,Y) -> bool @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||
extsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
extsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; (via 798 ($31E)) define an input channel
|
||||
extsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||
extsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
extsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
extsub $FFD2 = CHROUT(ubyte character @ A) ; (via 806 ($326)) output a character
|
||||
extsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||
extsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) clobbers (X, Y) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device. See also BSAVE
|
||||
extsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
extsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (in little endian order: A=lo,X=mid,Y=high) , however use RDTIM_safe() instead
|
||||
extsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A) also see STOP2
|
||||
extsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character also see GETIN2
|
||||
extsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
extsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
extsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; get size of text screen into X (columns) and Y (rows)
|
||||
extsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Also see txt.plot
|
||||
extsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
|
||||
; ---- utility
|
||||
|
||||
@@ -332,7 +332,7 @@ cx16 {
|
||||
&ubyte VERA_FX_X_POS_S = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_Y_POS_S = VERA_BASE + $000a
|
||||
&ubyte VERA_FX_POLY_FILL_L = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_POLY_FILL_H = VERA_BASE + $000c
|
||||
&ubyte VERA_FX_POLY_FILL_H = VERA_BASE + $000cF
|
||||
&uword VERA_FX_POLY_FILL = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_CACHE_L = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_CACHE_M = VERA_BASE + $000a
|
||||
@@ -394,152 +394,152 @@ cx16 {
|
||||
; ---- Commander X-16 additions on top of C64 kernal routines ----
|
||||
; spelling of the names is taken from the Commander X-16 rom sources
|
||||
|
||||
romsub $ff4a = CLOSE_ALL(ubyte device @A) clobbers(A,X,Y)
|
||||
romsub $ff59 = LKUPLA(ubyte la @A) clobbers(A,X,Y)
|
||||
romsub $ff5c = LKUPSA(ubyte sa @Y) clobbers(A,X,Y)
|
||||
romsub $ff5f = screen_mode(ubyte mode @A, bool getCurrent @Pc) -> ubyte @A, ubyte @X, ubyte @Y, bool @Pc ; also see SCREEN or get_screen_mode()
|
||||
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y)
|
||||
romsub $ff6e = JSRFAR() ; following word = address to call, byte after that=rom/ram bank it is in
|
||||
romsub $ff74 = fetch(ubyte zp_startaddr @A, ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
|
||||
romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X) ; note: The the zero page address containing the base address is passed in stavec ($03B2)
|
||||
romsub $ff7d = PRIMM()
|
||||
extsub $ff4a = CLOSE_ALL(ubyte device @A) clobbers(A,X,Y)
|
||||
extsub $ff59 = LKUPLA(ubyte la @A) clobbers(A,X,Y)
|
||||
extsub $ff5c = LKUPSA(ubyte sa @Y) clobbers(A,X,Y)
|
||||
extsub $ff5f = screen_mode(ubyte mode @A, bool getCurrent @Pc) -> ubyte @A, ubyte @X, ubyte @Y, bool @Pc ; also see SCREEN or get_screen_mode()
|
||||
extsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y)
|
||||
extsub $ff6e = JSRFAR() ; following word = address to call, byte after that=rom/ram bank it is in
|
||||
extsub $ff74 = fetch(ubyte zp_startaddr @A, ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
|
||||
extsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X) ; note: The the zero page address containing the base address is passed in stavec ($03B2)
|
||||
extsub $ff7d = PRIMM()
|
||||
|
||||
; high level graphics & fonts
|
||||
romsub $ff20 = GRAPH_init(uword vectors @R0) clobbers(A,X,Y)
|
||||
romsub $ff23 = GRAPH_clear() clobbers(A,X,Y)
|
||||
romsub $ff26 = GRAPH_set_window(uword x @R0, uword y @R1, uword width @R2, uword height @R3) clobbers(A,X,Y)
|
||||
romsub $ff29 = GRAPH_set_colors(ubyte stroke @A, ubyte fill @X, ubyte background @Y) clobbers (A,X,Y)
|
||||
romsub $ff2c = GRAPH_draw_line(uword x1 @R0, uword y1 @R1, uword x2 @R2, uword y2 @R3) clobbers(A,X,Y)
|
||||
romsub $ff2f = GRAPH_draw_rect(uword x @R0, uword y @R1, uword width @R2, uword height @R3, uword cornerradius @R4, bool fill @Pc) clobbers(A,X,Y)
|
||||
romsub $ff32 = GRAPH_move_rect(uword sx @R0, uword sy @R1, uword tx @R2, uword ty @R3, uword width @R4, uword height @R5) clobbers(A,X,Y)
|
||||
romsub $ff35 = GRAPH_draw_oval(uword x @R0, uword y @R1, uword width @R2, uword height @R3, bool fill @Pc) clobbers(A,X,Y)
|
||||
romsub $ff38 = GRAPH_draw_image(uword x @R0, uword y @R1, uword ptr @R2, uword width @R3, uword height @R4) clobbers(A,X,Y)
|
||||
romsub $ff3b = GRAPH_set_font(uword fontptr @R0) clobbers(A,X,Y)
|
||||
romsub $ff3e = GRAPH_get_char_size(ubyte baseline @A, ubyte width @X, ubyte height_or_style @Y, bool is_control @Pc) clobbers(A,X,Y)
|
||||
romsub $ff41 = GRAPH_put_char(uword x @R0, uword y @R1, ubyte character @A) clobbers(A,X,Y)
|
||||
romsub $ff41 = GRAPH_put_next_char(ubyte character @A) clobbers(A,X,Y) ; alias for the routine above that doesn't reset the position of the initial character
|
||||
extsub $ff20 = GRAPH_init(uword vectors @R0) clobbers(A,X,Y)
|
||||
extsub $ff23 = GRAPH_clear() clobbers(A,X,Y)
|
||||
extsub $ff26 = GRAPH_set_window(uword x @R0, uword y @R1, uword width @R2, uword height @R3) clobbers(A,X,Y)
|
||||
extsub $ff29 = GRAPH_set_colors(ubyte stroke @A, ubyte fill @X, ubyte background @Y) clobbers (A,X,Y)
|
||||
extsub $ff2c = GRAPH_draw_line(uword x1 @R0, uword y1 @R1, uword x2 @R2, uword y2 @R3) clobbers(A,X,Y)
|
||||
extsub $ff2f = GRAPH_draw_rect(uword x @R0, uword y @R1, uword width @R2, uword height @R3, uword cornerradius @R4, bool fill @Pc) clobbers(A,X,Y)
|
||||
extsub $ff32 = GRAPH_move_rect(uword sx @R0, uword sy @R1, uword tx @R2, uword ty @R3, uword width @R4, uword height @R5) clobbers(A,X,Y)
|
||||
extsub $ff35 = GRAPH_draw_oval(uword x @R0, uword y @R1, uword width @R2, uword height @R3, bool fill @Pc) clobbers(A,X,Y)
|
||||
extsub $ff38 = GRAPH_draw_image(uword x @R0, uword y @R1, uword ptr @R2, uword width @R3, uword height @R4) clobbers(A,X,Y)
|
||||
extsub $ff3b = GRAPH_set_font(uword fontptr @R0) clobbers(A,X,Y)
|
||||
extsub $ff3e = GRAPH_get_char_size(ubyte baseline @A, ubyte width @X, ubyte height_or_style @Y, bool is_control @Pc) clobbers(A,X,Y)
|
||||
extsub $ff41 = GRAPH_put_char(uword x @R0, uword y @R1, ubyte character @A) clobbers(A,X,Y)
|
||||
extsub $ff41 = GRAPH_put_next_char(ubyte character @A) clobbers(A,X,Y) ; alias for the routine above that doesn't reset the position of the initial character
|
||||
|
||||
; framebuffer
|
||||
romsub $fef6 = FB_init() clobbers(A,X,Y)
|
||||
romsub $fef9 = FB_get_info() clobbers(X,Y) -> byte @A, uword @R0, uword @R1 ; width=r0, height=r1
|
||||
romsub $fefc = FB_set_palette(uword pointer @R0, ubyte index @A, ubyte colorcount @X) clobbers(A,X,Y)
|
||||
romsub $feff = FB_cursor_position(uword x @R0, uword y @R1) clobbers(A,X,Y)
|
||||
romsub $ff02 = FB_cursor_next_line(uword x @R0) clobbers(A,X,Y)
|
||||
romsub $ff05 = FB_get_pixel() clobbers(X,Y) -> ubyte @A
|
||||
romsub $ff08 = FB_get_pixels(uword pointer @R0, uword count @R1) clobbers(A,X,Y)
|
||||
romsub $ff0b = FB_set_pixel(ubyte color @A) clobbers(A,X,Y)
|
||||
romsub $ff0e = FB_set_pixels(uword pointer @R0, uword count @R1) clobbers(A,X,Y)
|
||||
romsub $ff11 = FB_set_8_pixels(ubyte pattern @A, ubyte color @X) clobbers(A,X,Y)
|
||||
romsub $ff14 = FB_set_8_pixels_opaque(ubyte pattern @R0, ubyte mask @A, ubyte color1 @X, ubyte color2 @Y) clobbers(A,X,Y)
|
||||
romsub $ff17 = FB_fill_pixels(uword count @R0, uword pstep @R1, ubyte color @A) clobbers(A,X,Y)
|
||||
romsub $ff1a = FB_filter_pixels(uword pointer @ R0, uword count @R1) clobbers(A,X,Y)
|
||||
romsub $ff1d = FB_move_pixels(uword sx @R0, uword sy @R1, uword tx @R2, uword ty @R3, uword count @R4) clobbers(A,X,Y)
|
||||
extsub $fef6 = FB_init() clobbers(A,X,Y)
|
||||
extsub $fef9 = FB_get_info() clobbers(X,Y) -> byte @A, uword @R0, uword @R1 ; width=r0, height=r1
|
||||
extsub $fefc = FB_set_palette(uword pointer @R0, ubyte index @A, ubyte colorcount @X) clobbers(A,X,Y)
|
||||
extsub $feff = FB_cursor_position(uword x @R0, uword y @R1) clobbers(A,X,Y)
|
||||
extsub $ff02 = FB_cursor_next_line(uword x @R0) clobbers(A,X,Y)
|
||||
extsub $ff05 = FB_get_pixel() clobbers(X,Y) -> ubyte @A
|
||||
extsub $ff08 = FB_get_pixels(uword pointer @R0, uword count @R1) clobbers(A,X,Y)
|
||||
extsub $ff0b = FB_set_pixel(ubyte color @A) clobbers(A,X,Y)
|
||||
extsub $ff0e = FB_set_pixels(uword pointer @R0, uword count @R1) clobbers(A,X,Y)
|
||||
extsub $ff11 = FB_set_8_pixels(ubyte pattern @A, ubyte color @X) clobbers(A,X,Y)
|
||||
extsub $ff14 = FB_set_8_pixels_opaque(ubyte pattern @R0, ubyte mask @A, ubyte color1 @X, ubyte color2 @Y) clobbers(A,X,Y)
|
||||
extsub $ff17 = FB_fill_pixels(uword count @R0, uword pstep @R1, ubyte color @A) clobbers(A,X,Y)
|
||||
extsub $ff1a = FB_filter_pixels(uword pointer @ R0, uword count @R1) clobbers(A,X,Y)
|
||||
extsub $ff1d = FB_move_pixels(uword sx @R0, uword sy @R1, uword tx @R2, uword ty @R3, uword count @R4) clobbers(A,X,Y)
|
||||
|
||||
; misc
|
||||
romsub $fec6 = i2c_read_byte(ubyte device @X, ubyte offset @Y) clobbers (X,Y) -> ubyte @A, bool @Pc
|
||||
romsub $fec9 = i2c_write_byte(ubyte device @X, ubyte offset @Y, ubyte data @A) clobbers (A,X,Y) -> bool @Pc
|
||||
romsub $feb4 = i2c_batch_read(ubyte device @X, uword buffer @R0, uword length @R1, bool advance @Pc) clobbers(A,Y) -> bool @Pc
|
||||
romsub $feb7 = i2c_batch_write(ubyte device @X, uword buffer @R0, uword length @R1, bool advance @Pc) clobbers(A,Y) -> bool @Pc
|
||||
extsub $fec6 = i2c_read_byte(ubyte device @X, ubyte offset @Y) clobbers (X,Y) -> ubyte @A, bool @Pc
|
||||
extsub $fec9 = i2c_write_byte(ubyte device @X, ubyte offset @Y, ubyte data @A) clobbers (A,X,Y) -> bool @Pc
|
||||
extsub $feb4 = i2c_batch_read(ubyte device @X, uword buffer @R0, uword length @R1, bool advance @Pc) clobbers(A,Y) -> bool @Pc
|
||||
extsub $feb7 = i2c_batch_write(ubyte device @X, uword buffer @R0, uword length @R1, bool advance @Pc) clobbers(A,Y) -> bool @Pc
|
||||
|
||||
romsub $fef0 = sprite_set_image(uword pixels @R0, uword mask @R1, ubyte bpp @R2, ubyte number @A, ubyte width @X, ubyte height @Y, bool apply_mask @Pc) clobbers(A,X,Y) -> bool @Pc
|
||||
romsub $fef3 = sprite_set_position(uword x @R0, uword y @R1, ubyte number @A) clobbers(A,X,Y)
|
||||
romsub $fee4 = memory_fill(uword address @R0, uword num_bytes @R1, ubyte value @A) clobbers(A,X,Y)
|
||||
romsub $fee7 = memory_copy(uword source @R0, uword target @R1, uword num_bytes @R2) clobbers(A,X,Y)
|
||||
romsub $feea = memory_crc(uword address @R0, uword num_bytes @R1) clobbers(A,X,Y) -> uword @R2
|
||||
romsub $feed = memory_decompress(uword input @R0, uword output @R1) clobbers(A,X,Y) -> uword @R1 ; last address +1 is result in R1
|
||||
romsub $fedb = console_init(uword x @R0, uword y @R1, uword width @R2, uword height @R3) clobbers(A,X,Y)
|
||||
romsub $fede = console_put_char(ubyte character @A, bool wrapping @Pc) clobbers(A,X,Y)
|
||||
romsub $fee1 = console_get_char() clobbers(X,Y) -> ubyte @A
|
||||
romsub $fed8 = console_put_image(uword pointer @R0, uword width @R1, uword height @R2) clobbers(A,X,Y)
|
||||
romsub $fed5 = console_set_paging_message(uword msgptr @R0) clobbers(A,X,Y)
|
||||
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
|
||||
;; romsub $fea8 = extapi16(ubyte callnumber @A) clobbers (A,X,Y) ; not useful yet because is for 65816 cpu
|
||||
romsub $feab = extapi(ubyte callnumber @A) clobbers (A,X,Y)
|
||||
romsub $fecc = monitor() clobbers(A,X,Y)
|
||||
extsub $fef0 = sprite_set_image(uword pixels @R0, uword mask @R1, ubyte bpp @R2, ubyte number @A, ubyte width @X, ubyte height @Y, bool apply_mask @Pc) clobbers(A,X,Y) -> bool @Pc
|
||||
extsub $fef3 = sprite_set_position(uword x @R0, uword y @R1, ubyte number @A) clobbers(A,X,Y)
|
||||
extsub $fee4 = memory_fill(uword address @R0, uword num_bytes @R1, ubyte value @A) clobbers(A,X,Y)
|
||||
extsub $fee7 = memory_copy(uword source @R0, uword target @R1, uword num_bytes @R2) clobbers(A,X,Y)
|
||||
extsub $feea = memory_crc(uword address @R0, uword num_bytes @R1) clobbers(A,X,Y) -> uword @R2
|
||||
extsub $feed = memory_decompress(uword input @R0, uword output @R1) clobbers(A,X,Y) -> uword @R1 ; last address +1 is result in R1
|
||||
extsub $fedb = console_init(uword x @R0, uword y @R1, uword width @R2, uword height @R3) clobbers(A,X,Y)
|
||||
extsub $fede = console_put_char(ubyte character @A, bool wrapping @Pc) clobbers(A,X,Y)
|
||||
extsub $fee1 = console_get_char() clobbers(X,Y) -> ubyte @A
|
||||
extsub $fed8 = console_put_image(uword pointer @R0, uword width @R1, uword height @R2) clobbers(A,X,Y)
|
||||
extsub $fed5 = console_set_paging_message(uword msgptr @R0) clobbers(A,X,Y)
|
||||
extsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
|
||||
;; extsub $fea8 = extapi16(ubyte callnumber @A) clobbers (A,X,Y) ; not useful yet because is for 65816 cpu
|
||||
extsub $feab = extapi(ubyte callnumber @A) clobbers (A,X,Y)
|
||||
extsub $fecc = monitor() clobbers(A,X,Y)
|
||||
|
||||
romsub $ff44 = MACPTR(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
|
||||
romsub $feb1 = MCIOUT(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
|
||||
romsub $FEBA = BSAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) clobbers (X, Y) -> bool @ Pc, ubyte @ A ; like cbm.SAVE, but omits the 2-byte prg header
|
||||
romsub $ff47 = enter_basic(bool cold_or_warm @Pc) clobbers(A,X,Y)
|
||||
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, uword jiffiesweekday @R3) clobbers(A, X, Y)
|
||||
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, uword @R3 ; result registers see clock_set_date_time()
|
||||
extsub $ff44 = MACPTR(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
|
||||
extsub $feb1 = MCIOUT(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
|
||||
extsub $FEBA = BSAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) clobbers (X, Y) -> bool @ Pc, ubyte @ A ; like cbm.SAVE, but omits the 2-byte prg header
|
||||
extsub $ff47 = enter_basic(bool cold_or_warm @Pc) clobbers(A,X,Y)
|
||||
extsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, uword jiffiesweekday @R3) clobbers(A, X, Y)
|
||||
extsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, uword @R3 ; result registers see clock_set_date_time()
|
||||
|
||||
; keyboard, mouse, joystick
|
||||
; note: also see the cbm.kbdbuf_clear() helper routine
|
||||
romsub $febd = kbdbuf_peek() -> ubyte @A, ubyte @X ; key in A, queue length in X
|
||||
romsub $fec0 = kbdbuf_get_modifiers() -> ubyte @A
|
||||
romsub $fec3 = kbdbuf_put(ubyte key @A) clobbers(X)
|
||||
romsub $fed2 = keymap(uword identifier @XY, bool read @Pc) -> bool @Pc
|
||||
romsub $ff68 = mouse_config(byte shape @A, ubyte resX @X, ubyte resY @Y) clobbers (A, X, Y)
|
||||
romsub $ff6b = mouse_get(ubyte zdataptr @X) -> ubyte @A, byte @X ; use mouse_pos() instead
|
||||
romsub $ff71 = mouse_scan() clobbers(A, X, Y)
|
||||
romsub $ff53 = joystick_scan() clobbers(A, X, Y)
|
||||
romsub $ff56 = joystick_get(ubyte joynr @A) -> uword @AX, bool @Y ; note: everything is inverted
|
||||
extsub $febd = kbdbuf_peek() -> ubyte @A, ubyte @X ; key in A, queue length in X
|
||||
extsub $fec0 = kbdbuf_get_modifiers() -> ubyte @A
|
||||
extsub $fec3 = kbdbuf_put(ubyte key @A) clobbers(X)
|
||||
extsub $fed2 = keymap(uword identifier @XY, bool read @Pc) -> bool @Pc
|
||||
extsub $ff68 = mouse_config(byte shape @A, ubyte resX @X, ubyte resY @Y) clobbers (A, X, Y)
|
||||
extsub $ff6b = mouse_get(ubyte zdataptr @X) -> ubyte @A, byte @X ; use mouse_pos() instead
|
||||
extsub $ff71 = mouse_scan() clobbers(A, X, Y)
|
||||
extsub $ff53 = joystick_scan() clobbers(A, X, Y)
|
||||
extsub $ff56 = joystick_get(ubyte joynr @A) -> uword @AX, bool @Y ; note: everything is inverted
|
||||
|
||||
; X16Edit (rom bank 13/14 but you ideally should use the routine search_x16edit() to search for the correct bank)
|
||||
romsub $C000 = x16edit_default() clobbers(A,X,Y)
|
||||
romsub $C003 = x16edit_loadfile(ubyte firstbank @X, ubyte lastbank @Y, str filename @R0, ubyte filenameLength @R1) clobbers(A,X,Y)
|
||||
romsub $C006 = x16edit_loadfile_options(ubyte firstbank @X, ubyte lastbank @Y, str filename @R0,
|
||||
extsub $C000 = x16edit_default() clobbers(A,X,Y)
|
||||
extsub $C003 = x16edit_loadfile(ubyte firstbank @X, ubyte lastbank @Y, str filename @R0, ubyte filenameLength @R1) clobbers(A,X,Y)
|
||||
extsub $C006 = x16edit_loadfile_options(ubyte firstbank @X, ubyte lastbank @Y, str filename @R0,
|
||||
uword filenameLengthAndOptions @R1, uword tabstopAndWordwrap @R2,
|
||||
uword disknumberAndColors @R3, uword headerAndStatusColors @R4) clobbers(A,X,Y)
|
||||
|
||||
; Audio (rom bank 10 - you have to activate this bank manually first! Or use the stubs in the audio module. Or use callfar/JSRFAR manually.)
|
||||
romsub $C09F = audio_init() clobbers(A,X,Y) -> bool @Pc ; (re)initialize both vera PSG and YM audio chips
|
||||
romsub $C000 = bas_fmfreq(ubyte channel @A, uword freq @XY, bool noretrigger @Pc) clobbers(A,X,Y) -> bool @Pc
|
||||
romsub $C003 = bas_fmnote(ubyte channel @A, ubyte note @X, ubyte fracsemitone @Y, bool noretrigger @Pc) clobbers(A,X,Y) -> bool @Pc
|
||||
romsub $C006 = bas_fmplaystring(ubyte length @A, str string @XY) clobbers(A,X,Y)
|
||||
romsub $C009 = bas_fmvib(ubyte speed @A, ubyte depth @X) clobbers(A,X,Y) -> bool @Pc
|
||||
romsub $C00C = bas_playstringvoice(ubyte channel @A) clobbers(Y)
|
||||
romsub $C00F = bas_psgfreq(ubyte voice @A, uword freq @XY) clobbers(A,X,Y) -> bool @Pc
|
||||
romsub $C012 = bas_psgnote(ubyte voice @A, ubyte note @X, ubyte fracsemitone @Y) clobbers(A,X,Y) -> bool @Pc
|
||||
romsub $C015 = bas_psgwav(ubyte voice @A, ubyte waveform @X) clobbers(A,X,Y) -> bool @Pc
|
||||
romsub $C018 = bas_psgplaystring(ubyte length @A, str string @XY) clobbers(A,X,Y)
|
||||
romsub $C08D = bas_fmchordstring(ubyte length @A, str string @XY) clobbers(A,X,Y)
|
||||
romsub $C090 = bas_psgchordstring(ubyte length @A, str string @XY) clobbers(A,X,Y)
|
||||
romsub $C01B = notecon_bas2fm(ubyte note @X) clobbers(A) -> ubyte @X, bool @Pc
|
||||
romsub $C01E = notecon_bas2midi(ubyte note @X) clobbers(A) -> ubyte @X, bool @Pc
|
||||
romsub $C021 = notecon_bas2psg(ubyte note @X, ubyte fracsemitone @Y) clobbers(A) -> uword @XY, bool @Pc
|
||||
romsub $C024 = notecon_fm2bas(ubyte note @X) clobbers(A) -> ubyte @X, bool @Pc
|
||||
romsub $C027 = notecon_fm2midi(ubyte note @X) clobbers(A) -> ubyte @X, bool @Pc
|
||||
romsub $C02A = notecon_fm2psg(ubyte note @X, ubyte fracsemitone @Y) clobbers(A) -> uword @XY, bool @Pc
|
||||
romsub $C02D = notecon_freq2bas(uword freqHz @XY) clobbers(A) -> ubyte @X, ubyte @Y, bool @Pc
|
||||
romsub $C030 = notecon_freq2fm(uword freqHz @XY) clobbers(A) -> ubyte @X, ubyte @Y, bool @Pc
|
||||
romsub $C033 = notecon_freq2midi(uword freqHz @XY) clobbers(A) -> ubyte @X, ubyte @Y, bool @Pc
|
||||
romsub $C036 = notecon_freq2psg(uword freqHz @XY) clobbers(A) -> uword @XY, bool @Pc
|
||||
romsub $C039 = notecon_midi2bas(ubyte note @X) clobbers(A) -> ubyte @X, bool @Pc
|
||||
romsub $C03C = notecon_midi2fm(ubyte note @X) clobbers(A) -> ubyte @X, bool @Pc
|
||||
romsub $C03F = notecon_midi2psg(ubyte note @X, ubyte fracsemitone @Y) clobbers(A) -> uword @XY, bool @Pc
|
||||
romsub $C042 = notecon_psg2bas(uword freq @XY) clobbers(A) -> ubyte @X, ubyte @Y, bool @Pc
|
||||
romsub $C045 = notecon_psg2fm(uword freq @XY) clobbers(A) -> ubyte @X, ubyte @Y, bool @Pc
|
||||
romsub $C048 = notecon_psg2midi(uword freq @XY) clobbers(A) -> ubyte @X, ubyte @Y, bool @Pc
|
||||
romsub $C04B = psg_init() clobbers(A,X,Y) ; (re)init Vera PSG
|
||||
romsub $C04E = psg_playfreq(ubyte voice @A, uword freq @XY) clobbers(A,X,Y)
|
||||
romsub $C051 = psg_read(ubyte offset @X, bool cookedVol @Pc) clobbers(Y) -> ubyte @A
|
||||
romsub $C054 = psg_setatten(ubyte voice @A, ubyte attenuation @X) clobbers(A,X,Y)
|
||||
romsub $C057 = psg_setfreq(ubyte voice @A, uword freq @XY) clobbers(A,X,Y)
|
||||
romsub $C05A = psg_setpan(ubyte voice @A, ubyte panning @X) clobbers(A,X,Y)
|
||||
romsub $C05D = psg_setvol(ubyte voice @A, ubyte volume @X) clobbers(A,X,Y)
|
||||
romsub $C060 = psg_write(ubyte value @A, ubyte offset @X) clobbers(Y)
|
||||
romsub $C0A2 = psg_write_fast(ubyte value @A, ubyte offset @X) clobbers(Y)
|
||||
romsub $C093 = psg_getatten(ubyte voice @A) clobbers(Y) -> ubyte @X
|
||||
romsub $C096 = psg_getpan(ubyte voice @A) clobbers(Y) -> ubyte @X
|
||||
romsub $C063 = ym_init() clobbers(A,X,Y) -> bool @Pc ; (re)init YM chip
|
||||
romsub $C066 = ym_loaddefpatches() clobbers(A,X,Y) -> bool @Pc ; load default YM patches
|
||||
romsub $C069 = ym_loadpatch(ubyte channel @A, uword patchOrAddress @XY, bool what @Pc) clobbers(A,X,Y)
|
||||
romsub $C06C = ym_loadpatchlfn(ubyte channel @A, ubyte lfn @X) clobbers(X,Y) -> ubyte @A, bool @Pc
|
||||
romsub $C06F = ym_playdrum(ubyte channel @A, ubyte note @X) clobbers(A,X,Y) -> bool @Pc
|
||||
romsub $C072 = ym_playnote(ubyte channel @A, ubyte kc @X, ubyte kf @Y, bool notrigger @Pc) clobbers(A,X,Y) -> bool @Pc
|
||||
romsub $C075 = ym_setatten(ubyte channel @A, ubyte attenuation @X) clobbers(Y) -> bool @Pc
|
||||
romsub $C078 = ym_setdrum(ubyte channel @A, ubyte note @X) clobbers(A,X,Y) -> bool @Pc
|
||||
romsub $C07B = ym_setnote(ubyte channel @A, ubyte kc @X, ubyte kf @Y) clobbers(A,X,Y) -> bool @Pc
|
||||
romsub $C07E = ym_setpan(ubyte channel @A, ubyte panning @X) clobbers(A,X,Y) -> bool @Pc
|
||||
romsub $C081 = ym_read(ubyte register @X, bool cooked @Pc) clobbers(Y) -> ubyte @A, bool @Pc
|
||||
romsub $C084 = ym_release(ubyte channel @A) clobbers(A,X,Y) -> bool @Pc
|
||||
romsub $C087 = ym_trigger(ubyte channel @A, bool noRelease @Pc) clobbers(A,X,Y) -> bool @Pc
|
||||
romsub $C08A = ym_write(ubyte value @A, ubyte register @X) clobbers(Y) -> bool @Pc
|
||||
romsub $C099 = ym_getatten(ubyte channel @A) clobbers(Y) -> ubyte @X
|
||||
romsub $C09C = ym_getpan(ubyte channel @A) clobbers(Y) -> ubyte @X
|
||||
romsub $C0A5 = ym_get_chip_type() clobbers(X) -> ubyte @A
|
||||
; Audio (rom bank 10)
|
||||
extsub @bank 10 $C09F = audio_init() clobbers(A,X,Y) -> bool @Pc ; (re)initialize both vera PSG and YM audio chips
|
||||
extsub @bank 10 $C000 = bas_fmfreq(ubyte channel @A, uword freq @XY, bool noretrigger @Pc) clobbers(A,X,Y) -> bool @Pc
|
||||
extsub @bank 10 $C003 = bas_fmnote(ubyte channel @A, ubyte note @X, ubyte fracsemitone @Y, bool noretrigger @Pc) clobbers(A,X,Y) -> bool @Pc
|
||||
extsub @bank 10 $C006 = bas_fmplaystring(ubyte length @A, str string @XY) clobbers(A,X,Y)
|
||||
extsub @bank 10 $C009 = bas_fmvib(ubyte speed @A, ubyte depth @X) clobbers(A,X,Y) -> bool @Pc
|
||||
extsub @bank 10 $C00C = bas_playstringvoice(ubyte channel @A) clobbers(Y)
|
||||
extsub @bank 10 $C00F = bas_psgfreq(ubyte voice @A, uword freq @XY) clobbers(A,X,Y) -> bool @Pc
|
||||
extsub @bank 10 $C012 = bas_psgnote(ubyte voice @A, ubyte note @X, ubyte fracsemitone @Y) clobbers(A,X,Y) -> bool @Pc
|
||||
extsub @bank 10 $C015 = bas_psgwav(ubyte voice @A, ubyte waveform @X) clobbers(A,X,Y) -> bool @Pc
|
||||
extsub @bank 10 $C018 = bas_psgplaystring(ubyte length @A, str string @XY) clobbers(A,X,Y)
|
||||
extsub @bank 10 $C08D = bas_fmchordstring(ubyte length @A, str string @XY) clobbers(A,X,Y)
|
||||
extsub @bank 10 $C090 = bas_psgchordstring(ubyte length @A, str string @XY) clobbers(A,X,Y)
|
||||
extsub @bank 10 $C01B = notecon_bas2fm(ubyte note @X) clobbers(A) -> ubyte @X, bool @Pc
|
||||
extsub @bank 10 $C01E = notecon_bas2midi(ubyte note @X) clobbers(A) -> ubyte @X, bool @Pc
|
||||
extsub @bank 10 $C021 = notecon_bas2psg(ubyte note @X, ubyte fracsemitone @Y) clobbers(A) -> uword @XY, bool @Pc
|
||||
extsub @bank 10 $C024 = notecon_fm2bas(ubyte note @X) clobbers(A) -> ubyte @X, bool @Pc
|
||||
extsub @bank 10 $C027 = notecon_fm2midi(ubyte note @X) clobbers(A) -> ubyte @X, bool @Pc
|
||||
extsub @bank 10 $C02A = notecon_fm2psg(ubyte note @X, ubyte fracsemitone @Y) clobbers(A) -> uword @XY, bool @Pc
|
||||
extsub @bank 10 $C02D = notecon_freq2bas(uword freqHz @XY) clobbers(A) -> ubyte @X, ubyte @Y, bool @Pc
|
||||
extsub @bank 10 $C030 = notecon_freq2fm(uword freqHz @XY) clobbers(A) -> ubyte @X, ubyte @Y, bool @Pc
|
||||
extsub @bank 10 $C033 = notecon_freq2midi(uword freqHz @XY) clobbers(A) -> ubyte @X, ubyte @Y, bool @Pc
|
||||
extsub @bank 10 $C036 = notecon_freq2psg(uword freqHz @XY) clobbers(A) -> uword @XY, bool @Pc
|
||||
extsub @bank 10 $C039 = notecon_midi2bas(ubyte note @X) clobbers(A) -> ubyte @X, bool @Pc
|
||||
extsub @bank 10 $C03C = notecon_midi2fm(ubyte note @X) clobbers(A) -> ubyte @X, bool @Pc
|
||||
extsub @bank 10 $C03F = notecon_midi2psg(ubyte note @X, ubyte fracsemitone @Y) clobbers(A) -> uword @XY, bool @Pc
|
||||
extsub @bank 10 $C042 = notecon_psg2bas(uword freq @XY) clobbers(A) -> ubyte @X, ubyte @Y, bool @Pc
|
||||
extsub @bank 10 $C045 = notecon_psg2fm(uword freq @XY) clobbers(A) -> ubyte @X, ubyte @Y, bool @Pc
|
||||
extsub @bank 10 $C048 = notecon_psg2midi(uword freq @XY) clobbers(A) -> ubyte @X, ubyte @Y, bool @Pc
|
||||
extsub @bank 10 $C04B = psg_init() clobbers(A,X,Y) ; (re)init Vera PSG
|
||||
extsub @bank 10 $C04E = psg_playfreq(ubyte voice @A, uword freq @XY) clobbers(A,X,Y)
|
||||
extsub @bank 10 $C051 = psg_read(ubyte offset @X, bool cookedVol @Pc) clobbers(Y) -> ubyte @A
|
||||
extsub @bank 10 $C054 = psg_setatten(ubyte voice @A, ubyte attenuation @X) clobbers(A,X,Y)
|
||||
extsub @bank 10 $C057 = psg_setfreq(ubyte voice @A, uword freq @XY) clobbers(A,X,Y)
|
||||
extsub @bank 10 $C05A = psg_setpan(ubyte voice @A, ubyte panning @X) clobbers(A,X,Y)
|
||||
extsub @bank 10 $C05D = psg_setvol(ubyte voice @A, ubyte volume @X) clobbers(A,X,Y)
|
||||
extsub @bank 10 $C060 = psg_write(ubyte value @A, ubyte offset @X) clobbers(Y)
|
||||
extsub @bank 10 $C0A2 = psg_write_fast(ubyte value @A, ubyte offset @X) clobbers(Y)
|
||||
extsub @bank 10 $C093 = psg_getatten(ubyte voice @A) clobbers(Y) -> ubyte @X
|
||||
extsub @bank 10 $C096 = psg_getpan(ubyte voice @A) clobbers(Y) -> ubyte @X
|
||||
extsub @bank 10 $C063 = ym_init() clobbers(A,X,Y) -> bool @Pc ; (re)init YM chip
|
||||
extsub @bank 10 $C066 = ym_loaddefpatches() clobbers(A,X,Y) -> bool @Pc ; load default YM patches
|
||||
extsub @bank 10 $C069 = ym_loadpatch(ubyte channel @A, uword patchOrAddress @XY, bool what @Pc) clobbers(A,X,Y)
|
||||
extsub @bank 10 $C06C = ym_loadpatchlfn(ubyte channel @A, ubyte lfn @X) clobbers(X,Y) -> ubyte @A, bool @Pc
|
||||
extsub @bank 10 $C06F = ym_playdrum(ubyte channel @A, ubyte note @X) clobbers(A,X,Y) -> bool @Pc
|
||||
extsub @bank 10 $C072 = ym_playnote(ubyte channel @A, ubyte kc @X, ubyte kf @Y, bool notrigger @Pc) clobbers(A,X,Y) -> bool @Pc
|
||||
extsub @bank 10 $C075 = ym_setatten(ubyte channel @A, ubyte attenuation @X) clobbers(Y) -> bool @Pc
|
||||
extsub @bank 10 $C078 = ym_setdrum(ubyte channel @A, ubyte note @X) clobbers(A,X,Y) -> bool @Pc
|
||||
extsub @bank 10 $C07B = ym_setnote(ubyte channel @A, ubyte kc @X, ubyte kf @Y) clobbers(A,X,Y) -> bool @Pc
|
||||
extsub @bank 10 $C07E = ym_setpan(ubyte channel @A, ubyte panning @X) clobbers(A,X,Y) -> bool @Pc
|
||||
extsub @bank 10 $C081 = ym_read(ubyte register @X, bool cooked @Pc) clobbers(Y) -> ubyte @A, bool @Pc
|
||||
extsub @bank 10 $C084 = ym_release(ubyte channel @A) clobbers(A,X,Y) -> bool @Pc
|
||||
extsub @bank 10 $C087 = ym_trigger(ubyte channel @A, bool noRelease @Pc) clobbers(A,X,Y) -> bool @Pc
|
||||
extsub @bank 10 $C08A = ym_write(ubyte value @A, ubyte register @X) clobbers(Y) -> bool @Pc
|
||||
extsub @bank 10 $C099 = ym_getatten(ubyte channel @A) clobbers(Y) -> ubyte @X
|
||||
extsub @bank 10 $C09C = ym_getpan(ubyte channel @A) clobbers(Y) -> ubyte @X
|
||||
extsub @bank 10 $C0A5 = ym_get_chip_type() clobbers(X) -> ubyte @A
|
||||
|
||||
; extapi call numbers
|
||||
const ubyte EXTAPI_clear_status = $01
|
||||
@@ -734,9 +734,11 @@ asmsub vpeek(ubyte bank @A, uword address @XY) -> ubyte @A {
|
||||
}
|
||||
|
||||
asmsub vaddr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, byte autoIncrOrDecrByOne @Y) clobbers(A) {
|
||||
; -- setup the VERA's data address register 0 or 1
|
||||
; with optional auto increment or decrement of 1.
|
||||
; -- setup the VERA's data address register 0 or 1 with optional auto increment or decrement of 1.
|
||||
; This is a convenience routine, and not very efficient if you call it often;
|
||||
; it's usually better to write a tailor made version of it that accounts for the repeated values.
|
||||
; Note that the vaddr_autoincr() and vaddr_autodecr() routines allow to set all possible strides, not just 1.
|
||||
; Note also that Vera's addrset is reset to 0 on exit, even if you set port #1's address.
|
||||
%asm {{
|
||||
pha
|
||||
lda cx16.r1
|
||||
@@ -752,15 +754,19 @@ asmsub vaddr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, byte autoIncrO
|
||||
beq +
|
||||
ora #%00010000
|
||||
+ sta cx16.VERA_ADDR_H
|
||||
stz cx16.VERA_CTRL
|
||||
rts
|
||||
+ ora #%00011000
|
||||
sta cx16.VERA_ADDR_H
|
||||
stz cx16.VERA_CTRL
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vaddr_clone(ubyte port @A) clobbers (A,X,Y) {
|
||||
; -- clones Vera addresses from the given source port to the other one.
|
||||
; This is a convenience routine, and not very efficient if you call it often;
|
||||
; it's usually better to write a tailor made version of it that accounts for the repeated values.
|
||||
%asm {{
|
||||
sta VERA_CTRL
|
||||
ldx VERA_ADDR_L
|
||||
@@ -774,15 +780,16 @@ asmsub vaddr_clone(ubyte port @A) clobbers (A,X,Y) {
|
||||
ply
|
||||
sty VERA_ADDR_H
|
||||
eor #1
|
||||
sta VERA_CTRL
|
||||
stz VERA_CTRL
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vaddr_autoincr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, uword autoIncrAmount @R2) clobbers(A,Y) {
|
||||
; -- setup the VERA's data address register 0 or 1
|
||||
; including setting up optional auto increment amount.
|
||||
; -- setup the VERA's data address register 0 or 1, including setting up optional auto increment amount.
|
||||
; Specifiying an unsupported amount results in amount of zero. See the Vera docs about what amounts are possible.
|
||||
; This is a convenience routine, and not very efficient if you call it often;
|
||||
; it's usually better to write a tailor made version of it that accounts for the repeated values.
|
||||
%asm {{
|
||||
jsr _setup
|
||||
lda cx16.r2H
|
||||
@@ -791,6 +798,7 @@ asmsub vaddr_autoincr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, uword
|
||||
jsr _determine_incr_bits
|
||||
+ ora P8ZP_SCRATCH_REG
|
||||
sta cx16.VERA_ADDR_H
|
||||
stz cx16.VERA_CTRL
|
||||
rts
|
||||
|
||||
_setup sta P8ZP_SCRATCH_REG
|
||||
@@ -838,13 +846,15 @@ _large ora cx16.r2L
|
||||
+ lda #0
|
||||
rts
|
||||
_strides_lsb .byte 0,1,2,4,8,16,32,64,128,255,255,40,80,160,255,255
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vaddr_autodecr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, uword autoDecrAmount @R2) clobbers(A,Y) {
|
||||
; -- setup the VERA's data address register 0 or 1
|
||||
; including setting up optional auto decrement amount.
|
||||
; -- setup the VERA's data address register 0 or 1 including setting up optional auto decrement amount.
|
||||
; Specifiying an unsupported amount results in amount of zero. See the Vera docs about what amounts are possible.
|
||||
; This is a convenience routine, and not very efficient if you call it often;
|
||||
; it's usually better to write a tailor made version of it that accounts for the repeated values.
|
||||
%asm {{
|
||||
jsr vaddr_autoincr._setup
|
||||
lda cx16.r2H
|
||||
@@ -854,6 +864,7 @@ asmsub vaddr_autodecr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, uword
|
||||
ora #%00001000 ; autodecrement
|
||||
+ ora P8ZP_SCRATCH_REG
|
||||
sta cx16.VERA_ADDR_H
|
||||
stz cx16.VERA_CTRL
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@@ -955,6 +966,7 @@ asmsub save_virtual_registers() clobbers(A,Y) {
|
||||
_cx16_vreg_storage
|
||||
.word 0,0,0,0,0,0,0,0
|
||||
.word 0,0,0,0,0,0,0,0
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -993,6 +1005,7 @@ asmsub save_vera_context() clobbers(A) {
|
||||
sta _vera_storage+6
|
||||
rts
|
||||
_vera_storage: .byte 0,0,0,0,0,0,0,0
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -1315,6 +1328,7 @@ _continue iny
|
||||
bne _continue
|
||||
+ pla
|
||||
sta $00
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -1358,7 +1372,7 @@ _continue iny
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
const ubyte target = 16 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
|
||||
const ubyte target = 16 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
|
||||
|
||||
const ubyte sizeof_bool = 1
|
||||
const ubyte sizeof_byte = 1
|
||||
@@ -1367,90 +1381,6 @@ sys {
|
||||
const ubyte sizeof_uword = 2
|
||||
const ubyte sizeof_float = 5
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
%asm {{
|
||||
sei
|
||||
lda #0
|
||||
tax
|
||||
tay
|
||||
jsr cx16.mouse_config ; disable mouse
|
||||
lda cx16.VERA_DC_VIDEO
|
||||
and #%00000111 ; retain chroma + output mode
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda #$0a
|
||||
sta $01 ; rom bank 10 (audio)
|
||||
jsr cx16.audio_init ; silence
|
||||
stz $01 ; rom bank 0 (kernal)
|
||||
jsr cbm.IOINIT
|
||||
jsr cbm.RESTOR
|
||||
jsr cbm.CINT
|
||||
lda cx16.VERA_DC_VIDEO
|
||||
and #%11111000
|
||||
ora P8ZP_SCRATCH_REG
|
||||
sta cx16.VERA_DC_VIDEO ; restore old output mode
|
||||
lda #$90 ; black
|
||||
jsr cbm.CHROUT
|
||||
lda #1
|
||||
jsr cbm.CHROUT ; swap fg/bg
|
||||
lda #$9e ; yellow
|
||||
jsr cbm.CHROUT
|
||||
lda #147 ; clear screen
|
||||
jsr cbm.CHROUT
|
||||
lda #8 ; disable charset case switch
|
||||
jsr cbm.CHROUT
|
||||
lda #PROG8_VARSHIGH_RAMBANK
|
||||
sta $00 ; select ram bank
|
||||
lda #0
|
||||
sta $01 ; set ROM bank to kernal bank to speed up kernal calls
|
||||
tax
|
||||
tay
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
sei
|
||||
lda cbm.CINV
|
||||
sta restore_irq._orig_irqvec
|
||||
lda cbm.CINV+1
|
||||
sta restore_irq._orig_irqvec+1
|
||||
lda #PROG8_VARSHIGH_RAMBANK
|
||||
sta $00 ; select ram bank
|
||||
stz $01 ; set ROM bank to kernal bank to speed up kernal calls
|
||||
cli
|
||||
cld
|
||||
clc
|
||||
clv
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
%asm {{
|
||||
lda #1
|
||||
sta $00 ; ram bank 1
|
||||
lda #4
|
||||
sta $01 ; rom bank 4 (basic)
|
||||
stz $2d ; hack to reset machine code monitor bank to 0
|
||||
jsr cbm.CLRCHN ; reset i/o channels
|
||||
_exitcodeCarry = *+1
|
||||
lda #0
|
||||
lsr a
|
||||
_exitcode = *+1
|
||||
lda #0 ; exit code possibly modified in exit()
|
||||
_exitcodeX = *+1
|
||||
ldx #0
|
||||
_exitcodeY = *+1
|
||||
ldy #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irq(uword handler @AY) clobbers(A) {
|
||||
; Sets the handler for the VSYNC interrupt, and enable that interrupt.
|
||||
%asm {{
|
||||
@@ -1499,7 +1429,8 @@ asmsub restore_irq() clobbers(A) {
|
||||
cli
|
||||
rts
|
||||
_orig_irqvec .word 0
|
||||
}}
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
|
||||
@@ -1698,6 +1629,56 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcmp(uword address1 @R0, uword address2 @R1, uword size @AY) -> byte @A {
|
||||
; Compares two blocks of memory
|
||||
; Returns -1 (255), 0 or 1, meaning: block 1 sorts before, equal or after block 2.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldx P8ZP_SCRATCH_W1+1
|
||||
beq _no_msb_size
|
||||
|
||||
_loop_msb_size
|
||||
ldy #0
|
||||
- lda (cx16.r0),y
|
||||
cmp (cx16.r1),y
|
||||
bcs +
|
||||
lda #-1
|
||||
rts
|
||||
+ beq +
|
||||
lda #1
|
||||
rts
|
||||
+ iny
|
||||
bne -
|
||||
inc cx16.r0+1
|
||||
inc cx16.r1+1
|
||||
dec P8ZP_SCRATCH_W1+1
|
||||
dex
|
||||
bne _loop_msb_size
|
||||
|
||||
_no_msb_size
|
||||
lda P8ZP_SCRATCH_W1
|
||||
bne +
|
||||
rts
|
||||
|
||||
+ ldy #0
|
||||
- lda (cx16.r0),y
|
||||
cmp (cx16.r1),y
|
||||
bcs +
|
||||
lda #-1
|
||||
rts
|
||||
+ beq +
|
||||
lda #1
|
||||
rts
|
||||
+ iny
|
||||
cpy P8ZP_SCRATCH_W1
|
||||
bne -
|
||||
|
||||
lda #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub read_flags() -> ubyte @A {
|
||||
%asm {{
|
||||
php
|
||||
@@ -1775,6 +1756,7 @@ save_SCRATCH_ZPB1 .byte 0
|
||||
save_SCRATCH_ZPREG .byte 0
|
||||
save_SCRATCH_ZPWORD1 .word 0
|
||||
save_SCRATCH_ZPWORD2 .word 0
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -1799,37 +1781,37 @@ save_SCRATCH_ZPWORD2 .word 0
|
||||
asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
sta cleanup_at_exit._exitcode
|
||||
sta p8_sys_startup.cleanup_at_exit._exitcode
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
jmp cleanup_at_exit
|
||||
jmp p8_sys_startup.cleanup_at_exit
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub exit2(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y) {
|
||||
; -- immediately exit the program with result values in the A, X and Y registers.
|
||||
%asm {{
|
||||
sta cleanup_at_exit._exitcode
|
||||
stx cleanup_at_exit._exitcodeX
|
||||
sty cleanup_at_exit._exitcodeY
|
||||
sta p8_sys_startup.cleanup_at_exit._exitcode
|
||||
stx p8_sys_startup.cleanup_at_exit._exitcodeX
|
||||
sty p8_sys_startup.cleanup_at_exit._exitcodeY
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
jmp cleanup_at_exit
|
||||
jmp p8_sys_startup.cleanup_at_exit
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub exit3(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y, bool carry @Pc) {
|
||||
; -- immediately exit the program with result values in the A, X and Y registers, and the Carry flag in the status register.
|
||||
%asm {{
|
||||
sta cleanup_at_exit._exitcode
|
||||
sta p8_sys_startup.cleanup_at_exit._exitcode
|
||||
lda #0
|
||||
rol a
|
||||
sta cleanup_at_exit._exitcodeCarry
|
||||
stx cleanup_at_exit._exitcodeX
|
||||
sty cleanup_at_exit._exitcodeY
|
||||
sta p8_sys_startup.cleanup_at_exit._exitcodeCarry
|
||||
stx p8_sys_startup.cleanup_at_exit._exitcodeX
|
||||
sty p8_sys_startup.cleanup_at_exit._exitcodeY
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
jmp cleanup_at_exit
|
||||
jmp p8_sys_startup.cleanup_at_exit
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -1840,6 +1822,13 @@ save_SCRATCH_ZPWORD2 .word 0
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub progstart() -> uword @AY {
|
||||
%asm {{
|
||||
lda #<prog8_program_start
|
||||
ldy #>prog8_program_start
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub push(ubyte value @A) {
|
||||
%asm {{
|
||||
pha
|
||||
@@ -1867,3 +1856,91 @@ save_SCRATCH_ZPWORD2 .word 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
p8_sys_startup {
|
||||
; program startup and shutdown machinery. Needs to reside in normal system ram.
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
%asm {{
|
||||
sei
|
||||
lda #0
|
||||
tax
|
||||
tay
|
||||
jsr cx16.mouse_config ; disable mouse
|
||||
lda cx16.VERA_DC_VIDEO
|
||||
and #%00000111 ; retain chroma + output mode
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda #$0a
|
||||
sta $01 ; rom bank 10 (audio)
|
||||
jsr cx16.audio_init ; silence
|
||||
stz $01 ; rom bank 0 (kernal)
|
||||
jsr cbm.IOINIT
|
||||
jsr cbm.RESTOR
|
||||
jsr cbm.CINT
|
||||
lda cx16.VERA_DC_VIDEO
|
||||
and #%11111000
|
||||
ora P8ZP_SCRATCH_REG
|
||||
sta cx16.VERA_DC_VIDEO ; restore old output mode
|
||||
lda #$90 ; black
|
||||
jsr cbm.CHROUT
|
||||
lda #1
|
||||
jsr cbm.CHROUT ; swap fg/bg
|
||||
lda #$9e ; yellow
|
||||
jsr cbm.CHROUT
|
||||
lda #147 ; clear screen
|
||||
jsr cbm.CHROUT
|
||||
lda #8 ; disable charset case switch
|
||||
jsr cbm.CHROUT
|
||||
lda #PROG8_VARSHIGH_RAMBANK
|
||||
sta $00 ; select ram bank
|
||||
lda #0
|
||||
sta $01 ; set ROM bank to kernal bank to speed up kernal calls
|
||||
tax
|
||||
tay
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
sei
|
||||
lda cbm.CINV
|
||||
sta sys.restore_irq._orig_irqvec
|
||||
lda cbm.CINV+1
|
||||
sta sys.restore_irq._orig_irqvec+1
|
||||
lda #PROG8_VARSHIGH_RAMBANK
|
||||
sta $00 ; select ram bank
|
||||
stz $01 ; set ROM bank to kernal bank to speed up kernal calls
|
||||
cli
|
||||
cld
|
||||
clc
|
||||
clv
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
%asm {{
|
||||
lda #1
|
||||
sta $00 ; ram bank 1
|
||||
lda #4
|
||||
sta $01 ; rom bank 4 (basic)
|
||||
jsr cbm.CLRCHN ; reset i/o channels
|
||||
_exitcodeCarry = *+1
|
||||
lda #0
|
||||
lsr a
|
||||
_exitcode = *+1
|
||||
lda #0 ; exit code possibly modified in exit()
|
||||
_exitcodeX = *+1
|
||||
ldx #0
|
||||
_exitcodeY = *+1
|
||||
ldy #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,7 +16,7 @@ const ubyte DEFAULT_HEIGHT = 60
|
||||
const ubyte VERA_TEXTMATRIX_BANK = 1
|
||||
const uword VERA_TEXTMATRIX_ADDR = $b000
|
||||
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
|
||||
|
||||
sub clear_screen() {
|
||||
|
||||
@@ -28,7 +28,7 @@ verafx {
|
||||
|
||||
sub clear(ubyte vbank, uword vaddr, ubyte data, uword num_longwords) {
|
||||
; use cached 4-byte write to quickly clear a portion of the video memory to a given byte value
|
||||
; this routine is around 3 times faster as gfx2.clear_screen()
|
||||
; this routine is around 3 times faster as gfx_hires/gfx_lores.clear_screen()
|
||||
cx16.VERA_CTRL = 0
|
||||
cx16.VERA_ADDR_H = vbank | %00110000 ; 4-byte increment
|
||||
cx16.VERA_ADDR_M = msb(vaddr)
|
||||
|
||||
@@ -5,14 +5,13 @@
|
||||
math {
|
||||
%option no_symbol_prefixing, ignore_unused
|
||||
|
||||
%asminclude "library:math.asm"
|
||||
|
||||
asmsub sin8u(ubyte angle @A) clobbers(Y) -> ubyte @A {
|
||||
%asm {{
|
||||
tay
|
||||
lda _sinecos8u,y
|
||||
rts
|
||||
_sinecos8u .byte trunc(128.0 + 127.5 * sin(range(256+64) * rad(360.0/256.0)))
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -30,6 +29,7 @@ _sinecos8u .byte trunc(128.0 + 127.5 * sin(range(256+64) * rad(360.0/256.0)))
|
||||
lda _sinecos8,y
|
||||
rts
|
||||
_sinecos8 .char trunc(127.0 * sin(range(256+64) * rad(360.0/256.0)))
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ _sinecos8 .char trunc(127.0 * sin(range(256+64) * rad(360.0/256.0)))
|
||||
lda _sinecosR8u,y
|
||||
rts
|
||||
_sinecosR8u .byte trunc(128.0 + 127.5 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -64,6 +65,7 @@ _sinecosR8u .byte trunc(128.0 + 127.5 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
lda _sinecosR8,y
|
||||
rts
|
||||
_sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -77,13 +79,13 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
|
||||
asmsub rnd() clobbers(Y) -> ubyte @A {
|
||||
%asm {{
|
||||
jmp math.randbyte
|
||||
jmp prog8_math.randbyte
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub rndw() -> uword @AY {
|
||||
%asm {{
|
||||
jmp math.randword
|
||||
jmp prog8_math.randword
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -108,12 +110,12 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
asmsub rndseed(uword seed1 @AY, uword seed2 @R0) clobbers(A,Y) {
|
||||
; -- set new pseudo RNG's seed values. Defaults are: $00c2, $1137
|
||||
%asm {{
|
||||
sta math.randword.x1
|
||||
sty math.randword.c1
|
||||
sta prog8_math.randword.x1
|
||||
sty prog8_math.randword.c1
|
||||
lda cx16.r0L
|
||||
sta math.randword.a1
|
||||
sta prog8_math.randword.a1
|
||||
lda cx16.r0H
|
||||
sta math.randword.b1
|
||||
sta prog8_math.randword.b1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@@ -131,6 +133,7 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
rts
|
||||
+ lsr a
|
||||
bne -
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -172,8 +175,8 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
; as soon as a negative word value (or 2) was used in the multiplication, these upper 16 bits are not valid!!
|
||||
; Suggestion (if you are on the Commander X16): use verafx.muls() to get a hardware accelerated 32 bit signed multplication.
|
||||
%asm {{
|
||||
lda multiply_words.result+2
|
||||
ldy multiply_words.result+3
|
||||
lda prog8_math.multiply_words.result+2
|
||||
ldy prog8_math.multiply_words.result+3
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@@ -331,7 +334,7 @@ _quadrant_region_to_direction:
|
||||
.byte 8, 4,16,20
|
||||
.byte 7, 5,17,19
|
||||
.byte 6, 6,18,18
|
||||
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -464,7 +467,7 @@ log2_tab
|
||||
.byte $fb,$fb,$fb,$fc,$fc,$fc,$fc,$fc
|
||||
.byte $fd,$fd,$fd,$fd,$fd,$fd,$fe,$fe
|
||||
.byte $fe,$fe,$fe,$ff,$ff,$ff,$ff,$ff
|
||||
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -640,4 +643,23 @@ log2_tab
|
||||
cx16.r15 ^= $ffff
|
||||
cx16.r14 ^= $ffff
|
||||
}
|
||||
|
||||
|
||||
sub lerp(ubyte v0, ubyte v1, ubyte t) -> ubyte {
|
||||
; Linear interpolation (LERP)
|
||||
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 255]
|
||||
; guarantees v = v1 when t = 255
|
||||
return v0 + msb(t as uword * (v1 - v0) + 255)
|
||||
}
|
||||
|
||||
sub lerpw(uword v0, uword v1, uword t) -> uword {
|
||||
; Linear interpolation (LERP) on word values
|
||||
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 65535]
|
||||
; guarantees v = v1 when t = 65535
|
||||
t *= v1-v0
|
||||
cx16.r0 = math.mul16_last_upper()
|
||||
if t!=0
|
||||
cx16.r0++
|
||||
return v0 + cx16.r0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,500 @@
|
||||
; Convenience macros for Neo6502 applications programming
|
||||
; SPDX-License-Identifier: CC0-1.0
|
||||
; obtained from https://github.com/paulscottrobson/neo6502-firmware
|
||||
|
||||
;-----------------------------------------------------;
|
||||
; Neo6502 Kernel jump vectors (see kernel/kernel.asm) ;
|
||||
;-----------------------------------------------------;
|
||||
|
||||
ChainBasicOnlyProgram = $ffdf
|
||||
ChainBasicProgram = $ffe2
|
||||
WriteCharacterInline = $ffe5
|
||||
LoadExtended = $ffe8
|
||||
ReadLine = $FFEB
|
||||
ReadCharacter = $FFEE
|
||||
WriteCharacter = $FFF1
|
||||
WaitMessage = $FFF4
|
||||
SendMessage = $FFF7
|
||||
|
||||
|
||||
;--------------------------------------;
|
||||
; Neo6502 Kernel API control addresses ;
|
||||
;--------------------------------------;
|
||||
|
||||
ControlPort = $FF00
|
||||
API_COMMAND = ControlPort + 0 ; function group address
|
||||
API_FUNCTION = ControlPort + 1 ; function address
|
||||
API_ERROR = ControlPort + 2 ; function error codes
|
||||
API_STATUS = ControlPort + 3 ; misc hardware status codes (bit-field)
|
||||
API_PARAMETERS = ControlPort + 4 ; function parameters base address (+0-7)
|
||||
|
||||
|
||||
;------------------------------------------------;
|
||||
; Neo6502 Kernel API control codes (see api.pdf) ;
|
||||
;------------------------------------------------;
|
||||
|
||||
; Status Information
|
||||
API_ERROR_NONE = #$00 ; error code
|
||||
API_STATUS_ESC = #$07 ; flag
|
||||
|
||||
; System functions (Group 1)
|
||||
API_GROUP_SYSTEM = #$01 ; API function group
|
||||
API_FN_TIMER = #$01 ; API function
|
||||
API_FN_KEY_STATUS = #$02 ; API function
|
||||
API_FN_BASIC = #$03 ; API function
|
||||
API_FN_CREDITS = #$04 ; API function
|
||||
API_FN_SERIAL_STATUS = #$05 ; API function
|
||||
API_FN_LOCALE = #$06 ; API function
|
||||
API_FN_RESET = #$07 ; API function
|
||||
API_FN_MOS = #$08 ; API function
|
||||
API_FN_SWEET16 = #$09 ; API function
|
||||
|
||||
; Console functions (Group 2)
|
||||
API_GROUP_CONSOLE = #$02 ; API function group
|
||||
API_FN_READ_CHAR = #$01 ; API function
|
||||
API_FN_CONSOLE_STATUS = #$02 ; API function
|
||||
API_FN_READ_LINE = #$03 ; API function
|
||||
API_FN_DEFINE_HOTKEY = #$04 ; API function
|
||||
API_FN_DEFINE_CHAR = #$05 ; API function
|
||||
API_FN_WRITE_CHAR = #$06 ; API function
|
||||
API_FN_SET_CURSOR_POS = #$07 ; API function
|
||||
API_FN_LIST_HOTKEYS = #$08 ; API function
|
||||
API_FN_SCREEN_SIZE = #$09 ; API function
|
||||
API_FN_INSERT_LINE = #$0A ; API function
|
||||
API_FN_DELETE_LINE = #$0B ; API function
|
||||
API_FN_CLEAR_SCREEN = #$0C ; API function
|
||||
API_FN_CURSOR_POS = #$0D ; API function
|
||||
API_FN_CLEAR_REGIION = #$0E ; API function
|
||||
API_FN_SET_TEXT_COLOR = #$0F ; API function
|
||||
API_FN_CURSOR_INVERSE = #$10 ; API function
|
||||
|
||||
; Console results (Group 2 Function 2)
|
||||
API_QUEUE_EMPTY = #$FF ; API result (status code)
|
||||
|
||||
; File I/O functions (Group 3)
|
||||
API_GROUP_FILEIO = #$03 ; API function group
|
||||
API_FN_LIST_DIRECTORY = #$01 ; API function
|
||||
API_FN_LOAD_FILENAME = #$02 ; API function
|
||||
API_FN_STORE_FILENAME = #$03 ; API function
|
||||
API_FN_FILE_OPEN = #$04 ; API function
|
||||
API_FN_FILE_CLOSE = #$05 ; API function
|
||||
API_FN_FILE_SEEK = #$06 ; API function
|
||||
API_FN_FILE_TELL = #$07 ; API function
|
||||
API_FN_FILE_READ = #$08 ; API function
|
||||
API_FN_FILE_WRITE = #$09 ; API function
|
||||
API_FN_FILE_SIZE = #$0A ; API function
|
||||
API_FN_FILE_SET_SIZE = #$0B ; API function
|
||||
API_FN_FILE_RENAME = #$0C ; API function
|
||||
API_FN_FILE_DELETE = #$0D ; API function
|
||||
API_FN_DIR_CHDIR = #$0E ; API function
|
||||
API_FN_DIR_MKDIR = #$0F ; API function
|
||||
API_FN_FILE_STAT = #$10 ; API function
|
||||
API_FN_DIR_OPEN = #$11 ; API function
|
||||
API_FN_DIR_READ = #$12 ; API function
|
||||
API_FN_DIR_CLOSE = #$13 ; API function
|
||||
API_FN_FILE_COPY = #$14 ; API function
|
||||
API_FN_SET_ATTR = #$15 ; API function
|
||||
API_FN_LIST_FILTERED = #$20 ; API function
|
||||
|
||||
; File I/O parameters (Group 3 Function 2)
|
||||
API_FILE_TO_SCREEN = #$FFFF ; API parameter
|
||||
|
||||
; Mathematics functions (Group 4)
|
||||
API_GROUP_MATH = #$04 ; API function group
|
||||
API_FN_ADD = #$00 ; API function
|
||||
API_FN_SUB = #$01 ; API function
|
||||
API_FN_MUL = #$02 ; API function
|
||||
API_FN_DIV_DEC = #$03 ; API function
|
||||
API_FN_DIV_INT = #$04 ; API function
|
||||
API_FN_MOD = #$05 ; API function
|
||||
API_FN_COMP = #$06 ; API function
|
||||
API_FN_NEG = #$10 ; API function
|
||||
API_FN_FLOOR = #$11 ; API function
|
||||
API_FN_SQRT = #$12 ; API function
|
||||
API_FN_SINE = #$13 ; API function
|
||||
API_FN_COS = #$14 ; API function
|
||||
API_FN_TAN = #$15 ; API function
|
||||
API_FN_ATAN = #$16 ; API function
|
||||
API_FN_EXP = #$17 ; API function
|
||||
API_FN_LOG = #$18 ; API function
|
||||
API_FN_ABS = #$19 ; API function
|
||||
API_FN_SIGN = #$1A ; API function
|
||||
API_FN_RND_DEC = #$1B ; API function
|
||||
API_FN_RND_INT = #$1C ; API function
|
||||
API_FN_INT_TO_DEC = #$20 ; API function
|
||||
API_FN_STR_TO_NUM = #$21 ; API function
|
||||
API_FN_NUM_TO_STR = #$22 ; API function
|
||||
API_FN_SET_DEG_RAD_MODE = #$23 ; API function
|
||||
|
||||
; Graphics functions (Group 5)
|
||||
API_GROUP_GRAPHICS = #$05 ; API function group
|
||||
API_FN_SET_GFX = #$01 ; API function
|
||||
API_FN_DRAW_LINE = #$02 ; API function
|
||||
API_FN_DRAW_RECT = #$03 ; API function
|
||||
API_FN_DRAW_ELLIPSE = #$04 ; API function
|
||||
API_FN_DRAW_PIXEL = #$05 ; API function
|
||||
API_FN_DRAW_TEXT = #$06 ; API function
|
||||
API_FN_DRAW_IMG = #$07 ; API function
|
||||
API_FN_DRAW_TILEMAP = #$08 ; API function
|
||||
API_FN_SET_PALETTE = #$20 ; API function
|
||||
API_FN_READ_PIXEL = #$21 ; API function
|
||||
API_FN_RESET_PALETTE = #$22 ; API function
|
||||
API_FN_SET_TILEMAP = #$23 ; API function
|
||||
API_FN_READ_SPRITE_PXL = #$24 ; API function
|
||||
API_FN_FRAME_COUNT = #$25 ; API function
|
||||
API_FN_GET_PALETTE = #$26 ; API function
|
||||
API_FN_WRITE_PIXEL = #$27 ; API function
|
||||
API_FN_SET_COLOR = #$40 ; API function
|
||||
API_FN_SET_SOLID = #$41 ; API function
|
||||
API_FN_SET_DRAW_SIZE = #$42 ; API function
|
||||
API_FN_SET_FLIP = #$43 ; API function
|
||||
|
||||
; Graphics parameters (Group 5, Function 1 - Group 6, Function 2)
|
||||
API_FLIP_HORZ = #$00 ; API parameter (flag)
|
||||
API_FLIP_VERT = #$01 ; API parameter (flag)
|
||||
|
||||
; Graphics results (Group 5, Functions 33,36)
|
||||
API_PIXEL_TRANSPARENT = #$00 ; API result (flag)
|
||||
|
||||
; Sprites functions (Group 6)
|
||||
API_GROUP_SPRITES = #$06 ; API function group
|
||||
API_FN_SPRITE_RESET = #$01 ; API function
|
||||
API_FN_SPRITE_SET = #$02 ; API function
|
||||
API_FN_SPRITE_HIDE = #$03 ; API function
|
||||
API_FN_SPRITE_COLLISION = #$04 ; API function
|
||||
API_FN_SPRITE_POS = #$05 ; API function
|
||||
|
||||
; Sprites parameters (Group 6, Function 2)
|
||||
API_SPRITE_TURTLE = #$00 ; API parameter (sprite index)
|
||||
API_SPRITE_32BIT = #$40 ; API parameter (bit-mask)
|
||||
API_SPRITE_CLEAR = #$80 ; API parameter (bit-mask)
|
||||
API_ANCHOR_BL = #$01 ; API parameter (anchor position)
|
||||
API_ANCHOR_B = #$02 ; API parameter (anchor position)
|
||||
API_ANCHOR_BR = #$03 ; API parameter (anchor position)
|
||||
API_ANCHOR_L = #$04 ; API parameter (anchor position)
|
||||
API_ANCHOR_C = #$05 ; API parameter (anchor position)
|
||||
API_ANCHOR_R = #$06 ; API parameter (anchor position)
|
||||
API_ANCHOR_TL = #$07 ; API parameter (anchor position)
|
||||
API_ANCHOR_T = #$08 ; API parameter (anchor position)
|
||||
API_ANCHOR_TR = #$09 ; API parameter (anchor position)
|
||||
|
||||
; Sprites results (Group 6, Function 4)
|
||||
API_COLLISION_NONE = #$00 ; API result (flag)
|
||||
|
||||
; Controller functions (Group 7)
|
||||
API_GROUP_CONTROLLER = #$07 ; API function group
|
||||
API_FN_READ_DEFAULT_CONTROLLER = #$01 ; API function
|
||||
API_FN_READ_CONTROLLER_COUNT = #$02 ; API function
|
||||
API_FN_READ_CONTROLLER = #$03 ; API function
|
||||
|
||||
; Controller results (Group 7, Function 1)
|
||||
API_CONTROLLER_LEFT = #$01 ; API result (status bit-mask)
|
||||
API_CONTROLLER_RIGHT = #$02 ; API result (status bit-mask)
|
||||
API_CONTROLLER_UP = #$04 ; API result (status bit-mask)
|
||||
API_CONTROLLER_DOWN = #$08 ; API result (status bit-mask)
|
||||
API_CONTROLLER_BTNA = #$10 ; API result (status bit-mask)
|
||||
API_CONTROLLER_BTNB = #$20 ; API result (status bit-mask)
|
||||
|
||||
; Sound functions (Group 8)
|
||||
API_GROUP_SOUND = #$08 ; API function group
|
||||
API_FN_RESET_SOUND = #$01 ; API function
|
||||
API_FN_RESET_CHANNEL = #$02 ; API function
|
||||
API_FN_BEEP = #$03 ; API function
|
||||
API_FN_QUEUE_SOUND = #$04 ; API function
|
||||
API_FN_PLAY_SOUND = #$05 ; API function
|
||||
API_FN_SOUND_STATUS = #$06 ; API function
|
||||
|
||||
; Sound parameters (Group 8, Functions 2,4,5)
|
||||
API_SOUND_CH_00 = #$00 ; API parameter (channel index)
|
||||
|
||||
; Sound parameters (Group 8, Function 4)
|
||||
API_NOTE_REST = #$0000 ; API parameter (musical rest)
|
||||
API_NOTE_C0 = #$0010 ; API parameter (musical note)
|
||||
API_NOTE_Cs0 = #$0011 ; API parameter (musical note)
|
||||
API_NOTE_Df0 = #$0011 ; API parameter (musical note)
|
||||
API_NOTE_D0 = #$0012 ; API parameter (musical note)
|
||||
API_NOTE_Ds0 = #$0013 ; API parameter (musical note)
|
||||
API_NOTE_Ef0 = #$0013 ; API parameter (musical note)
|
||||
API_NOTE_E0 = #$0015 ; API parameter (musical note)
|
||||
API_NOTE_F0 = #$0016 ; API parameter (musical note)
|
||||
API_NOTE_Fs0 = #$0017 ; API parameter (musical note)
|
||||
API_NOTE_Gf0 = #$0017 ; API parameter (musical note)
|
||||
API_NOTE_G0 = #$0018 ; API parameter (musical note)
|
||||
API_NOTE_Af0 = #$001A ; API parameter (musical note)
|
||||
API_NOTE_Gs0 = #$001A ; API parameter (musical note)
|
||||
API_NOTE_A0 = #$001C ; API parameter (musical note)
|
||||
API_NOTE_As0 = #$001D ; API parameter (musical note)
|
||||
API_NOTE_Bf0 = #$001D ; API parameter (musical note)
|
||||
API_NOTE_B0 = #$001F ; API parameter (musical note)
|
||||
API_NOTE_C1 = #$0021 ; API parameter (musical note)
|
||||
API_NOTE_Cs1 = #$0023 ; API parameter (musical note)
|
||||
API_NOTE_Df1 = #$0023 ; API parameter (musical note)
|
||||
API_NOTE_D1 = #$0025 ; API parameter (musical note)
|
||||
API_NOTE_Ds1 = #$0027 ; API parameter (musical note)
|
||||
API_NOTE_Ef1 = #$0027 ; API parameter (musical note)
|
||||
API_NOTE_E1 = #$0029 ; API parameter (musical note)
|
||||
API_NOTE_F1 = #$002C ; API parameter (musical note)
|
||||
API_NOTE_Fs1 = #$002E ; API parameter (musical note)
|
||||
API_NOTE_Gf1 = #$002E ; API parameter (musical note)
|
||||
API_NOTE_G1 = #$0031 ; API parameter (musical note)
|
||||
API_NOTE_Af1 = #$0034 ; API parameter (musical note)
|
||||
API_NOTE_Gs1 = #$0034 ; API parameter (musical note)
|
||||
API_NOTE_A1 = #$0037 ; API parameter (musical note)
|
||||
API_NOTE_As1 = #$003A ; API parameter (musical note)
|
||||
API_NOTE_Bf1 = #$003A ; API parameter (musical note)
|
||||
API_NOTE_B1 = #$003E ; API parameter (musical note)
|
||||
API_NOTE_C2 = #$0041 ; API parameter (musical note)
|
||||
API_NOTE_Cs2 = #$0045 ; API parameter (musical note)
|
||||
API_NOTE_Df2 = #$0045 ; API parameter (musical note)
|
||||
API_NOTE_D2 = #$0049 ; API parameter (musical note)
|
||||
API_NOTE_Ds2 = #$004E ; API parameter (musical note)
|
||||
API_NOTE_Ef2 = #$004E ; API parameter (musical note)
|
||||
API_NOTE_E2 = #$0052 ; API parameter (musical note)
|
||||
API_NOTE_F2 = #$0057 ; API parameter (musical note)
|
||||
API_NOTE_Fs2 = #$005C ; API parameter (musical note)
|
||||
API_NOTE_Gf2 = #$005C ; API parameter (musical note)
|
||||
API_NOTE_G2 = #$0062 ; API parameter (musical note)
|
||||
API_NOTE_Af2 = #$0068 ; API parameter (musical note)
|
||||
API_NOTE_Gs2 = #$0068 ; API parameter (musical note)
|
||||
API_NOTE_A2 = #$006E ; API parameter (musical note)
|
||||
API_NOTE_As2 = #$0075 ; API parameter (musical note)
|
||||
API_NOTE_Bf2 = #$0075 ; API parameter (musical note)
|
||||
API_NOTE_B2 = #$007B ; API parameter (musical note)
|
||||
API_NOTE_C3 = #$0083 ; API parameter (musical note)
|
||||
API_NOTE_Cs3 = #$008B ; API parameter (musical note)
|
||||
API_NOTE_Df3 = #$008B ; API parameter (musical note)
|
||||
API_NOTE_D3 = #$0093 ; API parameter (musical note)
|
||||
API_NOTE_Ds3 = #$009C ; API parameter (musical note)
|
||||
API_NOTE_Ef3 = #$009C ; API parameter (musical note)
|
||||
API_NOTE_E3 = #$00A5 ; API parameter (musical note)
|
||||
API_NOTE_F3 = #$00AF ; API parameter (musical note)
|
||||
API_NOTE_Fs3 = #$00B9 ; API parameter (musical note)
|
||||
API_NOTE_Gf3 = #$00B9 ; API parameter (musical note)
|
||||
API_NOTE_G3 = #$00C4 ; API parameter (musical note)
|
||||
API_NOTE_Af3 = #$00D0 ; API parameter (musical note)
|
||||
API_NOTE_Gs3 = #$00D0 ; API parameter (musical note)
|
||||
API_NOTE_A3 = #$00DC ; API parameter (musical note)
|
||||
API_NOTE_As3 = #$00E9 ; API parameter (musical note)
|
||||
API_NOTE_Bf3 = #$00E9 ; API parameter (musical note)
|
||||
API_NOTE_B3 = #$00F7 ; API parameter (musical note)
|
||||
API_NOTE_C4 = #$0106 ; API parameter (musical note)
|
||||
API_NOTE_Cs4 = #$0115 ; API parameter (musical note)
|
||||
API_NOTE_Df4 = #$0115 ; API parameter (musical note)
|
||||
API_NOTE_D4 = #$0126 ; API parameter (musical note)
|
||||
API_NOTE_Ds4 = #$0137 ; API parameter (musical note)
|
||||
API_NOTE_Ef4 = #$0137 ; API parameter (musical note)
|
||||
API_NOTE_E4 = #$014A ; API parameter (musical note)
|
||||
API_NOTE_F4 = #$015D ; API parameter (musical note)
|
||||
API_NOTE_Fs4 = #$0172 ; API parameter (musical note)
|
||||
API_NOTE_Gf4 = #$0172 ; API parameter (musical note)
|
||||
API_NOTE_G4 = #$0188 ; API parameter (musical note)
|
||||
API_NOTE_Af4 = #$019F ; API parameter (musical note)
|
||||
API_NOTE_Gs4 = #$019F ; API parameter (musical note)
|
||||
API_NOTE_A4 = #$01B8 ; API parameter (musical note)
|
||||
API_NOTE_As4 = #$01D2 ; API parameter (musical note)
|
||||
API_NOTE_Bf4 = #$01D2 ; API parameter (musical note)
|
||||
API_NOTE_B4 = #$01EE ; API parameter (musical note)
|
||||
API_NOTE_C5 = #$020B ; API parameter (musical note)
|
||||
API_NOTE_Cs5 = #$022A ; API parameter (musical note)
|
||||
API_NOTE_Df5 = #$022A ; API parameter (musical note)
|
||||
API_NOTE_D5 = #$024B ; API parameter (musical note)
|
||||
API_NOTE_Ds5 = #$026E ; API parameter (musical note)
|
||||
API_NOTE_Ef5 = #$026E ; API parameter (musical note)
|
||||
API_NOTE_E5 = #$0293 ; API parameter (musical note)
|
||||
API_NOTE_F5 = #$02BA ; API parameter (musical note)
|
||||
API_NOTE_Fs5 = #$02E4 ; API parameter (musical note)
|
||||
API_NOTE_Gf5 = #$02E4 ; API parameter (musical note)
|
||||
API_NOTE_G5 = #$0310 ; API parameter (musical note)
|
||||
API_NOTE_Af5 = #$033F ; API parameter (musical note)
|
||||
API_NOTE_Gs5 = #$033F ; API parameter (musical note)
|
||||
API_NOTE_A5 = #$0370 ; API parameter (musical note)
|
||||
API_NOTE_As5 = #$03A4 ; API parameter (musical note)
|
||||
API_NOTE_Bf5 = #$03A4 ; API parameter (musical note)
|
||||
API_NOTE_B5 = #$03DC ; API parameter (musical note)
|
||||
API_NOTE_C6 = #$0417 ; API parameter (musical note)
|
||||
API_NOTE_Cs6 = #$0455 ; API parameter (musical note)
|
||||
API_NOTE_Df6 = #$0455 ; API parameter (musical note)
|
||||
API_NOTE_D6 = #$0497 ; API parameter (musical note)
|
||||
API_NOTE_Ds6 = #$04DD ; API parameter (musical note)
|
||||
API_NOTE_Ef6 = #$04DD ; API parameter (musical note)
|
||||
API_NOTE_E6 = #$0527 ; API parameter (musical note)
|
||||
API_NOTE_F6 = #$0575 ; API parameter (musical note)
|
||||
API_NOTE_Fs6 = #$05C8 ; API parameter (musical note)
|
||||
API_NOTE_Gf6 = #$05C8 ; API parameter (musical note)
|
||||
API_NOTE_G6 = #$0620 ; API parameter (musical note)
|
||||
API_NOTE_Af6 = #$067D ; API parameter (musical note)
|
||||
API_NOTE_Gs6 = #$067D ; API parameter (musical note)
|
||||
API_NOTE_A6 = #$06E0 ; API parameter (musical note)
|
||||
API_NOTE_As6 = #$0749 ; API parameter (musical note)
|
||||
API_NOTE_Bf6 = #$0749 ; API parameter (musical note)
|
||||
API_NOTE_B6 = #$07B8 ; API parameter (musical note)
|
||||
API_NOTE_C7 = #$082D ; API parameter (musical note)
|
||||
API_NOTE_Cs7 = #$08A9 ; API parameter (musical note)
|
||||
API_NOTE_Df7 = #$08A9 ; API parameter (musical note)
|
||||
API_NOTE_D7 = #$092D ; API parameter (musical note)
|
||||
API_NOTE_Ds7 = #$09B9 ; API parameter (musical note)
|
||||
API_NOTE_Ef7 = #$09B9 ; API parameter (musical note)
|
||||
API_NOTE_E7 = #$0A4D ; API parameter (musical note)
|
||||
API_NOTE_F7 = #$0AEA ; API parameter (musical note)
|
||||
API_NOTE_Fs7 = #$0B90 ; API parameter (musical note)
|
||||
API_NOTE_Gf7 = #$0B90 ; API parameter (musical note)
|
||||
API_NOTE_G7 = #$0C40 ; API parameter (musical note)
|
||||
API_NOTE_Af7 = #$0CFA ; API parameter (musical note)
|
||||
API_NOTE_Gs7 = #$0CFA ; API parameter (musical note)
|
||||
API_NOTE_A7 = #$0DC0 ; API parameter (musical note)
|
||||
API_NOTE_As7 = #$0E91 ; API parameter (musical note)
|
||||
API_NOTE_Bf7 = #$0E91 ; API parameter (musical note)
|
||||
API_NOTE_B7 = #$0F6F ; API parameter (musical note)
|
||||
API_NOTE_C8 = #$105A ; API parameter (musical note)
|
||||
API_NOTE_Cs8 = #$1153 ; API parameter (musical note)
|
||||
API_NOTE_Df8 = #$1153 ; API parameter (musical note)
|
||||
API_NOTE_D8 = #$125B ; API parameter (musical note)
|
||||
API_NOTE_Ds8 = #$1372 ; API parameter (musical note)
|
||||
API_NOTE_Ef8 = #$1372 ; API parameter (musical note)
|
||||
API_NOTE_E8 = #$149A ; API parameter (musical note)
|
||||
API_NOTE_F8 = #$15D4 ; API parameter (musical note)
|
||||
API_NOTE_Fs8 = #$1720 ; API parameter (musical note)
|
||||
API_NOTE_Gf8 = #$1720 ; API parameter (musical note)
|
||||
API_NOTE_G8 = #$1880 ; API parameter (musical note)
|
||||
API_NOTE_Af8 = #$19F5 ; API parameter (musical note)
|
||||
API_NOTE_Gs8 = #$19F5 ; API parameter (musical note)
|
||||
API_NOTE_A8 = #$1B80 ; API parameter (musical note)
|
||||
API_NOTE_As8 = #$1D23 ; API parameter (musical note)
|
||||
API_NOTE_Bf8 = #$1D23 ; API parameter (musical note)
|
||||
API_NOTE_B8 = #$1EDE ; API parameter (musical note)
|
||||
API_NOTE_C9 = #$20B4 ; API parameter (musical note)
|
||||
API_NOTE_Cs9 = #$22A6 ; API parameter (musical note)
|
||||
API_NOTE_Df9 = #$22A6 ; API parameter (musical note)
|
||||
API_NOTE_D9 = #$24B5 ; API parameter (musical note)
|
||||
API_NOTE_Ds9 = #$26E4 ; API parameter (musical note)
|
||||
API_NOTE_Ef9 = #$26E4 ; API parameter (musical note)
|
||||
API_NOTE_E9 = #$2934 ; API parameter (musical note)
|
||||
API_NOTE_F9 = #$2BA7 ; API parameter (musical note)
|
||||
API_NOTE_Fs9 = #$2E40 ; API parameter (musical note)
|
||||
API_NOTE_Gf9 = #$2E40 ; API parameter (musical note)
|
||||
API_NOTE_G9 = #$3100 ; API parameter (musical note)
|
||||
API_NOTE_Af9 = #$33EA ; API parameter (musical note)
|
||||
API_NOTE_Gs9 = #$33EA ; API parameter (musical note)
|
||||
API_NOTE_A9 = #$3700 ; API parameter (musical note)
|
||||
API_NOTE_As9 = #$3A45 ; API parameter (musical note)
|
||||
API_NOTE_Bf9 = #$3A45 ; API parameter (musical note)
|
||||
API_NOTE_B9 = #$3DBC ; API parameter (musical note)
|
||||
API_NOTE_C10 = #$4168 ; API parameter (musical note)
|
||||
API_NOTE_Cs10 = #$454C ; API parameter (musical note)
|
||||
API_NOTE_Df10 = #$454C ; API parameter (musical note)
|
||||
API_NOTE_D10 = #$496B ; API parameter (musical note)
|
||||
API_NOTE_Ds10 = #$4DC8 ; API parameter (musical note)
|
||||
API_NOTE_Ef10 = #$4DC8 ; API parameter (musical note)
|
||||
API_TEMPO_60 = #$0064 ; API parameter (musical note duration, 60BPM)
|
||||
API_TEMPO_80 = #$004B ; API parameter (musical note duration, 80BPM)
|
||||
API_TEMPO_90 = #$0042 ; API parameter (musical note duration, 90BPM)
|
||||
API_TEMPO_120 = #$0032 ; API parameter (musical note duration, 120BPM)
|
||||
API_SLIDE_NONE = #$0000 ; API parameter (slide value)
|
||||
API_SLIDE_SLOW = #$0004 ; API parameter (slide range)
|
||||
API_SLIDE_MED = #$0008 ; API parameter (slide range)
|
||||
API_SLIDE_FAST = #$0010 ; API parameter (slide range)
|
||||
API_SOUND_SRC_BEEP = #$00 ; API parameter (sound generator)
|
||||
|
||||
; Sound parameters (Group 8, Function 5)
|
||||
API_SFX_POSITIVE = #$00 ; API parameter (sound effect)
|
||||
API_SFX_NEGATIVE = #$01 ; API parameter (sound effect)
|
||||
API_SFX_ERROR = #$02 ; API parameter (sound effect)
|
||||
API_SFX_CONFIRM = #$03 ; API parameter (sound effect)
|
||||
API_SFX_REJECT = #$04 ; API parameter (sound effect)
|
||||
API_SFX_SWEEP = #$05 ; API parameter (sound effect)
|
||||
API_SFX_COIN = #$06 ; API parameter (sound effect)
|
||||
API_SFX_LASER_LONG = #$07 ; API parameter (sound effect)
|
||||
API_SFX_POWERUP = #$08 ; API parameter (sound effect)
|
||||
API_SFX_VICTORY = #$09 ; API parameter (sound effect)
|
||||
API_SFX_DEFEAT = #$0A ; API parameter (sound effect)
|
||||
API_SFX_FANFARE = #$0B ; API parameter (sound effect)
|
||||
API_SFX_ALARM1 = #$0C ; API parameter (sound effect)
|
||||
API_SFX_ALARM2 = #$0D ; API parameter (sound effect)
|
||||
API_SFX_ALARM3 = #$0E ; API parameter (sound effect)
|
||||
API_SFX_RING1 = #$0F ; API parameter (sound effect)
|
||||
API_SFX_RING2 = #$10 ; API parameter (sound effect)
|
||||
API_SFX_RING3 = #$11 ; API parameter (sound effect)
|
||||
API_SFX_DANGER = #$12 ; API parameter (sound effect)
|
||||
API_SFX_EXPL_LONG = #$13 ; API parameter (sound effect)
|
||||
API_SFX_EXPL_MEDIUM = #$14 ; API parameter (sound effect)
|
||||
API_SFX_EXPL_SHORT = #$15 ; API parameter (sound effect)
|
||||
API_SFX_LASER_MEDIUM = #$16 ; API parameter (sound effect)
|
||||
API_SFX_LASER_SHORT = #$17 ; API parameter (sound effect)
|
||||
|
||||
; Turtle Graphics functions (Group 9)
|
||||
API_GROUP_TURTLE = #$09 ; API function group
|
||||
API_FN_TURTLE_INIT = #$01 ; API function
|
||||
API_FN_TURTLE_TURN = #$02 ; API function
|
||||
API_FN_TURTLE_MOVE = #$03 ; API function
|
||||
API_FN_TURTLE_HIDE = #$04 ; API function
|
||||
API_FN_TURTLE_HOME = #$05 ; API function
|
||||
|
||||
; Turtle Graphics parameters (Group 9, Function 2)
|
||||
API_TURTLE_LEFT = #$010E ; API parameter (turn -90 degrees)
|
||||
API_TURTLE_RIGHT = #$005A ; API parameter (turn +90 degrees)
|
||||
API_TURTLE_FLIP = #$00B4 ; API parameter (turn 180 degrees)
|
||||
|
||||
; Turtle Graphics parameters (Group 9, Function 3)
|
||||
API_PEN_UP = #$00 ; API parameter (turtle tracks on)
|
||||
API_PEN_DOWN = #$01 ; API parameter (turtle tracks off)
|
||||
|
||||
; UExt functions (Group 10)
|
||||
API_GROUP_UEXT = #$09 ; API function group
|
||||
API_FN_UEXT_INIT = #$01 ; API function
|
||||
API_FN_GPIO_WRITE = #$02 ; API function
|
||||
API_FN_GPIO_READ = #$03 ; API function
|
||||
API_FN_SET_PORT_DIR = #$04 ; API function
|
||||
API_FN_I2C_WRITE = #$05 ; API function
|
||||
API_FN_I2C_READ = #$06 ; API function
|
||||
API_FN_ANALOG_READ = #$07 ; API function
|
||||
API_FN_I2C_STATUS = #$08 ; API function
|
||||
API_FN_I2C_READ_BLOCK = #$09 ; API function
|
||||
API_FN_I2C_WRITE_BLCOK = #$0a ; API function
|
||||
API_FN_SPI_READ_BLOCK = #$0b ; API function
|
||||
API_FN_SPI_WRITE_BLOCK = #$0c ; API function
|
||||
API_FN_UART_READ_BLOCK = #$0d ; API function
|
||||
API_FN_UART_WRITE_BLOPCK= #$0e ; API function
|
||||
API_FN_UART_SET_SPEED = #$0f ; API function
|
||||
API_FN_UART_WRITE = #$10 ; API function
|
||||
API_FN_UART_READ = #$11 ; API function
|
||||
API_FN_UART_CHECK_BYTE_AVAILABLE = #$12 ; API function
|
||||
|
||||
; TODO group 11 MOUSE
|
||||
; TODO group 12 BLITTER
|
||||
; TODO group 13 EDITOR
|
||||
|
||||
|
||||
;--------;
|
||||
; colors ;
|
||||
;--------;
|
||||
|
||||
COLOR_BLACK = #$80
|
||||
COLOR_RED = #$81
|
||||
COLOR_GREEN = #$82
|
||||
COLOR_YELLOW = #$83
|
||||
COLOR_BLUE = #$84
|
||||
COLOR_MAGENTA = #$85
|
||||
COLOR_CYAN = #$86
|
||||
COLOR_WHITE = #$87
|
||||
COLOR_ALT_BLACK = #$88
|
||||
COLOR_DARK_GREY = #$89
|
||||
COLOR_DARK_GREEN = #$8A
|
||||
COLOR_ORANGE = #$8B
|
||||
COLOR_DARK_ORANGE = #$8C
|
||||
COLOR_BROWN = #$8D
|
||||
COLOR_PINK = #$8E
|
||||
COLOR_LIGHT_GREY = #$8F
|
||||
|
||||
|
||||
;---------------------------------;
|
||||
; limited-case convenience macros ;
|
||||
;---------------------------------;
|
||||
|
||||
DoSendMessage .macro
|
||||
jsr neo.SendMessage
|
||||
.endm
|
||||
|
||||
DoWaitMessage .macro
|
||||
jsr neo.WaitMessage
|
||||
.endm
|
||||
|
||||
@@ -0,0 +1,488 @@
|
||||
; Prog8 definitions for the Neo6502
|
||||
|
||||
%option no_symbol_prefixing, ignore_unused
|
||||
|
||||
neo {
|
||||
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
|
||||
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
|
||||
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
||||
|
||||
%asminclude "library:neo/neo6502.asm"
|
||||
}
|
||||
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
const ubyte target = 7 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
|
||||
|
||||
const ubyte sizeof_bool = 1
|
||||
const ubyte sizeof_byte = 1
|
||||
const ubyte sizeof_ubyte = 1
|
||||
const ubyte sizeof_word = 2
|
||||
const ubyte sizeof_uword = 2
|
||||
const ubyte sizeof_float = 0 ; undefined, no floats supported
|
||||
|
||||
|
||||
asmsub reset_system() {
|
||||
; Soft-reset the system back to initial power-on status
|
||||
; TODO
|
||||
%asm {{
|
||||
sei
|
||||
jmp (neo.RESET_VEC)
|
||||
}}
|
||||
}
|
||||
|
||||
sub wait(uword jiffies) {
|
||||
; --- wait approximately the given number of jiffies (1/60th seconds)
|
||||
; TODO
|
||||
}
|
||||
|
||||
asmsub waitvsync() clobbers(A) {
|
||||
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
|
||||
; TODO
|
||||
%asm {{
|
||||
nop
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub internal_stringcopy(uword source @R0, uword target @AY) clobbers (A,Y) {
|
||||
; Called when the compiler wants to assign a string value to another string.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jmp prog8_lib.strcpy
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||
; note: only works for NON-OVERLAPPING memory regions!
|
||||
; note: can't be inlined because is called from asm as well
|
||||
%asm {{
|
||||
ldx cx16.r0
|
||||
stx P8ZP_SCRATCH_W1 ; source in ZP
|
||||
ldx cx16.r0+1
|
||||
stx P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
stx P8ZP_SCRATCH_W2 ; target in ZP
|
||||
ldx cx16.r1+1
|
||||
stx P8ZP_SCRATCH_W2+1
|
||||
cpy #0
|
||||
bne _longcopy
|
||||
|
||||
; copy <= 255 bytes
|
||||
tay
|
||||
bne _copyshort
|
||||
rts ; nothing to copy
|
||||
|
||||
_copyshort
|
||||
dey
|
||||
beq +
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
dey
|
||||
bne -
|
||||
+ lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
rts
|
||||
|
||||
_longcopy
|
||||
sta P8ZP_SCRATCH_B1 ; lsb(count) = remainder in last page
|
||||
tya
|
||||
tax ; x = num pages (1+)
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
bne -
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
dex
|
||||
bne -
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
bne _copyshort
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
ldy cx16.r0
|
||||
sty P8ZP_SCRATCH_W1
|
||||
ldy cx16.r0+1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
ldy cx16.r1+1
|
||||
jmp prog8_lib.memset
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
ldx cx16.r0
|
||||
stx P8ZP_SCRATCH_W1
|
||||
ldx cx16.r0+1
|
||||
stx P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
stx P8ZP_SCRATCH_W2
|
||||
ldx cx16.r1+1
|
||||
stx P8ZP_SCRATCH_W2+1
|
||||
jmp prog8_lib.memsetw
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub read_flags() -> ubyte @A {
|
||||
%asm {{
|
||||
php
|
||||
pla
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_carry() {
|
||||
%asm {{
|
||||
clc
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_carry() {
|
||||
%asm {{
|
||||
sec
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_irqd() {
|
||||
%asm {{
|
||||
cli
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_irqd() {
|
||||
%asm {{
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub irqsafe_set_irqd() {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub irqsafe_clear_irqd() {
|
||||
%asm {{
|
||||
plp
|
||||
}}
|
||||
}
|
||||
|
||||
sub disable_caseswitch() {
|
||||
; no-op
|
||||
}
|
||||
|
||||
sub enable_caseswitch() {
|
||||
; no-op
|
||||
}
|
||||
|
||||
asmsub save_prog8_internals() {
|
||||
%asm {{
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta save_SCRATCH_ZPB1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta save_SCRATCH_ZPREG
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta save_SCRATCH_ZPWORD1
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta save_SCRATCH_ZPWORD1+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta save_SCRATCH_ZPWORD2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta save_SCRATCH_ZPWORD2+1
|
||||
rts
|
||||
save_SCRATCH_ZPB1 .byte 0
|
||||
save_SCRATCH_ZPREG .byte 0
|
||||
save_SCRATCH_ZPWORD1 .word 0
|
||||
save_SCRATCH_ZPWORD2 .word 0
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_prog8_internals() {
|
||||
%asm {{
|
||||
lda save_prog8_internals.save_SCRATCH_ZPB1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
sta cleanup_at_exit._exitcode
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
jmp cleanup_at_exit
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub exit2(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y) {
|
||||
; -- immediately exit the program with result values in the A, X and Y registers.
|
||||
%asm {{
|
||||
sta cleanup_at_exit._exitcode
|
||||
stx cleanup_at_exit._exitcodeX
|
||||
sty cleanup_at_exit._exitcodeY
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
jmp cleanup_at_exit
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub exit3(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y, bool carry @Pc) {
|
||||
; -- immediately exit the program with result values in the A, X and Y registers, and the Carry flag in the status register.
|
||||
%asm {{
|
||||
sta cleanup_at_exit._exitcode
|
||||
lda #0
|
||||
rol a
|
||||
sta cleanup_at_exit._exitcodeCarry
|
||||
stx cleanup_at_exit._exitcodeX
|
||||
sty cleanup_at_exit._exitcodeY
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
jmp cleanup_at_exit
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub progend() -> uword @AY {
|
||||
%asm {{
|
||||
lda #<prog8_program_end
|
||||
ldy #>prog8_program_end
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub progstart() -> uword @AY {
|
||||
%asm {{
|
||||
lda #<prog8_program_start
|
||||
ldy #>prog8_program_start
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub push(ubyte value @A) {
|
||||
%asm {{
|
||||
pha
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub pushw(uword value @AY) {
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
pha
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub pop() -> ubyte @A {
|
||||
%asm {{
|
||||
pla
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub popw() -> uword @AY {
|
||||
%asm {{
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cx16 {
|
||||
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
|
||||
; the sixteen virtual 16-bit registers in both normal unsigned mode and signed mode (s)
|
||||
&uword r0 = $0002
|
||||
&uword r1 = $0004
|
||||
&uword r2 = $0006
|
||||
&uword r3 = $0008
|
||||
&uword r4 = $000a
|
||||
&uword r5 = $000c
|
||||
&uword r6 = $000e
|
||||
&uword r7 = $0010
|
||||
&uword r8 = $0012
|
||||
&uword r9 = $0014
|
||||
&uword r10 = $0016
|
||||
&uword r11 = $0018
|
||||
&uword r12 = $001a
|
||||
&uword r13 = $001c
|
||||
&uword r14 = $001e
|
||||
&uword r15 = $0020
|
||||
|
||||
&word r0s = $0002
|
||||
&word r1s = $0004
|
||||
&word r2s = $0006
|
||||
&word r3s = $0008
|
||||
&word r4s = $000a
|
||||
&word r5s = $000c
|
||||
&word r6s = $000e
|
||||
&word r7s = $0010
|
||||
&word r8s = $0012
|
||||
&word r9s = $0014
|
||||
&word r10s = $0016
|
||||
&word r11s = $0018
|
||||
&word r12s = $001a
|
||||
&word r13s = $001c
|
||||
&word r14s = $001e
|
||||
&word r15s = $0020
|
||||
|
||||
&ubyte r0L = $0002
|
||||
&ubyte r1L = $0004
|
||||
&ubyte r2L = $0006
|
||||
&ubyte r3L = $0008
|
||||
&ubyte r4L = $000a
|
||||
&ubyte r5L = $000c
|
||||
&ubyte r6L = $000e
|
||||
&ubyte r7L = $0010
|
||||
&ubyte r8L = $0012
|
||||
&ubyte r9L = $0014
|
||||
&ubyte r10L = $0016
|
||||
&ubyte r11L = $0018
|
||||
&ubyte r12L = $001a
|
||||
&ubyte r13L = $001c
|
||||
&ubyte r14L = $001e
|
||||
&ubyte r15L = $0020
|
||||
|
||||
&ubyte r0H = $0003
|
||||
&ubyte r1H = $0005
|
||||
&ubyte r2H = $0007
|
||||
&ubyte r3H = $0009
|
||||
&ubyte r4H = $000b
|
||||
&ubyte r5H = $000d
|
||||
&ubyte r6H = $000f
|
||||
&ubyte r7H = $0011
|
||||
&ubyte r8H = $0013
|
||||
&ubyte r9H = $0015
|
||||
&ubyte r10H = $0017
|
||||
&ubyte r11H = $0019
|
||||
&ubyte r12H = $001b
|
||||
&ubyte r13H = $001d
|
||||
&ubyte r14H = $001f
|
||||
&ubyte r15H = $0021
|
||||
|
||||
&byte r0sL = $0002
|
||||
&byte r1sL = $0004
|
||||
&byte r2sL = $0006
|
||||
&byte r3sL = $0008
|
||||
&byte r4sL = $000a
|
||||
&byte r5sL = $000c
|
||||
&byte r6sL = $000e
|
||||
&byte r7sL = $0010
|
||||
&byte r8sL = $0012
|
||||
&byte r9sL = $0014
|
||||
&byte r10sL = $0016
|
||||
&byte r11sL = $0018
|
||||
&byte r12sL = $001a
|
||||
&byte r13sL = $001c
|
||||
&byte r14sL = $001e
|
||||
&byte r15sL = $0020
|
||||
|
||||
&byte r0sH = $0003
|
||||
&byte r1sH = $0005
|
||||
&byte r2sH = $0007
|
||||
&byte r3sH = $0009
|
||||
&byte r4sH = $000b
|
||||
&byte r5sH = $000d
|
||||
&byte r6sH = $000f
|
||||
&byte r7sH = $0011
|
||||
&byte r8sH = $0013
|
||||
&byte r9sH = $0015
|
||||
&byte r10sH = $0017
|
||||
&byte r11sH = $0019
|
||||
&byte r12sH = $001b
|
||||
&byte r13sH = $001d
|
||||
&byte r14sH = $001f
|
||||
&byte r15sH = $0021
|
||||
|
||||
asmsub save_virtual_registers() clobbers(A,Y) {
|
||||
%asm {{
|
||||
ldy #31
|
||||
- lda cx16.r0,y
|
||||
sta _cx16_vreg_storage,y
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
|
||||
_cx16_vreg_storage
|
||||
.word 0,0,0,0,0,0,0,0
|
||||
.word 0,0,0,0,0,0,0,0
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_virtual_registers() clobbers(A,Y) {
|
||||
%asm {{
|
||||
ldy #31
|
||||
- lda save_virtual_registers._cx16_vreg_storage,y
|
||||
sta cx16.r0,y
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub cpu_is_65816() -> bool {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
p8_sys_startup {
|
||||
; program startup and shutdown machinery. Needs to reside in normal system ram.
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
%asm {{
|
||||
sei
|
||||
cld
|
||||
clc
|
||||
; TODO reset screen mode etc etc?
|
||||
clv
|
||||
; TODO what about IRQ handler? cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
rts ; no phase 2 steps on the Neo6502
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
%asm {{
|
||||
_exitcodeCarry = *+1
|
||||
lda #0
|
||||
lsr a
|
||||
_exitcode = *+1
|
||||
lda #0 ; exit code possibly modified in exit()
|
||||
_exitcodeX = *+1
|
||||
ldx #0
|
||||
_exitcodeY = *+1
|
||||
ldy #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,15 +24,15 @@ cbm {
|
||||
const uword Screen = $8000 ; to have this as an array[40*25] the compiler would have to support array size > 255
|
||||
|
||||
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; define an input channel
|
||||
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; define an output channel
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; restore default devices
|
||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFD2 = CHROUT(ubyte character @ A) ; output a character
|
||||
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; check the STOP key (and some others in A) also see STOP2
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; get a character also see GETIN2
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; close all files
|
||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
extsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; define an input channel
|
||||
extsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; define an output channel
|
||||
extsub $FFCC = CLRCHN() clobbers(A,X) ; restore default devices
|
||||
extsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
extsub $FFD2 = CHROUT(ubyte character @ A) ; output a character
|
||||
extsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; check the STOP key (and some others in A) also see STOP2
|
||||
extsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; get a character also see GETIN2
|
||||
extsub $FFE7 = CLALL() clobbers(A,X) ; close all files
|
||||
extsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
|
||||
|
||||
inline asmsub STOP2() clobbers(X,A) -> bool @Pz {
|
||||
@@ -96,7 +96,7 @@ asmsub kbdbuf_clear() {
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
const ubyte target = 32 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16, 32=PET
|
||||
const ubyte target = 32 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
|
||||
|
||||
const ubyte sizeof_bool = 1
|
||||
const ubyte sizeof_byte = 1
|
||||
@@ -106,46 +106,6 @@ sys {
|
||||
const ubyte sizeof_float = 0 ; undefined, no floats supported
|
||||
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
; Uppercase charset is activated.
|
||||
%asm {{
|
||||
sei
|
||||
lda #142
|
||||
jsr cbm.CHROUT ; uppercase
|
||||
lda #147
|
||||
jsr cbm.CHROUT ; clear screen
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
cld
|
||||
clc
|
||||
clv
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
%asm {{
|
||||
_exitcodeCarry = *+1
|
||||
lda #0
|
||||
lsr a
|
||||
_exitcode = *+1
|
||||
lda #0 ; exit code possibly modified in exit()
|
||||
_exitcodeX = *+1
|
||||
ldx #0
|
||||
_exitcodeY = *+1
|
||||
ldy #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub reset_system() {
|
||||
; Soft-reset the system back to initial power-on Basic prompt.
|
||||
%asm {{
|
||||
@@ -279,6 +239,65 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcmp(uword address1 @R0, uword address2 @R1, uword size @AY) -> byte @A {
|
||||
; Compares two blocks of memory
|
||||
; Returns -1 (255), 0 or 1, meaning: block 1 sorts before, equal or after block 2.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_REG ; lsb(size)
|
||||
sty P8ZP_SCRATCH_B1 ; msb(size)
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r1
|
||||
ldy cx16.r1+1
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
|
||||
ldx P8ZP_SCRATCH_B1
|
||||
beq _no_msb_size
|
||||
|
||||
_loop_msb_size
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bcs +
|
||||
lda #-1
|
||||
rts
|
||||
+ beq +
|
||||
lda #1
|
||||
rts
|
||||
+ iny
|
||||
bne -
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
dec P8ZP_SCRATCH_B1 ; msb(size) -= 1
|
||||
dex
|
||||
bne _loop_msb_size
|
||||
|
||||
_no_msb_size
|
||||
lda P8ZP_SCRATCH_REG ; lsb(size)
|
||||
bne +
|
||||
rts
|
||||
|
||||
+ ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bcs +
|
||||
lda #-1
|
||||
rts
|
||||
+ beq +
|
||||
lda #1
|
||||
rts
|
||||
+ iny
|
||||
cpy P8ZP_SCRATCH_REG ; lsb(size)
|
||||
bne -
|
||||
|
||||
lda #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub read_flags() -> ubyte @A {
|
||||
%asm {{
|
||||
php
|
||||
@@ -350,6 +369,7 @@ save_SCRATCH_ZPB1 .byte 0
|
||||
save_SCRATCH_ZPREG .byte 0
|
||||
save_SCRATCH_ZPWORD1 .word 0
|
||||
save_SCRATCH_ZPWORD2 .word 0
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -374,37 +394,37 @@ save_SCRATCH_ZPWORD2 .word 0
|
||||
asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
sta cleanup_at_exit._exitcode
|
||||
sta p8_sys_startup.cleanup_at_exit._exitcode
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
jmp cleanup_at_exit
|
||||
jmp p8_sys_startup.cleanup_at_exit
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub exit2(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y) {
|
||||
; -- immediately exit the program with result values in the A, X and Y registers.
|
||||
%asm {{
|
||||
sta cleanup_at_exit._exitcode
|
||||
stx cleanup_at_exit._exitcodeX
|
||||
sty cleanup_at_exit._exitcodeY
|
||||
sta p8_sys_startup.cleanup_at_exit._exitcode
|
||||
stx p8_sys_startup.cleanup_at_exit._exitcodeX
|
||||
sty p8_sys_startup.cleanup_at_exit._exitcodeY
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
jmp cleanup_at_exit
|
||||
jmp p8_sys_startup.cleanup_at_exit
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub exit3(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y, bool carry @Pc) {
|
||||
; -- immediately exit the program with result values in the A, X and Y registers, and the Carry flag in the status register.
|
||||
%asm {{
|
||||
sta cleanup_at_exit._exitcode
|
||||
sta p8_sys_startup.cleanup_at_exit._exitcode
|
||||
lda #0
|
||||
rol a
|
||||
sta cleanup_at_exit._exitcodeCarry
|
||||
stx cleanup_at_exit._exitcodeX
|
||||
sty cleanup_at_exit._exitcodeY
|
||||
sta p8_sys_startup.cleanup_at_exit._exitcodeCarry
|
||||
stx p8_sys_startup.cleanup_at_exit._exitcodeX
|
||||
sty p8_sys_startup.cleanup_at_exit._exitcodeY
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
jmp cleanup_at_exit
|
||||
jmp p8_sys_startup.cleanup_at_exit
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -415,6 +435,13 @@ save_SCRATCH_ZPWORD2 .word 0
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub progstart() -> uword @AY {
|
||||
%asm {{
|
||||
lda #<prog8_program_start
|
||||
ldy #>prog8_program_start
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub push(ubyte value @A) {
|
||||
%asm {{
|
||||
pha
|
||||
@@ -564,6 +591,7 @@ cx16 {
|
||||
_cx16_vreg_storage
|
||||
.word 0,0,0,0,0,0,0,0
|
||||
.word 0,0,0,0,0,0,0,0
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -584,3 +612,48 @@ cx16 {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
p8_sys_startup {
|
||||
; program startup and shutdown machinery. Needs to reside in normal system ram.
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
; Uppercase charset is activated.
|
||||
%asm {{
|
||||
sei
|
||||
lda #142
|
||||
jsr cbm.CHROUT ; uppercase
|
||||
lda #147
|
||||
jsr cbm.CHROUT ; clear screen
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
cld
|
||||
clc
|
||||
clv
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
%asm {{
|
||||
_exitcodeCarry = *+1
|
||||
lda #0
|
||||
lsr a
|
||||
_exitcode = *+1
|
||||
lda #0 ; exit code possibly modified in exit()
|
||||
_exitcodeX = *+1
|
||||
ldx #0
|
||||
_exitcodeY = *+1
|
||||
ldy #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,7 +12,7 @@ txt {
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
|
||||
|
||||
sub clear_screen() {
|
||||
@@ -171,6 +171,7 @@ _mod sta $ffff ; modified
|
||||
rts
|
||||
|
||||
_screenrows .word cbm.Screen + range(0, 1000, 40)
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
prog8_math {
|
||||
%option no_symbol_prefixing
|
||||
|
||||
%asminclude "library:math.asm"
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
; C64/C128 disk drive I/O routines.
|
||||
; shared CBM (C64/C128) disk drive I/O routines.
|
||||
|
||||
%import textio
|
||||
%import conv
|
||||
@@ -6,7 +6,7 @@
|
||||
%import syslib
|
||||
|
||||
diskio {
|
||||
%option no_symbol_prefixing, ignore_unused
|
||||
%option merge, no_symbol_prefixing, ignore_unused
|
||||
|
||||
const ubyte READ_IO_CHANNEL=12
|
||||
const ubyte WRITE_IO_CHANNEL=13
|
||||
@@ -124,6 +124,7 @@ io_error:
|
||||
uword list_pattern
|
||||
uword list_blocks
|
||||
bool iteration_in_progress = false
|
||||
bool write_iteration_in_progress = false
|
||||
str list_filetype = "???" ; prg, seq, dir
|
||||
str list_filename = "?" * 50
|
||||
|
||||
@@ -377,6 +378,7 @@ _end jsr cbm.READST
|
||||
|
||||
sub f_close() {
|
||||
; -- end an iterative file loading session (close channels).
|
||||
; it is safe to call this multiple times, or when no file is open for reading.
|
||||
if iteration_in_progress {
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
@@ -399,8 +401,12 @@ _end jsr cbm.READST
|
||||
cbm.SETNAM(string.length(filenameptr), filenameptr)
|
||||
cbm.SETLFS(WRITE_IO_CHANNEL, drivenumber, 1)
|
||||
void cbm.OPEN() ; open 13,8,1,"filename"
|
||||
if_cc
|
||||
return cbm.READST()==0
|
||||
if_cc {
|
||||
if cbm.READST()==0 {
|
||||
write_iteration_in_progress = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
cbm.CLOSE(WRITE_IO_CHANNEL)
|
||||
f_close_w()
|
||||
return false
|
||||
@@ -422,8 +428,12 @@ _end jsr cbm.READST
|
||||
|
||||
sub f_close_w() {
|
||||
; -- end an iterative file writing session (close channels).
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(WRITE_IO_CHANNEL)
|
||||
; it is safe to call this multiple times, or when no file is open for reading.
|
||||
if write_iteration_in_progress {
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(WRITE_IO_CHANNEL)
|
||||
write_iteration_in_progress = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -590,6 +600,15 @@ io_error:
|
||||
cbm.CLOSE(1)
|
||||
}
|
||||
|
||||
sub exists(str filename) -> bool {
|
||||
; -- returns true if the given file exists on the disk, otherwise false
|
||||
if f_open(filename) {
|
||||
f_close()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
sub send_command(uword commandptr) {
|
||||
; -- send a dos command to the drive
|
||||
cbm.SETNAM(string.length(commandptr), commandptr)
|
||||
@@ -171,6 +171,7 @@ _allzero lda #'0'
|
||||
eor P8ZP_SCRATCH_REG
|
||||
rts
|
||||
_offsets .byte 128, 0, 64, 32, 64, 192, 128, 128
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -256,4 +256,18 @@ inline asmsub pop() -> float @FAC1 {
|
||||
}}
|
||||
}
|
||||
|
||||
sub lerp(float v0, float v1, float t) -> float {
|
||||
; Linear interpolation (LERP)
|
||||
; Precise method, which guarantees v = v1 when t = 1.
|
||||
; returns an interpolation between two inputs (v0, v1) for a parameter t in the closed unit interval [0, 1]
|
||||
return (1 - t) * v0 + t * v1
|
||||
}
|
||||
|
||||
sub lerp_fast(float v0, float v1, float t) -> float {
|
||||
; Linear interpolation (LERP)
|
||||
; Imprecise (but slightly faster) method, which does not guarantee v = v1 when t = 1
|
||||
; returns an interpolation between two inputs (v0, v1) for a parameter t in the closed unit interval [0, 1]
|
||||
return v0 + t * (v1 - v0)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -178,7 +178,7 @@ _found tya
|
||||
rts
|
||||
|
||||
_str .word 0
|
||||
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
; file I/O routines. (partially implemented)
|
||||
; File I/O routines for the VM target
|
||||
;
|
||||
; NOTE: not all is implemented.
|
||||
; NOTE: some calls are slightly different from the "official" diskio library because for example,
|
||||
; here we cannot deal with multiple return values.
|
||||
|
||||
%import textio
|
||||
%import syslib
|
||||
@@ -11,7 +15,7 @@ diskio {
|
||||
%ir {{
|
||||
loadm.w r65534,diskio.load.filenameptr
|
||||
loadm.w r65535,diskio.load.address_override
|
||||
syscall 48 (): r0.b
|
||||
syscall 45 (): r0.b
|
||||
returnr.b r0
|
||||
}}
|
||||
}
|
||||
@@ -59,37 +63,87 @@ diskio {
|
||||
; NOTE: the default input isn't yet set to this logical file, you must use reset_read_channel() to do this,
|
||||
; if you're going to read from it yourself instead of using f_read()!
|
||||
|
||||
txt.print("@TODO: f_open\n")
|
||||
return false
|
||||
%ir {{
|
||||
loadm.w r65535,diskio.f_open.filenameptr
|
||||
syscall 52 (r65535.w): r0.b
|
||||
returnr.b r0
|
||||
}}
|
||||
}
|
||||
|
||||
sub f_read(uword bufferpointer, uword num_bytes) -> uword {
|
||||
; -- read from the currently open file, up to the given number of bytes.
|
||||
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
|
||||
txt.print("@TODO: f_read\n")
|
||||
return 0
|
||||
uword actual
|
||||
repeat num_bytes {
|
||||
%ir {{
|
||||
syscall 54 (): r0.w
|
||||
storem.w r0,$ff02
|
||||
}}
|
||||
if cx16.r0H==0
|
||||
return actual
|
||||
@(bufferpointer) = cx16.r0L
|
||||
bufferpointer++
|
||||
actual++
|
||||
}
|
||||
return actual
|
||||
}
|
||||
|
||||
sub f_read_all(uword bufferpointer) -> uword {
|
||||
; -- read the full contents of the file, returns number of bytes read.
|
||||
; It is assumed the file size is less than 64 K.
|
||||
txt.print("@TODO: f_read_all\n")
|
||||
return 0
|
||||
uword actual
|
||||
repeat {
|
||||
%ir {{
|
||||
syscall 54 (): r0.w
|
||||
storem.w r0,$ff02
|
||||
}}
|
||||
if cx16.r0H==0
|
||||
return actual
|
||||
@(bufferpointer) = cx16.r0L
|
||||
bufferpointer++
|
||||
actual++
|
||||
}
|
||||
}
|
||||
|
||||
sub f_readline(uword bufptr) -> ubyte {
|
||||
; Routine to read text lines from a text file. Lines must be less than 255 characters.
|
||||
; Reads characters from the input file UNTIL a newline or return character (or EOF).
|
||||
; The line read will be 0-terminated in the buffer (and not contain the end of line character).
|
||||
; The length of the line is returned in Y. Note that an empty line is okay and is length 0!
|
||||
; This routine is not able here to return the status as well in a secondary return value, so you have to do that yourself.
|
||||
txt.print("@TODO: f_readline\n")
|
||||
return 0
|
||||
; The length of the line is returned. Note that an empty line is okay and is length 0!
|
||||
; The success status is returned in the Carry flag instead: C set = success, C clear = failure/endoffile
|
||||
ubyte size
|
||||
repeat {
|
||||
%ir {{
|
||||
syscall 54 (): r0.w
|
||||
storem.w r0,$ff02
|
||||
}}
|
||||
|
||||
if cx16.r0H==0 {
|
||||
sys.clear_carry()
|
||||
return size
|
||||
} else {
|
||||
if cx16.r0L == '\n' or cx16.r0L=='\r' {
|
||||
@(bufptr) = 0
|
||||
sys.set_carry()
|
||||
return size
|
||||
}
|
||||
@(bufptr) = cx16.r0L
|
||||
bufptr++
|
||||
size++
|
||||
if_z {
|
||||
@(bufptr) = 0
|
||||
return 255
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub f_close() {
|
||||
; -- end an iterative file loading session (close channels).
|
||||
txt.print("@TODO: f_close\n")
|
||||
; it is safe to call this multiple times, or when no file is open for reading.
|
||||
%ir {{
|
||||
syscall 56 ()
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,20 +156,36 @@ diskio {
|
||||
; (for example, if it already exists). This is different than f_open()!
|
||||
; To be 100% sure if this call was successful, you have to use status()
|
||||
; and check the drive's status message!
|
||||
txt.print("@TODO: f_open_w\n")
|
||||
return false
|
||||
%ir {{
|
||||
loadm.w r65535,diskio.f_open_w.filenameptr
|
||||
syscall 53 (r65535.w): r0.b
|
||||
returnr.b r0
|
||||
}}
|
||||
}
|
||||
|
||||
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
|
||||
; -- write the given number of bytes to the currently open file
|
||||
; you can call this multiple times to append more data
|
||||
txt.print("@TODO: f_write\n")
|
||||
return false
|
||||
repeat num_bytes {
|
||||
%ir {{
|
||||
loadm.w r0,diskio.f_write.bufferpointer
|
||||
loadi.b r1,r0
|
||||
syscall 55 (r1.b): r0.b
|
||||
storem.b r0,$ff02
|
||||
}}
|
||||
if cx16.r0L==0
|
||||
return false
|
||||
bufferpointer++
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
sub f_close_w() {
|
||||
; -- end an iterative file writing session (close channels).
|
||||
txt.print("@TODO: f_close_w\n")
|
||||
; it is safe to call this multiple times, or when no file is open for reading.
|
||||
%ir {{
|
||||
syscall 57 ()
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -123,23 +193,34 @@ diskio {
|
||||
|
||||
sub chdir(str path) {
|
||||
; -- change current directory.
|
||||
txt.print("@TODO: chdir\n")
|
||||
%ir {{
|
||||
loadm.w r65535,diskio.chdir.path
|
||||
syscall 50 (r65535.w)
|
||||
}}
|
||||
}
|
||||
|
||||
sub mkdir(str name) {
|
||||
; -- make a new subdirectory.
|
||||
txt.print("@TODO: mkdir\n")
|
||||
%ir {{
|
||||
loadm.w r65535,diskio.mkdir.name
|
||||
syscall 49 (r65535.w)
|
||||
}}
|
||||
}
|
||||
|
||||
sub rmdir(str name) {
|
||||
; -- remove a subdirectory.
|
||||
txt.print("@TODO: rmdir\n")
|
||||
%ir {{
|
||||
loadm.w r65535,diskio.rmdir.name
|
||||
syscall 51 (r65535.w)
|
||||
}}
|
||||
}
|
||||
|
||||
sub curdir() -> uword {
|
||||
; return current directory name or 0 if error
|
||||
txt.print("@TODO: curdir\n")
|
||||
return 0
|
||||
%ir {{
|
||||
syscall 48 (): r0.w
|
||||
returnr.w r0
|
||||
}}
|
||||
}
|
||||
|
||||
sub status() -> str {
|
||||
@@ -159,7 +240,7 @@ diskio {
|
||||
loadm.w r65533,diskio.save.filenameptr
|
||||
loadm.w r65534,diskio.save.start_address
|
||||
loadm.w r65535,diskio.save.savesize
|
||||
syscall 45 (r65532.b, r65533.w, r65534.w, r65535.w): r0.b
|
||||
syscall 42 (r65532.b, r65533.w, r65534.w, r65535.w): r0.b
|
||||
returnr.b r0
|
||||
}}
|
||||
}
|
||||
@@ -171,7 +252,7 @@ diskio {
|
||||
loadm.w r65533,diskio.save.filenameptr
|
||||
loadm.w r65534,diskio.save.start_address
|
||||
loadm.w r65535,diskio.save.savesize
|
||||
syscall 45 (r65532.b, r65533.w, r65534.w, r65535.w): r0.b
|
||||
syscall 42 (r65532.b, r65533.w, r65534.w, r65535.w): r0.b
|
||||
returnr.b r0
|
||||
}}
|
||||
}
|
||||
@@ -186,7 +267,7 @@ diskio {
|
||||
%ir {{
|
||||
loadm.w r65534,diskio.load.filenameptr
|
||||
loadm.w r65535,diskio.load.address_override
|
||||
syscall 43 (r65534.w, r65535.w): r0.w
|
||||
syscall 40 (r65534.w, r65535.w): r0.w
|
||||
returnr.w r0
|
||||
}}
|
||||
}
|
||||
@@ -198,7 +279,7 @@ diskio {
|
||||
%ir {{
|
||||
loadm.w r65534,diskio.load_raw.filenameptr
|
||||
loadm.w r65535,diskio.load_raw.start_address
|
||||
syscall 44 (r65534.w, r65535.w): r0.w
|
||||
syscall 41 (r65534.w, r65535.w): r0.w
|
||||
returnr.w r0
|
||||
}}
|
||||
}
|
||||
@@ -207,7 +288,7 @@ diskio {
|
||||
; -- delete a file on the drive
|
||||
%ir {{
|
||||
loadm.w r65535,diskio.delete.filenameptr
|
||||
syscall 46 (r65535.w)
|
||||
syscall 43 (r65535.w)
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -216,7 +297,17 @@ diskio {
|
||||
%ir {{
|
||||
loadm.w r65534,diskio.rename.oldfileptr
|
||||
loadm.w r65535,diskio.rename.newfileptr
|
||||
syscall 47 (r65534.w, r65535.w)
|
||||
syscall 44 (r65534.w, r65535.w)
|
||||
}}
|
||||
}
|
||||
|
||||
sub exists(str filename) -> bool {
|
||||
; -- returns true if the given file exists on the disk, otherwise false
|
||||
if f_open(filename) {
|
||||
f_close()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -192,7 +192,6 @@ sub push(float value) {
|
||||
%ir {{
|
||||
loadm.f fr65535,floats.push.value
|
||||
push.f fr65535
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -204,4 +203,18 @@ sub pop() -> float {
|
||||
}}
|
||||
}
|
||||
|
||||
sub lerp(float v0, float v1, float t) -> float {
|
||||
; Linear interpolation (LERP)
|
||||
; Precise method, which guarantees v = v1 when t = 1.
|
||||
; returns an interpolation between two inputs (v0, v1) for a parameter t in the closed unit interval [0, 1]
|
||||
return (1 - t) * v0 + t * v1
|
||||
}
|
||||
|
||||
sub lerp_fast(float v0, float v1, float t) -> float {
|
||||
; Linear interpolation (LERP)
|
||||
; Imprecise (but slightly faster) method, which does not guarantee v = v1 when t = 1
|
||||
; returns an interpolation between two inputs (v0, v1) for a parameter t in the closed unit interval [0, 1]
|
||||
return v0 + t * (v1 - v0)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -398,4 +398,23 @@ math {
|
||||
cx16.r15 ^= $ffff
|
||||
cx16.r14 ^= $ffff
|
||||
}
|
||||
|
||||
|
||||
sub lerp(ubyte v0, ubyte v1, ubyte t) -> ubyte {
|
||||
; Linear interpolation (LERP)
|
||||
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 255]
|
||||
; guarantees v = v1 when t = 255
|
||||
return v0 + msb(t as uword * (v1 - v0) + 255)
|
||||
}
|
||||
|
||||
sub lerpw(uword v0, uword v1, uword t) -> uword {
|
||||
; Linear interpolation (LERP) on word values
|
||||
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 65535]
|
||||
; guarantees v = v1 when t = 65535
|
||||
t *= v1-v0
|
||||
cx16.r0 = math.mul16_last_upper()
|
||||
if t!=0
|
||||
cx16.r0++
|
||||
return v0 + cx16.r0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
; for the VM this is just empty, everything math is in math.p8
|
||||
Loaded 100 of 252 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user