From 7aaf4a7b21968f5fb5678cf4a2078ccbb24978e3 Mon Sep 17 00:00:00 2001 From: MichailKaa Date: Thu, 19 Feb 2026 23:32:44 +0300 Subject: [PATCH] ext version batty start, but not work --- .gitignore | 11 + Content/Batty/batty.tap | Bin 0 -> 30142 bytes Content/Batty/debuild.sh | 24 + Content/Makefile | 19 + Content/main.asm | 68 +++ Content/ram_part.asm | 20 + Content/tools/zx0/LICENSE | 29 ++ Content/tools/zx0/Makefile | 24 + Content/tools/zx0/README.md | 476 ++++++++++++++++++ Content/tools/zx0/src/compress.c | 164 ++++++ Content/tools/zx0/src/dzx0.c | 226 +++++++++ Content/tools/zx0/src/memory.c | 75 +++ Content/tools/zx0/src/optimize.c | 138 +++++ Content/tools/zx0/src/zx0.c | 177 +++++++ Content/tools/zx0/src/zx0.h | 46 ++ .../zx0/z80/OLD_V1/dzx0_fast_CLASSIC.asm | 250 +++++++++ .../zx0/z80/OLD_V1/dzx0_mega_CLASSIC.asm | 464 +++++++++++++++++ .../zx0/z80/OLD_V1/dzx0_standard_CLASSIC.asm | 63 +++ .../zx0/z80/OLD_V1/dzx0_turbo_CLASSIC.asm | 103 ++++ Content/tools/zx0/z80/dzx0_fast.asm | 235 +++++++++ Content/tools/zx0/z80/dzx0_mega.asm | 452 +++++++++++++++++ Content/tools/zx0/z80/dzx0_mega_back.asm | 459 +++++++++++++++++ Content/tools/zx0/z80/dzx0_standard.asm | 61 +++ Content/tools/zx0/z80/dzx0_standard_back.asm | 62 +++ Content/tools/zx0/z80/dzx0_turbo.asm | 100 ++++ Content/tools/zx0/z80/dzx0_turbo_back.asm | 99 ++++ FW/src/makefile | 2 +- FW/src/zx_cartridge.v | 142 ++++++ FW/src/zx_cartridge_tb.v | 263 ++++++++++ FW/src/zx_cartrige.v | 62 --- FW/src/zx_cartrige_tb.v | 196 -------- FW/zx_cartrige.qsf | 21 +- HW/src/main.SchDoc | Bin 416256 -> 419840 bytes HW/src/pcb.PcbDoc | Bin 4119552 -> 4120576 bytes 34 files changed, 4266 insertions(+), 265 deletions(-) create mode 100644 Content/Batty/batty.tap create mode 100644 Content/Batty/debuild.sh create mode 100644 Content/Makefile create mode 100644 Content/main.asm create mode 100644 Content/ram_part.asm create mode 100644 Content/tools/zx0/LICENSE create mode 100644 Content/tools/zx0/Makefile create mode 100644 Content/tools/zx0/README.md create mode 100644 Content/tools/zx0/src/compress.c create mode 100644 Content/tools/zx0/src/dzx0.c create mode 100644 Content/tools/zx0/src/memory.c create mode 100644 Content/tools/zx0/src/optimize.c create mode 100644 Content/tools/zx0/src/zx0.c create mode 100644 Content/tools/zx0/src/zx0.h create mode 100644 Content/tools/zx0/z80/OLD_V1/dzx0_fast_CLASSIC.asm create mode 100644 Content/tools/zx0/z80/OLD_V1/dzx0_mega_CLASSIC.asm create mode 100644 Content/tools/zx0/z80/OLD_V1/dzx0_standard_CLASSIC.asm create mode 100644 Content/tools/zx0/z80/OLD_V1/dzx0_turbo_CLASSIC.asm create mode 100644 Content/tools/zx0/z80/dzx0_fast.asm create mode 100644 Content/tools/zx0/z80/dzx0_mega.asm create mode 100644 Content/tools/zx0/z80/dzx0_mega_back.asm create mode 100644 Content/tools/zx0/z80/dzx0_standard.asm create mode 100644 Content/tools/zx0/z80/dzx0_standard_back.asm create mode 100644 Content/tools/zx0/z80/dzx0_turbo.asm create mode 100644 Content/tools/zx0/z80/dzx0_turbo_back.asm create mode 100644 FW/src/zx_cartridge.v create mode 100644 FW/src/zx_cartridge_tb.v delete mode 100644 FW/src/zx_cartrige.v delete mode 100644 FW/src/zx_cartrige_tb.v diff --git a/.gitignore b/.gitignore index ed8e30a..a623530 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,14 @@ /HW/Project Logs for fix/*.LOG /HW/Project Logs for zx_cartridge/*.LOG /FW/zx_cartrige_description.txt +/Content/build +/Content/tools/zx0/build +/Content/Batty/batty.sna +/Content/Batty/loader.asm +/Content/Batty/loader0.bin +/Content/Batty/loader1.bin +/Content/Batty/main.asm +/Content/Batty/main.bin +/Content/Batty/main.bin.zx0 +/Content/Batty/screen.scr +/Content/Batty/screen.scr.zx0 diff --git a/Content/Batty/batty.tap b/Content/Batty/batty.tap new file mode 100644 index 0000000000000000000000000000000000000000..67318abfa959745d61ab053c99af34ef1474c033 GIT binary patch literal 30142 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 BH|hWY literal 0 HcmV?d00001 diff --git a/Content/Batty/debuild.sh b/Content/Batty/debuild.sh new file mode 100644 index 0000000..5c5d80b --- /dev/null +++ b/Content/Batty/debuild.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# скрипт извлечения ресурсов из TAP файла с помощью tzxlist + +TAP_FILE="batty.tap" + +echo "Извлекаем блоки из $TAP_FILE..." + +# Извлекаем каждый блок в отдельный файл +tzxlist -d 0 "$TAP_FILE" +tzxlist -d 1 "$TAP_FILE" +tzxlist -d 2 "$TAP_FILE" +tzxlist -d 3 "$TAP_FILE" + +# Переименовываем в понятные имена +mv 00000000.dat loader0.bin # загрузчик +mv 00000001.dat loader1.bin # загрузчик +mv 00000002.dat screen.scr # картинка +mv 00000003.dat main.bin # код игры +rm *.dsc +rm *.hdr + +# Показываем результат +ls -la \ No newline at end of file diff --git a/Content/Makefile b/Content/Makefile new file mode 100644 index 0000000..ab0100f --- /dev/null +++ b/Content/Makefile @@ -0,0 +1,19 @@ +SJASMPLUS=sjasmplus + +build: clean + mkdir -p build + @${SJASMPLUS} ./ram_part.asm --syntax=F + @${SJASMPLUS} ./main.asm --syntax=F + +clean: +# @rm -f -r ./assets/*.zx0 + @rm -f -r build + + +# ./tools/zx0/build/zx0 ./assets/Spec.scr +# ./tools/zx0/build/zx0 ./assets/Arsen.scr +# ./tools/zx0/build/zx0 ./assets/Jura.scr +# tape2wav ./build/GBX2601.tap ./build/GBX2601.wav +# run: +# fuse --machine 128 -g 3x --tape ./build/GBX2601.tap +# assets: diff --git a/Content/main.asm b/Content/main.asm new file mode 100644 index 0000000..246f530 --- /dev/null +++ b/Content/main.asm @@ -0,0 +1,68 @@ +; 19 Feb 2026 +; Михаил Каа + + DEVICE ZXSPECTRUM48 + ORG 0 + +start: + di + ld sp, 0xffff + jp main + + ORG 100 +main: + ld bc, 0xdf7f + ld a, 0b00000001 + out (c), a + + ld hl, batty_scr + ld de, 0x4000 + call dzx0_standard + + ld hl, batty_bin + ld de, 0x6800 + call dzx0_standard + + ld hl, ram_part + ld de, 0x6700 + ld bc, ram_part_end - ram_part + ldir + + ld bc, 65535 + call delay + ld bc, 65535 + call delay + ld bc, 65535 + call delay + ld bc, 65535 + call delay + + jp 0x6700 + + jp main + +; Процедура задержки +; bc - время +delay: + dec bc + ld a, b + or c + jr nz, delay + ret + + INCLUDE "./tools/zx0/z80/dzx0_standard.asm" +batty_scr: + INCBIN "./Batty/screen.scr.zx0" +batty_bin: + INCBIN "./Batty/main.bin.zx0" +ram_part: + INCBIN "./build/ram_part_6700.bin" +ram_part_end + +end: + ; Выводим размер бинарника. + display "Cartridge BIOS code size: ", /d, end - start + display "Cartridge BIOS code start: ", /d, start + display "Cartridge BIOS code end: ", /d, end + + SAVEBIN "build/bios_0000.bin", start, 16384 diff --git a/Content/ram_part.asm b/Content/ram_part.asm new file mode 100644 index 0000000..45a8c78 --- /dev/null +++ b/Content/ram_part.asm @@ -0,0 +1,20 @@ +; 19 Feb 2026 +; Михаил Каа + + DEVICE ZXSPECTRUM48 + ORG 0x6700 + +start: + ld bc, 0xbf7f + ld a, 0b10000000 + out (c), a + + jp 0x6800 + +end: + ; Выводим размер бинарника. + display "ram_part code size: ", /d, end - start + display "ram_part code start: ", /d, start + display "ram_part code end: ", /d, end + + SAVEBIN "./build/ram_part_6700.bin", start, end - start diff --git a/Content/tools/zx0/LICENSE b/Content/tools/zx0/LICENSE new file mode 100644 index 0000000..a0b5162 --- /dev/null +++ b/Content/tools/zx0/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2021, Einar Saukas +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Content/tools/zx0/Makefile b/Content/tools/zx0/Makefile new file mode 100644 index 0000000..bb4bb65 --- /dev/null +++ b/Content/tools/zx0/Makefile @@ -0,0 +1,24 @@ +# We only allow compilation on linux! +ifneq ($(shell uname), Linux) +$(error OS must be WSL or Linux!) +endif + +CC = gcc +#CFLAGS = -ox -ob -ol+ -onatx -oh -zp8 -g0 -Ofast -oe -ot -Wall -xc -s -finline-functions -floop-optimize -fno-stack-check -march=i386 -mtune=i686 +CFLAGS = -Wall +RM = rm + +all: build_dir zx0 dzx0 + +build_dir: + mkdir -p build + +zx0: + $(CC) $(CFLAGS) -o build/zx0 src/zx0.c src/optimize.c src/compress.c src/memory.c + +dzx0: + $(CC) $(CFLAGS) -o build/dzx0 src/dzx0.c + +clean: + # Remove everything except source files + rm -r -f build diff --git a/Content/tools/zx0/README.md b/Content/tools/zx0/README.md new file mode 100644 index 0000000..6f36c0e --- /dev/null +++ b/Content/tools/zx0/README.md @@ -0,0 +1,476 @@ +# ZX0 + +**ZX0** is an optimal data compressor for a custom +[LZ77/LZSS](https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Storer%E2%80%93Szymanski) +based compression format, that provides a tradeoff between high compression +ratio, and extremely simple fast decompression. Therefore it's especially +appropriate for low-end platforms, including 8-bit computers like the ZX +Spectrum. + +A comparison with other compressors (courtesy of **introspec/spke**) can be seen +[here](https://www.cpcwiki.eu/forum/programming/new-cruncher-zx0/msg197727/#msg197727). + + +_**WARNING**: The ZX0 file format was changed in version 2. This new format allows +decompressors to be slightly smaller and run slightly faster. If you need to compress +a file to the old "classic" file format from version 1, then execute ZX0 compressor +using parameter "-c"._ + + +## Usage + +To compress a file, use the command-line compressor as follows: + +``` +zx0 Cobra.scr +``` + +This will generate a compressed file called "Cobra.scr.zx0". + +Afterwards you can choose a decompressor routine in assembly Z80, according to +your requirements for speed and size: + +* "Standard" routine: 68 bytes only +* "Turbo" routine: 126 bytes, about 21% faster +* "Fast" routine: 187 bytes, about 25% faster +* "Mega" routine: 673 bytes, about 28% faster + +Finally compile the chosen decompressor routine and load the compressed file +somewhere in memory. To decompress data, just call the routine specifying the +source address of compressed data in HL and the target address in DE. + +For instance, if you compile the decompressor routine to address 65000, load +"Cobra.scr.zx0" at address 51200, and you want to decompress it directly to the +screen, then execute the following code: + +``` + LD HL, 51200 ; source address (put "Cobra.scr.zx0" there) + LD DE, 16384 ; target address (screen memory in this case) + CALL 65000 ; decompress routine compiled at this address +``` + +It's also possible to decompress data into a memory area that partially overlaps +the compressed data itself (only if you won't need to decompress it again later, +obviously). In this case, the last address of compressed data must be at least +"delta" bytes higher than the last address of decompressed data. The exact value +of "delta" for each case is reported by **ZX0** during compression. See image +below: + +``` + |------------------| compressed data + |---------------------------------| decompressed data + start >> <---> + delta +``` + +For convenience, there's also a command-line decompressor that works as follows: + +``` +dzx0 Cobra.scr.zx0 +``` + + +## Performance + +The **ZX0** optimal compressor algorithm is fairly complex, thus compressing +typical files can take a few seconds. During development, you can speed up this +process simply using **ZX0** in "quick" mode. This will produce a non-optimal +larger compressed file but execute almost instantly: + +``` +zx0 -q Cobra.scr +``` + +This way, you can repeatedly modify your files, then quickly compress and test +them. Later, when you finish changing these files, you can compress them again +without "quick" mode for maximum compression. Notice that using "quick" mode +will only affect the size of the compressed file, not its format. Therefore +all decompressor routines will continue to work exactly the same way. + +Fortunately all complexity lies on the compression process only. The **ZX0** +compression format itself is very simple and efficient, providing a high +compression ratio that can be decompressed quickly and easily. The provided +**ZX0** decompressor routines in assembly Z80 are small and fast, they only use +main registers (BC, DE, HL, AF), consume very little stack space, and do not +require additional decompression buffer. + +The provided **ZX0** decompressor in C writes the output file while reading the +compressed file, without keeping it in memory. Therefore it always use the same +amount of memory, regardless of file size. Thus even large compressed files can +be decompressed in very small computers with limited memory, even if it took +considerable time and memory to compress it originally. It means decompressing +within asymptotically optimal space and time O(n) only, using storage space O(n) +for input and output files, and only memory space O(w) for processing. + + +## File Format + +The **ZX0** compressed format is very simple. There are only 3 types of blocks: + +* Literal (copy next N bytes from compressed file) +``` + 0 Elias(length) byte[1] byte[2] ... byte[N] +``` + +* Copy from last offset (repeat N bytes from last offset) +``` + 0 Elias(length) +``` + +* Copy from new offset (repeat N bytes from new offset) +``` + 1 Elias(MSB(offset)+1) LSB(offset) Elias(length-1) +``` + +**ZX0** needs only 1 bit to distinguish between these blocks, because literal +blocks cannot be consecutive, and reusing last offset can only happen after a +literal block. The first block is always a literal, so the first bit is omitted. + +The offset MSB and all lengths are stored using interlaced +[Elias Gamma Coding](https://en.wikipedia.org/wiki/Elias_gamma_coding). When +offset MSB equals 256 it means EOF. The offset LSB is stored using 7 bits +instead of 8, because it produces better results in most practical cases. + + +## Advanced Features + +The **ZX0** compressor contains a few extra "hidden" features, that are slightly +harder to use properly, and not supported by the **ZX0** decompressor in C. Please +read carefully these instructions before attempting to use any of them! + + +#### _COMPRESSING BACKWARDS_ + +When using **ZX0** for "in-place" decompression (decompressing data to overlap the +same memory area storing the compressed data), you must always leave a small +margin of "delta" bytes of compressed data at the end. However it won't work to +decompress some large data that will occupy all the upper memory until the last +memory address, since there won't be even a couple bytes left at the end. + +A possible workaround is to compress and decompress data backwards, starting at +the last memory address. Therefore you will only need to leave a small margin of +"delta" bytes of compressed data at the beginning instead. Technically, it will +require that lowest address of compressed data should be at least "delta" bytes +lower than lowest address of decompressed data. See image below: + + compressed data |------------------| + decompressed data |---------------------------------| + <---> << start + delta + +To compress a file backwards, use the command-line compressor as follows: + +``` +zx0 -b Cobra.scr +``` + +To decompress it later, you must call one of the supplied "backwards" variants +of the Assembly decompressor, specifying last source address of compressed data +in HL and last target address in DE. + +For instance, if you compile a "backwards" Assembly decompressor routine to +address 64000, load backwards compressed file "Cobra.scr.zx0" (with size 2202 +bytes) to address 51200, and want to decompress it directly to the ZX Spectrum +screen (with 6912 bytes), then execute the following code: + +``` + LD HL, 51200+2202-1 ; source (last address of "Cobra.scr.zx0") + LD DE, 16384+6912-1 ; target (last address of screen memory) + CALL 64000 ; backwards decompress routine +``` + +Notice that compressing backwards may sometimes produce slightly smaller +compressed files in certain cases, slightly larger compressed files in others. +Overall it shouldn't make much difference either way. + + +#### _COMPRESSING WITH PREFIX_ + +The LZ77/LZSS compression is achieved by "abbreviating repetitions", such that +certain sequences of bytes are replaced with much shorter references to previous +occurrences of these same sequences. For this reason, it's harder to get very +good compression ratio on very short files, or in the initial parts of larger +files, due to lack of choices for previous sequences that could be referenced. + +A possible improvement is to compress data while also taking into account what +else will be already stored in memory during decompression later. Thus the +compressed data may even contain shorter references to repetitions stored in +some previous "prefix" memory area, instead of just repetitions within the +decompressed area itself. + +An input file may contain both some prefix data to be referenced only, and the +actual data to be compressed. An optional parameter can specify how many bytes +must be skipped before compression. See below: + +``` + compressed data + |-------------------| + prefix decompressed data + |--------------|---------------------------------| + start >> + <--------------> <---> + skip delta +``` + +As usual, if you want to decompress data into a memory area that partially +overlaps the compressed data itself, the last address of compressed data must be +at least "delta" bytes higher than the last address of decompressed data. + +For instance, if you want the first 6144 bytes of a certain file to be skipped +(not compressed but possibly referenced), then use the command-line compressor +as follows: + +``` +zx0 +6144 Cobra.cbr +``` + +In practice, suppose an action game uses a few generic sprites that are common +for all levels (such as player graphics), and other sprites are specific for +each level (such as enemies). All generic sprites must stay always accessible at +a certain memory area, but any level specific data can be only decompressed as +needed, to the memory area immediately following it. In this case, the generic +sprites area could be used as prefix when compressing and decompressing each +level, in an attempt to improve compression. For instance, suppose generic +graphics are loaded from file "generic.gfx" to address 56000, occupying 2500 +bytes, and level specific graphics will be decompressed immediately afterwards, +to address 58500. To compress each level using "generic.gfx" as a 2500 bytes +prefix, use the command-line compressor as follows: + +``` +copy /b generic.gfx+level_1.gfx prefixed_level_1.gfx +zx0 +2500 prefixed_level_1.gfx + +copy /b generic.gfx+level_2.gfx prefixed_level_2.gfx +zx0 +2500 prefixed_level_2.gfx + +copy /b generic.gfx+level_3.gfx prefixed_level_3.gfx +zx0 +2500 prefixed_level_3.gfx +``` + +To decompress it later, you simply need to use one of the normal variants of the +Assembly decompressor, as usual. In this case, if you loaded compressed file +"prefixed_level_1.gfx.zx0" to address 48000 for instance, decompressing it will +require the following code: + +``` + LD HL, 48000 ; source address (put "prefixed_level_1.gfx.zx0" there) + LD DE, 58500 ; target address (level specific memory area in this case) + CALL 65000 ; decompress routine compiled at this address +``` + +However decompression will only work properly if exactly the same prefix data is +present in the memory area immediately preceding the decompression address. +Therefore you must be extremely careful to ensure the prefix area does not store +variables, self-modifying code, or anything else that may change prefix content +between compression and decompression. Also don't forget to recompress your +files whenever you modify a prefix! + +In certain cases, compressing with a prefix may considerably help compression. +In others, it may not even make any difference. It mostly depends on how much +similarity exists between data to be compressed and its provided prefix. + + +#### _COMPRESSING BACKWARDS WITH SUFFIX_ + +Both features above can be used together. A file can be compressed backwards, +with an optional parameter to specify how many bytes should be skipped (not +compressed but possibly referenced) from the end of the input file instead. See +below: + +``` + compressed data + |-------------------| + decompressed data suffix + |---------------------------------|--------------| + << start + <---> <--------------> + delta skip +``` + +As usual, if you want to decompress data into a memory area that partially +overlaps the compressed data itself, lowest address of compressed data must be +at least "delta" bytes lower than lowest address of decompressed data. + +For instance, if you want to skip the last 768 bytes of a certain input file and +compress everything else (possibly referencing this "suffix" of 768 bytes), then +use the command-line compressor as follows: + +``` +zx0 -b +768 Cobra.cbr +``` + +In previous example, suppose the action game now stores level-specific sprites +in the memory area from address 33000 to 33511 (512 bytes), just before generic +sprites that are stored from address 33512 to 34535 (1024 bytes). In this case, +these generic sprites could be used as suffix when compressing and decompressing +level-specific data as needed, in an attempt to improve compression. To compress +each level using "generic.gfx" as a 1024 bytes suffix, use the command-line +compressor as follows: + +``` +copy /b level_1.gfx+generic.gfx level_1_suffixed.gfx +zx0 -b +1024 level_1_suffixed.gfx + +copy /b level_2.gfx+generic.gfx level_2_suffixed.gfx +zx0 -b +1024 level_2_suffixed.gfx + +copy /b level_3.gfx+generic.gfx level_3_suffixed.gfx +zx0 -b +1024 level_3_suffixed.gfx +``` + +To decompress it later, use the backwards variant of the Assembly decompressor. +In this case, if you compile a "backwards" decompressor routine to address +64000, and load compressed file "level_1_suffixed.gfx.zx0" (with 217 bytes) to +address 39000 for instance, decompressing it will require the following code: + +``` + LD HL, 39000+217-1 ; source (last address of "level_1_suffixed.gfx.zx0") + LD DE, 33000+512-1 ; target (last address of level-specific data) + CALL 64000 ; backwards decompress routine +``` + +Analogously, decompression will only work properly if exactly the same suffix +data is present in the memory area immediately following the decompression area. +Therefore you must be extremely careful to ensure the suffix area does not store +variables, self-modifying code, or anything else that may change suffix content +between compression and decompression. Also don't forget to recompress your +files whenever you modify a suffix! + +Also if you are using "in-place" decompression, you must leave a small margin of +"delta" bytes of compressed data just before the decompression area. + + +## License + +The **ZX0** data compression format and algorithm was designed and implemented +by **Einar Saukas**. Special thanks to **introspec/spke** for several +suggestions and improvements, and together with **uniabis** for providing the +"Fast" decompressor. Also special thanks to **Urusergi** for additional ideas +and improvements. + +The optimal C compressor is available under the "BSD-3" license. In practice, +this is relevant only if you want to modify its source code and/or incorporate +the compressor within your own products. Otherwise, if you just execute it to +compress files, you can simply ignore these conditions. + +The decompressors can be used freely within your own programs (either for the +ZX Spectrum or any other platform), even for commercial releases. The only +condition is that you must indicate somehow in your documentation that you have +used **ZX0**. + + +## Links + +**ZX0** implemented in other programming languages: + +* [ZX0-Java](https://github.com/einar-saukas/ZX0-Java) - Faster +multi-thread data compressor for **ZX0** in [Java](https://www.java.com/). + +* [ZX0-Kotlin](https://github.com/einar-saukas/ZX0-Kotlin) - Faster +multi-thread data compressor for **ZX0** in [Kotlin](https://kotlinlang.org/). + +* [Salvador](https://github.com/emmanuel-marty/salvador) - A non-optimal but +much faster data compressor for **ZX0** in C. + +**ZX0** ported to other platforms: + +* [DEC PDP11](https://github.com/ivagorRetrocomp/DeZX) _("classic" file format v1)_ + +* [Hitachi 6309](https://github.com/dougmasten/zx0-6x09) _("classic" file format v1)_ + +* [Intel 8080](https://github.com/ivagorRetrocomp/DeZX) _("classic" file format v1)_ + +* [Intel 8088/x86](https://github.com/emmanuel-marty/unzx0_x86) _(all formats)_ + +* [MOS 6502](https://github.com/bboxy/bitfire/tree/master/packer/zx0/6502) _(all formats)_ + +* [MOS 6502](https://xxl.atari.pl/zx0-decompressor/) (stream) - _(all formats)_ + +* [Motorola 6809](https://github.com/dougmasten/zx0-6x09) _("classic" file format v1)_ + +* [Motorola 68000](https://github.com/emmanuel-marty/unzx0_68000) _(all formats)_ + +Tools supporting **ZX0**: + +* [z88dk](http://www.z88dk.org/) - The main C compiler for Z80 machines, that +provides built-in support for **ZX0**, **ZX1**, **ZX2**, and **ZX7**. + +* [ZX Basic](https://zxbasic.readthedocs.io/) - The main BASIC compiler for +Z80 machines, that provides built-in support for **ZX0**. + +* [Mad-Pascal](https://github.com/tebe6502/Mad-Pascal) - The 32-bit Turbo +Pascal compiler for Atari XE/XL, that provides built-in support for **ZX0**. + +* [RASM Assembler](https://github.com/EdouardBERGE/rasm/) - A very fast Z80 +assembler, that provides built-in support for **ZX0** and **ZX7**. + +* [MSXlib](https://github.com/theNestruo/msx-msxlib) - A set of libraries to +create MSX videogame cartridges, that provides built-in support +for **ZX0**, **ZX1**, and **ZX7**. + +* [coco-dev](https://github.com/jamieleecho/coco-dev) - A Docker development +environment to create Tandy Color Computer applications, that provides +built-in support for **ZX0**. + +* [Gfx2Next](https://github.com/headkaze/Gfx2Next) - A graphics conversion +utility for ZX Spectrum Next development, that provides built-in support +for **ZX0**. + +* [ConvImgCpc](https://github.com/DemoniakLudo/ConvImgCpc) - An image +conversion utility for Amstrad CPC development, that provides built-in support +for **ZX0** and **ZX1**. + +* [Vortex2_Player_SJASM](https://github.com/andydansby/Vortex2_Player_SJASM_ver2_compress) - +A packaging utility to compile Vortex 2 music for a ZX Spectrum, that compresses +songs using **ZX0**. + +Projects using **ZX0**: + +* [Bitfire](https://github.com/bboxy/bitfire) - A disk image loader/generator +for Commodore 64, that stores all compressed data using a modified version +of **ZX0**. + +* [Defender CoCo 3](http://www.lcurtisboyle.com/nitros9/defender.html) - A +conversion of the official Williams Defender game from the arcades for the +Tandy Color Computer 3 that stores all compressed data using **ZX0** to fit +on two 160K floppy disks. + +* [NSID_Emu](https://spectrumcomputing.co.uk/forums/viewtopic.php?f=8&t=2786) - +A SID Player for ZX Spectrum that stores all compressed data using **ZX0**. + +* [ZX Interface 2 Cartridges](http://www.fruitcake.plus.com/Sinclair/Interface2/Cartridges/Interface2_RC_New_3rdParty_GameConversions.htm) - +Several ZX Interface 2 conversions were created using either **ZX0** or **ZX7** +so a full game could fit into a small 16K cartridge. + +* [Joust CoCo 3](http://www.lcurtisboyle.com/nitros9/joust.html) - A port of +arcade game Joust for the Tandy Color Computer 3, that stores all compressed +data using **ZX0** to fit on a single 160K floppy disk. + +* [Sonic GX](http://norecess.cpcscene.net/) - A remake of video game Sonic the +Hedgehog for the GX-4000, that stores all compressed data using **ZX0**. + +* [Rit and Tam](http://www.indieretronews.com/2021/02/rit-and-tam-arcade-classic-rodland-is.html) - +A remake of platform game Rodland for the Amstrad, that stores all compressed +data using **ZX0**. + +* [others](https://spectrumcomputing.co.uk/entry/36245/ZX-Spectrum/ZX0) - +A list of Sinclair-related programs using **ZX0** is available at **Spectrum Computing**. + +Related projects (by the same author): + +* [RCS](https://github.com/einar-saukas/RCS) - Use **ZX0** and **RCS** together +to improve compression of ZX Spectrum screens. + +* [ZX0](https://github.com/einar-saukas/ZX0) - The official **ZX0** repository. + +* [ZX1](https://github.com/einar-saukas/ZX1) - A simpler but faster version +of **ZX0**, that sacrifices about 1.5% compression to run about 15% faster. + +* [ZX2](https://github.com/einar-saukas/ZX2) - A minimalist version of **ZX1**, +intended for compressing very small files. + +* [ZX5](https://github.com/einar-saukas/ZX5) - An experimental, more complex +compressor based on **ZX0**. + +* [ZX7](https://spectrumcomputing.co.uk/entry/27996/ZX-Spectrum/ZX7) - A widely +popular predecessor compressor (now superseded by **ZX0**). diff --git a/Content/tools/zx0/src/compress.c b/Content/tools/zx0/src/compress.c new file mode 100644 index 0000000..ca966a3 --- /dev/null +++ b/Content/tools/zx0/src/compress.c @@ -0,0 +1,164 @@ +/* + * (c) Copyright 2021 by Einar Saukas. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of its author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "zx0.h" + +unsigned char* output_data; +int output_index; +int input_index; +int bit_index; +int bit_mask; +int diff; +int backtrack; + +void read_bytes(int n, int *delta) { + input_index += n; + diff += n; + if (*delta < diff) + *delta = diff; +} + +void write_byte(int value) { + output_data[output_index++] = value; + diff--; +} + +void write_bit(int value) { + if (backtrack) { + if (value) + output_data[output_index-1] |= 1; + backtrack = FALSE; + } else { + if (!bit_mask) { + bit_mask = 128; + bit_index = output_index; + write_byte(0); + } + if (value) + output_data[bit_index] |= bit_mask; + bit_mask >>= 1; + } +} + +void write_interlaced_elias_gamma(int value, int backwards_mode, int invert_mode) { + int i; + + for (i = 2; i <= value; i <<= 1) + ; + i >>= 1; + while (i >>= 1) { + write_bit(backwards_mode); + write_bit(invert_mode ? !(value & i) : (value & i)); + } + write_bit(!backwards_mode); +} + +unsigned char *compress(BLOCK *optimal, unsigned char *input_data, int input_size, int skip, int backwards_mode, int invert_mode, int *output_size, int *delta) { + BLOCK *prev; + BLOCK *next; + int last_offset = INITIAL_OFFSET; + int length; + int i; + + /* calculate and allocate output buffer */ + *output_size = (optimal->bits+25)/8; + output_data = (unsigned char *)malloc(*output_size); + if (!output_data) { + fprintf(stderr, "Error: Insufficient memory\n"); + exit(1); + } + + /* un-reverse optimal sequence */ + prev = NULL; + while (optimal) { + next = optimal->chain; + optimal->chain = prev; + prev = optimal; + optimal = next; + } + + /* initialize data */ + diff = *output_size-input_size+skip; + *delta = 0; + input_index = skip; + output_index = 0; + bit_mask = 0; + backtrack = TRUE; + + /* generate output */ + for (optimal = prev->chain; optimal; prev=optimal, optimal = optimal->chain) { + length = optimal->index-prev->index; + + if (!optimal->offset) { + /* copy literals indicator */ + write_bit(0); + + /* copy literals length */ + write_interlaced_elias_gamma(length, backwards_mode, FALSE); + + /* copy literals values */ + for (i = 0; i < length; i++) { + write_byte(input_data[input_index]); + read_bytes(1, delta); + } + } else if (optimal->offset == last_offset) { + /* copy from last offset indicator */ + write_bit(0); + + /* copy from last offset length */ + write_interlaced_elias_gamma(length, backwards_mode, FALSE); + read_bytes(length, delta); + } else { + /* copy from new offset indicator */ + write_bit(1); + + /* copy from new offset MSB */ + write_interlaced_elias_gamma((optimal->offset-1)/128+1, backwards_mode, invert_mode); + + /* copy from new offset LSB */ + if (backwards_mode) + write_byte(((optimal->offset-1)%128)<<1); + else + write_byte((127-(optimal->offset-1)%128)<<1); + + /* copy from new offset length */ + backtrack = TRUE; + write_interlaced_elias_gamma(length-1, backwards_mode, FALSE); + read_bytes(length, delta); + + last_offset = optimal->offset; + } + } + + /* end marker */ + write_bit(1); + write_interlaced_elias_gamma(256, backwards_mode, invert_mode); + + /* done! */ + return output_data; +} diff --git a/Content/tools/zx0/src/dzx0.c b/Content/tools/zx0/src/dzx0.c new file mode 100644 index 0000000..502f96d --- /dev/null +++ b/Content/tools/zx0/src/dzx0.c @@ -0,0 +1,226 @@ +/* + * ZX0 decompressor - by Einar Saukas + * https://github.com/einar-saukas/ZX0 + */ + +#include +#include +#include + +#define BUFFER_SIZE 65536 /* must be > MAX_OFFSET */ +#define INITIAL_OFFSET 1 + +#define FALSE 0 +#define TRUE 1 + +FILE *ifp; +FILE *ofp; +char *input_name; +char *output_name; +unsigned char *input_data; +unsigned char *output_data; +size_t input_index; +size_t output_index; +size_t input_size; +size_t output_size; +size_t partial_counter; +int bit_mask; +int bit_value; +int backtrack; +int last_byte; + +int read_byte() { + if (input_index == partial_counter) { + input_index = 0; + partial_counter = fread(input_data, sizeof(char), BUFFER_SIZE, ifp); + input_size += partial_counter; + if (partial_counter == 0) { + fprintf(stderr, (input_size ? "Error: Truncated input file %s\n" : "Error: Empty input file %s\n"), input_name); + exit(1); + } + } + last_byte = input_data[input_index++]; + return last_byte; +} + +int read_bit() { + if (backtrack) { + backtrack = FALSE; + return last_byte & 1; + } + bit_mask >>= 1; + if (bit_mask == 0) { + bit_mask = 128; + bit_value = read_byte(); + } + return bit_value & bit_mask ? 1 : 0; +} + +int read_interlaced_elias_gamma(int inverted) { + int value = 1; + while (!read_bit()) { + value = value << 1 | read_bit() ^ inverted; + } + return value; +} + +void save_output() { + if (output_index != 0) { + if (fwrite(output_data, sizeof(char), output_index, ofp) != output_index) { + fprintf(stderr, "Error: Cannot write output file %s\n", output_name); + exit(1); + } + output_size += output_index; + output_index = 0; + } +} + +void write_byte(int value) { + output_data[output_index++] = value; + if (output_index == BUFFER_SIZE) { + save_output(); + } +} + +void write_bytes(int offset, int length) { + int i; + + if (offset > output_size+output_index) { + fprintf(stderr, "Error: Invalid data in input file %s\n", input_name); + exit(1); + } + while (length-- > 0) { + i = output_index-offset; + write_byte(output_data[i >= 0 ? i : BUFFER_SIZE+i]); + } +} + +void decompress(int classic_mode) { + int last_offset = INITIAL_OFFSET; + int length; + int i; + + input_data = (unsigned char *)malloc(BUFFER_SIZE); + output_data = (unsigned char *)malloc(BUFFER_SIZE); + if (!input_data || !output_data) { + fprintf(stderr, "Error: Insufficient memory\n"); + exit(1); + } + + input_size = 0; + input_index = 0; + partial_counter = 0; + output_index = 0; + output_size = 0; + bit_mask = 0; + backtrack = FALSE; + +COPY_LITERALS: + length = read_interlaced_elias_gamma(FALSE); + for (i = 0; i < length; i++) + write_byte(read_byte()); + if (read_bit()) + goto COPY_FROM_NEW_OFFSET; + +/*COPY_FROM_LAST_OFFSET:*/ + length = read_interlaced_elias_gamma(FALSE); + write_bytes(last_offset, length); + if (!read_bit()) + goto COPY_LITERALS; + +COPY_FROM_NEW_OFFSET: + last_offset = read_interlaced_elias_gamma(!classic_mode); + if (last_offset == 256) { + save_output(); + if (input_index != partial_counter) { + fprintf(stderr, "Error: Input file %s too long\n", input_name); + exit(1); + } + return; + } + last_offset = last_offset*128-(read_byte()>>1); + backtrack = TRUE; + length = read_interlaced_elias_gamma(FALSE)+1; + write_bytes(last_offset, length); + if (read_bit()) + goto COPY_FROM_NEW_OFFSET; + else + goto COPY_LITERALS; +} + +int main(int argc, char *argv[]) { + int forced_mode = FALSE; + int classic_mode = FALSE; + int i; + + printf("DZX0 v2.2: Data decompressor by Einar Saukas\n"); + + /* process hidden optional parameters */ + for (i = 1; i < argc && *argv[i] == '-'; i++) { + if (!strcmp(argv[i], "-f")) { + forced_mode = TRUE; + } else if (!strcmp(argv[i], "-c")) { + classic_mode = TRUE; + } else { + fprintf(stderr, "Error: Invalid parameter %s\n", argv[i]); + exit(1); + } + } + + /* determine output filename */ + if (argc == i+1) { + input_name = argv[i]; + input_size = strlen(input_name); + if (input_size > 4 && !strcmp(input_name+input_size-4, ".zx0")) { + input_size = strlen(input_name); + output_name = (char *)malloc(input_size); + strcpy(output_name, input_name); + output_name[input_size-4] = '\0'; + } else { + fprintf(stderr, "Error: Cannot infer output filename\n"); + exit(1); + } + } else if (argc == i+2) { + input_name = argv[i]; + output_name = argv[i+1]; + } else { + fprintf(stderr, "Usage: %s [-f] [-c] input.zx0 [output]\n" + " -f Force overwrite of output file\n" + " -c Classic file format (v1.*)\n", argv[0]); + exit(1); + } + + /* open input file */ + ifp = fopen(input_name, "rb"); + if (!ifp) { + fprintf(stderr, "Error: Cannot access input file %s\n", input_name); + exit(1); + } + + /* check output file */ + if (!forced_mode && fopen(output_name, "rb") != NULL) { + fprintf(stderr, "Error: Already existing output file %s\n", output_name); + exit(1); + } + + /* create output file */ + ofp = fopen(output_name, "wb"); + if (!ofp) { + fprintf(stderr, "Error: Cannot create output file %s\n", output_name); + exit(1); + } + + /* generate output file */ + decompress(classic_mode); + + /* close input file */ + fclose(ifp); + + /* close output file */ + fclose(ofp); + + /* done! */ + printf("File decompressed from %lu to %lu bytes!\n", (unsigned long)input_size, (unsigned long)output_size); + + return 0; +} diff --git a/Content/tools/zx0/src/memory.c b/Content/tools/zx0/src/memory.c new file mode 100644 index 0000000..be52c3a --- /dev/null +++ b/Content/tools/zx0/src/memory.c @@ -0,0 +1,75 @@ +/* + * (c) Copyright 2021 by Einar Saukas. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of its author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "zx0.h" + +#define QTY_BLOCKS 10000 + +BLOCK *ghost_root = NULL; +BLOCK *dead_array = NULL; +int dead_array_size = 0; + +BLOCK *allocate(int bits, int index, int offset, BLOCK *chain) { + BLOCK *ptr; + + if (ghost_root) { + ptr = ghost_root; + ghost_root = ptr->ghost_chain; + if (ptr->chain && !--ptr->chain->references) { + ptr->chain->ghost_chain = ghost_root; + ghost_root = ptr->chain; + } + } else { + if (!dead_array_size) { + dead_array = (BLOCK *)malloc(QTY_BLOCKS*sizeof(BLOCK)); + if (!dead_array) { + fprintf(stderr, "Error: Insufficient memory\n"); + exit(1); + } + dead_array_size = QTY_BLOCKS; + } + ptr = &dead_array[--dead_array_size]; + } + ptr->bits = bits; + ptr->index = index; + ptr->offset = offset; + if (chain) + chain->references++; + ptr->chain = chain; + ptr->references = 0; + return ptr; +} + +void assign(BLOCK **ptr, BLOCK *chain) { + chain->references++; + if (*ptr && !--(*ptr)->references) { + (*ptr)->ghost_chain = ghost_root; + ghost_root = *ptr; + } + *ptr = chain; +} diff --git a/Content/tools/zx0/src/optimize.c b/Content/tools/zx0/src/optimize.c new file mode 100644 index 0000000..99ca540 --- /dev/null +++ b/Content/tools/zx0/src/optimize.c @@ -0,0 +1,138 @@ +/* + * (c) Copyright 2021 by Einar Saukas. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of its author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "zx0.h" + +#define MAX_SCALE 50 + +int offset_ceiling(int index, int offset_limit) { + return index > offset_limit ? offset_limit : index < INITIAL_OFFSET ? INITIAL_OFFSET : index; +} + +int elias_gamma_bits(int value) { + int bits = 1; + while (value >>= 1) + bits += 2; + return bits; +} + +BLOCK* optimize(unsigned char *input_data, int input_size, int skip, int offset_limit) { + BLOCK **last_literal; + BLOCK **last_match; + BLOCK **optimal; + int* match_length; + int* best_length; + int best_length_size; + int bits; + int index; + int offset; + int length; + int bits2; + int dots = 2; + int max_offset = offset_ceiling(input_size-1, offset_limit); + + /* allocate all main data structures at once */ + last_literal = (BLOCK **)calloc(max_offset+1, sizeof(BLOCK *)); + last_match = (BLOCK **)calloc(max_offset+1, sizeof(BLOCK *)); + optimal = (BLOCK **)calloc(input_size, sizeof(BLOCK *)); + match_length = (int *)calloc(max_offset+1, sizeof(int)); + best_length = (int *)malloc(input_size*sizeof(int)); + if (!last_literal || !last_match || !optimal || !match_length || !best_length) { + fprintf(stderr, "Error: Insufficient memory\n"); + exit(1); + } + if (input_size > 2) + best_length[2] = 2; + + /* start with fake block */ + assign(&last_match[INITIAL_OFFSET], allocate(-1, skip-1, INITIAL_OFFSET, NULL)); + + printf("["); + + /* process remaining bytes */ + for (index = skip; index < input_size; index++) { + best_length_size = 2; + max_offset = offset_ceiling(index, offset_limit); + for (offset = 1; offset <= max_offset; offset++) { + if (index != skip && index >= offset && input_data[index] == input_data[index-offset]) { + /* copy from last offset */ + if (last_literal[offset]) { + length = index-last_literal[offset]->index; + bits = last_literal[offset]->bits + 1 + elias_gamma_bits(length); + assign(&last_match[offset], allocate(bits, index, offset, last_literal[offset])); + if (!optimal[index] || optimal[index]->bits > bits) + assign(&optimal[index], last_match[offset]); + } + /* copy from new offset */ + if (++match_length[offset] > 1) { + if (best_length_size < match_length[offset]) { + bits = optimal[index-best_length[best_length_size]]->bits + elias_gamma_bits(best_length[best_length_size]-1); + do { + best_length_size++; + bits2 = optimal[index-best_length_size]->bits + elias_gamma_bits(best_length_size-1); + if (bits2 <= bits) { + best_length[best_length_size] = best_length_size; + bits = bits2; + } else { + best_length[best_length_size] = best_length[best_length_size-1]; + } + } while(best_length_size < match_length[offset]); + } + length = best_length[match_length[offset]]; + bits = optimal[index-length]->bits + 8 + elias_gamma_bits((offset-1)/128+1) + elias_gamma_bits(length-1); + if (!last_match[offset] || last_match[offset]->index != index || last_match[offset]->bits > bits) { + assign(&last_match[offset], allocate(bits, index, offset, optimal[index-length])); + if (!optimal[index] || optimal[index]->bits > bits) + assign(&optimal[index], last_match[offset]); + } + } + } else { + /* copy literals */ + match_length[offset] = 0; + if (last_match[offset]) { + length = index-last_match[offset]->index; + bits = last_match[offset]->bits + 1 + elias_gamma_bits(length) + length*8; + assign(&last_literal[offset], allocate(bits, index, 0, last_match[offset])); + if (!optimal[index] || optimal[index]->bits > bits) + assign(&optimal[index], last_literal[offset]); + } + } + } + + /* indicate progress */ + if (index*MAX_SCALE/input_size > dots) { + printf("."); + fflush(stdout); + dots++; + } + } + + printf("]\n"); + + return optimal[input_size-1]; +} diff --git a/Content/tools/zx0/src/zx0.c b/Content/tools/zx0/src/zx0.c new file mode 100644 index 0000000..45b2efd --- /dev/null +++ b/Content/tools/zx0/src/zx0.c @@ -0,0 +1,177 @@ +/* + * (c) Copyright 2021 by Einar Saukas. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of its author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "zx0.h" + +#define MAX_OFFSET_ZX0 32640 +#define MAX_OFFSET_ZX7 2176 + +void reverse(unsigned char *first, unsigned char *last) { + unsigned char c; + + while (first < last) { + c = *first; + *first++ = *last; + *last-- = c; + } +} + +int main(int argc, char *argv[]) { + int skip = 0; + int forced_mode = FALSE; + int quick_mode = FALSE; + int backwards_mode = FALSE; + int classic_mode = FALSE; + char *output_name; + unsigned char *input_data; + unsigned char *output_data; + FILE *ifp; + FILE *ofp; + int input_size; + int output_size; + int partial_counter; + int total_counter; + int delta; + int i; + + printf("ZX0 v2.2: Optimal data compressor by Einar Saukas\n"); + + /* process optional parameters */ + for (i = 1; i < argc && (*argv[i] == '-' || *argv[i] == '+'); i++) { + if (!strcmp(argv[i], "-f")) { + forced_mode = TRUE; + } else if (!strcmp(argv[i], "-c")) { + classic_mode = TRUE; + } else if (!strcmp(argv[i], "-b")) { + backwards_mode = TRUE; + } else if (!strcmp(argv[i], "-q")) { + quick_mode = TRUE; + } else if ((skip = atoi(argv[i])) <= 0) { + fprintf(stderr, "Error: Invalid parameter %s\n", argv[i]); + exit(1); + } + } + + /* determine output filename */ + if (argc == i+1) { + output_name = (char *)malloc(strlen(argv[i])+5); + strcpy(output_name, argv[i]); + strcat(output_name, ".zx0"); + } else if (argc == i+2) { + output_name = argv[i+1]; + } else { + fprintf(stderr, "Usage: %s [-f] [-c] [-b] [-q] input [output.zx0]\n" + " -f Force overwrite of output file\n" + " -c Classic file format (v1.*)\n" + " -b Compress backwards\n" + " -q Quick non-optimal compression\n", argv[0]); + exit(1); + } + + /* open input file */ + ifp = fopen(argv[i], "rb"); + if (!ifp) { + fprintf(stderr, "Error: Cannot access input file %s\n", argv[i]); + exit(1); + } + /* determine input size */ + fseek(ifp, 0L, SEEK_END); + input_size = ftell(ifp); + fseek(ifp, 0L, SEEK_SET); + if (!input_size) { + fprintf(stderr, "Error: Empty input file %s\n", argv[i]); + exit(1); + } + + /* validate skip against input size */ + if (skip >= input_size) { + fprintf(stderr, "Error: Skipping entire input file %s\n", argv[i]); + exit(1); + } + + /* allocate input buffer */ + input_data = (unsigned char *)malloc(input_size); + if (!input_data) { + fprintf(stderr, "Error: Insufficient memory\n"); + exit(1); + } + + /* read input file */ + total_counter = 0; + do { + partial_counter = fread(input_data+total_counter, sizeof(char), input_size-total_counter, ifp); + total_counter += partial_counter; + } while (partial_counter > 0); + + if (total_counter != input_size) { + fprintf(stderr, "Error: Cannot read input file %s\n", argv[i]); + exit(1); + } + + /* close input file */ + fclose(ifp); + + /* check output file */ + if (!forced_mode && fopen(output_name, "rb") != NULL) { + fprintf(stderr, "Error: Already existing output file %s\n", output_name); + exit(1); + } + + /* create output file */ + ofp = fopen(output_name, "wb"); + if (!ofp) { + fprintf(stderr, "Error: Cannot create output file %s\n", output_name); + exit(1); + } + + /* conditionally reverse input file */ + if (backwards_mode) + reverse(input_data, input_data+input_size-1); + + /* generate output file */ + output_data = compress(optimize(input_data, input_size, skip, quick_mode ? MAX_OFFSET_ZX7 : MAX_OFFSET_ZX0), input_data, input_size, skip, backwards_mode, !classic_mode && !backwards_mode, &output_size, &delta); + + /* conditionally reverse output file */ + if (backwards_mode) + reverse(output_data, output_data+output_size-1); + + /* write output file */ + if (fwrite(output_data, sizeof(char), output_size, ofp) != output_size) { + fprintf(stderr, "Error: Cannot write output file %s\n", output_name); + exit(1); + } + + /* close output file */ + fclose(ofp); + + /* done! */ + printf("File%s compressed%s from %d to %d bytes! (delta %d)\n", (skip ? " partially" : ""), (backwards_mode ? " backwards" : ""), input_size-skip, output_size, delta); + + return 0; +} diff --git a/Content/tools/zx0/src/zx0.h b/Content/tools/zx0/src/zx0.h new file mode 100644 index 0000000..cb298cf --- /dev/null +++ b/Content/tools/zx0/src/zx0.h @@ -0,0 +1,46 @@ +/* + * (c) Copyright 2021 by Einar Saukas. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of its author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define INITIAL_OFFSET 1 + +#define FALSE 0 +#define TRUE 1 + +typedef struct block_t { + struct block_t *chain; + struct block_t *ghost_chain; + int bits; + int index; + int offset; + int references; +} BLOCK; + +BLOCK *allocate(int bits, int index, int offset, BLOCK *chain); + +void assign(BLOCK **ptr, BLOCK *chain); + +BLOCK *optimize(unsigned char *input_data, int input_size, int skip, int offset_limit); + +unsigned char *compress(BLOCK *optimal, unsigned char *input_data, int input_size, int skip, int backwards_mode, int invert_mode, int *output_size, int *delta); diff --git a/Content/tools/zx0/z80/OLD_V1/dzx0_fast_CLASSIC.asm b/Content/tools/zx0/z80/OLD_V1/dzx0_fast_CLASSIC.asm new file mode 100644 index 0000000..657a589 --- /dev/null +++ b/Content/tools/zx0/z80/OLD_V1/dzx0_fast_CLASSIC.asm @@ -0,0 +1,250 @@ +; +; Speed-optimized ZX0 decompressor by spke (191 bytes) - OLD FILE FORMAT v1 +; +; ver.00 by spke (27/01-23/03/2021, 191 bytes) +; ver.01 by spke (24/03/2021, 193(+2) bytes - fixed a bug in the initialization) +; ver.01patch2 by uniabis (25/03/2021, 191(-2) bytes - fixed a bug with elias over 8bits) +; ver.01patch5 by uniabis (29/03/2021, 191 bytes - a bit faster) +; +; Original ZX0 decompressors were written by Einar Saukas +; +; This decompressor was written on the basis of "Standard" decompressor by +; Einar Saukas and optimized for speed by spke. This decompressor is +; about 5% faster than the "Turbo" decompressor, which is 128 bytes long. +; It has about the same speed as the 412 bytes version of the "Mega" decompressor. +; +; The decompressor uses AF, AF', BC, DE, HL and IX and relies upon self-modified code. +; +; The decompression is done in the standard way: +; +; ld hl,FirstByteOfCompressedData +; ld de,FirstByteOfMemoryForDecompressedData +; call DecompressZX0 +; +; Of course, ZX0 compression algorithms are (c) 2021 Einar Saukas, +; see https://github.com/einar-saukas/ZX0 for more information +; +; Drop me an email if you have any comments/ideas/suggestions: zxintrospec@gmail.com +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. + +DecompressZX0: + scf + ex af, af' + ld ix, CopyMatch1 + ld bc, $ffff + ld (PrevOffset+1), bc ; default offset is -1 + inc bc + ld a, $80 + jr RunOfLiterals ; BC is assumed to contains 0 most of the time + + ; 7-bit offsets allow additional optimizations, based on the facts that C==0 and AF' has C ON! +ShorterOffsets: + ex af, af' + sbc a, a + ld (PrevOffset+2), a ; the top byte of the offset is always $FF + ld a, (hl) + inc hl + rra + ld (PrevOffset+1), a ; note that AF' always has flag C ON + jr nc, LongerMatch + +CopyMatch2: ; the case of matches with len=2 + ex af, af' + ld c, 2 + + ; the faster match copying code +CopyMatch1: + push hl ; preserve source + +PrevOffset: + ld hl, $ffff ; restore offset (default offset is -1) + add hl, de ; HL = dest - offset + ldir + pop hl ; restore source + + ; after a match you can have either + ; 0 + = run of literals, or + ; 1 + + [7-bits of offset lsb + 1-bit of length] + = another match +AfterMatch1: + add a, a + jr nc, RunOfLiterals + +UsualMatch: ; this is the case of usual match+offset + add a, a + jr nc, LongerOffets + jr nz, ShorterOffsets ; NZ after NC == "confirmed C" + + ld a, (hl) ; reload bits + inc hl + rla + + jr c, ShorterOffsets + +LongerOffets: + inc c + + add a, a ; inline read gamma + rl c + add a, a + jr nc, $-4 + + call z, ReloadReadGamma + +ProcessOffset: + ex af, af' + xor a + sub c + ret z ; end-of-data marker (only checked for longer offsets) + rra + ld (PrevOffset+2),a + ld a, (hl) + inc hl + rra + ld (PrevOffset+1), a + + ; lowest bit is the first bit of the gamma code for length + jr c, CopyMatch2 + + ; this wastes 1 t-state for longer matches far away, + ; but saves 4 t-states for longer nearby (seems to pay off in testing) + ld c, b +LongerMatch: + inc c + ; doing SCF here ensures that AF' has flag C ON and costs + ; cheaper than doing SCF in the ShortestOffsets branch + scf + ex af, af' + + add a, a ; inline read gamma + rl c + add a, a + jr nc, $-4 + + call z,ReloadReadGamma + +CopyMatch3: + push hl ; preserve source + ld hl, (PrevOffset+1) ; restore offset + add hl, de ; HL = dest - offset + + ; because BC>=3-1, we can do 2 x LDI safely + ldi + ldir + inc c + ldi + pop hl ; restore source + + ; after a match you can have either + ; 0 + = run of literals, or + ; 1 + + [7-bits of offset lsb + 1-bit of length] + = another match +AfterMatch3: + add a, a + jr c, UsualMatch + +RunOfLiterals: + inc c + add a, a + jr nc, LongerRun + jr nz, CopyLiteral ; NZ after NC == "confirmed C" + + ld a, (hl) ; reload bits + inc hl + rla + + jr c, CopyLiteral + +LongerRun: + add a, a ; inline read gamma + rl c + add a, a + jr nc, $-4 + + jr nz, CopyLiterals + + ld a, (hl) ; reload bits + inc hl + rla + + call nc, ReadGammaAligned + +CopyLiterals: + ldi + +CopyLiteral: + ldir + + ; after a literal run you can have either + ; 0 + = match using a repeated offset, or + ; 1 + + [7-bits of offset lsb + 1-bit of length] + = another match + add a, a + jr c, UsualMatch + +RepMatch: + inc c + add a, a + jr nc, LongerRepMatch + jr nz, CopyMatch1 ; NZ after NC == "confirmed C" + + ld a, (hl) ; reload bits + inc hl + rla + + jr c, CopyMatch1 + +LongerRepMatch: + add a, a ; inline read gamma + rl c + add a, a + jr nc, $-4 + + jp nz, CopyMatch1 + + ; this is a crafty equivalent of CALL ReloadReadGamma : JP CopyMatch1 + push ix + + ; the subroutine for reading the remainder of the partly read Elias gamma code. + ; it has two entry points: ReloadReadGamma first refills the bit reservoir in A, + ; while ReadGammaAligned assumes that the bit reservoir has just been refilled. +ReloadReadGamma: + ld a, (hl) ; reload bits + inc hl + rla + + ret c +ReadGammaAligned: + add a, a + rl c + add a, a + ret c + add a, a + rl c + add a, a + +ReadingLongGamma: ; this loop does not need unrolling, as it does not get much use anyway + ret c + add a, a + rl c + rl b + add a, a + jr nz, ReadingLongGamma + + ld a, (hl) ; reload bits + inc hl + rla + + jr ReadingLongGamma diff --git a/Content/tools/zx0/z80/OLD_V1/dzx0_mega_CLASSIC.asm b/Content/tools/zx0/z80/OLD_V1/dzx0_mega_CLASSIC.asm new file mode 100644 index 0000000..e703093 --- /dev/null +++ b/Content/tools/zx0/z80/OLD_V1/dzx0_mega_CLASSIC.asm @@ -0,0 +1,464 @@ +; ----------------------------------------------------------------------------- +; ZX0 decoder by Einar Saukas +; "Mega" version (681 bytes, 28% faster) - OLD FILE FORMAT v1 +; ----------------------------------------------------------------------------- +; Parameters: +; HL: source address (compressed data) +; DE: destination address (decompressing) +; ----------------------------------------------------------------------------- + +dzx0_mega: + ld bc, $ffff ; preserve default offset 1 + ld (dzx0m_last_offset+1), bc + inc bc + jr dzx0m_literals0 + +dzx0m_new_offset6: + inc c + add a, a ; obtain offset MSB + jp c, dzx0m_new_offset5 + add a, a + rl c + add a, a + jp c, dzx0m_new_offset3 + add a, a + rl c + add a, a + jp c, dzx0m_new_offset1 +dzx0m_elias_offset1: + add a, a + rl c + rl b + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp nc, dzx0m_elias_offset7 +dzx0m_new_offset7: + ex af, af' ; adjust for negative offset + xor a + sub c + ret z ; check end marker + ld b, a + ex af, af' + ld c, (hl) ; obtain offset LSB + inc hl + rr b ; last offset bit becomes first length bit + rr c + ld (dzx0m_last_offset+1), bc ; preserve new offset + ld bc, 1 + jp c, dzx0m_length7 ; obtain length + add a, a + rl c + add a, a + jp c, dzx0m_length5 + add a, a + rl c + add a, a + jp c, dzx0m_length3 +dzx0m_elias_length3: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_length1 +dzx0m_length1: + push hl ; preserve source + ld hl, (dzx0m_last_offset+1) + add hl, de ; calculate destination - offset + ldir ; copy from offset + inc c + ldi ; copy one more from offset + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr c, dzx0m_new_offset0 +dzx0m_literals0: + inc c + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a ; obtain length + jp c, dzx0m_literals7 + add a, a + rl c + add a, a + jp c, dzx0m_literals5 + add a, a + rl c + add a, a + jp c, dzx0m_literals3 +dzx0m_elias_literals3: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_literals1 +dzx0m_literals1: + ldir ; copy literals + add a, a ; copy from last offset or new offset? + jr c, dzx0m_new_offset0 + inc c + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a ; obtain length + jp c, dzx0m_reuse7 + add a, a + rl c + add a, a + jp c, dzx0m_reuse5 + add a, a + rl c + add a, a + jp c, dzx0m_reuse3 +dzx0m_elias_reuse3: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_reuse1 +dzx0m_reuse1: + push hl ; preserve source + ld hl, (dzx0m_last_offset+1) + add hl, de ; calculate destination - offset + ldir ; copy from offset + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr nc, dzx0m_literals0 + +dzx0m_new_offset0: + inc c + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a ; obtain offset MSB + jp c, dzx0m_new_offset7 + add a, a + rl c + add a, a + jp c, dzx0m_new_offset5 + add a, a + rl c + add a, a + jp c, dzx0m_new_offset3 +dzx0m_elias_offset3: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_offset1 +dzx0m_new_offset1: + ex af, af' ; adjust for negative offset + xor a + sub c + ret z ; check end marker + ld b, a + ex af, af' + ld c, (hl) ; obtain offset LSB + inc hl + rr b ; last offset bit becomes first length bit + rr c + ld (dzx0m_last_offset+1), bc ; preserve new offset + ld bc, 1 + jp c, dzx0m_length1 ; obtain length + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp c, dzx0m_length7 + add a, a + rl c + add a, a + jp c, dzx0m_length5 +dzx0m_elias_length5: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_length3 +dzx0m_length3: + push hl ; preserve source + ld hl, (dzx0m_last_offset+1) + add hl, de ; calculate destination - offset + ldir ; copy from offset + inc c + ldi ; copy one more from offset + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr c, dzx0m_new_offset2 +dzx0m_literals2: + inc c + add a, a ; obtain length + jp c, dzx0m_literals1 + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp c, dzx0m_literals7 + add a, a + rl c + add a, a + jp c, dzx0m_literals5 +dzx0m_elias_literals5: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_literals3 +dzx0m_literals3: + ldir ; copy literals + add a, a ; copy from last offset or new offset? + jr c, dzx0m_new_offset2 + inc c + add a, a ; obtain length + jp c, dzx0m_reuse1 + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp c, dzx0m_reuse7 + add a, a + rl c + add a, a + jp c, dzx0m_reuse5 +dzx0m_elias_reuse5: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_reuse3 +dzx0m_reuse3: + push hl ; preserve source + ld hl, (dzx0m_last_offset+1) + add hl, de ; calculate destination - offset + ldir ; copy from offset + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr nc, dzx0m_literals2 + +dzx0m_new_offset2: + inc c + add a, a ; obtain offset MSB + jp c, dzx0m_new_offset1 + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp c, dzx0m_new_offset7 + add a, a + rl c + add a, a + jp c, dzx0m_new_offset5 +dzx0m_elias_offset5: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_offset3 +dzx0m_new_offset3: + ex af, af' ; adjust for negative offset + xor a + sub c + ret z ; check end marker + ld b, a + ex af, af' + ld c, (hl) ; obtain offset LSB + inc hl + rr b ; last offset bit becomes first length bit + rr c + ld (dzx0m_last_offset+1), bc ; preserve new offset + ld bc, 1 + jp c, dzx0m_length3 ; obtain length + add a, a + rl c + add a, a + jp c, dzx0m_length1 + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp c, dzx0m_length7 +dzx0m_elias_length7: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_length5 +dzx0m_length5: + push hl ; preserve source + ld hl, (dzx0m_last_offset+1) + add hl, de ; calculate destination - offset + ldir ; copy from offset + inc c + ldi ; copy one more from offset + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr c, dzx0m_new_offset4 +dzx0m_literals4: + inc c + add a, a ; obtain length + jp c, dzx0m_literals3 + add a, a + rl c + add a, a + jp c, dzx0m_literals1 + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp c, dzx0m_literals7 +dzx0m_elias_literals7: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_literals5 +dzx0m_literals5: + ldir ; copy literals + add a, a ; copy from last offset or new offset? + jr c, dzx0m_new_offset4 + inc c + add a, a ; obtain length + jp c, dzx0m_reuse3 + add a, a + rl c + add a, a + jp c, dzx0m_reuse1 + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp c, dzx0m_reuse7 +dzx0m_elias_reuse7: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_reuse5 +dzx0m_reuse5: + push hl ; preserve source + ld hl, (dzx0m_last_offset+1) + add hl, de ; calculate destination - offset + ldir ; copy from offset + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr nc, dzx0m_literals4 + +dzx0m_new_offset4: + inc c + add a, a ; obtain offset MSB + jp c, dzx0m_new_offset3 + add a, a + rl c + add a, a + jp c, dzx0m_new_offset1 + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp c, dzx0m_new_offset7 +dzx0m_elias_offset7: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_offset5 +dzx0m_new_offset5: + ex af, af' ; adjust for negative offset + xor a + sub c + ret z ; check end marker + ld b, a + ex af, af' + ld c, (hl) ; obtain offset LSB + inc hl + rr b ; last offset bit becomes first length bit + rr c + ld (dzx0m_last_offset+1), bc ; preserve new offset + ld bc, 1 + jp c, dzx0m_length5 ; obtain length + add a, a + rl c + add a, a + jp c, dzx0m_length3 + add a, a + rl c + add a, a + jp c, dzx0m_length1 +dzx0m_elias_length1: + add a, a + rl c + rl b + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp nc, dzx0m_elias_length7 +dzx0m_length7: + push hl ; preserve source + ld hl, (dzx0m_last_offset+1) + add hl, de ; calculate destination - offset + ldir ; copy from offset + inc c + ldi ; copy one more from offset + pop hl ; restore source + add a, a ; copy from literals or new offset? + jp c, dzx0m_new_offset6 +dzx0m_literals6: + inc c + add a, a ; obtain length + jp c, dzx0m_literals5 + add a, a + rl c + add a, a + jp c, dzx0m_literals3 + add a, a + rl c + add a, a + jp c, dzx0m_literals1 +dzx0m_elias_literals1: + add a, a + rl c + rl b + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp nc, dzx0m_elias_literals7 +dzx0m_literals7: + ldir ; copy literals + add a, a ; copy from last offset or new offset? + jp c, dzx0m_new_offset6 + inc c + add a, a ; obtain length + jp c, dzx0m_reuse5 + add a, a + rl c + add a, a + jp c, dzx0m_reuse3 + add a, a + rl c + add a, a + jp c, dzx0m_reuse1 +dzx0m_elias_reuse1: + add a, a + rl c + rl b + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp nc, dzx0m_elias_reuse7 +dzx0m_reuse7: + push hl ; preserve source +dzx0m_last_offset: + ld hl, 0 + add hl, de ; calculate destination - offset + ldir ; copy from offset + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr nc, dzx0m_literals6 + + jp dzx0m_new_offset6 +; ----------------------------------------------------------------------------- diff --git a/Content/tools/zx0/z80/OLD_V1/dzx0_standard_CLASSIC.asm b/Content/tools/zx0/z80/OLD_V1/dzx0_standard_CLASSIC.asm new file mode 100644 index 0000000..9b7d8ff --- /dev/null +++ b/Content/tools/zx0/z80/OLD_V1/dzx0_standard_CLASSIC.asm @@ -0,0 +1,63 @@ +; ----------------------------------------------------------------------------- +; ZX0 decoder by Einar Saukas +; "Standard" version (69 bytes only) - OLD FILE FORMAT v1 +; ----------------------------------------------------------------------------- +; Parameters: +; HL: source address (compressed data) +; DE: destination address (decompressing) +; ----------------------------------------------------------------------------- + +dzx0_standard: + ld bc, $ffff ; preserve default offset 1 + push bc + inc bc + ld a, $80 +dzx0s_literals: + call dzx0s_elias ; obtain length + ldir ; copy literals + add a, a ; copy from last offset or new offset? + jr c, dzx0s_new_offset + call dzx0s_elias ; obtain length +dzx0s_copy: + ex (sp), hl ; preserve source, restore offset + push hl ; preserve offset + add hl, de ; calculate destination - offset + ldir ; copy from offset + pop hl ; restore offset + ex (sp), hl ; preserve offset, restore source + add a, a ; copy from literals or new offset? + jr nc, dzx0s_literals +dzx0s_new_offset: + call dzx0s_elias ; obtain offset MSB + ex af, af' + pop af ; discard last offset + xor a ; adjust for negative offset + sub c + ret z ; check end marker + ld b, a + ex af, af' + ld c, (hl) ; obtain offset LSB + inc hl + rr b ; last offset bit becomes first length bit + rr c + push bc ; preserve new offset + ld bc, 1 ; obtain length + call nc, dzx0s_elias_backtrack + inc bc + jr dzx0s_copy +dzx0s_elias: + inc c ; interlaced Elias gamma coding +dzx0s_elias_loop: + add a, a + jr nz, dzx0s_elias_skip + ld a, (hl) ; load another group of 8 bits + inc hl + rla +dzx0s_elias_skip: + ret c +dzx0s_elias_backtrack: + add a, a + rl c + rl b + jr dzx0s_elias_loop +; ----------------------------------------------------------------------------- diff --git a/Content/tools/zx0/z80/OLD_V1/dzx0_turbo_CLASSIC.asm b/Content/tools/zx0/z80/OLD_V1/dzx0_turbo_CLASSIC.asm new file mode 100644 index 0000000..fa94bc0 --- /dev/null +++ b/Content/tools/zx0/z80/OLD_V1/dzx0_turbo_CLASSIC.asm @@ -0,0 +1,103 @@ +; ----------------------------------------------------------------------------- +; ZX0 decoder by Einar Saukas & introspec +; "Turbo" version (128 bytes, 21% faster) - OLD FILE FORMAT v1 +; ----------------------------------------------------------------------------- +; Parameters: +; HL: source address (compressed data) +; DE: destination address (decompressing) +; ----------------------------------------------------------------------------- + +dzx0_turbo: + ld bc, $ffff ; preserve default offset 1 + ld (dzx0t_last_offset+1), bc + inc bc + ld a, $80 + jr dzx0t_literals +dzx0t_new_offset: + inc c ; obtain offset MSB + add a, a + jp nz, dzx0t_new_offset_skip + ld a, (hl) ; load another group of 8 bits + inc hl + rla +dzx0t_new_offset_skip: + call nc, dzx0t_elias + ex af, af' ; adjust for negative offset + xor a + sub c + ret z ; check end marker + ld b, a + ex af, af' + ld c, (hl) ; obtain offset LSB + inc hl + rr b ; last offset bit becomes first length bit + rr c + ld (dzx0t_last_offset+1), bc ; preserve new offset + ld bc, 1 ; obtain length + call nc, dzx0t_elias + inc bc +dzx0t_copy: + push hl ; preserve source +dzx0t_last_offset: + ld hl, 0 ; restore offset + add hl, de ; calculate destination - offset + ldir ; copy from offset + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr c, dzx0t_new_offset +dzx0t_literals: + inc c ; obtain length + add a, a + jp nz, dzx0t_literals_skip + ld a, (hl) ; load another group of 8 bits + inc hl + rla +dzx0t_literals_skip: + call nc, dzx0t_elias + ldir ; copy literals + add a, a ; copy from last offset or new offset? + jr c, dzx0t_new_offset + inc c ; obtain length + add a, a + jp nz, dzx0t_last_offset_skip + ld a, (hl) ; load another group of 8 bits + inc hl + rla +dzx0t_last_offset_skip: + call nc, dzx0t_elias + jp dzx0t_copy +dzx0t_elias: + add a, a ; interlaced Elias gamma coding + rl c + add a, a + jr nc, dzx0t_elias + ret nz + ld a, (hl) ; load another group of 8 bits + inc hl + rla + ret c + add a, a + rl c + add a, a + ret c + add a, a + rl c + add a, a + ret c + add a, a + rl c + add a, a + ret c +dzx0t_elias_loop: + add a, a + rl c + rl b + add a, a + jr nc, dzx0t_elias_loop + ret nz + ld a, (hl) ; load another group of 8 bits + inc hl + rla + jr nc, dzx0t_elias_loop + ret +; ----------------------------------------------------------------------------- diff --git a/Content/tools/zx0/z80/dzx0_fast.asm b/Content/tools/zx0/z80/dzx0_fast.asm new file mode 100644 index 0000000..b908145 --- /dev/null +++ b/Content/tools/zx0/z80/dzx0_fast.asm @@ -0,0 +1,235 @@ +; +; Speed-optimized ZX0 decompressor by spke (187 bytes) +; +; ver.00 by spke (27/01-23/03/2021, 191 bytes) +; ver.01 by spke (24/03/2021, 193(+2) bytes - fixed a bug in the initialization) +; ver.01patch2 by uniabis (25/03/2021, 191(-2) bytes - fixed a bug with elias over 8bits) +; ver.01patch9 by uniabis (10/09/2021, 187(-4) bytes - support for new v2 format) +; +; Original ZX0 decompressors were written by Einar Saukas +; +; This decompressor was written on the basis of "Standard" decompressor by +; Einar Saukas and optimized for speed by spke. This decompressor is +; about 5% faster than the "Turbo" decompressor, which is 128 bytes long. +; It has about the same speed as the 412 bytes version of the "Mega" decompressor. +; +; The decompressor uses AF, BC, DE, HL and IX and relies upon self-modified code. +; +; The decompression is done in the standard way: +; +; ld hl,FirstByteOfCompressedData +; ld de,FirstByteOfMemoryForDecompressedData +; call DecompressZX0 +; +; Of course, ZX0 compression algorithms are (c) 2021 Einar Saukas, +; see https://github.com/einar-saukas/ZX0 for more information +; +; Drop me an email if you have any comments/ideas/suggestions: zxintrospec@gmail.com +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. + +DecompressZX0: + + ld ix, CopyMatch1 + ld bc, $ffff + ld (PrevOffset+1), bc ; default offset is -1 + inc bc + ld a, $80 + jr RunOfLiterals ; BC is assumed to contains 0 most of the time + +ShorterOffsets: + ld b, $ff ; the top byte of the offset is always $FF + ld c, (hl) + inc hl + rr c + ld (PrevOffset+1), bc + jr nc, LongerMatch + +CopyMatch2: ; the case of matches with len=2 + ld bc, 2 + + ; the faster match copying code +CopyMatch1: + push hl ; preserve source + +PrevOffset: + ld hl, $ffff ; restore offset (default offset is -1) + add hl, de ; HL = dest - offset + ldir + pop hl ; restore source + + ; after a match you can have either + ; 0 + = run of literals, or + ; 1 + + [7-bits of offset lsb + 1-bit of length] + = another match +AfterMatch1: + add a, a + jr nc, RunOfLiterals + +UsualMatch: ; this is the case of usual match+offset + add a, a + jr nc, LongerOffets + jr nz, ShorterOffsets ; NZ after NC == "confirmed C" + + ld a, (hl) ; reload bits + inc hl + rla + + jr c, ShorterOffsets + +LongerOffets: + ld c, $fe + + add a, a ; inline read gamma + rl c + add a, a + jr nc, $-4 + + call z, ReloadReadGamma + +ProcessOffset: + + inc c + ret z ; end-of-data marker (only checked for longer offsets) + rr c + ld b, c + ld c, (hl) + inc hl + rr c + ld (PrevOffset+1), bc + + ; lowest bit is the first bit of the gamma code for length + jr c, CopyMatch2 + +LongerMatch: + ld bc, 1 + + add a, a ; inline read gamma + rl c + add a, a + jr nc, $-4 + + call z,ReloadReadGamma + +CopyMatch3: + push hl ; preserve source + ld hl, (PrevOffset+1) ; restore offset + add hl, de ; HL = dest - offset + + ; because BC>=3-1, we can do 2 x LDI safely + ldi + ldir + inc c + ldi + pop hl ; restore source + + ; after a match you can have either + ; 0 + = run of literals, or + ; 1 + + [7-bits of offset lsb + 1-bit of length] + = another match +AfterMatch3: + add a, a + jr c, UsualMatch + +RunOfLiterals: + inc c + add a, a + jr nc, LongerRun + jr nz, CopyLiteral ; NZ after NC == "confirmed C" + + ld a, (hl) ; reload bits + inc hl + rla + + jr c, CopyLiteral + +LongerRun: + add a, a ; inline read gamma + rl c + add a, a + jr nc, $-4 + + jr nz, CopyLiterals + + ld a, (hl) ; reload bits + inc hl + rla + + call nc, ReadGammaAligned + +CopyLiterals: + ldi + +CopyLiteral: + ldir + + ; after a literal run you can have either + ; 0 + = match using a repeated offset, or + ; 1 + + [7-bits of offset lsb + 1-bit of length] + = another match + add a, a + jr c, UsualMatch + +RepMatch: + inc c + add a, a + jr nc, LongerRepMatch + jr nz, CopyMatch1 ; NZ after NC == "confirmed C" + + ld a, (hl) ; reload bits + inc hl + rla + + jr c, CopyMatch1 + +LongerRepMatch: + add a, a ; inline read gamma + rl c + add a, a + jr nc, $-4 + + jp nz, CopyMatch1 + + ; this is a crafty equivalent of CALL ReloadReadGamma : JP CopyMatch1 + push ix + + ; the subroutine for reading the remainder of the partly read Elias gamma code. + ; it has two entry points: ReloadReadGamma first refills the bit reservoir in A, + ; while ReadGammaAligned assumes that the bit reservoir has just been refilled. +ReloadReadGamma: + ld a, (hl) ; reload bits + inc hl + rla + + ret c +ReadGammaAligned: + add a, a + rl c + add a, a + ret c + add a, a + rl c + add a, a +ReadingLongGamma: ; this loop does not need unrolling, as it does not get much use anyway + ret c + add a, a + rl c + rl b + add a, a + jr nz, ReadingLongGamma + + ld a, (hl) ; reload bits + inc hl + rla + jr ReadingLongGamma diff --git a/Content/tools/zx0/z80/dzx0_mega.asm b/Content/tools/zx0/z80/dzx0_mega.asm new file mode 100644 index 0000000..c3713d4 --- /dev/null +++ b/Content/tools/zx0/z80/dzx0_mega.asm @@ -0,0 +1,452 @@ +; ----------------------------------------------------------------------------- +; ZX0 decoder by Einar Saukas +; "Mega" version (673 bytes, 28% faster) +; ----------------------------------------------------------------------------- +; Parameters: +; HL: source address (compressed data) +; DE: destination address (decompressing) +; ----------------------------------------------------------------------------- + +dzx0_mega: + ld bc, $ffff ; preserve default offset 1 + ld (dzx0m_last_offset+1), bc + inc bc + jr dzx0m_literals0 + +dzx0m_new_offset6: + ld c, $fe ; prepare negative offset + add a, a ; obtain offset MSB + jp c, dzx0m_new_offset5 + add a, a + rl c + add a, a + jp c, dzx0m_new_offset3 + add a, a + rl c + add a, a + jp c, dzx0m_new_offset1 +dzx0m_elias_offset1: + add a, a + rl c + rl b + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp nc, dzx0m_elias_offset7 +dzx0m_new_offset7: + inc c + ret z ; check end marker + ld b, c + ld c, (hl) ; obtain offset LSB + inc hl + rr b ; last offset bit becomes first length bit + rr c + ld (dzx0m_last_offset+1), bc ; preserve new offset + ld bc, 1 + jp c, dzx0m_length7 ; obtain length + add a, a + rl c + add a, a + jp c, dzx0m_length5 + add a, a + rl c + add a, a + jp c, dzx0m_length3 +dzx0m_elias_length3: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_length1 +dzx0m_length1: + push hl ; preserve source + ld hl, (dzx0m_last_offset+1) + add hl, de ; calculate destination - offset + ldir ; copy from offset + inc c + ldi ; copy one more from offset + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr c, dzx0m_new_offset0 +dzx0m_literals0: + inc c + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a ; obtain length + jp c, dzx0m_literals7 + add a, a + rl c + add a, a + jp c, dzx0m_literals5 + add a, a + rl c + add a, a + jp c, dzx0m_literals3 +dzx0m_elias_literals3: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_literals1 +dzx0m_literals1: + ldir ; copy literals + add a, a ; copy from last offset or new offset? + jr c, dzx0m_new_offset0 + inc c + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a ; obtain length + jp c, dzx0m_reuse7 + add a, a + rl c + add a, a + jp c, dzx0m_reuse5 + add a, a + rl c + add a, a + jp c, dzx0m_reuse3 +dzx0m_elias_reuse3: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_reuse1 +dzx0m_reuse1: + push hl ; preserve source + ld hl, (dzx0m_last_offset+1) + add hl, de ; calculate destination - offset + ldir ; copy from offset + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr nc, dzx0m_literals0 + +dzx0m_new_offset0: + ld c, $fe ; prepare negative offset + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a ; obtain offset MSB + jp c, dzx0m_new_offset7 + add a, a + rl c + add a, a + jp c, dzx0m_new_offset5 + add a, a + rl c + add a, a + jp c, dzx0m_new_offset3 +dzx0m_elias_offset3: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_offset1 +dzx0m_new_offset1: + inc c + ret z ; check end marker + ld b, c + ld c, (hl) ; obtain offset LSB + inc hl + rr b ; last offset bit becomes first length bit + rr c + ld (dzx0m_last_offset+1), bc ; preserve new offset + ld bc, 1 + jp c, dzx0m_length1 ; obtain length + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp c, dzx0m_length7 + add a, a + rl c + add a, a + jp c, dzx0m_length5 +dzx0m_elias_length5: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_length3 +dzx0m_length3: + push hl ; preserve source + ld hl, (dzx0m_last_offset+1) + add hl, de ; calculate destination - offset + ldir ; copy from offset + inc c + ldi ; copy one more from offset + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr c, dzx0m_new_offset2 +dzx0m_literals2: + inc c + add a, a ; obtain length + jp c, dzx0m_literals1 + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp c, dzx0m_literals7 + add a, a + rl c + add a, a + jp c, dzx0m_literals5 +dzx0m_elias_literals5: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_literals3 +dzx0m_literals3: + ldir ; copy literals + add a, a ; copy from last offset or new offset? + jr c, dzx0m_new_offset2 + inc c + add a, a ; obtain length + jp c, dzx0m_reuse1 + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp c, dzx0m_reuse7 + add a, a + rl c + add a, a + jp c, dzx0m_reuse5 +dzx0m_elias_reuse5: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_reuse3 +dzx0m_reuse3: + push hl ; preserve source + ld hl, (dzx0m_last_offset+1) + add hl, de ; calculate destination - offset + ldir ; copy from offset + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr nc, dzx0m_literals2 + +dzx0m_new_offset2: + ld c, $fe ; prepare negative offset + add a, a ; obtain offset MSB + jp c, dzx0m_new_offset1 + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp c, dzx0m_new_offset7 + add a, a + rl c + add a, a + jp c, dzx0m_new_offset5 +dzx0m_elias_offset5: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_offset3 +dzx0m_new_offset3: + inc c + ret z ; check end marker + ld b, c + ld c, (hl) ; obtain offset LSB + inc hl + rr b ; last offset bit becomes first length bit + rr c + ld (dzx0m_last_offset+1), bc ; preserve new offset + ld bc, 1 + jp c, dzx0m_length3 ; obtain length + add a, a + rl c + add a, a + jp c, dzx0m_length1 + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp c, dzx0m_length7 +dzx0m_elias_length7: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_length5 +dzx0m_length5: + push hl ; preserve source + ld hl, (dzx0m_last_offset+1) + add hl, de ; calculate destination - offset + ldir ; copy from offset + inc c + ldi ; copy one more from offset + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr c, dzx0m_new_offset4 +dzx0m_literals4: + inc c + add a, a ; obtain length + jp c, dzx0m_literals3 + add a, a + rl c + add a, a + jp c, dzx0m_literals1 + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp c, dzx0m_literals7 +dzx0m_elias_literals7: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_literals5 +dzx0m_literals5: + ldir ; copy literals + add a, a ; copy from last offset or new offset? + jr c, dzx0m_new_offset4 + inc c + add a, a ; obtain length + jp c, dzx0m_reuse3 + add a, a + rl c + add a, a + jp c, dzx0m_reuse1 + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp c, dzx0m_reuse7 +dzx0m_elias_reuse7: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_reuse5 +dzx0m_reuse5: + push hl ; preserve source + ld hl, (dzx0m_last_offset+1) + add hl, de ; calculate destination - offset + ldir ; copy from offset + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr nc, dzx0m_literals4 + +dzx0m_new_offset4: + ld c, $fe ; prepare negative offset + add a, a ; obtain offset MSB + jp c, dzx0m_new_offset3 + add a, a + rl c + add a, a + jp c, dzx0m_new_offset1 + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp c, dzx0m_new_offset7 +dzx0m_elias_offset7: + add a, a + rl c + rl b + add a, a + jp nc, dzx0m_elias_offset5 +dzx0m_new_offset5: + inc c + ret z ; check end marker + ld b, c + ld c, (hl) ; obtain offset LSB + inc hl + rr b ; last offset bit becomes first length bit + rr c + ld (dzx0m_last_offset+1), bc ; preserve new offset + ld bc, 1 + jp c, dzx0m_length5 ; obtain length + add a, a + rl c + add a, a + jp c, dzx0m_length3 + add a, a + rl c + add a, a + jp c, dzx0m_length1 +dzx0m_elias_length1: + add a, a + rl c + rl b + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp nc, dzx0m_elias_length7 +dzx0m_length7: + push hl ; preserve source + ld hl, (dzx0m_last_offset+1) + add hl, de ; calculate destination - offset + ldir ; copy from offset + inc c + ldi ; copy one more from offset + pop hl ; restore source + add a, a ; copy from literals or new offset? + jp c, dzx0m_new_offset6 +dzx0m_literals6: + inc c + add a, a ; obtain length + jp c, dzx0m_literals5 + add a, a + rl c + add a, a + jp c, dzx0m_literals3 + add a, a + rl c + add a, a + jp c, dzx0m_literals1 +dzx0m_elias_literals1: + add a, a + rl c + rl b + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp nc, dzx0m_elias_literals7 +dzx0m_literals7: + ldir ; copy literals + add a, a ; copy from last offset or new offset? + jp c, dzx0m_new_offset6 + inc c + add a, a ; obtain length + jp c, dzx0m_reuse5 + add a, a + rl c + add a, a + jp c, dzx0m_reuse3 + add a, a + rl c + add a, a + jp c, dzx0m_reuse1 +dzx0m_elias_reuse1: + add a, a + rl c + rl b + ld a, (hl) ; load another group of 8 bits + inc hl + add a, a + jp nc, dzx0m_elias_reuse7 +dzx0m_reuse7: + push hl ; preserve source +dzx0m_last_offset: + ld hl, 0 + add hl, de ; calculate destination - offset + ldir ; copy from offset + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr nc, dzx0m_literals6 + + jp dzx0m_new_offset6 +; ----------------------------------------------------------------------------- diff --git a/Content/tools/zx0/z80/dzx0_mega_back.asm b/Content/tools/zx0/z80/dzx0_mega_back.asm new file mode 100644 index 0000000..bdbc4b7 --- /dev/null +++ b/Content/tools/zx0/z80/dzx0_mega_back.asm @@ -0,0 +1,459 @@ +; ----------------------------------------------------------------------------- +; ZX0 decoder by Einar Saukas & introspec +; "Mega" version (676 bytes, 28% faster) - BACKWARDS VARIANT +; ----------------------------------------------------------------------------- +; Parameters: +; HL: last source address (compressed data) +; DE: last destination address (decompressing) +; ----------------------------------------------------------------------------- + +dzx0_mega_back: + ld bc, 1 ; preserve default offset 1 + ld (dzx0mb_last_offset+1), bc + jr dzx0mb_literals0 + +dzx0mb_new_offset6: + add a, a ; obtain offset MSB + jp nc, dzx0mb_new_offset5 + add a, a + rl c + add a, a + jp nc, dzx0mb_new_offset3 + add a, a + rl c + add a, a + jp nc, dzx0mb_new_offset1 +dzx0mb_elias_offset1: + add a, a + rl c + rl b + ld a, (hl) ; load another group of 8 bits + dec hl + add a, a + jp c, dzx0mb_elias_offset7 +dzx0mb_new_offset7: + dec b + ret z ; check end marker + dec c ; adjust for positive offset + ld b, c + ld c, (hl) ; obtain offset LSB + dec hl + srl b ; last offset bit becomes first length bit + rr c + inc bc + ld (dzx0mb_last_offset+1), bc ; preserve new offset + ld bc, 1 + jp nc, dzx0mb_length7 ; obtain length + add a, a + rl c + add a, a + jp nc, dzx0mb_length5 + add a, a + rl c + add a, a + jp nc, dzx0mb_length3 +dzx0mb_elias_length3: + add a, a + rl c + rl b + add a, a + jp c, dzx0mb_elias_length1 +dzx0mb_length1: + push hl ; preserve source + ld hl, (dzx0mb_last_offset+1) + add hl, de ; calculate destination - offset + lddr ; copy from offset + inc c + ldd ; copy one more from offset + inc c + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr c, dzx0mb_new_offset0 +dzx0mb_literals0: + ld a, (hl) ; load another group of 8 bits + dec hl + add a, a ; obtain length + jp nc, dzx0mb_literals7 + add a, a + rl c + add a, a + jp nc, dzx0mb_literals5 + add a, a + rl c + add a, a + jp nc, dzx0mb_literals3 +dzx0mb_elias_literals3: + add a, a + rl c + rl b + add a, a + jp c, dzx0mb_elias_literals1 +dzx0mb_literals1: + lddr ; copy literals + inc c + add a, a ; copy from last offset or new offset? + jr c, dzx0mb_new_offset0 + ld a, (hl) ; load another group of 8 bits + dec hl + add a, a ; obtain length + jp nc, dzx0mb_reuse7 + add a, a + rl c + add a, a + jp nc, dzx0mb_reuse5 + add a, a + rl c + add a, a + jp nc, dzx0mb_reuse3 +dzx0mb_elias_reuse3: + add a, a + rl c + rl b + add a, a + jp c, dzx0mb_elias_reuse1 +dzx0mb_reuse1: + push hl ; preserve source + ld hl, (dzx0mb_last_offset+1) + add hl, de ; calculate destination - offset + lddr ; copy from offset + inc c + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr nc, dzx0mb_literals0 + +dzx0mb_new_offset0: + ld a, (hl) ; load another group of 8 bits + dec hl + add a, a ; obtain offset MSB + jp nc, dzx0mb_new_offset7 + add a, a + rl c + add a, a + jp nc, dzx0mb_new_offset5 + add a, a + rl c + add a, a + jp nc, dzx0mb_new_offset3 +dzx0mb_elias_offset3: + add a, a + rl c + rl b + add a, a + jp c, dzx0mb_elias_offset1 +dzx0mb_new_offset1: + dec b + ret z ; check end marker + dec c ; adjust for positive offset + ld b, c + ld c, (hl) ; obtain offset LSB + dec hl + srl b ; last offset bit becomes first length bit + rr c + inc bc + ld (dzx0mb_last_offset+1), bc ; preserve new offset + ld bc, 1 + jp nc, dzx0mb_length1 ; obtain length + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + dec hl + add a, a + jp nc, dzx0mb_length7 + add a, a + rl c + add a, a + jp nc, dzx0mb_length5 +dzx0mb_elias_length5: + add a, a + rl c + rl b + add a, a + jp c, dzx0mb_elias_length3 +dzx0mb_length3: + push hl ; preserve source + ld hl, (dzx0mb_last_offset+1) + add hl, de ; calculate destination - offset + lddr ; copy from offset + inc c + ldd ; copy one more from offset + inc c + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr c, dzx0mb_new_offset2 +dzx0mb_literals2: + add a, a ; obtain length + jp nc, dzx0mb_literals1 + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + dec hl + add a, a + jp nc, dzx0mb_literals7 + add a, a + rl c + add a, a + jp nc, dzx0mb_literals5 +dzx0mb_elias_literals5: + add a, a + rl c + rl b + add a, a + jp c, dzx0mb_elias_literals3 +dzx0mb_literals3: + lddr ; copy literals + inc c + add a, a ; copy from last offset or new offset? + jr c, dzx0mb_new_offset2 + add a, a ; obtain length + jp nc, dzx0mb_reuse1 + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + dec hl + add a, a + jp nc, dzx0mb_reuse7 + add a, a + rl c + add a, a + jp nc, dzx0mb_reuse5 +dzx0mb_elias_reuse5: + add a, a + rl c + rl b + add a, a + jp c, dzx0mb_elias_reuse3 +dzx0mb_reuse3: + push hl ; preserve source + ld hl, (dzx0mb_last_offset+1) + add hl, de ; calculate destination - offset + lddr ; copy from offset + inc c + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr nc, dzx0mb_literals2 + +dzx0mb_new_offset2: + add a, a ; obtain offset MSB + jp nc, dzx0mb_new_offset1 + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + dec hl + add a, a + jp nc, dzx0mb_new_offset7 + add a, a + rl c + add a, a + jp nc, dzx0mb_new_offset5 +dzx0mb_elias_offset5: + add a, a + rl c + rl b + add a, a + jp c, dzx0mb_elias_offset3 +dzx0mb_new_offset3: + dec b + ret z ; check end marker + dec c ; adjust for positive offset + ld b, c + ld c, (hl) ; obtain offset LSB + dec hl + srl b ; last offset bit becomes first length bit + rr c + inc bc + ld (dzx0mb_last_offset+1), bc ; preserve new offset + ld bc, 1 + jp nc, dzx0mb_length3 ; obtain length + add a, a + rl c + add a, a + jp nc, dzx0mb_length1 + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + dec hl + add a, a + jp nc, dzx0mb_length7 +dzx0mb_elias_length7: + add a, a + rl c + rl b + add a, a + jp c, dzx0mb_elias_length5 +dzx0mb_length5: + push hl ; preserve source + ld hl, (dzx0mb_last_offset+1) + add hl, de ; calculate destination - offset + lddr ; copy from offset + inc c + ldd ; copy one more from offset + inc c + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr c, dzx0mb_new_offset4 +dzx0mb_literals4: + add a, a ; obtain length + jp nc, dzx0mb_literals3 + add a, a + rl c + add a, a + jp nc, dzx0mb_literals1 + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + dec hl + add a, a + jp nc, dzx0mb_literals7 +dzx0mb_elias_literals7: + add a, a + rl c + rl b + add a, a + jp c, dzx0mb_elias_literals5 +dzx0mb_literals5: + lddr ; copy literals + inc c + add a, a ; copy from last offset or new offset? + jr c, dzx0mb_new_offset4 + add a, a ; obtain length + jp nc, dzx0mb_reuse3 + add a, a + rl c + add a, a + jp nc, dzx0mb_reuse1 + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + dec hl + add a, a + jp nc, dzx0mb_reuse7 +dzx0mb_elias_reuse7: + add a, a + rl c + rl b + add a, a + jp c, dzx0mb_elias_reuse5 +dzx0mb_reuse5: + push hl ; preserve source + ld hl, (dzx0mb_last_offset+1) + add hl, de ; calculate destination - offset + lddr ; copy from offset + inc c + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr nc, dzx0mb_literals4 + +dzx0mb_new_offset4: + add a, a ; obtain offset MSB + jp nc, dzx0mb_new_offset3 + add a, a + rl c + add a, a + jp nc, dzx0mb_new_offset1 + add a, a + rl c + ld a, (hl) ; load another group of 8 bits + dec hl + add a, a + jp nc, dzx0mb_new_offset7 +dzx0mb_elias_offset7: + add a, a + rl c + rl b + add a, a + jp c, dzx0mb_elias_offset5 +dzx0mb_new_offset5: + dec b + ret z ; check end marker + dec c ; adjust for positive offset + ld b, c + ld c, (hl) ; obtain offset LSB + dec hl + srl b ; last offset bit becomes first length bit + rr c + inc bc + ld (dzx0mb_last_offset+1), bc ; preserve new offset + ld bc, 1 + jp nc, dzx0mb_length5 ; obtain length + add a, a + rl c + add a, a + jp nc, dzx0mb_length3 + add a, a + rl c + add a, a + jp nc, dzx0mb_length1 +dzx0mb_elias_length1: + add a, a + rl c + rl b + ld a, (hl) ; load another group of 8 bits + dec hl + add a, a + jp c, dzx0mb_elias_length7 +dzx0mb_length7: + push hl ; preserve source + ld hl, (dzx0mb_last_offset+1) + add hl, de ; calculate destination - offset + lddr ; copy from offset + inc c + ldd ; copy one more from offset + inc c + pop hl ; restore source + add a, a ; copy from literals or new offset? + jp c, dzx0mb_new_offset6 +dzx0mb_literals6: + add a, a ; obtain length + jp nc, dzx0mb_literals5 + add a, a + rl c + add a, a + jp nc, dzx0mb_literals3 + add a, a + rl c + add a, a + jp nc, dzx0mb_literals1 +dzx0mb_elias_literals1: + add a, a + rl c + rl b + ld a, (hl) ; load another group of 8 bits + dec hl + add a, a + jp c, dzx0mb_elias_literals7 +dzx0mb_literals7: + lddr ; copy literals + inc c + add a, a ; copy from last offset or new offset? + jp c, dzx0mb_new_offset6 + add a, a ; obtain length + jp nc, dzx0mb_reuse5 + add a, a + rl c + add a, a + jp nc, dzx0mb_reuse3 + add a, a + rl c + add a, a + jp nc, dzx0mb_reuse1 +dzx0mb_elias_reuse1: + add a, a + rl c + rl b + ld a, (hl) ; load another group of 8 bits + dec hl + add a, a + jp c, dzx0mb_elias_reuse7 +dzx0mb_reuse7: + push hl ; preserve source +dzx0mb_last_offset: + ld hl, 0 + add hl, de ; calculate destination - offset + lddr ; copy from offset + inc c + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr nc, dzx0mb_literals6 + + jp dzx0mb_new_offset6 +; ----------------------------------------------------------------------------- diff --git a/Content/tools/zx0/z80/dzx0_standard.asm b/Content/tools/zx0/z80/dzx0_standard.asm new file mode 100644 index 0000000..ec8d3dc --- /dev/null +++ b/Content/tools/zx0/z80/dzx0_standard.asm @@ -0,0 +1,61 @@ +; ----------------------------------------------------------------------------- +; ZX0 decoder by Einar Saukas & Urusergi +; "Standard" version (68 bytes only) +; ----------------------------------------------------------------------------- +; Parameters: +; HL: source address (compressed data) +; DE: destination address (decompressing) +; ----------------------------------------------------------------------------- + +dzx0_standard: + ld bc, $ffff ; preserve default offset 1 + push bc + inc bc + ld a, $80 +dzx0s_literals: + call dzx0s_elias ; obtain length + ldir ; copy literals + add a, a ; copy from last offset or new offset? + jr c, dzx0s_new_offset + call dzx0s_elias ; obtain length +dzx0s_copy: + ex (sp), hl ; preserve source, restore offset + push hl ; preserve offset + add hl, de ; calculate destination - offset + ldir ; copy from offset + pop hl ; restore offset + ex (sp), hl ; preserve offset, restore source + add a, a ; copy from literals or new offset? + jr nc, dzx0s_literals +dzx0s_new_offset: + pop bc ; discard last offset + ld c, $fe ; prepare negative offset + call dzx0s_elias_loop ; obtain offset MSB + inc c + ret z ; check end marker + ld b, c + ld c, (hl) ; obtain offset LSB + inc hl + rr b ; last offset bit becomes first length bit + rr c + push bc ; preserve new offset + ld bc, 1 ; obtain length + call nc, dzx0s_elias_backtrack + inc bc + jr dzx0s_copy +dzx0s_elias: + inc c ; interlaced Elias gamma coding +dzx0s_elias_loop: + add a, a + jr nz, dzx0s_elias_skip + ld a, (hl) ; load another group of 8 bits + inc hl + rla +dzx0s_elias_skip: + ret c +dzx0s_elias_backtrack: + add a, a + rl c + rl b + jr dzx0s_elias_loop +; ----------------------------------------------------------------------------- diff --git a/Content/tools/zx0/z80/dzx0_standard_back.asm b/Content/tools/zx0/z80/dzx0_standard_back.asm new file mode 100644 index 0000000..2a52af8 --- /dev/null +++ b/Content/tools/zx0/z80/dzx0_standard_back.asm @@ -0,0 +1,62 @@ +; ----------------------------------------------------------------------------- +; ZX0 decoder by Einar Saukas +; "Standard" version (69 bytes only) - BACKWARDS VARIANT +; ----------------------------------------------------------------------------- +; Parameters: +; HL: last source address (compressed data) +; DE: last destination address (decompressing) +; ----------------------------------------------------------------------------- + +dzx0_standard_back: + ld bc, 1 ; preserve default offset 1 + push bc + ld a, $80 +dzx0sb_literals: + call dzx0sb_elias ; obtain length + lddr ; copy literals + inc c + add a, a ; copy from last offset or new offset? + jr c, dzx0sb_new_offset + call dzx0sb_elias ; obtain length +dzx0sb_copy: + ex (sp), hl ; preserve source, restore offset + push hl ; preserve offset + add hl, de ; calculate destination - offset + lddr ; copy from offset + inc c + pop hl ; restore offset + ex (sp), hl ; preserve offset, restore source + add a, a ; copy from literals or new offset? + jr nc, dzx0sb_literals +dzx0sb_new_offset: + inc sp ; discard last offset + inc sp + call dzx0sb_elias ; obtain offset MSB + dec b + ret z ; check end marker + dec c ; adjust for positive offset + ld b, c + ld c, (hl) ; obtain offset LSB + dec hl + srl b ; last offset bit becomes first length bit + rr c + inc bc + push bc ; preserve new offset + ld bc, 1 ; obtain length + call c, dzx0sb_elias_backtrack + inc bc + jr dzx0sb_copy +dzx0sb_elias_backtrack: + add a, a + rl c + rl b +dzx0sb_elias: + add a, a ; inverted interlaced Elias gamma coding + jr nz, dzx0sb_elias_skip + ld a, (hl) ; load another group of 8 bits + dec hl + rla +dzx0sb_elias_skip: + jr c, dzx0sb_elias_backtrack + ret +; ----------------------------------------------------------------------------- diff --git a/Content/tools/zx0/z80/dzx0_turbo.asm b/Content/tools/zx0/z80/dzx0_turbo.asm new file mode 100644 index 0000000..eb0ce28 --- /dev/null +++ b/Content/tools/zx0/z80/dzx0_turbo.asm @@ -0,0 +1,100 @@ +; ----------------------------------------------------------------------------- +; ZX0 decoder by Einar Saukas & introspec +; "Turbo" version (126 bytes, 21% faster) +; ----------------------------------------------------------------------------- +; Parameters: +; HL: source address (compressed data) +; DE: destination address (decompressing) +; ----------------------------------------------------------------------------- + +dzx0_turbo: + ld bc, $ffff ; preserve default offset 1 + ld (dzx0t_last_offset+1), bc + inc bc + ld a, $80 + jr dzx0t_literals +dzx0t_new_offset: + ld c, $fe ; prepare negative offset + add a, a + jp nz, dzx0t_new_offset_skip + ld a, (hl) ; load another group of 8 bits + inc hl + rla +dzx0t_new_offset_skip: + call nc, dzx0t_elias ; obtain offset MSB + inc c + ret z ; check end marker + ld b, c + ld c, (hl) ; obtain offset LSB + inc hl + rr b ; last offset bit becomes first length bit + rr c + ld (dzx0t_last_offset+1), bc ; preserve new offset + ld bc, 1 ; obtain length + call nc, dzx0t_elias + inc bc +dzx0t_copy: + push hl ; preserve source +dzx0t_last_offset: + ld hl, 0 ; restore offset + add hl, de ; calculate destination - offset + ldir ; copy from offset + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr c, dzx0t_new_offset +dzx0t_literals: + inc c ; obtain length + add a, a + jp nz, dzx0t_literals_skip + ld a, (hl) ; load another group of 8 bits + inc hl + rla +dzx0t_literals_skip: + call nc, dzx0t_elias + ldir ; copy literals + add a, a ; copy from last offset or new offset? + jr c, dzx0t_new_offset + inc c ; obtain length + add a, a + jp nz, dzx0t_last_offset_skip + ld a, (hl) ; load another group of 8 bits + inc hl + rla +dzx0t_last_offset_skip: + call nc, dzx0t_elias + jp dzx0t_copy +dzx0t_elias: + add a, a ; interlaced Elias gamma coding + rl c + add a, a + jr nc, dzx0t_elias + ret nz + ld a, (hl) ; load another group of 8 bits + inc hl + rla + ret c + add a, a + rl c + add a, a + ret c + add a, a + rl c + add a, a + ret c + add a, a + rl c + add a, a + ret c +dzx0t_elias_loop: + add a, a + rl c + rl b + add a, a + jr nc, dzx0t_elias_loop + ret nz + ld a, (hl) ; load another group of 8 bits + inc hl + rla + jr nc, dzx0t_elias_loop + ret +; ----------------------------------------------------------------------------- diff --git a/Content/tools/zx0/z80/dzx0_turbo_back.asm b/Content/tools/zx0/z80/dzx0_turbo_back.asm new file mode 100644 index 0000000..d8cfaa6 --- /dev/null +++ b/Content/tools/zx0/z80/dzx0_turbo_back.asm @@ -0,0 +1,99 @@ +; ----------------------------------------------------------------------------- +; ZX0 decoder by Einar Saukas & introspec +; "Turbo" version (126 bytes, 21% faster) - BACKWARDS VARIANT +; ----------------------------------------------------------------------------- +; Parameters: +; HL: last source address (compressed data) +; DE: last destination address (decompressing) +; ----------------------------------------------------------------------------- + +dzx0_turbo_back: + ld bc, 1 ; preserve default offset 1 + ld (dzx0tb_last_offset+1), bc + ld a, $80 + jr dzx0tb_literals +dzx0tb_new_offset: + add a, a ; obtain offset MSB + call c, dzx0tb_elias + dec b + ret z ; check end marker + dec c ; adjust for positive offset + ld b, c + ld c, (hl) ; obtain offset LSB + dec hl + srl b ; last offset bit becomes first length bit + rr c + inc bc + ld (dzx0tb_last_offset+1), bc ; preserve new offset + ld bc, 1 ; obtain length + call c, dzx0tb_elias_loop + inc bc +dzx0tb_copy: + push hl ; preserve source +dzx0tb_last_offset: + ld hl, 0 ; restore offset + add hl, de ; calculate destination - offset + lddr ; copy from offset + inc c + pop hl ; restore source + add a, a ; copy from literals or new offset? + jr c, dzx0tb_new_offset +dzx0tb_literals: + add a, a ; obtain length + call c, dzx0tb_elias + lddr ; copy literals + inc c + add a, a ; copy from last offset or new offset? + jr c, dzx0tb_new_offset + add a, a ; obtain length + call c, dzx0tb_elias + jp dzx0tb_copy +dzx0tb_elias_loop: + add a, a + rl c + add a, a + ret nc +dzx0tb_elias: + jp nz, dzx0tb_elias_loop ; inverted interlaced Elias gamma coding + ld a, (hl) ; load another group of 8 bits + dec hl + rla + ret nc + add a, a + rl c + add a, a + ret nc + add a, a + rl c + add a, a + ret nc + add a, a + rl c + add a, a + ret nc +dzx0tb_elias_reload: + add a, a + rl c + rl b + add a, a + ld a, (hl) ; load another group of 8 bits + dec hl + rla + ret nc + add a, a + rl c + rl b + add a, a + ret nc + add a, a + rl c + rl b + add a, a + ret nc + add a, a + rl c + rl b + add a, a + jr c, dzx0tb_elias_reload + ret +; ----------------------------------------------------------------------------- diff --git a/FW/src/makefile b/FW/src/makefile index 9dd527d..45f38da 100644 --- a/FW/src/makefile +++ b/FW/src/makefile @@ -1,6 +1,6 @@ # Для тестирования модуля неодходимо чтобы тест назывался %имя_модуля%_tb -TARGET ?= zx_cartrige +TARGET ?= zx_cartridge ICARUS = iverilog all: diff --git a/FW/src/zx_cartridge.v b/FW/src/zx_cartridge.v new file mode 100644 index 0000000..b52c280 --- /dev/null +++ b/FW/src/zx_cartridge.v @@ -0,0 +1,142 @@ +`timescale 1ns / 1ps +// Модуль картриджа для ZX Spectrum +// 19.02.2026 Mikhael Kaa +// +// Аппаратная часть: +// - 4 микросхем AM29F040 (по 512 КБ) -> всего 2 МБ = 256 страницы по 8 КБ. +// - Адресные линии CPU A0..A12 подключены напрямую ко всем микросхемам ПЗУ. +// - Старшие линии адреса A13..A18 формируются внутри этого модуля. +// +// Окно памяти 0x0000..0x3FFF (16 КБ) разделено на две 8‑килобайтные половины: +// - Нижняя половина (A13 = 0, 0x0000..0x1FFF) : всегда отображается на страницу 0 (BIOS картриджа). +// - Верхняя половина (A13 = 1, 0x2000..0x3FFF) : отображается на выбираемую страницу. +// +// Выбор страницы: +// 8‑битный номер страницы формируется как reg_bank[7:0]. +// - биты [7:6] : выбор одной из четырех микросхем (0‑3). +// - биты [5:0] : выбор 8‑килобайтной страницы внутри выбранной микросхемы (0‑63). +// Таким образом, можно адресовать любую из 256 страниц. +// +// Регистр управления reg_ctl (8 бит): +// reg_ctl[7] : при установке в 1 отключает ПЗУ картриджа (все выходы пассивны). +// Остальные биты зарезервированы. +// +// Порты ввода‑вывода (запись/чтение, активный уровень низкий): +// bank : запись reg_bank (биты 7..0) – происходит, когда +// A15=1, A14=1, A13=0, A7=0, IORQ=0, WR=0. Порт 0xdf7f. +// control : запись регистра управления reg_ctl – происходит, когда +// A15=1, A14=0, A13=1, A7=0, IORQ=0, WR=0. Порт 0xbf7f. +// Чтение любого из этих портов возвращает значение соответствующего регистра. +// +// Выходы: +// ZX_ROM_blk – активный высокий уровень; блокирует внутреннее ПЗУ ZX Spectrum. +// CR_ROM_oe_n – выход разрешения выходов для всех микросхем ПЗУ (активный низкий). +// CR_ROM_A[5:0] – линии адреса A13..A18 для микросхем ПЗУ. +// Для нижнего окна (A13=0) на эту шину выставляется 0. +// Для верхнего окна (A13=1) на ней передаётся 6‑битное смещение страницы. +// CR_ROM_CS[3:0] – выбор микросхем (активный низкий). Одна линия становится низкой +// только при обращении к картриджу (cpu_use_rom, MREQ и RD активны, +// картридж не отключён) и совпадении выбранной микросхемы. +// В нижнем окне всегда выбирается микросхема 0. +// +// Сброс: почти все регистры асинхронно очищаются низким уровнем reset_n. + +module zx_cartridge ( + // Сброс + input reset_n, + // Управляющие сигналы CPU + input iorq_n, + input rd_n, + input wr_n, + input mreq_n, + // Часть адресной шины CPU + input A7, + input A13, + input A14, + input A15, + inout [7:0] D, + + // Сигнал блокировки внутреннего ПЗУ ZX Spectrum + output ZX_ROM_blk, + // Выход разрешения для ПЗУ картриджа (активный низкий) + output CR_ROM_oe_n, + // Старшие биты адреса для ПЗУ (A13..A18) + output [5:0] CR_ROM_A, + // Выбор кристаллов для четырех ПЗУ 29040 (активный низкий) + output [3:0] CR_ROM_CS +); + + // 8‑битный банковый регистр (хранит биты 7..0 номера страницы) + reg [7:0] reg_bank = 8'b0; + // 8‑битный регистр управления: + // reg_ctl[6:0] - доступны софтам после сброса + // reg_ctl[7] – отключение картриджа (1 = отключён) + reg [7:0] reg_ctl = 8'b0; + + // В спектруме декодирование порта 7ffd идет по А1, А15 == 0. + // Декодирование портов ввода‑вывода картриджа + wire bank = iorq_n | A7 | A13 | ~A14 | ~A15; // A15=1, A14=1, A13=0, A7=0 + wire control = iorq_n | A7 | ~A13 | A14 | ~A15; // A15=1, A14=0, A13=1, A7=0 + + // CPU обращается к области ПЗУ 0x0000..0x3FFF (A15=0, A14=0) + wire cpu_use_rom = ~(A14 | A15); + + wire is_enable = reg_ctl[7]; + + + wire write_bank = ~bank & ~wr_n; + always @(posedge write_bank or negedge reset_n) begin + if (!reset_n) + reg_bank <= 8'b0; + else + reg_bank <= D; + end + + wire write_control = ~control & ~wr_n; + always @(posedge write_control or negedge reset_n) begin + if (!reset_n) + reg_ctl[7] <= 1'b0; // только бит отключения сбрасывается + else + reg_ctl <= D; + end + + // Чтение регистров обратно в CPU + assign D = (~bank & ~rd_n) ? reg_bank[7:0] : + (~control & ~rd_n) ? reg_ctl : 8'bz; + + + // Разделение на выбор микросхемы (2 бита) и смещение страницы (6 бит) + wire [1:0] chip_sel = reg_bank[7:6]; // какая из 4 микросхем (0..3) + wire [5:0] page_offs = reg_bank[5:0]; // смещение внутри микросхемы (0..63) + + // Условие доступа к картриджу: + // CPU читает область ПЗУ, MREQ и RD активны, картридж не отключён + wire rom_access = cpu_use_rom & ~mreq_n & ~rd_n & ~is_enable; + + // Сигнал разрешения выходов и блокировки ПЗУ + assign CR_ROM_oe_n = ~rom_access; + assign ZX_ROM_blk = rom_access; + + // CR_ROM_A зависит от окна: + // нижнее окно (A13=0) : принудительный адрес 0 (страница 0) + // верхнее окно (A13=1) : используется смещение из регистра + assign CR_ROM_A = (A13 == 1'b0) ? 6'b0 : page_offs; + + // Формирование сигналов выбора микросхем: + // Для нижнего окна всегда включается микросхема 0. + // Для верхнего окна включается микросхема, выбранная chip_sel. + // CS активен низким уровнем и активен только при rom_access = истина. + // CS активен (0) только при rom_access = 1 и выполнении условий: + // - для микросхемы 0: либо нижнее окно (A13=0), либо верхнее окно с chip_sel = 0 + // - для микросхем 1..3: только верхнее окно (A13=1) и chip_sel равен номеру микросхемы + assign CR_ROM_CS[0] = ~( rom_access & + ( (A13 == 1'b0) || // нижнее окно всегда выбирает чип 0 + ( (A13 == 1'b1) && (chip_sel == 2'd0) ) ) ); + assign CR_ROM_CS[1] = ~( rom_access & + ( (A13 == 1'b1) && (chip_sel == 2'd1) ) ); + assign CR_ROM_CS[2] = ~( rom_access & + ( (A13 == 1'b1) && (chip_sel == 2'd2) ) ); + assign CR_ROM_CS[3] = ~( rom_access & + ( (A13 == 1'b1) && (chip_sel == 2'd3) ) ); + +endmodule \ No newline at end of file diff --git a/FW/src/zx_cartridge_tb.v b/FW/src/zx_cartridge_tb.v new file mode 100644 index 0000000..cde4c45 --- /dev/null +++ b/FW/src/zx_cartridge_tb.v @@ -0,0 +1,263 @@ +`timescale 1ns / 1ps + +module tb_zx_cartridge(); + // Управляющие сигналы + reg reset_n; + reg iorq_n; + reg rd_n; + reg wr_n; + reg mreq_n; + + // Полная адресная шина (16 бит) + reg [15:0] address; + + // Подключение отдельных бит к DUT + wire A7 = address[7]; + wire A13 = address[13]; + wire A14 = address[14]; + wire A15 = address[15]; + + // Шина данных (8 бит) – двунаправленная + wire [7:0] D; + reg [7:0] D_drive; // данные для записи от тестбенча + wire [7:0] D_sample; // данные, читаемые из DUT + assign D = (wr_n == 0) ? D_drive : 8'bz; + assign D_sample = D; + + // Выходы DUT + wire ZX_ROM_blk; + wire CR_ROM_oe_n; + wire [5:0] CR_ROM_A; + wire [3:0] CR_ROM_CS; // теперь 4 бита + + // Вспомогательная переменная для чтения портов + reg [7:0] dummy; + + // Тестируемый модуль (новая версия) + zx_cartridge uut ( + .reset_n(reset_n), + .iorq_n(iorq_n), + .rd_n(rd_n), + .wr_n(wr_n), + .mreq_n(mreq_n), + .A7(A7), + .A13(A13), + .A14(A14), + .A15(A15), + .D(D), + .ZX_ROM_blk(ZX_ROM_blk), + .CR_ROM_oe_n(CR_ROM_oe_n), + .CR_ROM_A(CR_ROM_A), + .CR_ROM_CS(CR_ROM_CS) + ); + + // Задачи для моделирования циклов Z80 + + // Запись в порт ввода-вывода + task write_port(input [15:0] addr, input [7:0] data); + begin + address = addr; + D_drive = data; + #10; + iorq_n = 0; + wr_n = 0; + #20; + iorq_n = 1; + wr_n = 1; + #10; + D_drive = 8'bz; + end + endtask + + // Чтение из порта ввода-вывода (возвращает прочитанные данные) + task read_port(input [15:0] addr, output [7:0] data); + begin + address = addr; + #10; + iorq_n = 0; + rd_n = 0; + #20; + data = D_sample; + iorq_n = 1; + rd_n = 1; + #10; + end + endtask + + // Чтение из памяти с проверкой CR_ROM_A и CR_ROM_CS (новые сигналы) + task read_mem_check(input [15:0] addr, input [5:0] exp_A, input [3:0] exp_CS); + begin + address = addr; + #10; + mreq_n = 0; + rd_n = 0; + #10; // ждём стабилизации + check_equal(exp_A, CR_ROM_A, "CR_ROM_A during read"); + check_equal(exp_CS, CR_ROM_CS, "CR_ROM_CS during read"); + #10; + mreq_n = 1; + rd_n = 1; + #10; + end + endtask + + // Чтение из памяти с проверкой CR_ROM_oe_n и ZX_ROM_blk + task read_mem_check_oe(input [15:0] addr, input exp_oe, input exp_blk); + begin + address = addr; + #10; + mreq_n = 0; + rd_n = 0; + #10; + check_equal(exp_oe, CR_ROM_oe_n, "CR_ROM_oe_n during read"); + check_equal(exp_blk, ZX_ROM_blk, "ZX_ROM_blk during read"); + #10; + mreq_n = 1; + rd_n = 1; + #10; + end + endtask + + // Простое чтение из памяти (без проверки, для установки адреса) + task read_mem(input [15:0] addr); + begin + address = addr; + #10; + mreq_n = 0; + rd_n = 0; + #20; + mreq_n = 1; + rd_n = 1; + #10; + end + endtask + + // Проверка равенства (поддерживает 4‑битные и 6‑битные аргументы) + task check_equal(input [31:0] expected, input [31:0] actual, input [80*8:0] msg); + if (expected !== actual) begin + $display("ERROR: %s. Expected %h, got %h", msg, expected, actual); + end else begin + $display("OK: %s", msg); + end + endtask + + initial begin + $dumpfile("zx_cartridge.vcd"); + $dumpvars(0, tb_zx_cartridge); + + // Исходное состояние: сброс активен, все сигналы неактивны + reset_n = 0; + iorq_n = 1; + rd_n = 1; + wr_n = 1; + mreq_n = 1; + address = 16'h0000; + D_drive = 8'bz; + #100; + reset_n = 1; + #10; + + // ------------------------------------------------------------ + // Test 1: Запись и чтение регистров через порты + // ------------------------------------------------------------ + $display("=== Test 1: Write and read registers via I/O ports ==="); + + // Запись в bank: адрес 0xC000 (A15=1, A14=1, A13=0, A7=0) + write_port(16'hC000, 8'hA5); // запись reg_bank = 0xA5 + read_port(16'hC000, dummy); + check_equal(8'hA5, dummy, "Read bank returns written value"); + + // Запись в control: адрес 0xA000 (A15=1, A14=0, A13=1, A7=0) + write_port(16'hA000, 8'h80); // запись reg_ctl с битом 7 = 1 (отключение) + read_port(16'hA000, dummy); + check_equal(8'h80, dummy, "Read control returns written value"); + + // Сбрасываем бит disable (reg_ctl[7]=0) для дальнейших тестов + write_port(16'hA000, 8'h00); + read_port(16'hA000, dummy); + check_equal(8'h00, dummy, "Control = 0 after disable cleared"); + + // ------------------------------------------------------------ + // Test 2: Формирование страницы и выбор микросхемы + // ------------------------------------------------------------ + $display("=== Test 2: Page and chip select formation ==="); + + // Записываем bank = 0xA5 -> chip_sel = 2'b10 = 2, page_offs = 6'b100101 = 37 + write_port(16'hC000, 8'hA5); + + // Верхнее окно (A13=1): адрес 0x2000 + // Ожидаем CR_ROM_A = 37, активный CS2 (бит 2 = 0) -> 4'b1011 (младший бит = CS0) + read_mem_check(16'h2000, 6'd37, 4'b1011); // CS2 активен (0), остальные 1 + + // Нижнее окно (A13=0): адрес 0x1000 + // Ожидаем CR_ROM_A = 0, активный CS0 -> 4'b1110 + read_mem_check(16'h1000, 6'd0, 4'b1110); + + // ------------------------------------------------------------ + // Test 3: Проверка всех вариантов chip_sel + // ------------------------------------------------------------ + $display("=== Test 3: Chip select generation for all chip_sel values ==="); + + // chip_sel = 0 + write_port(16'hC000, 8'h00); // 0b00000000 + read_mem_check(16'h2000, 6'd0, 4'b1110); // CS0 активен (0) -> 1110 + read_mem_check(16'h1000, 6'd0, 4'b1110); // нижнее окно тоже CS0 + + // chip_sel = 1 + write_port(16'hC000, 8'h40); // 0b01000000 -> chip_sel=1, offs=0 + read_mem_check(16'h2000, 6'd0, 4'b1101); // CS1 активен -> 1101 + read_mem_check(16'h1000, 6'd0, 4'b1110); // нижнее окно CS0 + + // chip_sel = 2 + write_port(16'hC000, 8'h80); // 0b10000000 -> chip_sel=2, offs=0 + read_mem_check(16'h2000, 6'd0, 4'b1011); // CS2 активен -> 1011 + read_mem_check(16'h1000, 6'd0, 4'b1110); + + // chip_sel = 3 + write_port(16'hC000, 8'hC0); // 0b11000000 -> chip_sel=3, offs=0 + read_mem_check(16'h2000, 6'd0, 4'b0111); // CS3 активен -> 0111 + read_mem_check(16'h1000, 6'd0, 4'b1110); + + // ------------------------------------------------------------ + // Test 4: Сигнал rom_access и CR_ROM_oe_n / ZX_ROM_blk + // ------------------------------------------------------------ + $display("=== Test 4: rom_access control ==="); + + // Включим картридж (reg_ctl[7]=0) – уже 0 + // Чтение из области ROM (адрес 0x1000) + read_mem_check_oe(16'h1000, 1'b0, 1'b1); // CR_ROM_oe_n = 0, ZX_ROM_blk = 1 + + // Чтение из области не ROM (адрес 0x4000, A15=0, A14=1) + read_mem_check_oe(16'h4000, 1'b1, 1'b0); // оба неактивны + + // Отключим картридж (установим бит 7) + write_port(16'hA000, 8'h80); + read_mem_check_oe(16'h1000, 1'b1, 1'b0); // неактивны, т.к. картридж отключён + + // Снова включим + write_port(16'hA000, 8'h00); + + // ------------------------------------------------------------ + // Test 5: Сброс + // ------------------------------------------------------------ + $display("=== Test 5: Reset ==="); + reset_n = 0; + #20; + reset_n = 1; + #10; + + // Проверим, что регистры сброшены в 0 + read_port(16'hC000, dummy); + check_equal(8'h00, dummy, "bank reads 0 after reset"); + read_port(16'hA000, dummy); + check_equal(8'h00, dummy, "control reads 0 after reset"); + + // Проверим поведение после сброса: нижнее окно CS0, страница 0 + read_mem_check(16'h1000, 6'd0, 4'b1110); // нижнее окно: CS0 активен + read_mem_check(16'h2000, 6'd0, 4'b1110); // верхнее окно тоже должно быть CS0 (т.к. bank=0) + + $display("=== All tests completed ==="); + $finish; + end + +endmodule \ No newline at end of file diff --git a/FW/src/zx_cartrige.v b/FW/src/zx_cartrige.v deleted file mode 100644 index aaecdc7..0000000 --- a/FW/src/zx_cartrige.v +++ /dev/null @@ -1,62 +0,0 @@ -`timescale 1ns / 1ps -// ZX SPECTRUM cartrige module -// 17.02.2026 Mikhael Kaa -// CPU adr bus A0...A12 connect directly to CR_ROM chip -module zx_cartrige #( - // default example parameter - parameter SELF_LOCK_VAL = 15 -)( - // Reset - input reset_n, - // CPU ctrl signals - input iorq_n, - input rd_n, - input mreq_n, - // Part of CPU adr bus - input A7, - input A13, - input A14, - input A15, - - // ZX ROM block - output ZX_ROM_blk, - // Cartrige ROM enable - output CR_ROM_oe_n, - // Up part cartrige ROM adr bus (A13...A18) - output [5:0] CR_ROM_A, - output [3:0] CR_ROM_CS - -); - // CR_ROM 8kb bank counter - reg [5:0] CR_ROM_bank_cnt = 6'b0; - // Self lock register, disable all logic and CR_ROM - reg self_lock = 1'b0; - // rd or wr port 0x7f increment CR_ROM bank - wire rom_page_up = iorq_n | A7 | self_lock; - // CPU work with 0000...1fff adr - wire lower_rom = ({A13, A14, A15} == 3'b000) ? 1'b1 : 1'b0; - - always @(negedge rom_page_up or negedge reset_n) begin - if(!reset_n) begin - CR_ROM_bank_cnt <= 6'b0; - self_lock <= 1'b0; - end else begin - // increment bank counter - CR_ROM_bank_cnt <= CR_ROM_bank_cnt + 1'b1; - // check self lock - if(CR_ROM_bank_cnt == SELF_LOCK_VAL) begin - self_lock <= 1'b1; - end - end - end - - assign CR_ROM_oe_n = ~lower_rom | rd_n | mreq_n | self_lock ; - assign ZX_ROM_blk = ~CR_ROM_oe_n; - assign CR_ROM_CS[0] = CR_ROM_oe_n; - assign CR_ROM_CS[1] = 1'b1; - assign CR_ROM_CS[2] = 1'b1; - assign CR_ROM_CS[3] = 1'b1; - - assign CR_ROM_A = CR_ROM_bank_cnt; - -endmodule diff --git a/FW/src/zx_cartrige_tb.v b/FW/src/zx_cartrige_tb.v deleted file mode 100644 index 3a787e4..0000000 --- a/FW/src/zx_cartrige_tb.v +++ /dev/null @@ -1,196 +0,0 @@ -`timescale 1ns / 1ps - -module tb_zx_cartrige(); - // Управляющие сигналы - reg reset_n; - reg iorq_n; - reg rd_n; - reg mreq_n; - - // Полная адресная шина (16 бит) - reg [15:0] address; - - // Подключение отдельных бит к DUT - wire A7 = address[7]; - wire A13 = address[13]; - wire A14 = address[14]; - wire A15 = address[15]; - - // Выходы DUT - wire ZX_ROM_blk; - wire CR_ROM_oe_n; - wire [5:0] CR_ROM_A; - - // Тестируемый модуль (с уменьшенным параметром для быстрой проверки) - zx_cartrige #( - .SELF_LOCK_VAL(3) - ) uut ( - .reset_n(reset_n), - .iorq_n(iorq_n), - .rd_n(rd_n), - .mreq_n(mreq_n), - .A7(A7), - .A13(A13), - .A14(A14), - .A15(A15), - .ZX_ROM_blk(ZX_ROM_blk), - .CR_ROM_oe_n(CR_ROM_oe_n), - .CR_ROM_A(CR_ROM_A) - ); - - // Задачи для моделирования циклов Z80 - // Запись в порт (активируется iorq_n, для инкремента важен его спад) - task write_port(input [15:0] addr); - begin - address = addr; - #10; - iorq_n = 0; // начало цикла IN/OUT - #10; - iorq_n = 1; // завершение цикла – отрицательный фронт - #10; - end - endtask - - // Чтение из памяти - task read_mem(input [15:0] addr); - begin - address = addr; - #10; - mreq_n = 0; // запрос памяти - rd_n = 0; // чтение - #20; // удерживаем для проверки - mreq_n = 1; - rd_n = 1; - #10; - end - endtask - - // Проверка с выводом сообщения - task check_equal(input [31:0] expected, input [31:0] actual, input [80*8:0] msg); - if (expected !== actual) begin - $display("ERROR: %s. Expected %d, got %d", msg, expected, actual); - end - endtask - - initial begin - $dumpfile("zx_cartrige.vcd"); - $dumpvars(0, tb_zx_cartrige); - - // Исходное состояние: сброс активен, все сигналы неактивны - reset_n = 0; - iorq_n = 1; - rd_n = 1; - mreq_n = 1; - address = 16'h0000; - #100; - reset_n = 1; - #10; - - // ------------------------------------------------------------ - // Test 1: Инкремент происходит только при A7=0 и спаде iorq_n - // ------------------------------------------------------------ - $display("=== Test 1: Increment condition (A7=0 and iorq_n falling) ==="); - check_equal(0, CR_ROM_A, "Initial CR_ROM_A"); - - // Попытка с A7=1 – не должен инкрементироваться - write_port(16'h0080); // A7=1 (адрес 0x80) - #10; - check_equal(0, CR_ROM_A, "After write to port 0x80 (A7=1)"); - - // Корректный инкремент с A7=0 - write_port(16'h007F); // A7=0 - #10; - check_equal(1, CR_ROM_A, "After first write to 0x7F"); - - write_port(16'h007F); // второй раз - #10; - check_equal(2, CR_ROM_A, "After second write to 0x7F"); - - // ------------------------------------------------------------ - // Test 2: Достижение SELF_LOCK_VAL (3) блокирует дальнейшие инкременты - // ------------------------------------------------------------ - $display("=== Test 2: Self-lock at value 3 ==="); - write_port(16'h007F); // третий раз -> lock - #10; - check_equal(3, CR_ROM_A, "After third write (should lock)"); - - // Попытка инкремента после блокировки - write_port(16'h007F); - #10; - check_equal(3, CR_ROM_A, "Write after lock - no increment"); - - // ------------------------------------------------------------ - // Test 3: При self_lock=1 CR_ROM_oe_n не активируется даже в нижней ROM - // ------------------------------------------------------------ - $display("=== Test 3: CR_ROM_oe_n inactive while locked ==="); - read_mem(16'h0100); // адрес в нижней области (0x100) - #10; - check_equal(1, CR_ROM_oe_n, "CR_ROM_oe_n during locked read"); - check_equal(0, ZX_ROM_blk, "ZX_ROM_blk during locked read"); - - // ------------------------------------------------------------ - // Test 4: Сброс обнуляет счётчик и снимает блокировку - // ------------------------------------------------------------ - $display("=== Test 4: Reset ==="); - reset_n = 0; - #20; - reset_n = 1; - #10; - check_equal(0, CR_ROM_A, "After reset"); - check_equal(1, CR_ROM_oe_n, "CR_ROM_oe_n after reset"); - - // ------------------------------------------------------------ - // Test 5: Активация CR_ROM_oe_n при чтении нижних 8KB (self_lock=0) - // ------------------------------------------------------------ - $display("=== Test 5: CR_ROM_oe_n activation in lower ROM (0x0000-0x1FFF) ==="); - - // Чтение внутри нижней области - read_mem(16'h0100); - #10; - check_equal(1, CR_ROM_oe_n, "CR_ROM_oe_n at 0x100"); - check_equal(0, ZX_ROM_blk, "ZX_ROM_blk at 0x100"); - - read_mem(16'h1FFF); // граница нижней области - #10; - check_equal(1, CR_ROM_oe_n, "CR_ROM_oe_n at 0x1FFF"); - - // Чтение вне нижней области - read_mem(16'h2000); // A13=1 - #10; - check_equal(1, CR_ROM_oe_n, "CR_ROM_oe_n at 0x2000 (outside)"); - - read_mem(16'h4001); // A14=1 - #10; - check_equal(1, CR_ROM_oe_n, "CR_ROM_oe_n at 0x4001 (outside)"); - - // ------------------------------------------------------------ - // Test 6: Проверка влияния mreq_n и rd_n - // ------------------------------------------------------------ - $display("=== Test 6: Control signals mreq_n and rd_n ==="); - address = 16'h0100; - #10; - - // mreq_n=0, rd_n=1 – чтение не активно - mreq_n = 0; rd_n = 1; - #10; - check_equal(1, CR_ROM_oe_n, "CR_ROM_oe_n with rd_n=1"); - - // mreq_n=1, rd_n=0 – нет запроса памяти - mreq_n = 1; rd_n = 0; - #10; - check_equal(1, CR_ROM_oe_n, "CR_ROM_oe_n with mreq_n=1"); - - // Оба активны – должно включиться - mreq_n = 0; rd_n = 0; - #10; - check_equal(0, CR_ROM_oe_n, "CR_ROM_oe_n with both active"); - - // Возврат в исходное - mreq_n = 1; rd_n = 1; - #10; - - $display("=== All tests completed ==="); - $finish; - end - -endmodule \ No newline at end of file diff --git a/FW/zx_cartrige.qsf b/FW/zx_cartrige.qsf index 0c27fbc..9b8dd62 100644 --- a/FW/zx_cartrige.qsf +++ b/FW/zx_cartrige.qsf @@ -38,7 +38,7 @@ set_global_assignment -name FAMILY MAX7000S set_global_assignment -name DEVICE "EPM7064SLC44-10" -set_global_assignment -name TOP_LEVEL_ENTITY zx_cartrige +set_global_assignment -name TOP_LEVEL_ENTITY zx_cartridge set_global_assignment -name ORIGINAL_QUARTUS_VERSION "13.0 SP1" set_global_assignment -name PROJECT_CREATION_TIME_DATE "14:32:59 FEBRUARY 06, 2026" set_global_assignment -name LAST_QUARTUS_VERSION "13.0 SP1" @@ -51,8 +51,6 @@ set_global_assignment -name MIN_CORE_JUNCTION_TEMP 0 set_global_assignment -name MAX_CORE_JUNCTION_TEMP 85 set_global_assignment -name MAX7000_DEVICE_IO_STANDARD TTL set_location_assignment PIN_1 -to reset_n -set_global_assignment -name VERILOG_FILE src/zx_cartrige.v -set_global_assignment -name CDF_FILE output_files/Chain1.cdf set_location_assignment PIN_18 -to A7 set_location_assignment PIN_19 -to A13 set_location_assignment PIN_20 -to A14 @@ -65,10 +63,21 @@ set_location_assignment PIN_11 -to CR_ROM_A[1] set_location_assignment PIN_12 -to CR_ROM_A[0] set_location_assignment PIN_34 -to CR_ROM_oe_n set_location_assignment PIN_27 -to ZX_ROM_blk -set_location_assignment PIN_24 -to iorq_n +set_location_assignment PIN_2 -to iorq_n set_location_assignment PIN_25 -to mreq_n -set_location_assignment PIN_26 -to rd_n +set_location_assignment PIN_44 -to rd_n +set_global_assignment -name VERILOG_FILE src/zx_cartridge.v +set_global_assignment -name CDF_FILE output_files/Chain1.cdf set_location_assignment PIN_8 -to CR_ROM_CS[3] set_location_assignment PIN_6 -to CR_ROM_CS[2] set_location_assignment PIN_5 -to CR_ROM_CS[1] -set_location_assignment PIN_4 -to CR_ROM_CS[0] \ No newline at end of file +set_location_assignment PIN_4 -to CR_ROM_CS[0] +set_location_assignment PIN_37 -to D[7] +set_location_assignment PIN_39 -to D[6] +set_location_assignment PIN_40 -to D[5] +set_location_assignment PIN_41 -to D[4] +set_location_assignment PIN_16 -to D[3] +set_location_assignment PIN_14 -to D[2] +set_location_assignment PIN_43 -to wr_n +set_location_assignment PIN_33 -to D[1] +set_location_assignment PIN_26 -to D[0] \ No newline at end of file diff --git a/HW/src/main.SchDoc b/HW/src/main.SchDoc index 149512d80f0fdafede22c450243a01cb3a0d14c3..b8584bb25edd4584c468a4117dba1a401cd71733 100644 GIT binary patch delta 31382 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 YWm*x;=t3KU8KV4DFk^)9-*ATb{{%*8R{#J2 delta 30187 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 diff --git a/HW/src/pcb.PcbDoc b/HW/src/pcb.PcbDoc index dfdcf01b66016e3ccbc91f7b0f8c5b45e43d40a4..16c04034ab2da9db8e6ee086beafb1bf1752b15c 100644 GIT binary patch delta 20587 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