Modern computers use a von neumann architecture
CPU, Memory, IO. Instructions are stored in memory, processing happens in the CPU. IO looks like memory to the CPU.
Data busses connect components. These are just collections of wires transmitting signals. Usually there are several busses, including System bus, address bus, data bus, and control bus.
The data bus shuffles data around between locations on a machine. Memory, registers, etc. A 64 bit bus can process 64 bit values, but can also process byte aligned values that are smaller etc 32 bits, 16 bits, or 8 bits at a time.
The address bus maintains an address of the data we want to get. System designers assign memory addresses to locations in memory. When software wants the data at an address, it loads that address into the address bus. Circuitry checks if the address is valid and transfers the data using the data bus. The number of bits on the address bus tells you how many unique addresses you can get to (because they must hold a number that represents a unique address).
A 64 bit address bus is essentially infinite, so we’re good.
The control bus holds wires/lines that handle things like whether we are reading or writing to memory, interrupts, etc.
At the end of the day, memory is just a large array of bytes. The address bus tells you which byte to grab, and the data bus can grab more than one byte at a time since it’s usually 64 bits(8 bytes) long nowadays. Address busses on 64 bit systems are normally 48 bits wide(which supports upwards of 256 terabytes)
Since memory is a series of bytes, byte addressable memory is common(it means you can grab one byte at a time, at specific integer addresses, you cannot just grab the 8 bits starting half way in a byte). This makes 8 bit data busses the most convenient. However, to grab more than one byte at a time requires multiple fetch operations, since you can only fit 8 bits on the bus. This is annoying.
16 bit data busses get around this problem by organizing memory into two banks, even and odd. One series of 8 bits grabs bytes from even addresses, one series of 8 bits grabs it from odd addresses. This can get complex when trying to get a byte from an odd address because odd addresses cannot go into the address bus. If you try to put 125 on the address bus, you get 124. The cpus handle this for you, but it requires extra operations. Thus, if you arrange your data structures carefully, you can make your code way faster.
32 bit data busses work in a similar way. The memory is split into 4 banks, and control lines decide which byte you get. Any single byte can be grabbed with one memory operation. The address on the address space is always some multiple of 4. Word access works similarly, although you get extra operations when the LO byte is in the fourth bank and the HO byte is in the first bank. A double word can be accessed in one operation only if its address is a multiple of four.
These sections provide the reasoning for why data alignment is important. If you align data properly, you can get it in one memory access operation instead of 2 or 3, and without swapping.
Modern 64 bit processors have a 64 bit bus and provide a cache to make the cost of improper alignment smaller. Most CPUs made recently do not allow for byte, word, double word access. A lot of the time, especially on RISC machines, you have to access the whole 64 bit value and then shift and mask to get the value you want.
Endianness talks about whether the low order byte of a 16 bit, 32 bit, or 64 bit value comes first, or the high order byte. Some machines, including ARM I think but not sure, put the high order byte first, so the number is made like how you would read an array as a human. You have to handle this in your programs(although compilers do this now, but I’m talking about assembly etc). This endianness issue also arises in network protocols obviously. Which byte do you send and receive first?
Converting between endianness requires mirror image swaps, so two swap operations.
The endianness issue is most easily seen as it relates to a single PC when looking at unions in C. Should detail this more later and write some examples in C.
Electrical signal on the control bus that alternates between 0 and 1 at periodic rates, creating a clock cycle. All activity in the cpu is aligned with the edges of the clock signal. Frequency it switches is the clock frequency and the amount of time it takes for it to switch is the clock period. That period is known as the clock cycle.
Many operations take longer than a clock cycle. They just all start when the clock switches from 0 to 1 or 1 to 0.
Memory access is an operation that is synchronized with the system clock. Memory access occurs no more often than once every clock cycle. Nowadays, CPUs are way faster than memory devices, so the CPU uses a separate clock called a bus clock which runs at some fraction of the system clock.
When reading memory, memory access time is the time between when cpu places an address on the address bus and the time when the cpu take the data off the data bus.
Since cpu speeds are much faster than memory speeds, often time the cpu is stuck in a wait state, waiting more cpu cycles for the memory to be fetched and thrown on the data bus. Putting the CPU into wait state mode is done by the memory unit through a cpu pin whose signal appears on the control bus. Obviously using wait states sucks. Your wait state must be at least one system clock cycle, which is wasted time. If you were to do this on every memory access, faster clock speed would be essentially pointless, you’d be limited to the clock speed of your memory units.
Hardware designers get around this with several tricks.
When writing programs, it's often the case that we access memory locations near each other or the same memory location repeatedly. Cache memory sits between main memory and the cpu. It has no addresses, they are dynamic. The system keeps recently accessed memory in the cache. A cache hit is when you look for a memory address whose value is stored in the cache. A cache miss is expensive, and it's when you have to go into main memory. When you miss, the memory gets copied into the cache.
to solve the space locality, you read a cache line into the cache when you have a miss. Caching is a whole ordeal that we should dive more into later, maybe get a full computer architecture book.
To fix problems, add more cache memory, but usually you can’t add more l1 since it’s really expensive. So you get an L2 cache. Sometimes we even have 3 caches...
There are several addressing modes on x86 assembly. Knowing when to use which one is important, and more will be covered in the x64 assembly language book I have, so not worth worrying about here.