# Manuel Eggimann <meggimann@iis.ee.ethz.ch>
#
# Copyright (C) 2020-2022 ETH Zürich
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from enum import Enum
from typing import List
import bitstring
from dumpling.Common.ElfParser import ElfParser
from dumpling.Common.VectorBuilder import VectorBuilder
from dumpling.Drivers.JTAG import JTAGTap, JTAGDriver, JTAGRegister
from bitstring import BitArray
bitstring.lsb0 = True #Enables the experimental mode to index LSB with 0 instead of the MSB (see thread https://github.com/scott-griffiths/bitstring/issues/156)
[docs]class PULPJtagTap(JTAGTap):
"""
See Also:
Check the adv_dbg documentation for details on the protocol used for this JTAGTap
"""
DBG_MODULE_ID = BitArray('0b100000')
[docs] class DBG_OP(Enum):
NOP = '0x0'
WRITE8 = '0x1'
WRITE16 = '0x2'
WRITE32 = '0x3'
WRITE64 = '0x4'
READ8 = '0x5'
READ16 = '0x6'
READ32 = '0x7'
READ64 = '0x8'
INT_REG_WRITE = '0x9'
INT_REG_SELECT = '0xD'
[docs] def to_bits(self):
return BitArray(self.value)
def __init__(self, driver: JTAGDriver):
super().__init__("PULP JTAG module", 5, driver)
self.reg_idcode = self._add_reg(JTAGRegister("IDCODE", '00010', 32))
self.reg_soc_axireg = self._add_reg(JTAGRegister("SoC AXIREG", "00100", 0)) #The size of the axi reg depends on the burst setup
self.reg_soc_bbmuxreg = self._add_reg(JTAGRegister('SoC BBMUXREG', "00101", 21))
self.reg_soc_confreg = self._add_reg(JTAGRegister('SoC CONFREG', '00110', 9))
self.reg_soc_testmodereg = self._add_reg(JTAGRegister('SoC TESTMODEREG', '01000', 4))
self.reg_soc_bistreg = self._add_reg(JTAGRegister('SoC BISTREG', '01001', 20))
#self.reg_clk_byp = self._add_reg(JTAGRegister('CLK_BYP', '00111', ))
[docs] def set_config_reg(self, soc_jtag_reg_value: BitArray, sel_fll_clk: bool, comment=""):
"""
Generates stimuli to program the config register.
Args:
soc_jtag_reg_value (BitArray): An 8-bit value represented as a BitArray of length 8 with character 0,1
sel_fll_clk (bool): True if the internal FLL should be used for clock generation, False if the external reference clock should be directly used for clock gen.
comment (str): A string with which the first vector of the returned stimuli vectors will be annotated as a comment. If None, a default comment will be used.
Returns:
The generated vectors. The format of those vectors depends on the actual implementation of the VectorWriter instance used
"""
id_value = ('1' if sel_fll_clk else '0') + soc_jtag_reg_value.bin
comment += "/Set JTAG Config reg to {}, internal FLL {}".format(soc_jtag_reg_value.hex, 'enabled' if sel_fll_clk else 'disabled')
return self.driver.write_reg(self, self.reg_soc_confreg, id_value, comment=comment)
[docs] def verify_config_reg(self, soc_jtag_reg_value: BitArray, sel_fll_clk: bool, comment=""):
comment += "/Verify JTAG Config reg is {} and FLL is {}".format(soc_jtag_reg_value.hex, 'enabled' if sel_fll_clk else 'disabled')
id_value = ('1' if sel_fll_clk else '0') + soc_jtag_reg_value.bin
return self.driver.read_reg(self, self.reg_soc_confreg, 9, id_value)
[docs] def init_pulp_tap(self):
return self.driver.jtag_set_ir(self, self.reg_soc_axireg.IR_value, comment="Init Pulp Tap")
[docs] def module_select(self, comment=""):
return self.driver.jtag_set_dr(self, PULPJtagTap.DBG_MODULE_ID.bin, comment=comment)
[docs] def setup_burst(self, cmd: DBG_OP, start_addr:BitArray, nwords:int, comment=""):
comment += "/Setup AXI4 adv dbg burst @{} for {} words".format(start_addr, nwords)
dr_value = BitArray(53)
dr_value[48:52] = cmd.to_bits()
dr_value[16:48] = start_addr
dr_value[0:16] = BitArray(uint=nwords, length=16)
return self.driver.jtag_set_dr(self, dr_value.bin, comment=comment)
[docs] def write_burst(self, data:List[BitArray], comment=""):
comment += "/Write burst data for {} words".format(len(data))
burst = '1' #Start Bit (p.20 adv dbg docu)
for idx, word in enumerate(data):
burst += word.bin[::-1] #Actual Data to write LSB first
burst += 32*'1' #Dummy CRC (we do not check the match bit of the write transfer so we don't have to send a valid CRC code
burst += '0'
burst = burst[::-1] #set_dr is LSB first so we have to reverse the order
return self.driver.jtag_set_dr(self, burst, comment=comment)
[docs] def read_burst_no_loop(self, expected_data:List[BitArray], wait_cycles=3, comment=""):
comment += "/Read burst data for {} words".format(len(expected_data))
vectors = self.driver.jtag_goto_shift_dr(comment)
# Shift once for each tap before the jtag pulp
for tap in self.driver.chain:
if tap != self:
vectors += self.driver.jtag_shift('0', 'X', noexit=True)
else:
break
burst = ''
for idx, word in enumerate(expected_data):
burst += word.bin[::-1] # Actual Data to read LSB first
burst += 32 * 'X' # Ignore the CRC
# Shift DR until we see a status=1 bit
# In this matched_loop-free version of read_burst we expect the user to tell us how many cycles the pulp_tap needs for the read ready bit to be raised (wait_cycles argument)
wait_status_bits = '0' * wait_cycles + '1'
vectors += self.driver.jtag_shift('0' * (wait_cycles + 1), wait_status_bits, comment="Shift until status bit is 1", noexit=True)
# Now we shift the actual data
vectors += self.driver.jtag_shift(len(burst) * '0',
expected_chain=burst) # We leave the shift dr state before we shifted the bypass bits of the taps that follow the pulp jtag tap. This is not
# an issue
return vectors
[docs] def read_burst(self, expected_data:List[BitArray], comment="", retries=1):
comment += "/Read burst data for {} words".format(len(expected_data))
vectors = self.driver.jtag_goto_shift_dr(comment)
# Shift once for each tap before the jtag pulp
for tap in self.driver.chain:
if tap != self:
vectors += self.driver.jtag_shift('0', 'X', noexit=True)
else:
break
burst = ''
for idx, word in enumerate(expected_data):
burst += word.bin[::-1] # Actual Data to read LSB first
burst += 32 * 'X' # Ignore the CRC
#Shift DR until we see a status=1 bit
condition_vectors = self.driver.jtag_shift('0', '1', comment="Shift until status bit is 1", noexit=True)
#Pad to multiple of 8 vectors
condition_vectors = VectorBuilder.pad_vectors(condition_vectors, self.driver.jtag_idle_vector())
idle_vectors = self.driver.jtag_idle_vectors(8)
vectors += self.driver.vector_builder.matched_loop(condition_vectors, idle_vectors, retries)
vectors += self.driver.jtag_idle_vectors(8) # Make sure there are at least 8 normal vectors before the next matched loop by insertion idle instructions
vectors += self.driver.jtag_shift(len(burst)*'0', expected_chain=burst) #We leave the shift dr state before we shifted the bypass bits of the taps that follow the pulp jtag tap. This is not
# an issue
return vectors
[docs] def write32(self, start_addr:BitArray, data:List[BitArray], comment=""):
nwords = len(data)
comment += "/Write32 burst @{} for {} bytes".format(start_addr, nwords)
#Module Selet Command (p.15 of ADV DBG Doc)
vectors = self.module_select()
#Setup Burst (p.17 of ADV DBG Doc)
vectors += self.setup_burst(PULPJtagTap.DBG_OP.WRITE32, start_addr, nwords, comment=comment)
#Burst the data
vectors += self.write_burst(data)
return vectors
[docs] def read32(self, start_addr:BitArray, expected_data:List[BitArray], retries=1, comment=""):
nwords = len(expected_data)
comment += "/Read32 burst @{} for {} bytes".format(start_addr, nwords)
#Module Selet Command (p.15 of ADV DBG Doc)
vectors = self.module_select()
#Setup Burst (p.17 of ADV DBG Doc)
vectors += self.setup_burst(PULPJtagTap.DBG_OP.READ32, start_addr, nwords, comment=comment)
#Burst the data
vectors += self.read_burst(expected_data, retries=retries)
return vectors
[docs] def read32_no_loop(self, start_addr:BitArray, expected_data:List[BitArray], wait_cycles=3, comment=""):
nwords = len(expected_data)
comment += "/Read32 burst @{} for {} bytes".format(start_addr, nwords)
#Module Selet Command (p.15 of ADV DBG Doc)
vectors = self.module_select()
#Setup Burst (p.17 of ADV DBG Doc)
vectors += self.setup_burst(PULPJtagTap.DBG_OP.READ32, start_addr, nwords, comment=comment)
#Burst the data
vectors += self.read_burst_no_loop(expected_data, wait_cycles=wait_cycles)
return vectors
[docs] def loadL2(self, elf_binary:str, comment=""):
stim_generator = ElfParser(verbose=False)
stim_generator.add_binary(elf_binary)
stimuli = stim_generator.parse_binaries(4)
vectors = []
#Split the stimuli into bursts
burst_data = []
start_addr = None
prev_addr = None
for addr, word in sorted(stimuli.items()):
if not start_addr:
start_addr = int(addr)
#If there is a gap in the data to load or the burst would end up longer than 256 words, start a new burst
if prev_addr and (prev_addr+4 != int(addr) or len(burst_data)>=256):
vectors += self.write32(BitArray(uint=start_addr, length=32), burst_data)
start_addr = int(addr)
burst_data = []
prev_addr = int(addr)
burst_data.append(BitArray(uint=int(word), length=32))
#Create the final burst
vectors += self.write32(BitArray(uint=start_addr, length=32), burst_data)
return vectors
[docs] def verifyL2(self, elf_binary:str, retries=1, comment=""):
stim_generator = ElfParser(verbose=False)
stim_generator.add_binary(elf_binary)
stimuli = stim_generator.parse_binaries(4)
vectors = []
# Split the stimuli into bursts
burst_data = []
start_addr = None
prev_addr = None
for addr, word in sorted(stimuli.items()):
if not start_addr:
start_addr = int(addr)
# If there is a gap in the data to load or the burst would end up longer than 256 words, start a new burst
if prev_addr and (prev_addr + 4 != int(addr) or len(burst_data) >= 256):
vectors += self.read32(BitArray(uint=start_addr, length=32), burst_data)
start_addr = int(addr)
burst_data = []
prev_addr = int(addr)
burst_data.append(BitArray(uint=int(word), length=32))
#Create the final burst
vectors += self.read32(BitArray(uint=start_addr, length=32), burst_data)
return vectors
[docs] def verifyL2_no_loop(self, elf_binary: str, comment=""):
stim_generator = ElfParser(verbose=False)
stim_generator.add_binary(elf_binary)
stimuli = stim_generator.parse_binaries(4)
vectors = []
# Split the stimuli into bursts
burst_data = []
start_addr = None
prev_addr = None
for addr, word in sorted(stimuli.items()):
if not start_addr:
start_addr = int(addr)
# If there is a gap in the data to load or the burst would end up longer than 256 words, start a new burst
if prev_addr and (prev_addr + 4 != int(addr) or len(burst_data) >= 256):
vectors += self.read32_no_loop(BitArray(uint=start_addr, length=32), burst_data)
start_addr = int(addr)
burst_data = []
prev_addr = int(addr)
burst_data.append(BitArray(uint=int(word), length=32))
# Create the final burst
vectors += self.read32_no_loop(BitArray(uint=start_addr, length=32), burst_data)
return vectors
# def wait_for_end_of_computation(self, expected_return_code:int, retries=10, idle_cycles=100):
# expected_eoc_value = BitArray(int=expected_return_code, length=32)
# expected_eoc_value[31] = 1
# condition_vectors = self.read32(BitArray('0x1a1040a0'), [expected_eoc_value], retries=5, comment="Poll end of computation expecting return code {}.".format(expected_return_code))
# # Pad the condition vectors to be a multiple of 8
# condition_vectors += self.driver.jtag_idle_vectors(count=8 - len(condition_vectors) % 8)
# idle_vectors = self.driver.jtag_idle_vectors(count=idle_cycles)
# idle_vectors += self.driver.jtag_idle_vectors(count=8 - len(idle_vectors) % 8)
# vectors = self.driver.vector_builder.matched_loop(condition_vectors, idle_vectors, retries=retries)
# return vectors