Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The FPGA architecture runs at 100 MHz and embeds many peripherals:
- 2 x UART,
- I2C (master),
- ISO7816 (master),
- ISO14443-A (NFC daughterboard)
- SPI (master),
- Power supply controllers for each evaluation socket,
- 4 x Delay and pulse generators with 10 ns resolution
Expand Down
258 changes: 257 additions & 1 deletion api/scaffold/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1010,7 +1010,7 @@ def trigger_long(self):

@trigger_long.setter
def trigger_long(self, value):
# We want until transmission is ready to avoid triggering on a pending
# We wait until transmission is ready to avoid triggering on a pending
# one.
self.reg_config.set_bit(
self.__REG_CONFIG_TRIGGER_LONG,
Expand Down Expand Up @@ -1605,6 +1605,244 @@ def glitch_count(self, value):
self.reg_count.set(value)


class ISO14443Trigger(int, Enum):
"""Triggering modes for ISO-14443 peripheral."""
NONE = 0,
START = 1,
END = 2,
RX = 4,
START_LONG = 9
END_LONG = 10


class ISO14443(Module):
"""
ISO 14443-A module of Scaffold.
"""

__REG_STATUS_CONTROL_BIT_BUSY = 0
__REG_STATUS_CONTROL_BIT_START = 0
__REG_STATUS_CONTROL_BIT_POWER_ON = 1
__REG_STATUS_CONTROL_BIT_POWER_OFF = 2
__REG_CONFIG_BIT_USE_SYNC = 6
__REG_CONFIG_BIT_POLARITY = 7
__REG_CONFIG_BIT_TRIGGER_TX_START_EN = 0
__REG_CONFIG_BIT_TRIGGER_TX_END_EN = 1
__REG_CONFIG_BIT_TRIGGER_RX_START_EN = 2
__REG_CONFIG_BIT_TRIGGER_LONG_EN = 3

def __init__(self, parent):
super().__init__(parent, "/iso14443")
# Declare the signals
self.add_signals("tx", "trigger", "rx", "clock_13_56", "tearing")
# Declare the registers
self.__addr_base = base = 0x0b00
self.add_register("status_control", "rwv", base)
self.add_register("config", "w", base + 1, reset=0)
self.add_register("data", "rwv", base + 2)
self.add_register("timeout", "w", base + 3, wideness=3, reset=0x2faf08)
self.add_register("demod_delay", "w", base + 4, wideness=4, reset=0)

def reset(self):
self.reg_config.set(
(1 << self.__REG_CONFIG_BIT_POLARITY)
| (1 << self.__REG_CONFIG_BIT_USE_SYNC))

def power_on(self):
self.reg_status_control.write(1 << self.__REG_STATUS_CONTROL_BIT_POWER_ON)

def power_off(self):
self.reg_status_control.write(1 << self.__REG_STATUS_CONTROL_BIT_POWER_OFF)

def start(self):
self.reg_status_control.write(1 << self.__REG_STATUS_CONTROL_BIT_START)

def transmit_bits(self, bits: bytes):
"""
Transmits a frame.

:param bits: Bits to be transmitted. Does not include start and stop bits. Must
includes parity bits. Each element of this bytes object must be 0 or 1.
"""
patterns = bytearray()
# Symbols "X", "Y" and "Z" for type A according to ISO 14443-2
seq_x = 0b10
seq_y = 0b11
seq_z = 0b01
# Append start bit
patterns.append(seq_z)
previous_bit = 0
# Append symbols for all bits following the miller encoding.
for bit in bits:
assert bit in (0, 1)
if bit == 1:
seq = seq_x
elif previous_bit == 0:
seq = seq_z
else:
seq = seq_y
patterns.append(seq)
previous_bit = bit
# Append end of communication
if previous_bit == 0:
seq = seq_z
else:
seq = seq_y
patterns.append(seq)
patterns.append(seq_y)
# Send patterns to Scaffold board and trigger transmission.
# Flush RX fifo as well.
self.reg_data.write(patterns)
self.start()

def receive_bits(self, bit_size_hint: Optional[int] = None):
"""
Reads all bits received in the reception FIFO.

:param bit_size_hint: If the number of expected bits in the response is known,
it can be indicated as a hint to improve response readout performance. This
number includes start and parity bits. If response has more bits than
expected, it will still be read completely.
"""
# We don't know yet how many bits are available in the FIFO, and we
# want to read them all as fast as possible. Reading the data register
# returns one received bit and other informations that will help us to
# have low latency:
# - bit 1 is 1 if the FIFO is empty,
# - bits 7 to 2 hints how many bytes are still in the FIFO (value gives
# a range in multiple of 64).
# Therefore a good strategy is:
# - Read a first chunk and estimate how many bytes are still in the
# FIFO.
# - If there are other bits in the FIFO, read all the rest with a
# single read command.
# - Discard all bits that are invalid using the fifo emptyness flag.

# Read 255 bits from the FIFO.
# We wait for the reception hardware to say it has finished receiving.
if bit_size_hint is None:
# 255 is the max possible with a single Scaffold bus request.
size = 255
else:
size = bit_size_hint + 1
data = self.reg_data.read(
size,
self.reg_status_control.poll(
mask=(1 << self.__REG_STATUS_CONTROL_BIT_BUSY), value=0
),
)
# Look at last received byte to know if there are more, and how many
# about.
if (data[-1] & 2) == 0:
count = (data[-1] >> 2) * 64 + 63
# Fetch the remaining
data += self.reg_data.read(count)

# Strip invalid bits
count = 0
end = False
for (i, b) in enumerate(data):
if not end:
if b & 2 == 2:
end = True
else:
count = i + 1
else:
assert b & 2 == 2
data = data[:count]
bits = bytes(b & 1 for b in data)
return bits

def receive(self, bit_size_hint: Optional[int] = None) -> bytes:
"""
Reads all bytes received in the reception FIFO.

:param bit_size_hint: If the number of expected bits in the response is known,
it can be indicated as a hint to improve response readout performance. This
number includes start and parity bits. If response has more bits than
expected, it will still be read completely.
"""
# 1 start bit, 9 bits per byte with parity
bits = self.receive_bits(bit_size_hint = bit_size_hint)
if len(bits) == 0:
# No response
return b''
# Response always starts with a 1. The Scaffold hardware peripheral is
# not able to receive something else anyways, so if we have a 0 here we
# have a bug.
assert bits[0] == 1
bits = bits[1:]
result = bytearray()
while len(bits) > 0:
b = 0
p = 1
for i in range(8):
b += bits[i] << i
p ^= bits[i]
assert p == bits[8] # Check parity bit
bits = bits[9:]
result.append(b)
return bytes(result)

def transmit(self, data: bytes):
"""
Transmits a frame.
"""
bits = bytearray()
for byte in data:
parity_bit = 1
for i in range(8):
bit = (byte >> i & 1)
parity_bit ^= bit
bits.append(bit)
bits.append(parity_bit)
self.transmit_bits(bits)

def transmit_short(self, byte: int):
"""Transmits a short frame (7-bits)."""
assert byte < 128
bits = bytes((byte >> i) & 1 for i in range(7))
self.transmit_bits(bits)

@property
def trigger_mode(self) -> ISO14443Trigger:
return ISO14443Trigger(self.reg_config.get() & 0xf)

@trigger_mode.setter
def trigger_mode(self, value: ISO14443Trigger):
self.reg_config.set_mask(value, 0xf)

@property
def timeout(self) -> float:
ticks = self.reg_timeout.get()
return ticks * (2**6) / self.parent.sys_freq

@timeout.setter
def timeout(self, t: float):
if t < 0:
raise ValueError("Timeout cannot be negative")
ticks = round((t * self.parent.sys_freq) / 2**6)
if ticks > 0xffffff:
raise ValueError("Timeout is too long")
if ticks < 1:
raise ValueError("Timeout is too short")
self.reg_timeout.set(ticks)

@property
def demod_delay(self) -> float:
ticks = self.reg_demod_delay.get()
return ticks / self.parent.sys_freq

@demod_delay.setter
def demod_delay(self, t: float):
if t < 0:
raise ValueError("Demodulation delay cannot be negative")
ticks = round(t * self.parent.sys_freq)
if ticks >= 2**28:
raise ValueError("Demodulation delay is too long")
self.reg_demod_delay.set(ticks)


class IOMode(Enum):
AUTO = 0
OPEN_DRAIN = 1
Expand Down Expand Up @@ -2098,6 +2336,7 @@ def __init__(
"0.7.2",
"0.8",
"0.9",
"0.10"
)
],
)
Expand Down Expand Up @@ -2205,6 +2444,12 @@ def connect(
# Create the ISO7816 module
self.iso7816 = ISO7816(self)

# Create the ISO 14443-A module
if self.version >= parse_version("0.10"):
self.iso14443 = ISO14443(self)
else:
self.iso14443 = None

# FPGA left matrix input signals
self.add_mtxl_in("0")
self.add_mtxl_in("1")
Expand Down Expand Up @@ -2238,6 +2483,8 @@ def connect(
self.add_mtxl_in(f"/pgen{i}/out")
for i in range(len(self.chains)):
self.add_mtxl_in(f"/chain{i}/trigger")
if self.iso14443 is not None:
self.add_mtxl_in("/iso14443/trigger")

# FPGA left matrix output signals
# Update this section when adding new modules with inputs
Expand All @@ -2259,6 +2506,10 @@ def connect(
self.add_mtxl_out(f"/chain{i}/event{j}")
for i in range(len(self.clocks)):
self.add_mtxl_out(f"/clock{i}/glitch")
if self.iso14443 is not None:
self.add_mtxl_out("/iso14443/rx")
self.add_mtxl_out("/iso14443/clock_13_56")
self.add_mtxl_out("/iso14443/tearing")

# FPGA right matrix input signals
# Update this section when adding new modules with outpus
Expand Down Expand Up @@ -2290,6 +2541,9 @@ def connect(
self.add_mtxr_in(f"/chain{i}/trigger")
for i in range(len(self.clocks)):
self.add_mtxr_in(f"/clock{i}/out")
if self.version >= parse_version("0.10"):
self.add_mtxr_in("/iso14443/tx")
self.add_mtxr_in("/iso14443/trigger")

# FPGA right matrix output signals
self.add_mtxr_out("/io/a0")
Expand Down Expand Up @@ -2355,3 +2609,5 @@ def reset_config(self, init_ios=False):
i2c.reset_config()
for spi in self.spis:
spi.reset_registers()
if self.iso14443 is not None:
self.iso14443.reset()
2 changes: 1 addition & 1 deletion api/scaffold/bus.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ class ScaffoldBus:
"""

MAX_CHUNK = 255
FIFO_SIZE = 512
FIFO_SIZE = 2048

def __init__(self, sys_freq, baudrate):
"""
Expand Down
Loading
Loading