CW308 CEC1702 target

One of the target I didn’t try yet but which looks interesting is the CEC1702 target.

But I noticed it has a specific place at the CW hierarchy (chipwhisperer/firmware/mcu/CEC1702).

I guess the reason is the the application was originally developed by the commercial MicroC IDE. It looks a bit inconvenient to use third party IDE and run Windows for this purpose.

So I decided to port to be able to compile by GCC under chipwhisperer/firmware/mcu/hal/chipwhisperer-fw-extra/cec1702

This approach will help to unify integration of this target with the building infrastructure

Here is implementation of the HAL layer

// cec1702-hal.c

#include <stdint.h>
#include "mec2016_rom_api.h"
#include "MCHP_CEC1702_C0.h"

#define UART_REF_CLK 48000000UL

static uint32_t aes_buf[4] __attribute__((aligned(4)));

void platform_init(void)
{
    // 1. PWM5 Configuration (Address 0x40005850)
    // Set minimum cycles (n=1 means 1+1=2 cycles per phase)
    PWM5_INST->COUNTER_ON_TIME = 1;
    PWM5_INST->COUNTER_OFF_TIME = 1;

    // CONFIG Register Setup (0x40005858)
    // Clear all bits (CLK_SELECT, INVERT, CLK_PRE_DIVIDER) and enable (PWM_ENABLE = 1)
    // This generates a clean square wave at max frequency without dividers.
    PWM5_INST->CONFIG_b.PWM_ENABLE = 1;
    PWM5_INST->CONFIG_b.CLK_SELECT = 0;      // CLOCK_HIGH
    PWM5_INST->CONFIG_b.INVERT = 0;          // Active High
    PWM5_INST->CONFIG_b.CLK_PRE_DIVIDER = 0; // Divider disabled

    // 2. GPIO 002 Pin Setup (Address 0x40081008)
    // MUX = 1 switches the pin to PWM5 output mode.
    // Using bitmask as seen in IDA (bits 12-14):
    GPIO_000_036_INST->GPIO_002_PIN_CONTROL = (GPIO_000_036_INST->GPIO_002_PIN_CONTROL & ~0x7000) | (1UL << 12);

    // 3. Crypto Power and Reset (AES/HASH)
    // Call ROM API functions to enable the crypto block
    api_aes_hash_power(1);
    api_aes_hash_reset();
}

void init_uart(uint32_t baudrate) {
    // 1. Activate UART block (ACTIVATE register at 0x400F2730)
    UART0_INST->ACTIVATE = 1;

    // 2. Reset configuration to default (0x400F27F0)
    UART0_INST->CONFIG = 0;

    // 3. Calculate divisor for the given baudrate
    uint32_t divisor = UART_REF_CLK / (16 * baudrate);

    // 4. Enable access to divisor latches (DLAB = 1)
    UART0_INST->LINE_CR_b.DLAB = 1;

    // 5. Write divisor bytes
    UART0_INST->BAUDRATE_LSB = (uint8_t)(divisor & 0xFF);
    UART0_INST->BAUDRATE_MSB = (uint8_t)((divisor >> 8) & 0xFF);

    // 6. Set 8N1 format and exit DLAB mode
    UART0_INST->LINE_CR_b.WORD_LENGTH = 3; // 8 bits
    UART0_INST->LINE_CR_b.STOP_BITS = 0;   // 1 stop bit
    UART0_INST->LINE_CR_b.ENABLE_PARITY = 0;
    UART0_INST->LINE_CR_b.DLAB = 0;        // Close DLAB

    // 7. Clear and enable FIFO
    UART0_INST->FIFO_CR_b.EXRF = 1;
    UART0_INST->FIFO_CR_b.CLEAR_RECV_FIFO = 1;
    UART0_INST->FIFO_CR_b.CLEAR_XMIT_FIFO = 1;
}

// Send byte (similar to sub_B0864)
void putch(char c) {
    // Wait until TRANSMIT_EMPTY (bit 5 in LINE_STS) becomes 1
    while (!UART0_INST->LINE_STS_b.TRANSMIT_EMPTY);
    UART0_INST->TX_DATA = c;
}

// Receive byte (similar to sub_B0638)
char getch(void) {
    // Wait until DATA_READY (bit 0 in LINE_STS) becomes 1
    while (!UART0_INST->LINE_STS_b.DATA_READY);
    return UART0_INST->RX_DATA;
}

// Define Write Enable macros via offset from existing fields
// 0x40 bytes = 16 elements of uint32_t type (64 / 4)
#define OUTPUT_GPIO_WE_000_036 (*(&INPUT_OUTPUT_GPIO_INST->OUTPUT_GPIO_000_036 + 16)) // 0x40081380 + 0x40 = 0x400813C0
#define OUTPUT_GPIO_WE_140_176 (*(&INPUT_OUTPUT_GPIO_INST->OUTPUT_GPIO_140_176 + 16)) // 0x4008138C + 0x40 = 0x400813CC

void trigger_setup(void)
{
    // 1. Set direction (DIR = bit 9, BUFFER = bit 10) - same as MikroC (0x600)
    GPIO_000_036_INST->GPIO_017_PIN_CONTROL |= 0x600;
    GPIO_140_176_INST->GPIO_156_PIN_CONTROL |= 0x600;
    GPIO_140_176_INST->GPIO_157_PIN_CONTROL |= 0x600;

    // 2. Enable Write Access (Write Enable)
    // Pin 017 - bit 15. Pins 156, 157 - bits 14, 15.
    OUTPUT_GPIO_WE_000_036 |= (1UL << 15);
    OUTPUT_GPIO_WE_140_176 |= (1UL << 14) | (1UL << 15);

    // 3. Set initial levels
    INPUT_OUTPUT_GPIO_INST->OUTPUT_GPIO_000_036 &= ~(1UL << 15); // GPIO 017 = 0
    INPUT_OUTPUT_GPIO_INST->OUTPUT_GPIO_140_176 |= (1UL << 14);  // GPIO 156 = 1
}

/**
 * Ported trigger_high function.
 * In MikroC: GPIO_OUTPUT_010_017.B7 = 1;
 * In GCC: Set 15th bit (7th bit of 2nd byte) in OUTPUT_GPIO_000_036.
 */
void trigger_high(void)
{
    // Bit 15 corresponds to GPIO 017 pin in the parallel output register
    INPUT_OUTPUT_GPIO_INST->OUTPUT_GPIO_000_036 |= (1UL << 15);
}

void trigger_low(void)
{
    INPUT_OUTPUT_GPIO_INST->OUTPUT_GPIO_000_036 &= ~(1UL << 15);
}

void HW_AES128_LoadKey(uint8_t* key) {
    api_aes_prog_key(key, 16, 0);
}

void HW_AES128_Enc(uint8_t* pt) {
    for (int i = 0; i < 16; i++) {
        ((uint8_t*)aes_buf)[i] = pt[i];
    }

    // Call AES Encryption ROM API (ECB mode 0x00)
    api_aes_crypt(aes_buf, aes_buf, 1, 0x00);
    
    api_aes_start(0);
    
    // Busy wait for completion
    while (api_aes_busy());
    
    for (int i = 0; i < 16; i++) { 
        pt[i] = ((uint8_t*) aes_buf)[i];
    }
}

void HW_AES128_Dec(uint8_t* ct) {
    // Copy Input Ciphertext to the aligned buffer
    for (int i = 0; i < 16; i++) {
        ((uint8_t*)aes_buf)[i] = ct[i];
    }

    // Call AES Decryption ROM API (ECB mode 0x01)
    // Parameters: (src, dest, blocks=1, mode=0x01)
    api_aes_crypt(aes_buf, aes_buf, 1, 0x01);

    api_aes_start(0);

    // Busy wait for completion
    while (api_aes_busy());

    // Copy Output Plaintext back to the original buffer
    for (int i = 0; i < 16; i++) {
        ct[i] = ((uint8_t*) aes_buf)[i];
    }
}

The project includes also standard SDK header files MCHP_CEC1702_C0.h, mec2016_rom_api.h and the linker script CEC1702.ld

I don’t have the board to check the code but reversing of the app compiled by MicroC reveals that the MMIO addresses of the peripheral are the same (like in the ported app).

I have a question regarding clocking of this board. Why is A6 (which is grounded) not used instead of A3 to provide external clock 32 kHz to the target to get much more stable clock and more synced solution? If I correctly understand internal unstable 32 kHz source is used to pass the clock to PLL to produce core 48 Mhz. Then the clock is output to outside as PWM.

That is explained here.

Simple: ChipWhisperer capture devices can’t generate or use a clock that slow.

But we can use an external oscillator with frequency stability 20 PPM instead of a jittery internal oscillator with stability 20 000 PPM which affects the captured traces and UART.

To use an external oscillator, the platform_init() should be changed on:

void platform_init(void)
{   
    // Configure VBAT block parameters (0x4000A408)
    VBAT_INST->CLOCK_EN_b.XOSEL = 0;         // Parallel Resonant Crystal (XTAL1/XTAL2)
    VBAT_INST->CLOCK_EN_b.C32KHZ_SOURCE = 1; // Use Crystal Oscillator for 32K domain
    VBAT_INST->CLOCK_EN_b.EXT_32K = 1;       // Select external source for the system
    
    // Short delay for crystal stabilization
    for(volatile int i = 0; i < 10000; i++); 
    
    // Wait for PLL Lock (PLL Lock Status in PCR block)
    // While PLL_LOCK == 0, the system runs on the unstable internal oscillator
    while (PCR_INST->OSC_ID_b.PLL_LOCK == 0);
    
    // F_pwm = F_in / (On+1 + Off+1) = 48MHz / 4 = 12 MHz
    PWM5_INST->COUNTER_ON_TIME = 1;
    PWM5_INST->COUNTER_OFF_TIME = 1;
    
    // Square wave at max frequency, CLOCK_HIGH (PLL), pre-divider disabled
    PWM5_INST->CONFIG_b.PWM_ENABLE = 1;
    PWM5_INST->CONFIG_b.CLK_SELECT = 0;      
    PWM5_INST->CONFIG_b.INVERT = 0;          
    PWM5_INST->CONFIG_b.CLK_PRE_DIVIDER = 0; 
    
    // MUX = 1 switches GPIO 002 pin to PWM5 output mode
    GPIO_000_036_INST->GPIO_002_PIN_CONTROL = (GPIO_000_036_INST->GPIO_002_PIN_CONTROL & ~0x7000) | (1UL << 12);
    
    api_aes_hash_power(1);
    api_aes_hash_reset();
}