And we’re back! I debated whether to dive straight into the instruction decoder or to introduce the CPU registers and all of the bus connections first. I think I’m going to combine it though, and hopefully that won’t just confuse the issue. In this log, we’re going to go through the basics of the instruction decoder implementation, and then go into the first instruction addressing mode, and I’ll explain each register/bus as we get to it.
So last time we talked about how each instruction takes some number of CPU cycles to complete, and on each cycle the decoder will control the rest of the CPU to perform some action. To implement that, the instruction decoder is set up as two nested case statements:
module InstructionDecoder( input sys_clock, rst, // main system clock and reset input clk_ph2, // clock phase 2 input [2:0] cycle, // current instruction cycle input [7:0] IR, // instruction register output reg CTRL_SIG1 // output control signal(s) ); // Decode current opcode based on cycle: always @(posedge sys_clock) begin if (rst == 0) begin // Reset control lines CTRL_SIG(s) <= 0; end else if (clk_ph2) begin // Reset all control lines by default so we don't forget any CTRL_SIG(s) <= 0; // Switch on cycle first, then opcode (will determine what happens on the NEXT cycle): case (cycle) 0: begin case (IR) opcode1, opcode2: begin // set up CPU for cycle 1 operations CTRL_SIG(s) <= x; end opcode3: begin // set up CPU for cycle 1 operations CTRL_SIG(s) <= x; end endcase end 1: begin case (IR) opcode1, opcode2: begin // set up CPU for cycle 2 operations CTRL_SIG(s) <= x; end opcode3: begin // set up CPU for cycle 2 operations CTRL_SIG(s) <= x; end endcase end endcase end end endmodule // Opcode definitions localparam [7:0] opcode1 = 8'hxx, opcode2 = 8'hyy, opcode3 = 8'hzz;
As you can see, the decoder takes the clocks, instruction register, and current CPU cycle as inputs (actually, it will have more, but we’ll get there), and will output various 1-bit control signals which will be sent to the various parts of the CPU.
The control signals will be updated on each phase 2 clock. The outside case statement switches on the CPU cycle, while the nested case switches on the opcode (I think the opposite way would be easier to track each instruction, but this way should be less redundant since a lot of opcodes do the exact same thing on each cycle). One important thing here is that you’re actually setting up the operations that will take place on the next cycle, since we’re on phase 2 now (side note: this was, for some reason, stupidly hard for me to wrap my tiny brain around, so it took forever to get the first opcode done). And finally, to make things easier to read, we’ll create a list of local parameters which are just human readable names for each opcode.
I should also mention that this point (if not earlier…) that most of the 6502 instructions have multiple addressing modes, and each one of these addressing modes has a separate opcode. So if you want to load in a byte from memory, for example, there are up to 8 different ways of telling the CPU where that byte is in memory. It can get super confusing, but this actually means that all of the opcodes with a certain addressing mode are 90% identical – the hard part is figuring out how to calculate the address, but once you have it then the actual operation is pretty simple.
So that’s the general outline of the decoder. Next log, we’ll get into concrete examples (wooo, you say).