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 0000000..67318ab Binary files /dev/null and b/Content/Batty/batty.tap differ 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 149512d..b8584bb 100644 Binary files a/HW/src/main.SchDoc and b/HW/src/main.SchDoc differ diff --git a/HW/src/pcb.PcbDoc b/HW/src/pcb.PcbDoc index dfdcf01..16c0403 100644 Binary files a/HW/src/pcb.PcbDoc and b/HW/src/pcb.PcbDoc differ