1 |
8 |
gdevic |
#!/usr/bin/env python3
|
2 |
3 |
gdevic |
#
|
3 |
|
|
# This script generates a test include file from a set of "Fuse" test vectors.
|
4 |
|
|
#
|
5 |
|
|
# Three common testing configurations are:
|
6 |
|
|
#
|
7 |
|
|
# 1. You want to test a specific instruction only, say 02 LD (BC),A (see Fuse tests.in)
|
8 |
|
|
# start_test = "02"
|
9 |
|
|
# run_tests = 1
|
10 |
|
|
# regress = 0
|
11 |
|
|
#
|
12 |
|
|
# 2. You want to run a smaller subset of 'regression' tests:
|
13 |
|
|
# start_test = "00"
|
14 |
|
|
# run_tests = 1
|
15 |
|
|
# regress = 1
|
16 |
|
|
#
|
17 |
|
|
# 3. You want to run a full Fuse test suite (all instructions!):
|
18 |
|
|
# start_test = "00"
|
19 |
|
|
# run_tests = -1
|
20 |
|
|
# regress = 0
|
21 |
|
|
#
|
22 |
13 |
gdevic |
# Orthogonal to that, set m1wait to a non-zero value to test nWAIT insertion at
|
23 |
|
|
# the first M1 cycle of an instruction. Change it to the number of T-clocks to
|
24 |
|
|
# insert.
|
25 |
|
|
#
|
26 |
3 |
gdevic |
#-------------------------------------------------------------------------------
|
27 |
13 |
gdevic |
# Copyright (C) 2016 Goran Devic
|
28 |
3 |
gdevic |
#
|
29 |
|
|
# This program is free software; you can redistribute it and/or modify it
|
30 |
|
|
# under the terms of the GNU General Public License as published by the Free
|
31 |
|
|
# Software Foundation; either version 2 of the License, or (at your option)
|
32 |
|
|
# any later version.
|
33 |
|
|
#
|
34 |
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
35 |
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
36 |
|
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
37 |
|
|
# more details.
|
38 |
|
|
#-------------------------------------------------------------------------------
|
39 |
|
|
import os
|
40 |
|
|
|
41 |
|
|
# Start with this test name (this is a string; see tests files)
|
42 |
|
|
start_test = "00"
|
43 |
|
|
|
44 |
|
|
# Number of tests to run; use -1 to run all tests
|
45 |
|
|
run_tests = 1
|
46 |
|
|
|
47 |
13 |
gdevic |
# Set this to 1 to use regression test instead of selected or full 'tests.*'
|
48 |
|
|
# Regression test is a shorter set of tests and ignores start_test and run_tests values
|
49 |
3 |
gdevic |
regress = 1
|
50 |
|
|
|
51 |
13 |
gdevic |
# Set this to a number of WAIT cycles to add at M1 clock period or 0 not to test nWAIT
|
52 |
|
|
m1wait = 0
|
53 |
|
|
|
54 |
3 |
gdevic |
#------------------------------------------------------------------------------
|
55 |
|
|
# Determine which test files to use
|
56 |
|
|
tests_in = 'fuse/tests.in'
|
57 |
|
|
tests_expected = 'fuse/tests.expected'
|
58 |
|
|
|
59 |
|
|
# Regression testing executes all regression tests
|
60 |
|
|
if regress:
|
61 |
|
|
tests_in = 'fuse/regress.in'
|
62 |
|
|
tests_expected = 'fuse/regress.expected'
|
63 |
|
|
start_test = "00"
|
64 |
|
|
run_tests = -1
|
65 |
|
|
|
66 |
|
|
with open(tests_in) as f1:
|
67 |
|
|
t1 = f1.read().splitlines()
|
68 |
|
|
# Remove all tests until the one we need to start with. Tests are separated by empty lines.
|
69 |
|
|
while t1[0].split(" ")[0]!=start_test:
|
70 |
|
|
while len(t1.pop(0))>0:
|
71 |
|
|
pass
|
72 |
8 |
gdevic |
t1 = list(filter(None, t1)) # Filter out empty lines
|
73 |
3 |
gdevic |
|
74 |
|
|
with open(tests_expected) as f2:
|
75 |
|
|
t2 = f2.read().splitlines()
|
76 |
|
|
while t2[0].split(" ")[0]!=start_test:
|
77 |
|
|
while len(t2.pop(0))>0:
|
78 |
|
|
pass
|
79 |
|
|
|
80 |
|
|
# Count total clocks required to run all selected tests
|
81 |
|
|
total_clks = 0
|
82 |
|
|
|
83 |
|
|
def RegWrite(reg, hex):
|
84 |
|
|
global total_clks
|
85 |
|
|
ftest.write(" // Preset " + reg + "\n")
|
86 |
|
|
ftest.write(" force dut.reg_file_.b2v_latch_" + reg + "_lo.we=1;\n")
|
87 |
|
|
ftest.write(" force dut.reg_file_.b2v_latch_" + reg + "_hi.we=1;\n")
|
88 |
|
|
ftest.write(" force dut.reg_file_.b2v_latch_" + reg + "_lo.db=8'h" + hex[2:] + ";\n")
|
89 |
|
|
ftest.write(" force dut.reg_file_.b2v_latch_" + reg + "_hi.db=8'h" + hex[0:2] + ";\n")
|
90 |
|
|
ftest.write("#2 release dut.reg_file_.b2v_latch_" + reg + "_lo.we;\n")
|
91 |
|
|
ftest.write(" release dut.reg_file_.b2v_latch_" + reg + "_hi.we;\n")
|
92 |
|
|
ftest.write(" release dut.reg_file_.b2v_latch_" + reg + "_lo.db;\n")
|
93 |
|
|
ftest.write(" release dut.reg_file_.b2v_latch_" + reg + "_hi.db;\n")
|
94 |
|
|
total_clks = total_clks + 2
|
95 |
|
|
|
96 |
|
|
def RegRead(reg, hex):
|
97 |
|
|
ftest.write(" if (dut.reg_file_.b2v_latch_" + reg + "_lo.latch!==8'h" + hex[2:] + ") $fdisplay(f,\"* Reg " + reg + " " + reg[1] + "=%h !=" + hex[2:] + "\",dut.reg_file_.b2v_latch_" + reg + "_lo.latch);\n")
|
98 |
|
|
ftest.write(" if (dut.reg_file_.b2v_latch_" + reg + "_hi.latch!==8'h" + hex[0:2] + ") $fdisplay(f,\"* Reg " + reg + " " + reg[0] + "=%h !=" + hex[0:2] + "\",dut.reg_file_.b2v_latch_" + reg + "_hi.latch);\n")
|
99 |
|
|
|
100 |
|
|
#---------------------------- START -----------------------------------
|
101 |
|
|
# Create a file that should be included in the test_fuse source
|
102 |
6 |
gdevic |
ftest = open('test_fuse.vh', 'w')
|
103 |
3 |
gdevic |
ftest.write("// Automatically generated by genfuse.py\n\n")
|
104 |
|
|
|
105 |
|
|
# Initial pre-test state is reset and control signals asserted
|
106 |
8 |
gdevic |
ftest.write("force dut.resets_.clrpc=0;\n")
|
107 |
3 |
gdevic |
ftest.write("force dut.reg_file_.reg_gp_we=0;\n")
|
108 |
|
|
ftest.write("force dut.reg_control_.ctl_reg_sys_we=0;\n")
|
109 |
|
|
ftest.write("force dut.z80_top_ifc_n.fpga_reset=1;\n")
|
110 |
13 |
gdevic |
ftest.write("#2 // Start test loop\n\n")
|
111 |
3 |
gdevic |
total_clks = total_clks + 2
|
112 |
|
|
|
113 |
|
|
# Read each test from the testdat.in file
|
114 |
|
|
while True:
|
115 |
|
|
if len(t1)==0 or run_tests==0:
|
116 |
|
|
break
|
117 |
|
|
run_tests = run_tests-1
|
118 |
|
|
|
119 |
|
|
# Clear opcode register before starting a new instruction
|
120 |
8 |
gdevic |
ftest.write(" force dut.ir_.ctl_ir_we=1;\n")
|
121 |
|
|
ftest.write(" force dut.ir_.db=0;\n")
|
122 |
|
|
ftest.write("#2 release dut.ir_.ctl_ir_we;\n")
|
123 |
|
|
ftest.write(" release dut.ir_.db;\n")
|
124 |
3 |
gdevic |
total_clks = total_clks + 2
|
125 |
|
|
|
126 |
|
|
# Format of the test.in file:
|
127 |
|
|
# <arbitrary test description>
|
128 |
|
|
# AF BC DE HL AF' BC' DE' HL' IX IY SP PC
|
129 |
|
|
# I R IFF1 IFF2 IM <halted> <tstates>
|
130 |
|
|
name = t1.pop(0)
|
131 |
13 |
gdevic |
ftest.write(" $fdisplay(f,\"Testing opcode " + name + "\");\n")
|
132 |
3 |
gdevic |
name = name.split(" ")[0]
|
133 |
|
|
r = t1.pop(0).split(' ')
|
134 |
8 |
gdevic |
r = list(filter(None, r))
|
135 |
3 |
gdevic |
# 0 1 2 3 4 5 6 7 8 9 10 11 (index)
|
136 |
|
|
# AF BC DE HL AF' BC' DE' HL' IX IY SP PC
|
137 |
|
|
RegWrite("af", r[0])
|
138 |
|
|
RegWrite("bc", r[1])
|
139 |
|
|
RegWrite("de", r[2])
|
140 |
|
|
RegWrite("hl", r[3])
|
141 |
|
|
RegWrite("af2", r[4])
|
142 |
|
|
RegWrite("bc2", r[5])
|
143 |
|
|
RegWrite("de2", r[6])
|
144 |
|
|
RegWrite("hl2", r[7])
|
145 |
|
|
RegWrite("ix", r[8])
|
146 |
|
|
RegWrite("iy", r[9])
|
147 |
|
|
RegWrite("sp", r[10])
|
148 |
|
|
RegWrite("wz", "0000") # Initialize WZ with 0
|
149 |
|
|
RegWrite("pc", r[11])
|
150 |
|
|
|
151 |
|
|
s = t1.pop(0).split(' ')
|
152 |
8 |
gdevic |
s = list(filter(None, s))
|
153 |
3 |
gdevic |
# 0 1 2 3 4 5 6 (index)
|
154 |
|
|
# I R IFF1 IFF2 IM <halted> <tstates?>
|
155 |
|
|
RegWrite("ir", s[0]+s[1])
|
156 |
|
|
# TODO: Store IFF1/IFF2, IM, in_halt
|
157 |
|
|
|
158 |
|
|
# Read memory configuration from the test.in until the line contains only -1
|
159 |
|
|
while True:
|
160 |
|
|
m = t1.pop(0).split(' ')
|
161 |
|
|
if m[0]=="-1":
|
162 |
|
|
break
|
163 |
|
|
address = int(m.pop(0),16)
|
164 |
|
|
ftest.write(" // Preset memory\n")
|
165 |
|
|
while True:
|
166 |
|
|
d = m.pop(0)
|
167 |
|
|
if d=="-1":
|
168 |
|
|
break
|
169 |
|
|
ftest.write(" ram.Mem[" + str(address) + "] = 8'h" + d + ";\n")
|
170 |
|
|
address = address+1
|
171 |
|
|
|
172 |
|
|
# We need to prepare the IO map to be able to handle IN/OUT instructions.
|
173 |
|
|
# Copy tests.out (so we don't modify it just yet), parse all PR and PW (port read, write)
|
174 |
|
|
# statements and then fill in our IO map (for IO reads) or stack the check statements to be
|
175 |
|
|
# used below after the opcode has executed (for IO writes)
|
176 |
|
|
check_io = [] # List of check statements (for OUT instructions)
|
177 |
|
|
t2b = list(t2)
|
178 |
|
|
while True:
|
179 |
|
|
m = t2b.pop(0).split(' ')
|
180 |
8 |
gdevic |
m = list(filter(None, m))
|
181 |
3 |
gdevic |
if len(m)==0 or m[0]=="-1":
|
182 |
|
|
break
|
183 |
|
|
if len(m)==4 and m[1]=="PR":
|
184 |
|
|
address = int(m[2],16)
|
185 |
|
|
ftest.write(" io.IO[" + str(address) + "] = 8'h" + m[3] + ";\n")
|
186 |
|
|
if len(m)==4 and m[1]=="PW":
|
187 |
|
|
address = int(m[2],16)
|
188 |
|
|
check_io.append(" if (io.IO[" + str(address) + "]!==8'h" + m[3] + ") $fdisplay(f,\"* IO[" + hex(address)[2:] + "]=%h !=" + m[3] + "\",io.IO[" + str(address) + "]);\n")
|
189 |
|
|
|
190 |
|
|
# Prepare instruction to be run. By releasing the fpga_reset, internal CPU reset will be active for 1T.
|
191 |
|
|
# Due to the instruction execution overlap, first 2T of an instruction may be writing
|
192 |
|
|
# value back to a general purpose register (like AF) and we need to prevent that.
|
193 |
|
|
# Similarly, we let the execution continues 2T into the next instruction but we prevent
|
194 |
|
|
# it from writing to system registers so it cannot update PC and IR.
|
195 |
|
|
ftest.write(" force dut.z80_top_ifc_n.fpga_reset=0;\n")
|
196 |
8 |
gdevic |
ftest.write(" force dut.address_latch_.Q=16'h" + r[11] +";\n") # Force PC into the address latch
|
197 |
3 |
gdevic |
ftest.write(" release dut.reg_control_.ctl_reg_sys_we;\n")
|
198 |
|
|
ftest.write(" release dut.reg_file_.reg_gp_we;\n")
|
199 |
13 |
gdevic |
ftest.write("#2 // Execute: M1/T1 start\n") # 1T (#2) overlaps the reset cycle
|
200 |
|
|
ftest.write("#1 release dut.address_latch_.Q;\n")
|
201 |
|
|
total_clks = total_clks + 3 # We borrow 1T (#2) to to force the PC to be what our test wants...
|
202 |
3 |
gdevic |
ftest.write("#1\n")
|
203 |
|
|
total_clks = total_clks + 1
|
204 |
|
|
|
205 |
|
|
# Read and parse the tests expected list which contains the expected results of our run,
|
206 |
|
|
# including the number of clocks for a particular instruction
|
207 |
|
|
xname = t2.pop(0).split()[0]
|
208 |
|
|
if name!=xname:
|
209 |
|
|
print("Test " + name + " does not correspond to test.expected " + xname)
|
210 |
|
|
break
|
211 |
|
|
# Skip the memory access logs; read to the expected register content list
|
212 |
|
|
while True:
|
213 |
|
|
l = t2.pop(0)
|
214 |
|
|
if l[0]!=' ':
|
215 |
|
|
break
|
216 |
|
|
r = l.split(' ')
|
217 |
8 |
gdevic |
r = list(filter(None, r))
|
218 |
3 |
gdevic |
|
219 |
|
|
s = t2.pop(0).split(' ')
|
220 |
8 |
gdevic |
s = list(filter(None, s))
|
221 |
3 |
gdevic |
|
222 |
|
|
ticks = int(s[6]) * 2 - 2 # We return 1T (#2) that we borrowed to set PC
|
223 |
|
|
total_clks = total_clks + ticks
|
224 |
|
|
|
225 |
13 |
gdevic |
# Test WAIT state insertion at the M1 clock cycle
|
226 |
|
|
if m1wait:
|
227 |
|
|
ftest.write(" z.nWAIT <= 0;\n")
|
228 |
|
|
ftest.write("#" + str(m1wait * 2) + " z.nWAIT <= 1; // nWAIT during M1\n")
|
229 |
|
|
total_clks = total_clks + m1wait * 2
|
230 |
|
|
|
231 |
|
|
ftest.write("#" + str(ticks) + " // Wait for opcode end\n")
|
232 |
|
|
|
233 |
3 |
gdevic |
ftest.write(" force dut.reg_control_.ctl_reg_sys_we=0;\n")
|
234 |
|
|
ftest.write("#2 pc=z.A;\n") # Extra 2T for the next instruction overlap & read PC on the ABus
|
235 |
|
|
ftest.write("#2\n") # Complete this instruction
|
236 |
|
|
ftest.write("#1 force dut.reg_file_.reg_gp_we=0;\n") # Add 1/2 clock for any pending flops to latch (mainly the F register)
|
237 |
|
|
ftest.write(" force dut.z80_top_ifc_n.fpga_reset=1;\n")
|
238 |
|
|
total_clks = total_clks + 5
|
239 |
|
|
|
240 |
|
|
# Now we can issue register reading commands
|
241 |
|
|
# We are guided on what to read and check by the content of "test.expected" file
|
242 |
|
|
|
243 |
|
|
# Special case are the register exchange instructions and there are 3 of them.
|
244 |
|
|
# The exchange operations are not tested directly; instead, the latches that control register bank access are
|
245 |
|
|
if xname=="08": # EX AF,AF1
|
246 |
|
|
r[0],r[4] = r[4],r[0]
|
247 |
|
|
ftest.write(" if (dut.reg_control_.bank_af!==1) $fdisplay(f,\"* Bank AF!=1\");\n")
|
248 |
|
|
if xname=="eb": # EX DE,HL
|
249 |
|
|
r[2],r[3] = r[3],r[2]
|
250 |
|
|
ftest.write(" if (dut.reg_control_.bank_hl_de1!==1) $fdisplay(f,\"* Bank HL/DE!=1\");\n")
|
251 |
|
|
if xname=="d9": # EXX
|
252 |
|
|
r[1],r[5] = r[5],r[1]
|
253 |
|
|
r[2],r[6] = r[6],r[2]
|
254 |
|
|
r[3],r[7] = r[7],r[3]
|
255 |
|
|
ftest.write(" if (dut.reg_control_.bank_exx!==1) $fdisplay(f,\"* Bank EXX!=1\");\n")
|
256 |
|
|
|
257 |
|
|
# Read the result: registers and memory
|
258 |
|
|
# 0 1 2 3 4 5 6 7 8 9 10 11 (index)
|
259 |
|
|
# AF BC DE HL AF' BC' DE' HL' IX IY SP PC
|
260 |
|
|
RegRead("af", r[0])
|
261 |
|
|
RegRead("bc", r[1])
|
262 |
|
|
RegRead("de", r[2])
|
263 |
|
|
RegRead("hl", r[3])
|
264 |
|
|
RegRead("af2", r[4])
|
265 |
|
|
RegRead("bc2", r[5])
|
266 |
|
|
RegRead("de2", r[6])
|
267 |
|
|
RegRead("hl2", r[7])
|
268 |
|
|
RegRead("ix", r[8])
|
269 |
|
|
RegRead("iy", r[9])
|
270 |
|
|
RegRead("sp", r[10])
|
271 |
|
|
#RegRead("pc", r[11]) Instead of PC, we read the address bus of the next instruction
|
272 |
|
|
ftest.write(" if (pc!==16'h" + r[11] + ") $fdisplay(f,\"* PC=%h !=" + r[11] + "\",pc);\n")
|
273 |
|
|
|
274 |
|
|
# 0 1 2 3 4 5 6 (index)
|
275 |
|
|
# I R IFF1 IFF2 IM <halted> <tstates?>
|
276 |
|
|
RegRead("ir", s[0]+s[1])
|
277 |
|
|
|
278 |
|
|
# Read memory configuration until an empty line or -1 at the end
|
279 |
|
|
while True:
|
280 |
|
|
m = t2.pop(0).split(' ')
|
281 |
8 |
gdevic |
m = list(filter(None, m))
|
282 |
3 |
gdevic |
if len(m)==0 or m[0]=="-1":
|
283 |
|
|
break
|
284 |
|
|
address = int(m.pop(0),16)
|
285 |
|
|
while True:
|
286 |
|
|
d = m.pop(0)
|
287 |
|
|
if d=="-1":
|
288 |
|
|
break
|
289 |
|
|
ftest.write(" if (ram.Mem[" + str(address) + "]!==8'h" + d + ") $fdisplay(f,\"* Mem[" + hex(address)[2:] + "]=%h !=" + d + "\",ram.Mem[" + str(address) + "]);\n")
|
290 |
|
|
address = address+1
|
291 |
|
|
# Read a list of IO checks that was compiled while parsing the initial condition
|
292 |
|
|
while len(check_io)>0:
|
293 |
|
|
ftest.write(check_io.pop(0))
|
294 |
13 |
gdevic |
ftest.write("#1 // End opcode\n\n")
|
295 |
|
|
total_clks = total_clks + 1
|
296 |
3 |
gdevic |
|
297 |
|
|
# Write out the total number of clocks that this set of tests takes to execute
|
298 |
|
|
ftest.write("`define TOTAL_CLKS " + str(total_clks) + "\n")
|
299 |
|
|
ftest.write("$fdisplay(f,\"=== Tests completed ===\");\n")
|
300 |
13 |
gdevic |
ftest.close()
|
301 |
3 |
gdevic |
|
302 |
6 |
gdevic |
# Touch a file that includes 'test_fuse.vh' to ensure it will recompile correctly
|
303 |
3 |
gdevic |
os.utime("test_fuse.sv", None)
|