mirror of
https://github.com/openhwgroup/cvw
synced 2025-02-11 06:05:49 +00:00
Add JTAG simulation driver
This commit is contained in:
parent
a72a49ff00
commit
4c0ab90507
@ -79,6 +79,7 @@ module tap (
|
|||||||
PauseIR : State <= tms ? Exit2IR : PauseIR;
|
PauseIR : State <= tms ? Exit2IR : PauseIR;
|
||||||
Exit2IR : State <= tms ? UpdateIR : ShiftIR;
|
Exit2IR : State <= tms ? UpdateIR : ShiftIR;
|
||||||
UpdateIR : State <= tms ? SelectDR : RunTestIdle;
|
UpdateIR : State <= tms ? SelectDR : RunTestIdle;
|
||||||
|
default : State <= TLReset;
|
||||||
endcase
|
endcase
|
||||||
end
|
end
|
||||||
|
|
||||||
|
19
testbench/jtag/clk_divider.sv
Normal file
19
testbench/jtag/clk_divider.sv
Normal 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
|
174
testbench/jtag/jtag_driver.sv
Normal file
174
testbench/jtag/jtag_driver.sv
Normal 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
|
192
testbench/jtag/svf_convert.py
Normal file
192
testbench/jtag/svf_convert.py
Normal 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
8
testbench/jtag/test.svf
Normal 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);
|
||||||
|
|
@ -67,6 +67,8 @@ module testbench;
|
|||||||
logic reset_ext, reset;
|
logic reset_ext, reset;
|
||||||
logic ResetMem;
|
logic ResetMem;
|
||||||
|
|
||||||
|
// JTAG driver signals
|
||||||
|
logic jtag_clk; // divided clock that runs jtag_driver
|
||||||
logic tck;
|
logic tck;
|
||||||
logic tdi;
|
logic tdi;
|
||||||
logic tms;
|
logic tms;
|
||||||
@ -271,7 +273,7 @@ module testbench;
|
|||||||
logic ResetCntRst;
|
logic ResetCntRst;
|
||||||
logic CopyRAM;
|
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 begin_signature_addr, end_signature_addr, signature_size;
|
||||||
integer uartoutfile;
|
integer uartoutfile;
|
||||||
|
|
||||||
@ -579,10 +581,21 @@ module testbench;
|
|||||||
assign SDCIntr = 1'b0;
|
assign SDCIntr = 1'b0;
|
||||||
end
|
end
|
||||||
|
|
||||||
// Change these if testing debug
|
// TODO: drive jtag clock at 1/10th core clock (variable)
|
||||||
assign tck = 0;
|
if (P.DEBUG_SUPPORTED) begin
|
||||||
assign tdi = 0;
|
always @(posedge clk) begin
|
||||||
assign tms = 0;
|
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(
|
wallypipelinedsoc #(P) dut(
|
||||||
.clk, .reset_ext, .reset, .tck, .tdi, .tms, .tdo,
|
.clk, .reset_ext, .reset, .tck, .tdi, .tms, .tdo,
|
||||||
|
Loading…
Reference in New Issue
Block a user