Skip to content

Commit 3cf2184

Browse files
mchack-worksecworks
andcommitted
hw/tool: UDI/UDS storage
Describe how the UDI and UDS are actually stored in the FPGA, how they are accessed, and how they are initialled by the patch_uds_udi.py script. Co-authored-by: Joachim Strömbergson <[email protected]>
1 parent 1c90b1a commit 3cf2184

File tree

3 files changed

+87
-32
lines changed

3 files changed

+87
-32
lines changed

hw/application_fpga/core/tk1/README.md

+21-3
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,27 @@ secret for any secrets it needs to perform its intended use case.
110110
ADDR_UDI_LAST: 0x31
111111
```
112112

113-
These registers provide read access to the 64-bit unique device
114-
identity. The UDI is stored as ROM within the FPGA configuration. The
115-
registers can't be written to.
113+
These read-only registers provide access to the 64-bit Unique Device
114+
Identity (UDI).
115+
116+
The two UDI words are stored using 32 named SB\_LUT4 FPGA multiplexer
117+
(MUX) instances, identified in the source code as "udi\_rom\_idx". One
118+
instance for each bit in core read_data output bus.
119+
120+
Each SB\_LUT4 MUX is able to store 16 bits of data, in total 512 bits.
121+
But since the UDI is 64 bits, we only use the two LSBs in each MUX.
122+
Note that only the LSB address of the SB_LUT4 instances are connected
123+
to the CPU address. This means that only the two LSBs in each MUX can
124+
be addressed.
125+
126+
During build of the FPGA design, the UDI is set to a known bit
127+
pattern, which means that the SB_LUT4 instantiations are initialized
128+
to a fixed bit pattern.
129+
130+
The tool 'patch\_uds\_udi.py' is used to replace the fixed bit pattern
131+
with a unique bit pattern before generating the per device unique FPGA
132+
bitstream. This allows us to generate these device unique FPGA
133+
bitstreams without having to do a full FPGA build.
116134

117135

118136
### RAM memory protecion

hw/application_fpga/core/uds/README.md

+35-7
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,21 @@ Unique Device Secret core
55
## Introduction
66

77
This core store and protect the Unique Device Secret (UDS) asset. The
8-
UDS can be accessed as eight separate 32-bit words. The words can be
9-
accessed in any order, but a given word can only be accessed once
10-
between reset cycles. The words can only be accessed as long as the
11-
fw_app_mode input is low, implying that the CPU is executing the FW.
8+
UDS can be accessed as eight separate 32-bit words. The words can only
9+
be accessed as long as the fw_app_mode input is low, implying that the
10+
CPU is executing the FW.
1211

13-
Each UDS words has a companion read bit that is set when the word is
14-
accessed. This means that the even if the chip select (cs) control
12+
The UDS words can be accessed in any order, but a given word can only
13+
be accessed once between reset cycles. This read once functionality is
14+
implemented with a companion read bit for each word. The read bit is
15+
set when the word is first accessed. The read bit controls if the real
16+
UDS word is returned or not.
17+
18+
This means that the even if the chip select (cs) control
1519
input is forced high, the content will become all zero when the read
1620
bit has been set after one cycle.
1721

22+
1823
## API
1924
There are eight addresses in the API. These are defined by the
2025
two values ADDR_UDS_FIRST and ADDR_UDS_LAST:
@@ -31,4 +36,27 @@ Any access to another address will be ignored by the core.
3136

3237
## Implementation
3338

34-
The UDS words are implemented using discrete registers.
39+
These read-only registers provide read access to the 256-bit UDS.
40+
41+
The eight UDS words are stored using 32 named SB\_LUT4 FPGA
42+
multiplexer (MUX) instances, identified in the source code as
43+
"uds\_rom\_idx". One instance for each bit in the core read\_data
44+
output bus.
45+
46+
During build of the FPGA design, the UDS is set to a known bit
47+
pattern, which means that the SB\_LUT4 instantiations are initialized
48+
to a fixed bit pattern.
49+
50+
The tool 'patch\_uds\_udi.py' is used to replace the fixed bit pattern
51+
with a unique bit pattern before generating the per device unique FPGA
52+
bitstream. This allows us to generate these device unique FPGA
53+
bitstreams without haveing to do a full FPGA build.
54+
55+
Each SB\_LUT4 MUX is able to store 16 bits of data, in total 512 bits.
56+
But since the UDS is 256 bits, we only use the eight LSBs in each MUX.
57+
58+
The eighth MSBs in each MUX will be initialized to zero. The read
59+
access bit (se description above) for a given word is used as the
60+
highest address bit to the MUXes. This forces any subsequent accesses
61+
to a UDS word to read from the MUX MSBs, not the LSBs where the UDS is
62+
stored.

hw/application_fpga/tools/patch_uds_udi.py

+31-22
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,40 @@
55
# Written by Myrtle Shah <[email protected]>
66
# SPDX-License-Identifier: GPL-2.0-only
77
#
8-
# patch_uds_udi.py
9-
# --------------
10-
# Python program that patches the UDS and UDI implemented using
11-
# named LUT4 instances to have unique initial values, not the generic
12-
# values used during synthesis, p&r and mapping. This allows us to
13-
# generate device unique bitstreams without running the complete flow.
8+
# Script to patch in a Unique Device Secret (UDS) and a Unique Device
9+
# Identifier (UDI) from files into a bitstream.
1410
#
15-
# Both the UDI and UDS are using bit indexing from 32 LUTs for each
16-
# word, i.e., the first word consists of bit 0 from each 32 LUTs and
17-
# so on.
11+
# It's supposed to be run like this:
1812
#
19-
# The size requirements for the UDI and UDS are specified as 1 bit (8
20-
# bytes of data) and 3 bits (32 bytes of data), respectively. The UDI
21-
# does not occupy the entire LUT4 instance, and to conserve resources,
22-
# the pattern of the UDI is repeated over the unused portion of the
23-
# LUT4 instance. This eliminates the need to drive the three MSB pins
24-
# while still achieving the correct output.
13+
# nextpnr-ice40 --up5k --package sg48 --ignore-loops \
14+
# --json application_fpga_par.json --run patch_uds_udi.py
2515
#
26-
# In the case of UDS, a read-enable signal is present, and the most
27-
# significant bit serves as the read-enable input. This requires the
28-
# lower half of initialization bits to be forced to zero, ensuring
29-
# that the memory outputs zero when the read-enable signal is
30-
# inactive.
16+
# with this environment:
3117
#
18+
# - UDS_HEX: path to the UDS file, typically the path to
19+
# ../data/uds.hex
20+
# - UDI_HEX: path to the UDI file, typically the path to ../data/udi.hex
21+
# - OUT_ASC: path to the ASC output that is then used by icebram and icepack.
3222
#
33-
#=======================================================================
23+
# The script changes the UDS and UDI that are stored in named 4-bit
24+
# LUT instances in the JSON file so we can generate device
25+
# unique bitstreams without running the complete flow just to change
26+
# UDS and UDI. Then we can just run the final bitstream generation
27+
# from the ASC file.
28+
#
29+
# We represent our UDI and UDS values as a number of 32 bit words:
30+
#
31+
# - UDI: 2 words.
32+
# - UDS: 8 words.
33+
#
34+
# We reserve 32 named 4-bit LUTs *each* to store the data: UDS in
35+
# "uds_rom_idx" and UDI in "udi_rom_idx".
36+
#
37+
# The script repeats the value in the LUTs so we don't have to care
38+
# about the value of the unused address bits.
39+
#
40+
# See documentation in their implementation in ../core/uds/README.md
41+
# and ../core/tk1/README.md
3442

3543
import os
3644

@@ -49,7 +57,8 @@ def rewrite_lut(lut, idx, data, has_re=False):
4957
new_init = 0
5058
for i, word in enumerate(data):
5159
if (word >> idx) & 0x1:
52-
# repeat so inputs above address have a don't care value
60+
# repeat so we don't have to care about inputs above
61+
# address
5362
repeat = (16 // len(data))
5463
for k in range(repeat):
5564
# UDS also has a read enable

0 commit comments

Comments
 (0)