Content is a work in progress
The DECODE state figures out what to do in the next couple of cycles based on the opcode. For each opcode it sets a few signals that can be checked in subsequent states without needing much logic. Different opcodes may set the same signal, for example all opcodes that need to load one or more bytes, like LOADIL, POP and SETBRA, will set the loadb3 signal.
Some instructions do not set any signal but take a shortcut and do whatever they need to immediately in the DECODE state and change the state to FETCH1 to initiate a new fetch-decode-execute cycle. This is typically true of instructions like MOVE that do not refer to memory but just to registers. As we will see in a later article on the beginning of a pipelined implementation there are more shortcuts possible but we keep it simple for now (the version in the repository has implemented those shortcuts already)
We have more than ten instructions, not even counting the different ALU operations so weĺl look at the code in detail for only a few of those instructions
DECODE : begin
state <= EXEC1;
case(cmd)
CMD_MOVEP: begin
state <= FETCH1;
r[R2] <= sumr1r0;
end
For the MOVE instruction there are no signals to generate: we simply add the two source registers and assign the result to the destination register. We also make sure the the next state is the start of a new fetch.
The instructions shown in the code below, POP, LOADB, LOADL and LOADIL, all need to load one or more bytes in the cycles that follow, albeit from different addfesses.
So each of those instructions sets tbe mem_raddr register to the appropriate address: for the POP instruction that is the contents of register 14 (tbe stack pointer), and for example for the LOAD instructions whatever the sum of the source registers points to.
The difference between those last two instructions is that for a load byte instruction only tbe loadb3 signal will be set, while for a load long all loadb signals will be set (because we will need four cycles to read four bytes.)
The POP instruction also sets the pop signal so that later, after loading all the bytes, we can increment register 14 by 4.
CMD_POP: begin
mem_raddr <= r[14];
loadb3 <= 1;
loadb2 <= 1;
loadb1 <= 1;
loadb0 <= 1;
pop <= 1;
end
CMD_LOADB: begin
loadb3 <= 1;
mem_raddr <= sumr1r0_addr;
end
CMD_LOADL: begin
loadb3 <= 1;
loadb2 <= 1;
loadb1 <= 1;
loadb0 <= 1;
mem_raddr <= sumr1r0_addr;
end
CMD_LOADIL: begin
loadb3 <= 1;
loadb2 <= 1;
loadb1 <= 1;
loadb0 <= 1;
loadli <= 1;
mem_raddr <= r[15];
r[15] <= ip2; // we increment the pc in two steps to save on LUTs needed for adder
end
In the final part of the decode logic we look at the ALU instruction (Store and push are similar to load and pop but set the mem_waddr register)
If the alu operation is a division operation, the multicycle wire will be true and we set the div_go signal to start the divider module. We also set the div signal so that we can act on it in later cycles.
If it was not a division we can assign the output of the alu module (alu_c) to the destination register and set tbe flag bits in register 13. We also start the next fetch. This is not the case for division though, because division takes multiple cycles.
CMD_ALU: begin
if(multicycle) begin
div_go <= 1; // start the divider module if we have a divider operation
div <= 1;
end else begin
r[R2] <= alu_c;
r[13][29] <= alu_is_zero;
r[13][30] <= alu_is_negative;
state <= FETCH1;
end
end