lkolbly / ripstop

Apache License 2.0
0 stars 0 forks source link

bits<N> primitive type #3

Closed lkolbly closed 2 years ago

lkolbly commented 2 years ago

We want arrays of bits, of potentially arbitrary dimensions.

Trivially, we want 1-dimensional bit arrays, because those behave like numbers. For example, you might represent a 32-bit number like so in Verilog:

reg [31:0] number;

I think we can take a similar approach, and attach it to the type:

bits<32> number;

Note the use of angle brackets, rather than square brackets. Also notice the pluralization of bits. Hopefully that isn't too subtle for the user? Notionally, I think this makes sense as a specific example of "const generics": i.e., bits<32> is a distinct type (distinct from bit, which is really an alias of bits<1>, and also distinct from e.g. bits<31>).

Compare that to the more intuitive syntax we might choose:

bit number[32];

This is clearly an "array of bits." But the intent is that you be able to operate on this array of bits - for example, number[t] + number[t] would then be adding two arrays of bits - what are the semantics of adding arrays of things? How does carry work with that? My intuition would be that it would be an element-wise add, which isn't what we want.

If we make bits<32> its own type, then we're good to go - it's clear what the semantics of bits<32> + bits<32> is.

There is the question of what type the result should be, bits<32> + bits<32> could reasonably be either bits<33> or bits<32>. I think it should be the latter, bits<32>.

With the addition of a new type, comes the need for type safety. You shouldn't be able to assign a bits<32> to a bit, so for example this code:

module add(bits<32> a, bits<32> b) -> (bit c) {
   c[t] = a[t] + b[t];
}

should fail with an error message like:

2: Incompatible assignment, left hand side is bit right hand side is bits<32>.

or something.

I don't think assigning a bits<32> to a bits<16> should be allowed. Or, even, assigning a bits<32> to a bits<33>. In general I think we should try to have strict type safety. But, you still need to be able to express those concepts.

Verilog has a syntax for concatenating bits:

{a, b, c}

concatenates a, b, and c. This is in my mind a fine syntax, so we could have:

module concat(bits<16> a, bits<8> b) -> (bits<24> c) {
   c[t] = {a[t], b[t]};
}

Note that, in general, the type of {bits<N>, bits<M>} is bits<N+M>.

Another alternative syntax we could use is ++:

module concat(bits<16> a, bits<8> b) -> (bits<24> c) {
   c[t] = a[t] ++ b[t];
}

This has the advantage that it's just another operator, and reserves the {a, b, c, d} syntax for creating arrays.

I'm not sure which syntax is better yet, maybe I'll make a poll in Slack. (fun fact, the VHDL operator for concatenation is &, which is... confusing to me...)

If the user wanted to add two 32-bit numbers, keeping the carry, they could do something like:

module add_with_carry(bits<32> a, bits<32> b) -> (bits<33> c) {
   c[t] = (0'b1 ++ a[t]) + (0'b1 ++ b[t]);
}

(notice the literal syntax from issue #4)

The user could also want to take just the top number of bits, or some slice of bits in the middle (this is a common operation in instruction decoding). I think for this we can go with the bog-standard square bracket indexing, some examples:

bits<32>[5] -> bit
bits<16>[15] -> bit
bits<16>[16] // Compile error
bits<16>[5:0] -> bits<6> // Lowest 6 bits
bits<16>[15:10] -> bits<6> // Highest 6 bits

Note the use of the decreasing index - where python would do something like array[0:5], it's customary in Verilog to do the high bits first. This almost certainly is because (or maybe it inspired) the convention in most datasheets, which shows the low bits on the right: image (from https://ww1.microchip.com/downloads/en/DeviceDoc/25095A.pdf)

Also note that the indices are inclusive on both sides.

An example to bring it all together:

module j_type_immediate(bits<32> instruction) -> (bits<32> imm) {
   // See the J-type format on page 12 figure 2.3 of https://riscv.org/wp-content/uploads/2017/05/riscv-spec-v2.2.pdf
   imm[t] = instruction[t][31] ++ instruction[t][19:12] ++ instruction[t][20] ++ instruction[t][30:21] ++ 1'b0;
}

should generate verilog code like:

module j_type_immediate(
  input[31:0] instruction,
  output[31:0] imm
);

  assign imm = {instruction[31], instruction[19:12], instruction[20], instruction[30:21], 1'b0};

endmodule