L1 has 50788, which is the address of the current branch instruction being executed — it is the address that was fed into the Instruction Memory that results in fetching the beq x3, x14, 80
.
You can follow that after L1, there's an ADDer, which will add 4 to the PC value (offering that value to subsequent circuitry), adding 4 to skip past this 4 byte instruction, and thus referring to the next sequential memory address, which holds the next sequential instruction.
L2 is "don't care", which means it doesn't matter whether it is 0 or 1, so different implementations of the same basic design could use either value. Why doesn't it matter whether this signal is 0 or 1? Because this instruction is a conditional branch, and as such does not update a register, so RegWrite
will be 0, thus the value of WriteData
is unimportant and is ignored.
The hardware is a union of all the necessary components to execute any instruction in the instruction set, and as such, some circuitry here or there goes unused during execution of different instructions. Rather than turning off the unused circuitry (which is an advance technique that takes work to design & implement, not employed here) the unused circuitry for any given instruction is allowed to execute — but (whether turned off or allowed to execute) the control signals further down the line of datapaths are set up to ignore the results of these unused circuits based on the current instruction.
L3 is the branch condition signal, that dynamically informs the PC update circuitry whether to take the branch or not. Here that condition is effectively generated in the ALU from the expression x3 == x14
and determines the value of this control signal: if they are equal then that control signal needs to be 1 to make it take the branch (as per the definition of the conditional branch instruction) and that control signal needs to be 0 to make it not take the branch and instead continue with sequential execution.
Hopefully, you can see that for conditional branch instructions, the Branch
control signal is asserted (1/true) — this signal combined with Zero
goes into an AND gate, which results in 1
for take the branch vs. 0
for don't take the branch, by controlling that MUX after the AND gate. So, the only condition in which the branch can be taken [pc := pc + sxt(imm)*2] is when both Branch
and Zero
are true. If Branch
is false, it is not a branch instruction, so Zero
doesn't matter, and if Zero
is false, the branch condition is false, so Branch
is overridden [pc := pc + 4].
More explicitly, the PC update circuitry says:
PC := (Branch & Zero) ? PC + sxt(imm)*2 : PC + 4;
Using C ternary operator (could also be written using if-then-else).
Zero
is a rather poor choice for the name of this dynamic control signal. I would have chosen Take
or Taken
instead. I believe the name Zero
is historical from older RISC architectures.
This circuitry follows the RISC V standard of multiplying the branch target immediate field by 2 (instead of 4 as with MIPS), and this standard makes it so that regular 4 byte instructions are identical (unchanged) in the presence of compressed instructions — thus, on hardware that supports compressed instructions, no mode switching is needed, and, compressed instructions can be interleaved with uncompressed instructions (unlike with MIPS16 or ARM Thumb). However, this block diagram does not provide the other features necessary to execute compressed instructions (for one, there is no increment by 2 option diagrammed in this PC update circuitry, for another there is no compressed instruction expander, which would go in between the Instruction Memory output and the Decode logic).