Block Preview

Introduction

Principle of Operation

Each Citiroc Analog block produces, once per event, a 1024-bit packed frame plus a 32-bit run timestamp and a 64-bit global timestamp, and raises a level DV that it holds until it is acknowledged. This block is the other half of that handshake for all four ASICs at once.

A single state machine cycles A → B → C → D → A …, one ASIC per clock cycle. When it finds a DV_x asserted for an unmasked ASIC it:

  1. latches the whole event — header, timestamps, counter, 32 channel words, trailer — into a 1216-bit register in one clock cycle;
  2. pulses ACK_x so the analog block can release its DV and start accepting the next trigger;
  3. shifts the 38 words out of that register into the FIFO, one word per clock.

The FIFO is an xpm_fifo_async with independent write and read clocks, sized by the Samples property. The host reads it through the standard FIFO endpoint, and the generated CitirocFRAME_<name>_RECONSTRUCT_DATA function turns the raw 32-bit word stream back into per-channel charges.

The packet format is the real contract between firmware and software, so it is documented in full below — it is what you need if you write your own decoder.

Pin Description

DATA_A Input 1024 bit BIT VECTOR
TS_T0_A Input 32 bit BIT VECTOR
TS_A Input 64 bit BIT VECTOR
DV_A Input 1 bit BIT
DATA_B Input 1024 bit BIT VECTOR
TS_T0_B Input 32 bit BIT VECTOR
TS_B Input 64 bit BIT VECTOR
DV_B Input 1 bit BIT
DATA_C Input 1024 bit BIT VECTOR
TS_T0_C Input 32 bit BIT VECTOR
TS_C Input 64 bit BIT VECTOR
DV_C Input 1 bit BIT
DATA_D Input 1024 bit BIT VECTOR
TS_T0_D Input 32 bit BIT VECTOR
TS_D Input 64 bit BIT VECTOR
DV_D Input 1 bit BIT
FLUSH Input 1 bit BIT
Flush / reset – asynchronous, ORed with CONTROL[8]. While high it resets the arbiter, clears the event counter and flushes the FIFO. If left unconnected the compiler ties it to the board’s FIFO_RESET net, so the block is flushed by the global FIFO reset.
Default: FIFO_RESET
ACK_A Output 1 bit BIT
ACK_B Output 1 bit BIT
ACK_C Output 1 bit BIT
ACK_D Output 1 bit BIT
FULL Output 1 bit BIT
FIFO almost full – the XPM prog_full flag, asserted one full packet (38 words) before the real full. While it is high, incoming events are acknowledged but discarded. Use it to throttle the trigger.
DATA_AVAL Output 1 bit BIT
Data available – inverse of the XPM prog_empty flag, so it rises only when more than one complete packet is buffered. It is the same bit the host reads as STATUS[0].
BUSY Output 1 bit BIT
Busy – high only while the 38 words of a latched event are being written into the FIFO (38 consecutive clock cycles). It does not indicate FIFO fullness.
DATA_A, DATA_B, DATA_C, DATA_D Input 1024 bit
Packed event frame from the Citiroc Analog block of the corresponding ASIC. 32 channel words of 32 bits, channel 0 in the most significant word. Copied verbatim into words 5…36 of the packet, so its bit layout is the packet’s channel layout — but note the words are serialised LSW-first, so packet word 5 is ASIC channel 31.
TS_T0_A, TS_T0_B, TS_T0_C, TS_T0_D Input 32 bit
Run timestamp of the event, from the TS0_OUT pin of the matching Citiroc Analog block. Becomes word 1 of the packet and is decoded as Time_Code.
TS_A, TS_B, TS_C, TS_D Input 64 bit
Global 64-bit timestamp of the event, from the TS_OUT pin of the matching Citiroc Analog block. Split into words 2 (low) and 3 (high) and decoded as RunTime_Code.
DV_A, DV_B, DV_C, DV_D Input 1 bit
Event request – level signal from the analog block’s P_FRAME_DV. Sampled when the round-robin arbiter reaches that ASIC’s slot; must stay high until the matching ACK is returned. Masked ASICs (CONTROL[0..3] = 1) are never sampled.
ACK_A, ACK_B, ACK_C, ACK_D Output 1 bit
Event acknowledge – one-cycle pulse telling the analog block its frame has been latched. Wire it to the analog block’s P_FRAME_ACK. It is pulsed even when the FIFO was too full to actually store the event.

Properties

Property window

Name EndpointName

Set the name of the endpoint

Name of the memory-mapped FIFO endpoint, and the name embedded in every generated symbol: the registers become SCI_REG_<name>_CONTROL / _STATUS / _FIFOADDRESS, and the API functions become CitirocFRAME_<name>_START, _RESET, _GetStatus, _DOWNLOAD, _RECONSTRUCT_DATA (plus _GET_STATUS and _GET_DATA in Python). Changing it rebuilds the symbol.

Default: CitirocFrame0

Default: CitirocFrame0

Number of samples per channel Samples

Set the number of samples stored for each acquisition

Depth of the readout FIFO, in 32-bit words (not in samples per channel, despite the caption). It is passed straight to the memLength generic and becomes the XPM FIFO_WRITE_DEPTH. Capacity in events is Samples / 38.

Value Events buffered Usable?
1024 26 yes
4096 107 yes
8192 215 yes
16384 431 yes
32768 862 yes
65536 1724 yes — the largest depth whose word-count field still fits in STATUS
131072 3449 no — the STATUS word-count slice runs past bit 31
262144 6898 no — same problem

Default: 8192

Default: 8192

Options: 1024 4096 8192 16384 32768 65536 131072 262144

⚙️ Detailed Operation

Packet layout — 38 words of 32 bits

This is the authoritative layout, cross-checked between CitirocFrameTransfer.vhd, the generated C decoder and the generated Python decoder.

Word Content Decoder field
0 0x8000000n — sync header, n = ASIC index (A=0, B=1, C=2, D=3) AsicID
1 TS_T0_x — 32-bit run timestamp Time_Code
2 TS_x[31:0] — global timestamp, low half RunTime_Code (low)
3 TS_x[63:32] — global timestamp, high half RunTime_Code (high)
4 event counter of this block, free running Pack_Id
5 ASIC channel 31 data word ChargeHg[0], ChargeLg[0], Hit[0]
6 ASIC channel 30 data word …[1]
… … …
36 ASIC channel 0 data word ChargeHg[31], ChargeLg[31], Hit[31]
37 0xC0000000 — trailer —

Each channel data word:

Bits Field Width
31:29 reserved, always 000 3
28 Hit — channel discriminator 1
27:14 ChargeLg — low-gain charge 14
13:0 ChargeHg — high-gain charge 14

Note the naming: the 32-bit TS_T0 becomes Time_Code and the 64-bit global timestamp becomes RunTime_Code in the decoded structure — the opposite of what the names suggest.

Resynchronisation rules used by the decoder

The generated decoder is a 7-state machine over the raw word stream:

stateDiagram-v2
  [*] --> HUNT
  HUNT --> TS_T0 : word>>4 == 0x8000000
  HUNT --> HUNT  : otherwise (discard)
  TS_T0 --> TS_LO
  TS_LO --> TS_HI
  TS_HI --> PACKID
  PACKID --> DATA
  DATA --> DATA : 32 channel words
  DATA --> TRAIL : after the 32nd
  TRAIL --> HUNT : word & 0xC0000000 == 0xC0000000 -> emit packet
  TRAIL --> HUNT : otherwise -> discard packet

Two consequences worth knowing:

  • A packet is emitted only if the trailer check passes. A truncated or corrupted packet is silently dropped and the decoder resynchronises on the next header.
  • The header test is word >> 4 == 0x8000000, so any of 0x80000000…0x8000000F is a valid header. The low nibble is the ASIC index.

Round-robin arbiter and event loss

 
  • The poll slot advances every clock, unconditionally, whether or not an event was found. A DV_x is therefore serviced within at most 4 clock cycles.
  • BUSY is high only while the 38 words are being written into the FIFO.
  • The whole event is latched in one cycle, so the analog block may drop DV as soon as it sees ACK without any risk of tearing.

FIFO thresholds

With memLength = Samples and MaxPacketSize = 38:

XPM parameter Value Effect
FIFO_WRITE_DEPTH Samples total depth in 32-bit words
PROG_FULL_THRESH Samples − 38 drives FULL / FIFO_FULL, one packet of headroom
PROG_EMPTY_THRESH 38 drives DATA_AVAL and STATUS[0]
READ_MODE "std", latency 1 standard (non-FWFT) read
CDC_SYNC_STAGES 2 write and read clocks are independent

So DATA_AVAL (and the software “data available” bit) only rises once more than one full packet is buffered — the host is never woken up for a partial packet. Capacity in events is Samples / 38: 215 packets at the default 8192.

Control and status registers

CompileMMC declares one FIFO-read endpoint named after the EndpointName property, with two auto-registers.

CONTROL (write)

Bit Function
0 Mask ASIC A — 1 = ignore DV_A, its events are never collected
1 Mask ASIC B
2 Mask ASIC C
3 Mask ASIC D
8 Reset / flush — holds the arbiter and the FIFO in reset and clears the event counter

Note the polarity: a mask bit set disables that ASIC. Writing 0 therefore enables all four and releases the reset, which is exactly what the generated _START function does.

STATUS (read)

Bits Function
0 Data available (1 = more than one packet buffered)
16 + rBits − 1 : 16 Words currently in the FIFO

The FLUSH pin is ORed with CONTROL[8]: both reset the arbiter and flush the FIFO.

Generated software API

The C library gets five functions, named after the endpoint (CitirocFrame0 by default):

Function What it does
CitirocFRAME_<name>_START(handle) writes 0 to CONTROL — releases the reset and unmasks all four ASICs
CitirocFRAME_<name>_RESET(handle) writes 0x100 to CONTROL — asserts the flush and holds it
CitirocFRAME_<name>_GetStatus(&is_data_available, &word_available, handle) reads STATUS; is_data_available = STATUS & 1, word_available = (STATUS >> 16) & 0xFFFF
CitirocFRAME_<name>_DOWNLOAD(val, size, timeout, handle, &read_data, &valid_data) bulk FIFO read; val must be preallocated to size + 16 words
CitirocFRAME_<name>_RECONSTRUCT_DATA(buffer_handle, &decoded_packets) runs the state machine above over a circular buffer and fills a t_ASIC_packet_collection

RESET only asserts the flush — it does not release it. A RESET must always be followed by a START before data can flow.

The decoded structure carries, per packet: AsicID, Time_Code, RunTime_Code, Pack_Id, and three 32-element arrays Hit[], ChargeHg[], ChargeLg[]. Release it with free_CitirocFRAME_packet_collection.

C usage

c
  uint32_t data_frame[100000];
uint32_t read_data_frame, valid_data_frame, valid_data_enqueued;
t_ASIC_packet_collection decoded_packets;
void *BufferDownloadHandler = NULL;

Utility_ALLOCATE_DOWNLOAD_BUFFER(&BufferDownloadHandler, 1024 * 1024);

Citiroc_ALLFIFO_FLUSH(&handle);
CitirocFRAME_CitirocFrame0_START(&handle);

while (1) {
    uint32_t is_data_available = 0, word_available = 0;
    CitirocFRAME_CitirocFrame0_GetStatus(&is_data_available, &word_available, &handle);
    if (!is_data_available) continue;

    /* ask for whole packets: 38 words each */
    CitirocFRAME_CitirocFrame0_DOWNLOAD(data_frame, N_Packet * 38, 1000,
                                        &handle, &read_data_frame, &valid_data_frame);

    Utility_ENQUEUE_DATA_IN_DOWNLOAD_BUFFER(BufferDownloadHandler, data_frame,
                                            valid_data_frame, &valid_data_enqueued);
    CitirocFRAME_CitirocFrame0_RECONSTRUCT_DATA(BufferDownloadHandler, &decoded_packets);

    for (int p = 0; p < decoded_packets.valid_packets; p++) {
        printf("ASIC %u  pack %u  t0 %u\n",
               decoded_packets.packets[p].AsicID,
               decoded_packets.packets[p].Pack_Id,
               decoded_packets.packets[p].Time_Code);
        for (int i = 0; i < 32; i++) {
            int ch = 31 - i;          /* arrival order is reversed, see above */
            printf("  ch%2d hit=%u hg=%5u lg=%5u\n", ch,
                   decoded_packets.packets[p].Hit[i],
                   decoded_packets.packets[p].ChargeHg[i],
                   decoded_packets.packets[p].ChargeLg[i]);
        }
    }
    free_CitirocFRAME_packet_collection(&decoded_packets);
}
  

Python usage

python
  while True:
    [err, data, read_data, valid_data] = CitirocFRAME_CitirocFrame0_GET_DATA(16, 1000, handle)
    if valid_data > 0:
        [AsicId, Time_Code, RunTime_Code, Pack_Id,
         ChargeHg, ChargeLg, Hit] = CitirocFRAME_CitirocFrame0_RECONSTRUCT_DATA(data, valid_data)
        for i in range(len(Pack_Id)):
            print(Pack_Id[i], Time_Code[i], ChargeHg[i])
  

..._GET_DATA(n_packet, timeout_ms, handle) sizes its request as n_packet * 38 words, which is the correct packet size for this block.

Typical wiring

   Citiroc Analog (A) ─DATA_A/TS_A/TS_T0_A/DV_A─►┌──────────────────┐
                    ◄────────────── ACK_A ─────│                  │
 Citiroc Analog (B) ──────────────────────────►│  Citiroc Frame   │──► FIFO endpoint ──► PC
 Citiroc Analog (C) ──────────────────────────►│    Transfer      │
 Citiroc Analog (D) ──────────────────────────►│                  │──► FULL / DATA_AVAL / BUSY
                                  FLUSH ──────►└──────────────────┘
  

Unused ASIC ports may be left unconnected — just mask them with CONTROL[0..3] so the arbiter never latches an undriven DV.

Resources & Timing

  • Latency: Up to 4 clock cycles from DV_x to ACK_x (round-robin poll), then 38 clock cycles to write the packet into the FIFO

  • Throughput: One 38-word packet per 38 write clocks, shared by the four ASICs

  • Emits pcores/CitirocFrameTransfer.vhd (entity CitirocFrameTransfer) from the plain CitirocFrameTransfer.vhd resource — not the _NEW variant, which belongs to the V2 block.
  • xpm_fifo_async with independent write (CLK) and read (READ_CLK) clocks; the compiler currently binds both to the project global clock.
  • One event = 38 words = 152 bytes on the wire.
  • _RESET asserts the flush and leaves it asserted; always follow it with _START.
  • Supported board: DT5550W-Citiroc1A (97567308-DA16-4E48-BB5E-ACE3E84DF06D). Not available inside subpages or state machines.