bwat wrote:tjmonk15 wrote:
With my thinking on how a CPU actually works (assumption mostly, but a big circuit ring with branching paths for various operations)
Your bog standard CPU is a
control unit and a
function unit/
datapath. Each instruction leads to a sequence of control words, each which configure the
mutliplexers in the datapath. The key to figuring out how they work is that all operations happen in parallel and it is only the one actually chosen that updates registers/memory. So, there's no real branching in the CPU (
microprogramming aside). It's all done with multiplexers.
Unfortunately I don't have any of my CPU designs uploaded only some dedicated computers like
this which show how the control unit datapath interaction happens.
As far as interrupts are concerned, at some point the CPU will enter a state that will sample the IRQ line/flip-flop and possibly start a new sequence to configure the multiplexers to dump the state on the stack and branch off to some code routine.
Fortunately, I do happen to have
the code for a very simple CPU online. Its' actually an implementation of the
Nibbler 4-bit CPU; so ISA references there are comparable, but microarchitecturally the two are nothing alike (allowing you to contrast two different implementations of one processor if you like; we made different tradeoffs because FPGAs and discrete logic are very different). You can find a block diagram of my implementation
here.
The first thing to understand about VHDL (what my implementation is written in) is that it turns procedural programming languages on their head. It is
not procedural;
everything happens at once. After all, this is how the real world works. Between the main
begin and
end blocks, you'll find assorted logic and some
processes. Processes are "kind of" procedural: at the start of the decode process
pc_action is set to PC_NOP, and then a few lines later
the implementation of the JC instruction sometimes overrides that with PC_LOAD depending upon the carry flag.
Basically, how it works in all modern logic design is that you have a whole bunch of "random logic" which sits in between flipflops. The clock ticks and the flops change state, and then all of the "random logic" starts reacting to these changes. Everything goes "indeterminate" for a while, and then eventually all the changes have propagated all the way through and everything settles (Incidentally, the maximum settling time - determined by the slowest logic path from the output of one flop to the input of another - sets the maximum clock speed). You try and write your logic this way because it means minimum confusion for both you and the compiler.
So that pc_action variable only gets read in one place -
inside the update_regs process - inside an event which is gated to only occur at the rising egde of the clock. So pc_action has until the next time the clock ticks to settle.
So, this has kind of been a "120 second introduction to VHDL and digital logic"; so to get to the meat of things: How do interrupts work in hardware? Well, unfortunately Nibbler doesn't have interrupts. What if I wanted to add them?
Firstly, I'd want an interrupt signal synchronized with the CPU clock (and only changing at rising clock edges), because that simplifies my logic. Then the question is "How to react to it?"
I'd probably enlarge the "ir" register by a bit (so I could encode a virtual interrupt 'pseudo-instruction', or add an 'interrupt_accepted' signal inside the processor or something. Lets assume we are stashing it as a "virtual instruction:"
Change
around line 64:
Code: Select all
fetch: process(clk, phase) begin
if rising_edge(clk) and phase = FETCH_PHASE then
if int and not if_flag then
ir <= '10000';
else
ir <= '0' & prog_data;
end if;
end process;
& is the VHDL concatenate bits operator
And then add a interrupt "pseudo instruction" to the instruction switch table
Code: Select all
when "10000" => -- Interrupt
-- Stash the PC somewhere
-- Load the PC with the interrupt PC
-- set the if_flag (Interrupt Flag) to block future interrupts
-- probably save the other flags somewhere for safe keeping
-- Nibbler would of course be a terrible processor to do this to because it has one register, no stack, no return instructions, a full opcode map, etc.
-- I'm just using it as an easily comprehensible example :-)
Its not really polling. The synthesis tool (whether synthesizing for an FPGA or actual silicon) will produce a bunch of gates which look like "phase.fetch_phase AND int AND not if_flag" which feed a multiplexer which pick between "10000" and '0'&(the output of memory). The result of that will then be fed into the input of a flip-flop which ticks over on the system clock.
Hardware doesn't really poll, nor does it really push, in no part because the distinction is artificial in a world where doing something doesn't take time from something else.