Add JTAG simulation driver

This commit is contained in:
Matthew Otto 2024-07-03 16:50:59 -05:00
parent a72a49ff00
commit 4c0ab90507
6 changed files with 412 additions and 5 deletions

View File

@ -79,6 +79,7 @@ module tap (
PauseIR : State <= tms ? Exit2IR : PauseIR;
Exit2IR : State <= tms ? UpdateIR : ShiftIR;
UpdateIR : State <= tms ? SelectDR : RunTestIdle;
default : State <= TLReset;
endcase
end

View File

@ -0,0 +1,19 @@
module clk_divider #(parameter DIV) (
input logic clk_in, reset,
output logic clk_out
);
integer count;
always_ff @(posedge clk_in) begin
if (reset) begin
count <= 0;
clk_out <= 0;
end else if (count == DIV) begin
clk_out <= ~clk_out;
count <= 0;
end else begin
count <= count + 1;
end
end
endmodule

View File

@ -0,0 +1,174 @@
module jtag_driver(
input logic clk, reset,
output logic tdi, tms, tck,
input logic tdo
);
localparam logic [2:0] INSTR_RUN = 3'b000;
localparam logic [2:0] INSTR_SIR = 3'b001;
localparam logic [2:0] INSTR_SDR = 3'b010;
localparam CMD_BITS = 3;
localparam LENGTH_BITS = 10;
localparam DATA_BITS = 48;
localparam WIDTH = CMD_BITS + LENGTH_BITS + DATA_BITS*3;
localparam DEPTH = 100;
enum logic [4:0] {RESET, NOP, LOAD, DECODE, COMPLETE, ERROR, DR1, DR2, DR3, SHIFTDR,
IR1, IR2, IR3, IR4, SHIFTIR, RTI1, RTI2} State;
bit [WIDTH-1:0] MEM [DEPTH-1:0];
logic [$clog2(DEPTH)-1:0] IP;
logic [CMD_BITS-1:0] Instruction;
logic [LENGTH_BITS-1:0] Length, Idx;
logic [DATA_BITS-1:0] ScanIn, ScanOut, Mask, ScanData;
always_ff @(posedge clk) begin
if (reset) begin
IP <= 0;
Idx <= 0;
State <= RESET;
end else
case (State)
RESET : begin
// TAP controller has no reset signal
// we must clock a 111110 into TMS to guarantee Run-Test/Idle state
if (Idx == 5)
State <= LOAD;
else
Idx <= Idx + 1;
end
LOAD : begin
{Instruction, Length, ScanIn, ScanOut, Mask} <= MEM[IP];
if (|MEM[IP])
State <= DECODE;
else
State <= COMPLETE;
IP <= IP + 1;
end
DECODE : begin
ScanData <= '0;
Idx <= 0;
case (Instruction)
INSTR_RUN : State <= NOP;
INSTR_SDR : State <= DR1;
INSTR_SIR : State <= IR1;
default : State <= ERROR;
endcase
end
NOP : begin
if (Idx == Length-1)
State <= LOAD;
else
Idx <= Idx + 1;
end
DR1 : State <= DR2;
DR2 : State <= DR3;
DR3 : State <= SHIFTDR;
SHIFTDR : begin
ScanData[Idx] <= tdo;
if (Idx == Length-1)
State <= RTI1;
else
Idx <= Idx + 1;
end
IR1 : State <= IR2;
IR2 : State <= IR3;
IR3 : State <= IR4;
IR4 : State <= SHIFTIR;
SHIFTIR : begin
ScanData[Idx] <= tdo;
if (Idx == Length-1)
State <= RTI1;
else
Idx <= Idx + 1;
end
RTI1 : begin
if (|(ScanOut & Mask) & ((ScanData & Mask) != (ScanOut & Mask)))
State <= ERROR;
else
State <= RTI2;
end
RTI2 : State <= LOAD;
COMPLETE : State <= COMPLETE;
ERROR : State <= ERROR;
endcase
end
// Drive TMS/TDI on falling edge of clock to match spec and also avoid weird simulation bugs
always_ff @(negedge clk) begin
// default values
tdi <= 1'bx;
case (State)
RESET : tms <= (Idx != 5);
LOAD,
DECODE,
NOP,
COMPLETE : tms <= 0;
DR1 : tms <= 1;
DR2 : tms <= 0;
DR3 : tms <= 0;
SHIFTDR : begin
tms <= (Idx == Length-1);
tdi <= ScanIn[Idx];
end
IR1 : tms <= 1;
IR2 : tms <= 1;
IR3 : tms <= 0;
IR4 : tms <= 0;
SHIFTIR : begin
tms <= (Idx == Length-1);
tdi <= ScanIn[Idx];
end
RTI1 : tms <= 1;
RTI2 : tms <= 0;
endcase
end
// tck driver
always_comb begin
case (State)
LOAD,
DECODE,
COMPLETE,
ERROR : begin
tck = 0;
end
RESET,
NOP,
DR1,
DR2,
DR3,
SHIFTDR,
IR1,
IR2,
IR3,
IR4,
SHIFTIR,
RTI1,
RTI2 : begin
tck = clk;
end
endcase
end
endmodule

View File

@ -0,0 +1,192 @@
from enum import Enum
from math import log2
# Assembled SVF command format
CMD_BITS = 3
LENGTH_BITS = 10
DATA_BITS = 48
# Derived constants
MAXLEN = 2**LENGTH_BITS
def main():
filepath = "testbench/jtag/test.svf"
dest_filepath = "testbench/jtag/test.mem"
with open(filepath, "r") as file:
data = file.read()
data = data.lower()
tokens = svf_tokenizer(data)
tokens = remove_comments(tokens)
cmds = parse_tokens(tokens)
with open(dest_filepath, "w") as file:
for cmd in cmds:
asm = assemble_svf(cmd)
file.write(asm + '\n')
def svf_tokenizer(data):
"""Reads raw SVF ascii and converts to a list of tokens"""
keywords = ["sir", "sdr", "runtest", "tdi", "tdo", "mask", "smask", "(", ")", ";"]
comment_keywords = ["//", "!"]
keywords += comment_keywords
tokens = []
eof = len(data)
token = ""
for idx, char in enumerate(data):
if char != " " and char != "\n":
token += char
if (idx+1 < eof):
if data[idx+1] == " ":
if token:
tokens.append(token)
token = ""
elif data[idx+1] == "\n":
if token:
tokens.append(token)
token = ""
tokens.append("\n")
elif token in keywords or data[idx+1] in keywords:
if token:
tokens.append(token)
token = ""
return tokens
def remove_comments(tokens):
"""Removes comments and newlines from list of tokens"""
pruned = []
comment = False
for t in tokens:
if t == "\n":
comment = False
continue
if comment:
continue
if t in ["//", "!"]:
comment = True
continue
pruned.append(t)
return pruned
def parse_tokens(tokens):
"""groups tokens belonging to the same SVF command and checks if valid"""
cmds = []
cmd = Command()
start_idx = 0
i = -1
while i+1 < len(tokens):
i += 1
t = tokens[i]
if t == ";":
if cmd.complete():
cmds.append(cmd)
cmd = Command()
start_idx = i+1
continue
else:
raise Exception(f"Error: incomplete SVF command terminated : '{' '.join(tokens[start_idx:i+1])}'")
if cmd.op is None:
try:
cmd.op = SVF[t]
except KeyError:
raise Exception(f"Error: expected an SVF command, got '{t}' : '{' '.join(tokens[start_idx:i+1])}'")
continue
if cmd.length is None:
try:
cmd.length = int(t)
except Exception:
raise Exception(f"Error: expected a length value, got '{t}' : '{' '.join(tokens[start_idx:i+1])}'")
if cmd.length == 0:
raise Exception(f"Error: length parameter must not be 0 : '{' '.join(tokens[start_idx:i+1])}'")
if cmd.length >= MAXLEN:
raise Exception(f"Error: not enough bits to represent command length : {cmd.length} > {MAXLEN-1} : '{' '.join(tokens[start_idx:i+2])}'")
if cmd.op != SVF.runtest and cmd.length > DATA_BITS:
raise Exception(f"Error: length exceeds number of bits in data field : {cmd.length} > {DATA_BITS} : '{' '.join(tokens[start_idx:i+2])}'")
continue
match cmd.op:
case SVF.runtest:
continue
case SVF.sdr | SVF.sir:
if t not in ("tdi", "tdo", "mask"):
raise Exception(f"Error: unknown keyword '{t}' : '{' '.join(tokens[start_idx:i+1])}'")
if not (tokens[i+1] == "(" and tokens[i+3] == ")"):
raise Exception(f"Error: could not interpret value following token '{t}' (missing parentheses) : '{' '.join(tokens[start_idx:i+2])}'")
try:
val = int(tokens[i+2], 16)
i += 3
if t == "tdi":
cmd.tdi = val
elif t == "tdo":
cmd.tdo = val
elif t == "mask":
cmd.mask = val
except Exception:
raise Exception(f"Error: could not interpret {t} value: '{tokens[i+2]}' : '{' '.join(tokens[start_idx:i+1])}'")
if val > 0 and int(log2(val)) > cmd.length:
raise Exception(f"Error: shift data cannot be a value larger than the maximum implied by the length parameter : log2({val}) > {cmd.length}" +
f" : '{' '.join(tokens[start_idx:i+2])}'")
continue
case _:
raise Exception(f"Error: did not match on valid SVF command : '{' '.join(tokens[start_idx:i+1])}'")
if t != ";":
raise Exception(f"Error: file ended with incomplete command")
return cmds
def assemble_svf(cmd):
"""Converts svf command object to proprietary format for Wally simulation"""
cmdcode = (cmd.op.value << (LENGTH_BITS + DATA_BITS*3))
match cmd.op:
case SVF.runtest:
cmdcode += (cmd.length << DATA_BITS*3)
case SVF.sdr | SVF.sir:
if cmd.length:
cmdcode += (cmd.length << DATA_BITS*3)
if cmd.tdi:
cmdcode += (cmd.tdi << DATA_BITS*2)
if cmd.tdo:
cmdcode += (cmd.tdo << DATA_BITS)
if cmd.mask:
cmdcode += (cmd.mask)
elif not cmd.mask: # If mask isnt specified, set all bits to 1
cmdcode += 2**DATA_BITS-1
hexcmd = hex(cmdcode)[2:]
# TODO: pad left with 0 if needed (if CMD_BITS increases)
return hexcmd
class Command:
def __init__(self):
self.op = None
self.length = None
self.tdi = None
self.tdo = None
self.mask = None
def complete(self):
"""Returns true if object contains enough information to form a complete command"""
if self.op == SVF.runtest:
return self.length
elif self.op in (SVF.sdr, SVF.sir):
z = self.length and (self.tdi is not None or self.tdo is not None)
return z
else:
return False
# Supported SVF commands
class SVF(Enum):
runtest = 0
sir = 1
sdr = 2
if __name__ == "__main__":
main()

8
testbench/jtag/test.svf Normal file
View File

@ -0,0 +1,8 @@
// SVF commands to pull to idcode out of jtag module
SIR 5 TDI(1); // IDCODE
SDR 32 TDO(DEADBEEF);
SDR 32 TDO(DEADBEAF); // JTAG_Driver should throw error if TDO value doesn't match what is scanned out
RUNTEST 55;
sir 8 !and multiline commands
tdo(99);

View File

@ -67,6 +67,8 @@ module testbench;
logic reset_ext, reset;
logic ResetMem;
// JTAG driver signals
logic jtag_clk; // divided clock that runs jtag_driver
logic tck;
logic tdi;
logic tms;
@ -271,7 +273,7 @@ module testbench;
logic ResetCntRst;
logic CopyRAM;
string signame, elffilename, memfilename, bootmemfilename, uartoutfilename, pathname;
string signame, elffilename, memfilename, bootmemfilename, uartoutfilename, pathname, jtagmemfilename;
integer begin_signature_addr, end_signature_addr, signature_size;
integer uartoutfile;
@ -579,10 +581,21 @@ module testbench;
assign SDCIntr = 1'b0;
end
// Change these if testing debug
assign tck = 0;
assign tdi = 0;
assign tms = 0;
// TODO: drive jtag clock at 1/10th core clock (variable)
if (P.DEBUG_SUPPORTED) begin
always @(posedge clk) begin
jtagmemfilename = "../tests/test.svf.memfile";
if (LoadMem) begin
$readmemh(jtagmemfilename, jtag.MEM);
$display("Read memfile %s", jtagmemfilename);
end
end
clk_divider #(2) cdiv (.clk_in(clk), .clk_out(jtag_clk), .reset(reset_ext));
jtag_driver jtag(.clk(jtag_clk), .reset(reset_ext), .tdi, .tms, .tck, .tdo);
end else begin
assign {tdi,tms,tck} = '0;
end
wallypipelinedsoc #(P) dut(
.clk, .reset_ext, .reset, .tck, .tdi, .tms, .tdo,