xls
xls copied to clipboard
enhancement: Unions in DSLX language
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.
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