Home page Wiki Blog Code

Robin

A risc cpu in verilog targeted at the iCEbreaker board

Fetch-Decode-Exec: decoding signals

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