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.
445 lines
11 KiB
Python
445 lines
11 KiB
Python
2 years ago
|
#!/usr/bin/python3
|
||
|
|
||
|
# pyverilator
|
||
|
# fixed internal sig parsing (cdata/wdata)
|
||
|
# 1. this should be based on init setting AND should be done even w/o trace on!!!
|
||
|
# in add_to_vcd_trace(self), time is bumped +5
|
||
|
# 2. should count cycs
|
||
|
# 3. add parm so clock can be set but NOT eval (for multiclock, only fastest evals)
|
||
|
# 4. how to access mem[][]??
|
||
|
# 5. not adding vectors to gtk - cuz 0:n?
|
||
|
|
||
|
import os, sys
|
||
|
import datetime
|
||
|
from optparse import OptionParser
|
||
|
from optparse import OptionGroup
|
||
|
|
||
|
import random
|
||
|
from random import randint
|
||
|
|
||
|
from pysutils import *
|
||
|
|
||
|
user = os.environ['USER']
|
||
|
binPath = os.path.dirname(os.path.realpath(__file__))
|
||
|
|
||
|
localPV = True
|
||
|
if localPV:
|
||
|
import os, sys
|
||
|
sys.path.append(os.path.join(binPath, 'pyverilator'))
|
||
|
import pyverilator
|
||
|
|
||
|
####################################################################
|
||
|
# Defaults
|
||
|
|
||
|
rtl = ['src']
|
||
|
model = 'sdr'
|
||
|
|
||
|
stopOnFail = True
|
||
|
verbose = False
|
||
|
vcd = False
|
||
|
seed = randint(1, int('8675309', 16))
|
||
|
runCycs = 100
|
||
|
|
||
|
#rangesRd = [(0,63), (0,63), (0,63), (0,63)]
|
||
|
rangesRd = [(0,7), (0,7), (0,7), (0,7)]
|
||
|
#rangesWr = [(0,63), (0,63)]
|
||
|
rangesWr = [(0,7), (0,7)]
|
||
|
|
||
|
|
||
|
####################################################################
|
||
|
# Process command line
|
||
|
|
||
|
usage = "Usage: %prog [options]"
|
||
|
parser = OptionParser(usage)
|
||
|
|
||
|
parser.add_option('-m', '--model', dest='model', help=f'sdr or ddr')
|
||
|
parser.add_option('-s', '--seed', dest='seed', help=f'initialize seed to n')
|
||
|
parser.add_option('-c', '--cycles', dest='runCycs', help=f'cycles to run, default={runCycs}')
|
||
|
|
||
|
parser.add_option('-t', '--trace', dest='trace', action='store_true', help=f'create wave file')
|
||
|
parser.add_option('-f', '--stopfail', dest='stopOnFail', action='store_true', help=f'stop on first fail')
|
||
|
|
||
|
parser.add_option('-v', '--verbose', dest='verbose', action='store_true', help=f'noisy output')
|
||
|
|
||
|
options, args = parser.parse_args()
|
||
|
|
||
|
if options.model is not None:
|
||
|
model = options.model
|
||
|
|
||
|
if options.seed is not None:
|
||
|
seed = int(options.seed)
|
||
|
|
||
|
if options.runCycs is not None:
|
||
|
runCycs = int(options.runCycs)
|
||
|
|
||
|
if options.trace is not None:
|
||
|
vcd = True
|
||
|
|
||
|
if options.stopOnFail is not None:
|
||
|
stopOnFail = True
|
||
|
|
||
|
if options.verbose is not None:
|
||
|
verbose = True
|
||
|
|
||
|
####################################################################
|
||
|
# Init
|
||
|
|
||
|
sdr = False
|
||
|
ddr = False
|
||
|
ddr1x = False
|
||
|
|
||
|
if model == 'sdr':
|
||
|
top = 'test_ra_sdr.v'
|
||
|
sdr = True
|
||
|
elif model == 'ddr1x':
|
||
|
top = 'test_ra_ddr_1x.v'
|
||
|
ddr = True
|
||
|
ddr1x = True
|
||
|
else:
|
||
|
top = 'test_ra_ddr.v'
|
||
|
ddr = True
|
||
|
|
||
|
errors = 0
|
||
|
cyc = 0
|
||
|
quiesceCyc = 5 # before end
|
||
|
|
||
|
# build model
|
||
|
sim = pyverilator.PyVerilator.build(top, verilog_path=rtl)
|
||
|
print('io')
|
||
|
print(sim.io)
|
||
|
print()
|
||
|
|
||
|
print('internals')
|
||
|
# issue #8 - try local fix
|
||
|
print(sim.internals)
|
||
|
print()
|
||
|
|
||
|
#print('ra')
|
||
|
#print(sim.internals.ra)
|
||
|
# array0,1,2 dont exist as submodules???
|
||
|
#print()
|
||
|
#
|
||
|
#print('ra.add_clk')
|
||
|
#print(sim.internals.ra.add_clk)
|
||
|
#print()
|
||
|
|
||
|
if vcd:
|
||
|
sim.start_gtkwave(auto_tracing=False)
|
||
|
|
||
|
#wtf vectors are failing
|
||
|
# will make this load a savefile anyway someday
|
||
|
# this doesn't actually restrict what's beign recorded anyway; still
|
||
|
# can load saved netlist after sim
|
||
|
#sim.send_to_gtkwave(sim.io)
|
||
|
#for s in sim.io:
|
||
|
# try:
|
||
|
# sim.send_to_gtkwave(sim.io[s])
|
||
|
# except:
|
||
|
# print(f'*** failed {s}')
|
||
|
|
||
|
####################################################################
|
||
|
# Functions, Classes
|
||
|
|
||
|
def getSimTime():
|
||
|
return (sim.curr_time, cyc)
|
||
|
msg(init=getSimTime)
|
||
|
|
||
|
# sim-driven signals don't look like _q since they are set after the eval(clk=1) tick
|
||
|
# would have to set after eval of rising edge but also not do a simtick
|
||
|
def tick():
|
||
|
sim.eval()
|
||
|
if vcd:
|
||
|
sim.add_to_vcd_trace()
|
||
|
|
||
|
def run(n=1, cb=None):
|
||
|
global cyc
|
||
|
|
||
|
if sdr or ddr1x:
|
||
|
for i in range(n):
|
||
|
sim.io.clk = 0
|
||
|
tick()
|
||
|
sim.io.clk = 1
|
||
|
tick()
|
||
|
elif ddr:
|
||
|
for i in range(n):
|
||
|
sim.io.clk = 0
|
||
|
sim.io.clk2x = 1
|
||
|
tick()
|
||
|
sim.io.clk2x = 0
|
||
|
tick()
|
||
|
sim.io.clk = 1
|
||
|
sim.io.clk2x = 1
|
||
|
tick()
|
||
|
sim.io.clk2x = 0
|
||
|
tick()
|
||
|
|
||
|
cyc += 1
|
||
|
if not vcd: # should be done by pyv!!!!
|
||
|
sim.curr_time = cyc * 10
|
||
|
if cb is not None:
|
||
|
(cb)()
|
||
|
|
||
|
def fail(t=None):
|
||
|
global errors, stopOnFail
|
||
|
msg('*** FAIL ***')
|
||
|
errors += 1
|
||
|
if t is not None:
|
||
|
msg(t)
|
||
|
|
||
|
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}'
|
||
|
#t1 += f' {self.mem[j]:018X}\n'
|
||
|
t += t1 + '\n'
|
||
|
return t
|
||
|
|
||
|
class Port:
|
||
|
|
||
|
def __init__(self, id, type='r'):
|
||
|
self.id = id
|
||
|
self.type = type
|
||
|
|
||
|
def read(self, adr):
|
||
|
sim.io[f'rd_enb_{self.id}'] = 1
|
||
|
sim.io[f'rd_adr_{self.id}'] = adr
|
||
|
msg(f'Port={self.id} RD @{adr:02X}')
|
||
|
def write(self, adr, dat):
|
||
|
sim.io[f'wr_enb_{self.id}'] = 1
|
||
|
sim.io[f'wr_adr_{self.id}'] = adr
|
||
|
sim.io[f'wr_dat_{self.id}'] = dat
|
||
|
msg(f'Port={self.id} WR @{adr:02X}={dat:02X}')
|
||
|
|
||
|
def data(self):
|
||
|
return int(sim.io[f'rd_dat_{self.id}'])
|
||
|
|
||
|
def idle(self):
|
||
|
if self.type == 'r':
|
||
|
sim.io[f'rd_enb_{self.id}'] = 0
|
||
|
sim.io[f'rd_adr_{self.id}'] = 0 # random
|
||
|
else:
|
||
|
sim.io[f'wr_enb_{self.id}'] = 0
|
||
|
sim.io[f'wr_adr_{self.id}'] = 0 # random
|
||
|
sim.io[f'wr_dat_{self.id}'] = 0 # random
|
||
|
|
||
|
def printstate():
|
||
|
mac = sim.internals.ra
|
||
|
if sdr:
|
||
|
msg(f'R0: {mac.rd_enb_0_q:01X} {mac.rd_adr_0_q:02X} {mac.rd_dat_0_q:018X} R1: {mac.rd_enb_1_q:01X} {mac.rd_adr_1_q:02X} {mac.rd_dat_1_q:018X}')
|
||
|
msg(f'W0: {mac.wr_enb_0_q:01X} {mac.wr_adr_0_q:02X} {mac.wr_dat_0_q:018X}')
|
||
|
else:
|
||
|
msg(f'R0: {mac.rd_enb_0_q:01X} {mac.rd_adr_0_q:02X} {mac.rd_dat_0_q:018X} R1: {mac.rd_enb_1_q:01X} {mac.rd_adr_1_q:02X} {mac.rd_dat_1_q:018X} R2: {mac.rd_enb_2_q:01X} {mac.rd_adr_2_q:02X} {mac.rd_dat_2_q:018X} R3: {mac.rd_enb_3_q:01X} {mac.rd_adr_3_q:02X} {mac.rd_dat_3_q:018X}')
|
||
|
msg(f'W0: {mac.wr_enb_0_q:01X} {mac.wr_adr_0_q:02X} {mac.wr_dat_0_q:018X} W1: {mac.wr_enb_1_q:01X} {mac.wr_adr_1_q:02X} {mac.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}')
|
||
|
|
||
|
def check(port, adr, exp=None):
|
||
|
|
||
|
if exp is None:
|
||
|
exp = data.read(adr)
|
||
|
act = portsRd[port].data()
|
||
|
if act != exp:
|
||
|
fail(f'* RD MISCOMPARE * port={port} adr={adr:02X} act={act:018X} exp={exp:018X}')
|
||
|
return False
|
||
|
elif verbose:
|
||
|
msg(f'* RD COMPARE * port={port} adr={adr:02X} act={act:018X} exp={exp:018X}')
|
||
|
return True
|
||
|
|
||
|
####################################################################
|
||
|
# Do something
|
||
|
|
||
|
msg(f'Initializing seed to {hex(seed)}')
|
||
|
|
||
|
random.seed(seed)
|
||
|
|
||
|
data = Memory(64, 72)
|
||
|
if sdr:
|
||
|
portsRd = [Port(0, 'r'), Port(1, 'r')]
|
||
|
portsWr = [Port(0, 'w')]
|
||
|
else:
|
||
|
portsRd = [Port(0, 'r'), Port(1, 'r'), Port(2, 'r'), Port(3, 'r')]
|
||
|
portsWr = [Port(0, 'w'), Port(1, 'w')]
|
||
|
|
||
|
# Array Cycle Timings
|
||
|
#
|
||
|
# write
|
||
|
# | e/a/d | acc | valid |
|
||
|
# * latched by wrapper (in)
|
||
|
#
|
||
|
#
|
||
|
# read
|
||
|
# | e/a | acc | valid |
|
||
|
# * latched by wrapper (in)
|
||
|
# * latched by wrapper (out)
|
||
|
#
|
||
|
# rd(a) = wr(a) (both enabled):
|
||
|
|
||
|
|
||
|
# reset
|
||
|
sim.io.reset = 1
|
||
|
run(1)
|
||
|
sim.io.reset = 0
|
||
|
|
||
|
# idle
|
||
|
for p in portsRd:
|
||
|
p.idle()
|
||
|
for p in portsWr:
|
||
|
p.idle()
|
||
|
|
||
|
run(10)
|
||
|
|
||
|
# init array
|
||
|
if sdr:
|
||
|
for a in range(0, 64, 1):
|
||
|
d0 = int(f'5555555555555555{a:02X}', 16)
|
||
|
portsWr[0].write(a, d0)
|
||
|
run(1, printstate)
|
||
|
data.write(a, d0) # now visible for reads
|
||
|
portsWr[0].idle()
|
||
|
else:
|
||
|
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
|
||
|
# writes: visible to all reads in cycle n+1,...
|
||
|
# reads: check in cycle n+2 vs mem data in cycle n+1
|
||
|
#
|
||
|
# every cycle:
|
||
|
# save data state
|
||
|
# pick weighted read0, read1, read2, read3, write0, write1 (cmd freq, adr) and ensure no adr coll if req'd
|
||
|
# schedule data change (write)
|
||
|
# schedule checks (read)
|
||
|
|
||
|
updates = []
|
||
|
checks = []
|
||
|
reads = [0, 0, 0, 0]
|
||
|
writes = [0, 0]
|
||
|
saveData = None
|
||
|
quiesced = False
|
||
|
quiesceCyc = cyc + runCycs - quiesceCyc
|
||
|
|
||
|
#d = int('1000', 16)
|
||
|
msg('Starting random loop.')
|
||
|
for c in range(runCycs):
|
||
|
|
||
|
ok = True
|
||
|
|
||
|
# check reads
|
||
|
checksNext = []
|
||
|
for i in range(len(checks)):
|
||
|
rd = checks[i]
|
||
|
if cyc == rd[0]:
|
||
|
ok = ok and check(rd[1], rd[2], saveData[rd[2]])
|
||
|
else:
|
||
|
checksNext.append(rd)
|
||
|
checks = checksNext
|
||
|
|
||
|
# do writes
|
||
|
updatesNext = [] # always only 1 cycle
|
||
|
for i in range(len(updates)):
|
||
|
wr = updates[i]
|
||
|
if cyc == wr[0]:
|
||
|
data.write(wr[2], wr[3])
|
||
|
else:
|
||
|
print('HUH? should always be this cycle!', cyc, updates)
|
||
|
quit()
|
||
|
updates = updatesNext
|
||
|
|
||
|
# save current data
|
||
|
saveData = data.readall()
|
||
|
|
||
|
# quiesce?
|
||
|
if cyc >= quiesceCyc:
|
||
|
if not quiesced:
|
||
|
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((cyc+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((cyc+2, i, ar))
|
||
|
reads[i] += 1
|
||
|
|
||
|
run(1, printstate)
|
||
|
if not ok and stopOnFail:
|
||
|
break
|
||
|
|
||
|
####################################################################
|
||
|
# Clean up
|
||
|
|
||
|
printfinal()
|
||
|
|
||
|
if ok and errors == 0:
|
||
|
print()
|
||
|
print('You has opulence.')
|
||
|
print()
|
||
|
else:
|
||
|
print()
|
||
|
print('You are worthless and weak!')
|
||
|
print()
|
||
|
|
||
|
print('Done.')
|