google / xls

XLS: Accelerated HW Synthesis
http://google.github.io/xls/
Apache License 2.0
1.16k stars 166 forks source link

enhancement: Unions in DSLX language #1306

Open rw1nkler opened 5 months ago

rw1nkler commented 5 months ago

Unions in DSLX language

While working on DSLX designs, we found that adding unions could simplify the description of some design patterns, that currently require more elaborate implementation.

We found that having unions would help as while working on a design that included a packet decoding pipeline. The Proc used in our design received information on its input, and dispatched the packets to dedicated processing blocks depending on the information from the headers available in the input stream. The processing blocks handled the packet properly and returned processed data to the next steps of the pipeline.

packets

To handle this use case, we found C tagged union packet useful. We packed the data into a single structure, with an enum that allowed us to decode the block correctly in the following stages of the pipeline.

Due to a lack of union support, we created dedicated functions that can reinterpret the bit vector underneath. Unions could simplify this code.

For the mentioned case, the Dispatcher Proc without unions could look like this:

enum PacketType: u2 {
    A = 0,
    B = 1
}

struct Packet {
  p_type: PacketType,
  payload: bits[128],
}

struct PacketA {
  id: u32,
  base: s32,
  pow: s32
}

struct PacketB {
  id: u32,
  var: u32,
  a: u32,
  b: u32,
}

fn convert_to_packetA(payload: bits[128]) -> PacketA {
    let id = payload[0:32] as u32;
    let base = payload[32:64] as s32;
    let pow = payload[64:96] as s32;
    PacketA {id, base, pow}
}

fn convert_to_packetB(payload: bits[128]) -> PacketB {
    let id = payload[0:32];
    let var = payload[32:64];
    let a = payload[64:96];
    let b = payload[96:128];
    PacketB {id, var, a, b}
}

proc PacketDispatcher {
    input_r: chan<Packet> in;
    outputA_s: chan<PacketA> out;
    outputB_s: chan<PacketB> out;

    init {}

    config(
        input_r: chan<Packet> in,
        outputA_s: chan<PacketA> out,
        outputB_s: chan<PacketB> out,
    ) {
        (input_r, outputA_s, outputB_s)
    }

    next(tok: token, state:()) {
        let (tok, packet) = recv(tok, input_r);
        let packetA = convert_to_packetA(packet.payload);
        let tok = send_if(tok, outputA_s, packet.p_type == PacketType::A, packetA);
        let packetB = convert_to_packetB(packet.payload);
        let tok = send_if(tok, outputB_s, packet.p_type == PacketType::B, packetA);
    }
}

The ability to use unions could simplify the code to:

enum PacketType: u2 {
    A = 0,
    B = 1
}

struct PacketA {
  id: u32,
  base: s32,
  pow: s32
}

struct PacketB {
  id: u32,
  var: u32,
  a: u32,
  b: u32,
}

union PacketVariant {
    variant_a: PacketA,
    variant_b: PacketB
}

struct Packet {
  type: PacketType,
  payload: PacketVariant,
}

proc PacketDispatcher {
    input_r: chan<Packet> in;
    outputA_s: chan<PacketA> out;
    outputB_s: chan<PacketB> out;

    init {}

    config(
        input_r: chan<Packet> in,
        outputA_s: chan<PacketA> out,
        outputB_s: chan<PacketB> out,
    ) {
        (input_r, outputA_s, outputB_s)
    }

    next(tok: token, state:()) {
        let (tok, packet) = recv(tok, input_r);
        let tok = send_if(tok, outputA_s, packet.p_type == PacketType::A, packet.variant_a);
        let tok = send_if(tok, outputB_s, packet.p_type == PacketType::B, packet.variant_b);
    }
}

Unions can be especially useful when we need to send different types of data through channels. They can also be convenient when we want to store information as part of a state, where each state requires a different type of data. Currently, these use cases can be handled by manually casting the data, but adding unions could make it easier to express these concepts

cdleary commented 5 months ago

Agreed! Surprised I didn't already have a bug filed for add sum types.

Note that one of the complexities for hardware is what to do if an invalid-tagged value comes in on some interface -- when it's XLS components interacting with other XLS components this is less of a concern (so long as the toolchain is correct), but for any external channel where the outside world can masquerade a bunch of bits as a "supposedly validated" structured value it's more interesting.

cdleary commented 4 months ago

Going to mark as long term for now just because it's not actively in the queue to get implemented, but I agree it's high up there in the "pending features we'd really benefit from" queue.