PostHeaderIcon STM32F747: A Part Number That Does Not Resolve

Requests arrive as a purchase order line, a silkscreen, or a note from a purchaser who was told a part number over the phone. Occasionally the number does not exist. It looks plausible, it sits inside a real family, and every character in it follows the naming convention of the parts around it — but the manufacturer does not list it, no data sheet covers it, and no distributor stocks it.

This part number is one of those. The family it claims to belong to is real and well documented; the member it names is not in the catalogue. That does not mean the board in front of the customer is fictional. It means the number describes an intention rather than a device, and the first task is to find out which device the intention turned into.

The problem stated plainly

A recovery cannot start from a name. It starts from a memory map, a protection state and a flash geometry, and all three are properties of a die, not of a label. When the label does not resolve, every one of those properties is unconfirmed, and proceeding on the assumption that the nearest catalogue neighbour is correct is how a job produces a technically clean image of the wrong address space.

The nearby members are similar enough that the mistake survives a long way. They share a core, a package range, a development ecosystem and most of a reference manual. They differ in exactly the things that matter here: how much flash there is, whether there is a display controller, whether there is a crypto block, and where the RAM regions begin.

What the catalogue actually contains

The family line this number imitates has five documented members on the single-core side, and they form a deliberate ladder.

member flash RAM display path crypto distinguishing feature
STM32F745 1 MB, single bank 320 KB no TFT controller no the graphics member with the display path removed
STM32F746 1 MB, single bank 320 KB TFT controller plus 2-D accelerator no the mainstream graphics part
STM32F750 64 KB 320 KB TFT controller plus 2-D accelerator yes value line: tiny internal flash, external memory assumed
STM32F756 1 MB, single bank 320 KB TFT controller plus 2-D accelerator yes the 746 with a crypto block
STM32F765 2 MB 512 KB no TFT controller, no JPEG no the large-flash member without graphics

There is no row between 746 and 750 that carries this number, and the ladder explains why: the differentiation in this family is not numerical, it is feature-based. A part one digit away from another is not a slightly faster version of it — it is the same die with a peripheral removed or a block added.

Three readings that settle it

None of these requires removing the lid, and all three are read-only.

reading where it comes from what it answers
device identification code the debug identification register, readable over the debug port with the core halted which die family is present
flash capacity register a 16-bit word in the system memory area, in kilobytes how large the array is, to the kilobyte
peripheral presence probe reading a register that exists only on some members and checking whether the bus faults whether the display controller and crypto block are actually there

The third is the one that resolves the ambiguity a family code cannot. Members of this line share a family code because they share a die; what the code does not tell you is which of the optional blocks were bonded and enabled. A read of a display-controller register that returns a bus fault on a part the customer believes has a display is a decisive answer, and it costs one transaction.

The flash capacity register deserves emphasis because it is the cheapest independent check in the whole job. It answers in kilobytes, it is not affected by protection level, and it disagrees loudly with a wrong assumption. A customer who believes the part has 1 MB and finds a register reporting 64 gets a very different conversation than one who finds 1024.

what the readings show the device in practice consequence
1 MB, display controller absent the non-graphics member of the 1 MB line no display initialisation in the image, no layer descriptors to decode
1 MB, display controller present the graphics member a display pipeline exists and its configuration is part of the image
64 KB, display controller present the value-line member the real firmware is largely outside the chip and must be recovered separately
2 MB the large-flash member banked flash and a different RAM base map

Why the distinction changes the job

Two examples show how far the consequences reach.

If the part turns out to be the non-graphics member, then a display initialisation sequence found in the image is evidence that the image came from somewhere else — a development build for a different board, a reference firmware, or a previous hardware revision. That is a finding about the customer’s build process, not about the extraction, and it is worth reporting.

If the part turns out to be the value-line member with 64 KB of internal flash, then the job changes shape entirely. A modern application for this class of device does not fit in 64 KB. The internal array holds a boot stage and the real code sits in external memory, and an extraction that stops at the internal array has recovered a loader. The implications of that split are worked through in STM32F750 flash recovery, which is the member this situation actually describes.

The same reasoning governs protection. Crypto-equipped members can hold a key that never appears in the image, because it is loaded into a register and never written to memory. A member without the crypto block cannot do that, so the presence or absence of the block changes what “the image is complete” can possibly mean. STM32F756 code recovery covers that case.

What to do with a purchase order that says this

situation the right move
board available and powered read the three registers, then proceed on the identified device
board available but dead identify from the package, pin count and any accessible test points, then state the identification as provisional
no board, only the number quote against the nearest documented member and say in writing which one, before accepting the job
number appears on an internal drawing only treat it as a project code, not a part number, and ask for the schematic

The third row is the commercially important one. A quotation written against a name that does not resolve is a quotation for an unknown quantity of work, and the difference between the members is not marginal: one of them requires recovering a second memory device that is not on the die.

What the report should say

The report opens with the identification, states which readings established it, and lists what was ruled out. Only then does it describe the memory. A customer who has been told “the part on your board is this one, here is how we know” can correct their own documentation; a customer handed an image with no identification has been given a result they cannot check.

The adjacent reading of the same family from the perspective of the cache rather than the catalogue is in STM32F746 software recovery. The boot-from-ROM path that makes identification matter even more is in STM32F745 heximal extraction, and the smallest member of the line, where a great deal is defined by what is absent, is in STM32F722 code recovery.

FAQ

Does a number that is not in the catalogue mean the part is counterfeit? No. It usually means the number was assembled from a family pattern, or that it is an internal project code. Counterfeits normally carry a real number on a relabelled package, which is a different problem.

Can the package marking settle it? Partly. Marking gives the family and often the flash code and package, but not the enabled options. The register readings are what close the question.

Is it acceptable to proceed on the nearest documented member? Only with the assumption written down and the customer’s agreement, because the work differs in scope between members.

Conclusion

A designation that does not resolve is not a dead end; it is an instruction to identify before extracting. Three read-only register readings — die identification, flash capacity, and a probe for the optional blocks — turn a name into a device, and everything downstream depends on getting that order right. The recovery is only as good as the map it was performed against, and the map begins with knowing which die is on the bench.

PostHeaderIcon STM32F746: Why Two Reads of the Same Address Disagree

Every recovery job rests on a simple assumption: an address has a value. On this part that assumption needs a qualifier, because the processor does not read memory. It reads a 4 KB data cache, which reads memory when it has to. Between the two there is a window in which the cache holds a newer value than the memory does, and during that window the answer to “what is at this address” depends entirely on who is asking.

That is not a fault and not a rare condition. It is the normal state of a running device with a write-back cache, and it is the reason a live acquisition can produce an image that is neither the flash contents nor the RAM contents but a mix of both, frozen at slightly different moments.

A core that does not always read memory

The core in this device is a Cortex-M7 with a 4 KB instruction cache and a 4 KB data cache. The two caches are not symmetric in their consequences. The instruction cache matters for performance measurement and almost never for extraction, because code is not being modified while it runs. The data cache matters for extraction, because application state lives in RAM and application state is exactly what a recovery is trying to capture.

The data cache is write-back. When the core stores a word, the store lands in the cache line and the line is marked dirty. The write to the memory behind it is deferred until the line is evicted, either because the cache needs the space or because software explicitly cleans it. On a device running an interrupt-driven loop, evictions happen constantly and the window is short. On a device sitting in a tight polling loop with a small working set, the same lines can stay dirty for a very long time.

who reads the address what it sees why
the core, through a normal load the newest value it reads the cache first
the core, after a cache invalidate whatever memory holds the invalidate discarded the dirty line
a debug probe over the AHB access port whatever memory holds the probe bypasses the cache entirely
a DMA engine whatever memory holds DMA is not cache-coherent on this part
a second bus master reading through the same path the memory value there is no snooping hardware

The third row is the one that decides how a live read has to be done. A debug probe connected over the access port sees memory, not cache. That is usually described as an advantage, because it gives a stable and repeatable view. It is only an advantage if the value being captured is one that has already been written back.

Three ways this shows up in a recovery

The first is a torn parameter block. A device that stores calibration values in RAM and writes them back to flash on a timer will have the newest values in the cache. A probe reading the RAM copy gets the previous set. If the recovery reports the RAM copy, the customer compares it against the values the device displays on its own screen and finds a mismatch that looks like a reading error.

The second is a program that appears to branch into uninitialised memory. The stack holds return addresses and local variables. When the stack region is dirty, the memory image of it is older than what the core is actually using. A disassembly built from stack contents then produces a call chain that does not correspond to anything.

The third is the most expensive: a state machine captured one step behind. A device that keeps its current mode in a static variable, written on every transition, will show the previous mode in a probe read. The customer’s complaint is that the extracted logic is wrong, and the extracted logic is a faithful copy of a value that was true a few hundred microseconds earlier.

observation plausible wrong conclusion what is actually happening
RAM copy differs from the value shown on the device’s display the read is unreliable the display reads the cache, the probe reads memory
a variable changes on every read of the same address the device is still running the cache is being evicted between reads
the stack looks like it belongs to an older call the image is corrupted the newest stack frames were never written back
a watchdog counter reads as a stale value the watchdog is not running its update sits dirty in a cache line

Reading around it

Three approaches work, and they differ in how much they disturb the device.

approach how it works trade-off
halt the core, then clean and invalidate the cache after the halt, force a write-back of all dirty lines, then read the halt changes timing; a device with a watchdog may reset unless the watchdog is frozen
read the cache contents as well as memory extract both and reconcile requires knowing the cache geometry and the mapping, which is where errors enter
capture under a repeated read, then take the modal value read the same range many times and keep the most frequent value per address slow, and wrong for genuinely changing data

The first is what a careful bench does, and it has a side effect worth stating in the report: freezing the watchdog is a change to the device’s configuration. On a part where the watchdog runs from an independent oscillator and cannot be frozen while the core is halted for more than a short window, the halt has to be short enough to fit inside one timeout period. Sequencing the work — read the flash first, which does not need a halt, then halt briefly for the RAM — is the usual way to keep the window inside the budget.

The same problem in reverse: DMA

The cache problem has a mirror image that shows up during validation rather than acquisition. Code that writes a buffer and then hands it to a DMA engine must clean the cache first, or the DMA transfers the old contents. Firmware written for this family therefore contains cache maintenance calls scattered around every peripheral transfer, and their absence is itself informative.

A program that never touches the cache maintenance registers either runs with the cache disabled, which is common in bootloaders and in code that has been ported from an older part, or it does not use DMA at all. Both are visible in the image: the first from writes to the cache control block early in startup, the second from the absence of DMA stream configuration. When neither is present and the program clearly drives a peripheral at speed, the image is probably not the one that was running.

What belongs in the report

The deliverable should say which regions were captured with the core halted and which while running, whether cache maintenance was performed, and what the watchdog configuration was during the halt. It should also state the freshness caveat for any RAM region whose contents are used as evidence, because a customer who builds a behavioural specification from a stale state machine will not discover the discrepancy until the specification is already written.

For the memory layout that makes the same point from the other direction — a RAM map where the region bases do not follow the obvious pattern — STM32F765 heximal recovery covers the trap in detail. The path a device takes when it boots from its factory ROM rather than its own flash is in STM32F745 heximal extraction, and the external memory side of the same family, where the timing registers describe the attached part, is in STM32F769 program recovery. A worked case on a smaller member of the family with firmware encryption in play is at STM32F722RET6 firmware restoration.

FAQ

Does the cache affect a flash read? No. Flash is not cached in the data cache; the accelerator in front of it is a prefetch path, not a cache that can hold uncommitted writes.

Is halting the core enough to get a consistent view? Only if the cache is cleaned afterwards. Halting without cleaning leaves the dirty lines where they were, and the memory image stays old.

Can the cache be disabled to avoid the problem? It can, and some benches do, but disabling it changes execution timing enough to break devices with tight real-time loops, which defeats the purpose of reading a live system.

Conclusion

The single assumption underneath every extraction — that an address has a value — is true only once the writes have landed. On a part with a write-back data cache, the interval before they land is where inconsistent images come from, and the fix is procedural rather than technical: halt, clean, read, and state what was done. A recovery that reports a stable number without saying how it was obtained has reported a value, not a fact.

PostHeaderIcon R7FA2L1A92DFP: The Unique ID Is Part of the Firmware’s Problem

There are two different identifiers on this part and they are routinely confused. One is a protection mechanism that gates a debug session. The other is a factory-burned value that identifies the individual device. Neither is secret, and one of them has consequences that survive long after a recovery succeeds.

The second one is the subject here, because it is the reason a perfectly good image can fail on a perfectly good replacement part.

Two identifiers, two purposes

property protection identifier unique identifier
what it is four words the tool must present 128-bit value burned at test
who writes it production programming the factory
can it change yes, by programming no
what it gates establishing a debug session nothing
what it identifies an authorised tool this specific device

The first is a door policy. The second is a serial number. Confusing them leads to a specific and expensive mistake: assuming that a part whose identifier reads back normally has no protection, or that a part whose protection is clear has no identifier.

How firmware uses the unique value

Three patterns account for almost all uses, and they have very different consequences.

Binding. A key or a licence is encrypted using the identifier, so that the encrypted blob only decrypts on that device. The product cannot be cloned by copying the flash.

Derivation. A per-device key is computed from the identifier and a fixed secret in the code, and then used to encrypt the device’s own data. Nothing is stored that could be copied usefully.

Audit. The identifier is reported over a serial port or stored alongside production records, so that a unit can be traced. This is the harmless case and by far the most common.

pattern what is stored does the image move what breaks
binding an encrypted blob no the blob does not decrypt
derivation nothing, the key is computed no stored data becomes unreadable
audit the value itself yes nothing

The second row is the one that surprises people. Nothing in the image looks bound to anything, the code runs, and the data it reads back is nonsense — because the key it derived from its own identifier is not the key the data was written with.

The AES block, and what its presence implies

The part has hardware for block cipher operations, which is why designs of this class can afford encryption at all: a software implementation on a core without a floating-point unit would dominate the cycle budget.

Two things about its use are visible in an image. The first is the key schedule: a sequence of table lookups and byte manipulations that runs once per key and produces a round-key array in RAM. That array is large enough to be obvious, and its size states whether the design uses 128-bit or 256-bit keys.

The second is what happens immediately before the cipher is invoked. Where the key is a constant from flash, the code loads it and calls the block. Where the key is derived, the code reads the identifier first, mixes it with something, and then schedules. That read-modify-schedule sequence is the fingerprint of a bound design.

The random number generator is a third case

A hardware generator for random values is present as well, and its use is usually easier to spot than encryption because the pattern is smaller: a request, a wait for a valid result, a read.

What makes it worth mentioning in a recovery context is a failure mode. A design that seeds something from the generator and then relies on the seed being unpredictable is only as good as the generator’s health. Where the health check bits are being polled, the code that handles a failure is visible, and it is frequently a loop with no timeout — which means a device with a failing generator hangs in initialisation, and looks dead.

What happens when an image moves

This is the practical core. An image recovered from a bound device and written to a replacement produces one of three outcomes:

  1. It runs and reports an authentication failure, because the blob it holds does not decrypt under the new identifier.
  2. It runs and produces garbage data, because it derived a key that does not match the one used when the data was written.
  3. It runs normally, because the identifier was only ever used for audit.

Only the third case is a successful migration, and it cannot be distinguished from the other two without reading the code. That is what makes this worth a section in a recovery report rather than a footnote.

outcome what the customer sees what to do
authentication failure device refuses to start, or enters a recovery mode the blob must be reissued, not the image
garbage data readings implausible, settings ignored the data must be reinitialised, accepting loss
normal operation nothing nothing

Reading the identity path

  1. Find reads from the fixed address range where the identifier lives. They are plain loads and they cluster.
  2. Follow what happens to the loaded value: mixed into a key, stored for reporting, or compared against something.
  3. Look for a block cipher key schedule nearby. Its presence confirms binding or derivation.
  4. Find the random generator access and check whether its failure path has a timeout.
  5. Note in the report which of the three patterns applies.

Steps one and two take an hour and they answer the only question that matters before a replacement is attempted.

What the deliverable contains

The return set is the code flash image, the data flash separately, the configuration words, both identifiers as read, a disassembly with the identifier access annotated, and a statement of whether the firmware binds itself to the device.

That statement is the item that prevents a customer from spending money on a replacement that cannot work without a companion procedure.

The protection identifier, which is a different mechanism with a different failure, is covered in R7FA2L1AB2DFN locked program recovery. The clock configuration that the crypto blocks depend on is in R7FA2L1A93CNE binary recovery, and the supply range that constrains operation at the extremes in R7FA2L1A92DFM program recovery.

FAQ

Is the unique identifier a security feature? It is a serial number. Security comes from what the firmware does with it.

Can the identifier be changed? No. It is burned at test and is read-only.

Will a recovered image always work on a new part? Only where the identifier was used for reporting. Where it feeds a key, the image is device-specific by construction.

Conclusion

A 128-bit value burned at test turns a generic microcontroller into a specific one, and firmware is free to depend on that. Where it does, the image is not portable and no amount of correct reading changes that. Establishing which of the three patterns applies is a half-hour job that decides whether a replacement is a repair or a redesign.

PostHeaderIcon Break IC PIC16C556A Software: Reading Intent Out of Five Hundred Words

A disassembler converts words into mnemonics and stops there. What a customer needs is not a list of mnemonics but a description of a design, and the distance between the two is filled by recognising recurring shapes in the code — sequences that engineers wrote so often they became conventions.

On a part with a few hundred instruction words, this recognition is the entire job. There is not enough code to drown the patterns; every sequence is deliberate, and the design is small enough that its structure can be recovered completely.

Three layers of reading

It helps to separate what is being recovered at each stage.

Layer Unit Output
instruction one word a mnemonic and an operand
idiom a few instructions a recognised operation, named
intent a block a described behaviour, in ordinary language

Most tooling stops at the first layer. The second layer is where skill lives, and the third is what the customer is paying for.

The idioms worth memorising

This instruction set is small, and a handful of patterns account for most of the code in any product built on it.

Reading a port and testing one bit. A port read into the working register, a mask, an AND, a test for zero. The mask names the pin. Where the same pattern repeats for several bits of the same port, the block is an input scanner and its bit order is the product’s input priority.

Driving an output conditionally. A bit set or clear on a port register inside a conditional branch. The branch condition tells you what the product considers before changing an output — which is the closest thing to a requirements document that exists in the image.

A counted delay. A register loaded with a constant, decremented, tested, branched back. The constant is the timing budget. Where a delay sits immediately before an output change, the constant is a settling time; where it sits in a loop with a counter, it is a period.

A table read. An offset added to the program counter, followed by a run of return-with-literal instructions. The literals are the table. Their count is the table’s extent, and their values are usually the most informative constants in the whole program — segment patterns, calibration curves, character maps, state transition targets.

A bit-banged serial transfer. A loop with a shift, a conditional output, a delay and a clock toggle. The timing of the delay relative to the loop body gives the baud rate directly, which in turn reveals what the product talks to.

Recognising these five shapes converts a listing of several hundred lines into a description with perhaps a dozen statements in it.

Tables are the highest-value target

On a part this small, tables carry a disproportionate share of the information. A product with an eight-segment display, a sensor linearisation, or a state machine will encode the interesting part of its behaviour as a run of literals.

The technique is straightforward and requires care in one place. A table read adds an offset to the program counter and lands in the run; the run must therefore not cross a page boundary, because the addition cannot carry into the high bits. A table that appears truncated is usually a table the engineer split into two runs for that reason, and knowing the convention prevents the mistake of reading only the first half.

Where a table’s literals are displayed, the values are frequently interpretable without any further analysis: seven-segment patterns look like patterns, temperature compensation curves look monotonic, character maps look like ASCII with a transliteration.

What the configuration word adds

The configuration word sits at the top of the map and is not software, and it answers questions the code cannot.

The oscillator selection tells you whether the product requires an external clock, which is a board-level fact that changes how a replacement is fitted. The watchdog setting tells you whether the product relies on a reset to recover from a hang, which is a reliability statement the designer made. The protection bits tell you what state the device was last left in.

Reading it first is worthwhile because it can reframe the whole analysis. A product with the watchdog enabled and a very short timeout is a product designed to recover rather than to be correct, and its code will be structured as a set of restarts rather than as a continuous flow.

Verifying the reading

Interpretation can go wrong in ways that look convincing, so the reading should be checked against behaviour wherever a unit is available.

Three checks are cheap. Power the device and confirm the outputs behave as the interpretation predicts. Drive an input and confirm the branch taken matches the one identified. Where the product has a serial link, capture it and compare the timing against the delay constants you extracted.

A mismatch is informative rather than fatal: it usually means a table was misread or a bit mask was taken from the wrong port, and locating the discrepancy sharpens the whole reading.

The delivery

  • the disassembly with tables and literal runs marked as data
  • the idioms identified, with addresses
  • the table contents decoded where their meaning is evident
  • the configuration word decoded
  • a plain-language description of the product’s operating cycle
  • a list of the assumptions made where behaviour could not be confirmed

The fifth item is what distinguishes a report from a listing. A customer who can read one paragraph describing what the product does has received something a hex file cannot provide.

For the same family from the storage-technology angle, where the charge in each cell rather than the meaning of each word is the subject, PIC16C74B binary reading covers read margin and ageing. Where the array is mask-programmed and cannot be rewritten at all, PIC16CR83 heximal copying explains the consequence. And where the code is larger and paging becomes a structural feature rather than an incidental one, PIC16C73A program recovery develops the address side.

Why small devices are worth the effort

There is a temptation to treat a five-hundred-word program as trivial. In practice these are the devices where complete understanding is achievable, and complete understanding is worth more than partial understanding of something larger. On a big image, the honest deliverable is a map with regions marked unknown. Here, the honest deliverable can be the whole design — and that is a rare and useful thing to be able to hand to somebody.

PostHeaderIcon R7FA2L1A93CNE: The Clock Tree Is the First Thing to Read

An image read without knowing its clock is a list of instructions with no durations. Delay loops cannot be checked, serial rates cannot be computed, and the converter timing looks arbitrary.

Every one of those quantities descends from a single configuration: which oscillator the core runs from, what the phase-locked loop multiplies it to, and how the result is divided before it reaches the peripherals. That configuration is written in the first few hundred bytes of almost every firmware, and reading it converts the rest of the disassembly from numbers into behaviour.

Where the clock comes from

source characteristic what it is used for
high-speed internal selectable range up to the core maximum default operation, no external parts
middle-speed internal fixed, slower low-power operation with fast wake
low-speed internal very slow watchdog, long timers, standby
main external crystal or resonator accurate timing, communication
sub-clock external 32.768 kHz crystal real-time keeping across power cycles
phase-locked loop multiplies a reference running the core above the raw oscillator

A design that needs an accurate serial rate uses the external crystal, because an internal oscillator is specified in percent rather than in parts per million. A design that only needs to be approximately right uses the internal source and saves two pins and two components.

Which was chosen appears in the initialisation code as a write selecting the system clock source, and again as an enable for the oscillator that was chosen. Two writes, minutes apart in reading, seconds apart in execution.

The bring-up sequence has a fixed order

Oscillators cannot be switched instantly. The sequence a careful firmware follows is always the same shape:

  1. Enable the new source and wait for it to become stable.
  2. Set the dividers for the domains that will use it.
  3. Switch the system clock to the new source.
  4. Disable the source that is no longer needed.

Step one contains a wait loop, and that loop has a timeout. The timeout count is a constant in the image, and it is calibrated against the slowest oscillator the design expects — usually the low-speed one, whose start-up is measured in seconds rather than microseconds.

Finding that loop is a reliable landmark. It sits in the clock configuration, it is called exactly once at start-up, and it is followed immediately by the switch.

The baud divisor is a measurement of the clock

Serial rates are generated by dividing the peripheral clock, so a baud register holds a divisor that is a function of the clock frequency. That makes the register a measurement in disguise: read the divisor, assume the baud rate, and the clock falls out.

configuration divisor written implied clock
slow internal source, no division a large value the slow source directly
internal source at full rate a small value the raw internal frequency
external crystal through the loop a mid value the multiplied crystal
divided system clock a value with a large power-of-two factor the divided rate

This is the cross-check that resolves most disputes about which clock a design actually ran at, and it works in both directions: a divisor that does not correspond to any plausible clock means the image came from a different build than assumed, or the peripheral is clocked from a different domain than the core.

For a restoration the consequence is direct. An image restored onto a part with a different crystal, or with the crystal absent, produces a device whose serial communication is wrong by exactly the ratio of the two frequencies — and which otherwise works.

What the low-power modes cost

This family has several reduced-power states, and they differ in what survives. The shallow one stops the core. The deeper ones stop clocks to whole domains. The deepest stops nearly everything and requires a full start-up sequence to leave.

mode core peripherals what can wake it cost to leave
shallow sleep stopped running any interrupt immediate
deep sleep stopped most stopped selected interrupts fast
software standby stopped stopped except the wake logic a short list of sources oscillator restart
snooze stopped one selected peripheral keeps running that peripheral immediate

The fourth row is the interesting one on this part. A peripheral can be left running while the core sleeps and can wake it on a condition — a conversion above a threshold, a touch detection, a byte arriving on a serial port. That arrangement is why the part appears in products that wait a long time and react quickly.

The wake path is where the application hides

In a product built around standby, the code that matters is not the main loop. It is the short handler that runs after a wake, does one thing, and goes back to sleep. Reading a firmware of this shape from the reset vector forward means reading a great deal of initialisation followed by an instruction that stops the core, and nothing else.

The productive route is the reverse: find the wake source configuration, then the handler it triggers, then the code that decides whether to do work or to sleep again. That path crosses the whole application in a few hundred bytes.

Reading the clock section

  1. Find the oscillator enable and the wait-for-stable loop. That establishes which source exists.
  2. Read the divider registers set immediately afterwards. They give the domain frequencies.
  3. Read the loop multiplier if one is configured, and the source it multiplies.
  4. Find a serial divisor and compute the implied rate as a cross-check.
  5. Find the sleep instruction and the wake configuration, and note which peripheral is armed.

Steps one to four take under an hour and they make every later timing question answerable.

What the deliverable contains

The return set is the code flash image, the data flash separately, the configuration words, the identification words as read, a disassembly with the clock configuration annotated, a table of the resulting domain frequencies, and a note stating which oscillator the design depends on.

That last note decides whether an image can be moved. A design that assumes an external crystal will not keep time on a board that does not have one, and the failure looks like a firmware defect rather than a missing component.

The array size that limits how much of this firmware fits is in R7FA2L1A93CFP binary recovery. The supply range that constrains which oscillator can be used at the extremes is in R7FA2L1A92DFM program recovery, and the ordering code that names the part in R7FA2L1AB3CFP software extraction.

FAQ

Can the clock be determined from the image alone? Yes, from the source selection, the dividers and the loop multiplier, with the serial divisor as an independent check.

Why does a restored image run at the wrong speed? Because the oscillator the build assumed is not the oscillator the board has. The code is correct; the hardware is not what it expects.

Does standby erase RAM? No, but it does stop everything else. The wake path has to restore the clock before any peripheral is used.

Conclusion

A firmware’s timing is one decision propagated through a dozen registers. Read the oscillator selection, the dividers and the loop multiplier first, confirm with a serial divisor, and the rest of the image acquires durations. Then find the sleep instruction, because on a low-power product that is where the application actually starts.

PostHeaderIcon Break Chip PIC12CE518 Binary: Sixteen Bytes That Decide the Job

Sixteen bytes is not much. It fits in a single line of a hex file, it disappears in any discussion of array sizes, and it is the reason a job on this device is worth doing carefully.

This part is the variant of the eight-pin family that carries a small data memory alongside its program array. Five hundred instruction words of one-time-programmable program memory, forty-one bytes of RAM, sixteen bytes of data memory that survive power-off, and one configuration word. The total content of the device is smaller than a single page of text, and every part of it has a different claim on the word copy.

The complete inventory

Region Size Written by Recoverable when protected
program array about 512 instruction words the engineer, once no
data memory 16 bytes production line or field firmware separately protected
configuration word one word the engineer, once sometimes
RAM 41 bytes, volatile the running program not applicable

The table is short and it is complete. There is no hidden region, no factory information area worth chasing, no second array. What makes the device interesting is not its size but the ratio: the data memory is three percent of the content and the majority of the value in a repair job.

Why sixteen bytes matter

A product built around an eight-pin part is a product with severe constraints. The pin count forces the programming interface to share pins with the application, the RAM is too small for anything elaborate, and the code is measured in hundreds of instructions. In that environment, anything that must persist across power cycles has exactly one place to go.

What lands in sixteen bytes, in practice:

  • a serial number or a short identity code, because the product cannot afford a label
  • one or two trim values measured at production, because the analogue function needs them
  • a state flag or a usage counter, because the product needs to know something about its history
  • nothing at all, on the many products that never write to it

That last case is common and it is not a disappointment. A data memory reading as uniformly blank is an answer: it says the product’s per-unit variation is in the code, not the data, and it redirects attention to comparing two units’ program arrays instead.

The reading channel is the engineering problem

Eight pins, and the programming interface needs several of them. On this family the programming pins are shared with general-purpose I/O, which means the tool has to hold the device in programming mode while the application circuit is trying to drive the same lines.

Three consequences follow, and they are the practical content of this job.

The load matters. A pin that drives an LED through a resistor, or an input with a pull-up, presents an impedance the programmer was not designed for. The symptom is an intermittent failure to enter programming mode that depends on the board and not on the device.

The supply matters. The programmer supplies the target during programming, and a board with its own regulator will fight it. Where the board cannot be powered down, the regulator has to be isolated or the device lifted.

The clock matters. Entry into programming mode is a timing sequence relative to the supply rising, and a board with a large decoupling network rises slowly. Devices that program perfectly on a socket and refuse on the board are almost always a supply-rise timing issue.

None of these are device faults, and all of them present as device faults. A bench with a socket for the package and a known-good reference device distinguishes them in minutes.

One-time programmable means the read is a one-shot

The program array cannot be erased. Nothing in the read path writes, so a read is safe by construction — unless the programmer performs a blank check with an erase cycle, which some production tools do as a matter of routine. On this device that destroys both the array and the job.

Two habits protect against it. Configure the tool explicitly for a read-only operation rather than relying on its default. And read the data memory first, because it is the region with no backup anywhere and the region a wrong tool setting would take with it.

Reading the data memory is a separate operation

The sixteen bytes are addressed through the same register interface as the rest of the small register file, and they are read by a sequence that is not the same as reading the program array.

That separation has a practical benefit: the data memory can be read on a device whose program array is not readable through the interface, because the protection bits are separate. On a device where the code is lost, the sixteen bytes may still come out, and on a product with a per-unit trim value that is a usable outcome.

The reverse is also true and worth stating: a device whose data memory is protected may still yield its program array. The two protections are independent, and the order of a read should be chosen with that independence in mind rather than fixed in advance.

What a delivery contains when the total is under a kilobyte

A short list, and it should not be padded.

  • the program image, with the word-width convention stated
  • the sixteen data bytes, in address order, with blank bytes marked
  • the configuration word decoded
  • the package type and whether the device is in a socket or on a board
  • a statement of which regions were read through the interface and which were unavailable

The fourth item looks like paperwork and is not: a device read in a socket and the same device read in circuit can produce different results, and recording which was done makes a later discrepancy explicable.

Comparing two units is often the fastest route

Where two units of the same product are available, compare their sixteen bytes before attempting anything clever. If they differ, the differing bytes are the identity and the trim, and their positions are known immediately. If they are identical and blank, the product stores nothing per unit and the copy question reduces to the program array alone.

That comparison takes less time than reading either device and it answers the most important question in the job: whether the data memory is worth pursuing at all.

For the smallest flash members of the same pin-count family, where the array can be rewritten but the reading channel constraints are identical, PIC12F635 program recovery covers the physical side. The device without any data memory, where the whole question of the sixteen bytes disappears, is treated in PIC12C671 image copying. And for the same relationship between program and data arrays expressed on an 18-pin part with far more room, PIC16C72 software copying shows how the proportions change.

PostHeaderIcon Copy Microcontroller PIC16F882 Heximal

Copy Microcontroller PIC16F882 Heximal from flash memory and eeprom memory, unlock mcu pic16f882 protection and crack microcontroller pic16f882 security fuse bit to unleash embedded firmware.

Copy Microcontroller PIC16F882 Heximal from flash memory and eeprom memory, unlock mcu pic16f882 protection and crack microcontroller pic16f882 security fuse bit to unleash embedded firmware
Copy Microcontroller PIC16F882 Heximal from flash memory and eeprom memory, unlock mcu pic16f882 protection and crack microcontroller pic16f882 security fuse bit to unleash embedded firmware

High-Performance RISC CPU:

· Only 35 Instructions to Learn:

– All single-cycle instructions except branches

· Operating Speed:

– DC – 20 MHz oscillator/clock input

– DC – 200 ns instruction cycle

· Interrupt Capability

· 8-Level Deep Hardware Stack

· Direct, Indirect and Relative Addressing modes

Special Microcontroller Features:

· Precision Internal Oscillator:

– Factory calibrated to ±1%

– Software selectable frequency range of 8 MHz to 31 kHz

– Software tunable

– Two-Speed Start-up mode

– Crystal fail detect for critical applications

– Clock mode switching during operation for power savings

· Power-Saving Sleep mode

· Wide Operating Voltage Range (2.0V-5.5V)

· Industrial and Extended Temperature Range

· Power-on Reset (POR)

· Power-up Timer (PWRT) and Oscillator Start-up Timer (OST)

· Brown-out Reset (BOR) with Software Control Option

· Enhanced Low-Current Watchdog Timer (WDT) with On-Chip Oscillator (software selectable nominal 268 seconds with full prescaler) with software enable

· Multiplexed Master Clear with Pull-up/Input Pin

· Programmable Code Protection

· High Endurance Flash/EEPROM Cell:

– 100,000 write Flash endurance

– 1,000,000 write EEPROM endurance

– Flash/Data EEPROM retention: > 40 years

· Program Memory Read/Write during run time

· In-Circuit Debugger (on board)

PostHeaderIcon ATmega2560V: EIND and the Three-Byte Return Address

Halfway through this part’s flash there is a boundary that changes the shape of every call. Below it, a jump carries its own destination. Above it, the destination needs a second register, and the return address the call leaves behind is wider than the one below.

An image that crosses that boundary is not simply a larger image. It is an image with two calling conventions in it, and a disassembly that treats it as one will produce a call graph that is wrong in a way that looks correct.

The boundary, in words

The array is 128 K words. A direct jump instruction carries a sixteen-bit word address, which covers the first 64 K words — half the array, 128 KB of bytes. Any destination above that does not fit in the instruction’s field.

destination reached by register involved
below 64 K words relative jump, or direct jump and call none
above 64 K words extended indirect jump and call the high-bit register, through a pointer pair
any address return from subroutine the stack

The table’s middle row is the whole subject. Code in the upper half is only reachable indirectly, which means the address is computed rather than encoded, which means it can be computed wrongly.

Where the high bit comes from

A single register holds the seventeenth bit of the jump target. Before an extended indirect jump or call, that register has to be loaded with the correct value; the pointer pair supplies the low sixteen bits. Two registers, one instruction, one destination.

value in the high-bit register pointer pair effective destination
0 0x0000 start of the array
0 0x8000 32 K words, mid-array
1 0x0000 64 K words, the boundary
1 0x8000 96 K words, upper half

The pair of writes that sets it is short and easy to miss, and it appears immediately before the extended call rather than at the top of the function. That placement is a deliberate optimisation: the value is only needed for the jump, so it is set as late as possible.

The consequence for a reader is unpleasant and unavoidable. The destination of an extended call is not visible at the call site. It is the composition of two registers, one of which was written a few instructions earlier and the other of which may have been computed by arithmetic. Static reading gives a range, not an address.

Three bytes on the stack

A call pushes the return address, and on this part that address is three bytes. An interrupt pushes the address plus the status register, four bytes before any register saving.

That changes arithmetic that is otherwise routine. Stack depth per level is three bytes rather than two, so a nesting depth that fits comfortably on the smaller part may not fit here. A calculation carried over from a 128 KB design understates the stack by a third of the nesting depth, which is exactly the kind of error that survives testing and appears in the field.

It also changes how frames look in memory. A debugger or a hand analysis that reads a frame as two-byte slots will misalign every local variable after the first, and the values will look plausible because they are the neighbouring values of real data.

What a disassembler gets wrong, and how to notice

Three failure modes are common enough to check for explicitly before trusting any output on this part:

  1. The extended jump and call instructions are decoded as unknown, so the function containing them appears to end early and the code after it is attributed to whatever follows.
  2. The extended instructions are decoded but the high-bit register is ignored, so every indirect call is drawn as landing in the lower half. The call graph then has plausible edges to wrong places, which is worse than having none.
  3. The return-address width is assumed to be two bytes, so the stack model is off by one byte per frame and any derived analysis — local variable recovery, buffer sizes, frame pointers — drifts.

Each of the three produces output that looks syntactically fine. The check that catches them is arithmetic rather than visual: pick a function whose stack usage can be verified independently, compute the frame from the calling convention, and compare against what the analysis claims. A one-byte discrepancy per level points at the third failure mode.

Reading a call graph that spans the boundary

The workable approach is to treat the boundary as a partition rather than an inconvenience. Functions below the boundary have fully encoded call targets and can be read bottom-up. Functions above it are reached indirectly, so their entry points have to be established from the other direction: from the pointer values that are written, from tables of function pointers in the flash array, and from the vector table if any handler lives up there.

In practice that means building two graphs and joining them at the known interface — the extended calls. The join is where the risk is, and it is worth documenting which edges are certain and which are inferred. A report that presents an inferred edge as certain is worse than one that labels it.

Why the V suffix is worth a line

The trailing letter marks the low-voltage variant. It changes the supply range and the maximum frequency at the bottom of that range, which changes the timing constants a build is calibrated against. It does not change the array size, the counter width or the calling convention.

So the analysis above applies unchanged, and the only thing the suffix adds is a note in the deliverable: the clock the image expects. A low-voltage build restored onto a high-voltage part runs, and runs fast enough to break every serial timing in the design — a failure that looks like a firmware bug and is a mismatch between silicon and image.

The deliverable

The return set is the flash image at the correct length, the EEPROM separately, the fuse and lock state as read, a disassembly with the extended jump and call annotated with their computed targets where known, a call graph that marks inferred edges as inferred, and the stack model with the three-byte frame stated explicitly.

The data side of the same boundary — the register that extends constant loads and the tables that need it — is covered in ATmega2560 flash recovery. The part whose array fits entirely inside one sixteen-bit address is in ATmega1280V program recovery, and a case where the call graph stays entirely within one bank appears in ATtiny4313 code recovery.

FAQ

Can any single instruction jump anywhere in the array? No. Direct jumps reach half of it; the upper half requires an indirect jump with the high bit supplied from a register.

Does the wider return address change the vector table? No. The table’s location is fixed and its entries are jumps like any other. It is the stack that changes width.

Would a compiler for the smaller part produce a valid image here? It would produce code that runs below the boundary and cannot reach above it. The two parts are not interchangeable in that direction.

Conclusion

Above 128 KB the destination of a call is computed, not encoded, and the evidence of where it went is a register set a few instructions earlier. Add a three-byte return address and the stack model shifts with it. Read those two facts into the analysis from the start and the upper half of a 256 KB image stops being a region of ambiguous edges.

PostHeaderIcon ATmega2560: RAMPZ, ELPM, and the Tables That Live Above 64 KB

A 256 KB flash array sounds like one memory, and for instruction fetch it largely behaves like one. For constant data it does not. The ordinary load-from-program-memory instruction takes a sixteen-bit pointer, which reaches sixty-five thousand five hundred and thirty-six bytes — a quarter of the array. Everything above that is reachable, but only through a different instruction and only if a particular register holds the right value.

That asymmetry is the single most common reason a recovered image appears to be missing its tables. The tables are there. The code that reads them is using an address form that a quick disassembly has rendered as something else.

Two limits that are not the same limit

Instruction fetch and constant data access are separate paths with separate widths, and on this part they diverge:

path pointer width reach limitation
instruction fetch program counter, extended to 17 bits the whole 128K words indirect jumps need an extension register
ordinary constant load 16-bit register pair 64 KB of bytes, a quarter of the array cannot see the upper three quarters
extended constant load register pair plus a high-byte register the whole array the high-byte register must be correct

The middle row is the trap. A design with a font, a waveform table or a large string set placed in the upper part of the array reads that table with the extended instruction, and a disassembly that does not recognise the idiom will show the table as unreferenced and the reader will conclude it was never used.

The high-byte register is global state

The register that supplies the upper address bits for the extended load is not part of the pointer. It is a single global register that stays where it was last written. That produces a code pattern with no equivalent on smaller parts: before every extended load, the register is set, and after the load, if the code is careful, it is restored.

Careful is the operative word. A routine that performs an extended load and returns without restoring the register will corrupt the next extended load performed by its caller. The bug is real, it is common in hand-written assembly, and it appears in recovered images as a routine that sets the register at entry and never restores it.

For a reader the practical consequence is that the register’s value at any given instruction cannot be determined locally. It has to be tracked along each path, which is exactly the kind of analysis a disassembler does not do by default.

Interrupts make it worse, and the code knows

An interrupt can occur between setting the register and using it. If the service routine also performs an extended load, it overwrites the value and the interrupted code resumes with the wrong high byte — reading a table address in the wrong bank, which usually means reading plausible-looking bytes from somewhere else entirely.

The fix is to save and restore the register in the service routine, and that save appears in the image as a pair of instructions around the handler’s prologue that look redundant until the mechanism is understood. Seeing them is a good sign: it means the firmware was written by somebody who understood the part.

symptom in the field mechanism what to look for in the image
occasional wrong lookup values under interrupt load high-byte register clobbered by a handler handlers that omit the save and restore
a table that reads correctly in a test loop and wrongly in production same, with the test loop not enabling interrupts the enable instruction’s position relative to the table read
data that looks shifted by a fixed offset wrong bank selected, reading a neighbouring region the constant written to the register before the load

Three-byte pointers in the data structures

Constant pointers on this part are three bytes wide, not two, because the address space needs eighteen bits. That shows up in structures stored in flash or in SRAM: arrays of pointers are three bytes per entry, and a structure containing a program-memory pointer has different padding from the same structure on a 128 KB part.

Reading such a table by hand is straightforward once the width is known and hopeless if it is assumed to be two. The characteristic error is an array that decodes as alternating sensible and nonsensical values, which is what happens when a two-byte stride is applied to three-byte records: every third entry lands correctly.

The routine that reads the whole array

A bootloader that verifies the application before jumping to it has to read all 256 KB, and it cannot do that with the ordinary load. Such a routine is often the largest single user of the extended instruction in the image, and it is a useful landmark because it walks the address space linearly with the high-byte register incrementing at each 64 KB boundary.

Finding it answers a question that matters for a recovery: whether the device would have refused to run a modified image. A design with a checksum over the whole array will not boot after a partial write, which is why some «dead» units are not dead at all.

What the deliverable states

The return set is the flash image at the correct length, the EEPROM separately, the fuse and lock state as read, a disassembly in which the extended load is annotated with the bank it selects, and a map of where the large tables sit relative to the 64 KB byte boundary.

That map is the part of the work that prevents a wrong conclusion downstream. An engineer who knows that the font lives at an address requiring the extended load will not spend a day looking for where it is read.

The control-flow side of the same part — the extension register for indirect jumps and the three-byte return address — is covered in ATmega2560V firmware recovery. The part that stops one size below this boundary is in ATmega1280V program recovery, and the same table-versus-code distinction on a smaller array in ATtiny261 code recovery.

FAQ

Why can the ordinary load only reach a quarter of the array? Because it takes a sixteen-bit byte address, and the array is 256 KB. The extension register supplies the bits above sixteen.

Do the tables disappear in a bad read? No. They are present in the image. What fails is the disassembly’s recognition of how they are addressed.

Is the high-byte register saved by the compiler? Compilers that support this part do save and restore it around functions that use the extended load. Hand-written assembly often does not.

Conclusion

On a quarter-megabyte array the interesting problems are on the data side. One register holds the high address bits, it is global state, interrupts can clobber it, and pointers that reach the upper part of the array are three bytes wide. Read those four facts into the disassembly and the missing tables reappear where they always were.

PostHeaderIcon ATmega1280V: 128 KB Is Exactly 65,536 Words, and That Is the Interesting Part

Two numbers describe the program memory of this part and they are both correct: 128 kilobytes, and sixty-five thousand five hundred and thirty-six words. The second number is the one that matters, because the program counter on this architecture counts words, not bytes, and sixteen bits of counter can express exactly that many.

So the ATmega1280 sits at the last position before a design decision becomes unavoidable. One size larger and the instruction set needs an extra register to hold the top bit of a jump target, return addresses grow a byte, and every tool that touches the image has to know about it. At 128 KB none of that is required.

Words, not bytes

Instructions on this architecture are sixteen bits wide, with some taking a second word. Addresses in branches, calls and the program counter are word addresses, so an array of 128 KB is 65536 words and a sixteen-bit counter reaches every one of them, from zero to 65535.

The arithmetic is worth stating plainly because it explains an asymmetry that otherwise looks arbitrary:

array bytes words program counter
64 KB 65536 32768 15 bits used
128 KB 131072 65536 16 bits, fully used
256 KB 262144 131072 17 bits, needs an extension register

The middle row is this part. The bottom row is its larger sibling, and everything that makes that sibling awkward to analyse — a third byte on the stack, a register holding the high bit of a jump target, instructions that only appear there — is absent here.

What the stack looks like as a result

A call pushes the return address. On a sixteen-bit counter that address is two bytes, so each level of nesting costs two bytes of stack plus whatever the called function allocates. An interrupt pushes the program counter and the status register, which is three bytes before any register saving the compiler chooses to do.

That gives a directly checkable property: the stack depth at which a design runs out of RAM is computable from the disassembly. Walk the call graph, find the deepest path, add the interrupt frame, compare against the SRAM the stack pointer was initialised to. On this part that calculation is trustworthy, because there is no hidden extra byte per frame and no bank-switching artefact that changes the frame size.

On the 256 KB part the same calculation needs three bytes per return address, and any analysis that assumes two produces a stack estimate that is wrong by a third of the nesting depth. That error is invisible until a design that was analysed as safe overflows.

RAMPZ is for data, not for code

The part does have a register that extends addressing beyond 64 KB, and seeing it in an image leads people to conclude that the code is banked. It is not. That register supplies the high bit of a byte address for the load-from-program-memory instruction, which reads constant data out of the flash array during execution.

The distinction is architectural: instruction fetch uses the program counter, and data reads use a pointer register pair extended by this register. On a 128 KB part the data side genuinely needs the extension, because 128 KB of bytes exceeds sixteen bits of byte address, so a table placed above the 64 KB byte boundary is reachable only with the extended load.

address range reached by typical content
below 64 KB of bytes ordinary load from program memory small tables, strings, initial values
above 64 KB of bytes extended load with the high bit register large tables, fonts, waveforms
anywhere instruction fetch, no extension needed code

That is the practical consequence for a disassembly: the code needs no banking analysis, but the data does. A large table at a high address is read through a different instruction and appears in the image as a different idiom, and mixing the two up produces a table that looks unreferenced.

The V suffix, which changes none of this

The trailing letter marks the low-voltage variant: a wider supply range at the bottom and a correspondingly lower maximum frequency at the low end. It does not change the array size, the word count, the counter width or the instruction set.

What it changes is timing. A build compiled for the low-voltage part runs its timing loops against a lower clock, and the constants in those loops differ from the high-voltage build by the ratio of the two frequencies. Restoring a low-voltage image onto a high-voltage part, or the reverse, produces a device that runs and keeps the wrong time — the single most common functional complaint after a successful recovery on this family.

So the recovered image should be accompanied by the clock the initialisation code sets up and the divider state it assumes. That pair of facts tells the next engineer whether the image belongs on the part it is about to be written to.

Telling a 128 KB image from a 256 KB image

The two siblings share a pinout, a package and most of the peripheral map, so a mislabelled part is plausible. The images are distinguishable in a minute:

  1. Search for the extended indirect jump and call instructions. They exist only where the counter needs seventeen bits, so their absence is strong evidence of a 128 KB build.
  2. Check the interrupt vector table entry width. Where the array is large enough that a relative jump cannot reach the handler, entries become absolute jumps of two words instead of one.
  3. Measure the stack frame of a known function. Two bytes per return address versus three is decisive and requires no instruction-set reference once the convention is known.
  4. Look for writes to the register that holds the high bit of a jump target. A 256 KB build writes it before indirect calls; a 128 KB build has no reason to.

Any one of these settles it. Two agreeing settles it beyond argument, which is the standard a report should meet when the part marking cannot be trusted.

The deliverable

The recovery returns the flash image with the correct length for a 128 KB array, the EEPROM as a separate file, the fuse and lock state as read, a disassembly with the extended data addressing annotated wherever it appears, and a statement of the clock and divider the image assumes.

The EEPROM on this part deserves its own treatment, and ATmega1280 EEPROM recovery covers why merging it into the flash image destroys the distinction that matters. The larger sibling’s data addressing is in ATmega2560 flash recovery, and its control-flow consequences in ATmega2560V firmware recovery.

FAQ

Does 128 KB require bank switching? Not for code. The counter reaches the whole array. Only constant data above the 64 KB byte boundary needs the extended load.

Why does the size matter to a disassembly? Because the stack frame size, the vector entry width and the presence of extended jump instructions all follow from it, and all three change how an image is read.

Can a 128 KB image run on the 256 KB part? Yes. Every address it uses exists on the larger device. The reverse is not true.

Conclusion

128 KB is the largest array this architecture can address without extending the program counter, and that boundary fixes three things at once: two-byte return addresses, single-word vector entries, and code that needs no banking analysis. Recognising the position of the part on that ladder is what makes the rest of the image predictable.