Hello and welcome to Part 12 of my Beginning Logic Design series! In the last post I implemented the LOAD
and STORE
sets of operations. In this round I will start to implement branching operations that allow the code to take different paths through a program.
Branching Out
One of the most important things a CPU needs is the ability to branch, or jump, to different program code based on some conditions. Imagine an if/else statement in most common programming languages. You have some condition that you evaluate, and based on that outcome you perform one set of operations or another.
The first step I want to take towards implementing this is adding my CPU flags that will represent the conditions that can be considered.
// CPU flags
logic zero;
logic sign;
logic overflow;
logic carry;
- // CPU flags
- logic zero;
- logic sign;
- logic overflow;
- logic carry;
// CPU flags
logic zero;
logic sign;
logic overflow;
logic carry;
I’ll also modify my CPU’s RESET
state to set these all to 0
on reset.
RESET: begin
state <= FETCH;
program_counter <= 'h8000;
stack <= 0;
read <= 0;
write <= 0;
address_bus <= 0;
write_data <= 0;
zero <= 0;
sign <= 0;
overflow <= 0;
carry <= 0;
end
- RESET: begin
- state <= FETCH;
- program_counter <= 'h8000;
- stack <= 0;
- read <= 0;
- write <= 0;
- address_bus <= 0;
- write_data <= 0;
- zero <= 0;
- sign <= 0;
- overflow <= 0;
- carry <= 0;
- end
RESET: begin
state <= FETCH;
program_counter <= 'h8000;
stack <= 0;
read <= 0;
write <= 0;
address_bus <= 0;
write_data <= 0;
zero <= 0;
sign <= 0;
overflow <= 0;
carry <= 0;
end
Next, I want to check to make sure the instructions I have defined so far are setting these flags as I’d like them to. Right now the only commands that should be modifying these flags are the LOAD
commands, if the number loaded is 0
, then the zero
flag should be set. If the number loaded could be interpreted as a negative number (it’s highest bit is 1
), the sign
flag should get set.
This is easily implement by adding this to the final cycle of each load command, right near where the register loaded is being set.
if (data_bus == 0)
zero <= 1;
else
zero <= 0;
sign <= data_bus[7];
- if (data_bus == 0)
- zero <= 1;
- else
- zero <= 0;
- sign <= data_bus[7];
if (data_bus == 0)
zero <= 1;
else
zero <= 0;
sign <= data_bus[7];
I’ll write a test program to load A
with 0
, then load B
it with ff
(-1).
c0 00
c1 ff
In simulation, it looks good!
Now for the BRANCH
operations themselves! For now I have 10 Branch operations I’d like to define:
0 - Halt
1 - Jump
2 - Branch if zero set
3 - Branch if zero unset
4 - Branch if sign set
5 - Branch if sign unset
6 - Branch if overflow set
7 - Branch if overflow unset
8 - Branch if carry set
9 - Branch if carry unset
- 0 - Halt
- 1 - Jump
- 2 - Branch if zero set
- 3 - Branch if zero unset
- 4 - Branch if sign set
- 5 - Branch if sign unset
- 6 - Branch if overflow set
- 7 - Branch if overflow unset
- 8 - Branch if carry set
- 9 - Branch if carry unset
0 - Halt
1 - Jump
2 - Branch if zero set
3 - Branch if zero unset
4 - Branch if sign set
5 - Branch if sign unset
6 - Branch if overflow set
7 - Branch if overflow unset
8 - Branch if carry set
9 - Branch if carry unset
These will be fairly quick to implement, as all of them are quite similar. First I will setup my overall case
statement structure.
BRANCH: begin
case (instruction[3:0])
// Halt
0: begin
end
// Jump
1: begin
end
// Branch zero set
2: begin
end
// Branch zero clear
3: begin
end
// Branch sign set
4: begin
end
// Branch sign clear
5: begin
end
// Branch overflow set
6: begin
end
// Branch overflow clear
7: begin
end
// Branch carry set
8: begin
end
// Branch carry clear
9: begin
end
endcase
end
- BRANCH: begin
- case (instruction[3:0])
- // Halt
- 0: begin
- end
- // Jump
- 1: begin
- end
- // Branch zero set
- 2: begin
- end
- // Branch zero clear
- 3: begin
- end
- // Branch sign set
- 4: begin
- end
- // Branch sign clear
- 5: begin
- end
- // Branch overflow set
- 6: begin
- end
- // Branch overflow clear
- 7: begin
- end
- // Branch carry set
- 8: begin
- end
- // Branch carry clear
- 9: begin
- end
- endcase
- end
BRANCH: begin
case (instruction[3:0])
// Halt
0: begin
end
// Jump
1: begin
end
// Branch zero set
2: begin
end
// Branch zero clear
3: begin
end
// Branch sign set
4: begin
end
// Branch sign clear
5: begin
end
// Branch overflow set
6: begin
end
// Branch overflow clear
7: begin
end
// Branch carry set
8: begin
end
// Branch carry clear
9: begin
end
endcase
end
The HALT
operation is dead simple, just change the CPU state to HALT
// Halt
0: begin
state <= HALT;
end
- // Halt
- 0: begin
- state <= HALT;
- end
// Halt
0: begin
state <= HALT;
end
I’ll give this operation a test shortly, first I want to implement my JUMP
operation. The implementation of that begins pretty similarly to the other operations that look for a memory address, there will be 3 cycles. The first address byte will be requested; on the second cycle the first byte read and the second byte requested; on the last cycle the reading will stop and the program_counter
will be set to its new value.
// Jump
1: begin
case (cycle)
0: begin
read <= 1;
address_bus <= program_counter + 1;
end
1: begin
address_bus <= program_counter + 2;
x <= data_bus;
end
2: begin
read <= 0;
program_counter <= {x,data_bus};
state <= FETCH;
end
endcase
end
- // Jump
- 1: begin
- case (cycle)
- 0: begin
- read <= 1;
- address_bus <= program_counter + 1;
- end
- 1: begin
- address_bus <= program_counter + 2;
- x <= data_bus;
- end
- 2: begin
- read <= 0;
- program_counter <= {x,data_bus};
- state <= FETCH;
- end
- endcase
- end
// Jump
1: begin
case (cycle)
0: begin
read <= 1;
address_bus <= program_counter + 1;
end
1: begin
address_bus <= program_counter + 2;
x <= data_bus;
end
2: begin
read <= 0;
program_counter <= {x,data_bus};
state <= FETCH;
end
endcase
end
Now, as a test, I’ll extend my last program that set the flags to include a JUMP
call, after that instruction I’ll pad a few bytes with FF
and at 8010
I’ll have my HALT
instruction.
c0 00
c1 ff
e1 80 10
ff ff ff ff ff ff ff ff ff
e0
- c0 00
- c1 ff
- e1 80 10
- ff ff ff ff ff ff ff ff ff
- e0
c0 00
c1 ff
e1 80 10
ff ff ff ff ff ff ff ff ff
e0
Testing it in the simulator it works like a charm!
Conditional Branches
The conditional branches are fairly simple to implement, I just took my JUMP
implementation and added a condition on the flag during the first cycle. If the condition is not met, we can modify the program counter to start the fetch of the next instruction
// Branch zero set
2: begin
case (cycle)
0: begin
if (zero) begin
read <= 1;
address_bus <= program_counter + 1;
end else begin
program_counter += 3;
state <= FETCH;
end
end
1: begin
address_bus <= program_counter + 2;
x <= data_bus;
end
2: begin
read <= 0;
program_counter <= {x,data_bus};
state <= FETCH;
end
endcase
end
- // Branch zero set
- 2: begin
- case (cycle)
- 0: begin
- if (zero) begin
- read <= 1;
- address_bus <= program_counter + 1;
- end else begin
- program_counter += 3;
- state <= FETCH;
- end
- end
- 1: begin
- address_bus <= program_counter + 2;
- x <= data_bus;
- end
- 2: begin
- read <= 0;
- program_counter <= {x,data_bus};
- state <= FETCH;
- end
- endcase
- end
// Branch zero set
2: begin
case (cycle)
0: begin
if (zero) begin
read <= 1;
address_bus <= program_counter + 1;
end else begin
program_counter += 3;
state <= FETCH;
end
end
1: begin
address_bus <= program_counter + 2;
x <= data_bus;
end
2: begin
read <= 0;
program_counter <= {x,data_bus};
state <= FETCH;
end
endcase
end
This is basically the same for the Branch zero clear
operation, the only change is a flipping of the if
/else
statements.
// Branch zero clear
3: begin
case (cycle)
0: begin
if (zero) begin
program_counter += 3;
state <= FETCH;
end else begin
read <= 1;
address_bus <= program_counter + 1;
end
end
1: begin
address_bus <= program_counter + 2;
x <= data_bus;
end
2: begin
read <= 0;
program_counter <= {x,data_bus};
state <= FETCH;
end
endcase
end
- // Branch zero clear
- 3: begin
- case (cycle)
- 0: begin
- if (zero) begin
- program_counter += 3;
- state <= FETCH;
- end else begin
- read <= 1;
- address_bus <= program_counter + 1;
- end
- end
- 1: begin
- address_bus <= program_counter + 2;
- x <= data_bus;
- end
- 2: begin
- read <= 0;
- program_counter <= {x,data_bus};
- state <= FETCH;
- end
- endcase
- end
// Branch zero clear
3: begin
case (cycle)
0: begin
if (zero) begin
program_counter += 3;
state <= FETCH;
end else begin
read <= 1;
address_bus <= program_counter + 1;
end
end
1: begin
address_bus <= program_counter + 2;
x <= data_bus;
end
2: begin
read <= 0;
program_counter <= {x,data_bus};
state <= FETCH;
end
endcase
end
The remaining operations are a very slight derivation of these two, some copy-pasta will do the trick and the only change is what flag is being looked at in the if
condition.
// Branch sign set
4: begin
case (cycle)
0: begin
if (sign) begin
read <= 1;
address_bus <= program_counter + 1;
end else begin
program_counter += 3;
state <= FETCH;
end
end
1: begin
address_bus <= program_counter + 2;
x <= data_bus;
end
2: begin
read <= 0;
program_counter <= {x,data_bus};
state <= FETCH;
end
endcase
end
// Branch sign clear
5: begin
case (cycle)
0: begin
if (sign) begin
program_counter += 3;
state <= FETCH;
end else begin
read <= 1;
address_bus <= program_counter + 1;
end
end
1: begin
address_bus <= program_counter + 2;
x <= data_bus;
end
2: begin
read <= 0;
program_counter <= {x,data_bus};
state <= FETCH;
end
endcase
end
// Branch overflow set
6: begin
case (cycle)
0: begin
if (overflow) begin
read <= 1;
address_bus <= program_counter + 1;
end else begin
program_counter += 3;
state <= FETCH;
end
end
1: begin
address_bus <= program_counter + 2;
x <= data_bus;
end
2: begin
read <= 0;
program_counter <= {x,data_bus};
state <= FETCH;
end
endcase
end
// Branch overflow clear
7: begin
case (cycle)
0: begin
if (overflow) begin
program_counter += 3;
state <= FETCH;
end else begin
read <= 1;
address_bus <= program_counter + 1;
end
end
1: begin
address_bus <= program_counter + 2;
x <= data_bus;
end
2: begin
read <= 0;
program_counter <= {x,data_bus};
state <= FETCH;
end
endcase
end
// Branch carry set
8: begin
case (cycle)
0: begin
if (carry) begin
read <= 1;
address_bus <= program_counter + 1;
end else begin
program_counter += 3;
state <= FETCH;
end
end
1: begin
address_bus <= program_counter + 2;
x <= data_bus;
end
2: begin
read <= 0;
program_counter <= {x,data_bus};
state <= FETCH;
end
endcase
end
// Branch carry clear
9: begin
case (cycle)
0: begin
if (carry) begin
program_counter += 3;
state <= FETCH;
end else begin
read <= 1;
address_bus <= program_counter + 1;
end
end
1: begin
address_bus <= program_counter + 2;
x <= data_bus;
end
2: begin
read <= 0;
program_counter <= {x,data_bus};
state <= FETCH;
end
endcase
end
- // Branch sign set
- 4: begin
- case (cycle)
- 0: begin
- if (sign) begin
- read <= 1;
- address_bus <= program_counter + 1;
- end else begin
- program_counter += 3;
- state <= FETCH;
- end
- end
- 1: begin
- address_bus <= program_counter + 2;
- x <= data_bus;
- end
- 2: begin
- read <= 0;
- program_counter <= {x,data_bus};
- state <= FETCH;
- end
- endcase
- end
- // Branch sign clear
- 5: begin
- case (cycle)
- 0: begin
- if (sign) begin
- program_counter += 3;
- state <= FETCH;
- end else begin
- read <= 1;
- address_bus <= program_counter + 1;
- end
- end
- 1: begin
- address_bus <= program_counter + 2;
- x <= data_bus;
- end
- 2: begin
- read <= 0;
- program_counter <= {x,data_bus};
- state <= FETCH;
- end
- endcase
- end
- // Branch overflow set
- 6: begin
- case (cycle)
- 0: begin
- if (overflow) begin
- read <= 1;
- address_bus <= program_counter + 1;
- end else begin
- program_counter += 3;
- state <= FETCH;
- end
- end
- 1: begin
- address_bus <= program_counter + 2;
- x <= data_bus;
- end
- 2: begin
- read <= 0;
- program_counter <= {x,data_bus};
- state <= FETCH;
- end
- endcase
- end
- // Branch overflow clear
- 7: begin
- case (cycle)
- 0: begin
- if (overflow) begin
- program_counter += 3;
- state <= FETCH;
- end else begin
- read <= 1;
- address_bus <= program_counter + 1;
- end
- end
- 1: begin
- address_bus <= program_counter + 2;
- x <= data_bus;
- end
- 2: begin
- read <= 0;
- program_counter <= {x,data_bus};
- state <= FETCH;
- end
- endcase
- end
- // Branch carry set
- 8: begin
- case (cycle)
- 0: begin
- if (carry) begin
- read <= 1;
- address_bus <= program_counter + 1;
- end else begin
- program_counter += 3;
- state <= FETCH;
- end
- end
- 1: begin
- address_bus <= program_counter + 2;
- x <= data_bus;
- end
- 2: begin
- read <= 0;
- program_counter <= {x,data_bus};
- state <= FETCH;
- end
- endcase
- end
- // Branch carry clear
- 9: begin
- case (cycle)
- 0: begin
- if (carry) begin
- program_counter += 3;
- state <= FETCH;
- end else begin
- read <= 1;
- address_bus <= program_counter + 1;
- end
- end
- 1: begin
- address_bus <= program_counter + 2;
- x <= data_bus;
- end
- 2: begin
- read <= 0;
- program_counter <= {x,data_bus};
- state <= FETCH;
- end
- endcase
- end
// Branch sign set
4: begin
case (cycle)
0: begin
if (sign) begin
read <= 1;
address_bus <= program_counter + 1;
end else begin
program_counter += 3;
state <= FETCH;
end
end
1: begin
address_bus <= program_counter + 2;
x <= data_bus;
end
2: begin
read <= 0;
program_counter <= {x,data_bus};
state <= FETCH;
end
endcase
end
// Branch sign clear
5: begin
case (cycle)
0: begin
if (sign) begin
program_counter += 3;
state <= FETCH;
end else begin
read <= 1;
address_bus <= program_counter + 1;
end
end
1: begin
address_bus <= program_counter + 2;
x <= data_bus;
end
2: begin
read <= 0;
program_counter <= {x,data_bus};
state <= FETCH;
end
endcase
end
// Branch overflow set
6: begin
case (cycle)
0: begin
if (overflow) begin
read <= 1;
address_bus <= program_counter + 1;
end else begin
program_counter += 3;
state <= FETCH;
end
end
1: begin
address_bus <= program_counter + 2;
x <= data_bus;
end
2: begin
read <= 0;
program_counter <= {x,data_bus};
state <= FETCH;
end
endcase
end
// Branch overflow clear
7: begin
case (cycle)
0: begin
if (overflow) begin
program_counter += 3;
state <= FETCH;
end else begin
read <= 1;
address_bus <= program_counter + 1;
end
end
1: begin
address_bus <= program_counter + 2;
x <= data_bus;
end
2: begin
read <= 0;
program_counter <= {x,data_bus};
state <= FETCH;
end
endcase
end
// Branch carry set
8: begin
case (cycle)
0: begin
if (carry) begin
read <= 1;
address_bus <= program_counter + 1;
end else begin
program_counter += 3;
state <= FETCH;
end
end
1: begin
address_bus <= program_counter + 2;
x <= data_bus;
end
2: begin
read <= 0;
program_counter <= {x,data_bus};
state <= FETCH;
end
endcase
end
// Branch carry clear
9: begin
case (cycle)
0: begin
if (carry) begin
program_counter += 3;
state <= FETCH;
end else begin
read <= 1;
address_bus <= program_counter + 1;
end
end
1: begin
address_bus <= program_counter + 2;
x <= data_bus;
end
2: begin
read <= 0;
program_counter <= {x,data_bus};
state <= FETCH;
end
endcase
end
That’ll do it for the basic branching operations! In the next post I will begin the implementation of the operations that will utilize the ALU. As always, I welcome your feedback and questions in the comments. Keep tinkering!