The main task of the ROM stage of Coreboot is to initialize system RAM. This RAM initialization is part of the "hard part" of Coreboot. It depends on functionality of the chipset that is never accessed by normal operating system software. This initialization code is written in C. On most motherboards the level 1 data cache is configured so it can be used as a small RAM for the C program. This way the ROM stage can be normally compiled C code.
Roughly speaking this initialization consists of three steps.
The first IBM PC had RAM sockets on the motherboard for 64kB of RAM and it could take up to three expansion cards with 64kB each, making the maximum total amount of RAM 256kB. The expansion boards went into the expansion slots that could also take video cards, disk controllers etc. The RAM expansion boards contained the RAM chips themselves plus some interface logic and of course a few switches to configure the board for a certain address range. Early PCs had RAM chips on the motherboard, possibly with some sockets empty so you could insert more memory chips. Any RAM expansion beyond that required expansion cards. As long as the ISA bus speed was equal to the CPU clock speed (8MHz or less), RAM on expansion boards was a viable option.
In the second half of the 1980s, faster 80286 (and also 80386) CPUs arrived and the system bus was much faster than the ISA bus. The earliest RAM modules were 30-pin SIMM modules, providing 9 bits of data each (8 bits plus parity). A 16-bit system (286 or 386SX) required SIMMs in pairs of two, a 32-bit system (386 or 486) required SIMMs in identical sets of four. A SIMM module contained just a bunch of RAM chips, no other components.
This was the time before the real northbridge was invented, but the use of SIMMs did require a rather sophisticated chipset. If there was more than one set of SIMM slots, the base address of the second slot had to be programmable, depending on the size of the memory installed in the first slot. As there were no SPD ROMs in a SIMM, BIOS just had to find out the size of each memory module by writing to different addresses and trying to read the data back.
30-pin SIMMs were replaced by 72-pin SIMMs, which had a 32-bit data width. Instead of four RAM modules, a 32-bit system could take a single 72-pin SIMM. A Pentium system has a 64-bit external data bus and so it requires 72-pin SIMMs in pairs of two. Still, the timing (number of wait cycles) had to be configured manually (and default values had to be set conservatively, which means slow).
SIMMs gave way to DIMMs (Dual in-line memory modules). Two main advantages over 72-pin SIMMs: a 64-bit data width (so a Pentium system no longer required memory modules in pairs) and Serial Presence Detect EEPROMs. BIOS will only read and never write those SPD EEPROMs. All relevant parameters (size, configuration, timing) can be read from the SPD and manual configuration is not required and BIOS does not have to find the parameters by trial and error. Over the years several types of DIMMs were developed, SDRAM, DDR, DDR2 and DDR3. Those different DIMM types have physically different slots (different number of pins) and are incompatible with one another in terms of electrical signals. For example, a motherboard with DDR2 slots cannot use DDR3 DIMMs.
Early CPUs had a traditional CPU bus with an address bus, a data bus and several control signals. For static RAM (which is seldom used as main memory in computers) this is an almost perfect match. Traditional dynamic RAMs (DRAM) had a more complex interface. The address supplied to a DRAM chip had to be split into a row address and a column address and both had to be supplied in separate clock cycles. A comparatively small number of standard TTL chips could do the job though. Another complication was that DRAM needs to be refreshed. Bits are stored as tiny charges in tiny capacitors and this charge leaks away over time. Therefore each row of the chip has to be accessed at least once every few milliseconds. The this way the bits are read and written back (a halfway discharged capacitor gets the full charge again). In the original IBM PC a channel on the DMA controller was used to read memory addresses periodically, just to get them refreshed.
Over time the external CPU buses got more and more complex and interfacing to DRAM chips was a non-trivial task. Pentium class CPUs were used with northbridge chips to interface them with DRAM. Old-style DRAM gave way to SDRAM and latter DDR, DDR2 and DDR3. Each RAM type had its own more complex interface. Of course later northbridges could be used with those more complex RAM types.
DDR-type RAMs need training, this is: the exact timing of certain signals must be calibrated to the actual hardware, so that the they will arrive at the exact correct time. This training requires special action from the hardware and software. In some primitive systems this calibration would be performed entirely under software control (some embedded systems still do it that way). This happens as follows:
Basically the BIOS has to set the following types of configuration parameters in the northbridge.
Many modern CPUs (especially those from AMD) have a built-in nortbridge. But the configuration registers to configure the RAM modules correctly and to map them to the correct address ranges, is still there.
To configure the RAM modules properly, the BIOS has to know what types of memory modules are installed. For this it has to read the SPD. Serial Presence Detect (SPD) EEPROMS. The layout of the data in these memories is standardized. On the other hand, the way to read them out is very system-dependent.
Those SPD EEPROMs can be accessed via the System Management Bus (SMBus). This SMBus is a two-wire serial bus and it is used to interface with low bandwidth devices inside the PC, such as power supply control and temperature sensors. Access to the SMBus is implemented by the southbridge and the exact way to read the SPD EEPROMs is very dependent on the chipset and sometimes on the exact motherboard model.