Home page Wiki Blog Code

Robin

A risc cpu in verilog targeted at the iCEbreaker board

Fetch-Decode-Execute: execution

Content is a work in progress

EXEC1: the first cycle

Now that we have the decoded signals available the pattern for most EXEC cycles is straight forward: The default each time is to go to the next state, we also check specific signals to see if we have to do something in this cycle and sometimes we are done and set the next state to FETCH1 to start a new instruction cycle.

For example if the div signal was set we check whether the divider is done and if so, assign the result to the chosen destination register and update the flags. If not we make sure we will return in this same state.

If the storb3 signal was set we have two options: either we will store another 3 bytes or we store only this byte. If storing multiple bytes they will be in big endian order so we assign the highest byte of the source register to the mem_data_in register. If storing a single byte we assign the lowest byte. In the next cycle this byte will be written to memory.

Note that if we were loading bytes there is nothing to do in this first exec cycle: because we set the read address in the previous (decode) cycles, the actual data will be available after two clock cycles (i.e. in EXEC2). However, if we are loading a long word immediately we will have to increment the instruction pointer by four. We do this in two stages: two now and two later. Because we already need to have an adder that adds two to the instruction pointer because a branch has a two byte offset, we save on hardware this way. (We could have gone all the way and do it in four steps but I didn't bother)


EXEC1   : begin
            div_go <= 0; // flag down the divider module again so that it is not reset forever
            state <= EXEC2;
            if (div) begin // a divider operation (multiple cycles)
              if(div_is_available) begin
                r[R2] <= div_c;
                r[13][29] <= div_is_zero;
                r[13][30] <= div_is_negative;
                state <= FETCH1;
              end else begin
                state <= EXEC1; 
              end
            end
            if(storb3) mem_data_in <= storb2 ? r[R2][31:24] : r[R2][7:0];
            if(loadli) r[15] <= ip2;
          end

EXEC2: the second cycle

The second cycle acts on three different signals. If storb3 is set we assert the mem_write wire to actually write to ram. If the pop signal was set, we are not just reading bytes so here we add four to the stack pointer. And if loadb3 is true it means we are reading bytes (because of a POP instruction or one of the LOAD instructions, that is irrelevant here), so we assign the byte coming from ram to the highest spot in a temporary register. We also point the mem_raddr register to the next address.


EXEC2 :	begin
          state <= FETCH1;
          if(loadb3) begin
            temp[31:24] <= mem_data_out;
            mem_raddr <= mem_raddr + 1;
            state <= EXEC3;
          end
          if(storb3) begin
            mem_write <= 1;
            state <= EXEC3;
          end
          if(pop) r[14] <= r[14] + 4;
        end