def test_valid_generation():
    io_core = IOCoreValid()
    io_core_circuit = io_core.circuit()
    tester = BasicTester(io_core_circuit, io_core_circuit.clk,
                         io_core_circuit.reset)

    max_cycle = 16

    # mode set to 1
    # max cycle set to 16
    config_data = [
        io_core.get_config_data("mode", 1),
        io_core.get_config_data("max_cycle", max_cycle)
    ]
    config_data = compress_config_data(config_data)

    tester.zero_inputs()
    tester.poke(io_core_circuit.stall, 1)

    for addr, data in config_data:
        tester.configure(addr, data)
        tester.config_read(addr)
        tester.eval()
        tester.expect(io_core_circuit.read_config_data, data)

    # un-stall
    tester.poke(io_core_circuit.stall, 0)

    # it should stay low regardless of the IO as long as the reset signal is never high
    for _f2io_16 in [random_bv(16) for _ in range(10)]:
        tester.poke(io_core_circuit.f2io_16, _f2io_16)
        tester.eval()
        tester.expect(io_core_circuit.io2glb_16, _f2io_16)
        tester.expect(io_core_circuit.io2glb_1, 0)

    # hit the start  signal
    tester.poke(io_core_circuit.glb2io_1, 1)
    tester.eval()
    # rising clock edge
    tester.step(2)
    # de-assert the reset/start signal
    tester.poke(io_core_circuit.glb2io_1, 0)
    tester.eval()

    # from now on it should output 1 until it reaches max cycle
    for _ in range(max_cycle):
        tester.expect(io_core_circuit.io2glb_1, 1)
        tester.step(2)

    # then stay low
    for _ in range(max_cycle):
        tester.expect(io_core_circuit.io2glb_1, 0)
        tester.step(2)

    with tempfile.TemporaryDirectory() as tempdir:
        tester.compile_and_run(target="verilator",
                               magma_output="coreir-verilog",
                               directory=tempdir,
                               flags=["-Wno-fatal"])
Beispiel #2
0
def test_comparess_config_data():
    # test out compressing bitstream
    config_data = [(0, 1), (0, 2)]
    config_data = compress_config_data(config_data)
    assert len(config_data) == 1
    assert config_data[0][0] == 0
    assert config_data[0][1] == 1 | 2

    # test out duplicate without zeroing out, which is required to get rom
    # working under current lake design
    config_data = [(0, 0), (0, 1), (0, 2), (1, 0), (1, 0), (1, 0)]
    config_data = compress_config_data(config_data, skip_compression=[1])
    assert len(config_data) == 1 + 3
    assert config_data[0][0] == 0
    assert config_data[0][1] == 1 | 2
    for i in range(1, 4):
        config_data[i][0] == 1
        config_data[i][1] == 0
Beispiel #3
0
def test_valid_generation(run_tb):
    io_core = IOCoreValid()
    io_core_circuit = io_core.circuit()
    tester = BasicTester(io_core_circuit, io_core_circuit.clk, io_core_circuit.reset)

    max_cycle = 16

    # mode set to 1
    # max cycle set to 16
    config_data = [io_core.get_config_data("mode", 1), io_core.get_config_data("max_cycle", max_cycle)]
    config_data = compress_config_data(config_data)

    tester.zero_inputs()
    tester.reset()
    tester.poke(io_core_circuit.stall, 1)

    for addr, data in config_data:
        tester.configure(addr, data)
        tester.config_read(addr)
        tester.eval()
        tester.expect(io_core_circuit.read_config_data, data)

    # un-stall
    tester.poke(io_core_circuit.stall, 0)

    # it should stay low regardless of the IO as long as the reset signal is never high
    for _f2io_16 in [random_bv(16) for _ in range(10)]:
        tester.poke(io_core_circuit.f2io_16, _f2io_16)
        tester.eval()
        tester.expect(io_core_circuit.io2glb_16, _f2io_16)
        tester.expect(io_core_circuit.io2glb_1, 0)

    # hit the start  signal
    tester.poke(io_core_circuit.glb2io_1, 1)
    tester.eval()
    # rising clock edge
    tester.step(2)
    # de-assert the reset/start signal
    tester.poke(io_core_circuit.glb2io_1, 0)
    tester.eval()

    # from now on it should output 1 until it reaches max cycle
    for _ in range(max_cycle):
        tester.expect(io_core_circuit.io2glb_1, 1)
        tester.step(2)

    # then stay low
    for _ in range(max_cycle):
        tester.expect(io_core_circuit.io2glb_1, 0)
        tester.step(2)

    run_tb(tester)
Beispiel #4
0
def test_sb(num_tracks: int, bit_width: int, sb_ctor, reg: Tuple[bool, int]):
    """It only tests whether the circuit created matched with the graph
       representation.
    """
    addr_width = 8
    data_width = 32

    switchbox = sb_ctor(0, 0, num_tracks, bit_width)
    reg_mode, batch_size = reg
    # insert registers to every sides and tracks
    if reg_mode:
        for side in SwitchBoxSide:
            for track in range(num_tracks):
                switchbox.add_pipeline_register(side, track)

    sb_circuit = SB(switchbox, addr_width, data_width)
    circuit = sb_circuit.circuit()

    # test the sb routing as well
    tester = BasicTester(circuit, circuit.clk, circuit.reset)

    # generate the addr based on mux names, which is used to sort the addr
    config_names = list(sb_circuit.registers.keys())
    config_names.sort()

    # some of the sb nodes may turn into a pass-through wire. we still
    # need to test them.
    # we generate a pair of config data and expected values. if it's a
    # pass-through wire, we don't configure them, yet we still evaluate the
    # outcome to see if it's connected
    config_data = []
    test_data = []
    all_sbs = switchbox.get_all_sbs()
    for sb in all_sbs:
        mux_sel_name = get_mux_sel_name(sb)
        if mux_sel_name not in config_names:
            assert sb.io == SwitchBoxIO.SB_IN
            connected_sbs = sb.get_conn_in()
            # for a switch box where each SB_IN connects to 3 different
            # SN_OUT, the SB_IN won't have any incoming edges
            assert len(connected_sbs) == 0
            input_sb_name = create_name(str(sb))
            # as a result, we configure the fan-out sbs to see if they
            # can receive the signal. notice that this is overlapped with the
            # if statement above
            # we also wanted to test if the register mode can be turned on
            for connected_sb in sb:  # type: SwitchBoxNode
                entry = []
                mux_sel_name = get_mux_sel_name(connected_sb)
                assert mux_sel_name in config_names
                assert connected_sb.io == SwitchBoxIO.SB_OUT
                index = connected_sb.get_conn_in().index(sb)
                entry.append(sb_circuit.get_config_data(mux_sel_name, index))
                # we will also configure the register, if connected
                reg_node, reg_mux_node = find_reg_mux_node(connected_sb)
                if reg_mux_node is not None:
                    mux_sel_name = get_mux_sel_name(reg_mux_node)
                    assert mux_sel_name in config_names
                    index = reg_mux_node.get_conn_in().index(reg_node)
                    entry.append(
                        sb_circuit.get_config_data(mux_sel_name, index))
                config_data.append(entry)
                # get port
                output_sb_name = create_name(str(connected_sb))
                entry = []
                for _ in range(batch_size):
                    entry.append((circuit.interface.ports[input_sb_name],
                                  circuit.interface.ports[output_sb_name],
                                  fault.random.random_bv(bit_width)))
                test_data.append(entry)

    # compress the config data
    for i in range(len(config_data)):
        config_data[i] = compress_config_data(config_data[i])

    # poke and test, without registers configured
    assert len(config_data) == len(test_data)
    for i in range(len(config_data)):
        tester.reset()
        configs = config_data[i]
        data = test_data[i]
        for addr, index in configs:
            index = BitVector[data_width](index)
            tester.configure(BitVector[addr_width](addr), index)
            tester.configure(BitVector[addr_width](addr), index + 1, False)
            tester.config_read(BitVector[addr_width](addr))
            tester.eval()
            tester.expect(circuit.read_config_data, index)
        if len(data) == 1:
            # this is pass through mode
            for input_port, output_port, value in data:
                tester.poke(input_port, value)
                tester.eval()
                tester.expect(output_port, value)
        else:
            for j in range(len(data)):
                if j != 0:
                    tester.eval()
                    tester.expect(data[j - 1][1], data[j - 1][2])

                input_port, _, value = data[j]
                tester.poke(input_port, value)
                tester.step(2)

    with tempfile.TemporaryDirectory() as tempdir:
        tester.compile_and_run(target="verilator",
                               magma_output="coreir-verilog",
                               directory=tempdir,
                               flags=["-Wno-fatal"])
Beispiel #5
0
def test_double_buffer():
    addr_width = 8
    data_width = 32
    bit_widths = [1, 16]
    num_tracks = 5
    tile_id_width = 16
    x = 0
    y = 0

    dummy_core = DummyCore()
    core = CoreInterface(dummy_core)

    tiles: Dict[int, Tile] = {}

    for bit_width in bit_widths:
        # we use disjoint switch here
        switchbox = DisjointSwitchBox(x, y, num_tracks, bit_width)
        tile = Tile(x, y, bit_width, switchbox)
        tiles[bit_width] = tile

    # set the core and core connection
    # here all the input ports are connect to SB_IN and all output ports are
    # connected to SB_OUT
    input_connections = []
    for track in range(num_tracks):
        for side in SwitchBoxSide:
            input_connections.append(
                SBConnectionType(side, track, SwitchBoxIO.SB_IN))
    output_connections = []
    for track in range(num_tracks):
        for side in SwitchBoxSide:
            output_connections.append(
                SBConnectionType(side, track, SwitchBoxIO.SB_OUT))

    for bit_width, tile in tiles.items():
        tile.set_core(core)
        input_port_name = f"data_in_{bit_width}b"
        output_port_name = f"data_out_{bit_width}b"

        tile.set_core_connection(input_port_name, input_connections)
        tile.set_core_connection(output_port_name, output_connections)

    tile_circuit = TileCircuit(tiles,
                               addr_width,
                               data_width,
                               tile_id_width=tile_id_width,
                               double_buffer=True)
    tile_circuit.finalize()
    circuit = tile_circuit.circuit()
    bit_width = 16
    # find corresponding sb
    sb_circuit: SB = None
    for _, sb in tile_circuit.sbs.items():
        if sb.switchbox.width == bit_width:
            sb_circuit = sb
            break
    assert sb_circuit is not None
    # find that connection box
    input_port_name = f"data_in_{bit_width}b"

    cb_circuit: CB = None
    for _, cb in tile_circuit.cbs.items():
        if cb.node.name == input_port_name:
            cb_circuit = cb
            break
    assert cb_circuit

    output_port_name = f"data_out_{bit_width}b"
    out_port_node = tile_circuit.tiles[bit_width].ports[output_port_name]
    in_port_node = tile_circuit.tiles[bit_width].ports[input_port_name]

    input_1 = sb_circuit.switchbox.get_sb(SwitchBoxSide.NORTH, 0,
                                          SwitchBoxIO.SB_IN)
    input_1_name = create_name(str(input_1))
    input_2 = sb_circuit.switchbox.get_sb(SwitchBoxSide.EAST, 1,
                                          SwitchBoxIO.SB_IN)
    input_2_name = create_name(str(input_2))
    output_sb = sb_circuit.switchbox.get_sb(SwitchBoxSide.SOUTH, 2,
                                            SwitchBoxIO.SB_OUT)
    output_name = create_name(str(output_sb))

    input_1_bitstream = tile_circuit.get_route_bitstream_config(
        input_1, in_port_node)
    input_2_bitstream = tile_circuit.get_route_bitstream_config(
        input_2, in_port_node)
    output_bitstream = tile_circuit.get_route_bitstream_config(
        out_port_node, output_sb)

    # notice that both of them will be configured using the double buffer scheme

    def get_config_data(config_data, reg_data):
        for reg_addr, feat_addr, config_value in reg_data:
            reg_addr = reg_addr << tile_circuit.feature_config_slice.start
            feat_addr = feat_addr << tile_circuit.tile_id_width
            addr = reg_addr | feat_addr
            addr = BitVector[data_width](addr) | BitVector[data_width](0)
            config_data.append((addr, config_value))

    input1_config_data = []
    input2_config_data = []
    get_config_data(input1_config_data, [input_1_bitstream, output_bitstream])
    get_config_data(input2_config_data, [input_2_bitstream, output_bitstream])
    input1_config_data = compress_config_data(input1_config_data)
    input2_config_data = compress_config_data(input2_config_data)

    tester = BasicTester(circuit, circuit.clk, circuit.reset)
    tester.poke(circuit.tile_id, 0)

    for addr, config_value in input1_config_data:
        tester.configure(addr, config_value)
        tester.config_read(addr)
        tester.eval()
        tester.expect(circuit.read_config_data, config_value)

    # configure the double buffer register
    tester.poke(circuit.config_db, 1)
    for addr, config_value in input2_config_data:
        tester.configure(addr, config_value)
        tester.config_read(addr)
        tester.eval()
        tester.expect(circuit.read_config_data, config_value)

    # the route should still be input 1
    port = circuit.interface.ports[input_1_name]
    tester.poke(port, 42)
    port = circuit.interface.ports[input_2_name]
    tester.poke(port, 43)
    tester.eval()
    tester.expect(circuit.interface.ports[output_name], 42)
    # now use the double buffer
    tester.poke(circuit.use_db, 1)
    tester.eval()
    tester.expect(circuit.interface.ports[output_name], 43)

    with tempfile.TemporaryDirectory() as tempdir:
        tester.compile_and_run(target="verilator",
                               magma_output="coreir-verilog",
                               directory=tempdir,
                               flags=["-Wno-fatal"])
Beispiel #6
0
def test_tile(num_tracks: int, add_additional_core: bool):
    import random
    random.seed(0)
    addr_width = 8
    data_width = 32
    bit_widths = [1, 16]

    tile_id_width = 16
    x = 0
    y = 0

    dummy_core = DummyCore()
    core = CoreInterface(dummy_core)

    if add_additional_core:
        c = AdditionalDummyCore()
        additional_core = CoreInterface(c)
    else:
        additional_core = None

    tiles: Dict[int, Tile] = {}

    for bit_width in bit_widths:
        # we use disjoint switch here
        switchbox = DisjointSwitchBox(x, y, num_tracks, bit_width)
        tile = Tile(x, y, bit_width, switchbox)
        tiles[bit_width] = tile

    # set the core and core connection
    # here all the input ports are connect to SB_IN and all output ports are
    # connected to SB_OUT
    input_connections = []
    for track in range(num_tracks):
        for side in SwitchBoxSide:
            input_connections.append(
                SBConnectionType(side, track, SwitchBoxIO.SB_IN))
    output_connections = []
    for track in range(num_tracks):
        for side in SwitchBoxSide:
            output_connections.append(
                SBConnectionType(side, track, SwitchBoxIO.SB_OUT))

    for bit_width, tile in tiles.items():
        tile.set_core(core)
        if add_additional_core:
            connection_type = CoreConnectionType.Core | CoreConnectionType.CB
            tile.add_additional_core(additional_core, connection_type)

        input_port_name = f"data_in_{bit_width}b"
        input_port_name_extra = f"data_in_{bit_width}b_extra"
        output_port_name = f"data_out_{bit_width}b"

        tile.set_core_connection(input_port_name, input_connections)
        tile.set_core_connection(output_port_name, output_connections)

        if add_additional_core:
            tile.set_core_connection(input_port_name_extra, input_connections)

    tile_circuit = TileCircuit(tiles,
                               addr_width,
                               data_width,
                               tile_id_width=tile_id_width)

    # finalize it
    tile_circuit.finalize()

    circuit = tile_circuit.circuit()

    # set up the configuration and test data
    # there are several things we are interested in the tile level and
    # need to test
    # 1. given an input to SB_IN, and configure it to CB, will the core
    # receive the data or not
    # 2. given an output signal from core, and configure it to SB, will the
    # SB_OUT receive the data or not
    # However, because we can only poke input ports, we cannot test #2 in the
    # current environment. As a result, we will combined these 2 together, that
    # is:
    # given an SB_IN signal, we configure the CB to the data_in, then configure
    # the SB_OUT to receive the signal
    raw_config_data = []
    config_data = []
    test_data = []
    tile_id = fault.random.random_bv(tile_id_width)

    for bit_width in bit_widths:
        # find corresponding sb
        sb_circuit: SB = None
        for _, sb in tile_circuit.sbs.items():
            if sb.switchbox.width == bit_width:
                sb_circuit = sb
                break
        assert sb_circuit is not None

        # input
        if add_additional_core:
            input_port_name = f"data_in_{bit_width}b_extra"
        else:
            input_port_name = f"data_in_{bit_width}b"
        in_port_node = tile_circuit.tiles[bit_width].ports[input_port_name]
        # find that connection box
        cb_circuit: CB = None
        for _, cb in tile_circuit.cbs.items():
            if cb.node.name == input_port_name:
                cb_circuit = cb
                break
        assert cb_circuit

        output_port_name = f"data_out_{bit_width}b"
        out_port_node = tile_circuit.tiles[bit_width].ports[output_port_name]

        all_sbs = sb_circuit.switchbox.get_all_sbs()
        for in_sb_node in all_sbs:
            if in_sb_node.io != SwitchBoxIO.SB_IN:
                continue

            for out_sb_node in all_sbs:
                if out_sb_node.io != SwitchBoxIO.SB_OUT:
                    continue
                # find the output node's index to that switch box node
                data0 = tile_circuit.get_route_bitstream_config(
                    in_sb_node, in_port_node)
                data1 = tile_circuit.get_route_bitstream_config(
                    out_port_node, out_sb_node)
                raw_config_data.append(data0)

                raw_config_data.append(data1)

                # configure the cb to route data from additional core to the
                # main core
                if add_additional_core:
                    input_port_name = f"data_in_{bit_width}b"
                    output_port_name = f"data_out_{bit_width}b_extra"
                    additional_in_port_node = \
                        tile_circuit.tiles[bit_width].ports[input_port_name]
                    additional_out_port_node = \
                        tile_circuit.tiles[bit_width].ports[output_port_name]
                    data2 = tile_circuit.get_route_bitstream_config(
                        additional_out_port_node, additional_in_port_node)
                    raw_config_data.append(data2)

                in_sb_name = create_name(str(in_sb_node))
                out_sb_name = create_name(str(out_sb_node))
                test_data.append(
                    (circuit.interface.ports[in_sb_name],
                     circuit.interface.ports[out_sb_name],
                     fault.random.random_bv(bit_width), in_sb_node))

    if add_additional_core:
        assert len(raw_config_data) / 3 == len(test_data)
    else:
        assert len(raw_config_data) / 2 == len(test_data)

    # process the raw config data and change it into the actual config addr
    for reg_addr, feat_addr, config_value in raw_config_data:
        reg_addr = reg_addr << tile_circuit.feature_config_slice.start
        feat_addr = feat_addr << tile_circuit.tile_id_width
        addr = reg_addr | feat_addr
        addr = BitVector[data_width](addr) | BitVector[data_width](tile_id)
        config_data.append((addr, config_value))

    # actual tests
    tester = BasicTester(circuit, circuit.clk, circuit.reset)
    tester.poke(circuit.tile_id, tile_id)

    stride = 3 if add_additional_core else 2
    for i in range(0, len(config_data), stride):
        tester.reset()
        c_data = config_data[i:i + stride]
        c_data = compress_config_data(c_data)
        for addr, config_value in c_data:
            tester.configure(addr, config_value)
            tester.configure(addr, config_value + 1, False)
            tester.config_read(addr)
            tester.eval()
            tester.expect(circuit.read_config_data, config_value)

        input_port, output_port, value, in_node = test_data[i // stride]

        tester.poke(input_port, value)
        tester.eval()
        # add additional error to check, i.e. sending random junk data to
        # all unrelated ports
        for bit_width in bit_widths:
            sb = tile_circuit.sbs[bit_width]
            sbs = sb.switchbox.get_all_sbs()
            for sb in sbs:
                if sb == in_node or sb.io == SwitchBoxIO.SB_OUT:
                    continue
                port_name = create_name(str(sb))
                port = circuit.interface.ports[port_name]
                tester.poke(port, fault.random.random_bv(bit_width))
                tester.eval()

        tester.expect(output_port, value)

    with tempfile.TemporaryDirectory() as tempdir:
        tester.compile_and_run(target="verilator",
                               magma_output="coreir-verilog",
                               directory=tempdir,
                               flags=["-Wno-fatal"])
Beispiel #7
0
def test_interconnect(num_tracks: int, chip_size: int,
                      reg_mode: bool,
                      wiring: GlobalSignalWiring):
    bit_widths, data_width, ics, interconnect = create_dummy_cgra(chip_size,
                                                                  num_tracks,
                                                                  reg_mode,
                                                                  wiring)

    # assert tile coordinates
    for (x, y), tile_circuit in interconnect.tile_circuits.items():
        for _, tile in tile_circuit.tiles.items():
            assert_tile_coordinate(tile, x, y)

    circuit = interconnect.circuit()
    tester = BasicTester(circuit, circuit.clk, circuit.reset)
    config_data = []
    test_data = []

    # we have a 2x2 (for instance) chip as follows
    # |-------|
    # | A | B |
    # |---|---|
    # | C | D |
    # ---------
    # we need to test all the line-tile routes. that is, per row and per column
    # A <-> B, A <-> C, B <-> D, C <-> D
    # TODO: add core input/output as well

    vertical_conns = [[SwitchBoxSide.NORTH, SwitchBoxIO.SB_IN,
                       SwitchBoxSide.SOUTH, SwitchBoxIO.SB_OUT,
                       0, chip_size - 1],
                      [SwitchBoxSide.SOUTH, SwitchBoxIO.SB_IN,
                       SwitchBoxSide.NORTH, SwitchBoxIO.SB_OUT,
                       chip_size - 1, 0]]

    horizontal_conns = [[SwitchBoxSide.WEST, SwitchBoxIO.SB_IN,
                         SwitchBoxSide.EAST, SwitchBoxIO.SB_OUT,
                         0, chip_size - 1],
                        [SwitchBoxSide.EAST, SwitchBoxIO.SB_IN,
                         SwitchBoxSide.WEST, SwitchBoxIO.SB_OUT,
                         chip_size - 1, 0]]

    for bit_width in bit_widths:
        for track in range(num_tracks):
            # vertical
            for x in range(chip_size):
                for side_io in vertical_conns:
                    from_side, from_io, to_side, to_io, start, end = side_io
                    src_node = None
                    dst_node = None
                    config_entry = []
                    for y in range(chip_size):
                        tile_circuit = interconnect.tile_circuits[(x, y)]
                        tile = tile_circuit.tiles[bit_width]
                        pre_node = tile.get_sb(from_side, track, from_io)
                        tile_circuit = interconnect.tile_circuits[(x, y)]
                        tile = tile_circuit.tiles[bit_width]
                        next_node = tile.get_sb(to_side, track, to_io)
                        if y == start:
                            src_node = pre_node
                        if y == end:
                            dst_node = next_node

                        entry = \
                            interconnect.get_node_bitstream_config(pre_node,
                                                                   next_node)

                        config_entry.append(entry)
                    config_entry = compress_config_data(config_entry)
                    assert src_node is not None and dst_node is not None
                    config_data.append(config_entry)
                    value = fault.random.random_bv(bit_width)
                    src_name = interconnect.get_top_port_name(src_node)
                    dst_name = interconnect.get_top_port_name(dst_node)
                    test_data.append((circuit.interface[src_name],
                                      circuit.interface[dst_name],
                                      value))

            # horizontal connections
            for y in range(chip_size):
                for side_io in horizontal_conns:
                    from_side, from_io, to_side, to_io, start, end = side_io
                    src_node = None
                    dst_node = None
                    config_entry = []
                    for x in range(chip_size):
                        tile_circuit = interconnect.tile_circuits[(x, y)]
                        tile = tile_circuit.tiles[bit_width]
                        pre_node = tile.get_sb(from_side, track, from_io)
                        tile_circuit = interconnect.tile_circuits[(x, y)]
                        tile = tile_circuit.tiles[bit_width]
                        next_node = tile.get_sb(to_side, track, to_io)
                        if x == start:
                            src_node = pre_node
                        if x == end:
                            dst_node = next_node

                        entry = \
                            interconnect.get_node_bitstream_config(pre_node,
                                                                   next_node)

                        config_entry.append(entry)
                    config_entry = compress_config_data(config_entry)
                    assert src_node is not None and dst_node is not None
                    config_data.append(config_entry)
                    value = fault.random.random_bv(bit_width)
                    src_name = interconnect.get_top_port_name(src_node)
                    dst_name = interconnect.get_top_port_name(dst_node)
                    test_data.append((circuit.interface[src_name],
                                      circuit.interface[dst_name],
                                      value))

    # the actual test
    assert len(config_data) == len(test_data)
    # NOTE:
    # we don't test the configuration read here
    for i in range(len(config_data)):
        tester.reset()
        input_port, output_port, value = test_data[i]
        for addr, index in config_data[i]:
            tester.configure(BitVector[data_width](addr), index)
            tester.configure(BitVector[data_width](addr), index + 1, False)
            tester.config_read(addr)
            tester.eval()
            tester.expect(circuit.read_config_data, index)

        tester.poke(input_port, value)
        tester.eval()
        tester.expect(output_port, value)

    with tempfile.TemporaryDirectory() as tempdir:
        tester.compile_and_run(target="verilator",
                               magma_output="coreir-verilog",
                               directory=tempdir,
                               flags=["-Wno-fatal"])

    # also test the interconnect clone
    new_interconnect = interconnect.clone()
    with tempfile.TemporaryDirectory() as tempdir_old:
        interconnect.dump_pnr(tempdir_old, "old")
        with tempfile.TemporaryDirectory() as tempdir_new:
            new_interconnect.dump_pnr(tempdir_new, "new")

            # they should be exactly the same
            graph1_old = os.path.join(tempdir_old, "1.graph")
            graph1_new = os.path.join(tempdir_new, "1.graph")
            assert filecmp.cmp(graph1_old, graph1_new)

            graph16_old = os.path.join(tempdir_old, "16.graph")
            graph16_new = os.path.join(tempdir_new, "16.graph")
            assert filecmp.cmp(graph16_old, graph16_new)

            layout_old = os.path.join(tempdir_old, "old.layout")
            layout_new = os.path.join(tempdir_new, "new.layout")
            assert filecmp.cmp(layout_old, layout_new)