0
\$\begingroup\$

I'm attempting to use Cooctb to verify a simple Verilog counter with a reset:

`default_nettype none
`timescale 1ns / 1ps

module counter (
  input wire        clk,
  input wire        rst,
  output reg [7:0] count
);

always @(posedge clk) begin
  if (rst) begin
    count <= 0;
  end else begin
    count <= count + 1;
  end
end

endmodule

When I first wrote a test that verifies count goes back to 0 when rst is asserted, I used RisingEdge to set and assert values. However, I found that I had to wait for two rising edges for count to go back to 0:

    await RisingEdge(dut.clk)
    assert dut.count.value.integer == 3

    dut.rst.value = 1
    await RisingEdge(dut.clk)
    # count == 4 here
    await RisingEdge(dut.clk) # Why is this needed?
    assert dut.count.value.integer == 0

Here's the signal trace for this RisingEdge test:

RisingEdge trace

Even though it looks like rst is 1 at the 6ns rising edge, it must be getting set just after that edge, so it doesn't take effect until the 7ns rising edge?

If I use FallingEdge, then count goes back to 0 on the next edge, as I expect:

    await FallingEdge(dut.clk)
    assert dut.count.value.integer == 3

    dut.rst.value = 1
    await FallingEdge(dut.clk)
    assert dut.count.value.integer == 0

Here's the signal trace for the FallingEdge test:

FallingEdge trace

While this works, it seems a little odd to be using FallingEdge for all the test code, given the logic is synchronous to the rising edge. Is this how folks typically write Cocotb tests?

Here is the full Python code:

import cocotb
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge, FallingEdge

@cocotb.test()
async def count_then_reset_test_rising(dut):
    # Reset
    dut.rst.value = 0
    cocotb.start_soon(Clock(dut.clk, 1, units="ns").start())

    await RisingEdge(dut.clk)
    await RisingEdge(dut.clk)
    
    dut.rst.value = 1
    await RisingEdge(dut.clk)

    dut.rst.value = 0
    await RisingEdge(dut.clk)

    # Running
    assert dut.count.value.integer == 0

    await RisingEdge(dut.clk)
    assert dut.count.value.integer == 1

    await RisingEdge(dut.clk)
    assert dut.count.value.integer == 2

    await RisingEdge(dut.clk)
    assert dut.count.value.integer == 3

    dut.rst.value = 1
    await RisingEdge(dut.clk)
    # count == 4 here
    await RisingEdge(dut.clk) # Why is this needed?
    assert dut.count.value.integer == 0

    dut.rst.value = 0
    await RisingEdge(dut.clk)
    assert dut.count.value.integer == 0

    await RisingEdge(dut.clk)
    assert dut.count.value.integer == 1

@cocotb.test()
async def count_then_reset_test_falling(dut):
    # Reset
    dut.rst.value = 0
    cocotb.start_soon(Clock(dut.clk, 1, units="ns").start())

    await FallingEdge(dut.clk)
    await FallingEdge(dut.clk)
    
    dut.rst.value = 1
    await FallingEdge(dut.clk)
    dut.rst.value = 0

    # Running
    assert dut.count.value.integer == 0

    await FallingEdge(dut.clk)
    assert dut.count.value.integer == 1

    await FallingEdge(dut.clk)
    assert dut.count.value.integer == 2

    await FallingEdge(dut.clk)
    assert dut.count.value.integer == 3

    dut.rst.value = 1
    await FallingEdge(dut.clk)
    assert dut.count.value.integer == 0

    dut.rst.value = 0
    await FallingEdge(dut.clk)
    assert dut.count.value.integer == 1

    await FallingEdge(dut.clk)
    assert dut.count.value.integer == 2

    await FallingEdge(dut.clk)
    assert dut.count.value.integer == 3
\$\endgroup\$
0

2 Answers 2

1
\$\begingroup\$

Checking the value on the FallingEdge or the following RisingEdge feels unintuitive, but it does reflect how a real electrical system behaves. I wouldn't expect a stable value for the output of a physical DFF on the rising edge, and I wouldn't clock in any outputs until at least the falling edge.

Therefore, I would go with writing assert on the FallingEdge since it reflects real-world tests, and it's also more readable and clear than something like ReadWrite.

\$\endgroup\$
1
\$\begingroup\$

I got an answer on my cocotb GitHub Discussions post from GitHub user f-cozzocrea:

Checking the value on the FallingEdge or the following RisingEdge feels unintuitive, but it does reflect how a real electrical system behaves. I wouldn't expect a stable value for the output of a physical DFF on the rising edge, and I wouldn't clock in any outputs until at least the falling edge.

Therefore, I would go with writing assert on the FallingEdge since it reflects real-world tests, and it's also more readable and clear than something like ReadWrite.

\$\endgroup\$
1
  • \$\begingroup\$ @f-cozzocrea posted their answer here, so marking that one is the "correct" one. Not sure if I should delete my answer, but keeping it here for now. \$\endgroup\$ Commented Apr 21, 2023 at 21:18

Not the answer you're looking for? Browse other questions tagged or ask your own question.