JTAG Implementation Evolution From 2.25Mbps to 9Mbps - grumat/glossy-msp430 GitHub Wiki
JTAG Implementation Evolution: From 2.25 Mbps (ST-Link V2) to 9 Mbps (dtrig/BluePill)
Executive Summary
The glossy-msp430 project supports two distinct JTAG implementations, each optimized for different hardware constraints:
| Implementation | Hardware Target | Speed | Clock Source | Release | Use Case |
|---|---|---|---|---|---|
| GeneratorSTLinkPWM | ST-Link V2 Clone | 2.25 Mbps | Timer PWM | v1.0 | Low-cost accessibility; educational use |
| DtrigJtag | BluePill/F103 | 9 Mbps | SPI + Timer Toggle | v2.0+ | Performance debugging; production use |
This document traces the architectural evolution, explains why each design made different trade-offs, and when to use each variant.
Part 1: The Twin Implementations
1.1 Quick Comparison Table
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ JTAG IMPLEMENTATION COMPARISON โ
โโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Aspect โ GeneratorSTLinkPWM โ DtrigJtag โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Hardware Target โ ST-Link V2 Clone โ BluePill / STM32F103 โ
โ Speed โ 2.25 Mbps โ 9 Mbps (9ร faster!) โ
โ Cost (Hardware) โ $3-5 โ $15-20 โ
โ Clock Source โ TIM1 PWM (50% duty) โ SPI + TIM1 Toggle Mode โ
โ Data Transfer โ DMA to GPIO BSRR โ SPI DMA (byte-per-cycle) โ
โ JTDI/JTDO Control โ GPIO BSRR writes โ SPI peripheral (native) โ
โ TMS Control โ GPIO BSRR writes โ TIM1_CH2N toggle (0-DMA) โ
โ DMA Channels Used โ 3 (read/write/tms) โ 2 (SPI RX/TX only) โ
โ Port Layout โ Split (PA/PB) โ Single (PA only) โ
โ Synchronization โ Timer repetition ctr โ Critical section + CNT โ
โ Speed-Grades โ 1 (fixed) โ 5 (0.5-9 Mbps) โ
โ TCLK Support โ Via SPI bit-stream โ Via SPI burst + PWM muting โ
โ Overhead โ High DMA arbitration โ Low (SPI native protocol) โ
โ Temperature Stable โ ยฑ0.5% (PLL) โ ยฑ0.1% (SPI baud ratio) โ
โโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Part 2: Why Two Implementations?
2.1 Hardware Constraints Led to Different Solutions
ST-Link V2 Clone Constraints:
- GPIO pins split across Port A (PA6/PA7) and Port B (PB13/PB14)
- No SPI peripheral easily available without internal surgery
- Maximum 2.25 MHz due to 3-channel DMA arbitration overhead
- Physical layout forces multi-port coordination
BluePill Advantages:
- All JTAG signals on Port A (PA5/PA6/PA7)
- SPI1 peripheral native to pins PA5/PA6/PA7
- Flexible timer channels for TMS modulation
- Single-port DMA enables 9 MHz operation
2.2 Design Philosophy
GeneratorSTLinkPWM (ST-Link V2):
"Given rigid hardware constraints, maximize speed with autonomous hardware waveforms rather than CPU bit-banging."
- Accepted lower speed (2.25 Mbps) for cost reduction and accessibility
- Prioritized robustness (deterministic waveforms) over throughput
DtrigJtag (BluePill):
"Leverage SPI efficiency and timer versatility to achieve native protocol speeds."
- Exploits flexible GPIO routing and native peripherals
- Prioritizes performance (9 Mbps) for production debugging
Part 3: Architectural Comparison
3.1 GeneratorSTLinkPWM Architecture (2.25 Mbps)
The Problem: Split GPIO ports + no SPI + need precision clock
The Solution: Automated waveform generation via PWM + multi-DMA
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ STM32F103 (ST-Link V2 Clone) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ TIM1 (Master Timer, 8-count cycle) โ โ
โ โ โข PWM on CH1N (PB13) โ JTCK (50% duty) โ โ
โ โ โข CCR2 (count 5) โ triggers DMA for JTMS writes โ โ
โ โ โข CCR3 (count 1) โ triggers DMA for JTDI writes โ โ
โ โ โข CCR4 (count 0) โ triggers DMA for JTDO reads โ โ
โ โโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ DMA Arbitration (3 channels, different addresses) โ
โ โ โ
โ โ DMA1_Ch2: Port A IDR โ Memory (JTDO read) โ
โ โ DMA1_Ch3: Memory โ Port A BSRR (JTDI write) โ
โ โ DMA1_Ch4: Memory โ Port B BSRR (JTMS write) โ
โ โ โ
โ โ โ Problem: Each channel = ~115 ns overhead โ
โ โ 3 channels ร 8-count = 444 ns cycle โ
โ โ Margin: ~99 ns (safe but tight) โ
โ โโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ GPIO Ports (Split across A and B) โ โ
โ โ PA6/PA7 (JTDO/JTDI) โโ Target JTAG โ โ
โ โ PB13/PB14 (JTCK/JTMS) โโ โ โ
โ โโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโผโโโโโโ
โ MSP430 โ
โ Target โ
โโโโโโโโโโโโโ
Key Bottleneck: DMA arbitration across 3 separate peripheral addresses
- Each DMA request: ~115 ns (setup + arbitrate + transfer)
- 3 channels per cycle ร 115 ns = 345 ns minimum
- At 2.25 MHz, cycle = 444 ns โ only 99 ns margin โ but limited
3.2 DtrigJtag Architecture (9 Mbps)
The Insight: SPI handles data transfer natively; timer toggle mode handles TMS for free
The Solution: SPI + Timer Toggle Mode + Critical-Section Synchronization
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ STM32F103 (BluePill, Flexible GPIO) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ SPI1 (Data Transfer, byte-per-8-cycles) โ โ
โ โ โข SCK = PA5 (JTCK) โ SPI clock (8 cycles/byte) โ โ
โ โ โข MOSI = PA7 (JTDI) โ TX via DMA1_Ch3 โ โ
โ โ โข MISO = PA6 (JTDO) โ RX via DMA1_Ch2 โ โ
โ โ โ โ
โ โ DMA Cost: Only 2 channels (RX/TX) โ โ
โ โ Each byte: 8 cycles @ APB2 / N baud โ โ
โ โโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ TIM1 (TMS Generator, Toggle Mode, zero-DMA) โ โ
โ โ โข CH2N = PB14 (JTMS) โ toggle output โ โ
โ โ โข CH3 DMA-trigger = CCR2 reload (one DMA shot) โ โ
โ โ โข CH4 DMA-trigger = CCR2 reload (one DMA shot) โ โ
โ โ โ โ
โ โ Toggle Pattern (Toggle mode): โ โ
โ โ Entry: CCR2 = count N โ toggle โ TMS LOW โ โ
โ โ Exit: CCR2 = count M โ toggle โ TMS HIGH โ โ
โ โ Return: CCR2 = count Z โ toggle โ TMS LOW โ โ
โ โ โ โ
โ โ DMA Cost: Only 2 CCR2 reloads (not per-bit!) โ โ
โ โโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Critical Section: Atomic SPI + TIM1 Launch โ โ
โ โ โ โ
โ โ void Start(..., uint16_t cnt_offset) { โ โ
โ โ { โ โ
โ โ CriticalSection lock; โ Disable interrupts โ โ
โ โ SpiTxDma::Enable(); โ Start SPI DMA โ โ
โ โ CycleTimer::CounterResume(); โ Resume TIM1 โ โ
โ โ } โ Re-enable interrupts โ โ
โ โ } โ โ
โ โ โ โ
โ โ Result: SPI and TIM1 phase-locked from bit 0 โ โ
โ โ cnt_offset pre-loads TIM1 CNT for TMS timing โ โ
โ โโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ GPIO Port A (All JTAG signals on same port!) โ โ
โ โ PA5 (SPI SCK) โโ JTCK โ โ
โ โ PA6 (SPI MISO) โโ JTDO โ โ
โ โ PA7 (SPI MOSI) โโ JTDI โ โ
โ โ โ โ
โ โ GPIO Port B โ โ
โ โ PB14 (TIM1_CH2N) โโ JTMS โ โ
โ โโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโผโโโโโโ
โ MSP430 โ
โ Target โ
โโโโโโโโโโโโโ
Key Innovation:
- SPI handles data transfer (byte per 8 cycles, protocol-native)
- TIM1 toggle mode provides TMS pulses for FREE (no per-bit DMA cost)
- Only 2 DMA channels needed (SPI RX/TX) โ 4ร less arbitration
- Critical section ensures first bit alignment โ cnt_offset tuning per grade
Result: Can sustain 9 Mbps safely โ
Part 4: Why Speed Differs (The Physics)
4.1 DMA Arbitration Analysis
At 2.25 MHz (GeneratorSTLinkPWM):
Cycle time: 444 ns (8-count ร 55.5 ns/count)
Timeline:
Count 0: DmaRead (Port A IDR) โ 115 ns
Count 1: DmaWrite (Port A BSRR) โ 115 ns (gap: 55 ns)
Count 2: DmaTms (Port B BSRR) โ 115 ns (gap: 55 ns)
Count 3-7: PWM high/low, wait for next cycle
Total margin: ~99 ns โ Safe โ (but no room to increase speed)
At 9 Mbps (DtrigJtag, per byte):
Cycle time: 111 ns (8-count ร 13.9 ns/count @ 72 MHz with N=8 prescaler)
Timeline (Single-port SPI):
SPI TX DMA: Start transfer (DMA native, single address)
TIM1 Toggle: Hardware generates TMS transitions
No per-bit DMA arbitration!
Total margin: Excellent โ (can add speed grades safely)
Key Insight:
- GeneratorSTLinkPWM: Each DMA request = separate peripheral address = full setup/arbitrate/transfer cycle
- DtrigJtag: SPI peripheral handles clock/data natively; toggle mode needs zero DMA
4.2 Synchronization Strategy Differences
GeneratorSTLinkPWM (Timer repetition counter):
// Set exact cycle count via RCR (repetition counter)
BeatTimer::SetupRepetition(kTimerPeriod_, 1); // repeat once
// Timer runs autonomously for exact bit count
DtrigJtag (Critical section + CNT preload):
// Lock interrupts, launch SPI and TIM1 atomically
{
CriticalSection lock;
SpiTxDma::Enable(); // Start SPI first byte
CycleTimer::CounterResume(); // Resume TIM1 with preset CNT
}
// cnt_offset = LA-calibrated phase shift per speed grade
Part 5: Historical Evolution
Timeline: How Did We Get Here?
Phase 1: Discovery (Early Design)
- Problem identified: ST-Link V2 clone lacks SPI but has GPIO and timer
- Initial attempt: Naive bit-banging (unreliable, jittery)
- Solution: GeneratorSTLinkPWM template (Timer PWM + 3-channel DMA)
- Result: 2.25 Mbps, stable, deterministic
- Release: v1.0
Phase 2: SPI Opportunity (BluePill)
- Observation: BluePill has flexible GPIO + native SPI on PA5/PA6/PA7
- Strategy: Use SPI for data (byte per 8 cycles) + timer for TMS
- Challenge: TMS timing must align with SPI bit edges
- Solution: Critical section synchronization + cnt_offset tuning
- Result: 9 Mbps, 4ร faster than ST-Link V2
- Release: v1.5+
Phase 3: Optimization (Current)
- Insight: TIM1 toggle mode generates TMS pulses with zero DMA cost
- Improvement: Removed 3rd DMA channel (TMS control)
- Validation: Speed grades (0.5, 1.125, 2.25, 4.5, 9 Mbps) with per-grade tuning
- Addition: TCLK high-frequency support (~450 kHz) via SPI burst
- Release: v2.0+
Part 6: Detailed Technical Comparison
6.1 Clock Generation
| Aspect | GeneratorSTLinkPWM | DtrigJtag |
|---|---|---|
| Clock Source | TIM1 PWM (GPIO output) | SPI SCK (peripheral) |
| Frequency Control | Timer prescaler | SPI baud rate (BR field) |
| Duty Cycle | Fixed 50% (4 high, 4 low) | Fixed 50% (SPI native) |
| Accuracy | ยฑ0.5% (PLL drift) | ยฑ0.1% (baud ratio) |
| Jitter | ยฑ1 timer count (~55 ns @ 2.25 MHz) | <1 ns (SPI native) |
| Adjustability | Single fixed speed | 5 speed grades at runtime |
6.2 Data Transfer
| Aspect | GeneratorSTLinkPWM | DtrigJtag |
|---|---|---|
| JTDI/JTDO Method | DMA to GPIO BSRR | SPI MOSI/MISO via DMA |
| Unit of Transfer | Per-DMA (variable) | Per-byte (8 cycles/byte) |
| DMA Channels | 2 (RX, TX) | 2 (RX, TX) |
| Arbitration Overhead | None (not used for JTDI) | Minimal (SPI protocol) |
| Throughput | Limited by timer cycle | Limited by SPI baud |
| Byte Alignment | 8-count cycle per bit | Native (8 counts/byte) |
6.3 TMS (Mode Select) Generation
| Aspect | GeneratorSTLinkPWM | DtrigJtag |
|---|---|---|
| TMS Method | DMA writes to GPIO BSRR | TIM1_CH2N toggle mode |
| DMA Channels for TMS | 1 dedicated | 0 (toggle is hardware) |
| Pulses per Frame | Multiple (every state change) | 3 (entry, exit, RTI) |
| Control Method | Per-bit DMA updates | Two CCR2 reload DMA shots |
| Timing Precision | Timer-based (counts) | Toggle mode (exact) |
| CPU Overhead | Render TMS sequence each frame | Render TMS bit pattern once |
Key Difference: DtrigJtag's toggle mode requires only 2 DMA writes for entire TMS sequence, vs GeneratorSTLinkPWM's multi-write approach.
6.4 Hardware Port Usage
GeneratorSTLinkPWM:
Port A: PA6 (JTDO), PA7 (JTDI)
Port B: PB13 (JTCK), PB14 (JTMS)
โ Separate DMA writes needed
DtrigJtag:
Port A: PA5 (SPI SCK=JTCK), PA6 (SPI MISO=JTDO), PA7 (SPI MOSI=JTDI)
โ Single peripheral, atomic SPI protocol
Port B: PB14 (TIM1_CH2N=JTMS) โ no DMA
Part 7: Implementation Details
7.1 File Structure
GeneratorSTLinkPWM (ST-Link V2):
Firmware.shared/
drivers/
JtagDev.tim.cpp โ Main implementation
util/
WaveJtag.h โ GeneratorSTLinkPWM template
target.stlinv2/
platform.h โ Pin definitions
DtrigJtag (BluePill):
Firmware.shared/
drivers/
JtagDev.dtrig.cpp โ Main implementation
util/
DtrigJtag.h โ DtrigJtag template
target.bluepill/
platform.h โ Pin definitions
7.2 Speed Grade Configuration (DtrigJtag Only)
DtrigJtag supports 5 speed grades tuned via cnt_offset parameter:
// From JtagDev.dtrig.cpp
static constexpr uint16_t kDtrigCntOffset_1 = 2; // 0.5625 MHz (slowest, safest)
static constexpr uint16_t kDtrigCntOffset_2 = 2; // 1.125 MHz
static constexpr uint16_t kDtrigCntOffset_3 = 2; // 2.25 MHz
static constexpr uint16_t kDtrigCntOffset_4 = 4; // 4.5 MHz
static constexpr uint16_t kDtrigCntOffset_5 = 4; // 9 MHz (fastest)
// Each grade calibrated with logic analyzer to align TMS toggles
// with SPI bit edges within the 8-count cycle
GeneratorSTLinkPWM: Fixed 2.25 MHz (no speed grades)
7.3 TCLK (Flash Clock) Support
| Implementation | Method | Frequency |
|---|---|---|
| GeneratorSTLinkPWM | Via SPI bit-stream (kJtmsWave pattern) | ~450 kHz |
| DtrigJtag | SPI burst at reduced baud + GPIO muting | ~450 kHz |
Both achieve ~450 kHz for MSP430 flash operations (requires <500 kHz per spec).
Part 8: Performance Comparison
8.1 Real-World Speed Impact
Scenario: Reading 256 bytes from target flash
Operation: 16-bit reads ร 128 = 2048 JTAG clock cycles total
GeneratorSTLinkPWM @ 2.25 MHz:
Time = 2048 cycles / 2.25 MHz = 911 ยตs
Frame overhead: ~50 ยตs (20 frames ร ~2.5 ยตs setup)
Total: ~961 ยตs
DtrigJtag @ 9 Mbps:
Time = 2048 cycles / 9 MHz = 228 ยตs
Frame overhead: ~20 ยตs (20 frames ร ~1 ยตs setup)
Total: ~248 ยตs
Speedup: 961 / 248 โ 3.87ร (vs theoretical 4ร)
Reason: Frame overhead amortized over multiple transfers
Real Impact:
- Small reads (1-4 bytes): ~10-50 ยตs difference (imperceptible)
- Large transfers (>256 bytes): dtrig clearly faster
- Typical debug session: Mix of operations, overall 2-3ร faster
8.2 Throughput Limits
GeneratorSTLinkPWM:
- Theoretical max: 2.25 Mbps (2,250,000 bits/sec)
- Practical max: ~1.8 Mbps (accounting for frame gaps, bus arbitration)
DtrigJtag:
- Theoretical max: 9 Mbps (9,000,000 bits/sec @ 72 MHz / 8 prescaler)
- Practical max: ~8.5 Mbps (accounting for frame gaps, DMA setup)
Part 9: When to Use Each Implementation
Selection Matrix
Use GeneratorSTLinkPWM (2.25 Mbps, ST-Link V2) if:
- โ Hardware: ST-Link V2 clone (constrained GPIO, no SPI)
- โ Budget: $3-5 required
- โ Use case: Educational, accessibility, proof-of-concept
- โ Speed acceptable: โค2.25 Mbps is enough
- โ Availability: Local hobbyist shop has stock
Use DtrigJtag (9 Mbps, BluePill) if:
- โ Hardware: STM32F103 BluePill or equivalent
- โ Performance: Need โฅ4 Mbps
- โ Use case: Production debugging, development
- โ Features: Speed-grade tuning, TCLK support required
- โ Flexibility: Can use 5 different speed grades
Hardware Compatibility Table
| Hardware | Implementation | Speed | Notes |
|---|---|---|---|
| ST-Link V2 Clone | GeneratorSTLinkPWM | 2.25 Mbps | Fixed; only option |
| BluePill (STM32F103) | DtrigJtag | 9 Mbps | 5 speed grades |
| Nucleo-L432KC | DtrigJtag | 9 Mbps | Limited testing |
| Nucleo-G431KB | DtrigJtag | 9 Mbps | Limited testing |
| Custom F1/F3 board | DtrigJtag (if PA5/PA6/PA7 available) | 9 Mbps | Board-dependent |
Part 10: Key Takeaways
10.1 Design Lessons
| Lesson | Implication |
|---|---|
| Hardware constraints drive design | ST-Link V2's split GPIO โ timer/DMA; BluePill's SPI โ dtrig |
| DMA arbitration is expensive | Each peripheral address = ~115 ns overhead; limits speed |
| SPI is underrated for JTAG | Native protocol efficiency beats bit-banging by 4ร |
| Timer toggle mode is free | TIM1_CH2N toggle costs zero DMA budget |
| Synchronization matters | Critical section + cnt_offset = phase-locked precision |
| Speed grades = robustness | Tuning per frequency proves margins exist |
10.2 Code Quality Insights
- GeneratorSTLinkPWM: Template-heavy, demonstrates C++17
constexprtricks - DtrigJtag: More complex (critical sections, timing tuning), but clearer intent
- Both use bmt abstractions exclusively (no raw register access)
10.3 Future Directions
Potential optimizations:
- Extend DtrigJtag to G4/L4 families (higher clock = higher Mbps possible)
- Explore H7 dual-DMA for ST-Link V2 โ eliminate arbitration, reach 3-4 Mbps
- Add variable speed-grade support to GeneratorSTLinkPWM (minor work)
Conclusion
The evolution from 2.25 Mbps (GeneratorSTLinkPWM) to 9 Mbps (DtrigJtag) illustrates how hardware constraints and design choices shape performance:
- ST-Link V2: Rigid GPIO + no SPI โ timer/DMA workaround โ 2.25 Mbps
- BluePill: Flexible GPIO + native SPI โ SPI + timer toggle โ 9 Mbps
Each implementation is optimal for its target hardware and use case. The project supports both because different users have different priorities: cost vs. performance, accessibility vs. production-grade debugging.
References & Related Documents
- Repurposing-ST-Link-V2-Clones-for-MSP430-JTAG-Debugging.md โ Detailed GeneratorSTLinkPWM design
- Firmware.shared/util/DtrigJtag.h โ DtrigJtag template documentation
- Firmware.shared/drivers/JtagDev.dtrig.cpp โ DtrigJtag implementation & speed grades
- Firmware.shared/util/TimDmaWave.h โ Timer/DMA utilities (supports both approaches)
- CODE_GUIDELINES.md โ C++17 template and bmt abstraction standards