1
0
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:
Karol Stasiak 2018-07-02 00:31:47 +02:00
parent 62e94d96f7
commit 9512e8e7ae
12 changed files with 218 additions and 9 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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