Home

Awesome

HDL-deflate

FPGA implementation of deflate (de)compress RFC 1950/1951 ((g)zip / zlib)

This design is implemented in MyHDL (www.myhdl.org) and can be translated to Verilog.

It has been verified in Icarus, Xilinx Vivado and on a physical Xilinx device (Digilent Arty).

In addition it has been tested with Lattice iCE40 UltraPlus using IceStorm and an Upduino.

Also on an ECP5 board with Lattice Diamond on Ubuntu 18.04.

Usage should be clear from the test bench in test_deflate.py.

Tunable parameters

OBSIZE = 8192   # Size of output buffer (BRAM)
                # You need 32768 to decompress ALL valid deflate streams!

IBSIZE = 2048   # Size of input buffer (LUT-RAM)

CWINDOW = 32    # Search window for compression

Sliding input window

One can use a sliding window to reduce the size of the input buffer and the LUT-usage.

The minimal value is 2 * CWINDOW (64 bytes), the UnitTest in test_deflate.py uses this strategy.

Compression efficiency

By default the compressor will reduce repeated 3/4/5 byte sequences in the search window to 15 bit. This will result in a decent compression ratio for many real life input data patterns.

At the expense of additional LUTs one can improve this by enlarging the CWINDOW or expanding the matching code to include 6/7/8/9/10 byte matches. Set MATCH10 to True in the top of deflate.py to activate this option.

Another strategy for data sets with just a small set of used byte values would be to use a dedicated pre-computed Huffman tree. I could add this if there is interest, but it is probably better to use a more dense coding in your FPGA application data in the first place.

Decompression speed

Method 0 (copy mode) 2 cycles for each output byte. Other methods from 1 (long repeated sequences) to 4 cycles for each output byte.

Compression speed

To reduce LUT usage the original implementation matched each slot in the search window in a dedicated clock cycle. By setting FAST to True it will generate the logic to match the whole window in a single cycle. The effective speed will be around 1 input byte every 3 cycles.

Disabling functionality to save LUTs

The compress mode can be disabled by setting COMPRESS to False.

The decompress mode can be disabled by setting DECOMPRESS to False.

As an option you can disable dynamic tree decompression by setting DYNAMIC to False. This will save a lot of BRAM and LUTs and HDL-Deflate compressed output is always using a static tree, but zlib will normally generate dynamic trees. Set zlib option Z_FIXED to generate streams with a static tree.

In general the size of leaves and d_leaves can be reduced a lot when the maximal length of the input stream is less than 32768. One can replace test_data() in test_deflate.py with a specific version which generates typical test data for the intended FPGA application, and repeatedly halve the sizes of the leaves arrays until the test fails.

FAST MATCH10 compress only has quite good resource usage.

LOWLUT disables some options (DYNAMIC and multi block handling) for minimal LUT usage.

Practical considerations

In general HDL-Deflate is interesting when speed is important. When speed is not a real issue using a (soft) CPU with zlib and dynamic RAM is probably the better approach. Especially decompression is also reasonable fast with a CPU and HDL-Deflate needs a lot of BRAM when configured to decompress ANY deflate input stream.

Compression is another story because it is a LOT faster in hardware with the FAST option and uses a reasonable amount of LUTs on Xilinx. Lattice compress resource usage is bigger because it has no LUT-ram.

Decompression only mode with the LOWLUT option can be interesting because it also has a reasonable size. Its size is comparable with a soft CPU on Lattice (but it is a lot faster) and it is much smaller on Xilinx.

FPGA validation

Xilinx

Default (Decompress with IBUF = 16 * CWINDOW and Compress with FAST/MATCH10)

ResourceEstimation
LUT9823
LUTRAM1248
FF2910
BRAM18

Compress only and FAST and MATCH10

ResourceEstimation
LUT2854
LUTRAM156
FF760
BRAM8.5

Compress only and FAST

ResourceEstimation
LUT2397
LUTRAM84
FF695
BRAM8.5

Compress only and MATCH10

ResourceEstimation
LUT1191
LUTRAM84
FF385
BRAM1

Decompress only

ResourceEstimation
LUT4752
LUTRAM48
FF2443
BRAM17.5

Decompress only and LOWLUT

ResourceEstimation
LUT712
LUTRAM24
FF330
BRAM1

Lattice ECP5 (LFE5UM5G-85F)

Default setting

Number of registers:   3108 out of 84255 (4%)
  PFU registers:         3108 out of 83640 (4%)
  PIO registers:            0 out of   615 (0%)
Number of SLICEs:     12491 out of 41820 (30%)
  SLICEs as Logic/ROM:  10955 out of 41820 (26%)
  SLICEs as RAM:         1536 out of 31365 (5%)
  SLICEs as Carry:       1276 out of 41820 (3%)
Number of LUT4s:        21678 out of 83640 (26%)
  Number used as logic LUTs:        16054
  Number used as distributed RAM:   3072

Lattice UltraPLus

Decompress only with LOWLUT

Number of cells:               3312
 SB_CARRY                      511
 SB_DFF                         41
 SB_DFFE                       302
 SB_DFFESR                      33
 SB_DFFESS                      10
 SB_LUT4                      2412
 SB_RAM40_4K                     3

Compress

Number of cells                6796
 SB_CARRY                      917
 SB_DFF                         43
 SB_DFFE                       794
 SB_DFFESR                      49
 SB_DFFESS                       6
 SB_LUT4                      4986

Speed

The Vivado timing report fails at 100Mhz for FAST/MATCH10, but the test bench runs fine on my Arty at 100Mhz. Non FAST passes timing constraints for 100 Mhz.

Future Improvements (when there is interest)