You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

551 lines
16 KiB
Python

# toysram sdr tb
# converted from pyverilator version
#
# variations:
# sdr: 1x
# ddr: 1x
# ddr: 2x
import cocotb
from cocotb.clock import Clock
from cocotb.triggers import Timer
from cocotb.triggers import FallingEdge
from cocotb.triggers import RisingEdge
from cocotb.handle import Force
from cocotb.handle import Release
import itertools
from dotmap import DotMap
from random import getrandbits
from random import randint
from OPEnv import *
def hexrandom(w=16):
n = getrandbits(w*4)
return '{0:0{l}X}'.format(n, l=w)
# Classes ----------------------------------------------------------------------------------------
class Memory:
def __init__(self, locs, bits, init=0):
self.mem = [init] * locs
self.bits = bits
def read(self, adr):
return self.mem[adr]
def readall(self):
mem = []
for i in range(len(self.mem)):
mem.append(self.mem[i])
return mem
def write(self, adr, dat):
self.mem[adr] = dat
def __str__(self):
t = ''
for i in range(0,len(self.mem),4):
t1 = f'[{i:02X}] {self.mem[i]:018X}'
for j in range(i+1, i+4):
t1 += f' [{j:02X}] {self.mem[j]:018X}'
t += t1 + '\n'
return t
class Port:
def __init__(self, sim, id, type='r'):
self.sim = sim
self.dut = sim.dut
self.id = id
self.type = type
def read(self, adr):
if self.id == 0:
self.dut.rd_enb_0.value = 1
self.dut.rd_adr_0.value = adr
elif self.id == 1:
self.dut.rd_enb_1.value = 1
self.dut.rd_adr_1.value = adr
elif self.id == 2:
self.dut.rd_enb_2.value = 1
self.dut.rd_adr_2.value = adr
elif self.id == 3:
self.dut.rd_enb_3.value = 1
self.dut.rd_adr_3.value = adr
self.sim.msg(f'Port={self.id} RD @{adr:02X}')
def write(self, adr, dat):
if self.id == 0:
self.dut.wr_enb_0.value = 1
self.dut.wr_adr_0.value = adr
self.dut.wr_dat_0.value = dat
elif self.id == 1:
self.dut.wr_enb_1.value = 1
self.dut.wr_adr_1.value = adr
self.dut.wr_dat_1.value = dat
elif self.id == 2:
self.dut.wr_enb_2.value = 1
self.dut.wr_adr_2.value = adr
self.dut.wr_dat_2.value = dat
elif self.id == 3:
self.dut.wr_enb_3.value = 1
self.dut.wr_adr_3.value = adr
self.dut.wr_dat_3.value = dat
self.sim.msg(f'Port={self.id} WR @{adr:02X}={dat:018X}')
def data(self):
if self.id == 0:
return self.dut.rd_dat_0.value.integer
elif self.id == 1:
return self.dut.rd_dat_1.value.integer
elif self.id == 2:
return self.dut.rd_dat_2.value.integer
elif self.id == 3:
return self.dut.rd_dat_3.value.integer
def idle(self):
if self.type == 'r':
if id == 0:
self.dut.rd_enb_0.value = 0
self.dut.rd_adr_0.value = 0 # random
elif id == 1:
self.dut.rd_enb_1.value = 0
self.dut.rd_adr_1.value = 0 # random
elif id == 2:
self.dut.rd_enb_2.value = 0
self.dut.rd_adr_2.value = 0 # random
elif id == 3:
self.dut.rd_enb_3.value = 0
self.dut.rd_adr_3.value = 0 # random
else:
if id == 0:
self.dut.wr_enb_0.value = 0
self.dut.wr_adr_0.value = 0 # random
self.dut.wr_dat_0.value = 0 # random
elif id == 1:
self.dut.wr_enb_1.value = 0
self.dut.wr_adr_1.value = 0 # random
self.dut.wr_dat_1.value = 0 # random
elif id == 2:
self.dut.wr_enb_2.value = 0
self.dut.wr_adr_2.value = 0 # random
self.dut.wr_dat_2.value = 0 # random
elif id == 3:
self.dut.wr_enb_3.value = 0
self.dut.wr_adr_3.value = 0 # random
self.dut.wr_dat_31.value = 0 # random
def printstate(sim):
if sim.sdr:
sim.msg(f'R0: {sim.dut.ra.rd_enb_0_q.value} {sim.dut.ra.rd_adr_0_q.value} {sim.dut.ra.rd_dat_0_q.value} R1: {sim.dut.ra.rd_enb_1_q.value} {sim.dut.ra.rd_adr_1_q.value} {sim.dut.ra.rd_dat_1_q.value}')
sim.msg(f'W0: {sim.dut.ra.wr_enb_0_q.value} {sim.dut.ra.wr_adr_0_q.value} {sim.dut.ra.wr_dat_0_q.value}')
else:
sim.msg(f'R0: {sim.dut.ra.rd_enb_0_q:01X} {sim.dut.ra.rd_adr_0_q:02X} {sim.dut.ra.rd_dat_0_q:018X} R1: {sim.dut.ra.rd_enb_1_q:01X} {sim.dut.ra.rd_adr_1_q:02X} {sim.dut.ra.rd_dat_1_q:018X} R2: {sim.dut.ra.rd_enb_2_q:01X} {sim.dut.ra.rd_adr_2_q:02X} {sim.dut.ra.rd_dat_2_q:018X} R3: {sim.dut.ra.rd_enb_3_q:01X} {sim.dut.ra.rd_adr_3_q:02X} {sim.dut.ra.rd_dat_3_q:018X}')
sim.msg(f'W0: {sim.dut.ra.wr_enb_0_q:01X} {sim.dut.ra.wr_adr_0_q:02X} {sim.dut.ra.wr_dat_0_q:018X} W1: {sim.dut.ra.wr_enb_1_q:01X} {sim.dut.ra.wr_adr_1_q:02X} {sim.dut.ra.wr_dat_1_q:018X}')
def printfinal():
print()
print()
print('Final State')
print(f'Model : {top}')
print()
print(data)
# should be checking actual mem[][] here, but can't access signals
print()
for i in range(len(portsRd)):
print(f'Reads Port {i}: {reads[i]}')
for i in range(len(portsWr)):
print(f'Writes Port {i}: {writes[i]}')
print()
print(f'Seed: {seed:08X}')
print(f'Cycles: {cyc}')
print(f'Errors: {errors}')
# ------------------------------------------------------------------------------------------------
# Tasks
# get rid of z on anything that will be sampled here
# is there a func to get all inputs?
async def init(dut, sim):
"""Initialize inputs. """
return
dut.nclk.value = 0
dut.scan_in.value = 0
dut.an_ac_scan_type_dc.value = 0x0
dut.an_ac_chipid_dc.value = 0x0
dut.an_ac_coreid.value = 0x0
dut.an_ac_scom_sat_id.value = 0x0
dut.an_ac_lbist_ary_wrt_thru_dc.value = 0
dut.an_ac_gsd_test_enable_dc.value = 0
dut.an_ac_gsd_test_acmode_dc.value = 0
dut.an_ac_ccflush_dc.value = 0
dut.an_ac_ccenable_dc.value = 0
dut.an_ac_lbist_en_dc.value = 0
dut.an_ac_lbist_ip_dc.value = 0
dut.an_ac_lbist_ac_mode_dc.value = 0
dut.an_ac_scan_diag_dc.value = 0
dut.an_ac_scan_dis_dc_b.value = 0
dut.an_ac_rtim_sl_thold_8.value = 0
dut.an_ac_func_sl_thold_8.value = 0
dut.an_ac_func_nsl_thold_8.value = 0
dut.an_ac_ary_nsl_thold_8.value = 0
dut.an_ac_sg_8.value = 0
dut.an_ac_fce_8.value = 0
dut.an_ac_abst_scan_in.value = 0
dut.an_ac_checkstop.value = 0
dut.an_ac_reset_1_complete.value = 0
dut.an_ac_reset_2_complete.value = 0
dut.an_ac_reset_3_complete.value = 0
dut.an_ac_reset_wd_complete.value = 0
dut.an_ac_pm_fetch_halt.value = 0
dut.an_ac_debug_stop.value = 0
dut.an_ac_tb_update_enable.value = 1
dut.an_ac_tb_update_pulse.value = 0 # tb clock if xucr0[tcs]=1 (must be <1/2 proc clk; tb pulse is 2x this clock)
# why is coco turning [0] into non-vector??? or is that gpi/vpi/icarus/???
if sim.threads == 1:
dut.an_ac_pm_thread_stop.value = 0x1
dut.an_ac_external_mchk.value = 0
dut.an_ac_sleep_en.value = 0
dut.an_ac_ext_interrupt.value = 0
dut.an_ac_crit_interrupt.value = 0
dut.an_ac_perf_interrupt.value = 0
dut.an_ac_hang_pulse.value = 0
dut.an_ac_uncond_dbg_event.value = 0
else:
for i in range(sim.threads):
dut.an_ac_pm_thread_stop[i].value = 0x1
dut.an_ac_external_mchk[i].value = 0
dut.an_ac_sleep_en[i].value = 0
dut.an_ac_ext_interrupt[i].value = 0
dut.an_ac_crit_interrupt[i].value = 0
dut.an_ac_perf_interrupt[i].value = 0
dut.an_ac_hang_pulse[i].value = 0
dut.an_ac_uncond_dbg_event[i].value = 0
await Timer(9, units='ns')
async def config(dut, sim):
"""Configure core, etc. """
return
#wtf make A2 module to do core-specific stuff
# A2L2 load/store credits
creditsLd = dut.c0.lq0.lsq.arb.load_cred_cnt_d # 8 max
creditsLdMax = dut.c0.lq0.lsq.arb.ld_cred_max # hdw check
creditsSt = dut.c0.lq0.lsq.arb.store_cred_cnt_d # 32 max
creditsStMax = dut.c0.lq0.lsq.arb.st_cred_max # hdw check
creditsLdStSingle = dut.c0.lq0.lsq.arb.spr_xucr0_cred_d.value # 1 total credit
#wtf this affects A2L2 - default=1
#creditsLdStSingle = dut.c0.lq0.lsq.arb.spr_lsucr0_b2b_q.value # 0=crit first, every other 1=crit first, b2b **the a2l2 spec does not say crit must be first**
await RisingEdge(sim.clk1x)
if sim.config.core.creditsLd is not None:
creditsLd.value = Force(sim.config.core.creditsLd)
creditsLdMax.value = Force(sim.config.core.creditsLd)
sim.msg(f'A2L2: load credits changed from {creditsLd.value.integer} to {sim.config.core.creditsLd}.')
await RisingEdge(sim.clk1x)
creditsLd.value = Release()
if sim.config.core.creditsSt is not None:
creditsSt.value = Force(sim.config.core.creditsSt)
creditsStMax.value = Force(sim.config.core.creditsSt)
sim.msg(f'A2L2: store credits changed from {creditsSt.value.integer} to {sim.config.core.creditsSt}.')
await RisingEdge(sim.clk1x)
creditsSt.value = Release()
if sim.config.core.creditsLdStSingle:
creditsLdStSingle = Force(1)
sim.msg(f'A2L2: only one load OR store allowed when credits=1/1.')
await RisingEdge(sim.clk1x)
creditsLdStSingle.value = Release()
await RisingEdge(sim.clk1x)
async def coreMonitor(dut, sim):
"""Watch for core events. """
return
me = 'a2oMonitor'
# errors
creditsLdErr = dut.c0.lq0.lsq.arb.ld_cred_err_q
creditsStErr = dut.c0.lq0.lsq.arb.st_cred_err_q
# watches
iu0Comp = dut.c0.iu_lq_i0_completed
iu0CompIFAR = dut.c0.iuq0.iuq_cpl_top0.iuq_cpl0.cp2_i0_ifar
iu1Comp = dut.c0.iu_lq_i1_completed
iu1CompIFAR = dut.c0.iuq0.iuq_cpl_top0.iuq_cpl0.cp2_i1_ifar
iuCompFlushIFAR = dut.c0.cp_t0_flush_ifar
cp3NIA = dut.c0.iuq0.iuq_cpl_top0.iuq_cpl0.iuq_cpl_ctrl.cp3_nia_q # nia after last cycle's completions
# queue depths, etc.
errors = [
{'name': 'Load Credits', 'sig': creditsLdErr},
{'name': 'Store Credits', 'sig': creditsStErr},
]
done = False
while not done:
await RisingEdge(sim.clk1x)
for i in range(len(errors)):
assert errors[i]['sig'].value == 0, f'{me} Error: {errors[i]["name"]}'
comp = ''
if iu0Comp.value == 1:
comp = f'0:{int(iu0CompIFAR.value.binstr + "00", 2):06X} '
if iu1Comp.value == 1:
comp = f'{comp}1:{int(iu1CompIFAR.value.binstr + "00", 2):06X} '
if comp != '':
comp = f'{comp}{int(iuCompFlushIFAR.value.binstr + "00", 2):016X}'
sim.msg(f'C0: CP {comp}')
async def genReset(dut, sim):
"""Generate reset. """
first = True
done = False
while not done:
await RisingEdge(sim.clk1x)
if sim.cycle < sim.resetCycle:
if first:
sim.msg(f'[{sim.cycle:08d}] Resetting...')
first = False
dut.reset.value = 1
elif not done:
sim.msg(f'[{sim.cycle:08d}] Releasing reset.')
dut.reset.value = 0
done = True
async def genClocks(dut, sim):
"""Generate clock pulses. """
sim.clk1x = dut.clk
clk = Clock(sim.clk1x, sim.clk1xPeriod, 'ns')
await cocotb.start(clk.start())
for cycle in range(sim.maxCycles):
sim.cycle = cycle
if cycle % sim.hbCycles == 0:
sim.msg(f'[{cycle:08d}] ...tick...')
await RisingEdge(sim.clk1x)
sim.msg(f'[{sim.cycle:08d}] Reached max cycle. Clocks stopped.')
# ------------------------------------------------------------------------------------------------
# Interfaces
# ------------------------------------------------------------------------------------------------
# Do something
@cocotb.test()
async def tb(dut):
"""ToySRAM array test"""
sim = Sim(dut)
sim.sdr = True
sim.ddr = False
sim.clk1xPeriod = 1
sim.clk2x = False
sim.maxCycles = 500
# init stuff
await init(dut, sim)
# start clocks,reset
await cocotb.start(genClocks(dut, sim))
await cocotb.start(genReset(dut, sim))
# start interfaces
#await cocotb.start(A2L2.driver(dut, sim))
#await cocotb.start(A2L2.checker(dut, sim))
#await cocotb.start(A2L2.monitor(dut, sim))
await Timer((sim.resetCycle + 5)*8, units='ns')
if dut.reset.value != 0:
sim.ok = False
sim.fail = 'Reset active too long!'
# config stuff
await config(dut, sim)
# monitor stuff
await cocotb.start(coreMonitor(dut, sim))
# do stuff
data = Memory(64, 72)
if sim.sdr:
portsRd = [Port(sim, 0, 'r'), Port(sim, 1, 'r')]
portsWr = [Port(sim, 0, 'w')]
else:
portsRd = [Port(sim, 0, 'r'), Port(sim, 1, 'r'), Port(sim, 2, 'r'), Port(sim, 3, 'r')]
portsWr = [Port(sim, 0, 'w'), Port(sim, 1, 'w')]
await RisingEdge(sim.clk1x)
# idle
for p in portsRd:
p.idle()
for p in portsWr:
p.idle()
for i in range(10):
await RisingEdge(sim.clk1x)
# init array
sim.msg('Initializing array...')
if sim.sdr:
for a in range(64):
d0 = int(f'{a:02X}55555555555555{a:02X}', 16)
portsWr[0].write(a, d0)
await RisingEdge(sim.clk1x)
data.write(a, d0) # now visible for reads
portsWr[0].idle()
else:
pass
'''
for a in range(0, 64, 2):
d0 = int(f'5555555555555555{a:02X}', 16)
portsWr[0].write(a, d0)
d1 = int(f'5555555555555555{a+1:02X}', 16)
portsWr[1].write(a+1, d1)
run(1, printstate)
data.write(a, d0) # now visible for reads
data.write(a+1, d1) # now visible for reads
portsWr[0].idle()
portsWr[1].idle()
'''
# random cmds
updates = []
checks = []
reads = [0, 0, 0, 0]
writes = [0, 0]
saveData = None
quiesced = False
quiesceCyc = sim.cycle + sim.maxCycles - 10
errors = 0
rangesRd = [(0,7), (0,7), (0,7), (0,7)]
#rangesWr = [(0,63), (0,63)]
rangesWr = [(0,7), (0,7)]
verbose = True
for c in range(sim.maxCycles):
sim.ok = True
await FallingEdge(sim.clk1x)
# check reads
checksNext = []
for i in range(len(checks)):
rd = checks[i]
if sim.cycle == rd[0]:
port = rd[1]
adr = rd[2]
act = portsRd[port].data()
exp = saveData[rd[2]]
if act != exp:
sim.ok = False
try:
sim.msg(f'* RD MISCOMPARE * port={port} adr={adr:02X} act={act:018X} exp={exp:018X}')
except:
sim.msg(f'* RD MISCOMPARE * port={port} adr={adr} act={act} exp={exp}')
elif verbose:
sim.msg(f'* RD COMPARE * port={port} adr={adr:02X} act={act:018X} exp={exp:018X}')
else:
checksNext.append(rd)
checks = checksNext
# do writes
updatesNext = [] # always only 1 cycle
for i in range(len(updates)):
wr = updates[i]
if sim.cycle == wr[0]:
data.write(wr[2], wr[3])
else:
assert False, f'HUH? should always be this cycle! {sim.cycle},{updates}'
updates = updatesNext
# save current data
saveData = data.readall()
# quiesce?
if sim.cycle >= quiesceCyc:
if not quiesced:
sim.msg('Quiescing...')
quiesced = True
# write coll will give w1 precedence - or make it avoid
aw = [None] * 2
for i in range(len(portsWr)):
portsWr[i].idle()
aw[i] = -1
if not quiesced and randint(1, 10) < 5:
r = rangesWr[i]
aw[i] = randint(r[0], r[1])
d = int(hexrandom(18), 16)
portsWr[i].write(aw[i], d)
updates.append((sim.cycle + 1, i, aw[i], d))
writes[i] += 1
for i in range(len(portsRd)):
portsRd[i].idle()
if not quiesced and randint(1, 10) < 5:
r = rangesRd[i]
ar = randint(r[0], r[1])
while ar == aw[0] or ar == aw[1]:
ar = randint(r[0], r[1])
portsRd[i].read(ar)
checks.append((sim.cycle + 2, i, ar))
reads[i] += 1
await RisingEdge(sim.clk1x)
printstate(sim)
if not sim.ok: # and stopOnFail:
break
if sim.ok:
sim.msg(f'[{sim.cycle:08d}] You has opulence.')
else:
for i in range(10):
await RisingEdge(sim.clk1x)
sim.msg(f'[{sim.cycle:08d}] You are worthless and weak!')
assert False, f'[{sim.cycle:08d}] {sim.fail}'