Read-only memory is a family rather than a device, and its members differ more than the name suggests. What they share is that their contents survive power loss and that the running program cannot casually rewrite them. Almost everything else – how the bits are stored, whether they can be changed at all, and what it costs – varies by a couple of orders of magnitude across the family.
Mask ROM
The oldest kind is programmed at the factory. The bit pattern is fixed when the photolithographic mask is made: a transistor or a diode is either present or absent at each cell, and that presence or absence is the bit. There is no field-programming step and no programming cost, and no way to change anything afterwards. Mask ROM is the cheapest per unit at high volume and has the worst lead time of anything in the family, since a correction means a new mask set and a new production run. Character generators, calculator firmware and 1980s cartridge games were built this way, and the technology has not disappeared – it survives wherever volumes are enormous and the code is genuinely frozen.
PROM and EPROM
Field-programmable ROM moved the programming to the customer. A PROM is programmed once, by blowing fusible links or, in the anti-fuse variant, by breaking down a thin oxide to create a connection rather than destroy one. One-time-programmable parts are still common for calibration constants, and for security keys that should never be rewritable.
EPROM is where the quartz window comes from. Cells store charge on a floating gate – a conductor surrounded by insulator, with no electrical connection to anything else. Charge placed there stays there, and it changes the threshold voltage of the transistor, which is what a read senses. The charge is not stuck permanently, though: ultraviolet light gives it enough energy to escape, so an EPROM in a package with a transparent window could be erased by twenty minutes under a UV lamp. The 27C256 and its relatives erased to all ones, which is why an erased part reads as 0xFF everywhere.
EEPROM
EEPROM moved erasure on-chip and made it electrical. The mechanism is Fowler–Nordheim tunnelling: a high field across a very thin oxide lets charge cross it without destroying the cell. The practical consequences are that erasure happens in-circuit, at byte granularity, under software control – and that it is slow, on the order of milliseconds per byte, with an endurance limit somewhere between a hundred thousand and a million write cycles per cell.
That combination defines where EEPROM belongs: configuration, calibration constants, serial numbers, the last known good settings after a power failure. It is the right place for data written rarely that must survive everything. It is the wrong place for anything written in a loop. A counter incremented on every pass of a main loop will exhaust a cell in hours, and the failure is quiet – the byte simply stops changing rather than reporting an error. The usual defences are to write only when the value has actually changed, to rotate writes across a set of cells, or to keep the value in RAM and commit it on shutdown or on a timer.
Flash
Flash is EEPROM’s descendant and the reason the distinction between these parts matters less than it used to. The two common organisations solve different problems.
NOR flash allows random access at byte or word granularity and can be executed in place: the processor fetches instructions directly from it, which is why a system can boot with no code in RAM at all. It is the technology behind the firmware in most embedded systems, including the program memory of every AVR and STM32.
NAND flash is organised in pages, read sequentially, and cannot be executed from. It needs a controller to handle error correction and bad blocks, and in exchange it is far cheaper per bit. It is where bulk storage went.
What both share is the awkward part: erasure happens in blocks rather than bytes, and erasing sets bits to one while writing can only clear them. Changing a single bit from zero to one therefore means copying the whole block somewhere else, erasing it, and writing everything back – which is why file systems for flash are a subject in themselves, and why wear levelling exists at all.
Read-only, mostly
On nearly every modern microcontroller the program flash is memory-mapped, readable as ordinary data, and self-programmable. That is what makes a bootloader possible: the same memory holding the code being executed can be rewritten by that code. It is also why bootloaders are careful about where they live. On AVR, writing to flash uses the SPM instruction, which stalls the CPU for the duration of the page write, and a page cannot be read while it is being erased – so the code performing the update must not be running from the page being updated. The boot section fuses exist to reserve a region the updater never overwrites, and a part with too small a boot section runs its updater from RAM.
The other consequence of separate memories is the Harvard split. AVR keeps program and data in different address spaces, so a const array is stored in flash but cannot be reached by an ordinary load. It needs the LPM instruction, which is why avr-libc provides PROGMEM and pgm_read_byte(), and why passing a flash string to printf without the accessor prints whatever happens to be at that address in RAM. ARM Cortex-M is von Neumann at the bus level, so const data is directly addressable and none of this is required.
ROM as a table
One use of read-only storage has nothing to do with firmware. A ROM is a place to put answers you would otherwise compute. Sine tables, CRC tables, character generators, gamma curves and filter coefficients are all the same trade: spend memory to save cycles. On a part with two kilobytes of RAM and thirty-two of flash it is rarely a close call, and the table is often smaller than the code that would replace it. It is the oldest optimisation in computing and it still wins.
What is called ROM in a microcontroller today is almost always flash, and what is called flash in a phone is usually managed NAND behind a translation layer that hides all of the above. The distinctions survive because the constraints do: something has to hold the code before there is any RAM to copy it into, and that something has to remember it after the power goes away.