mirror of https://github.com/efabless/caravel.git
371 lines
18 KiB
Python
371 lines
18 KiB
Python
|
from audioop import add
|
||
|
from ctypes import BigEndianStructure
|
||
|
from operator import truediv
|
||
|
from cocotb import top
|
||
|
import cocotb
|
||
|
from cocotb.binary import BinaryValue
|
||
|
from cocotb.triggers import Timer, RisingEdge, ReadOnly
|
||
|
import fnmatch
|
||
|
import copy
|
||
|
import logging
|
||
|
from cocotb.result import TestFailure
|
||
|
from cocotb_coverage.coverage import *
|
||
|
|
||
|
class HK_models():
|
||
|
def __init__(self,reg_model,expeceted_output,hk_hdl):
|
||
|
self.reg_model = reg_model
|
||
|
self.expeceted_output = expeceted_output
|
||
|
self.hk_hdl = hk_hdl
|
||
|
self.old_hold_val = 1
|
||
|
self.gpio_out_pre = BinaryValue(value=0,n_bits=38,bigEndian=False)
|
||
|
self.mgmt_gpio_oeb = BinaryValue(value=0,n_bits=38,bigEndian=False)
|
||
|
self.exp_out_wb = [] # expected output for wishbone
|
||
|
self.exp_out_uart_rx = [self.expeceted_output['UART']] # expected output for uart
|
||
|
self.exp_out_debug = [self.expeceted_output['debug']] # expected output for
|
||
|
self.exp_out_spi = [self.expeceted_output['SPI']] # expected output for
|
||
|
self.reset_spi_vals(True)
|
||
|
self.input_dis =3
|
||
|
self.regs_full_list()
|
||
|
self.intial_cov()
|
||
|
|
||
|
|
||
|
|
||
|
"""model for the wishbone interface with housekeeping"""
|
||
|
def wishbone_model(self,trans):
|
||
|
clock_signal = trans["_clk"]["signal"]
|
||
|
if trans["stb"]["val"] == 1 : # valid data
|
||
|
address = trans["adr"]["val"]
|
||
|
data = trans["data"]["val"]
|
||
|
# if ack isn't realsead don't care about the new input it must be the same as the old and the monitor checks that
|
||
|
if (self.old_hold_val == 0):
|
||
|
self.old_hold_val = trans["_hold"]["val"]
|
||
|
return
|
||
|
self.old_hold_val = trans["_hold"]["val"]
|
||
|
if trans["write_en"]["val"] ==1 : # write cycle
|
||
|
cocotb.log.debug(f'[HK_models][_valid_address] write enable for address: {hex(address)} and data: {hex(data)} ')
|
||
|
select = trans["sel"]["val"]
|
||
|
cocotb.scheduler.add(self._write_reg_wb(address,data,select,clock_signal))
|
||
|
else: #read cycle
|
||
|
self.write_exp_out_wb(self._read_reg_wb(address))
|
||
|
self.wishbone_last_trans = trans
|
||
|
|
||
|
"""model for the system interface with housekeeping"""
|
||
|
def system_model(self,trans):
|
||
|
self.reg_model['sys']['0x00'][0][6] = trans["vdd2_good"]["val"]
|
||
|
self.reg_model['sys']['0x00'][1][6] = trans["vdd1_good"]["val"]
|
||
|
self.reg_model['sys']['0x00'][2][6] = trans["vcc2_good"]["val"]
|
||
|
self.reg_model['sys']['0x00'][3][6] = trans["vcc1_good"]["val"]
|
||
|
|
||
|
|
||
|
"""model for the UART interface with housekeeping"""
|
||
|
def UART_model(self,trans):
|
||
|
# when transmiting TX the gpio data out should change to the tx value
|
||
|
if trans["enable"]["val"]:
|
||
|
self.gpio_out_pre[6]= trans["TX"]["val"].value
|
||
|
else:
|
||
|
gpio_data = self._read_reg_keys(["GPIO","0x0c"]) # mgmt_gpio_data[7:0]
|
||
|
self.gpio_out_pre[6]= int(gpio_data[len(gpio_data)-1 -6]) # mgmt_gpio_data[6]
|
||
|
# predecting the rx output value when the value UART is enble is the value drom pin [5]
|
||
|
gpio_in_size = trans["gpio_in"]["val"].n_bits -1
|
||
|
output = self.expeceted_output['UART']
|
||
|
output["RX"]["val"] = 0
|
||
|
if trans["enable"]["val"]:
|
||
|
output["RX"]["val"] = trans["gpio_in"]["val"][gpio_in_size-5] #gpio_in[5]
|
||
|
self.exp_out_uart_rx.append(output)
|
||
|
|
||
|
|
||
|
"""model for the debug interface with housekeeping"""
|
||
|
def debug_model(self,trans):
|
||
|
# when debug is enable gpio out [0] should follow debug_out
|
||
|
if trans["enable"]["val"]:
|
||
|
self.gpio_out_pre[0] = trans["data"]["val"].value
|
||
|
self.mgmt_gpio_oeb[0] = trans["data"]["val"].value
|
||
|
else:
|
||
|
gpio_data = self._read_reg_keys(["GPIO","0x0c"]) # mgmt_gpio_data[7:0]
|
||
|
self.gpio_out_pre[0]= int(gpio_data[len(gpio_data)-1 -0]) # mgmt_gpio_data[6]
|
||
|
gpio_data_en = self._read_reg_keys(["GPIO","0x24"]) # gpio_configure[0][7:0]
|
||
|
self.mgmt_gpio_oeb[0]= 1- int(gpio_data[len(gpio_data_en)-1 -self.input_dis]) # gpio_configure[0][3]
|
||
|
|
||
|
# predecting the debug in signal when debug mode is enbled taking the value from pin [0]
|
||
|
gpio_in_size = trans["gpio_in"]["val"].n_bits -1
|
||
|
output = self.expeceted_output['debug']
|
||
|
output["data"]["val"] = 0
|
||
|
if trans["enable"]["val"]:
|
||
|
output["data"]["val"] = trans["gpio_in"]["val"][gpio_in_size-0] # mgmt_gpio_in[0]
|
||
|
self.exp_out_debug.append(output)
|
||
|
|
||
|
"""model for the SPI interface with housekeeping"""
|
||
|
def spi_model(self,trans):
|
||
|
output = self.expeceted_output['SPI']
|
||
|
output["SDO"]["val"] = 0
|
||
|
cocotb.log.debug(f'[HK_models][spi_model] spi mode {self.spi_mode[0]} bit number {self.spi_mode[1]} command = {self.command_spi} address = {self.address_spi} write = {self.write_spi} stream = {self.spi_mode[2]}')
|
||
|
if self.spi_mode[0] == "command":
|
||
|
self._setCommand(trans['SDI']['val'].binstr)
|
||
|
self.exp_out_spi.append(output)
|
||
|
elif self.spi_mode[0] == "address":
|
||
|
self._setAddress(trans['SDI']['val'].binstr)
|
||
|
self.exp_out_spi.append(output)
|
||
|
elif self.spi_mode[0] == "write":
|
||
|
self._setWriteData(trans['SDI']['val'].binstr)
|
||
|
self.exp_out_spi.append(output)
|
||
|
elif self.spi_mode[0] == "read":
|
||
|
output["SDO"]["val"] = self._getReadData()
|
||
|
self.exp_out_spi.append(output)
|
||
|
elif self.spi_mode[0] == "read/write":
|
||
|
output["SDO"]["val"] = self._getReadData()
|
||
|
self.exp_out_spi.append(output)
|
||
|
elif self.spi_mode[0] == "noOP":
|
||
|
self._setWriteData(trans['SDI']['val'].binstr)
|
||
|
else:
|
||
|
raise TestFailure("[HK_models][spi_model] invalid command type")
|
||
|
|
||
|
|
||
|
"""reset the spi vals when CSB is going from low to high"""
|
||
|
def reset_spi_vals(self,trans):
|
||
|
cocotb.log.info(f"[HK_models][reset_spi_vals] CSB is disabled")
|
||
|
self.spi_mode = ["command",0,0] # [mode type, bit number, stream number]
|
||
|
self.command_spi = ['0']*8
|
||
|
self.address_spi = ['0']*8
|
||
|
self.write_spi = ['0']*8
|
||
|
self.read_spi = ['0']*8
|
||
|
|
||
|
def _setCommand(self,bit):
|
||
|
if bit not in ['0','1']:
|
||
|
cocotb.log.warning(f"[HK_models][_setCommand] incorrect bit size bit = {bit}")
|
||
|
self.command_spi[self.spi_mode[1]] = bit
|
||
|
self.spi_mode[1] += 1
|
||
|
self.spi_mode[2] = 0 # stream number
|
||
|
if self.spi_mode[1] >= 8:
|
||
|
self.spi_mode[0] = "address"
|
||
|
self.spi_mode[1] = 0
|
||
|
|
||
|
def _setAddress(self,bit):
|
||
|
if bit not in ['0','1']:
|
||
|
cocotb.log.warning(f"[HK_models][_setAddress] incorrect bit size bit = {bit}")
|
||
|
self.address_spi[self.spi_mode[1]] = bit
|
||
|
self.spi_mode[1] += 1
|
||
|
self.spi_mode[2] = 0 # stream number
|
||
|
if self.spi_mode[1] >= 8:
|
||
|
self.spi_mode[1] = 0
|
||
|
if self.command_spi[0:2] == ['0','0']:
|
||
|
self.spi_mode[0] = "noOP"
|
||
|
if self.command_spi[0:2] == ['1','0']:
|
||
|
self.spi_mode[0] = "write"
|
||
|
if self.command_spi[0:2] == ['0','1']:
|
||
|
self.spi_mode[0] = "read"
|
||
|
if self.command_spi[0:2] == ['1','1']:
|
||
|
self.spi_mode[0] = "read/write"
|
||
|
|
||
|
def _setWriteData(self,bit):
|
||
|
if bit not in ['0','1']:
|
||
|
cocotb.log.warning(f"[HK_models][_setWriteData] incorrect bit size bit = {bit}")
|
||
|
# return if write is write n-bytes command and number of bytes exceeds the required
|
||
|
if self.command_spi[2:5] != ['0','0','0']:
|
||
|
self.spi_mode_cov('write-n')
|
||
|
byte_num = int(''.join(self.command_spi[2:5]),2)
|
||
|
if byte_num <= self.spi_mode[2]: #number of written byte <= stream number
|
||
|
self.spi_mode[0] = "noOP"
|
||
|
return
|
||
|
else: self.spi_mode_cov('write')
|
||
|
self.write_spi[self.spi_mode[1]] = bit
|
||
|
self.spi_mode[1] += 1
|
||
|
if self.spi_mode[1] >= 8:
|
||
|
self.spi_mode[1] = 0
|
||
|
address = int(''.join(self.address_spi),2) + self.spi_mode[2]
|
||
|
data = ''.join(self.write_spi)
|
||
|
data = BinaryValue(value = data, n_bits =8)
|
||
|
is_valid, keys=self._valid_address_spi(address)
|
||
|
if is_valid:
|
||
|
self.reg_cov(keys[0],keys[1],is_read=False)
|
||
|
cocotb.log.debug(f'[HK_models][_valid_address] writing {data} to memory:{keys[0]} field: {keys[1]} through housekeeping SPI address = {address}')
|
||
|
self._write_fields(keys,data)
|
||
|
if self.command_spi[2:5] != ['0','0','0']:self.spi_mode_cov('write-n')
|
||
|
else: self.spi_mode_cov('write')
|
||
|
self.spi_mode[2] += 1 # stream number
|
||
|
|
||
|
|
||
|
def _getReadData(self):
|
||
|
# return if write is write n-bytes command and number of bytes exceeds the required
|
||
|
if self.command_spi[2:5] != ['0','0','0']:
|
||
|
byte_num = int(''.join(self.command_spi[2:5]),2)
|
||
|
if byte_num <= self.spi_mode[2]: #number of written byte <= stream number
|
||
|
self.spi_mode[0] = "noOP"
|
||
|
return
|
||
|
bit_num = self.spi_mode[1]
|
||
|
address = int(''.join(self.address_spi),2) + self.spi_mode[2]
|
||
|
is_valid, keys=self._valid_address_spi(address)
|
||
|
data = ''
|
||
|
if is_valid:
|
||
|
self.reg_cov(keys[0],keys[1])
|
||
|
cocotb.log.info(f'[HK_models][_getReadData] reading from memory:{keys[0]} field: {keys[1]} through SPI')
|
||
|
for field in self.reg_model[keys[0]][keys[1]]:
|
||
|
data = str(bin(field[6])[2:]).zfill(field[3]) + data
|
||
|
data = data.zfill(8)
|
||
|
cocotb.log.info(f'[HK_models][_getReadData] reading from memory:{keys[0]} field: {keys[1]} through SPI data {data} bit[{7-bit_num}] = {data[bit_num]}')
|
||
|
|
||
|
self.spi_mode[1] += 1
|
||
|
if self.spi_mode[1] >= 8:
|
||
|
self.spi_mode[1] = 0
|
||
|
self.spi_mode[2] += 1 # stream number
|
||
|
if self.command_spi[2:5] != ['0','0','0']:self.spi_mode_cov('read-n')
|
||
|
else: self.spi_mode_cov('read')
|
||
|
|
||
|
if data == '':
|
||
|
return 0
|
||
|
return int(data[bit_num],2)
|
||
|
|
||
|
|
||
|
def write_exp_out_wb(self,data):
|
||
|
output = self.expeceted_output['wishbone']
|
||
|
output['ack']['val'] = 1
|
||
|
output['_valid_cycle']['val'] = 1
|
||
|
output['data']['val'] = BinaryValue(value = data,n_bits = 32,bigEndian=False)
|
||
|
self.exp_out_wb.append(output)
|
||
|
|
||
|
"""write register through wishbone """
|
||
|
async def _write_reg_wb(self,address,data,select,clk):
|
||
|
old_data = self._read_reg_wb(address)
|
||
|
self.write_exp_out_wb(old_data)
|
||
|
|
||
|
if address == 0x2600000c: # mgmt_gpio_data is a special case as it got written completely at the end of the serial writing
|
||
|
for i in range(6):
|
||
|
await RisingEdge(self.hk_hdl._id(clk,False))
|
||
|
|
||
|
for i in range(3):
|
||
|
await RisingEdge(self.hk_hdl._id(clk,False))
|
||
|
|
||
|
for i,sel in enumerate(select.binstr):
|
||
|
temp_addr = BinaryValue(value=(address.value +i))
|
||
|
if sel is '1':
|
||
|
is_valid, keys=self._valid_address(temp_addr)
|
||
|
if is_valid:
|
||
|
self.reg_cov(keys[0],keys[1],is_SPI=False,is_read=False)
|
||
|
cocotb.log.debug(f'[HK_models][_valid_address] writing {data[(8*((4-i-1))):8*(4-i)-1]} to memory:{keys[0]} field: {keys[1]}')
|
||
|
temp_data = data[(8*((4-i-1))):8*(4-i)-1]
|
||
|
self._write_fields(keys,temp_data)
|
||
|
cocotb.log.debug(f" [HK_models][_valid_address] address {hex(address)} used for access housekeeping memory success")
|
||
|
if address != 0x2600000c:
|
||
|
for i in range(2):
|
||
|
await RisingEdge(self.hk_hdl._id(clk,False))
|
||
|
|
||
|
def _write_fields(self,keys,data):
|
||
|
for field in self.reg_model[keys[0]][keys[1]]:
|
||
|
if field[4] == "RW":
|
||
|
shift = field[2]
|
||
|
size = field[3]
|
||
|
cocotb.log.debug(f'[HK_models][_write_fields] before update field : {field[1]} data = {bin(field[6])} with data {data[shift:shift+size-1]} ')
|
||
|
field[6] = data[8-shift-size:8-shift-1]
|
||
|
cocotb.log.debug(f'[HK_models][_write_fields] after update field : {field[1]} data = {bin(field[6])} with data {data[shift:shift+size-1]} ')
|
||
|
"""read register value using keys return size binary value"""
|
||
|
def _read_reg_keys(self,keys:list):
|
||
|
size =0
|
||
|
data = ""
|
||
|
for field in self.reg_model[keys[0]][keys[1]]:
|
||
|
size += field[3]
|
||
|
data += bin(field[6])[2:].zfill(size)
|
||
|
return data
|
||
|
|
||
|
|
||
|
"""read register through wishbone """
|
||
|
def _read_reg_wb(self,address):
|
||
|
total_size = 32
|
||
|
data_string ="0"
|
||
|
data_out = list(bin(0)[2:].zfill(total_size))
|
||
|
for i in range(4):
|
||
|
temp_addr = BinaryValue(value=(address.value +i))
|
||
|
is_valid, keys=self._valid_address(temp_addr)
|
||
|
if is_valid:
|
||
|
self.reg_cov(keys[0],keys[1],is_SPI=False)
|
||
|
for field in self.reg_model[keys[0]][keys[1]]:
|
||
|
shift = field[2]
|
||
|
size = field[3]
|
||
|
first_index = ((8 *(4-i))) - shift-size
|
||
|
second_index = ((8 *(4-i))) - shift
|
||
|
data = bin(field[6])[2:].zfill(size)
|
||
|
cocotb.log.debug(f"[HK_models][_read_reg] memory:{keys[0]} shift:{keys[1]} field:{field[0]} data:{data} ")
|
||
|
for k,j in enumerate(range(first_index, second_index, 1)):
|
||
|
data_out[j] = data[k]
|
||
|
data_string = "".join(data_out)
|
||
|
cocotb.log.debug(f"[HK_models][_read_reg] register {hex(address)} has value {hex(int(data_string,2))} ")
|
||
|
return int(data_string,2)
|
||
|
|
||
|
"""return false if the address isn't exist inside housekeeping if the address exist return true and the key of address in JSON file"""
|
||
|
def _valid_address(self,address):
|
||
|
size = max(len(address),32)
|
||
|
address = address.binstr.zfill(size)
|
||
|
for key,memory in self.reg_model.items():
|
||
|
if fnmatch.fnmatch(key, "_*") :
|
||
|
continue
|
||
|
# remove the first 2 element 0b and
|
||
|
base_addr= bin(memory["base_addr"][1])[2:].zfill(size)
|
||
|
cocotb.log.debug(f'[HK_models][_valid_address] base address[:13]={hex(int(base_addr[size-32:size-13],2))} target address[:13]={hex(int(address[size-32:size-13],2))} shift={"{0:#0{1}x}".format(int(address[size-12:],2),4)}')
|
||
|
if base_addr[size-32:size-13] == address[size-32:size-13]:
|
||
|
if "{0:#0{1}x}".format(int(address[size-12:],2),4) in memory:
|
||
|
cocotb.log.debug(f'[HK_models][_valid_address] base address={hex(int(base_addr,2))} key {key}')
|
||
|
return True, [key,"{0:#0{1}x}".format(int(address[size-12:],2),4)]
|
||
|
cocotb.log.warning(f"[HK_models][_valid_address] address {hex(int(address,2))} used for access housekeeping memory isn't valid")
|
||
|
return False, None
|
||
|
|
||
|
"""return false if the address isn't exist inside housekeeping if the address exist return true and the key of address in JSON file"""
|
||
|
def _valid_address_spi(self,address):
|
||
|
for key,memory in self.reg_model.items():
|
||
|
if fnmatch.fnmatch(key, "_*") :
|
||
|
continue
|
||
|
for key2,reg in memory.items():
|
||
|
if key2 == "base_addr" :
|
||
|
continue
|
||
|
for field in reg:
|
||
|
if field[7] == address:
|
||
|
return True,[key,key2]
|
||
|
cocotb.log.warning(f"[HK_models][_valid_address_spi] address {hex(address)} for SPI housekeeping isn't valid")
|
||
|
return False, None
|
||
|
|
||
|
|
||
|
######################## coverage ############################
|
||
|
def regs_full_list(self):
|
||
|
bins = list()
|
||
|
labels = list()
|
||
|
for key,mem in self.reg_model.items():
|
||
|
if fnmatch.fnmatch(key, "_*"):
|
||
|
continue
|
||
|
for key2,fields in mem.items():
|
||
|
if key2=='base_addr':
|
||
|
continue
|
||
|
bins.append((key,key2))
|
||
|
labels.append((self.reg_model[key][key2][0][0]))
|
||
|
self.reg_bins = bins
|
||
|
self.reg_labels = labels
|
||
|
def intial_cov(self):
|
||
|
for i in [True,False]:
|
||
|
for j in [True,False]:
|
||
|
self.reg_cov(0,0,i,j)
|
||
|
self.spi_mode_cov(0)
|
||
|
|
||
|
def reg_cov(self,key1,key2,is_SPI=True,is_read=True):
|
||
|
s=''
|
||
|
if is_SPI:
|
||
|
s = "spi."
|
||
|
else:
|
||
|
s="wishbone."
|
||
|
if is_read:
|
||
|
s +="read"
|
||
|
else:
|
||
|
s+= "write"
|
||
|
@CoverPoint(f"top.caravel.housekeeping.regs.{s}",
|
||
|
xf = lambda key1,key2:(key1,key2),
|
||
|
bins = self.reg_bins,
|
||
|
bins_labels=self.reg_labels)
|
||
|
def cov(key1,key2):
|
||
|
pass
|
||
|
cov(key1,key2)
|
||
|
return (key1,key2)
|
||
|
|
||
|
|
||
|
def spi_mode_cov(self,mode):
|
||
|
@CoverPoint(f"top.caravel.housekeeping.spi.modes",
|
||
|
bins = ['noOP','write','read','read/write','write-n','read-n','read/write-n','Pass-m','pass-u'],
|
||
|
bins_labels=['No operation','Write in streaming mode','Read in streaming mode','Simultaneous Read/Write in streaming mode','Write in n-byte mode','Read in n-byte mode','Simultaneous Read/Write in n-byte mode','Pass-through (management) Read/Write streaming mode','Pass-through (user) Read/Write in streaming mode' ])
|
||
|
def cov(mode):
|
||
|
pass
|
||
|
cov(mode)
|
||
|
return mode
|