嵌入式系统中的代码压缩

时间:2022-08-24 22:29:33
    在我们的某一个POWERPC系统中,通过总线挂接一片FPGA,和NorFlash等外设。以前我们一直是通过jtag烧写NorFlash的,速度很慢,操作也比较麻烦。现在有了PGA,tpu决定在里面放4k的代码,用于通过串口下载bootloader,并烧写到NorFlash中。这样生产时只要FPGA烧写工具就行了。
    后来,在实现时发现,4k的代码空间,放上各种功能后,已经不够了。只好把各种功能打个折,勉强塞了进去。现在,可以用xmodem协议下载bootloader,但只能直接执行,没有足够的代码空间写NorFlash了。其实,如果把4k的二进制文件,用zip等工具压缩,能压到原来的一半大小。我们也可以仿照lzss等算法,实现一个自己的压缩功能。

    首先,我们定义好我们的压缩方案:
    用一到两个bit做标志,描述数据流。标志凑齐一个字节就输出到数据流。
        比如:  flag data data ...... data flag data ...... data
    标志1 : 数据流中下一个字节是未压缩数据,直接输出。
    标志00: 表示一个短的前向匹配。下一个字节包含距离和长度:
            高2位是匹配长度,0-3对应2-5的匹配长度。
            低6位是匹配距离,0-63对应1-64的匹配位置。
    标志01: 表示一个长的前向匹配。下面两个字节包含距离和长度:
            高4位是匹配长度,0-15对应3-18的匹配长度。
            低4位和下一个字节是匹配距离,0-4095对应1-4096的匹配位置。

    根据这个方案,就可以开始写解压与压缩的实现了。实际测试发现,这个压缩方案,可以把PowerPC的代码,压缩到原来的65%左右。基本的硬件初始化和解压代码,大概用了512字节,剩余的3.5k空间,大概可以放5.3k的代码。这样以来,最初的设想完全可以实现了,而且很有很多的剩余空间备用。


    C语言实现的压缩代码:

static u8 *dst, *src;
static int dp, sp, fp;
static int flag, bits;

void put_flag(int bit)
{
if(bits==8){
if(c_debug) printf("flag %02x at %04x\n", flag, fp);
dst[fp] = flag;
fp = dp;
dp += 1;
bits = 0;
flag = 0;
}

flag >>= 1;
if(bit)
flag |= 0x80;
bits += 1;
}

int tlz_compress(u8 *dst_buf, u8 *src_buf, int src_len)
{
int i, j, pp;
int match_len, match_pos;

dst = dst_buf;
src = src_buf;
sp = 0;
fp = 0;
dp = 1;
bits = 0;
flag = 0;


while(sp<src_len){
/* find match
* max windows: 1-4095 1-63
* max length: 3-18/2-5
*/
match_len = 1;
match_pos = 1;
pp = sp-4095;
if(pp<0)
pp = 0;
for(i=pp; i<sp; i++){
for(j=0; (j<18 && sp+j<src_len); j++){
if(src[i+j]!=src[sp+j])
break;
}
if(j>=match_len){
match_len = j;
match_pos = sp-i;
}
}

if(match_len==1 || (match_len==2 && match_pos>63)){
/* raw byte */
put_flag(1);
if(c_debug) printf("%04x raw: %02x\n", sp, src[sp]);
dst[dp++] = src[sp++];
}else{
put_flag(0);

if(match_len<6 && match_pos<64){
/* short match */
put_flag(0);

if(c_debug) printf("%04x short: pos=%4d len=%2d\n", sp, match_pos, match_len);
sp += match_len;

match_pos -= 1;
match_len -= 2;
match_pos |= (match_len<<6);

dst[dp++] = match_pos;

}else{
/* long match */
put_flag(1);

if(c_debug) printf("%04x long: pos=%4d len=%2d\n", sp, match_pos, match_len);
sp += match_len;

match_pos -= 1;
match_len -= 3;
match_pos |= (match_len<<12);

dst[dp++] = (match_pos>>8);
dst[dp++] = (match_pos&0xff);
}
}

}

/* end of stream */
put_flag(0);
put_flag(0);
dst[dp++] = 0xff;

if(bits){
flag >>= (8-bits);
if(c_debug) printf("flag %02x at %04x\n", flag, fp);
dst[fp] = flag;
}

return dp;
}


    C语言实现的解压代码:
int tlz_decompress(u8 *dst, u8 *src)
{
int i, sp, dp;
int flag, offset, len;
u8 *pbuf;

sp = 0;
dp = 0;
flag = 0x0100;

while(1){
if(flag&0x0100){
flag = src[sp++];
if(d_debug) printf("flag %02x at %04x\n", flag, sp-1);
flag |= 0x00010000;
}

if(flag&1){
/* raw byte */
if(d_debug) printf("%04x raw: %02x\n", dp, src[sp]);
dst[dp++] = src[sp++];
flag >>= 1;
}else{
flag >>= 1;
if(flag&0x0100){
flag = src[sp++];
if(d_debug) printf("flag %02x at %04x\n", flag, sp-1);
flag |= 0x00010000;
}
offset = src[sp++];
if(flag&1){
/* 01: long format */
len = offset>>4;
len += 3;
offset <<= 8;
offset |= src[sp++];
offset &= 0x0fff;
if(d_debug) printf("%04x long: pos=%4d len=%2d\n", dp, offset+1, len);
}else{
/* 00: short format */
if(offset==0xff)
break;
len = (offset>>6);
len += 2;
offset &= 0x3f;
if(d_debug) printf("%04x short: pos=%4d len=%2d\n", dp, offset+1, len);
}
flag >>= 1;
pbuf = &dst[dp-offset-1];
for(i=0; i<len; i++){
dst[dp++] = pbuf[i];
}
}
}

return dp;
}


    PowerPC汇编实现的解压代码:

/* 
int tlz_decomp(u8 *dst, u8 *src);

r3: dst-1
r4: src-1
r5: flag
r8: match offset
r7: match len

*/

tlz_decomp:
lir5, 0x0100

_main_loop:
andi.r0, r5, 0x0100
beq1f
lbzur5, 1(r4)
orisr5, r5, 0x0001
1:
andi.r0, r5, 0x01
srawir5, r5, 1
beq_match
_raw_byte:
lbzur6, 1(r4)
stbur6, 1(r3)
b_main_loop

_match:
andi.r0, r5, 0x0100
beq1f
lbzur5, 1(r4)
orisr5, r5, 0x0001
1:
lbzur6, 1(r4)
andi.r0, r5, 0x01
beq_short_match
_long_match:
srawir7, r6, 4
addir7, r7, 3
lbzur8, 1(r4)
rlwimir8, r6, 8, 20, 23
b_copy
_short_match:
cmpwir6, 0xff
beqlr
srawir7, r6, 6
addir7, r7, 2
andi.r8, r6, 0x3f
_copy:
srawir5, r5, 1
subr8, r3, r8
subir8, r8, 1
mtctrr7
1:
lbzur6, 1(r8)
stbur6, 1(r3)
bdnz1b

b_main_loop
    这里与C实现不一样的是,入口参数都要减一。这是为了充分利用PPC的指令特点,可以节省两条指令。这段代码占144字节空间。