Skip to content

Commit 1fd4718

Browse files
committed
TT DBv3 (ETR) SDK, tested with FPGA and TT08 breakout
1 parent a1eab65 commit 1fd4718

File tree

12 files changed

+651
-65
lines changed

12 files changed

+651
-65
lines changed

src/ttboard/boot/demoboard_detect.py

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -139,20 +139,49 @@ def probe_tt04mux(cls):
139139

140140
@classmethod
141141
def probe_rp2350(cls):
142-
try:
143-
_p = Pin(37, Pin.IN)
144-
except ValueError:
142+
if not platform.IsRP2350:
145143
return False
144+
145+
# already determined RP2350, so that's good
146146
cls.PCB = DemoboardVersion.TTDBv3
147-
cls.CarrierPresent = True
148-
cls.CarrierVersion = DemoboardCarrier.TT_CARRIER # TODO: FIXME just assuming carrier here
149-
return True
147+
148+
# check for FPGA board
149+
fpga_detect_pin = GPIOMapTTDBv3.get_raw_pin(GPIOMapTTDBv3.MNG07, Pin.IN)
150+
151+
# mng 7 pulled high?
152+
if fpga_detect_pin():
153+
cls.CarrierPresent = True
154+
cls.CarrierVersion = DemoboardCarrier.FPGA
155+
return True
156+
157+
158+
# check for standard carrier
159+
cena_pin = GPIOMapTT06.get_raw_pin(GPIOMapTTDBv3.ctrl_enable(), Pin.IN)
160+
crst_pin = GPIOMapTT06.get_raw_pin(GPIOMapTTDBv3.ctrl_reset(), Pin.IN)
161+
162+
crst = crst_pin()
163+
cena = cena_pin()
164+
165+
if (not crst) and (not cena):
166+
log.debug("ctrl mux lines pulled to indicate TT carrier present on RP2350 board")
167+
log.info("TTDBv3 demoboard with carrier present")
168+
cls.CarrierPresent = True
169+
cls.CarrierVersion = DemoboardCarrier.TT_CARRIER
170+
return True
171+
172+
173+
cls.CarrierVersion = DemoboardCarrier.UNKNOWN
174+
return False
175+
150176

151177
@classmethod
152178
def rp_all_inputs(cls):
153179
log.debug("Setting all RP GPIO to INPUTS")
154180
pins = []
155-
for i in range(29):
181+
num_io = 30
182+
if platform.IsRP2350:
183+
num_io = 41
184+
for i in range(num_io):
156185
pins.append(platform.pin_as_input(i, Pin.PULL_DOWN))
157186

158187
return pins
@@ -186,7 +215,7 @@ def _configure_gpiomap(cls):
186215
DemoboardVersion.TT06: GPIOMapTT06,
187216
DemoboardVersion.TTDBv3: GPIOMapTTDBv3,
188217

189-
}
218+
}
190219
if cls.PCB in mapToUse:
191220
log.debug(f'Setting GPIOMap to {mapToUse[cls.PCB]}')
192221
ttboard.pins.gpio_map.GPIOMap = mapToUse[cls.PCB]

src/ttboard/fpga/fabricfoxv2.py

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
'''
2+
3+
FPGA breakout bitstream configurator.
4+
5+
Both PIO and SPI bit bang work to program Lattice FPGAs over the
6+
control and mux lines, as defined in the fabricfox FPGA breakout
7+
project
8+
9+
@author: Pat Deegan
10+
@copyright: Copyright (C) 2025 Pat Deegan, https://psychogenic.com
11+
'''
12+
13+
import rp2
14+
from machine import Pin
15+
from rp2 import PIO, StateMachine, asm_pio
16+
import utime
17+
from ttboard.pins.gpio_map import GPIOMap
18+
from ttboard.demoboard import DemoBoard
19+
DoDummyClocks = True
20+
def pin_indices():
21+
'''
22+
returns map of pin ID (GPIO #)
23+
'''
24+
return {
25+
'sck': GPIOMap.MNG03, # mng03,
26+
'mosi': GPIOMap.MNG00, # mng 00,
27+
'ss': GPIOMap.MNG02, # mng02,
28+
'reset': GPIOMap.ctrl_reset()
29+
}
30+
31+
def pin_objects(tt):
32+
'''
33+
returns map of pin objects
34+
'''
35+
# tt.pins.mng00.init(Pin.OUT)
36+
# tt.pins.mng02.init(Pin.OUT)
37+
# tt.pins.mng03.init(Pin.OUT)
38+
return {
39+
'sck': tt.pins.mng03,
40+
'mosi': tt.pins.mng00,
41+
'ss': tt.pins.mng02,
42+
'reset':tt.pins.ncrst
43+
}
44+
45+
46+
47+
# Simple PIO to do writes over SPI
48+
49+
# PIO program for SPI write-only (8 bits, SCK on Pin 1, MOSI on Pin 2)
50+
@rp2.asm_pio(
51+
sideset_init=(rp2.PIO.OUT_LOW), # SCK idle low (CPOL=0)
52+
out_init=(rp2.PIO.OUT_LOW), # MOSI idle low
53+
out_shiftdir=rp2.PIO.SHIFT_LEFT, #
54+
autopull=True, # Automatically pull 8 bits from FIFO
55+
pull_thresh=8, # Pull 8 bits at a time
56+
fifo_join=rp2.PIO.JOIN_TX # Optimize for TX FIFO
57+
)
58+
def spi_write():
59+
wrap_target()
60+
set(x, 7) .side(0) # Initialize loop counter to 7 (for 8 bits), SCK low
61+
label("bitloop")
62+
out(pins, 1) .side(0) [1] # Output 1 bit to MOSI, SCK low, delay 1 cycle
63+
nop() .side(1) [1] # SCK high, delay 1 cycle (data sampled here)
64+
jmp(x_dec, "bitloop") .side(0) # Decrement X, loop if not zero, SCK low
65+
wrap()
66+
67+
68+
69+
70+
def fpga_reset(ss, reset):
71+
# xtra
72+
reset.low()
73+
ss.low()
74+
utime.sleep_us(15000) # Small delay to ensure reset
75+
reset.high()
76+
utime.sleep_us(15000) # delay to ensure slave ready, minimum 1200us
77+
78+
79+
80+
def spi_transferPIO(filepath: str, freq: int = 1_000_000):
81+
"""
82+
Transfer all bytes from a file over SPI using PIO.
83+
84+
Args:
85+
filepath (str): Path to the file to transmit.
86+
freq (int): SPI clock frequency in Hz (default 1 MHz).
87+
"""
88+
tt = DemoBoard.get()
89+
# Initialize pins
90+
pins = pin_objects(tt)
91+
pins_idx = pin_indices()
92+
93+
reset = pins['reset']
94+
ss = pins['ss']
95+
96+
reset.high()
97+
98+
99+
# Calculate PIO frequency (2 cycles per bit, 8 bits per byte)
100+
pio_freq = freq * 2 * 8 # e.g., 1 MHz SPI -> 16 MHz PIO
101+
print(f"Configuring PIO with frequency: {pio_freq} Hz")
102+
103+
# Configure PIO state machine
104+
sm = StateMachine(0, spi_write, freq=pio_freq, sideset_base=Pin(pins_idx['sck']),
105+
out_base=Pin(pins_idx['mosi']))
106+
107+
# Clear FIFO and ensure state machine is reset
108+
sm.restart()
109+
sm.active(1) # Activate state machine
110+
print("State machine activated")
111+
112+
try:
113+
# Open file in binary read mode
114+
with open(filepath, 'rb') as f:
115+
# Set SS low to start SPI transaction
116+
fpga_reset(ss, reset)
117+
118+
119+
if DoDummyClocks:
120+
# release CS
121+
ss.high()
122+
utime.sleep_us(2000)
123+
# send 8 dummy clocks
124+
sm.put(0)
125+
126+
# wait until sent
127+
utime.sleep_us(20)
128+
while sm.tx_fifo() != 0:
129+
utime.sleep_us(2)
130+
131+
132+
# actually select
133+
ss.low()
134+
utime.sleep_us(2000)
135+
136+
137+
print("SS low, starting transmission")
138+
139+
# Read file in chunks
140+
chunk_size = 128 # Reduced chunk size to avoid FIFO overflow
141+
byte_count = 0
142+
termination_bytes_to_send = 6
143+
while True:
144+
data = f.read(chunk_size)
145+
if not data: # End of file
146+
for _i in range(termination_bytes_to_send):
147+
148+
while sm.tx_fifo() != 0:
149+
utime.sleep_us(1)
150+
151+
sm.put(0)
152+
break
153+
154+
# print(f"Sending {len(data)} bytes")
155+
# Send each byte to PIO TX FIFO
156+
for byte in data:
157+
# Wait if FIFO is full (tx_fifo() returns free slots, 0 means full)
158+
159+
sm.put(byte & 0xff, 24) # LEFT shift of the 32 bits, must push up 24 to see byte
160+
while sm.tx_fifo() != 0:
161+
utime.sleep_us(1) # Small delay to prevent tight loop
162+
byte_count += 1
163+
#if byte_count % 128 == 0:
164+
# print(f"Sent {byte_count} bytes, TX FIFO free slots: {sm.tx_fifo()}")
165+
166+
# Wait for FIFO to drain
167+
while sm.tx_fifo():
168+
print(f"Waiting for FIFO to drain, free slots: {sm.tx_fifo()}")
169+
utime.sleep_us(10)
170+
171+
print(f"Transmission complete, total bytes: {byte_count}")
172+
173+
except OSError as e:
174+
print(f"Error accessing file: {e}")
175+
finally:
176+
# Deactivate state machine and set SS high
177+
sm.active(0)
178+
ss.high()
179+
sm.restart()
180+
sm = None
181+
# sck.init(mode=Pin.OUT, pull=None)
182+
# mosi.init(mode=Pin.OUT)
183+
184+
185+
print("State machine deactivated, SS high")
186+
187+
188+
189+
190+
191+
192+
def spi_send(sck, mosi, val, delay_us=1):
193+
sck.low()
194+
for i in range(8):
195+
if val & (1 << (7 - i)):
196+
mosi.high()
197+
else:
198+
mosi.low()
199+
sck.high()
200+
utime.sleep_us(delay_us)
201+
sck.low()
202+
utime.sleep_us(delay_us)
203+
204+
205+
def spi_transferBitBang(filepath: str, freq: int = 1_000_000):
206+
"""
207+
Transfer all bytes from a file over SPI using PIO.
208+
209+
Args:
210+
filepath (str): Path to the file to transmit.
211+
freq (int): SPI clock frequency in Hz (default 1 MHz).
212+
"""
213+
# Initialize pins
214+
tt = DemoBoard.get()
215+
# Initialize pins
216+
pins = pin_objects(tt)
217+
# pins_idx = pin_indices()
218+
219+
reset = pins['reset']
220+
ss = pins['ss']
221+
sck = pins['sck']
222+
mosi = pins['mosi']
223+
reset.high()
224+
225+
226+
227+
try:
228+
# Open file in binary read mode
229+
with open(filepath, 'rb') as f:
230+
# Set SS low to start SPI transaction
231+
fpga_reset(ss, reset)
232+
233+
234+
if DoDummyClocks:
235+
# release CS
236+
ss.high()
237+
utime.sleep_us(2000)
238+
# send 8 dummy clocks
239+
spi_send(sck, mosi, 0)
240+
241+
# wait until sent
242+
utime.sleep_us(20)
243+
244+
245+
# actually select
246+
ss.low()
247+
utime.sleep_us(2000)
248+
249+
250+
print("SS low, starting transmission")
251+
252+
# Read file in chunks
253+
chunk_size = 128 # Reduced chunk size to avoid FIFO overflow
254+
byte_count = 0
255+
termination_bytes_to_send = 6
256+
while True:
257+
data = f.read(chunk_size)
258+
if not data: # End of file
259+
for _i in range(termination_bytes_to_send):
260+
261+
spi_send(sck, mosi, 0)
262+
break
263+
264+
# print(f"Sending {len(data)} bytes")
265+
# Send each byte to PIO TX FIFO
266+
for byte in data:
267+
# Wait if FIFO is full (tx_fifo() returns free slots, 0 means full)
268+
spi_send(sck, mosi, byte)
269+
byte_count += 1
270+
print(f"Transmission complete, total bytes: {byte_count}")
271+
272+
except OSError as e:
273+
print(f"Error accessing file: {e}")
274+
finally:
275+
# Deactivate state machine and set SS high
276+
# sck.init(mode=Pin.OUT, pull=None)
277+
# mosi.init(mode=Pin.OUT)
278+
ss.high()
279+
280+
281+
print("State machine deactivated, SS high")
282+
283+
return {
284+
'ss': ss,
285+
'sck': sck,
286+
'reset': reset,
287+
'mosi': mosi,
288+
}
289+
290+
291+
# Example usage
292+
if __name__ == "__main__":
293+
# Example: Transfer contents of 'data.bin' at 1 MHz
294+
spi_transferPIO("/bitstreams/data.bin", freq=1_000_000)

src/ttboard/fpga/fpga_mux.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from ttboard.boot.shuttle_properties import HardcodedShuttle
99
import ttboard.log as logging
1010
log = logging.getLogger(__name__)
11-
import ttboard.fpga.fabricfox as fpgaloader
11+
import ttboard.fpga.fabricfoxv2 as fpgaloader
1212
class BitStream:
1313
def __init__(self, loader, filepath:str, name:str, clock_hz:int=100):
1414
self._filepath = filepath

src/ttboard/log.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
@author: Pat Deegan
55
@copyright: Copyright (C) 2024 Pat Deegan, https://psychogenic.com
66
'''
7-
from ttboard.util.platform import IsRP2040
7+
from ttboard.util.platform import IsRP2
88
import ttboard.util.colors as colors
99
import ttboard.util.time as time
1010
import gc
1111
RPLoggers = dict()
1212
DefaultLogLevel = 20 # info by default
1313
LoggingPrefix = 'BOOT'
14-
if IsRP2040:
14+
if IsRP2:
1515
# no logging support, add something basic
1616
DEBUG = 10
1717
INFO = 20

0 commit comments

Comments
 (0)