Skip to content
Draft
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
60 changes: 60 additions & 0 deletions ip/bin/aegis_genip.dart
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,50 @@ Future<void> main(List<String> arguments) async {
serdesCount: serdesCount,
clockTileCount: clockTiles,
);
// Top-level assembly script (reads pre-synthesized tile macros)
File(
'$outputDir/${fpga.name}-yosys.tcl',
).writeAsStringSync(yosysEmitter.generate());
// Determine which macro modules actually exist in the generated SV
final svContent = File('$outputDir/${fpga.name}.sv').readAsStringSync();
final presentModules = YosysTclEmitter.macroModules
.where((mod) => svContent.contains('module $mod ('))
.toList();

// Per-tile synthesis scripts (only for modules that exist)
for (final mod in presentModules) {
File(
'$outputDir/${fpga.name}-yosys-$mod.tcl',
).writeAsStringSync(yosysEmitter.generateTileSynth(mod));
}
final stubs = StringBuffer();
stubs.writeln(
'// Auto-generated tile blackbox stubs for hierarchical synthesis',
);
stubs.writeln(
'// These are empty modules with matching ports so the top-level',
);
stubs.writeln(
'// synthesis can elaborate without processing tile internals.',
);
stubs.writeln();
for (final mod in presentModules) {
final modStart = svContent.indexOf('module $mod (');
if (modStart == -1) continue;

// Extract from "module X (" to the closing ");"
final portEnd = svContent.indexOf(');', modStart);
if (portEnd == -1) continue;

final decl = svContent.substring(modStart, portEnd + 2);
stubs.writeln('(* blackbox *)');
stubs.writeln(decl);
stubs.writeln('endmodule');
stubs.writeln();
}
File(
'$outputDir/${fpga.name}_tile_stubs.v',
).writeAsStringSync(stubs.toString());

final techmapEmitter = YosysTechmapEmitter(
deviceName: fpga.name,
Expand Down Expand Up @@ -208,22 +249,41 @@ Future<void> main(List<String> arguments) async {
height: height,
serdesCount: serdesCount,
clockTileCount: clockTiles,
bramColumnInterval: bramInterval,
dspColumnInterval: dspInterval,
hasConfigClk: results.flag('config-clk'),
);
File(
'$outputDir/${fpga.name}-openroad.tcl',
).writeAsStringSync(openroadEmitter.generate());

// Per-tile OpenROAD PnR scripts (tile macro hardening)
final tileOpenroadEmitter = OpenroadTileTclEmitter(
deviceName: fpga.name,
tracks: tracks,
);
for (final mod in presentModules) {
File(
'$outputDir/${fpga.name}-openroad-$mod.tcl',
).writeAsStringSync(tileOpenroadEmitter.generateTilePnr(mod));
}

print('Generated:');
print(' $outputDir/${fpga.name}.sv');
print(' $outputDir/${fpga.name}.json');
print(' $outputDir/${fpga.name}-xschem.tcl');
print(' $outputDir/${fpga.name}-xschem.sch');
print(' $outputDir/${fpga.name}-yosys.tcl');
for (final mod in presentModules) {
print(' $outputDir/${fpga.name}-yosys-$mod.tcl');
}
print(' $outputDir/${fpga.name}_cells.v');
print(' $outputDir/${fpga.name}_techmap.v');
print(' $outputDir/${fpga.name}-synth-aegis.tcl');
print(' $outputDir/${fpga.name}-openroad.tcl');
for (final mod in presentModules) {
print(' $outputDir/${fpga.name}-openroad-$mod.tcl');
}
} on FormatException catch (e) {
print(e.message);
print('');
Expand Down
151 changes: 74 additions & 77 deletions ip/lib/src/components/digital/fabric_config_loader.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:rohd/rohd.dart';
import 'package:rohd_hcl/rohd_hcl.dart';

/// Streams configuration bits into the FPGA fabric config chain.
class FabricConfigLoader extends Module {
Logic get clk => input('clk');
Logic get start => input('start');
Expand Down Expand Up @@ -37,92 +38,81 @@ class FabricConfigLoader extends Module {
final wordWidth = readPort.data.width;
final wordsNeeded = (totalBits + wordWidth - 1) ~/ wordWidth;

// Use rohd_hcl Deserializer to collect words from memory into a
// flat LogicArray, then a Serializer to shift them out bit-by-bit.
final active = Logic(name: 'active');
final wordAddr = Logic(width: readPort.addr.width, name: 'wordAddr');
final wordCount = Logic(width: wordsNeeded.bitLength, name: 'wordCount');
final fetchDone = Logic(name: 'fetchDone');

// --- Word fetch FSM: reads words from memory sequentially ---
Sequential(clk, [
If(
start,
then: [
active < Const(1),
wordAddr < Const(0, width: wordAddr.width),
wordCount < Const(0, width: wordCount.width),
fetchDone < Const(0),
],
orElse: [
If(
active & ~fetchDone,
then: [
wordCount < wordCount + 1,
wordAddr < wordAddr + 1,
If(
wordCount.eq(Const(wordsNeeded - 1, width: wordCount.width)),
then: [fetchDone < Const(1)],
),
],
),
],
),
]);

readPort.en <= active & ~fetchDone;
readPort.addr <= wordAddr;

// --- Deserializer: collects words into a wide register ---
final deser = Deserializer(
readPort.data,
wordsNeeded,
clk: clk,
reset: start,
enable: active & ~fetchDone,
final wordBuf = Logic(width: wordWidth, name: 'wordBuf');
final bitIdx = Logic(width: wordWidth.bitLength, name: 'bitIdx');
final totalShifted = Logic(
width: totalBits.bitLength,
name: 'totalShifted',
);

// --- Serializer: shifts the collected words out bit-by-bit ---
// We need to serialize the deserialized array one bit at a time.
// Use a simple bit counter + shift approach on the deserialized output.
final bitCounter = Logic(width: totalBits.bitLength, name: 'bitCounter');
final shifting = Logic(name: 'shifting');
final shiftDone = Logic(name: 'shiftDone');

// Flatten the deserialized array into a single wide bus
final flatBits = Logic(width: wordsNeeded * wordWidth, name: 'flatBits');
flatBits <=
deser.deserialized.elements
.map((e) => e)
.toList()
.reversed
.toList()
.swizzle();

// Latch the flat bits when deserialization completes
final latchedBits = Logic(
width: wordsNeeded * wordWidth,
name: 'latchedBits',
);
final wordReady = Logic(name: 'wordReady');
final allDone = Logic(name: 'allDone');

Sequential(
clk,
[
If(
deser.done & active,
start,
then: [
latchedBits < flatBits,
shifting < Const(1),
bitCounter < Const(0, width: bitCounter.width),
active < Const(1),
wordAddr < Const(0, width: wordAddr.width),
bitIdx < Const(0, width: bitIdx.width),
totalShifted < Const(0, width: totalShifted.width),
shifting < Const(0),
wordReady < Const(0),
allDone < Const(0),
],
orElse: [
If(
shifting,
active & ~allDone,
then: [
bitCounter < bitCounter + 1,
If(
bitCounter.eq(Const(totalBits - 1, width: bitCounter.width)),
then: [shifting < Const(0), shiftDone < Const(1)],
~shifting & ~wordReady,
then: [
wordBuf < readPort.data,
wordReady < Const(1),
bitIdx < Const(0, width: bitIdx.width),
],
orElse: [
If(
wordReady & ~shifting,
then: [shifting < Const(1)],
orElse: [
If(
shifting,
then: [
bitIdx < bitIdx + 1,
totalShifted < totalShifted + 1,
If(
totalShifted.eq(
Const(totalBits - 1, width: totalShifted.width),
),
then: [
// All bits shifted
allDone < Const(1),
shifting < Const(0),
],
orElse: [
If(
bitIdx.eq(
Const(wordWidth - 1, width: bitIdx.width),
),
then: [
// Word exhausted, fetch next
shifting < Const(0),
wordReady < Const(0),
wordAddr < wordAddr + 1,
],
),
],
),
],
),
],
),
],
),
],
),
Expand All @@ -131,15 +121,22 @@ class FabricConfigLoader extends Module {
],
reset: start,
resetValues: {
active: Const(0),
shifting: Const(0),
shiftDone: Const(0),
bitCounter: Const(0, width: bitCounter.width),
latchedBits: Const(0, width: latchedBits.width),
wordReady: Const(0),
allDone: Const(0),
wordAddr: Const(0, width: wordAddr.width),
wordBuf: Const(0, width: wordWidth),
bitIdx: Const(0, width: bitIdx.width),
totalShifted: Const(0, width: totalShifted.width),
},
);

cfgIn <= mux(shifting, latchedBits[bitCounter], Const(0));
cfgLoad <= shiftDone;
done <= shiftDone & ~active;
readPort.en <= active & ~allDone & ~shifting & ~wordReady;
readPort.addr <= wordAddr;

cfgIn <= mux(shifting, wordBuf[bitIdx], Const(0));
cfgLoad <= allDone;
done <= allDone;
}
}
18 changes: 11 additions & 7 deletions ip/lib/src/components/digital/fpga.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,12 @@ class AegisFPGA extends Module {
addOutput('padOut', width: pads);
addOutput('padOutputEnable', width: pads);

serialIn = addInput('serialIn', serialIn, width: serdesCount);
addOutput('serialOut', width: serdesCount);
addOutput('txReady', width: serdesCount);
addOutput('rxValid', width: serdesCount);
if (serdesCount > 0) {
serialIn = addInput('serialIn', serialIn, width: serdesCount);
addOutput('serialOut', width: serdesCount);
addOutput('txReady', width: serdesCount);
addOutput('rxValid', width: serdesCount);
}

addOutput('clkOut', width: clockTileCount * ClockTile.numOutputs);
addOutput('clkLocked', width: clockTileCount);
Expand Down Expand Up @@ -149,9 +151,11 @@ class AegisFPGA extends Module {

output('padOut') <= ioFabric.padOut;
output('padOutputEnable') <= ioFabric.padOutputEnable;
output('serialOut') <= ioFabric.serialOut;
output('txReady') <= ioFabric.txReady;
output('rxValid') <= ioFabric.rxValid;
if (serdesCount > 0) {
output('serialOut') <= ioFabric.serialOut!;
output('txReady') <= ioFabric.txReady!;
output('rxValid') <= ioFabric.rxValid!;
}
}

/// Generates a JSON-serializable descriptor of the device, suitable for
Expand Down
Loading
Loading