It's an amazing book and doing all the projects is really fun. However keep in mind that the computer you build is really simple and in my opinion maybe a little bit too simple. In particular there is no decent IO model and reading from the keyboard is simply done by busy waiting and writing to the screen is done by writing to a special region in memory. This is ok, as the book covers many topics, but I would enjoy finding a book that covers this topic in detail. E.g. the famous patterson-hennessy book about MIPS covers the implementation of a RISC processor in great detail but does not go into detail about IO stuff which is in my opinion the really hard part.
Historically, writing to the screen was done by writing either ASCII or pixel values to a special region in memory (e.g. Apple II, IBM PC). I'm not sure what you're suggesting the book should do instead? Use a serial port and terminal? Implement a GPU? (I'm not being sarcastic, just looking for clarification.)
My point is that after reading the book I kind of knew how I could implement a simple CPU in real HW (with ALU, memory, etc.) but it was not clear to me how the IO part (kbd, screen) would work. Is the kbd connected directly to a certain place of memory? How is this implemented? Would there be some screen/gpu HW that is directly connected to the memory region? How is the CPU clock involved?
E.g. if you press a key, hold it and then release it, it is possible that this event gets missed as it was too short and the key press method did not check the memory region at the right point of time. For me their implementation enforces this bogus "I am in total control of everything that happens feeling" which often leads to bad design as it ignores the messy real world. I would have appreciated something more sophisticated and generic there which would work for different kinds of HW. Maybe I am missing some simple bus-system one might say ...
Nevertheless it is a wonderful book and I had lots of fun with it! :)
If I'm not mistaken, the keyboard should raise high an interrupt pin on the CPU, which should cause an interrupt service routine to be called.
That routine should then mask lower-priority interrupts, poll the appropriate region of memory (assuming memory-mapped IO) for the byte or bytes held down, push those onto the buffer for key inputs or into the STDIN equivalent, unmask lower-priorty interrupts, and return.
It is then up to the user program to read in from the buffer and do the needful.
I was actually working on implementing a simple little VM library in C as a fun exercise, and deciding how to handle interrupts was where I got caught up.
Most VMs don't have interrupts. As a less-complex alternative, you might consider implementing an I/O thread, something rarely done in hardware because an extra CPU is a lot more hardware than an interrupt mechanism.
One interesting exception is the Unix VM, whose interrupts are called "signals".
I would love to see a book or interactive course that started fairly simple like this, but also had progressively more advanced "second passes" that would go through most or all the abstraction layers of the first project, but with the goal of extending the entire system for some specific advanced feature (like a nice IO model).