# toysram sdr tb
#
# variations:
#  sdr: 1x
#  ddr: 1x
#  ddr: 2x

import cocotb
from cocotb.clock import Clock
from cocotb.triggers import Timer
from cocotb.triggers import FallingEdge
from cocotb.triggers import RisingEdge
from cocotb.handle import Force
from cocotb.handle import Release

import itertools
from dotmap import DotMap
from random import getrandbits
from random import randint

from OPEnv import *

def hexrandom(w=16):
  n = getrandbits(w*4)
  return '{0:0{l}X}'.format(n, l=w)

# Classes ----------------------------------------------------------------------------------------

class Memory:

    def __init__(self, rows, bits, init=0):
        self.mem = [init] * rows
        self.rows = rows
        self.bits = bits

    def read(self, adr):
        return self.mem[adr]

    def readall(self):
       mem = []
       for i in range(len(self.mem)):
          mem.append(self.mem[i])
       return mem

    def write(self, adr, dat):
        self.mem[adr] = dat

    def __str__(self):
        t = ''
        for i in range(self.rows, 4):
            t1 = f'[{i:02X}] {self.mem[i]:0{bits/4}X}'
            for j in range(i+1, i+4):
                t1 += f'  [{j:02X}] {self.mem[j]:0{bits/4}X}'
            t += t1 + '\n'
        return t

class Port:

   def __init__(self, sim, id, type='r', nibbles=18):
      self.sim = sim
      self.dut = sim.dut
      self.id = id
      self.type = type
      self.nibbles = nibbles

   def read(self, adr):
      if self.id == 0:
         self.dut.rd_enb_0.value = 1
         self.dut.rd_adr_0.value = adr
      elif self.id == 1:
         self.dut.rd_enb_1.value = 1
         self.dut.rd_adr_1.value = adr
      elif self.id == 2:
         self.dut.rd_enb_2.value = 1
         self.dut.rd_adr_2.value = adr
      elif self.id == 3:
         self.dut.rd_enb_3.value = 1
         self.dut.rd_adr_3.value = adr

      self.sim.msg(f'Port={self.id} RD @{adr:02X}')

   def write(self, adr, dat):
      if self.id == 0:
         self.dut.wr_enb_0.value = 1
         self.dut.wr_adr_0.value = adr
         self.dut.wr_dat_0.value = dat
      elif self.id == 1:
         self.dut.wr_enb_1.value = 1
         self.dut.wr_adr_1.value = adr
         self.dut.wr_dat_1.value = dat
      elif self.id == 2:
         self.dut.wr_enb_2.value = 1
         self.dut.wr_adr_2.value = adr
         self.dut.wr_dat_2.value = dat
      elif self.id == 3:
         self.dut.wr_enb_3.value = 1
         self.dut.wr_adr_3.value = adr
         self.dut.wr_dat_3.value = dat

      self.sim.msg(f'Port={self.id} WR @{adr:02X}={dat:0{self.nibbles}X}')

   def data(self):
      if self.id == 0:
         return self.dut.rd_dat_0.value.integer
      elif self.id == 1:
         return self.dut.rd_dat_1.value.integer
      elif self.id == 2:
         return self.dut.rd_dat_2.value.integer
      elif self.id == 3:
         return self.dut.rd_dat_3.value.integer

   def idle(self):
      if self.type == 'r':
         if self.id == 0:
            self.dut.rd_enb_0.value = 0
            self.dut.rd_adr_0.value = 0 # random
         elif self.id == 1:
            self.dut.rd_enb_1.value = 0
            self.dut.rd_adr_1.value = 0 # random
         elif self.id == 2:
            self.dut.rd_enb_2.value = 0
            self.dut.rd_adr_2.value = 0 # random
         elif self.id == 3:
            self.dut.rd_enb_3.value = 0
            self.dut.rd_adr_3.value = 0 # random
      else:
         if self.id == 0:
            self.dut.wr_enb_0.value = 0
            self.dut.wr_adr_0.value = 0 # random
            self.dut.wr_dat_0.value = 0 # random
         elif self.id == 1:
            self.dut.wr_enb_1.value = 0
            self.dut.wr_adr_1.value = 0 # random
            self.dut.wr_dat_1.value = 0 # random
         elif self.id == 2:
            self.dut.wr_enb_2.value = 0
            self.dut.wr_adr_2.value = 0 # random
            self.dut.wr_dat_2.value = 0 # random
         elif self.id == 3:
            self.dut.wr_enb_3.value = 0
            self.dut.wr_adr_3.value = 0 # random
            self.dut.wr_dat_31.value = 0 # random

def hex(n, pad=0):
   if pad:
      return f'000000000000000000000000{n.value.hex()[2:].upper()}'[-pad:]
   else:
      return n.value.hex()[2:].upper()

def printstate(sim):
   if sim.sdr:
      try:
         sim.msg(f'R0: {sim.dut.ra.rd_enb_0_q.value} {hex(sim.dut.ra.rd_adr_0_q, 2)} {hex(sim.dut.ra.rd_dat_0_q, 18)} R1: {sim.dut.ra.rd_enb_1_q.value} {hex(sim.dut.ra.rd_adr_1_q, 2)} {hex(sim.dut.ra.rd_dat_1_q, 18)}')
      except:
         sim.msg(f'R0: {sim.dut.ra.rd_enb_0_q.value} {sim.dut.ra.rd_adr_0_q.value} {sim.dut.ra.rd_dat_0_q.value} R1: {sim.dut.ra.rd_enb_1_q.value} {sim.dut.ra.rd_adr_1_q.value} {sim.dut.ra.rd_dat_1_q.value}')

      try:
         sim.msg(f'W0: {sim.dut.ra.wr_enb_0_q.value} {hex(sim.dut.ra.wr_adr_0_q, 2)} {hex(sim.dut.ra.wr_dat_0_q, 18)}')
      except:
         sim.msg(f'W0: {sim.dut.ra.wr_enb_0_q.value} {sim.dut.ra.wr_adr_0_q.value} {sim.dut.ra.wr_dat_0_q.value}')
   else:
      pass

def printstate_32(sim):
   if sim.sdr:
      try:
         sim.msg(f'R0: {sim.dut.ra.rd_enb_0_q.value} {hex(sim.dut.ra.rd_adr_0_q, 2)} {hex(sim.dut.ra.rd_dat_0_q, 8)} R1: {sim.dut.ra.rd_enb_1_q.value} {hex(sim.dut.ra.rd_adr_1_q, 2)} {hex(sim.dut.ra.rd_dat_1_q, 8)}')
      except:
         sim.msg(f'R0: {sim.dut.ra.rd_enb_0_q.value} {sim.dut.ra.rd_adr_0_q.value} {sim.dut.ra.rd_dat_0_q.value} R1: {sim.dut.ra.rd_enb_1_q.value} {sim.dut.ra.rd_adr_1_q.value} {sim.dut.ra.rd_dat_1_q.value}')

      try:
         sim.msg(f'W0: {sim.dut.ra.wr_enb_0_q.value} {hex(sim.dut.ra.wr_adr_0_q, 2)} {hex(sim.dut.ra.wr_dat_0_q, 8)}')
      except:
         sim.msg(f'W0: {sim.dut.ra.wr_enb_0_q.value} {sim.dut.ra.wr_adr_0_q.value} {sim.dut.ra.wr_dat_0_q.value}')
   else:
      pass

# ------------------------------------------------------------------------------------------------
# Tasks

# get rid of z on anything that will be sampled here
# is there a func to get all inputs?
async def init(dut, sim):
   """Initialize inputs. """

   return

async def initSite(dut, sim):
   """Initialize inputs. """

   dut.wbs_stb_i.value = 0
   dut.wbs_cyc_i.value = 0
   dut.la_data_in.value = 0
   dut.la_oenb.value = 0
   dut.io_in.value = 0

   return

async def config(dut, sim):
   """Configure core, etc. """

   return

async def coreMonitor(dut, sim):
   """Watch for core events. """

   return

async def genReset(dut, sim):
   """Generate reset. """

   first = True
   done = False

   while not done:
      await RisingEdge(sim.clk1x)
      if sim.cycle < sim.resetCycle:
         if first:
            sim.msg(f'Resetting...')
            first = False
         dut.reset.value = 1
      elif not done:
         sim.msg(f'Releasing reset.')
         dut.reset.value = 0
         done = True

async def genClocks(dut, sim):
   """Generate clock pulses. """

   sim.clk1x = dut.clk
   clk = Clock(sim.clk1x, sim.clk1xPeriod, 'ns')
   await cocotb.start(clk.start())

   sim.cycle = 0
   while True:
      sim.cycle += 1
      if sim.cycle % sim.hbCycles == 0:
         sim.msg(f'...tick...')
      await RisingEdge(sim.clk1x)


async def configSite(dut, sim):
   """Configure core, etc. """

   return

async def genResetSite(dut, sim):
   """Generate reset. """

   first = True
   done = False

   while not done:
      await RisingEdge(sim.clk1x)
      if sim.cycle < sim.resetCycle:
         if first:
            sim.msg(f'Resetting...')
            first = False
         dut.wb_rst_i.value = 1
      elif not done:
         sim.msg(f'Releasing reset.')
         dut.wb_rst_i.value = 0
         done = True

async def genClocksSite(dut, sim):
   """Generate clock pulses. """

   sim.clk1x = dut.wb_clk_i
   clk = Clock(sim.clk1x, sim.clk1xPeriod, 'ns')
   await cocotb.start(clk.start())

   sim.cycle = 0
   while True:
      sim.cycle += 1
      if sim.randomIO:
         dut.io_in.value = int(hexrandom(9),16)*2  # randomize 1:36 (test_enable=0)
      assert sim.cycle < sim.maxCycles, f'Maximum cycles reached!'
      if sim.cycle % sim.hbCycles == 0:
         sim.msg(f'...tick...')
      await RisingEdge(sim.clk1x)


# ------------------------------------------------------------------------------------------------
# Interfaces

# ------------------------------------------------------------------------------------------------
# Do something

# ------------------------------------------------------------------------------------------------
# ------------------------------------------------------------------------------------------------
@cocotb.test()
async def tb(dut):
   """ToySRAM 64x72 array test"""

   sim = Sim(dut)
   sim.sdr = True
   sim.ddr = False
   sim.clk1xPeriod = 1
   sim.clk2x = False
   sim.maxCycles = 50000

   # init stuff
   await init(dut, sim)

   # start clocks,reset
   clkGen = await cocotb.start(genClocks(dut, sim))
   await cocotb.start(genReset(dut, sim))

   await Timer(sim.resetCycle + 5, units='ns')
   if dut.reset.value != 0:
      sim.ok = False
      sim.fail = 'Reset active too long!'

   # config stuff
   await config(dut, sim)

   # monitor stuff
   await cocotb.start(coreMonitor(dut, sim))

   # do stuff
   data = Memory(64, 72)
   if sim.sdr:
      portsRd = [Port(sim, 0, 'r'), Port(sim, 1, 'r')]
      portsWr = [Port(sim, 0, 'w')]
   else:
      portsRd = [Port(sim, 0, 'r'), Port(sim, 1, 'r'), Port(sim, 2, 'r'), Port(sim, 3, 'r')]
      portsWr = [Port(sim, 0, 'w'), Port(sim, 1, 'w')]

   await RisingEdge(sim.clk1x)

   # idle
   for p in portsRd:
      p.idle()
   for p in portsWr:
      p.idle()

   for i in range(10):
      await RisingEdge(sim.clk1x)

   # init array
   sim.msg('Initializing array...')
   if sim.sdr:
      for a in range(64):
         d0 = int(f'{a:02X}55555555555555{a:02X}', 16)
         portsWr[0].write(a, d0)
         await RisingEdge(sim.clk1x)
         data.write(a, d0)  # now visible for reads
         portsWr[0].idle()
   else:
      pass
      '''
      for a in range(0, 64, 2):
         d0 = int(f'5555555555555555{a:02X}', 16)
         portsWr[0].write(a, d0)
         d1 = int(f'5555555555555555{a+1:02X}', 16)
         portsWr[1].write(a+1, d1)
         run(1, printstate)
         data.write(a, d0)  # now visible for reads
         data.write(a+1, d1)  # now visible for reads
         portsWr[0].idle()
         portsWr[1].idle()
      '''

   # random cmds
   sim.msg('Running random commands...')
   updates = []
   checks = []
   reads = [0, 0, 0, 0]
   writes = [0, 0]
   saveData = None
   quiesced = False
   quiesceCyc = sim.cycle + sim.maxCycles - 10
   errors = 0

   # configure allowed ports/entries
   rangesRd = [(0,63), (0,63), (0,63), (0,63)]     # full
   #rangesRd = [(0,7), (0,7), (0,7), (0,7)]        # hammer
   rangesWr = [(0,63), (0,63),(0,63), (0,63)]      # full
   #rangesWr = [(0,7), (0,7)]                      # hammer
   #rangesWr = [None, None]                        # none

   verbose = True

   for c in range(sim.maxCycles):

      sim.ok = True

      await FallingEdge(sim.clk1x)

      # check reads
      checksNext = []
      for i in range(len(checks)):
         rd = checks[i]
         if sim.cycle == rd[0]:
            port = rd[1]
            adr = rd[2]
            act = portsRd[port].data()
            exp = saveData[rd[2]]
            if act != exp:
               sim.ok = False
               try:
                  sim.msg(f'* RD MISCOMPARE * port={port} adr={adr:02X} act={act:018X} exp={exp:018X}')
               except:
                  sim.msg(f'* RD MISCOMPARE * port={port} adr={adr} act={act} exp={exp}')
            elif verbose:
               sim.msg(f'* RD COMPARE * port={port} adr={adr:02X} act={act:018X} exp={exp:018X}')

         else:
            checksNext.append(rd)
      checks = checksNext

      # do writes
      updatesNext = [] # always only 1 cycle
      for i in range(len(updates)):
         wr = updates[i]
         if sim.cycle == wr[0]:
            data.write(wr[2], wr[3])
         else:
            assert False, f'HUH? should always be this cycle! {sim.cycle},{updates}'
      updates = updatesNext

      # save current data
      saveData = data.readall()

      # quiesce?
      if sim.cycle >= quiesceCyc:
         if not quiesced:
            sim.msg('Quiescing...')
            quiesced = True

      # w0/w1 write coll will give w1 precedence - or make it avoid
      aw = [None] * 2
      for i in range(len(portsWr)):
         portsWr[i].idle()
         aw[i] = -1
         if rangesWr[i] is not None and not quiesced and randint(1, 10) < 5:
            r = rangesWr[i]
            aw[i] = randint(r[0], r[1])
            d = int(hexrandom(18), 16)
            portsWr[i].write(aw[i], d)
            updates.append((sim.cycle + 1, i, aw[i], d))
            writes[i] += 1

      for i in range(len(portsRd)):
         portsRd[i].idle()
         if rangesRd[i] is not None and not quiesced and randint(1, 10) < 5:
            r = rangesRd[i]
            ar = randint(r[0], r[1])
            while ar == aw[0] or ar == aw[1]:
               ar = randint(r[0], r[1])
            portsRd[i].read(ar)
            checks.append((sim.cycle + 2, i, ar))
            reads[i] += 1

      await RisingEdge(sim.clk1x)
      #printstate(sim)

      if not sim.ok: # and stopOnFail:
         break

   sim.msg('Done.')
   # idle
   for p in portsRd:
      p.idle()
   for p in portsWr:
      p.idle()

   sim.msg('Final State')
   print(data)
   for i in range(len(portsRd)):
      print(f'Reads Port {i}:  {reads[i]}')
   for i in range(len(portsWr)):
      print(f'Writes Port {i}: {writes[i]}')

   if sim.ok:
      sim.msg(f'You has opulence.')
   else:
      for i in range(10):
          await RisingEdge(sim.clk1x)
      sim.msg(f'You are worthless and weak!')
      assert False, f'[{sim.cycle:08d}] {sim.fail}'

# ------------------------------------------------------------------------------------------------
# ------------------------------------------------------------------------------------------------
@cocotb.test()
async def tb_32x32(dut):
   """ToySRAM 32x32 array test"""

   sim = Sim(dut)
   sim.sdr = True
   sim.ddr = False
   sim.clk1xPeriod = 1
   sim.clk2x = False
   sim.maxCycles = 10000

   # init stuff
   await init(dut, sim)

   # start clocks,reset
   clkGen = await cocotb.start(genClocks(dut, sim))
   await cocotb.start(genReset(dut, sim))

   await Timer(sim.resetCycle + 5, units='ns')
   if dut.reset.value != 0:
      sim.ok = False
      sim.fail = 'Reset active too long!'

   # config stuff
   await config(dut, sim)

   # monitor stuff
   await cocotb.start(coreMonitor(dut, sim))

   # do stuff
   data = Memory(32, 32)
   if sim.sdr:
      portsRd = [Port(sim, 0, 'r', 8), Port(sim, 1, 'r', 8)]
      portsWr = [Port(sim, 0, 'w', 8)]
   else:
      portsRd = [Port(sim, 0, 'r', 8), Port(sim, 1, 'r', 8), Port(sim, 2, 'r', 8), Port(sim, 3, 'r', 8)]
      portsWr = [Port(sim, 0, 'w', 8), Port(sim, 1, 'w', 8)]

   await RisingEdge(sim.clk1x)

   # idle
   for p in portsRd:
      p.idle()
   for p in portsWr:
      p.idle()

   for i in range(10):
      await RisingEdge(sim.clk1x)

   # init array
   sim.msg('Initializing array...')
   if sim.sdr:
      for a in range(32):
         d0 = int(f'{a:02X}5555{a:02X}', 16)
         portsWr[0].write(a, d0)
         await RisingEdge(sim.clk1x)
         data.write(a, d0)  # now visible for reads
         portsWr[0].idle()
   else:
      pass

   # random cmds
   sim.msg('Running random commands...')
   updates = []
   checks = []
   reads = [0, 0, 0, 0]
   writes = [0, 0]
   saveData = None
   quiesced = False
   quiesceCyc = sim.cycle + sim.maxCycles - 10
   errors = 0
   #rangesRd = [(0,1), (0,1), (0,1), (0,1)]
   rangesRd = [(0,31), (0,31), (0,31), (0,31)]
   #rangesWr = [(0,1), (0,1)]
   rangesWr = [(0,31), (0,31)]
   verbose = True

   for c in range(sim.maxCycles):

      sim.ok = True

      await FallingEdge(sim.clk1x)

      # check reads
      checksNext = []
      for i in range(len(checks)):
         rd = checks[i]
         if sim.cycle == rd[0]:
            port = rd[1]
            adr = rd[2]
            act = portsRd[port].data()
            exp = saveData[rd[2]]
            if act != exp:
               sim.ok = False
               try:
                  sim.msg(f'* RD MISCOMPARE * port={port} adr={adr:02X} act={act:08X} exp={exp:08X}')
               except:
                  sim.msg(f'* RD MISCOMPARE * port={port} adr={adr} act={act} exp={exp}')
            elif verbose:
               sim.msg(f'* RD COMPARE * port={port} adr={adr:02X} act={act:08X} exp={exp:08X}')

         else:
            checksNext.append(rd)
      checks = checksNext

      # do writes
      updatesNext = [] # always only 1 cycle
      for i in range(len(updates)):
         wr = updates[i]
         if sim.cycle == wr[0]:
            data.write(wr[2], wr[3])
         else:
            assert False, f'HUH? should always be this cycle! {sim.cycle},{updates}'
      updates = updatesNext

      # save current data
      saveData = data.readall()

      # quiesce?
      if sim.cycle >= quiesceCyc:
         if not quiesced:
            sim.msg('Quiescing...')
            quiesced = True

      # w0/w1 write coll will give w1 precedence - or make it avoid
      aw = [None] * 2
      for i in range(len(portsWr)):
         portsWr[i].idle()
         aw[i] = -1
         if not quiesced and randint(1, 10) < 5:
            r = rangesWr[i]
            aw[i] = randint(r[0], r[1])
            d = int(hexrandom(8), 16)
            portsWr[i].write(aw[i], d)
            updates.append((sim.cycle + 1, i, aw[i], d))
            writes[i] += 1

      for i in range(len(portsRd)):
         portsRd[i].idle()
         if not quiesced and randint(1, 10) < 5:
            r = rangesRd[i]
            ar = randint(r[0], r[1])
            while ar == aw[0] or ar == aw[1]:
               ar = randint(r[0], r[1])
            portsRd[i].read(ar)
            checks.append((sim.cycle + 2, i, ar))
            reads[i] += 1

      await RisingEdge(sim.clk1x)
      #printstate_32(sim)

      if not sim.ok: # and stopOnFail:
         break

   sim.msg('Done.')
   # idle
   for p in portsRd:
      p.idle()
   for p in portsWr:
      p.idle()

   sim.msg('Final State')
   print(data)
   for i in range(len(portsRd)):
      print(f'Reads Port {i}:  {reads[i]}')
   for i in range(len(portsWr)):
      print(f'Writes Port {i}: {writes[i]}')

   if sim.ok:
      sim.msg(f'You has opulence.')
   else:
      for i in range(10):
          await RisingEdge(sim.clk1x)
      sim.msg(f'You are worthless and weak!')
      assert False, f'[{sim.cycle:08d}] {sim.fail}'

# ------------------------------------------------------------------------------------------------
# ------------------------------------------------------------------------------------------------
@cocotb.test()
async def tb_site(dut):
   """ToySRAM site test"""

   sim = Sim(dut)
   sim.sdr = True
   sim.ddr = False
   sim.clk1xPeriod = 1
   sim.clk2x = False
   sim.maxCycles = 50000
   sim.randomIO = True

   cfgBase = 0x00000000
   ctlBase = 0x00010000
   ra0Base = 0x00100000

   # init stuff
   await initSite(dut, sim)

   # start clocks,reset
   clkGen = await cocotb.start(genClocksSite(dut, sim))
   await cocotb.start(genResetSite(dut, sim))

   await Timer(sim.resetCycle + 5, units='ns')
   if dut.wb_rst_i.value != 0:
      sim.ok = False
      sim.fail = 'Reset active too long!'

   if dut.io_in.value != 0:
      sim.ok = False
      sim.fail = 'I/O inputs not zero!'

   # config stuff
   await configSite(dut, sim)

   # monitor stuff
   await cocotb.start(coreMonitor(dut, sim))

   # do stuff

   await RisingEdge(sim.clk1x)

   ra = Memory(32, 32)

   # write and read ra0 w0/r0
   for i in range(32):

      adr = ra0Base + i
      dat = int(hexrandom(8), 16)
      sim.msg(f'Writing Port 0 @{adr:08X} {i:02X}={dat:08X}')
      dut.wbs_cyc_i.value = 1
      dut.wbs_stb_i.value = 1
      dut.wbs_we_i.value = 1
      dut.wbs_adr_i.value = adr
      dut.wbs_sel_i.value = 0xF
      dut.wbs_dat_i.value = dat
      ra.write(i, dat)

      await RisingEdge(sim.clk1x)
      dut.wbs_cyc_i.value = 0
      dut.wbs_stb_i.value = 0

      while (dut.wbs_ack_o.value == 0):
         await RisingEdge(sim.clk1x)

      sim.msg(f'Reading Port 0 @{adr:08X} {i:02X}')
      dut.wbs_cyc_i.value = 1
      dut.wbs_stb_i.value = 1
      dut.wbs_we_i.value = 0
      dut.wbs_adr_i.value = adr

      await RisingEdge(sim.clk1x)
      dut.wbs_cyc_i.value = 0
      dut.wbs_stb_i.value = 0

      while (dut.wbs_ack_o.value == 0):
         await RisingEdge(sim.clk1x)

      sim.msg(f'Read Data: {dut.wbs_dat_o.value.integer:08X}')
      assert dut.wbs_dat_o.value == dat, f'Read data miscompare! exp={dat:08X} act={dut.wbs_dat_o.value.integer:08X}'

   # read ra0 r1
   for i in range(32):

      adr = ra0Base + 0x00004000 + i
      sim.msg(f'Reading Port 1 @{adr:08X}  {i:02X}')
      dut.wbs_cyc_i.value = 1
      dut.wbs_stb_i.value = 1
      dut.wbs_we_i.value = 0
      dut.wbs_adr_i.value = adr

      await RisingEdge(sim.clk1x)
      dut.wbs_cyc_i.value = 0
      dut.wbs_stb_i.value = 0

      while (dut.wbs_ack_o.value == 0):
         await RisingEdge(sim.clk1x)

      sim.msg(f'Read Data: {dut.wbs_dat_o.value.integer:08X}')
      assert dut.wbs_dat_o.value == ra.read(i), f'Read data miscompare! exp={ra.read(i):08X} act={dut.wbs_dat_o.value.integer:08X}'

   # read ra0 r0,r1 through scan

   # control.v
   test_enable = dut.io_in[0];
   scan_clk = dut.io_in[1];
   scan_di = dut.io_in[2];
   scan_do = dut.io_out[3]

   io_ra0_clk = dut.io_in[4];
   io_ra0_rst = dut.io_in[5];
   io_ra0_r0_enb = dut.io_in[6];
   io_ra0_r1_enb = dut.io_in[7];
   io_ra0_w0_enb = dut.io_in[8];

   '''
   io_ra0_r0_adr = scan_reg_q[127:123];
   io_ra0_r0_dat = scan_reg_q[122:91]; // loaded by io_ra0_clk
   io_ra0_r1_adr = scan_reg_q[90:86];
   io_ra0_r1_dat = scan_reg_q[85:54];  // loaded by io_ra0_clk
   io_ra0_w0_adr = scan_reg_q[53:49];
   io_ra0_w0_dat = scan_reg_q[48:17];
   '''

   scanLen = 128
   scanHalfPer = 1
   noisy = False

   sim.msg(f'Starting scan sequences.')
   test_enable.value = 1
   sim.randomIO = False

   # load 0's
   sim.msg(f'Writing zeroes...')
   scan_di.value = 0
   for i in range(scanLen):
      scan_clk.value = 0
      await Timer(scanHalfPer, units='ns')
      scan_clk.value = 1
      await Timer(scanHalfPer, units='ns')
      if noisy:
         sim.msg(f' ScanReg={dut.site.ctl.scan_reg_q.value}')

   # check 0's
   sim.msg(f'Checking zeroes...')
   assert scan_do.value == 0, f'Scanout not 0! {scan_do.value}'
   for i in range(1, scanLen):
      scan_clk.value = 0
      await Timer(scanHalfPer, units='ns')
      scan_clk.value = 1
      await Timer(scanHalfPer, units='ns')
      assert scan_do.value == 0, f'Scanout not 0! {scan_do.value}'

   # read r0,r1
   sim.msg(f'Reading R0@15, R1@16...')
   scanIn = '01111' + '11111111111111111111111111111111' + \
            '10000' + '11111111111111111111111111111111' + \
            '00000' + '00000000000000000000000000000000' + \
            '00000000000000000'

   sim.msg(f'Scanning in...')
   for i in range(scanLen):
      scan_di.value = int(scanIn[i])
      scan_clk.value = 0
      await Timer(scanHalfPer, units='ns')
      scan_clk.value = 1
      await Timer(scanHalfPer, units='ns')
      if noisy:
         sim.msg(f' ScanReg={dut.site.ctl.scan_reg_q.value}')

   readHalfPer = 50

   io_ra0_rst.value = 0
   io_ra0_r0_enb.value = 1
   io_ra0_r1_enb.value = 1
   await Timer(readHalfPer, units='ns')
   # adr
   sim.msg('Blipping RA0 clk...')
   io_ra0_clk.value = 1
   await Timer(readHalfPer, units='ns')
   io_ra0_clk.value = 0
   await Timer(readHalfPer, units='ns')
   if noisy:
      sim.msg(f' ScanReg={dut.site.ctl.scan_reg_q.value}')
   # dat
   sim.msg('Blipping RA0 clk...')
   io_ra0_r0_enb.value = 0
   io_ra0_r1_enb.value = 0
   io_ra0_clk.value = 1
   await Timer(readHalfPer, units='ns')
   io_ra0_clk.value = 0
   await Timer(readHalfPer, units='ns')
   # capture in scan_reg
   sim.msg('Blipping RA0 clk...')
   io_ra0_clk.value = 1
   await Timer(readHalfPer, units='ns')
   io_ra0_clk.value = 0
   await Timer(readHalfPer, units='ns')

   sim.msg(f'Scanning out...')
   scan_di.value = 0
   scanOut = f'{scan_do.value}'
   for i in range(1, scanLen):
      scan_clk.value = 0
      await Timer(scanHalfPer, units='ns')
      scan_clk.value = 1
      await Timer(scanHalfPer, units='ns')
      scanOut += f'{scan_do.value}'

   sim.msg(f'ScanData={int(scanOut,2):032X}')
   sim.msg(f' r0 adr:{int(scanOut[0:5], 2):02X}')
   sim.msg(f' r0 dat:{int(scanOut[5:37], 2):08X}')
   assert int(scanOut[5:37], 2) == ra.read(15), f'R0 Miscompare! exp={ra.read(15):08X} dat={int(scanOut[5:37], 2):08X}'
   sim.msg(f' r1 adr:{int(scanOut[37:42], 2):02X}')
   sim.msg(f' r1 dat:{int(scanOut[42:74], 2):08X}')
   assert int(scanOut[42:74], 2) == ra.read(16), f'R0 Miscompare! exp={ra.read(16):08X} dat={int(scanOut[42:74], 2):08X}'
   sim.msg(f' w0 adr:{int(scanOut[74:79], 2 ):02X}')
   sim.msg(f' w0 dat:{int(scanOut[79:111], 2):08X}')
   sim.msg(f'    cfg:{int(scanOut[111:], 2):05X}')

   # write w0
   sim.msg(f'Writing W0@15=08675309...')
   scanIn = '00000' + '11111111111111111111111111111111' + \
            '00000' + '11111111111111111111111111111111' + \
            '01111' + '00001000011001110101001100001001' + \
            '00000000000000000'

   sim.msg(f'Scanning in...')
   for i in range(scanLen):
      scan_di.value = int(scanIn[i])
      scan_clk.value = 0
      await Timer(scanHalfPer, units='ns')
      scan_clk.value = 1
      await Timer(scanHalfPer, units='ns')
      if noisy:
         sim.msg(f' ScanReg={dut.site.ctl.scan_reg_q.value}')

   readHalfPer = 50

   io_ra0_rst.value = 0
   io_ra0_w0_enb.value = 1
   await Timer(readHalfPer, units='ns')
   # adr/dat
   sim.msg('Blipping RA0 clk...')
   io_ra0_clk.value = 1
   await Timer(readHalfPer, units='ns')
   io_ra0_clk.value = 0
   await Timer(readHalfPer, units='ns')
   if noisy:
      sim.msg(f' ScanReg={dut.site.ctl.scan_reg_q.value}')
   io_ra0_w0_enb.value = 0
   ra.write(15, int('08675309', 16))

   # read r0,r1
   sim.msg(f'Reading R0@15, R1@16...')
   scanIn = '01111' + '11111111111111111111111111111111' + \
            '10000' + '11111111111111111111111111111111' + \
            '00000' + '00000000000000000000000000000000' + \
            '00000000000000000'

   sim.msg(f'Scanning in...')
   for i in range(scanLen):
      scan_di.value = int(scanIn[i])
      scan_clk.value = 0
      await Timer(scanHalfPer, units='ns')
      scan_clk.value = 1
      await Timer(scanHalfPer, units='ns')
      if noisy:
         sim.msg(f' ScanReg={dut.site.ctl.scan_reg_q.value}')

   readHalfPer = 50

   io_ra0_rst.value = 0
   io_ra0_r0_enb.value = 1
   io_ra0_r1_enb.value = 1
   await Timer(readHalfPer, units='ns')
   # adr
   sim.msg('Blipping RA0 clk...')
   io_ra0_clk.value = 1
   await Timer(readHalfPer, units='ns')
   io_ra0_clk.value = 0
   await Timer(readHalfPer, units='ns')
   if noisy:
      sim.msg(f' ScanReg={dut.site.ctl.scan_reg_q.value}')
   # dat
   sim.msg('Blipping RA0 clk...')
   io_ra0_r0_enb.value = 0
   io_ra0_r1_enb.value = 0
   io_ra0_clk.value = 1
   await Timer(readHalfPer, units='ns')
   io_ra0_clk.value = 0
   await Timer(readHalfPer, units='ns')
   # capture in scan_reg
   sim.msg('Blipping RA0 clk...')
   io_ra0_clk.value = 1
   await Timer(readHalfPer, units='ns')
   io_ra0_clk.value = 0
   await Timer(readHalfPer, units='ns')

   sim.msg(f'Scanning out...')
   scan_di.value = 0
   scanOut = f'{scan_do.value}'
   for i in range(1, scanLen):
      scan_clk.value = 0
      await Timer(scanHalfPer, units='ns')
      scan_clk.value = 1
      await Timer(scanHalfPer, units='ns')
      scanOut += f'{scan_do.value}'

   sim.msg(f'ScanData={int(scanOut,2):032X}')
   sim.msg(f' r0 adr:{int(scanOut[0:5], 2):02X}')
   sim.msg(f' r0 dat:{int(scanOut[5:37], 2):08X}')
   assert int(scanOut[5:37], 2) == ra.read(15), f'R0 Miscompare! exp={ra.read(15):08X} dat={int(scanOut[5:37], 2):08X}'
   sim.msg(f' r1 adr:{int(scanOut[37:42], 2):02X}')
   sim.msg(f' r1 dat:{int(scanOut[42:74], 2):08X}')
   assert int(scanOut[42:74], 2) == ra.read(16), f'R0 Miscompare! exp={ra.read(16):08X} dat={int(scanOut[42:74], 2):08X}'
   sim.msg(f' w0 adr:{int(scanOut[74:79], 2 ):02X}')
   sim.msg(f' w0 dat:{int(scanOut[79:111], 2):08X}')
   sim.msg(f'    cfg:{int(scanOut[111:], 2):05X}')

   test_enable.value = 0
   sim.randomIO = True

   await RisingEdge(sim.clk1x)
   sim.msg(f'Starting regular sequences.')

   # write and read ra0 w0/r0
   for i in range(32):

      adr = ra0Base + i
      dat = int(hexrandom(8), 16)
      sim.msg(f'Writing Port 0 @{adr:08X} {i:02X}={dat:08X}')
      dut.wbs_cyc_i.value = 1
      dut.wbs_stb_i.value = 1
      dut.wbs_we_i.value = 1
      dut.wbs_adr_i.value = adr
      dut.wbs_sel_i.value = 0xF
      dut.wbs_dat_i.value = dat
      ra.write(i, dat)

      await RisingEdge(sim.clk1x)
      dut.wbs_cyc_i.value = 0
      dut.wbs_stb_i.value = 0

      while (dut.wbs_ack_o.value == 0):
         await RisingEdge(sim.clk1x)

      sim.msg(f'Reading Port 0 @{adr:08X} {i:02X}')
      dut.wbs_cyc_i.value = 1
      dut.wbs_stb_i.value = 1
      dut.wbs_we_i.value = 0
      dut.wbs_adr_i.value = adr

      await RisingEdge(sim.clk1x)
      dut.wbs_cyc_i.value = 0
      dut.wbs_stb_i.value = 0

      while (dut.wbs_ack_o.value == 0):
         await RisingEdge(sim.clk1x)

      sim.msg(f'Read Data: {dut.wbs_dat_o.value.integer:08X}')
      assert dut.wbs_dat_o.value == dat, f'Read data miscompare! exp={dat:08X} act={dut.wbs_dat_o.value.integer:08X}'

   sim.msg('Done')

   for i in range(10):
      await RisingEdge(sim.clk1x)

