LOKI JTAG - stfc-aeg/loki GitHub Wiki
LOKI JTAG
This is a python library that facilitates the control of JTAG devices. It is baked into LOKI devices; the source can be found here, installed as python-axi-jtag.
Table of Contents:
- Creating a JTAG Device
- Device Configuration File
- Creating a JTAG Chain
- Operating on a Device
- Classes
- Example Usage
[!NOTE] A note on firmware design and device tree
This software is a Python uio-based driver, which requires that the JTAG interface being used is registered as a UIO device and is of type
debug_bridgefrom Xilinx in From AXI to JTAG mode (selected when IP is added to the block design).For the IP to be added to the device tree and accessible, you must also add a device tree modification like this:
&debug_bridge_0 { compatible = "generic-uio"; };The full guide on how to perform this modification process is here.
Creating a JTAG Device
To create an instance of a JTAG device to interact with, simply instantiate an instance of the GenericDevice class. This class' constructor takes a path to the corresponding configuration file (more on this below).
This can also be used as a base class to expand the functionality for more device-specific actions.
Device Configuration File
A device configuration file is a JSON file that stores information about a device such as:
- The device's instruction register length
- Device instructions
- Device registers
- Register addresses
- Fields inside the device's registers
- Subfields in the registers' fields
- The device's boundary scan register length
Creating a Device Configuration File
Top Level
| Key | Value Type | Description | Optional |
|---|---|---|---|
| ir_length | Integer | The length of the device's instruction register | No |
| instructions | JSON | JSON containing all the instructions for a device (see below) | Yes |
| registers | List of JSON | A list of JSON containing the device's register information (see Register Configuration) | Yes |
| bsr_length | Integer | The length of the boundary scan register | Yes |
The instructions should be structured as "instruction_name": "instruction_code". Where instruction_code is a binary string (see below for an example).
[!NOTE] Please ensure each instruction has an associated register (that has the same name) with at least the basic information, e.g name, address and length.
[!NOTE] If a register has no associated instruction, the address will be used as the register's instruction
Register Configuration
| Key | Value Type | Description | Optional |
|---|---|---|---|
| name | String | The name of the register | No |
| address | Integer | The memory address of the register | No |
| reg_length | Integer | The total length of the register in bits | No |
| fields | List of JSON | JSON containing the register's field information (see Field Configuration) | No |
Field Configuration
| Key | Value Type | Description | Optional |
|---|---|---|---|
| name | String | The name of the field | No |
| bit_length | Integer | The length of the field in bits | No |
| reversed | Boolean | Defines if the field should be reversed, i.e. 110001 becomes 100011 | Yes |
| subfields | List of JSON | Defines fields inside fields | Yes |
[!NOTE] If you do not know any of this information, please do not include the key in the configuration file (if the key is optional). Some features may not be available until this information is provided
Example Device Configuration File
{
"ir_length": 5,
"instructions": {
"IDCODE": "110110"
},
"registers": [
{
"name": "IDCODE",
"address": 0,
"reg_length": 0,
"fields": [
{
"name": "field_name",
"bit_length": 0,
"reversed": false
},
{
"name": "field_name",
"bit_length": 5,
"subfields": [
{
"name": "field_name",
"bit_length": 1,
"reversed": false
},
{
"name": "field_name",
"bit_length": 10,
"reversed": false
}
]
}
]
}
],
"bsr_length": 0
}
Creating a JTAG Chain
To create a JTAG chain, firstly ensure that all of your devices have been instantiated. Instantiate a new instance of the JTAGChain class by passing a list containing your devices to the constructor, and the path to the UIO device you wish to use.
[!NOTE] Ensure that the devices are in the same position in the list as they are in the physical JTAG chain
Operating on a Device
How it Works
Calling the shift_ir() function on a specific device will shift the given instruction into the device's instruction register, while putting all other devices in the chain into bypass.
Shifting Instructions
To shift an instruction into a device's instruction register, there are two options.
The first is to have the instructions defined in the device's configuration file (see Creating a Device Configuration File). For example: device.shift_ir("IDCODE")
The second is to pass in a string of bits. This is useful if it is an instruction that is being tested quickly. For example: device.shift_ir("110001")
Data Register Interaction
To read the data in a register, simply call the read_reg() function, or call the read_modify_update() function if the register is destructive, (once the instruction for that register has been loaded into the instruction register) with the name of the register you would like to read as an argument, this will automatically shift 0s through the data register to shift out all of the data. The data returned will also be trimmed to account for the other devices being in bypass (if applicable).
Manually Shifting Bits
Sometimes, you may just want to manually shift in bits to both the data register and the instruction register (see here for information on shifting instructions into the instruction register)
You can manually shift bits into the data register by calling the shift_dr() function on the device class. This works in a similar way to the shift_ir() function, where you shift in a string of bits.
When shifting out data from the data register after shifting in an instruction as a string of bits, you must provide the number of bits you wish to shift out. This is because the length of bits to shift out is usually determined by the register that is associated with the instruction (defined in the device configuration file).
[!NOTE] The argument passed into the
shift_dr()function must be the same length as the data register of the device you are interacting with
Boundary Scan
In order for the boundary scan operation to work, ensure:
- A boundary scan instruction is defined in the device+ configuration file (e.g "SAMPLE", "PRELOAD", "EXTEST")
- The boundary scan register length is defined in the device configuration file
To do a boundary scan, call the boundary_scan function on the device class and pass in the bits to shift through the boundary scan register, as well as the instruction name from the configuration file as an argument.
Interacting With Registers
The update_reg() function allows you to change the value stored in the specified register by passing in the name of the register and the
The read_reg() function returns the current value of the register specified.
[!NOTE] The name of the register must match the corresponding instruction to read it
Reading Register Fields
The get_reg_field_value() functions allows you to read a specific field from a register that has been defined in the device configuration file.
Classes
The list below is comprised of all functions that are intended to be used by users
JTAGChain
read_all_id_codes() - Returns the ID code for all the devices on the chain
GenericDevice
shift_ir(instruction: str) - Shifts instruction into device's instruction register, puts all other devices in bypass
shift_dr(value: Optional[str]) - Shifts bits into data register to either read out data or write data
get_all_registers() - Returns all registers defined on the device
get_register(name: str) - Returns the register with the name specified in the argument
get_reg_field_value(reg_name: str, field_name: str) - Returns the current value in field specified, inside the register specified
read_reg(reg_name: str) - Returns the current value of the register specified
update_reg(reg_name: str, bits: str) - Updates the register specified with the bits specified
get_bsr_length() - Returns the boundary scan register length in bits
boundary_scan(tdi: str, read_instruction_name: str) - Completes a boundary scan on the device, shifts tdi through the boundary scan register
JTAGReg
get_total_bit_length() - Returns the total length of the register in bits
get_name() - Returns the name of the register
get_address() - Returns the memory address of the register
JTAGField
get_name() - Returns the name of the field
get_bit_length() - Returns the length of the field in bits
get_reversed() - Returns a boolean for if a field is reversed
Example Usage
Reading All ID Codes
device1 = GenericDevice("device1_config.json")
device2 = GenericDevice("device2_config.json")
device3 = GenericDevice("device3_config.json")
chain = JTAGChain([device1, device2, device3])
chain.read_all_id_codes()
Running a Boundary Scan (Read)
device1 = GenericDevice("device1_config.json")
device2 = GenericDevice("device2_config.json")
device3 = GenericDevice("device3_config.json")
chain = JTAGChain([device1, device2, device3])
bits_in = "0" * device2.get_bsr_length()
device2.boundary_scan(bits_in)
Shifting an Instruction From Configuration File
device1 = GenericDevice("device1_config.json")
device2 = GenericDevice("device2_config.json")
device3 = GenericDevice("device3_config.json")
chain = JTAGChain([device1, device2, device3])
device2.shift_ir("IDCODE")
device2.shift_dr()
Shifting an Instruction Not From Configuration File
device1 = GenericDevice("device1_config.json")
device2 = GenericDevice("device2_config.json")
device3 = GenericDevice("device3_config.json")
chain = JTAGChain([device1, device2, device3])
device2.shift_ir("10001110")
device2.shift_dr()
Reading a Register
device1 = GenericDevice("device1_config.json")
device2 = GenericDevice("device2_config.json")
device3 = GenericDevice("device3_config.json")
chain = JTAGChain([device1, device2, device3])
device2.shift_ir("IDCODE")
device2.read_reg("IDCODE")
Reading a Destructive Register
device1 = GenericDevice("device1_config.json")
device2 = GenericDevice("device2_config.json")
device3 = GenericDevice("device3_config.json")
chain = JTAGChain([device1, device2, device3])
device2.shift_ir("IDCODE")
device2.read_modify_write_reg("IDCODE")