Search code examples
assemblymipscpu-architectureinstruction-setno-op

How are the bytecodes for MIPS nop and sll differentiated?


As far as I'm aware both instructions have opcode and function code of 0, so how does the computer know which one it's doing?


Solution

  • As Jester says, a MIPS CPU doesn't have to differentiate if it doesn't want to do any special performance optimization. sll $0, $0, 0 already has no architectural effect, since writes to $0 are discarded, and it has no side effects (MIPS has no FLAGS/condition-codes register). So simple hardware can just let it run through the pipeline.

    The point of having an agreed-upon nop opcode is that if hardware wants to look for a special case and not even execute it, there's only the one bit-pattern it has to match on, instead of considering every choice of instruction that writes to $0 (except loads which might still fault.)

    So developers of compilers / assemblers don't have to guess what nop might be cheapest for some hardware. Things hardware might do include:

    • A superscalar pipeline with only one shift execution unit could run nop and sll $t0, $t1, 12 in the same clock cycle, even if it couldn't normally run two shifts in the same cycle.
    • Possibly save a bit of power from not activating the ALUs?
    • Possibly don't send the NOP through the pipeline at all, just discard it while decoding and grab the next real instruction. (Plausible for a CPU that fetches and decodes more instructions than its pipeline width, or especially for out-of-order exec CPUs.)
    • If hazard detection doesn't already special-case every read and write on $0, it can be skipped for a nop. (Only plausible for a toy CPU without bypass forwarding, see below.)

    Even sll $t0, $t0, 0 has no architectural effect because it writes the register with the same value it already had. But it's a worse choice since hazard detection would normally be looking at register reads and writes, so it might lead to a stall, or a longer dependency chain for $t0 on an out-of-order exec MIPS like R10000. If MIPS didn't have a zero register, though, it would be a reasonable choice to agree upon as a NOP, except on MIPS 1 where it wouldn't be safe to put it in the delay slot of a load that wrote $t0.


    Are there any possible corner cases with lw $0, (mem) / nop for hardware that didn't try to special-case nop or the zero-register at all, with the zero register discarding writes and reading as zero handled only in the register file?

    In that case, it would see an instruction reading a load result in the next instruction. On MIPS I, that gives unpredictable results for the value read since it doesn't stall for that hazard which bypass-forwarding can't handle. (On later MIPS, it stalls; load delay slots aren't architectural, just performance.) Of course the result is eventually just written to the zero register so there's no observable difference.

    But if simplistic hardware didn't special case the zero register for bypass forwarding, something like addiu $0, $0, 123 / addi $1, $0, 0 could copy a non-zero value. So a correct MIPS design couldn't be that simplistic, and does have to already special case $zero when doing hazard detection for bypass forwarding, to make sure it doesn't bypass-forward to a read from $0, which is required to always read zero.

    A MIPS design that stalls instead of bypass forwarding wouldn't have to special-case $0 in hazard detection, because all values would go through the register file. But that probably wasn't a consideration for MIPS architects because bypass forwarding is essential for good performance in most code.