ext version

batty start, but not work
This commit is contained in:
MichailKaa 2026-02-19 23:32:44 +03:00
parent a24c5d1045
commit 7aaf4a7b21
34 changed files with 4266 additions and 265 deletions

11
.gitignore vendored
View File

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

BIN
Content/Batty/batty.tap Normal file

Binary file not shown.

24
Content/Batty/debuild.sh Normal file
View File

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

19
Content/Makefile Normal file
View File

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

68
Content/main.asm Normal file
View File

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

20
Content/ram_part.asm Normal file
View File

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

29
Content/tools/zx0/LICENSE Normal file
View File

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

View File

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

476
Content/tools/zx0/README.md Normal file
View File

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

View File

@ -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 <COPYRIGHT HOLDER> 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 <stdio.h>
#include <stdlib.h>
#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;
}

View File

@ -0,0 +1,226 @@
/*
* ZX0 decompressor - by Einar Saukas
* https://github.com/einar-saukas/ZX0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

View File

@ -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 <COPYRIGHT HOLDER> 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 <stdio.h>
#include <stdlib.h>
#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;
}

View File

@ -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 <COPYRIGHT HOLDER> 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 <stdio.h>
#include <stdlib.h>
#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];
}

177
Content/tools/zx0/src/zx0.c Normal file
View File

@ -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 <COPYRIGHT HOLDER> 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

View File

@ -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 <COPYRIGHT HOLDER> 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);

View File

@ -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 + <elias length> = run of literals, or
; 1 + <elias offset msb> + [7-bits of offset lsb + 1-bit of length] + <elias 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 + <elias length> = run of literals, or
; 1 + <elias offset msb> + [7-bits of offset lsb + 1-bit of length] + <elias 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 + <elias length> = match using a repeated offset, or
; 1 + <elias offset msb> + [7-bits of offset lsb + 1-bit of length] + <elias 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

View File

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

View File

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

View File

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

View File

@ -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 + <elias length> = run of literals, or
; 1 + <elias offset msb> + [7-bits of offset lsb + 1-bit of length] + <elias 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 + <elias length> = run of literals, or
; 1 + <elias offset msb> + [7-bits of offset lsb + 1-bit of length] + <elias 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 + <elias length> = match using a repeated offset, or
; 1 + <elias offset msb> + [7-bits of offset lsb + 1-bit of length] + <elias 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
# Для тестирования модуля неодходимо чтобы тест назывался %имя_модуля%_tb
TARGET ?= zx_cartrige
TARGET ?= zx_cartridge
ICARUS = iverilog
all:

142
FW/src/zx_cartridge.v Normal file
View File

@ -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] : выбор одной из четырех микросхем (03).
// - биты [5:0] : выбор 8килобайтной страницы внутри выбранной микросхемы (063).
// Таким образом, можно адресовать любую из 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

263
FW/src/zx_cartridge_tb.v Normal file
View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Binary file not shown.