x393  1.0
FPGAcodeforElphelNC393camera
x393_cocotb_server.py
Go to the documentation of this file.
1 from __future__ import print_function
2 """
3 # Copyright (C) 2016, Elphel.inc.
4 # Simulation code for cocotb simulation for x393 project
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http:#www.gnu.org/licenses/>.
18 @author: Andrey Filippov
19 @copyright: 2016 Elphel, Inc.
20 @license: GPLv3.0+
21 @contact: andrey@elphel.coml
22 """
23 import os
24 import cocotb
25 import socket
26 import select
27 #import json
28 from socket_command import SocketCommand
29 
30 from cocotb.triggers import Timer
31 from x393interfaces import MAXIGPMaster, PSBus, SAXIRdSim, SAXIWrSim
32 from cocotb.drivers import BitDriver
33 from cocotb.triggers import Timer, RisingEdge, ReadOnly
34 from cocotb.result import ReturnValue, TestFailure, TestError, TestSuccess
35 import logging
36 import re
37 import struct
38 def hex_list(lst, max_items=0, frmt="0x%08x"):
39  if (max_items == 0) or (len(lst) <= max_items):
40  hs="["
41  for d in lst:
42  hs+=frmt%(d)+", "
43  return hs[:-2]+"]"
44  hs = "%d ["%len(lst)
45  fi = max_items-1 if max_items > 1 else max_items
46  for d in lst[:fi]:
47  hs+=frmt%(d)+", "
48  hs += "..."
49  if fi < max_items:
50  hs += " "+frmt%(d)
51  return hs+"]"
52 
53 class X393_cocotb_server(object):
54  INTR_ADDRESS = 0xfffffff0 #temporary address
55  INTM_ADDRESS = 0xfffffff4 #temporary address
56  RESERVED = (INTR_ADDRESS,INTM_ADDRESS)
57  writeIDMask = (1 <<12) -1
58  readIDMask = (1 <<12) -1
59  started=False
60  int_mask = 0 # all disabled
61  def __init__(self, dut, port, host, mempath=None, autoflush=True): # , debug=False):
62  self.ACLK_FREQ=50000000 # 50 MHz
63  debug = os.getenv('COCOTB_DEBUG') # None/1
64  if mempath is None:
65  mempath = os.getenv('SIMULATION_PATH')+"/"+"memfile"
66  self.mempath = mempath
67  self.memlow = 0
68  self.memhigh = 0x40000000
69  self.autoflush = autoflush
70  self.cmd= SocketCommand()
71  self.dut = dut
72  #Open file to use as system memory
73  try:
74  self._memfile=open(mempath, 'r+') #keep old file if it exists already
75  except:
76  self._memfile=open(mempath, 'w+') #create a new file if it does not exist
77  self.dut._log.info ("Created a new 'memory' file %s"%(mempath)) #
78  #Extend to full size
79  self._memfile.seek(self.memhigh-1)
80  readOK=False
81  try:
82  readOK = len(self._memfile.read(1))>0
83  self.dut._log.info ("Read from 0x%08x"%(self.memhigh-1)) #
84 
85  except:
86  pass
87  if not readOK:
88  self._memfile.seek(self.memhigh-1)
89  self._memfile.write(chr(0))
90  self._memfile.flush()
91  self.dut._log.info("Wrote to 0x%08x to extend file to full size"%(self.memhigh-1)) #
92 
93  #initialize MAXIGP0 interface (main control/status registers, TODO: add MAXIGP1 for SATA)
94  self.maxigp0 = MAXIGPMaster(entity = dut,
95  name = "dutm0",
96  clock = dut.dutm0_aclk,
97  rdlag = 0,
98  blag=0)
99  self.writeID=0
100  self.readID=0
101  #initialize Zynq register access, has methods write_reg(a,d) and read_reg(a)
102  self.ps_sbus = PSBus (entity = dut,
103  name = "ps_sbus",
104  clock = dut.ps_sbus_clk)
105  #Bus masters (communicated over mempath file
106  #Membridge to FPGA
107  self.saxihp0r = SAXIRdSim (entity = dut,
108  name = "saxihp0",
109  clock = dut.axi_hclk,
110  mempath = self.mempath,
111  memhigh = self.memhigh,
112  data_bytes = 8)
113  #Membridge from FPGA
114  self.saxihp0w = SAXIWrSim (entity = dut,
115  name = "saxihp0",
116  clock = dut.axi_hclk,
117  mempath = self.mempath,
118  memhigh = self.memhigh,
119  data_bytes = 8,
120  autoflush = self.autoflush,
121  blatency = 5)
122  #Compressors from FPGA
123  self.saxihp1w = SAXIWrSim (entity = dut,
124  name = "saxihp1",
125  clock = dut.axi_hclk,
126  mempath = self.mempath,
127  memhigh = self.memhigh,
128  data_bytes = 8,
129  autoflush = self.autoflush,
130  blatency = 5)
131  #histograms from FPGA
132  self.saxigp0 = SAXIWrSim (entity = dut,
133  name = "saxigp0",
134  clock = dut.saxi0_aclk,
135  mempath = self.mempath,
136  memhigh = self.memhigh,
137  data_bytes = 4,
138  autoflush = self.autoflush,
139  blatency = 5)
140 
141  level = logging.DEBUG if debug else logging.WARNING
142  self.dut._log.info('Set debug level '+str(level)+", debug="+str(debug))
143 
144  self.maxigp0.log.setLevel(level)
145  self.ps_sbus.log.setLevel(level)
146  self.saxihp0r.log.setLevel(level)
147  self.saxihp0w.log.setLevel(level)
148  self.saxihp1w.log.setLevel(level)
149  self.saxigp0.log.setLevel(level)
150 
151  #Initialize socket
152  self.PORT = port
153  self.HOST = host # Symbolic name meaning all available interfaces
154  self.socket_conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
155  self.socket_conn.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # Otherwise restarting program will need 2 minutes
156  try:
157  self.socket_conn.bind((self.HOST, self.PORT))
158  self.dut._log.debug('Socket bind complete, HOST=%s, PORT=%d'%(self.HOST,self.PORT))
159  self.socket_conn.listen(1) # just a single request (may increase to 5 (backlog)
160  self.dut._log.info ('Socket now listening to a single request on port %d: send command, receive response, close'%(self.PORT))
161  except socket.error as msg:
162  self.dut._log.info ("Maybe you need to run 'killall vvp' to close previously opened socket?" )
163  self.logErrorTerminate('Bind failed. Error Code : %s Message %s'%( str(msg[0]),msg[1]))
164 
165 
166  def logErrorTerminate(self, msg):
167  self.dut._log.error(msg)
168  cocotb.regression.tear_down()
169  raise TestFailure(msg)
170 
171  @cocotb.coroutine
173  line = None
174  try:
175  self.soc_conn, soc_addr = self.socket_conn.accept()
176  self.dut._log.debug ("Connected with %s"%(soc_addr[0] + ':' + str(soc_addr[1])))
177  #Sending message to connected client
178  line = self.soc_conn.recv(4096) # or make it unlimited?
179  self.dut._log.debug("Received from socket: %s"%(line))
180  except:
181  self.logErrorTerminate("Socket seems to have died :-(")
182  self.dut._log.debug("1.Received from socket: %s"%(line))
183  yield self.executeCommand(line)
184  self.dut._log.debug("3.Received from socket: %s"%(line))
185 
186  @cocotb.coroutine
187  def executeCommand(self,line):
188  self.dut._log.debug("1.executeCommand: %s"%(line))
189  if not line:
190  raise ReturnValue(None)
191  self.dut._log.debug("2.executeCommand: %s"%(line))
192  self.cmd.fromJSON(line)
193 
194  #TODO: add interrupt related commands (including wait IRQ with timeout
195  if self.cmd.getStart():
196  self.dut._log.info('Received START, waiting reset to be over')
197  yield Timer(10000)
198 
199  while self.dut.reset_out.value.get_binstr() != "1":
200  yield Timer(10000)
201  while self.dut.reset_out.value:
202  yield Timer(10000)
203 
204  self.saxihp0r_thread = cocotb.fork(self.saxihp0r.saxi_rd_run())
205  self.saxihp0w_thread = cocotb.fork(self.saxihp0w.saxi_wr_run())
206  self.saxihp1w_thread = cocotb.fork(self.saxihp1w.saxi_wr_run())
207  self.saxigp0_thread = cocotb.fork(self.saxigp0.saxi_wr_run())
208  self.soc_conn.send(self.cmd.toJSON(0)+"\n")
209  self.dut._log.debug('Sent 0 to the socket')
210  started=True
211 
212  elif self.cmd.getStop():
213  self.dut._log.info('Received STOP, closing...')
214  self.soc_conn.send(self.cmd.toJSON(0)+"\n")
215  self.soc_conn.close()
216  yield Timer(10000) # small pause for the wave output
217 
218  self.socket_conn.shutdown(socket.SHUT_RDWR)
219  self.socket_conn.close()
220  cocotb.regression.tear_down()
221  started=False
222  raise TestSuccess('Terminating as received STOP command')
223  #For now write - one at a time, TODO: a) consolidate, b) decode address (some will be just a disk file)
224  elif self.cmd.getWrite():
225  ad = self.cmd.getWrite()
226  self.dut._log.debug('Received WRITE, 0x%0x: %s'%(ad[0],hex_list(ad[1])))
227  if ad[0]in self.RESERVED:
228  if ad[0] == self.INTM_ADDRESS:
229  self.int_mask = ad[1][0]
230  rslt = 0
231  elif (ad[0] >= self.memlow) and (ad[0] < self.memhigh):
232  addr = ad[0]
233  self._memfile.seek(addr)
234  for data in ad[1]: # currently only single word is supported
235  sdata=struct.pack("<L",data) # little-endian, u32
236  self._memfile.write(sdata)
237  self.dut._log.debug("Written 'system memory': 0x%08x => 0x%08x"%(data,addr))
238  addr += 4
239  rslt = 0
240  elif(ad[0] >= 0x40000000) and (ad[0] < 0x80000000):
241  rslt = yield self.maxigp0.axi_write(address = ad[0],
242  value = ad[1],
243  byte_enable = None,
244  id = self.writeID,
245  dsize = 2,
246  burst = 1,
247  address_latency = 0,
248  data_latency = 0)
249  self.dut._log.debug('maxigp0.axi_write yielded %s'%(str(rslt)))
250  self.writeID = (self.writeID+1) & self.writeIDMask
251  elif (ad[0] >= 0xc0000000) and (ad[0] < 0xfffffffc):
252  self.ps_sbus.write_reg(ad[0],ad[1][0])
253  rslt = 0
254  else:
255  self.dut._log.info('Write address 0x%08x is outside of maxgp0, not yet supported'%(ad[0]))
256  rslt = 0
257  self.dut._log.info('WRITE 0x%08x <= %s'%(ad[0],hex_list(ad[1], max_items = 4)))
258  self.soc_conn.send(self.cmd.toJSON(rslt)+"\n")
259  self.dut._log.debug('Sent rslt to the socket')
260  elif self.cmd.getRead():
261  ad = self.cmd.getRead()
262  self.dut._log.debug(str(ad))
263  if not isinstance(ad,(list,tuple)):
264  ad=(ad,1)
265  elif len(ad) < 2:
266  ad=(ad[0],1)
267  self.dut._log.debug(str(ad))
268  if ad[0]in self.RESERVED:
269  if ad[0] == self.INTR_ADDRESS:
270  try:
271  dval=[self.dut.irq_r.value.integer]
272  except:
273  bv = self.dut.irq_r.value
274  bv.binstr = re.sub("[^1]","0",bv.binstr)
275  dval=[bv.integer]
276  elif ad[0] == self.INTM_ADDRESS:
277  dval = [self.int_mask]
278  else:
279  dval = [0]
280  elif (ad[0] >= self.memlow) and (ad[0] < self.memhigh):
281  addr = ad[0]
282  self._memfile.seek(addr)
283  self.dut._log.debug("read length="+str(len(self._memfile.read(4*ad[1]))))
284 
285  self._memfile.seek(addr)
286  self.dut._log.debug(str(ad))
287  dval = list(struct.unpack("<"+"L"*ad[1],self._memfile.read(4*ad[1])))
288  msg="'Written 'system memory: 0x%08x => "%(addr)
289  for d in dval:
290  msg += "0x%08x "%(d)
291  self.dut._log.debug(msg)
292 
293  elif(ad[0] >= 0x40000000) and (ad[0] < 0x80000000):
294  dval = yield self.maxigp0.axi_read(address = ad[0],
295  id = self.readID,
296  dlen = ad[1],
297  dsize = 2,
298  address_latency = 0,
299  data_latency = 0 )
300  self.dut._log.debug("axi_read returned 0x%08x => %s"%(ad[0],hex_list(dval, max_items = 4)))
301  self.readID = (self.readID+1) & self.readIDMask
302  elif (ad[0]>= 0xc0000000) and (ad[0] < 0xfffffffc):
303  dval = yield self.ps_sbus.read_reg(ad[0])
304  else:
305  self.dut._log.info('Read address 0x%08x is outside of maxgp0, not yet supported'%(ad[0]))
306  dval = [0]
307  self.soc_conn.send(self.cmd.toJSON(dval)+"\n")
308  self.dut._log.debug('Sent dval to the socket')
309  self.dut._log.info("READ 0x%08x =>%s"%(ad[0],hex_list(dval, max_items = 4)))
310  elif self.cmd.getFlush():
311  self.dut._log.info('Received flush')
312  self.flush_all()
313  self.soc_conn.send(self.cmd.toJSON(0)+"\n")
314  self.dut._log.debug('Sent 0 to the socket')
315 
316  elif self.cmd.getWait():
317 #self.MAXIGP0_CLK_FREQ
318  int_dly = self.cmd.getWait()
319  self.int_mask = int_dly[0]
320  num_clk= (int_dly[1] * self.ACLK_FREQ) // 1000000000
321  self.dut._log.info('Received WAIT, interrupt mask = 0x%0x, timeout = %d ns, %d clocks'%(self.int_mask,int_dly[1], num_clk))
322  n = 0
323  for _ in range(num_clk):
324  yield RisingEdge(self.dut.dutm0_aclk)
325  try:
326  irq_r=self.dut.irq_r.value.integer
327  except:
328  bv = self.dut.irq_r.value
329  bv.binstr = re.sub("[^1]","0",bv.binstr)
330  irq_r=bv.integer
331  if (self.int_mask & irq_r):
332  break
333  n += 1
334  self.soc_conn.send(self.cmd.toJSON(n)+"\n")
335  self.dut._log.debug('Sent %d to the socket'%(n))
336  self.dut._log.info(' WAIT over, passed %d ns'%((n * 1000000000)//self.ACLK_FREQ))
337  else:
338  self.dut._log.warning('Received unknown command: '+str(self.cmd))
339  self.soc_conn.send(self.cmd.toJSON(1)+"\n")
340  self.dut._log.debug('Sent 1 to the socket')
341 
342 def convert_string(txt):
343  number=0
344  for c in txt:
345  number = (number << 8) + ord(c)
346  return number
347 
348 @cocotb.coroutine
349 def run_test(dut, port=7777):
350  tb = X393_cocotb_server(dut=dut, host = "", port=7777)
351  dut._log.warn("Waiting for commnad on socket port %s"%(port))
352  while True:
353  try:
354  rslt= yield tb.receiveCommandFromSocket()
355  dut._log.debug("rslt = %s"%(str(rslt)))
356  except ReturnValue as rv:
357  line = rv.retval;
358  dut._log.info("rv = %s"%(str(rv)))
359  dut._log.info("line = %s"%(str(line)))
360  tb.socket_conn.close()
361  cocotb.regression.tear_down()
362 
def run_test( dut, port=7777)
def __init__( self, dut, port, host, mempath=None, autoflush=True)
def hex_list( lst, max_items=0, frmt="0x%08x")