Skip to content

Commit adeca1b

Browse files
feat(nextpnr-aegis): add jtag bel
1 parent 6a41e4d commit adeca1b

4 files changed

Lines changed: 109 additions & 7 deletions

File tree

ip/bin/aegis_genip.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ Future<void> main(List<String> arguments) async {
233233
bramDataWidth: 8,
234234
bramAddrWidth: 7,
235235
bramColumnInterval: bramInterval,
236+
hasJtag: results.flag('jtag'),
236237
);
237238
File(
238239
'$outputDir/${fpga.name}_cells.v',

ip/lib/src/yosys/techmap_emitter.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ class YosysTechmapEmitter {
1414
final int bramColumnInterval;
1515

1616
int get bramDepth => 1 << bramAddrWidth;
17+
final bool hasJtag;
18+
1719
int get totalLuts => width * height;
1820

1921
const YosysTechmapEmitter({
@@ -24,6 +26,7 @@ class YosysTechmapEmitter {
2426
this.bramDataWidth = 8,
2527
this.bramAddrWidth = 7,
2628
this.bramColumnInterval = 0,
29+
this.hasJtag = false,
2730
});
2831

2932
/// Generate Verilog blackbox cell definitions for Aegis primitives.
@@ -40,6 +43,9 @@ class YosysTechmapEmitter {
4043
if (bramColumnInterval > 0) {
4144
_writeBramCell(buf);
4245
}
46+
if (hasJtag) {
47+
_writeJtagCell(buf);
48+
}
4349

4450
return buf.toString();
4551
}
@@ -287,4 +293,18 @@ class YosysTechmapEmitter {
287293
buf.writeln('// passed to memory_bram -rules in the synth script.');
288294
buf.writeln();
289295
}
296+
297+
void _writeJtagCell(StringBuffer buf) {
298+
buf.writeln('(* blackbox *)');
299+
buf.writeln('module AEGIS_JTAG (');
300+
buf.writeln(' output wire tdi,');
301+
buf.writeln(' input wire tdo,');
302+
buf.writeln(' output wire shift,');
303+
buf.writeln(' output wire update,');
304+
buf.writeln(' output wire capture,');
305+
buf.writeln(' output wire reset');
306+
buf.writeln(');');
307+
buf.writeln('endmodule');
308+
buf.writeln();
309+
}
290310
}

nextpnr-aegis/aegis.cc

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,13 @@ struct AegisImpl : ViaductAPI {
3030
int K = 4;
3131

3232
// Cached IdStrings
33-
IdString id_LUT4, id_DFF, id_IOB, id_INBUF, id_OUTBUF;
33+
IdString id_LUT4, id_DFF, id_IOB, id_INBUF, id_OUTBUF, id_JTAG;
3434
IdString id_CLK, id_D, id_Q, id_F, id_I, id_O, id_PAD, id_EN;
3535
IdString id_INIT, id_PIP, id_LOCAL;
3636

37+
// JTAG wires (global)
38+
WireId jtag_tdi, jtag_tdo, jtag_shift, jtag_update, jtag_capture, jtag_reset;
39+
3740
dict<std::string, std::string> device_args;
3841

3942
// Per-tile wire storage
@@ -61,6 +64,7 @@ struct AegisImpl : ViaductAPI {
6164
id_IOB = ctx->id("IOB");
6265
id_INBUF = ctx->id("INBUF");
6366
id_OUTBUF = ctx->id("OUTBUF");
67+
id_JTAG = ctx->id("JTAG");
6468
id_CLK = ctx->id("CLK");
6569
id_D = ctx->id("D");
6670
id_Q = ctx->id("Q");
@@ -168,6 +172,8 @@ struct AegisImpl : ViaductAPI {
168172

169173
bool isBelLocationValid(BelId bel, bool explain_invalid) const override {
170174
Loc l = ctx->getBelLocation(bel);
175+
if (l.x == 0 && l.y == 0)
176+
return true; // JTAG BEL
171177
if (is_io(l.x, l.y))
172178
return true;
173179
return slice_valid(l.x, l.y, l.z / 2);
@@ -242,6 +248,20 @@ struct AegisImpl : ViaductAPI {
242248
tw.track_w.push_back(ctx->addWire(h.xy_id(x, y, ctx->idf("W%d", t)),
243249
ctx->id("ROUTING"), x, y));
244250
}
251+
} else if (x == 0 && y == 0) {
252+
// Bottom-left corner: JTAG BEL site
253+
jtag_tdi = ctx->addWire(h.xy_id(x, y, ctx->id("JTAG_TDI")),
254+
ctx->id("JTAG"), x, y);
255+
jtag_tdo = ctx->addWire(h.xy_id(x, y, ctx->id("JTAG_TDO")),
256+
ctx->id("JTAG"), x, y);
257+
jtag_shift = ctx->addWire(h.xy_id(x, y, ctx->id("JTAG_SHIFT")),
258+
ctx->id("JTAG"), x, y);
259+
jtag_update = ctx->addWire(h.xy_id(x, y, ctx->id("JTAG_UPDATE")),
260+
ctx->id("JTAG"), x, y);
261+
jtag_capture = ctx->addWire(h.xy_id(x, y, ctx->id("JTAG_CAPTURE")),
262+
ctx->id("JTAG"), x, y);
263+
jtag_reset = ctx->addWire(h.xy_id(x, y, ctx->id("JTAG_RESET")),
264+
ctx->id("JTAG"), x, y);
245265
} else if (!is_corner(x, y)) {
246266
// IO tile wires
247267
for (int z = 0; z < 2; z++) {
@@ -273,9 +293,13 @@ struct AegisImpl : ViaductAPI {
273293
for (int y = 0; y < H; y++) {
274294
for (int x = 0; x < W; x++) {
275295
if (is_io(x, y)) {
276-
if (is_corner(x, y))
296+
if (x == 0 && y == 0) {
297+
add_jtag_bel(x, y);
298+
} else if (is_corner(x, y)) {
277299
continue;
278-
add_io_bels(x, y);
300+
} else {
301+
add_io_bels(x, y);
302+
}
279303
} else {
280304
add_logic_bels(x, y);
281305
}
@@ -297,6 +321,17 @@ struct AegisImpl : ViaductAPI {
297321
}
298322
}
299323

324+
void add_jtag_bel(int x, int y) {
325+
BelId b = ctx->addBel(h.xy_id(x, y, ctx->id("JTAG0")), id_JTAG,
326+
Loc(x, y, 0), false, false);
327+
ctx->addBelOutput(b, ctx->id("tdi"), jtag_tdi);
328+
ctx->addBelInput(b, ctx->id("tdo"), jtag_tdo);
329+
ctx->addBelOutput(b, ctx->id("shift"), jtag_shift);
330+
ctx->addBelOutput(b, ctx->id("update"), jtag_update);
331+
ctx->addBelOutput(b, ctx->id("capture"), jtag_capture);
332+
ctx->addBelOutput(b, ctx->id("reset"), jtag_reset);
333+
}
334+
300335
void add_logic_bels(int x, int y) {
301336
auto &tw = tile_wires[y][x];
302337

@@ -332,6 +367,34 @@ struct AegisImpl : ViaductAPI {
332367
add_inter_tile_pips(x, y);
333368
}
334369
}
370+
add_jtag_pips();
371+
}
372+
373+
void add_jtag_pips() {
374+
// Connect JTAG wires to the adjacent fabric tile (1,1)
375+
if (W <= 2 || H <= 2)
376+
return;
377+
auto &tw = tile_wires[1][1];
378+
Loc loc(0, 0, 0);
379+
380+
// JTAG outputs -> fabric input tracks (so user designs can read them)
381+
for (int t = 0; t < T; t++) {
382+
add_pip(loc, jtag_tdi, tw.track_w[t], 0.05);
383+
add_pip(loc, jtag_shift, tw.track_w[t], 0.05);
384+
add_pip(loc, jtag_update, tw.track_w[t], 0.05);
385+
add_pip(loc, jtag_capture, tw.track_w[t], 0.05);
386+
add_pip(loc, jtag_reset, tw.track_w[t], 0.05);
387+
}
388+
389+
// Fabric -> JTAG tdo (so user designs can drive TDO)
390+
for (int t = 0; t < T; t++) {
391+
add_pip(loc, tw.track_n[t], jtag_tdo, 0.05);
392+
add_pip(loc, tw.track_e[t], jtag_tdo, 0.05);
393+
add_pip(loc, tw.track_s[t], jtag_tdo, 0.05);
394+
add_pip(loc, tw.track_w[t], jtag_tdo, 0.05);
395+
}
396+
add_pip(loc, tw.lut_out, jtag_tdo, 0.05);
397+
add_pip(loc, tw.ff_q, jtag_tdo, 0.05);
335398
}
336399

337400
void add_logic_pips(int x, int y) {

nextpnr-aegis/aegis_test.cc

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ class AegisTest : public ::testing::Test {
4646
return x == 0 || x == gw() - 1 || y == 0 || y == gh() - 1;
4747
}
4848

49+
// Find a BEL by name string "X{x}/Y{y}/name"
50+
BelId find_bel(const std::string &name) const {
51+
return ctx->getBelByName(IdStringList::parse(ctx, name));
52+
}
53+
4954
// Find a wire by name string "X{x}/Y{y}/name"
5055
WireId find_wire(const std::string &name) const {
5156
auto id = ctx->getWireByName(IdStringList::parse(ctx, name));
@@ -368,10 +373,23 @@ TEST_F(AegisTest, IOTileHasIOBels) {
368373
EXPECT_NE(io1, BelId()) << "Missing IO1 BEL";
369374
}
370375

371-
TEST_F(AegisTest, CornerTilesHaveNoBels) {
372-
// Corner tile (0,0) should have no BELs (x == y for corners)
373-
auto wires = wires_at(0, 0);
374-
EXPECT_TRUE(wires.empty()) << "Corner tile should have no wires/BELs";
376+
TEST_F(AegisTest, CornerTilesHaveNoBelsExceptJtag) {
377+
// Corner (0,0) has the JTAG BEL; other corners have nothing
378+
auto w00 = wires_at(0, 0);
379+
EXPECT_FALSE(w00.empty()) << "Corner (0,0) should have JTAG wires";
380+
381+
// Verify the JTAG BEL exists
382+
auto jtag_bel = find_bel("X0/Y0/JTAG0");
383+
EXPECT_NE(jtag_bel, BelId()) << "JTAG BEL should exist at (0,0)";
384+
385+
// Other corners should still be empty
386+
int gw_val = gw(), gh_val = gh();
387+
auto w_tr = wires_at(gw_val - 1, 0);
388+
EXPECT_TRUE(w_tr.empty()) << "Corner (W-1,0) should have no wires";
389+
auto w_bl = wires_at(0, gh_val - 1);
390+
EXPECT_TRUE(w_bl.empty()) << "Corner (0,H-1) should have no wires";
391+
auto w_br = wires_at(gw_val - 1, gh_val - 1);
392+
EXPECT_TRUE(w_br.empty()) << "Corner (W-1,H-1) should have no wires";
375393
}
376394

377395
// === Completeness tests ===

0 commit comments

Comments
 (0)