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;
|
||||
Exit2IR : State <= tms ? UpdateIR : ShiftIR;
|
||||
UpdateIR : State <= tms ? SelectDR : RunTestIdle;
|
||||
default : State <= TLReset;
|
||||
endcase
|
||||
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 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,
|
||||
|
Loading…
Reference in New Issue
Block a user