From 10b4f98276737c8446288dfc3512f6180a6c2a3d Mon Sep 17 00:00:00 2001 From: Wayne Warthen Date: Sun, 12 Oct 2025 15:53:04 -0700 Subject: [PATCH] Compressed ROM for EaZyZ80-512, Issue #500 Thanks and credit to Paul de Bak for providing the compression utility. Co-Authored-By: PauldB <169483608+p42db@users.noreply.github.com> --- Doc/ChangeLog.txt | 1 + Source/Clean.cmd | 1 + Source/EZ512/Build.cmd | 12 ++- Source/EZ512/Clean.cmd | 4 + Source/EZ512/Makefile | 11 ++- Source/EZ512/decomp.z80 | 151 +++++++++++++++++++++++++++++++++ Source/ver.inc | 2 +- Source/ver.lib | 2 +- Tools/Makefile.inc | 1 + Tools/compress/compress.exe | Bin 0 -> 35328 bytes Tools/unix/Makefile | 2 +- Tools/unix/compress/Makefile | 12 +++ Tools/unix/compress/compress.c | 150 ++++++++++++++++++++++++++++++++ 13 files changed, 342 insertions(+), 7 deletions(-) create mode 100644 Source/EZ512/decomp.z80 create mode 100644 Tools/compress/compress.exe create mode 100644 Tools/unix/compress/Makefile create mode 100644 Tools/unix/compress/compress.c diff --git a/Doc/ChangeLog.txt b/Doc/ChangeLog.txt index c4932518..7b57f23b 100644 --- a/Doc/ChangeLog.txt +++ b/Doc/ChangeLog.txt @@ -33,6 +33,7 @@ Version 3.6 - HJB: Added loader for MSX - HJB: Added IDE driver master media detect option - WBW: Add support for S100 Serial I/O DLP Serial connection +- P?D: Generate compressed ROM for EaZyZ80 512 Version 3.5.1 ------------- diff --git a/Source/Clean.cmd b/Source/Clean.cmd index ec661f7c..24229309 100644 --- a/Source/Clean.cmd +++ b/Source/Clean.cmd @@ -27,3 +27,4 @@ pushd ZRC && call Clean & popd pushd Z1RCC && call Clean & popd pushd ZZRCC && call Clean & popd pushd MSX && call Clean & popd +pushd EZ512 && call Clean & popd diff --git a/Source/EZ512/Build.cmd b/Source/EZ512/Build.cmd index 341333e8..a56bdb7c 100644 --- a/Source/EZ512/Build.cmd +++ b/Source/EZ512/Build.cmd @@ -3,9 +3,13 @@ setlocal set TOOLS=../../Tools -set PATH=%TOOLS%\srecord;%PATH% +set PATH=%TOOLS%\zxcc;%TOOLS%\srecord;%TOOLS%\compress;%PATH% -for %%f in (..\..\Binary\RCZ80_ez512_*.rom) do call :build %%~nf +set CPMDIR80=%TOOLS%/cpm/ + +zxcc z80asm -decomp/HL + +for %%f in (..\..\Binary\RCZ80_ez512_*.upd) do call :build %%~nf goto :eof @@ -23,4 +27,8 @@ move temp.dat ..\..\Binary\%1_hd1k_prefix.dat copy /b ..\..\Binary\%1_hd1k_prefix.dat + ..\..\Binary\hd1k_cpm22.img + ..\..\Binary\hd1k_zsdos.img + ..\..\Binary\hd1k_nzcom.img + ..\..\Binary\hd1k_cpm3.img + ..\..\Binary\hd1k_zpm3.img + ..\..\Binary\hd1k_ws4.img ..\..\Binary\%1_hd1k_combo.img || exit /b +srec_cat ..\..\Binary\%1.upd -binary -exclude 0x13700 0x14A00 -fill 0xC9 0x13700 0x14A00 -o temp.upd -binary +compress temp.upd +srec_cat decomp.hex -intel temp.upd.cmp -binary -offset 3 -o ..\..\Binary\%1_64k.rom -binary + goto :eof diff --git a/Source/EZ512/Clean.cmd b/Source/EZ512/Clean.cmd index fa12c5c7..8e903c78 100644 --- a/Source/EZ512/Clean.cmd +++ b/Source/EZ512/Clean.cmd @@ -1,3 +1,7 @@ @echo off setlocal +if exist *.upd del *.upd +if exist *.cmp del *.cmp +if exist *.hex del *.hex +if exist *.lst del *.lst diff --git a/Source/EZ512/Makefile b/Source/EZ512/Makefile index cf738f03..9eb3e1e6 100644 --- a/Source/EZ512/Makefile +++ b/Source/EZ512/Makefile @@ -1,13 +1,15 @@ DEST=../../Binary +OTHERS=*.hex *.upd *.cmp HD1KIMGS = $(DEST)/hd1k_cpm22.img $(DEST)/hd1k_zsdos.img $(DEST)/hd1k_nzcom.img \ $(DEST)/hd1k_cpm3.img $(DEST)/hd1k_zpm3.img $(DEST)/hd1k_ws4.img -ROMS := $(wildcard $(DEST)/RCZ80_ez512_*.rom) -ROMS := $(patsubst $(DEST)/%.rom,%,$(ROMS)) +ROMS := $(wildcard $(DEST)/RCZ80_ez512_*.upd) +ROMS := $(patsubst $(DEST)/%.upd,%,$(ROMS)) OBJECTS := $(patsubst %,%_hd1k_prefix.dat,$(ROMS)) OBJECTS += $(patsubst %,%_hd1k_combo.img,$(ROMS)) +OBJECTS += $(patsubst %,%_64k.rom,$(ROMS)) TOOLS = ../../Tools @@ -25,3 +27,8 @@ DIFFPATH = $(DIFFTO)/Binary %_hd1k_combo.img: %_hd1k_prefix.dat $(HD1KIMGS) cat $^ > $@ + +%_64k.rom: $(DEST)/%.upd decomp.hex + srec_cat $< -binary -exclude 0x13700 0x14A00 -fill 0xC9 0x13700 0x14A00 -o temp.upd -binary + $(COMPRESS) temp.upd + srec_cat decomp.hex -intel temp.upd.cmp -binary -offset 3 -o $@ -binary diff --git a/Source/EZ512/decomp.z80 b/Source/EZ512/decomp.z80 new file mode 100644 index 00000000..17c741d6 --- /dev/null +++ b/Source/EZ512/decomp.z80 @@ -0,0 +1,151 @@ +; 251003 decomp_rom_v1.1 +; Mark Pruden: Fix for omitted Bank 4 +; +; 250208 decomp_rom v1.0 +; Paul de Bak: Renamed and used in RomWBW utility program 'compress_upd.c' +; to compress *.upd binary files +; +;1/17/25 +;decompress RomWBW v1.0 - Original version by Bill Shen +;Routine in ROM that decompress RomWBW data file into RAM and jump to it. +;copy data file to RAM starting from bank 0, addr 0. +;when encountered two consecutive bytes of same values, the tird byte following the consecutive values +; describe how many more identical bytes need to be added. +;decompression ends when 3 banks (96KB) are expanded +;compressed data is stored from address $3 to below $FF00 +;decompress program is located at $FF00 + +;register usage +;regC points to bankreg +;regB is ROM source ($20) +;regD is RAM destination bank +;regE is previous value +;regHL is destination pointer +;regIX is source pointer +;regIY points to byte count + +bankreg equ 0ch ;PIO port C is 512K RAM bank register +SIOAData equ 8h ;location of SIO chan A data +SIOACmd equ 9h ;location of SIO ch A command/status reg +SIOBData equ 0ah ;location of SIO chan B data +SIOBCmd equ 0bh ;location of SIO ch B command/status reg +PORTCData equ 0ch ;PIA port C data +PORTCCmd equ 0dh ;PIA port C command + + org 0 + jp 0ff00h ;do program at top of memory +dataRomWBW: +;compressed RomWBW data appended here + org 0ff00h + ld a,09h ;write to KIO command reg first, enable PIA mux + out (0eh),a ; SIO-CTC-PIO priority daisy chain + + ld hl,0ff00h ;copy self into RAM + ld de,0ff00h + ld bc,100h + ldir + + xor a ;PortC are all outputs. This will cause both RAM and ROM + out (PORTCCmd),a ; to be enabled until following OUT instruction, since RAM and + ; ROM have same contents, this won't cause contention + ld a,00100010b ; ROM enable, RAM disable, bank$2, nRTSA, nRTSB enabled + out (PORTCData),a +;register usage +;regC points to bankreg +;regB is ROM source ($20) +;regD is RAM destination bank +;regE is previous value +;regHL is destination pointer +;regIX is source pointer +;regIY points to byte count + ld c,bankreg ;regC points to bank register IO address + ld e,0 ;make sure regE is not the same as very first byte which is 0xC3 + ld b,20h ;regB is ROM source + ld d,80h ;regD is RAM destination + ld hl,0 ;destination starts from bank 0, address 0 + ld ix,dataRomWBW ;regIX points to compressed RomWBW + ld iy,cSame ;regIY points to the count of same value +start: + out (c),b ;read data from ROM +;these two lines are executing program in ROM which has same program as RAM + ld a,(ix) ;read from source + out (c),d +;running in RAM + ld (hl),a ;write to destination + cp e ;two consecutive same values? + jp z,decomp + ld e,a ;update the 'previous' value + inc ix ;next value in ROM + inc hl ;next destination value + ld a,h + cp 80h ;check for address greater than 32KB + jp z,nxtDbank + jp start +nxtDbank: + ld hl,0 + inc d ;next bank + ld a,d +;; cp 83h ;compare to bank 3 + cp 84h ;MP compare to bank 4 + jp z,decompDone + jp start +decomp: + out (c),b ;read data from ROM +;running in ROM + inc ix ;point to byte count of same value + ld a,(ix) ;get the count value + out (c),d ;switch to RAM bank +;running in RAM + dec a ;reduce by 1 because one byte is already written + ld (iy),a ;store count to cSame pointed by regIY + jp z,DoDecomp2 ;just two consecutive values +DoDecomp: + inc hl ;next destination value + ld a,h + cp 80h + jp z,nxtDbank1 +DoDecomp1: + ld (hl),e ;write previous value (in regE) to destination + dec (iy) + jp nz,DoDecomp +DoDecomp2: +;done with block decompression, get ready to start over + out (c),b +;running in ROM + inc ix ;point to next data in ROM + ld e,(ix) + dec e ;make sure previous value does not match, in case of large + ; block (>256) of same values + out (c),d +;running in RAM + inc hl + ld a,h + cp 80h ;check for address grater than 32KB + jp z,nxtDbank2 + jp start +nxtDbank1: + ld hl,0 + inc d ;next bank + ld a,d + cp 83h ;compare to bank3 + jp z,decompDone + out (c),d ;update bank before continuing + jp DoDecomp1 +nxtDbank2: + ld hl,0 + inc d ;next bank + ld a,d + cp 83h ;compare to bank 3 + jp z,decompDone + jp start +decompDone: +;decompression is done, reaching 96KB of data + ld a,80h ;ROM disable, RAM enable, bank$0, nRTSA, nRTSB enabled + out (c),a + jp 0 ;execute RomWBW + +cSame: db 0 ;count of the same value + + end + + diff --git a/Source/ver.inc b/Source/ver.inc index 2a8d9914..54df491b 100644 --- a/Source/ver.inc +++ b/Source/ver.inc @@ -2,7 +2,7 @@ #DEFINE RMN 6 #DEFINE RUP 0 #DEFINE RTP 0 -#DEFINE BIOSVER "3.6.0-dev.33" +#DEFINE BIOSVER "3.6.0-dev.34" #define rmj RMJ #define rmn RMN #define rup RUP diff --git a/Source/ver.lib b/Source/ver.lib index 3502ac45..bd16ea55 100644 --- a/Source/ver.lib +++ b/Source/ver.lib @@ -3,5 +3,5 @@ rmn equ 6 rup equ 0 rtp equ 0 biosver macro - db "3.6.0-dev.33" + db "3.6.0-dev.34" endm diff --git a/Tools/Makefile.inc b/Tools/Makefile.inc index 1701be38..cde5b3d5 100644 --- a/Tools/Makefile.inc +++ b/Tools/Makefile.inc @@ -45,6 +45,7 @@ OPENSPIN=$(BINDIR)/openspin CPMCP=$(BINDIR)/cpmcp CPMLS=$(BINDIR)/cpmls CPMCHATTR=$(BINDIR)/cpmchattr +COMPRESS=$(BINDIR)/compress # # directory containing cpm binaries diff --git a/Tools/compress/compress.exe b/Tools/compress/compress.exe new file mode 100644 index 0000000000000000000000000000000000000000..7643b8067d95914240bcb102f99d4cbfd3e65c5e GIT binary patch literal 35328 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 VznuI{N?%G^N>++11<(e5{~Lq{e!l + * + * If compression is successful, an ouput file is created with the name + * of the input file + ".cmp". + * + * ===================================================================== + * Program exit codes + * 0: Success. An output file is created. + * 1: Incorrect usage or input file not found. + * 2: Unable to create output file. + * + * ===================================================================== + */ + +#include +#include +#include +//#include +//#include // For basename function + +#define VERSION "compress v1.1 - 251012 Paul de Bak & Le Chat AI" +#define MAX_COUNT 0x100 // Maximum count is 256 +#define BUFFER_SIZE 65536 + +size_t calculate_original_size(FILE *input) { + fseek(input, 0, SEEK_END); + return ftell(input); +} + +size_t calculate_compressed_size(FILE *input) { + uint8_t buffer[BUFFER_SIZE]; + size_t size, compressed_size = 0; + while ((size = fread(buffer, 1, BUFFER_SIZE, input)) > 0) { + size_t i = 0; + while (i < size) { + uint8_t byte = buffer[i]; + size_t count = 1; + // Count the number of repetitions of the current byte + while (i + count < size && buffer[i + count] == byte && count < MAX_COUNT) { + count++; + } + if (count >= 2) { + compressed_size += 3; // Two bytes + count + } else { + compressed_size += 1; // Single byte + } + i += count; + } + } + return compressed_size; +} + +int compress_file(FILE *input, FILE *output) { + uint8_t buffer[BUFFER_SIZE]; + size_t size; + uint8_t repeat_count; + while ((size = fread(buffer, 1, BUFFER_SIZE, input)) > 0) { + size_t i = 0; + while (i < size) { + uint8_t byte = buffer[i]; + size_t count = 1; + // Count the number of repetitions of the current byte + while (i + count < size && buffer[i + count] == byte && count < MAX_COUNT) { + count++; + } + if (count >= 2) { + fwrite(&byte, 1, 1, output); + fwrite(&byte, 1, 1, output); + repeat_count = count - 1; + fwrite(&repeat_count, 1, 1, output); + } else { + fwrite(&byte, 1, 1, output); + } + i += count; + } + } + return 1; +} + +int main(int argc, char *argv[]) { + char *input_filename; + long free_bytes; + FILE *input; + size_t original_size; + size_t compressed_size; + char output_filename[256]; + char *base_name; + char *dot; + FILE *output; + + printf("\n%s\n\n", VERSION); + + if (argc != 2) { + printf("Usage: %s \n", argv[0]); + printf("Example: %s RCZ80_ez512_std.upd\n", argv[0]); + return 1; // Error: Incorrect usage + } + + input_filename = argv[1]; + input = fopen(input_filename, "rb"); + if (!input) { + fprintf(stderr, "Error: Input file '%s' not found\n", input_filename); + return 1; // Error: Input file not found + } + + // Calculate the original file size + original_size = calculate_original_size(input); + rewind(input); + + // Calculate the compressed size first + compressed_size = calculate_compressed_size(input); + rewind(input); + + // Construct the output filename + snprintf(output_filename, sizeof(output_filename), "%s.cmp", input_filename); + + printf("Compressing %s to %s...\n", input_filename, output_filename); + + output = fopen(output_filename, "wb"); + if (!output) { + fprintf(stderr, "Error: Unable to create output file '%s'\n", output_filename); + fclose(input); + return 2; // Error: Unable to create output file + } + + compress_file(input, output); + + fclose(input); + fclose(output); + + printf("Compressed from %lu bytes to %lu bytes (%lu%% reduction)\n", original_size, compressed_size, (((original_size - compressed_size) * 100) / original_size)); + + return 0; // Success +}