Skip to content

Commit fc25c20

Browse files
committed
Merge branch 'main' into maerhart-rtg-reductions
2 parents 0339ce5 + 54266a8 commit fc25c20

File tree

211 files changed

+9255
-2990
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

211 files changed

+9255
-2990
lines changed

.github/copilot-instructions.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copilot build & test guidance
2+
3+
- Default to the integration docker image in `CIRCT_INTEGRATION_IMAGE` (set by the Copilot setup workflow and the `utils/run-docker.sh` default; currently `ghcr.io/circt/images/circt-integration-test:v19.2`) when compiling or testing.
4+
- Run inside that image via `./utils/run-docker.sh ./utils/run-tests-docker.sh "$CIRCT_INTEGRATION_IMAGE"` or `docker run` with the repo root bind-mounted.
5+
- When cloning or checking out, ensure submodules are present (`git submodule update --init --recursive` if needed).
6+
- Configure builds from the repo root with Ninja, matching the README:
7+
```
8+
cmake -G Ninja llvm/llvm -B build \
9+
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
10+
-DLLVM_ENABLE_ASSERTIONS=ON \
11+
-DLLVM_TARGETS_TO_BUILD=host \
12+
-DLLVM_ENABLE_PROJECTS=mlir \
13+
-DLLVM_EXTERNAL_PROJECTS=circt \
14+
-DLLVM_EXTERNAL_CIRCT_SOURCE_DIR=$PWD \
15+
-DLLVM_ENABLE_LLD=ON
16+
```
17+
- Build everything with `ninja -C build check-circt`; use `ninja -C build bin/circt-opt` or `ninja -C build bin/firtool` for tool-only builds.
18+
- Keep Python bindings enabled when needed via `-DMLIR_ENABLE_BINDINGS_PYTHON=ON -DCIRCT_BINDINGS_PYTHON_ENABLED=ON`.
19+
- For PyCDE and the ESI runtime, add `-DCIRCT_ENABLE_FRONTENDS=PyCDE -DESI_RUNTIME=ON` (keep Python bindings on). Test with `ninja -C build check-pycde` (PyCDE only) and `ninja -C build check-pycde-integration` (these integration tests exercise both PyCDE and the ESI runtime and are the only ESIRuntime tests).
20+
- Prefer the integration image and the setup steps workflow for reliable dependencies; only fall back to host builds when explicitly requested.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Copilot setup steps
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
paths:
7+
- .github/workflows/copilot-setup-steps.yml
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
copilot-setup-steps:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: Pre-pull integration docker image
17+
env:
18+
CIRCT_INTEGRATION_IMAGE: ghcr.io/circt/images/circt-integration-test:v19.2
19+
run: |
20+
echo "CIRCT_INTEGRATION_IMAGE=$CIRCT_INTEGRATION_IMAGE" >> $GITHUB_ENV
21+
docker pull "$CIRCT_INTEGRATION_IMAGE"
22+
- name: Summarize defaults
23+
run: |
24+
{
25+
echo "### CIRCT Copilot setup"
26+
echo "- Integration image: $CIRCT_INTEGRATION_IMAGE"
27+
echo "- Default build directory: build"
28+
} >> "$GITHUB_STEP_SUMMARY"

.github/workflows/esiRuntimePublish.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ jobs:
1717
fail-fast: false
1818
matrix:
1919
config:
20-
- os: ubuntu-24.04
21-
cibw_build: cp38-manylinux_x86_64
2220
- os: ubuntu-24.04
2321
cibw_build: cp39-manylinux_x86_64
2422
- os: ubuntu-24.04
@@ -29,8 +27,8 @@ jobs:
2927
cibw_build: cp312-manylinux_x86_64
3028
- os: ubuntu-24.04
3129
cibw_build: cp313-manylinux_x86_64
32-
- os: windows-2022
33-
cibw_build: cp38-win_amd64
30+
- os: ubuntu-24.04
31+
cibw_build: cp314-manylinux_x86_64
3432
- os: windows-2022
3533
cibw_build: cp39-win_amd64
3634
- os: windows-2022
@@ -41,6 +39,8 @@ jobs:
4139
cibw_build: cp312-win_amd64
4240
- os: windows-2022
4341
cibw_build: cp313-win_amd64
42+
- os: windows-2022
43+
cibw_build: cp314-win_amd64
4444

4545
steps:
4646
- name: Get CIRCT
@@ -68,7 +68,7 @@ jobs:
6868
& "${env:VCPKG_INSTALLATION_ROOT}/vcpkg" --triplet x64-windows install zlib grpc
6969
7070
- name: Install cibuildwheel
71-
run: python -m pip install cibuildwheel==2.23.3
71+
run: python -m pip install cibuildwheel==3.3.0
7272

7373
- name: Build wheels
7474
if: runner.os != 'Windows'

docs/Dialects/FIRRTL/FIRRTLAnnotations.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,20 +1192,24 @@ Example:
11921192

11931193
#### ExtractGrandCentralAnnotation
11941194

1195-
| Property | Type | Description |
1196-
| --- | --- | --- |
1197-
| class | string | sifive.enterprise.grandcentral.ExtractGrandCentralAnnotation |
1198-
| directory | string | Directory where Grand Central outputs go, except a bindfile |
1199-
| filename | string | Filename with full path where the bindfile will be written |
1195+
| Property | Type | Description |
1196+
| --- | --- | --- |
1197+
| class | string | sifive.enterprise.grandcentral.ExtractGrandCentralAnnotation |
1198+
| directory | string | Directory where Grand Central outputs go, except a bindfile |
1199+
| filename | string | Optional filename with full path where the bindfile will be written |
12001200

12011201
This annotation controls where to "extract" Grand Central collateral from the
1202-
circuit. This annotation is mandatory and can only appear once if the full
1202+
circuit. This annotation is mandatory and can only appear once if the full
12031203
Grand Central transform pipeline is run in the SFC. (An error is generated by
12041204
the `ExtractGrandCentralCode` transform.)
12051205

12061206
The directory member has no effect on the filename member, i.e., the directory
12071207
will not be prepended to the filename.
12081208

1209+
The filename is optional. When specified, all binds will be emitted to that
1210+
file. When omitted, each module will have an associated bindfile in `directory`
1211+
named `<module>-bind.sv`.
1212+
12091213
Example:
12101214
```json
12111215
{

docs/PythonBindings.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ Running this script through `python3 silicon.py` should print the following MLIR
107107

108108
```mlir
109109
module {
110-
hw.module @magic(%a: i42, %b: i42) -> (c: i42) {
110+
hw.module @magic(in %a: i42, in %b: i42, out c: i42) {
111111
%0 = comb.xor %a, %b : i42
112112
hw.output %0 : i42
113113
}

docs/VerilogGeneration.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ The current set of "tool capability" Lowering Options is:
9191
like `assert`, `assume`, and `cover` will always be emitted with a label. If
9292
the statement has no label in the IR, a generic one will be created. Some EDA
9393
tools require verification statements to be labeled.
94+
* `disallowDeclAssignments` (default=`false`). If true, always emit assignments
95+
(both continuous and blocking) separately rather than inlining them in net and
96+
variable declarations.
9497

9598
The current set of "style" Lowering Options is:
9699

frontends/PyCDE/integration_test/esi_test.py

Lines changed: 219 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
from pycde.common import Constant, Input, Output
1111
from pycde.constructs import ControlReg, Mux, Reg, Wire
1212
from pycde.esi import ChannelService, FuncService, MMIO, MMIOReadWriteCmdType
13-
from pycde.types import (Bits, Channel, ChannelSignaling, UInt)
13+
from pycde.types import (Bits, Channel, ChannelSignaling, StructType, UInt,
14+
Window)
1415
from pycde.handshake import Func
1516

1617
import sys
@@ -137,6 +138,221 @@ def construct(ports):
137138
ChannelService.to_host(AppID("join_x"), f.x)
138139

139140

141+
# Define the struct with four fields
142+
FourFieldStruct = StructType({
143+
"a": Bits(32),
144+
"b": Bits(32),
145+
"c": Bits(32),
146+
"d": Bits(32),
147+
})
148+
149+
# Create a window that divides the struct into two frames
150+
windowed_struct = Window(
151+
"four_field_window", FourFieldStruct,
152+
[Window.Frame("frame1", ["a", "b"]),
153+
Window.Frame("frame2", ["c", "d"])])
154+
155+
156+
class WindowToStructFunc(Module):
157+
"""Exposes a function that accepts a windowed struct (four fields split into
158+
two frames) and returns the reassembled struct without windowing.
159+
160+
The input struct has four UInt(32) fields: a, b, c, d.
161+
The window divides these into two frames:
162+
- Frame 1: fields a and b
163+
- Frame 2: fields c and d
164+
165+
Frames arrive in-order. The function reads both frames, reassembles the
166+
complete struct, and outputs it.
167+
"""
168+
169+
clk = Clock()
170+
rst = Reset()
171+
172+
@generator
173+
def construct(ports):
174+
175+
# Result is the complete struct (no windowing)
176+
result_chan = Wire(Channel(FourFieldStruct))
177+
args = FuncService.get_call_chans(AppID("struct_from_window"),
178+
arg_type=windowed_struct,
179+
result=result_chan)
180+
181+
# State register to track which frame we're expecting (0 = frame1, 1 = frame2)
182+
expecting_frame2 = Reg(Bits(1),
183+
name="expecting_frame2",
184+
clk=ports.clk,
185+
rst=ports.rst,
186+
rst_value=0)
187+
188+
# Registers to hold the values from frame1
189+
a_reg = Reg(Bits(32),
190+
name="a_reg",
191+
clk=ports.clk,
192+
rst=ports.rst,
193+
rst_value=0)
194+
b_reg = Reg(Bits(32),
195+
name="b_reg",
196+
clk=ports.clk,
197+
rst=ports.rst,
198+
rst_value=0)
199+
200+
# Unwrap the incoming channel
201+
ready = Wire(Bits(1))
202+
window_data, window_valid = args.unwrap(ready)
203+
204+
# Unwrap the window to get the union of frames
205+
frame_union = window_data.unwrap()
206+
207+
# Extract data from both frames (only one is valid at a time based on state)
208+
# Access the frame structs through the union - the data is reinterpreted
209+
# based on which frame we're expecting
210+
frame1_data = frame_union["frame1"]
211+
frame2_data = frame_union["frame2"]
212+
213+
# When we receive frame1, store a and b
214+
got_frame1 = window_valid & ~expecting_frame2
215+
a_reg.assign(Mux(got_frame1, a_reg, frame1_data.a))
216+
b_reg.assign(Mux(got_frame1, b_reg, frame1_data.b))
217+
218+
# When we receive frame2, we can output the complete struct
219+
got_frame2 = window_valid & expecting_frame2
220+
221+
# Update state: after receiving frame1, expect frame2; after frame2, expect frame1
222+
expecting_frame2.assign(
223+
Mux(window_valid, expecting_frame2, ~expecting_frame2))
224+
225+
# Output the reassembled struct when we have frame2
226+
output_struct = FourFieldStruct({
227+
"a": a_reg,
228+
"b": b_reg,
229+
"c": frame2_data["c"],
230+
"d": frame2_data["d"]
231+
})
232+
result_internal, result_ready = Channel(FourFieldStruct).wrap(
233+
output_struct, got_frame2)
234+
235+
# We're ready to accept when either:
236+
# - We're waiting for frame1 (always ready)
237+
# - We're waiting for frame2 and downstream is ready
238+
ready.assign(~expecting_frame2 | result_ready)
239+
result_chan.assign(result_internal)
240+
241+
242+
class StructToWindowFunc(Module):
243+
"""Exposes a function that accepts a complete struct and returns it as a
244+
windowed struct split into two frames.
245+
246+
This is the inverse of WindowedStructFunc.
247+
248+
The input struct has four Bits(32) fields: a, b, c, d.
249+
The output window divides these into two frames:
250+
- Frame 1: fields a and b
251+
- Frame 2: fields c and d
252+
253+
The function reads the complete struct, then outputs two frames in order.
254+
"""
255+
256+
clk = Clock()
257+
rst = Reset()
258+
259+
@generator
260+
def construct(ports):
261+
# Result is the windowed struct
262+
result_chan = Wire(Channel(windowed_struct))
263+
args = FuncService.get_call_chans(AppID("struct_to_window"),
264+
arg_type=FourFieldStruct,
265+
result=result_chan)
266+
267+
# State register to track which frame we're sending (0 = frame1, 1 = frame2)
268+
sending_frame2 = Reg(Bits(1),
269+
name="sending_frame2",
270+
clk=ports.clk,
271+
rst=ports.rst,
272+
rst_value=0)
273+
274+
# Register to indicate we have a valid struct to send
275+
have_struct = Reg(Bits(1),
276+
name="have_struct",
277+
clk=ports.clk,
278+
rst=ports.rst,
279+
rst_value=0)
280+
281+
# Registers to hold the input struct fields
282+
a_reg = Reg(Bits(32),
283+
name="a_reg",
284+
clk=ports.clk,
285+
rst=ports.rst,
286+
rst_value=0)
287+
b_reg = Reg(Bits(32),
288+
name="b_reg",
289+
clk=ports.clk,
290+
rst=ports.rst,
291+
rst_value=0)
292+
c_reg = Reg(Bits(32),
293+
name="c_reg",
294+
clk=ports.clk,
295+
rst=ports.rst,
296+
rst_value=0)
297+
d_reg = Reg(Bits(32),
298+
name="d_reg",
299+
clk=ports.clk,
300+
rst=ports.rst,
301+
rst_value=0)
302+
303+
# Unwrap the incoming channel
304+
ready = Wire(Bits(1))
305+
struct_data, struct_valid = args.unwrap(ready)
306+
307+
# Get the lowered type (a union of frame structs)
308+
lowered_type = windowed_struct.lowered_type
309+
310+
# Create frame1 and frame2 data
311+
frame1_struct = lowered_type.frame1({"a": a_reg, "b": b_reg})
312+
frame2_struct = lowered_type.frame2({"c": c_reg, "d": d_reg})
313+
314+
# Select which frame to output based on state
315+
frame1_union = lowered_type(("frame1", frame1_struct))
316+
frame2_union = lowered_type(("frame2", frame2_struct))
317+
318+
# Mux between frames based on state
319+
output_union = Mux(sending_frame2, frame1_union, frame2_union)
320+
output_window = windowed_struct.wrap(output_union)
321+
322+
# Output is valid when we have a struct to send
323+
output_valid = have_struct
324+
result_internal, result_ready = Channel(windowed_struct).wrap(
325+
output_window, output_valid)
326+
327+
# Compute state transitions
328+
frame_sent = output_valid & result_ready
329+
store_struct = struct_valid & ~have_struct
330+
done_sending = frame_sent & sending_frame2
331+
332+
# Store the incoming struct when we receive it and aren't busy
333+
a_reg.assign(Mux(store_struct, a_reg, struct_data["a"]))
334+
b_reg.assign(Mux(store_struct, b_reg, struct_data["b"]))
335+
c_reg.assign(Mux(store_struct, c_reg, struct_data["c"]))
336+
d_reg.assign(Mux(store_struct, d_reg, struct_data["d"]))
337+
338+
# have_struct: set when storing, clear when done sending both frames
339+
have_struct.assign(
340+
Mux(store_struct, Mux(done_sending, have_struct,
341+
Bits(1)(0)),
342+
Bits(1)(1)))
343+
344+
# sending_frame2: set after sending frame1, clear after sending frame2
345+
sending_frame2.assign(
346+
Mux(frame_sent & ~sending_frame2,
347+
Mux(done_sending, sending_frame2,
348+
Bits(1)(0)),
349+
Bits(1)(1)))
350+
351+
# We're ready to accept a new struct when we don't have one
352+
ready.assign(~have_struct)
353+
result_chan.assign(result_internal)
354+
355+
140356
class Top(Module):
141357
clk = Clock()
142358
rst = Reset()
@@ -148,6 +364,8 @@ def construct(ports):
148364
MMIOClient(i)()
149365
MMIOReadWriteClient(clk=ports.clk, rst=ports.rst)
150366
ConstProducer(clk=ports.clk, rst=ports.rst)
367+
WindowToStructFunc(clk=ports.clk, rst=ports.rst)
368+
StructToWindowFunc(clk=ports.clk, rst=ports.rst)
151369

152370
# Disable broken test.
153371
# Join(clk=ports.clk, rst=ports.rst)

0 commit comments

Comments
 (0)