It says, "both memories are 16-bit wide and have a 15-bit address space, meaning that the maximum addressable size of each memory is 32K 16-bit words." Why only 15-bit address space? Also, so far I have only created 16K RAM which has 14-bit address space (if that is what is meant by address space)?
I saw that someone asked a similar question before, and the answer was "as to why only 15 bits are used for addresses, there is no easy way to use an address >= 32K in A-commands (@)." Is there more info on this? I don't understand the answer.
The designers of this course had to make the implementation simple enough, so that the students can implement all this in 1 or 2 semesters. Because of this many times when they were faced with a choice they had to take the simpler implementation path.
The HACK CPU distinguishes between A and C type instructions by the highest order bit. If it's 0, then it's an A instruction, otherwise, it's C instruction. This means, that only 15 bits are left for the value in the A instruction and it is treated as a non-negative value between 0 and 32767. Suppose that you want to be able to get a value from or jump to an address >= 32768. Let's say it's address 50000.
you'll need to write something like:
or, if it the address was an odd number, like 50001, you'll need to do:
But these also override the value in D, so you'll need extra instructions and a dedicated memory register to temporary store A. So accessing higher RAM or jumping to higher ROM will be more expensive and will make the assembler much harder to implement, because it will have to "inject" instructions.
As for the RAM, it consists of 16K general purpose RAM + 8K video RAM + 1 word keyboard RAM. So there's almost 8K unused addresses. I guess they could've put the video RAM at the top of the addressable space (24576-32767) and put the KBD word at 24575. That would leave almost 24K for general purpose RAM. One of the problems is, that this RAM will have to use 15-bit address, but ignore some of the addresses. And specifically the KBD word will require additional circuitry to handle. I guess that they again decided to simplify things instead.
It would certainly be possible for the Hack to have any size memory space we wanted -- actual processors with small data widths have been accessing relatively large amounts of memory pretty much since the beginning. But you have to play some kind of game to do so and that adds complexity to the both the hardware and the software.
In the Hack the goal is learning fundamental concepts and that is quickly defeated if they are mixed together with a lot of unnecessary complexity, so the choice was made to live within the natural bounds of a simple hardware implementation. One consequence of this is that the only way to get a literal value into the processor is with an A instruction and that requires the msb to be 0, thus we can only directly load the A register with a 15-bit value. So the decision was made to accept that as the width of the address bus going to memory.
Within that 15-bit range (32768 addresses), we need to put three things -- the data RAM, the Screen memory map, and the keyboard memory map. The easiest way to partition things in a binary world is in halves, so we use half of that space for data RAM (16384 addresses requiring 14 bits) and half for the memory maps. We use half of the memory mapped space for the screen (8192 addresses -- which is what then drove the choice of screen size) and the other half for any other memory mapped I/O. Since there's only one other address needed (the keyboard), the rest of the memory space is unused. Not because we couldn't use it, but merely because doing so would increase the complexity in a way that is counterproductive. The good news is that people that want to extend the Nand2Tetris project by adding additional I/O devices (such as a file system) have quite a bit of available I/O address space to work with while leaving the existing usage untouched.
So your next question might be how we could use more memory than we can access directly via the A instruction. The simplest way (in terms of mechanics) would be to load the 1's complement of the intended address into the A register and then complement it. So something like
A = !A
This will result in the value 53,190 (also known as -12346) being in the A register.
The assembler could be tweaked to generate this automatically by allowing
and testing if the value is greater than 32767 and, if it is, then generating the above two-instruction sequence.
It might seem like we could do something similar using A=-A except that this approach would not be able to reach address 32768, which would be a pretty significant problem since it's right in the middle of our address space.
The reason of the above situation is that -32768 and 32768 both have the same representation in two's complement, namely a leading 1 followed by fifteen 0's, meaning that we can't load either into the A register directly. All binary signed integer representations have at least one pathological value because the range of integers from -N to +N has an odd number of values (2N+1 thanks to zero) but an even number of patterns available. For one's complement the pathological case is 0 because it has two representations, +0 is a pattern of all zeroes and -0 is a pattern of all ones -- but we can actually exploit that property using the A=!A method of accessing high memory.
If we wanted to reach more than 64K addresses we could simply map two of the unused memory addresses as our RAM address bus giving us 32 bits of address space. But now the mechanics of reading and writing from memory get a lot more complicated. For the goals of the Hack, that added complexity simply isn't justified.