google / google/xls

enhancement: Unions in DSLX language

Open
#1,306 2 comments 1 reaction 0 assignees View on GitHub
dslx enhancement long-term-enhancement 🧦 sox
Dominant language
C++
Stars
1.9k
Forks
283
Avg merge
2d 10h
Merged PRs (30d)
135

Description

# 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](https://github.com/google/xls/assets/52699314/edf23cbb-c824-4436-b7c0-e169e1e45054)

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:
```rust
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 in;
outputA_s: chan out;
outputB_s: chan out;

init {}

config(
input_r: chan in,
outputA_s: chan out,
outputB_s: chan 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:

```rust
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 in;
outputA_s: chan out;
outputB_s: chan out;

init {}

config(
input_r: chan in,
outputA_s: chan out,
outputB_s: chan 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

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.