The world of programming is often introduced to new students with a program that prints "Hello World!" on a screen. In the world of embedded students are made to blink an LED. It's essentially a basic exercise that you can build upon to understand most if not all concepts of a particular field.
Similarly in the world of digital circuits, a NOT gate is the most basic circuit out there.
But with just one input and one output, it does not give a whole lot of room to play around and I personally am a big fan of XOR gate.
A | B | Y |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
Now that we have chosen a circuit, or DUT - Device Under Test (Industry jargon 101). It's time we write the code for the same using Verilog.
module xor_gate(input wire a,
input wire b,
output wire y
);
assign y = a^b;
endmodule
import cocotb
from cocotb.triggers import Timer, RisingEdge
@cocotb.test()
async def test_xor(dut):
a = (0,0,1,1)
b = (0,1,0,1)
y = (0,1,1,0)
for i in range(4):
dut.a.value = a[i]
dut.b.value = b[i]
await Timer(1, 'ns')
assert dut.y.value == y[i], f"Error at iteration {i}"
In the python file we have basically defined the inputs that we will give to the DUT and the outputs that we expect.
# Makefile
TOPLEVEL_LANG = verilog
VERILOG_SOURCES = $(shell pwd)/../HDL/xor_gate.v
TOPLEVEL = xor_gate
MODULE = test_xor
include $(shell cocotb-config --makefiles)/Makefile.sim
And Voila!
We did get a pass, but this is boring. I want to look at waveforms! To that end we will create a wrapper that dumps the waveform and calls our module. I could not get these two work in two separate files so I ended up pasting them in the same file.
module xor_gate(input wire a,
input wire b,
output wire y
);
assign y = a^b;
endmodule
module xor_wrapper(input wire a,
input wire b,
output wire y
);
xor_gate xor_1 (.a(a), .b(b), .y(y));
initial begin
$dumpfile("waves.vcd");
$dumpvars;
end
endmodule
This dumps out a waves.vcd file that you can view with GTKWave.
gtkwave waves.vcd
Lo and behold! Waveforms!