A PIC18 table read has three possible outcomes: real data, 0xFFFF for erased, and 0x0000 for a location that either refuses to answer or does not exist. That third case is the one that produces files which pass every obvious check and still cannot be rebuilt.
The PIC18F4515 is the 48 KB member of the PIC18F2X1X/4X1X family: 24,576 instruction words, 3,968 bytes of SRAM, 256 bytes of data EEPROM, 36 I/O pins, thirteen channels of 10-bit ADC sampling at 100 ksps, and 40-pin PDIP or 44-pin TQFP and QFN packaging. Its smaller sibling is the 32 KB PIC18F4510.
Three Readings, Two Meanings
On this core:
| Read result | What it means |
|---|---|
| Real data | Implemented, erased and unprotected — this is the program |
0xFFFF |
Implemented, erased, unprotected — never written |
0x0000 |
Either implemented-but-protected, or not implemented at all |
0x0000 is deliberately unhelpful. A protected block will not hand over its contents, and an address past the end of the array has nothing to hand over, so the silicon answers the same way in both cases. Distinguishing them requires information from outside the read: the device ID at 0x3FFFFE tells you which part you have, and the datasheet tells you how large its array is.
For a 4515 that is 0x0000 to 0xBFFF in byte addresses. Anything a reader returns above 0xBFFF is padding it invented, and it will be 0x0000 if the reader pads with zeros and 0xFFFF if it pads with erased-flash values.
Why 0x0000 Is the Worse Padding
0xFFFF does not decode to a valid instruction on PIC18. It is the erased value, and disassemblers and programmers both treat a run of it as unused space.
0x0000 decodes as NOP. A padded file therefore contains a region that looks like deliberate code: a long run of single-cycle no-ops. A disassembler will happily render it, and a reviewer scanning the listing will read it as alignment padding written by a compiler. Both readings are wrong.
The practical damage is in the middle of a file, not at the end. If a recovery tool skipped a block and padded the gap, the result is a 48 KB image with a hole at 0x3000–0x5FFF full of NOPs. The image programs, the reset vector is intact, the startup code runs, and execution falls off the end of the first block into a NOP sled until the stack gives out.
Three Ways to Find a Hole
1. Boundary test. The protection blocks on this part are quarters of the array, so 12 KB each: 0x0000–0x2FFF, 0x3000–0x5FFF, 0x6000–0x8FFF, 0x9000–0xBFFF. A 0x0000 run that begins on one of those boundaries and ends on the next is a block, not code. A 0x0000 run that starts mid-block is a padded gap or a genuine data table. 2. Branch-target test. Disassemble from the reset vector at 0x0000. Every CALL and GOTO target should land inside code you can identify. A call landing in the middle of a zero run is a hole. 3. Word-count sanity. A product with 48 KB available rarely uses all of it. If the file has 10 KB of real content, 20 KB of 0xFFFF, and 18 KB of 0x0000 in the middle, the shape is wrong — the 0xFFFF should be at the top, not the 0x0000.
The third check is fastest and needs no disassembler: count the runs and look at where they start and stop.
What a Complete 4515 Delivery Looks Like
- 48 KB of program memory,
0x0000–0xBFFF, in four 12 KB code protection blocks - Twelve configuration bytes at
0x300000 - 256 bytes of EEPROM at
0xF00000 - Device ID at
0x3FFFFEand revision at0x3FFFFF - A statement of which protection bits were set, and therefore which blocks the file can legitimately contain
That last line is what separates a delivery from a dump. If one of four blocks was locked and the file contains 48 KB anyway, then 12 KB of it is fabricated — either zeros or 0xFFFF — and the person rebuilding the product needs to know which 12 KB is missing before they start, not after the first prototype fails to boot.
Reading It Right
Where the array is partly open, the read is a set of block reads with a documented map. Where it is fully closed, the route runs through the die, and the image is checked against the same map rather than a byte count.
Send the marking photograph and a note on whether the product was ever updated in the field: a bootloader changes which blocks matter, because the reset vector usually points at the top block. Everything runs under the non-disclosure agreement, and the services page lists what each route covers.