Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added function call design #119

Merged
merged 21 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions design/func_call/dup_range.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*

# Python Function
@verilogify(
mode=Modes.OVERWRITE,
module_output="./design/func_call/dup_range.sv",
testbench_output="./design/func_call/dup_range_tb.sv",
optimization_level=0,
)
def dup_range(base, limit, step):
counter = base
inst = 0 # fake generator
while counter < limit:
value = inst # fake iter
yield value
yield value
counter += step


# Test Cases
print(list(dup_range(*(0, 10, 2))))

*/

module dup_range (
// Function parameters:
input wire signed [31:0] base,
input wire signed [31:0] limit,
input wire signed [31:0] step,

input wire _clock, // clock for sync
input wire _reset, // set high to reset, i.e. done will be high
input wire _start, // set high to capture inputs (in same cycle) and start generating

// Implements a ready/valid handshake based on
// http://www.cjdrake.com/readyvalid-protocol-primer.html
input wire _ready, // set high when caller is ready for output
output reg _valid, // is high if output is valid

output reg _done, // is high if module done outputting

// Output values as a tuple with respective index(es)
output reg signed [31:0] _0
);
localparam _state_1 = 0;
localparam _state_0_while = 1;
localparam _state_0_while_3 = 2;
localparam _state_fake = 3;
localparam _state_0_while_0 = 4;
localparam _state_0_while_2 = 5;
localparam _state_2 = 6;
localparam _state_0_while_1 = 7;
// Global variables
reg signed [31:0] _value;
reg signed [31:0] _counter;
reg signed [31:0] _state;
reg signed [31:0] _base;
reg signed [31:0] _limit;
reg signed [31:0] _step;

// _hrange_inst
hrange _inst (
._clock(_clock),
._start(_hrange_inst__start),
._reset(1'b0),
._ready(_hrange_inst__ready),
.base(_hrange_inst_base),
.limit(_hrange_inst_limit),
.step(_hrange_inst_step),
._done(_hrange_inst__done),
._valid(_hrange_inst__valid),
._0(_hrange_inst__0)
);
// Standard
reg _hrange_inst__start;
reg _hrange_inst__reset;
reg _hrange_inst__ready;
reg _hrange_inst__done;
reg _hrange_inst__valid;
// Inputs
reg signed [31:0] _hrange_inst_base;
reg signed [31:0] _hrange_inst_limit;
reg signed [31:0] _hrange_inst_step;
// Outputs
wire signed [31:0] _hrange_inst__0;

always @(posedge _clock) begin
_done <= 0;
if (_ready) begin
_valid <= 0;
end
// Start signal takes precedence over reset
if (_reset) begin
_state <= _state_fake;
end
if (_start) begin
_base <= base;
_limit <= limit;
_step <= step;
_counter <= base;
_state <= _state_1;
end else begin
// If ready or not valid, then continue computation
if ($signed(_ready || !(_valid))) begin
case (_state)
_state_0_while_0: begin
_counter <= $signed(_counter + _step);
_state <= _state_0_while;
end
_state_0_while_1: begin
_0 <= _value;
_valid <= 1;
_state <= _state_0_while_0;
end
_state_0_while_2: begin
_0 <= _value;
_valid <= 1;
_state <= _state_0_while_1;
end
_state_0_while_3: begin
_hrange_inst__start <= 0;
_hrange_inst__ready <= 1;
if (_hrange_inst__ready && _hrange_inst__valid) begin
_value <= _hrange_inst__0;
_hrange_inst__ready <= 0;
_state <= _state_0_while_2;
end
if (_hrange_inst__done) begin
_state <= _state_fake;
_hrange_inst__ready <= 0;
end
end
_state_fake: begin
_done <= 1;
_state <= _state_fake;
end
_state_0_while: begin
if ((_counter < _limit)) begin
_state <= _state_0_while_3;
end else begin
_state <= _state_fake;
end
end
_state_1: begin
_hrange_inst_base <= _base;
_hrange_inst_limit <= _limit;
_hrange_inst_step <= _step;
_hrange_inst__start <= 1;
_hrange_inst__ready <= 0; // optimizer pass should combine this state with next
_state <= _state_0_while_3;
end
endcase
end
end
end
endmodule
55 changes: 55 additions & 0 deletions design/func_call/dup_range_tb.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
module dup_range_tb (
);
reg _clock;
reg _start;
reg _reset;
reg _ready;
reg signed [31:0] base;
reg signed [31:0] limit;
reg signed [31:0] step;
wire _done;
wire _valid;
wire signed [31:0] _0;
dup_range DUT (
._clock(_clock),
._start(_start),
._reset(_reset),
._ready(_ready),
.base(base),
.limit(limit),
.step(step),
._done(_done),
._valid(_valid),
._0(_0)
);
always #5 _clock = !_clock;
initial begin
_clock = 0;
_start = 0;
_ready = 1;
_reset = 1;
@(negedge _clock);
_reset = 0;
// ============ Test Case 0 with arguments (0, 10, 2) ============
base = $signed(0);
limit = $signed(10);
step = $signed(2);
_start = 1;
@(negedge _clock);
base = 'x; // only need inputs when start is set
limit = 'x; // only need inputs when start is set
step = 'x; // only need inputs when start is set
_start = 0;
while ($signed(!(_done) || !(_ready))) begin
// `if (_ready && _valid)` also works as a conditional
if (_ready && _valid) begin
$display("%0d, %0d, %0d", _valid, _ready, _0);
end
@(negedge _clock);
end
if (_ready && _valid) begin
$display("%0d, %0d, %0d", _valid, _ready, _0);
end
$finish;
end
endmodule
152 changes: 152 additions & 0 deletions design/func_call/func_call.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Function Calls
WorldofKerry marked this conversation as resolved.
Show resolved Hide resolved

## Abstract

Function calls are a crutial part of programming languages.

How can this abstraction be converted to hardware?

## Example

Let there be a function `hrange`, which is a more basic implementation of the built-in `range` function. This function is already [convertible by the tool](./hrange.sv).

```python
@verilogify(mode=Modes.OVERWRITE)
def hrange(base, limit, step):
i = base
while i < limit:
yield i
i += step
```

Lets make another function called `dup_range` that calls `hrange`, and uses its outputs in its own yields.

```python
@verilogify(mode=Modes.OVERWRITE)
def dup_range(base, limit, step):
inst = hrange(base, limit, step) # 1
for value in inst: # 2
yield value # 3
yield value + 1 # 4
```

How can we convert the function all portion of this into Verilog?

```verilog
module dup_range(
// ...
);
// ...
hrange _inst(
.base(_hrange_inst_base),
.limit(_hrange_inst_limit),
.step(_hrange_inst_step),
._0(_hrange_inst__0),
._ready(_hrange_inst__ready),
// ...
)
// ...
case (_state) begin
_state_1: begin // instantiation node
_hrange_inst_base <= _base;
_hrange_inst_limit <= _limit;
_hrange_inst_base <= _step;
_hrange_inst__start <= 1;
_hrange_inst__ready <= 0;
_state <= _state_2;
end
_state_2: begin // iterate node
_hrange_inst__ready <= 1;
if (_hrange_inst__ready && _hrange_inst__valid) begin
_value <= _hrange_inst__0;
_state <= _state_3;
_hrange_inst__ready <= 0;
end
if (_hrange_inst__done) begin
_state <= _state_done;
_hrange_inst__ready <= 0;
end
end
_state_3: begin
valid <= 1;
_0 <= value;
_state <= _state_4;
end
_state_4: begin
valid <= 1;
_0 <= value + 1;
_state <= _state_2;
end
_state_done: // ...
// ...
endmodule
```

## Proposal

Two new nodes will need to be added to the intermediate representation, to represent instantiating and iterating a generator respectively.

A new variable type will have to be added for generator instances

### Instantiation Node

Represents the instantiation of a generator in Python, e.g. `inst = hrange(a, b, c)`.
Represents a module that has been "started" in Verilog.

Also encapsulates the arguments passed in by the callee.

Members:

- Instance name
- Arguments Passed
- Callee function/module name

### Iterate Node

For a instance `inst` of generator `hrange`, and caller variables `a` and `b`, this node represents a

- `a, b = next(inst)` call on a generator instance, or a
- `for a, b in inst`, or a
- portion of a `yield from inst`

Members:

- Instance name, e.g. `inst`
- Output mapping, e.g. in the above example `a` maps to [`_0`](./hrange.sv) of `hrange`

### Generator Instance Variable

Abstraction for a variable that holds a generator instance.
Represented by a module in Verilog.

Members:

- name
- python function "pointer", for key in the namespace

## Timeline

### Milestone 1 - Concreteness

1. Finish Verilog pseudocode and verify - 1 point
1. Draw intermdiate representation for `dup_range` - 1 point

### Milestone 2 - Preparation

1. Update tests to support multiple functions in one file - 2 points
1. Reword `Var` class to handle different types (or allow for inheritance) - 3 points

### Milestone 3 - Function Calls

1. (Optional) improve graph visualzation tooling if debugging becomes inefficient - 2 points
1. Add the two new nodes and generator instance variable - 3 points
1. including handling it in the Verilog codegen
1. Add function parse ability to frontend - 2 points
1. start with only supporting `inst = gen(a, b, c); for x, y in inst: ...`
1. (Optional) add support for `yield from` and `for x, y in gen(a, b, c)`

## Finish Verilog pseudocode and verify

```bash
iverilog -s dup_range_tb ./design/func_call/dup_range.sv ./design/func_call/dup_range_tb.sv ./design/func_call/hrange.sv -g2005-sv -o output.log && vvp output.log
```
Loading
Loading