mirror of
https://github.com/KarolS/millfork.git
synced 2024-12-22 16:31:02 +00:00
Really early and very incomplete PC-88 support
This commit is contained in:
parent
62e94d96f7
commit
9512e8e7ae
@ -2,6 +2,10 @@
|
||||
|
||||
## Current version
|
||||
|
||||
* A very incomplete support for the Z80 microprocessor.
|
||||
|
||||
* A very incomplete support for NEC PC-88.
|
||||
|
||||
## 0.3.0
|
||||
|
||||
* Finally faster than C.
|
||||
|
@ -59,6 +59,8 @@ Therefore, no attribution is needed if you are developing and distributing Millf
|
||||
|
||||
## Planned features
|
||||
|
||||
* more targets: Oric computers, PC-Engine/Turbografx-16, Atari Lynx
|
||||
* Z80 support, targetting PC-88, ZX Spectrum, later also maybe Armstrad CPC, MSX, TI-83
|
||||
|
||||
* more 6502 targets: Oric computers, PC-Engine/Turbografx-16, Atari Lynx
|
||||
|
||||
* support for 65816, SuperFamicom/SNES and Apple IIgs
|
||||
|
@ -57,12 +57,12 @@ Default: native if targeting 65816, no otherwise.
|
||||
|
||||
* `-fjmp-fix`, `-fno-jmp-fix` – Whether should prevent indirect JMP bug on page boundary.
|
||||
`.ini` equivalent: `prevent_jmp_indirect_bug`.
|
||||
Default: no if targeting a 65C02-compatible architecture, yes otherwise.
|
||||
Default: no if targeting a 65C02-compatible architecture or a non-6502 architecture, yes otherwise.
|
||||
|
||||
* `-fzp-register`, `-fno-zp-register` – Whether should reserve 2 bytes of zero page as a pseudoregister.
|
||||
Increases language features.
|
||||
`.ini` equivalent: `zeropage_register`.
|
||||
Default: yes.
|
||||
Default: yes if targeting a 6502-based architecture, no otherwise.
|
||||
|
||||
* `-fdecimal-mode`, `-fno-decimal-mode` – Whether decimal mode should be available.
|
||||
`.ini` equivalent: `decimal_mode`.
|
||||
|
@ -45,6 +45,8 @@ Read [the NES programming guide](./famicom-programming-guide.md) for more info.
|
||||
|
||||
* `apple2` – Apple II+/IIe/Enhanced IIe
|
||||
|
||||
* `pc88` – NEC PC-88 (very incomplete and not usable for anything yet)
|
||||
|
||||
The primary and most tested platform is Commodore 64.
|
||||
|
||||
Currently, targets that assume that the program will be loaded from disk or tape are better tested.
|
||||
@ -115,6 +117,8 @@ Every platform is defined in an `.ini` file with an appropriate name.
|
||||
* `huc6280` (Hudson HuC6280; experimental)
|
||||
|
||||
* `65816` (WDC 65816/65802; experimental; currently only programs that use only 16-bit addressing are supported)
|
||||
|
||||
* `z80` (Zilog Z80; experimental and very incomplete)
|
||||
|
||||
* `modules` – comma-separated list of modules that will be automatically imported
|
||||
|
||||
@ -141,7 +145,10 @@ Every platform is defined in an `.ini` file with an appropriate name.
|
||||
|
||||
#### `[allocation]` section
|
||||
|
||||
* `zp_pointers` – either a list of comma separated zeropage addresses that can be used by the program as zeropage pointers, or `all` for all. Each value should be the address of the first of two free bytes in the zeropage.
|
||||
* `zp_pointers` –
|
||||
either a list of comma separated zeropage addresses that can be used by the program as zeropage pointers, or `all` for all.
|
||||
Each value should be the address of the first of two free bytes in the zeropage.
|
||||
Only used for 6502-based targets.
|
||||
|
||||
* `segments` – a comma-separated list of segment names.
|
||||
A segment named `default` is always required.
|
||||
@ -192,6 +199,8 @@ Default: `after_code`.
|
||||
|
||||
* `<segment>:<addr>:<addr>` - inclusive range of bytes in a given segment
|
||||
|
||||
* `d88` - a D88 floppy disk image for PC-88
|
||||
|
||||
* `extension` – target file extension, with or without the dot
|
||||
|
||||
* `bbc_inf` – should the `.inf` file with file metadata for BBC Micro be created
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
* [Functions](lang/functions.md)
|
||||
|
||||
* [Inline assembly syntax](lang/assembly.md)
|
||||
* [Inline 6502 assembly syntax](lang/assembly.md)
|
||||
|
||||
* [Important guidelines regarding reentrancy](lang/reentrancy.md)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[< back to index](../index.md)
|
||||
|
||||
# Using assembly within Millfork programs
|
||||
# Using 6502 assembly within Millfork programs
|
||||
|
||||
There are two ways to include raw assembly code in your Millfork programs:
|
||||
|
||||
|
18
include/pc88.ini
Normal file
18
include/pc88.ini
Normal file
@ -0,0 +1,18 @@
|
||||
;a single-load PC-88 program
|
||||
[compilation]
|
||||
arch=z80
|
||||
modules=default_panic
|
||||
|
||||
[allocation]
|
||||
; TODO: find a more optimal start address
|
||||
segment_default_start=$9000
|
||||
segment_default_codeend=$bfff
|
||||
segment_default_datastart=after_code
|
||||
segment_default_end=$efff
|
||||
|
||||
[output]
|
||||
style=single
|
||||
format=d88
|
||||
extension=d88
|
||||
|
||||
|
@ -192,6 +192,7 @@ object Cpu extends Enumeration {
|
||||
case "strictricoh" => StrictRicoh
|
||||
case "strict2a03" => StrictRicoh
|
||||
case "strict2a07" => StrictRicoh
|
||||
case "z80" => Z80
|
||||
case _ => ErrorReporting.fatal("Unknown CPU achitecture: " + name)
|
||||
}
|
||||
}
|
||||
|
@ -6,14 +6,15 @@ import java.util.Locale
|
||||
|
||||
import millfork.assembly.mos.AssemblyLine
|
||||
import millfork.assembly.mos.opt._
|
||||
import millfork.assembly.z80.opt.Z80OptimizationPresets
|
||||
import millfork.buildinfo.BuildInfo
|
||||
import millfork.cli.{CliParser, CliStatus}
|
||||
import millfork.compiler.mos.MosCompiler
|
||||
import millfork.env.Environment
|
||||
import millfork.error.ErrorReporting
|
||||
import millfork.node.StandardCallGraph
|
||||
import millfork.output.{AbstractAssembler, AssemblerOutput, MosAssembler, MosInliningCalculator}
|
||||
import millfork.parser.MosSourceLoadingQueue
|
||||
import millfork.output._
|
||||
import millfork.parser.{MosSourceLoadingQueue, ZSourceLoadingQueue}
|
||||
|
||||
/**
|
||||
* @author Karol Stasiak
|
||||
@ -94,6 +95,7 @@ object Main {
|
||||
|
||||
val result: AssemblerOutput = CpuFamily.forType(platform.cpu) match {
|
||||
case CpuFamily.M6502 => assembleForMos(c, platform, options)
|
||||
case CpuFamily.I80 => assembleForI80(c, platform, options)
|
||||
}
|
||||
|
||||
if (c.outputAssembly) {
|
||||
@ -190,6 +192,39 @@ object Main {
|
||||
result
|
||||
}
|
||||
|
||||
private def assembleForI80(c: Context, platform: Platform, options: CompilationOptions): AssemblerOutput = {
|
||||
val optLevel = c.optimizationLevel.getOrElse(0)
|
||||
val unoptimized = new ZSourceLoadingQueue(
|
||||
initialFilenames = c.inputFileNames,
|
||||
includePath = c.includePath,
|
||||
options = options).run()
|
||||
|
||||
val program = if (optLevel > 0) {
|
||||
OptimizationPresets.NodeOpt.foldLeft(unoptimized)((p, opt) => p.applyNodeOptimization(opt, options))
|
||||
} else {
|
||||
unoptimized
|
||||
}
|
||||
val callGraph = new StandardCallGraph(program)
|
||||
|
||||
val env = new Environment(None, "")
|
||||
env.collectDeclarations(program, options)
|
||||
|
||||
val assemblyOptimizations = optLevel match {
|
||||
case 0 => Nil
|
||||
case _ => Z80OptimizationPresets.Good
|
||||
}
|
||||
|
||||
// compile
|
||||
val assembler = new Z80Assembler(program, env, platform)
|
||||
val result = assembler.assemble(callGraph, assemblyOptimizations, options)
|
||||
ErrorReporting.assertNoErrors("Codegen failed")
|
||||
ErrorReporting.debug(f"Unoptimized code size: ${assembler.unoptimizedCodeSize}%5d B")
|
||||
ErrorReporting.debug(f"Optimized code size: ${assembler.optimizedCodeSize}%5d B")
|
||||
ErrorReporting.debug(f"Gain: ${(100L * (assembler.unoptimizedCodeSize - assembler.optimizedCodeSize) / assembler.unoptimizedCodeSize.toDouble).round}%5d%%")
|
||||
ErrorReporting.debug(f"Initialized variables: ${assembler.initializedVariablesSize}%5d B")
|
||||
result
|
||||
}
|
||||
|
||||
private def parser = new CliParser[Context] {
|
||||
|
||||
fluff("Main options:", "")
|
||||
|
@ -143,7 +143,7 @@ object Platform {
|
||||
|
||||
val codeAllocators = banks.map(b => b -> new UpwardByteAllocator(bankStarts(b), bankCodeEnds(b)))
|
||||
val variableAllocators = banks.map(b => b -> new VariableAllocator(
|
||||
if (b == "default") freePointers else Nil, bankDataStarts(b) match {
|
||||
if (b == "default" && CpuFamily.forType(cpu) == CpuFamily.M6502) freePointers else Nil, bankDataStarts(b) match {
|
||||
case None => new AfterCodeByteAllocator(bankEnds(b))
|
||||
case Some(start) => new UpwardByteAllocator(start, bankEnds(b))
|
||||
}))
|
||||
@ -155,6 +155,7 @@ object Platform {
|
||||
case "endaddr" => EndAddressOutput
|
||||
case "pagecount" => PageCountOutput
|
||||
case "allocated" => AllocatedDataOutput
|
||||
case "d88" => D88Output
|
||||
case n => n.split(":").filter(_.nonEmpty) match {
|
||||
case Array(b, s, e) => BankFragmentOutput(b, parseNumber(s), parseNumber(e))
|
||||
case Array(s, e) => CurrentBankFragmentOutput(parseNumber(s), parseNumber(e))
|
||||
|
119
src/main/scala/millfork/output/D88Output.scala
Normal file
119
src/main/scala/millfork/output/D88Output.scala
Normal file
@ -0,0 +1,119 @@
|
||||
package millfork.output
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
/**
|
||||
* @author Karol Stasiak
|
||||
*/
|
||||
object D88Output extends OutputPackager {
|
||||
|
||||
private def bootloader(targetAddress: Int, sectorCount: Int): Array[Byte] = Array(
|
||||
0x21, targetAddress, targetAddress >> 8, 0x01, 0x00, sectorCount, 0x11, 0x02, 0x00, 0xCD, 0x0F, 0xC0, 0xC3, targetAddress, targetAddress >> 8, 0x3E,
|
||||
0x02, 0xCD, 0x41, 0xC0, 0x3E, 0x11, 0x93, 0xB8, 0x38, 0x01, 0x78, 0xCD, 0x52, 0xC0, 0x79, 0xCD,
|
||||
0x52, 0xC0, 0x7A, 0xCD, 0x52, 0xC0, 0x7B, 0xCD, 0x52, 0xC0, 0x3E, 0x12, 0xCD, 0x41, 0xC0, 0xCD,
|
||||
0x71, 0xC0, 0x1C, 0x7B, 0xFE, 0x11, 0x28, 0x03, 0x10, 0xF5, 0xC9, 0x14, 0x1E, 0x01, 0x10, 0xCF,
|
||||
0xC9, 0xF5, 0x3E, 0x0F, 0xD3, 0xFF, 0xDB, 0xFE, 0xE6, 0x02, 0x28, 0xFA, 0x3E, 0x0E, 0xD3, 0xFF,
|
||||
0x18, 0x07, 0xF5, 0xDB, 0xFE, 0xE6, 0x02, 0x28, 0xFA, 0xF1, 0xD3, 0xFD, 0x3E, 0x09, 0xD3, 0xFF,
|
||||
0xDB, 0xFE, 0xE6, 0x04, 0x28, 0xFA, 0x3E, 0x08, 0xD3, 0xFF, 0xDB, 0xFE, 0xE6, 0x04, 0x20, 0xFA,
|
||||
0xC9, 0xC5, 0xD5, 0x01, 0xFC, 0x00, 0x11, 0x0C, 0x0A, 0x3E, 0x0B, 0xD3, 0xFF, 0xDB, 0xFE, 0x0F,
|
||||
0x30, 0xFB, 0x7A, 0xD3, 0xFF, 0xED, 0xA2, 0x3E, 0x0D, 0xD3, 0xFF, 0xDB, 0xFE, 0x0F, 0x38, 0xFB,
|
||||
0xED, 0xA2, 0x7B, 0xD3, 0xFF, 0xC2, 0x79, 0xC0, 0xD1, 0xC1, 0xC9, 0xC9
|
||||
).map(_.toByte)
|
||||
|
||||
|
||||
override def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = {
|
||||
val b = mem.banks(bank)
|
||||
val start = b.start
|
||||
val header = new D88Header
|
||||
val trackList = new D88TrackList
|
||||
val sectors = mutable.ListBuffer[D88Sector]()
|
||||
var trackOffset = 688
|
||||
var cylinder = 0
|
||||
var head = 0
|
||||
var sector = 1
|
||||
var trackCount = 0
|
||||
|
||||
@inline def addSector(data: Array[Byte]): Unit = {
|
||||
val s = D88Sector(cylinder, head, sector, data)
|
||||
if (sector == 1) {
|
||||
trackList.offsets(trackCount) = trackOffset
|
||||
trackCount += 1
|
||||
}
|
||||
sectors += s
|
||||
trackOffset += s.length
|
||||
sector += 1
|
||||
if (sector == 17) {
|
||||
sector = 1
|
||||
head += 1
|
||||
if (head == 2) {
|
||||
cylinder += 1
|
||||
head = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val size = b.end + 1 - b.start
|
||||
val sizeInPages = size.|(0xff).+(1).>>(8)
|
||||
addSector(bootloader(b.start, sizeInPages))
|
||||
for (page <- 0 until sizeInPages) {
|
||||
addSector(b.output.slice(b.start + (page << 8), b.start + (page << 8) + 0x100))
|
||||
}
|
||||
header.totalSize = trackOffset
|
||||
sectors.map(_.toArray).foldLeft(header.toArray ++ trackList.toArray)(_ ++ _)
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait D88Part {
|
||||
def toArray: Array[Byte]
|
||||
}
|
||||
|
||||
class D88Header extends D88Part {
|
||||
var totalSize: Long = 0
|
||||
|
||||
def toArray: Array[Byte] = {
|
||||
Array(
|
||||
'M', 'I', 'L', 'L', 'F', 'O', 'R', 'K',
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, // NUL
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, // reserved
|
||||
0, // not write protected
|
||||
0, // 2D
|
||||
totalSize,
|
||||
totalSize >> 8,
|
||||
totalSize >> 16,
|
||||
totalSize >> 24
|
||||
).map(_.toByte)
|
||||
}
|
||||
}
|
||||
|
||||
class D88TrackList extends D88Part {
|
||||
val offsets: Array[Int] = Array.fill[Int](164)(0)
|
||||
|
||||
def toArray: Array[Byte] = {
|
||||
offsets.flatMap(i => Array[Byte](
|
||||
i.toByte,
|
||||
i.>>(8).toByte,
|
||||
i.>>(16).toByte,
|
||||
i.>>(24).toByte
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
case class D88Sector(cylinder: Int, head: Int, sector: Int, data: Array[Byte]) extends D88Part {
|
||||
def length: Int = 16 + 256
|
||||
|
||||
override def toArray: Array[Byte] = {
|
||||
val header = Array(
|
||||
cylinder, head, sector,
|
||||
1, // 256B
|
||||
16, 0, // sectors per track
|
||||
0, // double density
|
||||
0, // not deleted
|
||||
0, // no crc error
|
||||
0, 0, 0, 0, 0, // reserved
|
||||
0, 1 // data size
|
||||
).map(_.toByte)
|
||||
val padding = Array.fill[Byte]((256 - data.length.&(0xff)).&(0xff))(-1)
|
||||
header ++ data ++ padding
|
||||
}
|
||||
}
|
20
src/main/scala/millfork/parser/ZSourceLoadingQueue.scala
Normal file
20
src/main/scala/millfork/parser/ZSourceLoadingQueue.scala
Normal file
@ -0,0 +1,20 @@
|
||||
package millfork.parser
|
||||
|
||||
import millfork.assembly.mos.AssemblyLine
|
||||
import millfork.assembly.z80.ZLine
|
||||
import millfork.{CompilationFlag, CompilationOptions}
|
||||
|
||||
/**
|
||||
* @author Karol Stasiak
|
||||
*/
|
||||
class ZSourceLoadingQueue(initialFilenames: List[String],
|
||||
includePath: List[String],
|
||||
options: CompilationOptions) extends AbstractSourceLoadingQueue[ZLine](initialFilenames, includePath, options) {
|
||||
|
||||
override def createParser(filename: String, src: String, parentDir: String): MfParser[ZLine] = Z80Parser(filename, src, parentDir, options)
|
||||
|
||||
def enqueueStandardModules(): Unit = {
|
||||
// TODO
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user