Beginning Logic Design – Part 13

Hello and welcome to Part 13 of my Beginning Logic Design series! In the last post I implemented my branch instructions. For this round, I want to implement my ALU operations.

ALU Instructions and Arguments

For my ALU, I want to follow a slightly different pattern for my arguments. In the instructions implemented so far the lower 4 bits of the instruction represented a certain operation within the instruction family. For the ALU operations I’d like to use these 4 bits to instead represent the operands of the instruction.

With the 4  bits available, I’ll use 2 bits to encode each operand with the following representations:

  1. 00 - A register
  2. 01 - B register
  3. 10 - C register
  4. 11 - Unused
00 - A register
01 - B register
10 - C register
11 - Unused

So the overall format (in binary) of these ALU instructions will be iiiiaabb. Where i represents the instruction, a the first encoded operand and b for the second.

For all of the ALU instructions, I will use the second operand to indicate where the result will be stored. The instructions ADD, SUBTRACT, BIT_AND, BIT_OR and BIT_XOR all use two operands, so the second operand is used in the instruction and is where the result is stored. For the remaining operations INCREMENT, DECREMENT, BIT_NOT, SHIFT_LEFT, SHIFT_RIGHT, ROTATE_LEFT and ROTATE_RIGHT the first operand is used in the operation and the second is where the result is to be stored.



Wiring up the ALU

The first thing I’ll need to build instructions for the ALU, will be to actually include it in the processor!

First, near the top of my cpu.sv file I’ll include my ALU package.

  1. import ALU::*;
import ALU::*;

Next, inside my cpu module, just under the other internal declarations, I’ll add signals to interface with my ALU and the ALU instance itself.

  1. // ALU signals and module
  2. logic alu_clock;
  3. opcode alu_operation;
  4. logic [7:0] alu_a;
  5. logic [7:0] alu_b;
  6. logic alu_carry_in;
  7. logic [7:0] alu_y;
  8. logic alu_zero;
  9. logic alu_sign;
  10. logic alu_carry_out;
  11. logic alu_overflow;
  12. assign alu_clock = !clock;
  13. alu cpu_alu (
  14. alu_clock,
  15. alu_operation,
  16. alu_a,
  17. alu_b,
  18. alu_carry_in,
  19. alu_y,
  20. alu_zero,
  21. alu_sign,
  22. alu_carry_out,
  23. alu_overflow
  24. );
// ALU signals and module
logic alu_clock;
opcode alu_operation;
logic [7:0] alu_a;
logic [7:0] alu_b;
logic alu_carry_in;
logic [7:0] alu_y;
logic alu_zero;
logic alu_sign;
logic alu_carry_out;
logic alu_overflow;
assign alu_clock = !clock;
alu cpu_alu (
  alu_clock,
  alu_operation,
  alu_a,
  alu_b,
  alu_carry_in,
  alu_y,
  alu_zero,
  alu_sign,
  alu_carry_out,
  alu_overflow
);

I’ve set my alu_clock to follow an inverted clock similar to how the system bus operates.

Next, within my FETCH  CPU state, I’ll add another $cast() call to set my alu_operation to be upper four bits of my current instruction, just like I have for my op_type since I mapped the same values for CPU and ALU operations. There are some possible edge cases where the CPU operation will map to a number that has no meaning to the ALU, so we’ll add a sanity check to make sure it’s within the supported range.

  1. if (data_bus[7:4] < 15)
  2. $cast(alu_operation, data_bus[7:4]);
if (data_bus[7:4] < 15)
 $cast(alu_operation, data_bus[7:4]);

That’ll get the basics in place for the ALU.

Implementing ALU operations

The first instruction I want to get setup is the ADD instruction.

In the first cycle of ADD, I’ll set my ALU variables to match the registers specified in the instruction as well as passing in our current carry flag:

  1. CPU_ADD: begin
  2. case(cycle)
  3. 0: begin
  4. case(instruction[3:2])
  5. 0: alu_a <= a;
  6. 1: alu_a <= b;
  7. 2: alu_a <= c;
  8. endcase
  9. case(instruction[1:0])
  10. 0: alu_b <= a;
  11. 1: alu_b <= b;
  12. 2: alu_b <= c;
  13. endcase
  14. alu_carry_in <= carry;
  15. end
  16. endcase
  17. end
CPU_ADD: begin
  case(cycle)
    0: begin
      case(instruction[3:2])
        0: alu_a <= a;
        1: alu_a <= b;
        2: alu_a <= c;
      endcase
      case(instruction[1:0])
        0: alu_b <= a;
        1: alu_b <= b;
        2: alu_b <= c;
      endcase
      alu_carry_in <= carry;
    end
  endcase
end

On the next cycle our ALU will have presented its results so we can, in a similar fashion, store the result and set the modified flags.

  1. 1: begin
  2. case(instruction[1:0])
  3. 0: a <= alu_y;
  4. 1: b <= alu_y;
  5. 2: c <= alu_y;
  6. endcase
  7. carry <= alu_carry_out;
  8. zero <= alu_zero;
  9. sign <= alu_sign;
  10. overflow <= alu_overflow;
  11. program_counter++;
  12. state <= FETCH;
  13. end
1: begin
  case(instruction[1:0])
    0: a <= alu_y;
    1: b <= alu_y;
    2: c <= alu_y;
  endcase
  carry <= alu_carry_out;
  zero <= alu_zero;
  sign <= alu_sign;
  overflow <= alu_overflow;
  program_counter++;
  state <= FETCH;
end

Getting the ADD to work was just that easy, but better yet this pattern also works for SUBTRACT! We can just let both operations follow this same case statement.

  1. CPU_ADD, CPU_SUBTRACT: begin
  2. case(cycle)
  3. 0: begin
  4. case(instruction[3:2])
  5. 0: alu_a <= a;
  6. 1: alu_a <= b;
  7. 2: alu_a <= c;
  8. endcase
  9. case(instruction[1:0])
  10. 0: alu_b <= a;
  11. 1: alu_b <= b;
  12. 2: alu_b <= c;
  13. endcase
  14. alu_carry_in <= carry;
  15. end
  16. 1: begin
  17. case(instruction[1:0])
  18. 0: a <= alu_y;
  19. 1: b <= alu_y;
  20. 2: c <= alu_y;
  21. endcase
  22. carry <= alu_carry_out;
  23. zero <= alu_zero;
  24. sign <= alu_sign;
  25. overflow <= alu_overflow;
  26. program_counter++;
  27. state <= FETCH;
  28. end
  29. endcase
  30. end
CPU_ADD, CPU_SUBTRACT: begin
  case(cycle)
    0: begin
      case(instruction[3:2])
        0: alu_a <= a;
        1: alu_a <= b;
        2: alu_a <= c;
      endcase
      case(instruction[1:0])
        0: alu_b <= a;
        1: alu_b <= b;
        2: alu_b <= c;
      endcase
      alu_carry_in <= carry;
    end
    1: begin
      case(instruction[1:0])
        0: a <= alu_y;
        1: b <= alu_y;
        2: c <= alu_y;
      endcase
      carry <= alu_carry_out;
      zero <= alu_zero;
      sign <= alu_sign;
      overflow <= alu_overflow;
      program_counter++;
      state <= FETCH;
    end
  endcase
end

It almost supports SHIFT_RIGHT, ROTATE_LEFT and ROTATE_RIGHT too, as these operations should also set most of these same flags. The issue is that ADD and SUBTRACT affect the overflow flag, so I’ll use my powers of copy-pasta to separate those into a case that doesn’t set overflow, but is otherwise identical.

  1. CPU_SHIFT_RIGHT, CPU_ROTATE_LEFT, CPU_ROTATE_RIGHT: begin
  2. case(cycle)
  3. 0: begin
  4. case(instruction[3:2])
  5. 0: alu_a <= a;
  6. 1: alu_a <= b;
  7. 2: alu_a <= c;
  8. endcase
  9. case(instruction[1:0])
  10. 0: alu_b <= a;
  11. 1: alu_b <= b;
  12. 2: alu_b <= c;
  13. endcase
  14. alu_carry_in <= carry;
  15. end
  16. 1: begin
  17. case(instruction[1:0])
  18. 0: a <= alu_y;
  19. 1: b <= alu_y;
  20. 2: c <= alu_y;
  21. endcase
  22. carry <= alu_carry_out;
  23. zero <= alu_zero;
  24. sign <= alu_sign;
  25. program_counter++;
  26. state <= FETCH;
  27. end
  28. endcase
  29. end
CPU_SHIFT_RIGHT, CPU_ROTATE_LEFT, CPU_ROTATE_RIGHT: begin
  case(cycle)
    0: begin
      case(instruction[3:2])
        0: alu_a <= a;
        1: alu_a <= b;
        2: alu_a <= c;
      endcase
      case(instruction[1:0])
        0: alu_b <= a;
        1: alu_b <= b;
        2: alu_b <= c;
      endcase
      alu_carry_in <= carry;
    end
    1: begin
      case(instruction[1:0])
        0: a <= alu_y;
        1: b <= alu_y;
        2: c <= alu_y;
      endcase
      carry <= alu_carry_out;
      zero <= alu_zero;
      sign <= alu_sign;
      program_counter++;
      state <= FETCH;
    end
  endcase
end

That’s 5 of the 12 operations already. The last 7 can also be bundled into the same case statement, the only difference for them is they don’t care what the carry flag is set to, and they only affect the sign and zero flags.

  1. CPU_INCREMENT, CPU_DECREMENT, CPU_AND, CPU_OR, CPU_XOR, CPU_NOR, CPU_SHIFT_LEFT: begin
  2. case(cycle)
  3. 0: begin
  4. case(instruction[3:2])
  5. 0: alu_a <= a;
  6. 1: alu_a <= b;
  7. 2: alu_a <= c;
  8. endcase
  9. case(instruction[1:0])
  10. 0: alu_b <= a;
  11. 1: alu_b <= b;
  12. 2: alu_b <= c;
  13. endcase
  14. end
  15. 1: begin
  16. case(instruction[1:0])
  17. 0: a <= alu_y;
  18. 1: b <= alu_y;
  19. 2: c <= alu_y;
  20. endcase
  21. zero <= alu_zero;
  22. sign <= alu_sign;
  23. program_counter++;
  24. state <= FETCH;
  25. end
  26. endcase
  27. end
CPU_INCREMENT, CPU_DECREMENT, CPU_AND, CPU_OR, CPU_XOR, CPU_NOR, CPU_SHIFT_LEFT: begin
  case(cycle)
    0: begin
      case(instruction[3:2])
        0: alu_a <= a;
        1: alu_a <= b;
        2: alu_a <= c;
      endcase
      case(instruction[1:0])
        0: alu_b <= a;
        1: alu_b <= b;
        2: alu_b <= c;
      endcase
    end
    1: begin
      case(instruction[1:0])
        0: a <= alu_y;
        1: b <= alu_y;
        2: c <= alu_y;
      endcase
      zero <= alu_zero;
      sign <= alu_sign;
      program_counter++;
      state <= FETCH;
    end
  endcase
end

Huzzah! We can now utilize our ALU operations via our CPU program code. In the next post I will add some operations to my CPU to include stack functionality and operations that can be used to call subroutines. As always, I welcome your feedback and questions in the comments. Keep tinkering!

Leave a Reply