test: test bench for game of life cell

to run: `make -C test`
diff --git a/test/.gitignore b/test/.gitignore
new file mode 100644
index 0000000..7b99d8a
--- /dev/null
+++ b/test/.gitignore
@@ -0,0 +1,3 @@
+__pycache__
+sim_build
+results.xml
\ No newline at end of file
diff --git a/test/Makefile b/test/Makefile
new file mode 100644
index 0000000..22766ba
--- /dev/null
+++ b/test/Makefile
@@ -0,0 +1,34 @@
+## SPDX-FileCopyrightText: © 2021 Uri Shaked <uri@wokwi.com>
+## SPDX-License-Identifier: MIT
+# See https://docs.cocotb.org/en/stable/quickstart.html for more info
+
+# defaults
+SIM ?= icarus
+TOPLEVEL_LANG ?= verilog
+
+export COCOTB_REDUCED_LOG_FMT=1
+
+ifneq ($(GATES),yes)
+# normal simulation
+VERILOG_SOURCES += $(PWD)/cell_tb.v $(PWD)/../verilog/rtl/user_module_349472166361694804.v $(PWD)/../verilog/rtl/cells.v
+else
+# gate level simulation requires some extra setup
+COMPILE_ARGS    += -DGL_TEST
+COMPILE_ARGS    += -DFUNCTIONAL
+COMPILE_ARGS    += -DUSE_POWER_PINS
+COMPILE_ARGS    += -DSIM
+COMPILE_ARGS    += -DUNIT_DELAY=#1
+VERILOG_SOURCES += $(PDK_ROOT)/gf180mcuC/libs.ref/gf180mcu_fd_sc_mcu7t5v0/verilog/gf180mcu_fd_sc_mcu7t5v0.v
+
+# the github action copies the gatelevel verilog from /runs/wokwi/results/final/verilog/gl/ 
+VERILOG_SOURCES += $(PWD)/cell_tb_gl.v $(PWD)/../verilog/gl/tiny_user_project.v
+endif
+
+# TOPLEVEL is the name of the toplevel module in your Verilog or VHDL file
+TOPLEVEL = test_cell
+
+# MODULE is the basename of the Python test file
+MODULE = test_cell
+
+# include cocotb's make rules to take care of the simulator setup
+include $(shell cocotb-config --makefiles)/Makefile.sim
diff --git a/test/cell_tb.v b/test/cell_tb.v
new file mode 100644
index 0000000..0c2712e
--- /dev/null
+++ b/test/cell_tb.v
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: © 2022 Uri Shaked <uri@wokwi.com>
+// SPDX-License-Identifier: Apache2.0
+
+`timescale 1ns / 1ps
+// 
+`default_nettype none
+
+module test_cell ();
+  reg clk;
+  reg data;
+  reg set;
+  reg reset;
+  reg [7:0] neighbors;
+  wire [7:0] io_out;
+  wire alive = io_out[0];
+  wire notalive = io_out[1];
+
+  user_module_349472166361694804 dut (
+  `ifdef GL_TEST
+      .vccd1( 1'b1),
+      .vssd1( 1'b0),
+  `endif
+      .io_in ({reset, set, clk, neighbors}),
+      .io_out(io_out)
+  );
+
+  initial begin
+    clk = 0;
+    forever begin
+      #5 clk = ~clk;
+    end
+  end
+
+  initial begin
+    $dumpfile("cell_tb.vcd");
+    $dumpvars(0, test_cell);
+  end
+endmodule
\ No newline at end of file
diff --git a/test/cell_tb_gl.v b/test/cell_tb_gl.v
new file mode 100644
index 0000000..e0f2ce3
--- /dev/null
+++ b/test/cell_tb_gl.v
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: © 2022 Uri Shaked <uri@wokwi.com>
+// SPDX-License-Identifier: Apache2.0
+
+`timescale 1ns / 1ps
+// 
+`default_nettype none
+
+module test_cell ();
+  reg clk;
+  reg data;
+  reg set;
+  reg reset;
+  reg [7:0] neighbors;
+  wire [37:0] io_out;
+  wire alive = io_out[20];
+  wire notalive = io_out[21];
+
+  tiny_user_project dut (
+  `ifdef GL_TEST
+      .vccd1( 1'b1),
+      .vssd1( 1'b0),
+  `endif
+      .io_in ({reset, set, clk, neighbors, 9'bxxxxxxxxx}),
+      .io_out(io_out)
+  );
+
+  initial begin
+    clk = 0;
+    forever begin
+      #5 clk = ~clk;
+    end
+  end
+
+  initial begin
+    $dumpfile("cell_tb.vcd");
+    $dumpvars(0, test_cell);
+  end
+endmodule
\ No newline at end of file
diff --git a/test/test_cell.py b/test/test_cell.py
new file mode 100644
index 0000000..d6ab2fe
--- /dev/null
+++ b/test/test_cell.py
@@ -0,0 +1,65 @@
+# SPDX-FileCopyrightText: © 2022 Uri Shaked <uri@wokwi.com>
+# SPDX-License-Identifier: Apache2.0
+
+import os
+import cocotb
+from cocotb.clock import Clock
+from cocotb.triggers import ClockCycles, Timer
+
+@cocotb.test()
+async def test_cell(dut):
+    i_neighbors = dut.neighbors
+    i_set = dut.set
+    i_reset = dut.reset
+    o_alive = dut.alive
+    o_notalive = dut.notalive
+    #clk = make_clock(dut, 10)
+
+    i_reset <= 1
+    i_set <= 0
+    i_neighbors <= 0b00000000
+    await ClockCycles(dut.clk, 2)
+    assert o_alive.value == 0
+    assert o_notalive.value == 1
+
+    i_reset <= 0
+    i_set <= 1
+    await ClockCycles(dut.clk, 2)
+    assert o_alive.value == 1
+    assert o_notalive.value == 0
+
+    i_set <= 0
+    i_reset <= 1
+    await ClockCycles(dut.clk, 2)
+    assert o_alive.value == 0
+    assert o_notalive.value == 1
+
+    i_reset <= 0
+    i_neighbors <= 0b00000001
+    await ClockCycles(dut.clk, 2)
+    assert o_alive.value == 0
+
+    i_neighbors <= 0b00000011
+    await ClockCycles(dut.clk, 2)
+    assert o_alive.value == 0
+
+    i_neighbors <= 0b00000111
+    await ClockCycles(dut.clk, 2)
+    assert o_alive.value == 1
+
+    i_neighbors <= 0b00001111
+    await ClockCycles(dut.clk, 2)
+    assert o_alive.value == 0
+
+    i_neighbors <= 0b10011000
+    await ClockCycles(dut.clk, 2)
+    assert o_alive.value == 1
+
+    i_neighbors <= 0b00011000
+    await ClockCycles(dut.clk, 2)
+    assert o_alive.value == 1
+
+    i_neighbors <= 0b00001000
+    await ClockCycles(dut.clk, 2)
+    assert o_alive.value == 0
+