x393_cocotb_server.py 17.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
from __future__ import print_function
"""
# Copyright (C) 2016, Elphel.inc.
# Simulation code for cocotb simulation for x393 project
#   
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http:#www.gnu.org/licenses/>.
Andrey Filippov's avatar
Andrey Filippov committed
18 19 20
@brief:      Simulation server for x393_dut.v top Verilog module.
Listens to the socket connection, accepts incoming commands, applies them
to the simulator and sends back data from the model. 
21 22 23 24 25 26 27 28 29 30 31 32 33
@author:     Andrey Filippov
@copyright:  2016 Elphel, Inc.
@license:    GPLv3.0+
@contact:    andrey@elphel.coml
"""
import os
import cocotb
import socket
import select
#import json
from socket_command import SocketCommand

from cocotb.triggers import Timer
34
from x393interfaces import MAXIGPMaster, PSBus, SAXIRdSim, SAXIWrSim
35 36 37 38
from cocotb.drivers import BitDriver
from cocotb.triggers import Timer, RisingEdge, ReadOnly
from cocotb.result import ReturnValue, TestFailure, TestError, TestSuccess
import logging
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
import re
import struct
def hex_list(lst, max_items=0, frmt="0x%08x"):
    if (max_items == 0) or (len(lst) <= max_items):
        hs="["
        for d in lst:
            hs+=frmt%(d)+", "
        return hs[:-2]+"]"    
    hs = "%d ["%len(lst)
    fi = max_items-1 if max_items > 1 else max_items
    for d in lst[:fi]:
        hs+=frmt%(d)+", "
    hs += "..."
    if fi < max_items:
        hs += " "+frmt%(d)
    return hs+"]"    
    
56
class X393_cocotb_server(object):
57 58 59
    INTR_ADDRESS = 0xfffffff0 #temporary address
    INTM_ADDRESS = 0xfffffff4 #temporary address
    RESERVED = (INTR_ADDRESS,INTM_ADDRESS)
60 61
    writeIDMask = (1 <<12) -1
    readIDMask = (1 <<12) -1
62
    started=False
63 64 65
    int_mask = 0 # all disabled
    def __init__(self, dut, port, host, mempath=None, autoflush=True): # , debug=False):
        self.ACLK_FREQ=50000000 # 50 MHz
66
        debug = os.getenv('COCOTB_DEBUG') # None/1
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
        if mempath is None:
            mempath =    os.getenv('SIMULATION_PATH')+"/"+"memfile"
        self.mempath =   mempath
        self.memlow =    0
        self.memhigh =   0x40000000
        self.autoflush = autoflush
        self.cmd=        SocketCommand()
        self.dut =       dut
        #Open file to use as system memory
        try:
            self._memfile=open(mempath, 'r+') #keep old file if it exists already
        except:    
            self._memfile=open(mempath, 'w+') #create a new file if it does not exist
            self.dut._log.info ("Created a new 'memory' file %s"%(mempath)) #
        #Extend to full size
        self._memfile.seek(self.memhigh-1)
        readOK=False
        try:
            readOK = len(self._memfile.read(1))>0
            self.dut._log.info ("Read from 0x%08x"%(self.memhigh-1)) #
            
        except:
            pass
        if not readOK:
            self._memfile.seek(self.memhigh-1)
            self._memfile.write(chr(0))
            self._memfile.flush()
            self.dut._log.info("Wrote to 0x%08x to extend file to full size"%(self.memhigh-1)) #
        
96
        #initialize MAXIGP0 interface (main control/status registers, TODO: add MAXIGP1 for SATA)
97 98 99 100 101
        self.maxigp0 = MAXIGPMaster(entity =   dut,
                                    name =     "dutm0",
                                    clock =    dut.dutm0_aclk,
                                    rdlag =    0,
                                    blag=0)
102 103
        self.writeID=0
        self.readID=0
104
        #initialize Zynq register access, has methods write_reg(a,d) and read_reg(a)
105 106 107
        self.ps_sbus = PSBus       (entity =     dut,
                                    name =       "ps_sbus",
                                    clock =      dut.ps_sbus_clk)
108 109
        #Bus masters (communicated over mempath file
        #Membridge to FPGA
110 111 112 113 114 115
        self.saxihp0r = SAXIRdSim  (entity =     dut,
                                    name =       "saxihp0",
                                    clock =      dut.axi_hclk,
                                    mempath =    self.mempath,
                                    memhigh =    self.memhigh,
                                    data_bytes = 8)
116
        #Membridge from FPGA
117 118 119 120 121 122 123 124
        self.saxihp0w = SAXIWrSim  (entity =     dut,
                                    name =       "saxihp0",
                                    clock =      dut.axi_hclk,
                                    mempath =    self.mempath,
                                    memhigh =    self.memhigh,
                                    data_bytes = 8,
                                    autoflush =  self.autoflush,
                                    blatency =   5)
125
        #Compressors from FPGA
126 127 128 129 130 131 132 133
        self.saxihp1w = SAXIWrSim  (entity =     dut,
                                    name =       "saxihp1",
                                    clock =      dut.axi_hclk,
                                    mempath =    self.mempath,
                                    memhigh =    self.memhigh,
                                    data_bytes = 8,
                                    autoflush =  self.autoflush,
                                    blatency =   5)
134
        #histograms from FPGA
135 136 137 138 139 140 141 142
        self.saxigp0 =   SAXIWrSim (entity =     dut,
                                    name =       "saxigp0",
                                    clock =      dut.saxi0_aclk,
                                    mempath =    self.mempath,
                                    memhigh =    self.memhigh,
                                    data_bytes = 4,
                                    autoflush =  self.autoflush,
                                    blatency =   5)
143 144 145 146 147 148 149 150 151 152
        #event logger from FPGA
        self.saxigp1 =   SAXIWrSim (entity =     dut,
                                    name =       "saxigp1",
                                    clock =      dut.saxi0_aclk,
                                    mempath =    self.mempath,
                                    memhigh =    self.memhigh,
                                    data_bytes = 4,
                                    autoflush =  self.autoflush,
                                    blatency =   5)

153
        level = logging.DEBUG if debug else logging.INFO # WARNING
154 155
        self.dut._log.info('Set debug level '+str(level)+", debug="+str(debug))
        
156
        self.maxigp0.log.setLevel(level)
157 158 159 160 161
        self.ps_sbus.log.setLevel(level)
        self.saxihp0r.log.setLevel(level)
        self.saxihp0w.log.setLevel(level)
        self.saxihp1w.log.setLevel(level)
        self.saxigp0.log.setLevel(level)
162
        self.saxigp1.log.setLevel(level)
163
        
164 165 166 167
        #Initialize socket
        self.PORT = port
        self.HOST = host   # Symbolic name meaning all available interfaces
        self.socket_conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
168
        self.socket_conn.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # Otherwise restarting program will need 2 minutes
169 170 171 172 173 174
        try:
            self.socket_conn.bind((self.HOST, self.PORT))
            self.dut._log.debug('Socket bind complete, HOST=%s, PORT=%d'%(self.HOST,self.PORT))
            self.socket_conn.listen(1) # just a single request (may increase to 5 (backlog)
            self.dut._log.info ('Socket now listening to a single request on port %d: send command, receive response, close'%(self.PORT))
        except socket.error as msg:
175
            self.dut._log.info ("Maybe you need to run 'killall vvp' to close previously opened socket?" )
176
            self.logErrorTerminate('Bind failed. Error Code : %s Message %s'%( str(msg[0]),msg[1]))
177
            
178 179 180 181 182 183 184 185 186 187 188 189 190 191
        
    def logErrorTerminate(self, msg):
        self.dut._log.error(msg)
        cocotb.regression.tear_down()
        raise TestFailure(msg)
    
    @cocotb.coroutine
    def receiveCommandFromSocket(self):
        line = None
        try:
            self.soc_conn, soc_addr = self.socket_conn.accept()
            self.dut._log.debug ("Connected with %s"%(soc_addr[0] + ':' + str(soc_addr[1])))
            #Sending message to connected client
            line = self.soc_conn.recv(4096) # or make it unlimited?
192
            self.dut._log.debug("Received from socket: %s"%(line))
193 194
        except:
            self.logErrorTerminate("Socket seems to have died :-(")
195
        self.dut._log.debug("1.Received from socket: %s"%(line))
196
        yield self.executeCommand(line)
197
        self.dut._log.debug("3.Received from socket: %s"%(line))
198 199 200
    
    @cocotb.coroutine
    def executeCommand(self,line):
201
        self.dut._log.debug("1.executeCommand: %s"%(line))
202 203
        if not line:
            raise ReturnValue(None)
204
        self.dut._log.debug("2.executeCommand: %s"%(line))
205
        self.cmd.fromJSON(line)
206 207
        
        #TODO: add interrupt related commands (including wait IRQ with timeout
208 209 210
        if self.cmd.getStart():
            self.dut._log.info('Received START, waiting reset to be over')
            yield Timer(10000)
211

212 213 214 215
            while self.dut.reset_out.value.get_binstr() != "1":
                yield Timer(10000)
            while self.dut.reset_out.value:
                yield Timer(10000)
216 217 218 219 220

            self.saxihp0r_thread = cocotb.fork(self.saxihp0r.saxi_rd_run())    
            self.saxihp0w_thread = cocotb.fork(self.saxihp0w.saxi_wr_run())    
            self.saxihp1w_thread = cocotb.fork(self.saxihp1w.saxi_wr_run())    
            self.saxigp0_thread =  cocotb.fork(self.saxigp0.saxi_wr_run())    
221
            self.saxigp1_thread =  cocotb.fork(self.saxigp1.saxi_wr_run())    
222 223
            self.soc_conn.send(self.cmd.toJSON(0)+"\n")
            self.dut._log.debug('Sent 0 to the socket')
224
            started=True
225 226

        elif self.cmd.getStop():
227
            self.dut._log.info('Received STOP, closing...')
228
            self.soc_conn.send(self.cmd.toJSON(0)+"\n")
229
            self.soc_conn.close()
230 231
            yield Timer(10000) # small pause for the wave output

232 233 234
            self.socket_conn.shutdown(socket.SHUT_RDWR)
            self.socket_conn.close()
            cocotb.regression.tear_down()
235
            started=False
236
            raise TestSuccess('Terminating as received STOP command')
237 238 239
        #For now write - one at a time, TODO: a) consolidate, b) decode address (some will be just a disk file)
        elif self.cmd.getWrite():
            ad = self.cmd.getWrite()
240 241 242 243 244 245 246 247 248 249 250 251 252 253
            self.dut._log.debug('Received WRITE, 0x%0x: %s'%(ad[0],hex_list(ad[1])))
            if ad[0]in self.RESERVED:
                if ad[0] == self.INTM_ADDRESS:
                    self.int_mask = ad[1][0]
                rslt = 0 
            elif (ad[0] >= self.memlow) and  (ad[0] < self.memhigh):
                addr = ad[0]
                self._memfile.seek(addr)
                for data in ad[1]: # currently only single word is supported
                    sdata=struct.pack("<L",data) # little-endian, u32
                    self._memfile.write(sdata)
                    self.dut._log.debug("Written 'system memory': 0x%08x => 0x%08x"%(data,addr))
                    addr += 4
                rslt = 0 
254 255 256 257 258 259 260 261 262
            elif(ad[0] >= 0x40000000) and (ad[0] < 0x80000000):
                rslt = yield self.maxigp0.axi_write(address =     ad[0],
                                                value =           ad[1],
                                                byte_enable =     None,
                                                id =              self.writeID,
                                                dsize =           2,
                                                burst =           1,
                                                address_latency = 0,
                                                data_latency =    0)
263
                self.dut._log.debug('maxigp0.axi_write yielded %s'%(str(rslt)))
264
                self.writeID = (self.writeID+1) & self.writeIDMask
265
            elif (ad[0] >= 0xc0000000) and (ad[0] < 0xfffffffc):
266 267 268 269
                self.ps_sbus.write_reg(ad[0],ad[1][0])
                rslt = 0 
            else:
                self.dut._log.info('Write address 0x%08x is outside of maxgp0, not yet supported'%(ad[0]))
270 271
                rslt = 0
            self.dut._log.info('WRITE 0x%08x <= %s'%(ad[0],hex_list(ad[1], max_items = 4)))
272 273 274
            self.soc_conn.send(self.cmd.toJSON(rslt)+"\n")
            self.dut._log.debug('Sent rslt to the socket')
        elif self.cmd.getRead():
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
            ad = self.cmd.getRead()
            self.dut._log.debug(str(ad))
            if not isinstance(ad,(list,tuple)):
                ad=(ad,1)
            elif len(ad) < 2:
                ad=(ad[0],1)
            self.dut._log.debug(str(ad))
            if ad[0]in self.RESERVED:
                if ad[0] == self.INTR_ADDRESS:
                    try:
                        dval=[self.dut.irq_r.value.integer]
                    except:
                        bv = self.dut.irq_r.value
                        bv.binstr = re.sub("[^1]","0",bv.binstr)
                        dval=[bv.integer]
                elif ad[0] == self.INTM_ADDRESS:
                    dval = [self.int_mask]
                else:
                    dval = [0]    
            elif (ad[0] >= self.memlow) and  (ad[0] < self.memhigh):
                addr = ad[0]
                self._memfile.seek(addr)
                self.dut._log.debug("read length="+str(len(self._memfile.read(4*ad[1]))))
                
                self._memfile.seek(addr)
                self.dut._log.debug(str(ad))
                dval = list(struct.unpack("<"+"L"*ad[1],self._memfile.read(4*ad[1])))
                msg="'Written 'system memory: 0x%08x => "%(addr)
                for d in dval:
                    msg += "0x%08x "%(d)
                self.dut._log.debug(msg)
                
            elif(ad[0] >= 0x40000000) and (ad[0] < 0x80000000):
                dval = yield  self.maxigp0.axi_read(address =     ad[0],
309
                                               id =               self.readID,
310
                                                dlen =            ad[1],
311 312 313
                                                dsize =           2,
                                                address_latency = 0,
                                                data_latency =    0 )
314
                self.dut._log.debug("axi_read returned 0x%08x => %s"%(ad[0],hex_list(dval, max_items = 4)))
315
                self.readID = (self.readID+1) & self.readIDMask
316
            elif (ad[0]>= 0xc0000000) and (ad[0] < 0xfffffffc):
317 318
                dval = yield  self.ps_sbus.read_reg(ad[0])
            else:
319
                self.dut._log.info('Read address 0x%08x is outside of maxgp0, not yet supported'%(ad[0]))
320
                dval = [0]    
321 322
            self.soc_conn.send(self.cmd.toJSON(dval)+"\n")
            self.dut._log.debug('Sent dval to the socket')
323 324 325 326 327 328
            self.dut._log.info("READ 0x%08x =>%s"%(ad[0],hex_list(dval, max_items = 4)))
        elif self.cmd.getFlush():
            self.dut._log.info('Received flush')
            self.flush_all()
            self.soc_conn.send(self.cmd.toJSON(0)+"\n")
            self.dut._log.debug('Sent 0 to the socket')
329
            
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
        elif self.cmd.getWait():
#self.MAXIGP0_CLK_FREQ            
            int_dly = self.cmd.getWait()
            self.int_mask = int_dly[0]
            num_clk= (int_dly[1] * self.ACLK_FREQ) // 1000000000
            self.dut._log.info('Received WAIT, interrupt mask = 0x%0x, timeout = %d ns, %d clocks'%(self.int_mask,int_dly[1], num_clk))
            n = 0
            for _ in range(num_clk):
                yield RisingEdge(self.dut.dutm0_aclk)
                try:
                    irq_r=self.dut.irq_r.value.integer
                except:
                    bv = self.dut.irq_r.value
                    bv.binstr = re.sub("[^1]","0",bv.binstr)
                    irq_r=bv.integer
                if (self.int_mask & irq_r):
                    break
                n += 1
            self.soc_conn.send(self.cmd.toJSON(n)+"\n")
            self.dut._log.debug('Sent %d to the socket'%(n))
            self.dut._log.info(' WAIT over, passed %d ns'%((n * 1000000000)//self.ACLK_FREQ))
        else:
            self.dut._log.warning('Received unknown command: '+str(self.cmd))
            self.soc_conn.send(self.cmd.toJSON(1)+"\n")
            self.dut._log.debug('Sent 1 to the socket')
355
            
356 357 358 359 360 361 362 363
def convert_string(txt):
    number=0
    for c in txt:
        number = (number << 8) + ord(c)
    return number   

@cocotb.coroutine
def run_test(dut, port=7777):
364 365
    tb = X393_cocotb_server(dut=dut, host = "", port=7777)
    dut._log.warn("Waiting for commnad on socket port %s"%(port))
366 367 368
    while True:
        try:
            rslt= yield tb.receiveCommandFromSocket()
369
            dut._log.debug("rslt = %s"%(str(rslt)))
370
        except ReturnValue as rv:
371
            line = rv.retval;
372
            dut._log.info("rv = %s"%(str(rv)))
373
            dut._log.info("line = %s"%(str(line)))
374 375 376
    tb.socket_conn.close()
    cocotb.regression.tear_down()