-
Notifications
You must be signed in to change notification settings - Fork 14
Bootcamp FAQ
Some useful questions on ROHD.
Is ROHD only support logic data type? In System Verilog, there are other data types such as reg
, wire
, integer
, real
, time
, realtime
, bit
, byte
, shortint
, int
, longint
, shortreal
and tri
.
As of current, ROHD does not support variables other than logic. There's no need wire, or reg in SystemVerilog (or ROHD). ROHD doesn't support tristate, inout, etc. (yet), though it's feasible to add it.
How to create packed/unpacked port? There are some examples in System Verilog where you can create multidimensional ports as below:
input bit inbitA,
output bit outbitA,
input byte inbyteA,
output byte outbyteA,
input logic inlogicA,
output logic outlogicA,
input logic [15:0] in16logicA,
output logic [15:0] out16logicA,
input logic [3:0][2:0] in2DA,
output logic [3:0][2:0] out2DA,
input logic [1:0][3:0][2:0] inMDA,
output logic [1:0][3:0][2:0] outMDA,
input logic [2:0] inMDAUP [1:0][3:0],
output logic [2:0] outMDAUP [1:0][3:0],
Multi-dimensional signals are not supported (yet) in ROHD, though you can create Dart List<Logic>
s of signals, or List<List<List<Logic>...>>>
s for many dimensions. This enables all the built-in Dart list and iterable operations on collections of signals. It's a limitation for connecting to port lists already written in systemverilog though, so support will be added eventually.
Does ROHD support [WIDTH-1:0]
initialization, where you override the WIDTH parameter to 1?
ROHD actually even supports 0-width signals, which you can't do in SystemVerilog, and will just omit those signals with 0 width when generating SV. Yes, there's no need to support [0:0] instead of just [0].
What is the maximum number of BigInt
in ROHD can support?
The underlying implementation in ROHD for large LogicValues
uses Dart's BigInt
implementation (with some extra magic for X and Z). Dart BigInt
's can be really big, as big as your computer can handle.
Big integers are signed and can have an arbitrary number of significant digits, only limited by memory.
https://api.flutter.dev/flutter/dart-core/BigInt-class.html
Why didn't the generated RTL assign value when using .put()
?
.put()
is non-synthesizable signal deposition (think testbench poke). That is why the generated RTL code does not assign value.
Is there any difference when execute ROHD code on web browser (Github Codespace) vs native platform?
The codespace is not really running in-browser, it's connected to a server hosted by GitHub with the front-end server only hosted in the browser. You're running on a "real" system, not just a javascript engine. Thefore, its won't be any difference running ROHD code on Github codespace vs native platform.
Note about javascript have to do with the fact that Dart can also be compiled directly into native javascript, which we're not doing in any of these tutorials.
Why does the generated RTL code not show the assign
statement when using Const()
in ROHD?
The assign
statement will only be shown if you declare Const()
in the module class.
import 'package:rohd/rohd.dart';
void main() async {
final a = Const(5, width: 16);
// Instantiate Module and display system verilog
final constantLogic = ConstantValue(a);
await constantLogic.build();
print(constantLogic.generateSynth());
}
class ConstantValue extends Module {
Logic get a => output('a');
ConstantValue(Logic val) : super(name: 'const_val') {
final a = addOutput('a', width: val.width); // Output instead of Input
a <= val;
}
}
Output:
/**
* Generated by ROHD - www.github.com/intel/rohd
* Generation time: 2023-03-13 13:40:24.710
* ROHD Version: 0.4.2
*/
module ConstantValue(
output logic [15:0] a
);
assign a = 16'h5;
endmodule : ConstantValue
In the ROHD documentation, there is no mention of blocking and non-blocking assignments, as there is in System Verilog (https://nandland.com/blocking-vs-nonblocking-in-verilog/). The documentation only mentions the following:
To assign one signal to the value of another signal, use the
<=
operator. This is a hardware synthesizable assignment that connects two wires together. Assignments within an _Always block should be executed conditionally, so use the<
operator, which creates a ConditionalAssign object instead of<=
.
In System Verilog's always block, you have the option to use <=
or =
. There is a difference between <=
and <
in the always block because it depends on your sensitivity list and the sequence of how you place the line. It affects the simulation result. Outside of the always block, there is only a single equal sign.
The question is, how does ROHD know when to use <=
or <
and how to use blocking and non-blocking assignment in ROHD?
Refer: (https://github.com/intel/rohd/discussions/307)
<=
(assign
in Verilog) is used outside of Combinational
/Sequential
blocks.
<
(blocking/non-blocking assignment) is used inside Combinational
/Sequential
blocks.
For a blocking assignment use the Combinational
block, for a non-blocking assignment use the Sequential
block. ROHD does not allow mixing different assignment types in the same block.
More
ROHD has two logical assignment operators: <=
and <
.
<=
(similar to assign from Verilog) cannot be used inside Combinational
/Sequential
procedural blocks. It should behave like Combinational
with a single assignment.
<
is used for blocking or non-blocking assignment depending on the type of procedural block: Combinational
(blocking), Sequential
(non-blocking). Since ROHD clearly separates combinational (always_comb) and sequential (always_ff) logic, the user cannot mix different types of logic in the same procedural block (always) (see https://www.verilogpro.com/systemverilog-always_comb-always_ff/).
In ROHD, the width of the swizzle must be equal to the width of the output. The code below fails with error message.
Uncaught Error: Failed to put value on signal (f): Updated value width mismatch. The width of 10'bzzzz00111z should be 11
.
Will there be support that allows 0
to be added Infront if the width of the output is larger than the swizzle?
import 'package:rohd/rohd.dart';
void slicing(Logic a, Logic b, Logic c, Logic d, Logic e, Logic f) {
d <= b[7];
e <= [d, c, a].swizzle();
f <= [d, c, a].rswizzle();
}
void main() async {
// Declare Logic
final a = Logic(name: 'a', width: 4);
final b = Logic(name: 'b', width: 8);
final c = Const(7, width: 5);
final d = Logic(name: 'd');
final e = Logic(name: 'e', width: d.width + c.width + a.width);
// PURPOSELY ADD +1 here.
final f = Logic(name: 'f', width: d.width + c.width + a.width + 1);
final rangeSwizzling = RangeSwizzling(a, b, c, d, e, f, slicing);
await rangeSwizzling.build();
print(rangeSwizzling.generateSynth());
print('\n');
a.put(bin('1110'));
b.put(bin('11000100'));
print('e: ${rangeSwizzling.e.value.toString(includeWidth: false)}');
print('f: ${rangeSwizzling.f.value.toString(includeWidth: false)}');
}
class RangeSwizzling extends Module {
Logic get d => output('d');
Logic get e => output('e');
Logic get f => output('f');
RangeSwizzling(Logic a, Logic b, Logic c, Logic d, Logic e, Logic f,
void Function(Logic a, Logic b, Logic c, Logic d, Logic e, Logic f) slice)
: super(name: 'range_swizzling') {
a = addInput(a.name, a, width: a.width);
b = addInput(b.name, b, width: b.width);
c = addInput(c.name, c, width: c.width);
d = addOutput(d.name, width: d.width);
e = addOutput(e.name, width: e.width);
f = addOutput(f.name, width: f.width);
slice(a, b, c, d, e, f);
}
}
Reference: https://github.com/intel/rohd/discussions/308
There is a method zeroExtend.
f <= [d, c, a].rswizzle().zeroExtend(f.width);
Is it possible to access a range of indices in ROHD using syntax similar to Logic a[2:4] = '01'
like Python? The ROHD example,
e = e.withSet(3, d)
function only changes a single bit. What if we want to change multiple bits at once? Is it possible to have syntax similar to python like above?
Reference: https://github.com/intel/rohd/discussions/309
There are some restrictions related to the fact that this syntax is not planned to be added to Dart: https://github.com/dart-lang/sdk/issues/983#issuecomment-842176477.
You can change multiple bits as such:
import 'package:rohd/rohd.dart';
void main() {
final a = Logic(width: 8);
final b = Logic(width: 8);
b <= a.withSet(2, Const(31, width: 5));
a.put(0);
print(a.value.toString(includeWidth: false));
print(b.value.toString(includeWidth: false));
}
Output:
00000000
01111100
Reference: https://github.com/intel/rohd/discussions/320
In the unit test provided by ROHD, is there any way to see the reason/variables that cause the failure?
You can use the reason
named argument in expect
to print a more helpful message.
For example:
final expected = faTruthTable(i, j, k).sum;
final actual = sum.value.toInt();
expect(sum.value.toInt(), expected,
reason: 'For inputs a=$i, b=$j, cIn=$k,'
' expected sum=$expected but found $actual');
Now when the test fails it prints:
Expected: <0>
Actual: <1>
For inputs a=1, b=1, cIn=1, expected sum=0 but found 1
Reference: https://github.com/intel/rohd/discussions/331
How should I code my unit testing if the expected result is one clock later? Like how am I going to test for flip flop? What if the output is one clock or two clocks later?
Here's an example where you can use await
on the edges of the clock to drive stimulus and check values out of the flop.
import 'dart:async';
import 'package:rohd/rohd.dart';
import 'package:test/test.dart';
void main() {
test('test flop', () async {
final clk = SimpleClockGenerator(10).clk;
final input = Logic(width: 8);
final output = FlipFlop(clk, input).q;
/// Prints the current state of the flop.
void printFlop([String message = '']) {
print('@${Simulator.time}:\t'
' input=${input.value}, output=${output.value}\t$message');
}
// set a max time in case something goes longer than expected
Simulator.setMaxSimTime(100);
// kick off the simulator, but dont wait for it, so we can do other async
// stuff meanwhile.
unawaited(Simulator.run());
await clk.nextPosedge;
input.put(0);
// check after posedge sampling that the value matches expectations
await clk.nextPosedge;
printFlop('After sampling 0');
expect(output.value.toInt(), 0);
// drive a new value onto the flop
input.put(0xa5);
printFlop('After driving 0xa5, before posedge sampling');
await clk.nextPosedge;
printFlop('After driving 0xa5 and posedge sampling');
expect(output.value.toInt(), 0xa5);
// we're done, we can end the simulation
Simulator.endSimulation();
await Simulator.simulationEnded;
});
}
The output of this test looks like this:
@15: input=8'h0, output=8'h0 After sampling 0
@15: input=8'ha5, output=8'h0 After driving 0xa5, before posedge sampling
@25: input=8'ha5, output=8'ha5 After driving 0xa5 and posedge sampling
Reference: https://github.com/intel/rohd/discussions/332
In general, when should we create a class or function in dart/ROHD during the design? Any rule of thumb or best practices?
From ROHD's perspective, one time when you do want to make a class is when you want to define a Module
(e.g. class ___ extends Module
). When you want something to be a Module
is sometimes similar to when you want to make something a module
in SystemVerilog. For example, a top-level hierarchy or backend synthesis partition should be a Module
. A Module
always translates into a module
when converted to SystemVerilog.
SystemVerilog uses module
s and function
s as common ways to group functionality. In ROHD, you are not limited to only Module
s to group reusable functionality, and ROHD will not generate a SystemVerilog function
.
ROHD, as a generator framework, enables you to write software that generates hardware. You can use any types of software collections, architectures, design patterns, etc. including classes and functions to develop your hardware. The only time you need to explicitly define a Module
as a layer of hierarchy is when you actually want there to be a formal module boundary there. Any logic generated by your software has its hierarchy and placement determined by connectivity alone, so it will end up in the "appropriate" module hierarchy as long as you follow the input/output rules of ROHD (only consume input
s of a Module
within that Module
, and communicate externally through output
s of that Module
).
So to summarize, make a class
that extends Module
when you want an explicit hardware hierarchy level to be added. Otherwise, you can do whatever you like (including other classes and functions), since it's just software!
Question on System Verilog generated in NBitAdder
chapter 4 tutorial.
/**
* Generated by ROHD - www.github.com/intel/rohd
* Generation time: 2023-03-27 10:08:30.025 +08:00
* ROHD Version: 0.4.2
*/
module FullAdder(
input logic a,
input logic b,
input logic carry_in,
output logic carry_out,
output logic sum
);
assign carry_out = (carry_in & (a ^ b)) | (b & a); // or_
assign sum = (a ^ b) ^ carry_in; // xor__0
endmodule : FullAdder
////////////////////
module NBitAdder(
input logic [7:0] a,
input logic [7:0] b,
input logic carry_in
);
logic a_0;
logic b_0;
logic sum;
logic a_1;
logic b_1;
logic carry_in_1;
logic sum_0;
logic a_2;
logic b_2;
logic carry_in_2;
logic sum_1;
logic a_3;
logic b_3;
logic carry_in_3;
logic sum_2;
logic a_4;
logic b_4;
logic carry_in_4;
logic sum_3;
logic a_5;
logic b_5;
logic carry_in_5;
logic sum_4;
logic a_6;
logic b_6;
logic carry_in_6;
logic sum_5;
logic a_7;
logic b_7;
logic carry_in_7;
logic carry_out_6;
logic sum_6;
assign a_0 = a[0]; // bussubset
FullAdder full_adder(.a(a_0),.b(b_0),.carry_in(carry_in),.carry_out(carry_in_1),.sum(sum));
FullAdder full_adder_0(.a(a_1),.b(b_1),.carry_in(carry_in_1),.carry_out(carry_in_2),.sum(sum_0));
FullAdder full_adder_1(.a(a_2),.b(b_2),.carry_in(carry_in_2),.carry_out(carry_in_3),.sum(sum_1));
FullAdder full_adder_2(.a(a_3),.b(b_3),.carry_in(carry_in_3),.carry_out(carry_in_4),.sum(sum_2));
FullAdder full_adder_3(.a(a_4),.b(b_4),.carry_in(carry_in_4),.carry_out(carry_in_5),.sum(sum_3));
FullAdder full_adder_4(.a(a_5),.b(b_5),.carry_in(carry_in_5),.carry_out(carry_in_6),.sum(sum_4));
FullAdder full_adder_5(.a(a_6),.b(b_6),.carry_in(carry_in_6),.carry_out(carry_in_7),.sum(sum_5));
FullAdder full_adder_6(.a(a_7),.b(b_7),.carry_in(carry_in_7),.carry_out(carry_out_6),.sum(sum_6));
assign a_7 = a[7]; // bussubset_0
assign b_7 = b[7]; // bussubset_1
assign a_6 = a[6]; // bussubset_2
assign b_6 = b[6]; // bussubset_3
assign a_5 = a[5]; // bussubset_4
assign b_5 = b[5]; // bussubset_5
assign a_4 = a[4]; // bussubset_6
assign b_4 = b[4]; // bussubset_7
assign a_3 = a[3]; // bussubset_8
assign b_3 = b[3]; // bussubset_9
assign a_2 = a[2]; // bussubset_10
assign b_2 = b[2]; // bussubset_11
assign a_1 = a[1]; // bussubset_12
assign b_1 = b[1]; // bussubset_13
assign b_0 = b[0]; // bussubset_14
endmodule : NBitAdder
What is the engine behind that converts ROHD code to System Verilog code? How many lines of content can it handle, since the System Verilog generated is a string? How is it actually designed so that an n-bit adder is coded this way? Because if we have a 128-bit n-bit adder, we are not supposed to code it like that (ROHD currently generates System Verilog).
Because System Verilog also has a for loop, why is the generated System Verilog not using it? The currently generated System Verilog is not human-readable. Are there any commands that we can use to control the generated System Verilog code? Is it manipulation through configuration, or can we change the design? Or how does ROHD know that it should be this kind of design since there are so many ways to design in System Verilog?
One of the key features in ROHD is its one-to-one mapping, which enables you to do ECO. Is that the reason the System Verilog is being generated this way? If we want to code it in ECO, it's better to write in netlist. But if we write it in a netlist way, simulation cannot be optimized, so it actually slows down the simulation. When we don't go for gate-level simulation, every single bit is an And gate or Or gate, compared to the function of a + b. Simulation can be optimized for a + b, as we straight away know the function. And we need to register every single intermediate signal, and that's why it slows down the whole simulation, because every intermediate signal change is considered as an object that needs to be registered into memory, and there is a sequence of tick changes. It actually increases the tick of the time wheel, so that's why it slows down.
- What is the engine behind that convert ROHD code to System Verilog Code?
The default generator of SystemVerilog is a custom Dart-based generator included in ROHD. There are other Synthesizers being developed, for example one that uses CIRCT.
- How it actually design in the way that n-bit adder is coded this way? Because if we have 128 bits n-bit adder, we are not supposed to code like that (ROHD currently generated system verilog). Because System Verilog also have for loop. Why the generated System Verilog not using for loop.
ROHD is a hardware generator framework, not a SystemVerilog authoring framework. The generated SystemVerilog hardware matches exactly 1-to-1 with the creation of hardware instances in the original ROHD code. The code generated 8 FullAdder modules and connected them in a certain way, and that's whats mirrored in the generated SystemVerilog. There is no software reflection: the way you achieved the hardware has no impact on how the hardware is represented. ROHD does not convert a Dart for loop into a SystemVerilog for loop, but you could achieve that by making a custom module for it. It would probably have limited value, though, since you'd be additionally restrained on the method by which you could describe it in ROHD and you can't use SystemVerilog parameters either. Using parameters in the generated verilog would constrain what you can represent in ROHD to only what can be described in verilog.
- The current generated System Verilog is not human readable.
It may be more verbose than you would have written it yourself, but I think it is human readable. Module definitions, signal and port names, and hierarchy are all maintained here.
- Is there any commands that we can used to control the generated system Verilog code? Is it manipulation through configuration or we can change the design? Or how ROHD know that it should be this kind of design since there are so many ways to design in system Verilog.
You can name signals, ports, modules, and definitions as you wish in ROHD and they will translate accordingly. You can even reserve names to force names to not be uniquified (or else fail if it's not achievable). You can define custom modules if there's a specific SystemVerilog syntax you're looking to generate. Again, the way it is designed is exactly 1-to-1 with how the objects are constructed. There's no magic behind the scenes or optimization happening.
- Simulation can optimized, a + b
This example is a demonstration of designing a multi-bit adder using independent full adders. ROHD does support + syntax (and other math operators) to add signals together, and it will generate a + in the SystemVerilog as well.
- If we want to code it in ECO, better write in netlist.
ROHD generates things 1-to-1, so it's predictable what comes out based on your inputs. ECOing in ROHD is similar to ECOing in SystemVerilog. A netlist ECO would be done in a similar way to SystemVerilog as well (probably netlist ECO manually, then modify original representation and do formal equivalence checking). Generating a netlist out of ROHD would make simulation slower (as you said) and also make it much less human readable.
Let's say we want to test a 4-bit adder. How do we do it? Do we need to create a giant truth table? Are there any other better ways? What if we want to build not a simple formula of an adder? We have 4 bits of input (a) and 4 bits of input (b), and we have a different pattern of output. How should we code the expected table/value in the unit test? If our formula is not a simple a + b, maybe other things like a + 1 / 2 + something else (a complex formula) - it cannot be represented using a single mathematical calculation.
Our concern is that if we are the designer and we create the function test case for the design, and we are also the tester and we create the same function with the same algorithm in the testing, then we won't catch anything. This is the most feared scenario in validation when the designer and validation are the same single person working on the same pieces of code. Therefore, we need a different way of coding the testing so that they cannot just directly plug in the same function. For a big design, the designer cannot validate their own code. However, the intention of a unit test is to allow the designer to pre-test their code or test it as they code. If we end up using the same formula for both designer coding and testing, then there is no point in testing it since it's going to be always correct. If we get the wrong formula from the beginning, then everything is wrong from the beginning. Is there a different way to code the testing so that the designer doesn't have to use the same formula in design?
ROHD has mathematical operations in the same ways that SystemVerilog does, and it maps to those SystemVerilog operators in the same way. You can build your logic in whatever way suits you. Usually you would not define a large complex operation using a truth table (in either SystemVerilog or ROHD). ROHD has more options than SystemVerilog for generating complex hardware, but what's available in SystemVerilog is also available in ROHD.
What is happening when .build()
function is called?
The way that ROHD determine the design hierarchy module is by connectivity between objects within each module in the hierarchy build. So, when you called .build()
one of the main things that its does is searches throughout the entire design and build the hierarchy. So, until you called build, it does not have any context which module are inside of the other module.
So, if we put one module inside of another module. The way it determine its inside of the another module is by tracing the signal connectivity through input and output port.
What if one of my module is independent from each other? Mean I have one module which is separate from another module, do I have to call .build()
separately?
Yes, you need to call .build()
seperate if your module are not connecting with each other.
What if we want to generate one module one RTL? Does ROHD support that?
Yes, ROHD does support that by enabling an API called .SyncBuilder()
.
https://github.com/intel/rohd/discussions/353
Missing triple equal in ROHD. Double equal and triple equal are different. Triple equals can compare x to x, z to z. Triple equals will be seen in the analog behavioral code which they can compare x vs z which you use triple equal. If both side x considers true in triple equal, if both side x, output is still x.
If the triple equal is needed, then we will need to have !== as well (Triple not equal)
ROHD takes a different approach to how signals, values, and equality checks work than SystemVerilog.
Comparing the values of two Logic signals in a synthesizable way is done using Logic.eq. This will always represent a synthesizable comparison. In simulation, if either value is not valid, the resulting equality check will also be invalid.
Comparing whether two Logic objects are exactly the same is done using Logic.==. This is a pure-software object reference comparison.
Comparing whether two LogicValues have the same value is done using LogicValue.==. This checks that every bit of the value is identical, including invalid bits like x and z. Note that LogicValue is sort of like an arbitrary width, 4-value, immutable primitive. LogicValues are a software construct, and aren't synthesizable signals.
There's also LogicValue.eq, which will behave similarly to Logic.eq in that if any bit in either of the objects being compared is invalid, the result will also be invalid.
These mechanisms for comparison make it easy to tell when you're dealing with a value versus a signal, and hard to accidentally use something non-synthesizable in a hardware design.