Beginning Logic Design – Part 8

Hello and welcome to Part 8 of my Beginning Logic Design series. In this episode I will be implementing a system bus to allow one master component to communicate with many other components.

Building a Bus

The style of bus I will be implementing is quite common. There will be a few control signals and a 16-bit address bus controlled by the master. There will also be a bidirectional 8-bit data bus that is used for the master to write to and read from to the slave components.

System Bus graphic from Wikipedia

In my example, the control bus will be used to provide a clock to the slave devices as well as some signals to indicate if a read or write action is being performed. The address bus is how a slave device is selected, a slave may listen to a single address or a range of addresses. The data bus is used for the actual data being read or written.

For this design, I will have a RAM implementation listening to any address that starts with 0, and a second component I will call a writer that will, in simulation, print out the data written to any address that starts with 1. Based on this design, each will own half of the address space.

I’ll first create the rough outline of these modules and a top module to tie it all together.

First the master.sv definition.

`timescale 1ns / 1ps

module master (
  input logic clock,
  output logic read,
  output logic write,
  output logic [15:0] address_bus,
  inout logic [7:0] data_bus
);

endmodule

The master drives most of the bus signals, controlling the operations. The data_bus is declared as an inout so that it can be used as an input or as an output.

Next the ram and writer modules.

`timescale 1ns / 1ps

module ram (
  input logic clock,
  input logic read,
  input logic write,
  input logic [15:0] address_bus,
  inout logic [7:0] data_bus
);

endmodule
`timescale 1ns / 1ps

module writer (
  input logic clock,
  input logic read,
  input logic write,
  input logic [15:0] address_bus,
  inout logic [7:0] data_bus
);

endmodule

These modules start off with a nearly identical design. Lastly the top module to pull these together.

`timescale 1ns / 1ps

module top ();
  logic clock;

  // System Bus
  logic slave_clock;
  logic read;
  logic write;
  logic [15:0] address_bus;
  wire logic [7:0] data_bus;

  assign slave_clock = ~clock;

  master controller (
    clock,
    read,
    write,
    address_bus,
    data_bus
  );

  ram memory (
    slave_clock,
    read,
    write,
    address_bus,
    data_bus
  );

  writer io (
    slave_clock,
    read,
    write,
    address_bus,
    data_bus
  );

  initial clock = 0;

  always begin
    #1 clock = ~clock;
  end

endmodule

This top module declares the variable that are used to refer to the bus components. I will have the master follow the main clock but have the slaves follow the inverse of that clock, slave_clock. For the data_bus I need to add the wire keyword to explicitly describe it as a network shared by multiple components.



Writer Module

The writer module here will only be useful for read operations, so I will build that out first and test its usage from the master.

It’s a relatively simple module, every clock cycle the module will look to see if the highest address bit is 1 and the write control signal is high, if so it’ll display the character in the simulation output.

`timescale 1ns / 1ps

module writer (
  input logic clock,
  input logic read,
  input logic write,
  input logic [15:0] address_bus,
  inout logic [7:0] data_bus
);

  always_ff @ (posedge clock) begin
    if (write && address_bus[15] == 1) begin
      $display("%d: %c", data_bus, data_bus);
    end
  end

endmodule

Writing to the bidirectional data_bus is a bit trickier than writing to an ordinary register variable. We will need to use a register as a scratch pad to hold the write data, and use a conditional assign to put that registers data on the data_bus during the right condition (when write is 1).

logic [7:0] write_data;

assign data_bus = (write) ? write_data : 'bZ;

This assign statement will put the data from write_data into data_bus when write is 1. When write is 0, it will assign the Z (high impedance) state, which allows the signal to be driven elsewhere.

With that in place to let my master write to the data_bus, I’ll use an initial block to perform a series of write operations.

`timescale 1ns / 1ps

module master (
  input logic clock,
  output logic read,
  output logic write,
  output logic [15:0] address_bus,
  inout logic [7:0] data_bus
);

  logic [7:0] write_data;

  assign data_bus = (write) ? write_data : 'bZ;

  initial begin
    read <= 0;
    write <= 0;
    address_bus <= 16'b1000_0000_0000_0000;
    #1 write_data <= "H";
    write <= 1;
    #2 write_data <= "E";
    #2 write_data <= "L";
    #4 write_data <= "O";
    #2 write <= 0;
    #2 $finish();
  end

endmodule

As a note the underscores in my address write do not have an effect on the value, I put them there to more easily identify the 16 bits.

When I run this in the simulator, I get exactly the output I expected.

Vivado Simulator 2017.2
Time resolution is 1 ps
run -all
 72: H
 69: E
 76: L
 76: L
 79: O
$finish called at time : 13 ns : File "/home/kwilke/suchprogramming/systembus/master.sv" Line 25
exit

Implementing RAM

The RAM is a little bit trickier, but still just a few lines of SystemVerilog. The first thing needed is the space for storing data itself. We can use an array for this.

logic [7:0] memory [0:(1<<15)-1];

This is declaring an array of 8 bit registers, since we’re using the first address bit to point to memory, this leaves 15 bits for my ram module to interpret as it pleases. So I declare the array to contain 2^15 elements with [0:(1<<15)-1].

To write to this space, it’s nearly identical to how we read from the data bus for the writer module. A difference this time is that we’re also looking at the rest of the address_bus to specify which location in RAM we want this data.

always_ff @ (posedge clock) begin
  if (address_bus[15] == 0 && write) begin
    memory[address_bus[14:0]] <= data_bus;
  end
end

For reading, I’ll need a conditional assignment similar to how the master module is writing (since from the ram module perspective, a read IS a write. In this case I’ll need to consider the most significant address bit in my condition. I’ll also need to consider the lower 15 bits of the address_bus in the cases where the read condition is true.

assign data_bus = (read && address_bus[15] == 0) ? memory[address_bus[14:0]] : 'bZ;

With that in place, the RAM module should be functional.

`timescale 1ns / 1ps

module ram (
  input logic clock,
  input logic read,
  input logic write,
  input logic [15:0] address_bus,
  inout logic [7:0] data_bus
);

  assign data_bus = (read && address_bus[15] == 0) ? memory[address_bus[14:0]] : 'bZ;

  logic [7:0] memory [0:(1<<15)-1];

  always_ff @ (posedge clock) begin
    if (address_bus[15] == 0 && write) begin
      memory[address_bus[14:0]] <= data_bus;
    end
  end


endmodule

Moar Testing

With my RAM module in place I am ready to test to make sure that reads and writes can work from that as well. To test this I’d like to first write a few characters to store in RAM, then copy what’s in the RAM to the writer.

It sounds complicated, but it’ll be easy! First I’ll restructure my master to write my HELLO message to the ram module. To separate out how I control my address bus, I’ll separate out the variables I use to set the address_bus bits.

logic device; // 0 = ram; 1 = writer
logic [14:0] address;

assign address_bus = {device, address};

Then I’ll change my initial block so that I write a byte, and on the next few cycles I increment the address and write the next byte.

initial begin
  read <= 0;
  write <= 0;
  device <= 0;
  address <= 0;

  #1 write_data <= "H";
  write <= 1;

  #2 address++;
  write_data <= "E";

  #2 address++;
  write_data <= "L";

  #2 address++;

  #2 address++;
  write_data <= "O";

  #2 write <= 0;

  #2 $finish();
end

To verify this is working as I expect, and visualize what’s in my RAM, I’ll need to run my xelab command with the options -debug all so I can monitor all the signals, then launch the simulation using xsim -g top so that it loads the Vivado GUI and doesn’t automatically run the simulation.

To open the waveform viewer, you can go to Window -> Waveform. I’ll step 1ns at a time to see what my design is doing. By clicking on memory from my Scope tab, and expanding memory in the Objects tab I can see each memory location.

I’ll change the radix of memory to be ASCII so I can see the character values more easily, and step through the rest of the simulation.

It looks good to go!

Now to implement the copy, I’ll use a for loop after I’ve written my bytes to ram to read one byte and write the returned data back to my writer, looping 5 times.

// Copy 5 bytes from ram to writer
for (int i = 0; i < 5; i++) begin
  // Read byte from ram
  #2 device <= 0;
  address <= i;
  read <= 1;
  write <= 0;
  // Store byte and write to writer
  #2 write_data <= data_bus;
  device <= 1;
  read <= 0;
  write <= 1;
end

Testing again in the simulator I can verify it writes the data to RAM, then proceeds through my loop to read each byte and send it to the writer!

Bus getting busy

And that does it for this post! I have a working bus system that can allow a single component a means to interface with multiple devices through a shared bus. Please leave any feedback or questions you may have in the comments. Keep tinkering!

Beginning Logic Design – Part 7

Hello and welcome to Part 7 of my Beginning Logic Design series. In this post I will continue to add functionality to the ALU I’ve been working on in the last couple posts.

Using Vivado Via Command Line

This ALU project is making decent progress and I’d like to start managing the code with Git and make my work flow a bit faster. When I talk to engineers that work with FPGAs for a living, it seems like most of them do not use the project flow that I’ve been working with. They favor the command line or batch mode approach.

I’ll first copy my alu.sv and top.sv to a new directory so that those files are all I have. To build and test on the command line there are 3 commands I’ll use, xvlog to parse my SystemVerilog, xelab to elaborate the design, and xsim to run the simulation.

xvlog --sv alu.sv top.sv
xelab top
xsim -R top

Armed with these commands, I can more easily write my code in whichever editor I choose!

I’ll use this Makefile to give me an easy way to test and clean up the built files and logs.

XVLOG_FILES=xvlog.log xvlog.pb xsim.dir
XELAB_FILES=webtalk*.log webtalk*.jou xelab.log xelab.pb .Xil/
XSIM_FILES=xsim*.log xsim*.jou

all:
  @echo "Parsing HDL"
  @xvlog --sv *.sv > build.log
  @echo "Elaborating design"
  @xelab top >> build.log
  @echo "Starting simulation"
  @xsim -R top

clean:
  rm -rf $(XVLOG_FILES) $(XELAB_FILES) $(XSIM_FILES) build.log



Implementing Subtraction

Subtraction in binary is nearly identical to addition. In the case of subtraction, carry is looked at as a borrow value. Building a subtraction operation in SystemVerilog is the same as addition, but with - instead of +.

// Subtract operation internals
logic [7:0] subtract;
logic [6:0] subtract_lower_bits;
logic subtract_borrow_6;
logic subtract_borrow_7;

...

assign {subtract_borrow_6, subtract_lower_bits} = a[6:0] - b[6:0] - carry_in,
    {subtract_borrow_7, subtract} = a - b - carry_in;

...

        SUBTRACT: begin
            y <= subtract;
            carry_out <= subtract_borrow_7;
            sign <= subtract[7];
            overflow <= subtract_borrow_7 ^ subtract_borrow_6;
            if (subtract == 0)
                zero <= 1;
            else
                zero <= 0;
        end

As with the addition, I’ll make some tests that validate I’m getting the results I expect from the operation.

// Test 100 - 80
operation = SUBTRACT;
a = 100;
b = 80;
#2 assert(y == 20) else $error("Subtract assertion failed.");
assert(zero == 0) else $error("Zero assertion failed.");
assert(sign == 0) else $error("Sign assertion failed.");
assert(carry_out == 0) else $error("Carry assertion failed.");
assert(overflow == 0) else $error("Overflow assertion failed.");

// Test 80 - 100
a = 80;
b = 100;
#2 assert(y == 236) else $error("Subtract assertion failed.");
assert(zero == 0) else $error("Zero assertion failed.");
assert(sign == 1) else $error("Sign assertion failed.");
assert(carry_out == 1) else $error("Carry assertion failed.");
assert(overflow == 0) else $error("Overflow assertion failed.");

// Test 80 - 176
a = 80;
b = 176;
#2 assert(y == 160) else $error("Subtract assertion failed.");
assert(zero == 0) else $error("Zero assertion failed.");
assert(sign == 1) else $error("Sign assertion failed.");
assert(carry_out == 1) else $error("Carry assertion failed.");
assert(overflow == 1) else $error("Overflow assertion failed.");

// Test 208 - 112
a = 208;
b = 112;
#2 assert(y == 96) else $error("Subtract assertion failed.");
assert(zero == 0) else $error("Zero assertion failed.");
assert(sign == 0) else $error("Sign assertion failed.");
assert(carry_out == 0) else $error("Carry assertion failed.");
assert(overflow == 1) else $error("Overflow assertion failed.");

// Test 208 - 208
a = 208;
b = 208;
#2 assert(y == 0) else $error("Subtract assertion failed.");
assert(zero == 1) else $error("Zero assertion failed.");
assert(sign == 0) else $error("Sign assertion failed.");
assert(carry_out == 0) else $error("Carry assertion failed.");
assert(overflow == 0) else $error("Overflow assertion failed.");

Implementing Remaining Operations

Addition and Subtraction are the trickiest operations to implement of the operations planned for this ALU. With two instructions down there are 10 to go,

  • Arithmetic Operations
    • Add with Carry
    • Subtract with Carry
    • Increment
    • Decrement
  • Logic Operations
    • AND
    • OR
    • XOR
    • NOT/Negate (not in 6502, but I want my ALU to have it!)
  • Shifting Operations
    • Arithmetic Shift Left
    • Logical Shift Right
    • Rotate Left
    • Rotate Right

Increment & Decrement

Increment and decrement only affect the sign and zero flags, and adds or subtracts 1 from a single operand. To implement them I’ll use assign again to give me a reference to an incremented and decremented value.

// Increment/Decrement
logic [7:0] incremented;
logic [7:0] decremented;


assign incremented = a + 1,
  decremented = a - 1;

Then in my case statement on operation, I’ll add the INCREMENT and DECREMENT cases.

INCREMENT: begin
  y <= incremented;
  sign <= incremented[7];
  if (incremented == 0) 
    zero <= 1;
  else
    zero <= 0;
end
DECREMENT: begin
  y <= decremented;
  sign <= decremented[7];
  if (decremented == 0) 
    zero <= 1;
  else
    zero <= 0;
end

Then I’ll add some more tests to top to verify it.

// Test 0++
operation = INCREMENT;
a = 0;
#2 assert(y == 1) else $error("Increment assertion failed.");
assert(zero == 0) else $error("Zero assertion failed.");
assert(sign == 0) else $error("Sign assertion failed.");

// Test 200++
a = 200;
#2 assert(y == 201) else $error("Increment assertion failed.");
assert(zero == 0) else $error("Zero assertion failed.");
assert(sign == 1) else $error("Sign assertion failed.");

// Test 255++
a = 255;
#2 assert(y == 0) else $error("Increment assertion failed.");
assert(zero == 1) else $error("Zero assertion failed.");
assert(sign == 0) else $error("Sign assertion failed.");

// Test 0--
operation = DECREMENT;
a = 0;
#2 assert(y == 255) else $error("Decrement assertion failed.");
assert(zero == 0) else $error("Zero assertion failed.");
assert(sign == 1) else $error("Sign assertion failed.");

// Test 200--
a = 200;
#2 assert(y == 199) else $error("Decrement assertion failed.");
assert(zero == 0) else $error("Zero assertion failed.");
assert(sign == 1) else $error("Sign assertion failed.");

// Test 1--
a = 1;
#2 assert(y == 0) else $error("Decrement assertion failed.");
assert(zero == 1) else $error("Zero assertion failed.");
assert(sign == 0) else $error("Sign a

Logic Operations

The logic operations AND, OR, XOR, and NOT are the easiest operations to implement. Very similar to what I did for increment and decrement, I’ll name some new variables and keep them assigned to the results for each operation. Then they are added to the case statement.

// Logic operations
logic [7:0] and_result;
logic [7:0] or_result;
logic [7:0] xor_result;
logic [7:0] not_result;

// Logic assignments
assign and_result = a & b,
  or_result = a | b,
  xor_result = a ^ b,
  not_result = ~a;

...

    BIT_AND: begin
      y <= and_result;
      sign <= and_result[7];
      if (and_result == 0)
        zero <= 1;
      else
        zero <= 0;
    end
    BIT_OR: begin
      y <= or_result;
      sign <= or_result[7];
      if (or_result == 0)
        zero <= 1;
      else
        zero <= 0;
    end
    BIT_XOR: begin
      y <= xor_result;
      sign <= xor_result[7];
      if (xor_result == 0)
        zero <= 1;
      else
        zero <= 0;
    end
    BIT_NOT: begin
      y <= not_result;
      sign <= not_result[7];
      if (not_result == 0)
        zero <= 1;
      else
        zero <= 0;
    end

Then some tests to verify.

// Test 12 & 10
operation = BIT_AND;
a = 12;
b = 10;
#2 assert(y == 8) else $error("AND assertion failed.");
assert(zero == 0) else $error("Zero assertion failed.");
assert(sign == 0) else $error("Sign assertion failed.");

// Test 13 | 15
operation = BIT_OR;
a = 13;
b = 15;
#2 assert(y == 15) else $error("OR assertion failed.");
assert(zero == 0) else $error("Zero assertion failed.");
assert(sign == 0) else $error("Sign assertion failed.");

// Test 13 ^ 15
operation = BIT_XOR;
a = 13;
b = 15;
#2 assert(y == 2) else $error("XOR assertion failed.");
assert(zero == 0) else $error("Zero assertion failed.");
assert(sign == 0) else $error("Sign assertion failed.");

// Test 13 & 15
operation = BIT_NOT;
a = 13;
#2 assert(y == 242) else $error("NOT assertion failed.");
assert(zero == 0) else $error("Zero assertion failed.");
assert(sign == 1) else $error("Sign assertion failed.");

Shifting Operations

The last few operations to add are the shifters. Rotate left and rotate right shift all the bits left or right by one position. In the case of a right shift, the leftmost bit will change to the carry in bit, and the rightmost bit will become the new carry out. In the arithmetic and logical shift operations the bit being pushed in will be 0 regardless of the carry in value.

// Shifting operations
logic [7:0] rotated_left;
logic [7:0] rotated_right;
logic [7:0] shifted_left;
logic [7:0] shifted_right;

// Shifting assignments
assign rotated_left = {a[6:0], carry_in},
  rotated_right = {carry_in, a[7:1]},
  shifted_left = {a[6:0], 1'b0},
  shifted_right = {1'b0, a[7:1]};

...

    ROTATE_LEFT: begin
      y <= rotated_left;
      carry_out <= a[7];
      sign <= rotated_left[7];
      if (rotated_left == 0)
        zero <= 1;
      else
        zero <= 0;
    end
    ROTATE_RIGHT: begin
      y <= rotated_right;
      carry_out <= a[0];
      sign <= rotated_right[7];
      if (rotated_right == 0)
        zero <= 1;
      else
        zero <= 0;
    end
    SHIFT_LEFT: begin
      y <= shifted_left;
      carry_out <= a[7];
      sign <= shifted_left[7];
      if (shifted_left == 0)
        zero <= 1;
      else
        zero <= 0;
    end
    SHIFT_RIGHT: begin
      y <= shifted_right;
      carry_out <= a[0];
      sign <= shifted_right[7];
      if (shifted_right == 0)
        zero <= 1;
      else
        zero <= 0;

For the shift assignments I use 1'b0 to specify a 1 bit binary with the value 0 in the concatenation. As always some tests to validate we’re getting the right behavior.

// Test shift left
operation = SHIFT_LEFT;
carry_in = 1;
a = 136;
#2 assert(y == 16) else $error("Shift left assertion failed.");
assert(carry_out == 1) else $error("carry assertion failed.");
assert(zero == 0) else $error("Zero assertion failed.");
assert(sign == 0) else $error("Sign assertion failed.");

// Test shift right
operation = SHIFT_RIGHT;
a = 8;
#2 assert(y == 4) else $error("Shift right assertion failed.");
assert(carry_out == 0) else $error("carry assertion failed.");
assert(zero == 0) else $error("Zero assertion failed.");
assert(sign == 0) else $error("Sign assertion failed.");

// Test rotate left
operation = ROTATE_LEFT;
a = 8;
#2 assert(y == 17) else $error("Rotate left assertion failed.");
assert(carry_out == 0) else $error("carry assertion failed.");
assert(zero == 0) else $error("Zero assertion failed.");
assert(sign == 0) else $error("Sign assertion failed.");

// Test rotate right
operation = ROTATE_RIGHT;
a = 9;
#2 assert(y == 132) else $error("Rotate right assertion failed.");
assert(carry_out == 1) else $error("carry assertion failed.");
assert(zero == 0) else $error("Zero assertion failed.");
assert(sign == 1) else $error("Sign assertion failed.");

It Works!

There it is! An ALU implementation that can handle 12 different 8-bit operations. It took 3 posts to get here, but this is a decently large design. Here’s the final alu.sv file

`timescale 1ns / 1ps

package ALU;
  typedef enum logic [3:0] {
    ADD,
    SUBTRACT,
    INCREMENT,
    DECREMENT,
    BIT_AND,
    BIT_OR,
    BIT_XOR,
    BIT_NOT,
    SHIFT_LEFT,
    SHIFT_RIGHT,
    ROTATE_LEFT,
    ROTATE_RIGHT
  } opcode;
endpackage

import ALU::*;

module alu (
  input logic clock,
  input opcode operation,
  input logic [7:0] a,
  input logic [7:0] b,
  input logic carry_in,
  output logic [7:0] y,
  output logic zero,
  output logic sign,
  output logic carry_out,
  output logic overflow
);

  // Add operation internals
  logic [7:0] add;
  logic [6:0] add_lower_bits;
  logic add_carry_6;
  logic add_carry_7;

  // Subtract operation internals
  logic [7:0] subtract;
  logic [6:0] subtract_lower_bits;
  logic subtract_borrow_6;
  logic subtract_borrow_7;

  // Increment/Decrement
  logic [7:0] incremented;
  logic [7:0] decremented;

  // Logic operations
  logic [7:0] and_result;
  logic [7:0] or_result;
  logic [7:0] xor_result;
  logic [7:0] not_result;

  // Shifting operations
  logic [7:0] rotated_left;
  logic [7:0] rotated_right;
  logic [7:0] shifted_left;
  logic [7:0] shifted_right;

  // Add assignments
  assign {add_carry_6, add_lower_bits} = a[6:0] + b[6:0] + carry_in,
    {add_carry_7, add} = a + b + carry_in;

  // Subtract assignments
  assign {subtract_borrow_6, subtract_lower_bits} = a[6:0] - b[6:0] - carry_in,
    {subtract_borrow_7, subtract} = a - b - carry_in;

  // Increment/decrement assignments
  assign incremented = a + 1,
    decremented = a - 1;

  // Logic assignments
  assign and_result = a & b,
    or_result = a | b,
    xor_result = a ^ b,
    not_result = ~a;

  // Shifting assignments
  assign rotated_left = {a[6:0], carry_in},
    rotated_right = {carry_in, a[7:1]},
    shifted_left = {a[6:0], 1'b0},
    shifted_right = {1'b0, a[7:1]};

  always_ff @ (posedge clock) begin
    case (operation)
      ADD: begin
        y <= add;
        carry_out <= add_carry_7;
        sign <= add[7];
        overflow <= add_carry_7 ^ add_carry_6;
        if (add == 0)
          zero <= 1;
        else
          zero <= 0;
      end
      SUBTRACT: begin
        y <= subtract;
        carry_out <= subtract_borrow_7;
        sign <= subtract[7];
        overflow <= subtract_borrow_7 ^ subtract_borrow_6;
        if (subtract == 0)
          zero <= 1;
        else
          zero <= 0;
      end
      INCREMENT: begin
        y <= incremented;
        sign <= incremented[7];
        if (incremented == 0) 
          zero <= 1;
        else
          zero <= 0;
      end
      DECREMENT: begin
        y <= decremented;
        sign <= decremented[7];
        if (decremented == 0) 
          zero <= 1;
        else
          zero <= 0;
      end
      BIT_AND: begin
        y <= and_result;
        sign <= and_result[7];
        if (and_result == 0)
          zero <= 1;
        else
          zero <= 0;
      end
      BIT_OR: begin
        y <= or_result;
        sign <= or_result[7];
        if (or_result == 0)
          zero <= 1;
        else
          zero <= 0;
      end
      BIT_XOR: begin
        y <= xor_result;
        sign <= xor_result[7];
        if (xor_result == 0)
          zero <= 1;
        else
          zero <= 0;
      end
      BIT_NOT: begin
        y <= not_result;
        sign <= not_result[7];
        if (not_result == 0)
          zero <= 1;
        else
          zero <= 0;
      end
      ROTATE_LEFT: begin
        y <= rotated_left;
        carry_out <= a[7];
        sign <= rotated_left[7];
        if (rotated_left == 0)
          zero <= 1;
        else
          zero <= 0;
      end
      ROTATE_RIGHT: begin
        y <= rotated_right;
        carry_out <= a[0];
        sign <= rotated_right[7];
        if (rotated_right == 0)
          zero <= 1;
        else
          zero <= 0;
      end
      SHIFT_LEFT: begin
        y <= shifted_left;
        carry_out <= a[7];
        sign <= shifted_left[7];
        if (shifted_left == 0)
          zero <= 1;
        else
          zero <= 0;
      end
      SHIFT_RIGHT: begin
        y <= shifted_right;
        carry_out <= a[0];
        sign <= shifted_right[7];
        if (shifted_right == 0)
          zero <= 1;
        else
          zero <= 0;
      end
    endcase
  end

  endmodule

And the final top.sv

`timescale 1ns / 1ps

import ALU::*;

module top ();
  logic clock;
  opcode operation;
  logic [7:0] a;
  logic [7:0] b;
  logic carry_in;
  logic [7:0] y;
  logic zero;
  logic sign;
  logic carry_out;
  logic overflow;

  alu myALU (
    clock,
    operation,
    a,
    b,
    carry_in,
    y,
    zero,
    sign,
    carry_out,
    overflow
  );

  initial begin
    clock = 0;
    operation = ADD;
    carry_in = 0;

    // Test 208 + 144
    a = 208;
    b = 144;
    #2 assert(y == 96) else $error("Sum assertion failed.");
    assert(zero == 0) else $error("Zero assertion failed.");
    assert(sign == 0) else $error("Sign assertion failed.");
    assert(carry_out == 1) else $error("Carry assertion failed.");
    assert(overflow == 1) else $error("Overflow assertion failed.");

    // Test 208 + 48 (signed -48 + 48)
    a = 208;
    b = 48;
    #2 assert(y == 0) else $error("Sum assertion failed.");
    assert(zero == 1) else $error("Zero assertion failed.");
    assert(sign == 0) else $error("Sign assertion failed.");
    assert(carry_out == 1) else $error("Carry assertion failed.");
    assert(overflow == 0) else $error("Overflow assertion failed.");

    // Test 80 + 80
    a = 80;
    b = 80;
    #2 assert(y == 160) else $error("Sum assertion failed.");
    assert(zero == 0) else $error("Zero assertion failed.");
    assert(sign == 1) else $error("Sign assertion failed.");
    assert(carry_out == 0) else $error("Carry assertion failed.");
    assert(overflow == 1) else $error("Overflow assertion failed.");

    // Test 100 - 80
    operation = SUBTRACT;
    a = 100;
    b = 80;
    #2 assert(y == 20) else $error("Subtract assertion failed.");
    assert(zero == 0) else $error("Zero assertion failed.");
    assert(sign == 0) else $error("Sign assertion failed.");
    assert(carry_out == 0) else $error("Carry assertion failed.");
    assert(overflow == 0) else $error("Overflow assertion failed.");

    // Test 80 - 100
    a = 80;
    b = 100;
    #2 assert(y == 236) else $error("Subtract assertion failed.");
    assert(zero == 0) else $error("Zero assertion failed.");
    assert(sign == 1) else $error("Sign assertion failed.");
    assert(carry_out == 1) else $error("Carry assertion failed.");
    assert(overflow == 0) else $error("Overflow assertion failed.");

    // Test 80 - 176
    a = 80;
    b = 176;
    #2 assert(y == 160) else $error("Subtract assertion failed.");
    assert(zero == 0) else $error("Zero assertion failed.");
    assert(sign == 1) else $error("Sign assertion failed.");
    assert(carry_out == 1) else $error("Carry assertion failed.");
    assert(overflow == 1) else $error("Overflow assertion failed.");

    // Test 208 - 112
    a = 208;
    b = 112;
    #2 assert(y == 96) else $error("Subtract assertion failed.");
    assert(zero == 0) else $error("Zero assertion failed.");
    assert(sign == 0) else $error("Sign assertion failed.");
    assert(carry_out == 0) else $error("Carry assertion failed.");
    assert(overflow == 1) else $error("Overflow assertion failed.");

    // Test 208 - 208
    a = 208;
    b = 208;
    #2 assert(y == 0) else $error("Subtract assertion failed.");
    assert(zero == 1) else $error("Zero assertion failed.");
    assert(sign == 0) else $error("Sign assertion failed.");
    assert(carry_out == 0) else $error("Carry assertion failed.");
    assert(overflow == 0) else $error("Overflow assertion failed.");

    // Test 0++
    operation = INCREMENT;
    a = 0;
    #2 assert(y == 1) else $error("Increment assertion failed.");
    assert(zero == 0) else $error("Zero assertion failed.");
    assert(sign == 0) else $error("Sign assertion failed.");

    // Test 200++
    a = 200;
    #2 assert(y == 201) else $error("Increment assertion failed.");
    assert(zero == 0) else $error("Zero assertion failed.");
    assert(sign == 1) else $error("Sign assertion failed.");

    // Test 255++
    a = 255;
    #2 assert(y == 0) else $error("Increment assertion failed.");
    assert(zero == 1) else $error("Zero assertion failed.");
    assert(sign == 0) else $error("Sign assertion failed.");

    // Test 0--
    operation = DECREMENT;
    a = 0;
    #2 assert(y == 255) else $error("Decrement assertion failed.");
    assert(zero == 0) else $error("Zero assertion failed.");
    assert(sign == 1) else $error("Sign assertion failed.");

    // Test 200--
    a = 200;
    #2 assert(y == 199) else $error("Decrement assertion failed.");
    assert(zero == 0) else $error("Zero assertion failed.");
    assert(sign == 1) else $error("Sign assertion failed.");

    // Test 1--
    a = 1;
    #2 assert(y == 0) else $error("Decrement assertion failed.");
    assert(zero == 1) else $error("Zero assertion failed.");
    assert(sign == 0) else $error("Sign assertion failed.");

    // Test 12 & 10
    operation = BIT_AND;
    a = 12;
    b = 10;
    #2 assert(y == 8) else $error("AND assertion failed.");
    assert(zero == 0) else $error("Zero assertion failed.");
    assert(sign == 0) else $error("Sign assertion failed.");

    // Test 13 | 15
    operation = BIT_OR;
    a = 13;
    b = 15;
    #2 assert(y == 15) else $error("OR assertion failed.");
    assert(zero == 0) else $error("Zero assertion failed.");
    assert(sign == 0) else $error("Sign assertion failed.");

    // Test 13 ^ 15
    operation = BIT_XOR;
    a = 13;
    b = 15;
    #2 assert(y == 2) else $error("XOR assertion failed.");
    assert(zero == 0) else $error("Zero assertion failed.");
    assert(sign == 0) else $error("Sign assertion failed.");

    // Test 13 & 15
    operation = BIT_NOT;
    a = 13;
    #2 assert(y == 242) else $error("NOT assertion failed.");
    assert(zero == 0) else $error("Zero assertion failed.");
    assert(sign == 1) else $error("Sign assertion failed.");

    // Test shift left
    operation = SHIFT_LEFT;
    carry_in = 1;
    a = 136;
    #2 assert(y == 16) else $error("Shift left assertion failed.");
    assert(carry_out == 1) else $error("carry assertion failed.");
    assert(zero == 0) else $error("Zero assertion failed.");
    assert(sign == 0) else $error("Sign assertion failed.");

    // Test shift right
    operation = SHIFT_RIGHT;
    a = 8;
    #2 assert(y == 4) else $error("Shift right assertion failed.");
    assert(carry_out == 0) else $error("carry assertion failed.");
    assert(zero == 0) else $error("Zero assertion failed.");
    assert(sign == 0) else $error("Sign assertion failed.");

    // Test rotate left
    operation = ROTATE_LEFT;
    a = 8;
    #2 assert(y == 17) else $error("Rotate left assertion failed.");
    assert(carry_out == 0) else $error("carry assertion failed.");
    assert(zero == 0) else $error("Zero assertion failed.");
    assert(sign == 0) else $error("Sign assertion failed.");

    // Test rotate right
    operation = ROTATE_RIGHT;
    a = 9;
    #2 assert(y == 132) else $error("Rotate right assertion failed.");
    assert(carry_out == 1) else $error("carry assertion failed.");
    assert(zero == 0) else $error("Zero assertion failed.");
    assert(sign == 1) else $error("Sign assertion failed.");

    #1 $finish();
  end

  always begin
    #1 clock = ~clock;
  end

endmodule

With the ALU working I’ll call it a wrap for this post. If you have any questions or feedback be sure to leave a comment. Keep tinkering!