Added JTAG interface with testbench

This commit is contained in:
2026-02-23 15:37:49 +01:00
parent 20cfece6e3
commit 8f4e887b9d
12 changed files with 731 additions and 24 deletions

196
sim/overrides/jtag_if.v Normal file
View File

@@ -0,0 +1,196 @@
`timescale 1ns/1ps
// =============================================================================
// JTAG interface (simulation override)
// Behavioral SVF player for simple USER-chain simulation.
//
// Supported SVF commands (line-oriented, uppercase recommended):
// - SIR <n> TDI (<hex>);
// - SDR <n> TDI (<hex>);
// - RUNTEST <n> TCK;
// - STATE RESET;
// - STATE IDLE;
// Other lines are ignored.
// =============================================================================
module jtag_if #(
parameter integer chain = 1,
parameter [8*256-1:0] SVF_FILE = "write_jtag.svf",
parameter integer TCK_HALF_PERIOD_NS = 50,
parameter [31:0] USER_IR_OPCODE = 32'h00000002
)(
input wire i_tdo,
output reg o_tck,
output reg o_tdi,
output reg o_drck,
output reg o_capture,
output reg o_shift,
output reg o_update,
output reg o_runtest,
output reg o_reset,
output reg o_sel
);
integer fd;
integer got;
integer nbits;
integer cycles;
integer i;
integer line_no;
integer scan_bits;
reg [8*1024-1:0] line;
reg [8*32-1:0] cmd;
reg [8*32-1:0] arg1;
reg [8*256-1:0] svf_file_path;
reg [4095:0] scan_data;
reg [31:0] current_ir;
reg selected;
// Keep linters quiet: TDO is not consumed by this simple player.
wire _unused_tdo;
assign _unused_tdo = i_tdo;
task pulse_tck;
begin
#TCK_HALF_PERIOD_NS o_tck = 1'b1;
#TCK_HALF_PERIOD_NS o_tck = 1'b0;
end
endtask
task pulse_drck_shift;
input bit_in;
begin
o_tdi = bit_in;
o_shift = 1'b1;
#TCK_HALF_PERIOD_NS begin
o_tck = 1'b1;
o_drck = 1'b1;
end
#TCK_HALF_PERIOD_NS begin
o_tck = 1'b0;
o_drck = 1'b0;
end
end
endtask
task pulse_capture;
begin
o_capture = 1'b1;
#TCK_HALF_PERIOD_NS begin
o_tck = 1'b1;
o_drck = 1'b1;
end
#TCK_HALF_PERIOD_NS begin
o_tck = 1'b0;
o_drck = 1'b0;
o_capture = 1'b0;
end
end
endtask
task pulse_update;
begin
o_shift = 1'b0;
#TCK_HALF_PERIOD_NS o_update = 1'b1;
#TCK_HALF_PERIOD_NS o_update = 1'b0;
end
endtask
initial begin
// Default output levels
o_tck = 1'b0;
o_tdi = 1'b0;
o_drck = 1'b0;
o_capture = 1'b0;
o_shift = 1'b0;
o_update = 1'b0;
o_runtest = 1'b0;
o_reset = 1'b0;
o_sel = 1'b0;
selected = 1'b0;
current_ir = 32'h0;
line_no = 0;
svf_file_path = SVF_FILE;
// Deterministic startup reset pulse before consuming SVF.
o_reset = 1'b1;
#TCK_HALF_PERIOD_NS o_tck = 1'b1;
#TCK_HALF_PERIOD_NS o_tck = 1'b0;
#TCK_HALF_PERIOD_NS o_tck = 1'b1;
#TCK_HALF_PERIOD_NS o_tck = 1'b0;
o_reset = 1'b0;
fd = $fopen(svf_file_path, "r");
if (fd == 0) begin
$display("jtag_if(sim): could not open SVF file '%0s'", svf_file_path);
end else begin
while (!$feof(fd)) begin
line = {8*1024{1'b0}};
got = $fgets(line, fd);
line_no = line_no + 1;
if (got > 0) begin
cmd = {8*32{1'b0}};
arg1 = {8*32{1'b0}};
scan_data = {4096{1'b0}};
nbits = 0;
cycles = 0;
got = $sscanf(line, "%s", cmd);
if (got < 1) begin
// Empty line
end else if (cmd == "!") begin
// Comment line
end else if (cmd == "SIR") begin
got = $sscanf(line, "SIR %d TDI (%h);", nbits, scan_data);
if (got == 2) begin
current_ir = scan_data[31:0];
selected = (current_ir == USER_IR_OPCODE);
o_sel = selected;
// Emit TCK pulses for IR shift activity.
scan_bits = nbits;
if (scan_bits > 4096) scan_bits = 4096;
for (i = 0; i < scan_bits; i = i + 1) begin
o_tdi = scan_data[i];
pulse_tck;
end
end
end else if (cmd == "SDR") begin
got = $sscanf(line, "SDR %d TDI (%h);", nbits, scan_data);
if (got == 2) begin
if (selected) begin
pulse_capture;
end
scan_bits = nbits;
if (scan_bits > 4096) scan_bits = 4096;
for (i = 0; i < scan_bits; i = i + 1) begin
pulse_drck_shift(scan_data[i]);
end
if (selected) begin
pulse_update;
end
end
end else if (cmd == "RUNTEST") begin
got = $sscanf(line, "RUNTEST %d TCK;", cycles);
if (got == 1) begin
o_runtest = 1'b1;
for (i = 0; i < cycles; i = i + 1)
pulse_tck;
o_runtest = 1'b0;
end
end else if (cmd == "STATE") begin
got = $sscanf(line, "STATE %s", arg1);
if (got == 1) begin
if (arg1 == "RESET;") begin
o_reset = 1'b1;
#TCK_HALF_PERIOD_NS o_reset = 1'b0;
selected = 1'b0;
o_sel = 1'b0;
end
end
end
end
end
$fclose(fd);
end
end
endmodule