If you do decide to learn a bit of assembler, you should probably learn something like 6502 assembler on a Commodore 64 (emulated, of course), or 68000 on an Amiga.
You can get some idea of the Commodore 64 here...
The classic everything-you-need-to-know book is the one described here...
You can probably find a PDF scan if you look around.
IMO, 6502 is easier than Z80, and 68000 is easier than 8086 - more regular instruction sets etc.
But the CPU is only one aspect of the hardware. Also, a modern CPU is a massively different beast, and it does things that are transparent even from the point of view of compilers - such as presenting a virtual address space.
A particular advantage of the 6502 on the C64 is that not only is the CPU simple, but there's some very simple to hack-around-with hardware too. I used to have great fun playing around with the SID music chip.
So - it's probably a worthwhile exercise if you don't spend too much time on it. I learned 6502 assembler as my second language when I was about 14, right after Commodore Basic. But mostly it's getting that very simple working model so that you can add more sophisticated ideas to it with a minimum of misunderstanding.
Some useful things you can learn working in assembler...
- How CPU registers work.
- How memory addressing works, including indirection.
- How the CPU stack works.
- How bitwise logic works.
- How the CPU controls I/O devices.
- How interrupts work.
One particular reason I'd recommend it is to get a better intuition of the way simple steps operate entirely deterministically and mechanically and utterly without intelligence or common sense. Basically getting used to the imperative execution model in it's purest and most stubbornly ignorant form.
Precisely how useful it is to know most of those things now, though, is a difficult question.
One thing you won't learn is how to play well with a memory heirarchy. Those old machines mostly had a simple memory model with no layers of cache and no virtual memory. You also won't learn much about concurrency - they were certainly ways to handle that, but it mostly meant interrupts. You didn't need to worry about mutexes etc.
Sometimes, a mental model of how these things once worked, or of how assembler works, can even mislead. For example, thinking of a C pointer as an address can lead to undefined behaviour issues. A C pointer is normally implemented as an integer containing an address, but there's no guarantee that that's strictly true. For example, on some bizarre platforms, different pointers may point into different address spaces. This becomes important when you want to do arithmetic or bitwise-logic with two pointers.
Unless you have one of those bizarre platforms, you may not think you care about that - but compilers these days are more and more likely to exploit standards-undefined behaviour for optimisation.
So a mental model of the system architecture can be useful, but it's still important to code to the language spec., not to a hypothetical model that your language and platform may not respect.
Finally, a lot of useful mental model stuff comes from getting an idea of how compilers generate code - and code generation for modern languages is very different from the quite trivial compilers available back then.
This is a favorite book of mine for that...
Along with the stuff about parsing and ASTs etc, it covers code generation for a range of language paradigms - imperative, OOP, functional, logic, parallel and distributed - and also for memory management. If you want to know how polymorphic method calls work without getting bogged down in CPU instruction set details, a book like this one is your friend - and there's a new edition due out soon.