def __init__(self, dut): self.dut = dut self.csrs = dict() with open("csr.csv", newline='') as csr_csv_file: csr_csv = csv.reader(csr_csv_file) # csr_register format: csr_register, name, address, size, rw/ro for row in csr_csv: if row[0] == 'csr_register': self.csrs[row[1]] = int(row[2], base=0) cocotb.fork(Clock(dut.clk48, 20800, 'ps').start()) self.wb = WishboneMaster(dut, "wishbone", dut.clk12, timeout=20) # Set the signal "test_name" to match this test import inspect tn = cocotb.binary.BinaryValue(value=None, n_bits=4096) tn.buff = inspect.stack()[1][3] self.dut.test_name = tn
class HaddecksTest: def __init__(self, dut, test_name): self.dut = dut self.test_name = test_name self.csrs = dict() self.dut.reset = 1 with open("../build/csr.csv", newline='') as csr_csv_file: csr_csv = csv.reader(csr_csv_file) # csr_register format: csr_register, name, address, size, rw/ro for row in csr_csv: if row[0] == 'csr_register': exec("self.{} = {}".format(row[1].upper(), int(row[2], base=0))) cocotb.fork(Clock(dut.clk48, 20800 // 4, 'ps').start()) self.wb = WishboneMaster(dut, "wishbone", dut.clk48, timeout=20) # Set the signal "test_name" to match this test, so that we can # tell from gtkwave which test we're in. tn = cocotb.binary.BinaryValue(value=None, n_bits=4096) tn.buff = test_name self.dut.test_name = tn @cocotb.coroutine def write(self, addr, val): yield self.wb.write(addr, val) @cocotb.coroutine def read(self, addr): value = yield self.wb.read(addr) raise ReturnValue(value) @cocotb.coroutine def reset(self): self.dut.reset = 1 yield RisingEdge(self.dut.clk48) yield RisingEdge(self.dut.clk48) yield RisingEdge(self.dut.clk48) yield RisingEdge(self.dut.clk48) self.dut.reset = 0 yield RisingEdge(self.dut.clk48) yield RisingEdge(self.dut.clk48) yield RisingEdge(self.dut.clk48) yield RisingEdge(self.dut.clk48)
def __init__(self, dut, test_name): if "WIRES" in os.environ: self.wires = int(os.environ["WIRES"]) else: self.wires = 4 self.dut = dut self.test_name = test_name self.csrs = dict() self.dut.reset = 1 with open("csr.csv", newline='') as csr_csv_file: csr_csv = csv.reader(csr_csv_file) # csr_register format: csr_register, name, address, size, rw/ro for row in csr_csv: if row[0] == 'csr_register': exec("self.{} = {}".format(row[1].upper(), int(row[2], base=0))) cocotb.fork(Clock(dut.clk48, 20800, 'ps').start()) self.wb = WishboneMaster(dut, "wishbone", dut.clk12, timeout=20) # Set the signal "test_name" to match this test, so that we can # tell from gtkwave which test we're in. tn = cocotb.binary.BinaryValue(value=None, n_bits=4096) tn.buff = test_name self.dut.test_name = tn
class UsbTest: def __init__(self, dut): self.dut = dut self.csrs = dict() with open("csr.csv", newline='') as csr_csv_file: csr_csv = csv.reader(csr_csv_file) # csr_register format: csr_register, name, address, size, rw/ro for row in csr_csv: if row[0] == 'csr_register': self.csrs[row[1]] = int(row[2], base=0) cocotb.fork(Clock(dut.clk48, 20800, 'ps').start()) self.wb = WishboneMaster(dut, "wishbone", dut.clk12, timeout=20) # Set the signal "test_name" to match this test import inspect tn = cocotb.binary.BinaryValue(value=None, n_bits=4096) tn.buff = inspect.stack()[1][3] self.dut.test_name = tn @cocotb.coroutine def reset(self): self.dut.reset = 1 yield RisingEdge(self.dut.clk12) self.dut.reset = 0 yield RisingEdge(self.dut.clk12) self.dut.usb_d_p = 1 self.dut.usb_d_n = 0 yield self.disconnect() # Enable endpoint 0 yield self.write(self.csrs['usb_enable_out0'], 0xff) yield self.write(self.csrs['usb_enable_out1'], 0xff) yield self.write(self.csrs['usb_enable_in0'], 0xff) yield self.write(self.csrs['usb_enable_in1'], 0xff) @cocotb.coroutine def write(self, addr, val): yield self.wb.write(addr, val) @cocotb.coroutine def read(self, addr): value = yield self.wb.read(addr) raise ReturnValue(value) @cocotb.coroutine def connect(self): USB_PULLUP_OUT = self.csrs['usb_pullup_out'] yield self.write(USB_PULLUP_OUT, 1) @cocotb.coroutine def clear_pending(self, _ep): yield Timer(0) @cocotb.coroutine def disconnect(self): USB_PULLUP_OUT = self.csrs['usb_pullup_out'] yield self.write(USB_PULLUP_OUT, 0) def assertEqual(self, a, b, msg): if a != b: raise TestFailure("{} != {} - {}".format(a, b, msg)) def assertSequenceEqual(self, a, b, msg): if a != b: raise TestFailure("{} vs {} - {}".format(a, b, msg)) def print_ep(self, epaddr, msg, *args): self.dut._log.info("ep(%i, %s): %s" % ( EndpointType.epnum(epaddr), EndpointType.epdir(epaddr).name, msg) % args) # Host->Device @cocotb.coroutine def _host_send_packet(self, packet): """Send a USB packet.""" # Packet gets multiplied by 4x so we can send using the # usb48 clock instead of the usb12 clock. packet = 'JJJJJJJJ' + wrap_packet(packet) self.assertEqual('J', packet[-1], "Packet didn't end in J: "+packet) for v in packet: if v == '0' or v == '_': # SE0 - both lines pulled low self.dut.usb_d_p <= 0 self.dut.usb_d_n <= 0 elif v == '1': # SE1 - illegal, should never occur self.dut.usb_d_p <= 1 self.dut.usb_d_n <= 1 elif v == '-' or v == 'I': # Idle self.dut.usb_d_p <= 1 self.dut.usb_d_n <= 0 elif v == 'J': self.dut.usb_d_p <= 1 self.dut.usb_d_n <= 0 elif v == 'K': self.dut.usb_d_p <= 0 self.dut.usb_d_n <= 1 else: raise TestFailure("Unknown value: %s" % v) yield RisingEdge(self.dut.clk48) @cocotb.coroutine def host_send_token_packet(self, pid, addr, ep): epnum = EndpointType.epnum(ep) yield self._host_send_packet(token_packet(pid, addr, epnum)) @cocotb.coroutine def host_send_data_packet(self, pid, data): assert pid in (PID.DATA0, PID.DATA1), pid yield self._host_send_packet(data_packet(pid, data)) @cocotb.coroutine def host_send_sof(self, time): yield self._host_send_packet(sof_packet(time)) @cocotb.coroutine def host_send_ack(self): yield self._host_send_packet(handshake_packet(PID.ACK)) @cocotb.coroutine def host_send(self, data01, addr, epnum, data, expected=PID.ACK): """Send data out the virtual USB connection, including an OUT token""" yield self.host_send_token_packet(PID.OUT, addr, epnum) yield self.host_send_data_packet(data01, data) yield self.host_expect_packet(handshake_packet(expected), "Expected {} packet.".format(expected)) @cocotb.coroutine def host_setup(self, addr, epnum, data): """Send data out the virtual USB connection, including a SETUP token""" yield self.host_send_token_packet(PID.SETUP, addr, epnum) yield self.host_send_data_packet(PID.DATA0, data) yield self.host_expect_ack() @cocotb.coroutine def host_recv(self, data01, addr, epnum, data): """Send data out the virtual USB connection, including an IN token""" yield self.host_send_token_packet(PID.IN, addr, epnum) yield self.host_expect_data_packet(data01, data) yield self.host_send_ack() # Device->Host @cocotb.coroutine def host_expect_packet(self, packet, msg=None): """Except to receive the following USB packet.""" def current(): values = (self.dut.usb_d_p, self.dut.usb_d_n) if values == (0, 0): return '_' elif values == (1, 1): return '1' elif values == (1, 0): return 'J' elif values == (0, 1): return 'K' else: raise TestFailure("Unrecognized dut values: {}".format(values)) # Wait for transmission to start tx = 0 bit_times = 0 for i in range(0, 100): tx = self.dut.usb_tx_en if tx == 1: break yield RisingEdge(self.dut.clk48) bit_times = bit_times + 1 if tx != 1: raise TestFailure("No packet started, " + msg) # # USB specifies that the turn-around time is 7.5 bit times for the device bit_time_max = 12.5 bit_time_acceptable = 7.5 if (bit_times/4.0) > bit_time_max: raise TestFailure("Response came after {} bit times, which is more than {}".format(bit_times / 4.0, bit_time_max)) if (bit_times/4.0) > bit_time_acceptable: self.dut._log.warn("Response came after {} bit times (> {})".format(bit_times / 4.0, bit_time_acceptable)) else: self.dut._log.info("Response came after {} bit times".format(bit_times / 4.0)) # Read in the transmission data result = "" for i in range(0, 1024): result += current() yield RisingEdge(self.dut.clk48) if self.dut.usb_tx_en != 1: break if tx == 1: raise TestFailure("Packet didn't finish, " + msg) self.dut.usb_d_p = 1 self.dut.usb_d_n = 0 # Check the packet received matches expected = pp_packet(wrap_packet(packet)) actual = pp_packet(result) self.assertSequenceEqual(expected, actual, msg) @cocotb.coroutine def host_expect_ack(self): yield self.host_expect_packet(handshake_packet(PID.ACK), "Expected ACK packet.") @cocotb.coroutine def host_expect_nak(self): yield self.host_expect_packet(handshake_packet(PID.NAK), "Expected NAK packet.") @cocotb.coroutine def host_expect_stall(self): yield self.host_expect_packet(handshake_packet(PID.STALL), "Expected STALL packet.") @cocotb.coroutine def host_expect_data_packet(self, pid, data): assert pid in (PID.DATA0, PID.DATA1), pid yield self.host_expect_packet(data_packet(pid, data), "Expected %s packet with %r" % (pid.name, data)) @cocotb.coroutine def pending(self, ep): if EndpointType.epdir(ep) == EndpointType.IN: val = yield self.read(self.csrs['usb_epin_status']) else: val = yield self.read(self.csrs['usb_epout_status']) raise ReturnValue(val & 1) @cocotb.coroutine def expect_setup(self, epaddr, expected_data): actual_data = [] # wait for data to appear for i in range(128): self.dut._log.debug("Prime loop {}".format(i)) status = yield self.read(self.csrs['usb_setup_status']) have = status & 1 if have: break yield RisingEdge(self.dut.clk12) for i in range(48): self.dut._log.debug("Read loop {}".format(i)) status = yield self.read(self.csrs['usb_setup_status']) have = status & 1 if not have: break v = yield self.read(self.csrs['usb_setup_data']) yield self.write(self.csrs['usb_setup_ctrl'], 1) actual_data.append(v) yield RisingEdge(self.dut.clk12) if len(actual_data) < 2: raise TestFailure("data was short (got {}, expected {})".format(expected_data, actual_data)) actual_data, actual_crc16 = actual_data[:-2], actual_data[-2:] self.print_ep(epaddr, "Got: %r (expected: %r)", actual_data, expected_data) self.assertSequenceEqual(expected_data, actual_data, "SETUP packet not received") self.assertSequenceEqual(crc16(expected_data), actual_crc16, "CRC16 not valid") @cocotb.coroutine def expect_data(self, epaddr, expected_data, expected): actual_data = [] # wait for data to appear for i in range(128): self.dut._log.debug("Prime loop {}".format(i)) status = yield self.read(self.csrs['usb_epout_status']) have = status & 1 if have: break yield RisingEdge(self.dut.clk12) for i in range(256): self.dut._log.debug("Read loop {}".format(i)) status = yield self.read(self.csrs['usb_epout_status']) have = status & 1 if not have: break v = yield self.read(self.csrs['usb_epout_data']) yield self.write(self.csrs['usb_epout_ctrl'], 3) actual_data.append(v) yield RisingEdge(self.dut.clk12) if expected == PID.ACK: if len(actual_data) < 2: raise TestFailure("data {} was short".format(actual_data)) actual_data, actual_crc16 = actual_data[:-2], actual_data[-2:] self.print_ep(epaddr, "Got: %r (expected: %r)", actual_data, expected_data) self.assertSequenceEqual(expected_data, actual_data, "DATA packet not correctly received") self.assertSequenceEqual(crc16(expected_data), actual_crc16, "CRC16 not valid") @cocotb.coroutine def set_response(self, ep, response): if EndpointType.epdir(ep) == EndpointType.IN and response == EndpointResponse.ACK: yield self.write(self.csrs['usb_epin_epno'], EndpointType.epnum(ep)) @cocotb.coroutine def send_data(self, token, ep, data): for b in data: yield self.write(self.csrs['usb_epin_data'], b) yield self.write(self.csrs['usb_epin_epno'], ep) @cocotb.coroutine def transaction_setup(self, addr, data, epnum=0): epaddr_out = EndpointType.epaddr(0, EndpointType.OUT) epaddr_in = EndpointType.epaddr(0, EndpointType.IN) xmit = cocotb.fork(self.host_setup(addr, epnum, data)) yield self.expect_setup(epaddr_out, data) yield xmit.join() @cocotb.coroutine def transaction_data_out(self, addr, ep, data, chunk_size=64, expected=PID.ACK): epnum = EndpointType.epnum(ep) datax = PID.DATA1 # # Set it up so we ACK the final IN packet # yield self.write(self.csrs['usb_epin_epno'], 0) for _i, chunk in enumerate(grouper_tofit(chunk_size, data)): self.dut._log.warning("Sening {} bytes to host".format(len(chunk))) # Enable receiving data yield self.write(self.csrs['usb_epout_ctrl'], (1 << 1)) xmit = cocotb.fork(self.host_send(datax, addr, epnum, chunk, expected)) yield self.expect_data(epnum, list(chunk), expected) yield xmit.join() if datax == PID.DATA0: datax = PID.DATA1 else: datax = PID.DATA0 @cocotb.coroutine def transaction_data_in(self, addr, ep, data, chunk_size=64): epnum = EndpointType.epnum(ep) datax = PID.DATA1 sent_data = 0 for i, chunk in enumerate(grouper_tofit(chunk_size, data)): sent_data = 1 self.dut._log.debug("Actual data we're expecting: {}".format(chunk)) for b in chunk: yield self.write(self.csrs['usb_epin_data'], b) yield self.write(self.csrs['usb_epin_epno'], epnum) recv = cocotb.fork(self.host_recv(datax, addr, epnum, chunk)) yield recv.join() if datax == PID.DATA0: datax = PID.DATA1 else: datax = PID.DATA0 if not sent_data: yield self.write(self.csrs['usb_epin_epno'], epnum) recv = cocotb.fork(self.host_recv(datax, addr, epnum, [])) yield self.send_data(datax, epnum, data) yield recv.join() @cocotb.coroutine def set_data(self, ep, data): _epnum = EndpointType.epnum(ep) for b in data: yield self.write(self.csrs['usb_epin_data'], b) @cocotb.coroutine def transaction_status_in(self, addr, ep): epnum = EndpointType.epnum(ep) assert EndpointType.epdir(ep) == EndpointType.IN xmit = cocotb.fork(self.host_recv(PID.DATA1, addr, epnum, [])) yield xmit.join() @cocotb.coroutine def transaction_status_out(self, addr, ep): epnum = EndpointType.epnum(ep) assert EndpointType.epdir(ep) == EndpointType.OUT xmit = cocotb.fork(self.host_send(PID.DATA1, addr, epnum, [])) yield xmit.join() @cocotb.coroutine def control_transfer_out(self, addr, setup_data, descriptor_data=None): epaddr_out = EndpointType.epaddr(0, EndpointType.OUT) epaddr_in = EndpointType.epaddr(0, EndpointType.IN) if (setup_data[0] & 0x80) == 0x80: raise Exception("setup_data indicated an IN transfer, but you requested an OUT transfer") # Setup stage self.dut._log.info("setup stage") yield self.transaction_setup(addr, setup_data) # Data stage if (setup_data[7] != 0 or setup_data[6] != 0) and descriptor_data is None: raise Exception("setup_data indicates data, but no descriptor data was specified") if (setup_data[7] == 0 and setup_data[6] == 0) and descriptor_data is not None: raise Exception("setup_data indicates no data, but descriptor data was specified") if descriptor_data is not None: self.dut._log.info("data stage") yield self.transaction_data_out(addr, epaddr_out, descriptor_data) # Status stage self.dut._log.info("status stage") # yield self.set_response(epaddr_in, EndpointResponse.ACK) yield self.transaction_status_in(addr, epaddr_in) @cocotb.coroutine def control_transfer_in(self, addr, setup_data, descriptor_data=None): epaddr_out = EndpointType.epaddr(0, EndpointType.OUT) epaddr_in = EndpointType.epaddr(0, EndpointType.IN) if (setup_data[0] & 0x80) == 0x00: raise Exception("setup_data indicated an OUT transfer, but you requested an IN transfer") # Setup stage self.dut._log.info("setup stage") yield self.transaction_setup(addr, setup_data) # Data stage # Data stage if (setup_data[7] != 0 or setup_data[6] != 0) and descriptor_data is None: raise Exception("setup_data indicates data, but no descriptor data was specified") if (setup_data[7] == 0 and setup_data[6] == 0) and descriptor_data is not None: raise Exception("setup_data indicates no data, but descriptor data was specified") if descriptor_data is not None: self.dut._log.info("data stage") yield self.transaction_data_in(addr, epaddr_in, descriptor_data) # Status stage self.dut._log.info("status stage") # yield self.set_response(epaddr_in, EndpointResponse.ACK) yield self.transaction_status_out(addr, epaddr_out)
class SpiboneTest: def __init__(self, dut, test_name): if "WIRES" in os.environ: self.wires = int(os.environ["WIRES"]) else: self.wires = 4 self.dut = dut self.test_name = test_name self.csrs = dict() self.dut.reset = 1 with open("csr.csv", newline='') as csr_csv_file: csr_csv = csv.reader(csr_csv_file) # csr_register format: csr_register, name, address, size, rw/ro for row in csr_csv: if row[0] == 'csr_register': exec("self.{} = {}".format(row[1].upper(), int(row[2], base=0))) cocotb.fork(Clock(dut.clk48, 20800, 'ps').start()) self.wb = WishboneMaster(dut, "wishbone", dut.clk12, timeout=20) # Set the signal "test_name" to match this test, so that we can # tell from gtkwave which test we're in. tn = cocotb.binary.BinaryValue(value=None, n_bits=4096) tn.buff = test_name self.dut.test_name = tn @cocotb.coroutine # Validate that the MOSI line doesn't change while CLK is 1 def validate_mosi_stability(self, test_name): previous_clk = self.dut.spi_clk previous_val = self.dut.spi_mosi while True: v = self.dut.spi_cs_n.value v_str = "{}".format(v) # self.dut._log.error("{} - CS_N value: {} Reset value: {}".format(test_name, v_str, self.dut.reset)) if v_str == "0" and self.dut.reset == 0: if self.dut.spi_clk: # Clock went high, so capture value if previous_clk == 0: previous_val = self.dut.spi_mosi else: if int(self.dut.spi_mosi) != int(previous_val): raise TestFailure( "spi.mosi changed while clk was high (was {}, now {})" .format(previous_val, self.dut.spi_mosi)) previous_clk = self.dut.spi_clk # else: # self.dut._log.error("CS_N is Z") yield Edge(self.dut.clk48) @cocotb.coroutine # Validate that the MOSI and MISO line don't change while CLK is 1 def validate_mosi_miso_stability(self, test_name): previous_clk = self.dut.spi_clk previous_mosi = self.dut.spi_mosi if self.wires == 4: previous_miso = self.dut.spi_miso while True: v = self.dut.spi_cs_n.value v_str = "{}".format(v) # self.dut._log.error("{} - CS_N value: {} Reset value: {}".format(test_name, v_str, self.dut.reset)) if v_str == "0" and self.dut.reset == 0: if self.dut.spi_clk: # Clock went high, so capture value if previous_clk == 0: previous_mosi = self.dut.spi_mosi if self.wires == 4: previous_miso = self.dut.spi_miso else: if int(self.dut.spi_mosi) != int(previous_mosi): raise TestFailure( "spi.mosi changed while clk was high (was {}, now {})" .format(previous_mosi, self.dut.spi_mosi)) if self.wires == 4: if int(self.dut.spi_miso) != int(previous_miso): raise TestFailure( "spi.mosi changed while clk was high (was {}, now {})" .format(previous_miso, self.dut.spi_miso)) previous_clk = self.dut.spi_clk # else: # self.dut._log.error("CS_N is Z") yield Edge(self.dut.clk48) @cocotb.coroutine def write(self, addr, val): yield self.wb.write(addr, val) @cocotb.coroutine def read(self, addr): value = yield self.wb.read(addr) raise ReturnValue(value) @cocotb.coroutine def reset(self): self.dut.reset = 1 self.dut.spi_cs_n = 1 self.dut.spi_mosi = 0 self.dut.spi_clk = 0 yield RisingEdge(self.dut.clk12) yield RisingEdge(self.dut.clk12) cocotb.fork(self.validate_mosi_miso_stability(self.test_name)) self.dut.reset = 0 self.dut.spi_mosi = 0 self.dut.spi_clk = 0 self.dut.spi_cs_n = 1 yield RisingEdge(self.dut.clk12) yield RisingEdge(self.dut.clk12) @cocotb.coroutine def host_spi_tick(self): self.dut.spi_clk = 0 yield RisingEdge(self.dut.clk12) self.dut.spi_clk = 1 yield RisingEdge(self.dut.clk12) @cocotb.coroutine def host_spi_write_byte(self, val): for shift in range(7, -1, -1): self.dut.spi_mosi = (val >> shift) & 1 yield self.host_spi_tick() @cocotb.coroutine def host_spi_read_byte(self): val = 0 for shift in range(7, -1, -1): yield self.host_spi_tick() if self.wires == 3 or self.wires == 2: val = val | (int(self.dut.spi_mosi) << shift) elif self.wires == 4: val = val | (int(self.dut.spi_miso) << shift) raise ReturnValue(val) @cocotb.coroutine def host_start(self): if self.wires == 3 or self.wires == 4: self.dut.spi_cs_n = 0 self.dut.spi_mosi = 0 elif self.wires == 2: yield self.host_spi_write_byte(0xab) if False: yield @cocotb.coroutine def host_finish(self): if self.wires == 3 or self.wires == 4: self.dut.spi_cs_n = 1 self.dut.spi_mosi = 0 yield self.host_spi_tick() self.dut.spi_mosi = 0 yield self.host_spi_tick() self.dut.spi_mosi = 0 yield self.host_spi_tick() self.dut.spi_mosi = 0 yield self.host_spi_tick() self.dut.spi_mosi = 0 yield self.host_spi_tick() if False: yield @cocotb.coroutine def host_spi_write(self, addr, val): yield self.host_start() # Header # 0: Write # 1: Read yield self.host_spi_write_byte(0) # Address for shift in [24, 16, 8, 0]: yield self.host_spi_write_byte(addr >> shift) # Value for shift in [24, 16, 8, 0]: yield self.host_spi_write_byte(val >> shift) # Wait for response timeout_counter = 0 while True: val = yield self.host_spi_read_byte() if val != 0xff: if val == 0: break raise TestFailure( "response byte was 0x{:02x}, not 0x00".format(val)) timeout_counter = timeout_counter + 1 if timeout_counter > 20: raise TestFailure("timed out waiting for response") yield self.host_finish() @cocotb.coroutine def host_spi_read(self, addr): yield self.host_start() # Header # 0: Write # 1: Read yield self.host_spi_write_byte(1) # Address for shift in [24, 16, 8, 0]: yield self.host_spi_write_byte(addr >> shift) # Wait for response timeout_counter = 0 while True: val = yield self.host_spi_read_byte() if val != 0xff: if val == 1: break raise TestFailure( "response byte was 0x{:02x}, not 0x01".format(val)) timeout_counter = timeout_counter + 1 if timeout_counter > 20: raise TestFailure("timed out waiting for response") # Value val = 0 for shift in [24, 16, 8, 0]: addon = yield self.host_spi_read_byte() val = val | (addon << shift) self.dut.spi_cs_n = 1 yield self.host_finish() raise ReturnValue(val)