241 lines
7.2 KiB
Python
241 lines
7.2 KiB
Python
#!/usr/bin/python3
|
|
# Copyright 2023 (C) Peter McGoron
|
|
#
|
|
# This file is a part of Upsilon, a free and open source software project.
|
|
# For license terms, refer to the files in `doc/copying` in the Upsilon
|
|
# source distribution.
|
|
#######################################################################
|
|
#
|
|
# This file generates a Micropython module "mmio" with functions that
|
|
# do raw reads and writes to MMIO registers.
|
|
#
|
|
# TODO: Devicetree?
|
|
|
|
import collections
|
|
import argparse
|
|
import json
|
|
import sys
|
|
|
|
class MMIORegister:
|
|
def __init__(self, name, read_only=False, number=1, exntype=None):
|
|
"""
|
|
Describes a MMIO register.
|
|
:param name: The name of the MMIO register, excluding the prefix
|
|
defining its module (i.e. ``base_``) and excluding its
|
|
numerical suffix.
|
|
:param read_only: True if the register is read only. Defaults to
|
|
``False``.
|
|
:param number: The number of MMIO registers with the same name
|
|
and number suffixes. The number suffixes must go from 0
|
|
to ``number - 1`` with no gaps.
|
|
"""
|
|
self.name = name
|
|
self.read_only = read_only
|
|
self.number = number
|
|
self.exntype = exntype
|
|
|
|
# These are filled in by the CSR file.
|
|
self.size = None
|
|
|
|
def mmio_factory(num, exntype):
|
|
"""
|
|
Return a function that simplifies the creation of instances of
|
|
:py:class:`MMIORegister` with the same number and exception type.
|
|
|
|
:param num: Number of registers with the same name (minus suffix).
|
|
:param exntype: MicroPython exception type.
|
|
:return: A function ``f(name, read_only=False)``. Each argument is
|
|
the same as the one in the initializer of py:class:`MMIORegister`.
|
|
"""
|
|
def f(name, read_only=False):
|
|
return MMIORegister(name, read_only, number=num, exntype=exntype)
|
|
return f
|
|
|
|
class CSRHandler:
|
|
"""
|
|
Class that wraps the CSR file and fills in registers with information
|
|
from those files.
|
|
"""
|
|
def __init__(self, csrjson, bitwidthjson, registers):
|
|
"""
|
|
Reads in the CSR files.
|
|
|
|
:param csrjson: Filename of a LiteX "csr.json" file.
|
|
:param bitwidthjson: Filename of an Upsilon "bitwidthjson" file.
|
|
:param registers: A list of :py:class:`MMIORegister`s.
|
|
:param outf: Output file.
|
|
"""
|
|
self.registers = registers
|
|
self.csrs = json.load(open(csrjson))
|
|
self.bws = json.load(open(bitwidthjson))
|
|
|
|
def update_reg(self, reg):
|
|
"""
|
|
Fill in size information from bitwidth json file.
|
|
|
|
:param reg: The register.
|
|
:raises Exception: When the bit width exceeds 64.
|
|
"""
|
|
b = self.bws[reg.name]
|
|
if b <= 8:
|
|
reg.size = 8
|
|
elif b <= 16:
|
|
reg.size = 16
|
|
elif b <= 32:
|
|
reg.size = 32
|
|
elif b <= 64:
|
|
reg.size = 64
|
|
else:
|
|
raise Exception(f"unsupported width {b} in {reg.name}")
|
|
|
|
def get_reg_addr(self, reg, num=None):
|
|
"""
|
|
Get address of register.
|
|
|
|
:param reg: The register.
|
|
:param num: Select which register number. Registers without
|
|
numerical suffixes require ``None``.
|
|
:return: The address.
|
|
"""
|
|
if num is None:
|
|
regname = f"base_{reg.name}"
|
|
else:
|
|
regname = f"base_{reg.name}_{num}"
|
|
return self.csrs["csr_registers"][regname]["addr"]
|
|
|
|
class InterfaceGenerator:
|
|
"""
|
|
Interface for file generation. Implement the unimplemented functions
|
|
to generate a CSR interface for another language.
|
|
"""
|
|
|
|
def __init__(self, csr, outf):
|
|
"""
|
|
:param CSRHandler csr:
|
|
:param FileIO outf:
|
|
"""
|
|
self.outf = outf
|
|
self.csr = csr
|
|
|
|
def print(self, *args):
|
|
"""
|
|
Print to the file specified in the initializer and without
|
|
newlines.
|
|
"""
|
|
print(*args, end='', file=self.outf)
|
|
|
|
def fun(self, reg, optype):
|
|
""" Print function for reads/writes to register. """
|
|
pass
|
|
def header(self):
|
|
""" Print header of file. """
|
|
pass
|
|
|
|
def print_file(self):
|
|
self.print(self.header())
|
|
for r in self.csr.registers:
|
|
self.print(self.fun(r, "read"))
|
|
if not r.read_only:
|
|
self.print(self.fun(r, "write"))
|
|
|
|
class MicropythonGenerator(InterfaceGenerator):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def get_accessor(self, reg, num):
|
|
addr = self.csr.get_reg_addr(reg, num)
|
|
if reg.size in [8, 16, 32]:
|
|
return [f"machine.mem{reg.size}[{addr}]"]
|
|
return [f"machine.mem32[{addr}]", f"machine.mem32[{addr + 4}]"]
|
|
|
|
def print_write_register(self, indent, varname, reg, num):
|
|
acc = self.get_accessor(reg, num)
|
|
if len(acc) == 1:
|
|
return f'{indent}{acc[0]} = {varname}\n'
|
|
else:
|
|
assert len(acc) == 2
|
|
return f'{indent}{acc[0]} = {varname} & 0xFFFFFFFF\n' + \
|
|
f'{indent}{acc[1]} = {varname} >> 32\n'
|
|
|
|
def print_read_register(self, indent, varname, reg, num):
|
|
acc = self.get_accessor(reg, num)
|
|
if len(acc) == 1:
|
|
return f'{indent}return {acc[0]}\n'
|
|
else:
|
|
assert len(acc) == 2
|
|
return f'{indent}return {acc[0]} | ({acc[1]} << 32)\n'
|
|
|
|
def fun(self, reg, optype):
|
|
rs = ""
|
|
def a(s):
|
|
nonlocal rs
|
|
rs = rs + s
|
|
a(f'def {optype}_{reg.name}(')
|
|
|
|
printed_argument = False
|
|
if optype == 'write':
|
|
a('val')
|
|
printed_argument = True
|
|
pfun = self.print_write_register
|
|
else:
|
|
pfun = self.print_read_register
|
|
|
|
if reg.number != 1:
|
|
if printed_argument:
|
|
a(', ')
|
|
a('num')
|
|
a('):\n')
|
|
|
|
if reg.number == 1:
|
|
a(pfun('\t', 'val', reg, None))
|
|
else:
|
|
for i in range(0,reg.number):
|
|
if i == 0:
|
|
a(f'\tif ')
|
|
else:
|
|
a(f'\telif ')
|
|
a(f'num == {i}:\n')
|
|
a(pfun('\t\t', 'val', reg, i))
|
|
a(f'\telse:\n')
|
|
a(f'\t\traise {r.exntype}(regnum)\n')
|
|
a('\n')
|
|
|
|
return rs
|
|
|
|
def header(self):
|
|
return """import machine
|
|
class InvalidDACException(Exception):
|
|
pass
|
|
class InvalidADCException(Exception):
|
|
pass
|
|
"""
|
|
|
|
if __name__ == "__main__":
|
|
dac_num = 8
|
|
adc_num = 8
|
|
dac_reg = mmio_factory(dac_num, "InvalidDACException")
|
|
adc_reg = mmio_factory(adc_num, "InvalidADCException")
|
|
|
|
registers = [
|
|
dac_reg("dac_sel"),
|
|
dac_reg("dac_finished", read_only=True),
|
|
dac_reg("dac_arm"),
|
|
dac_reg("dac_recv_buf", read_only=True),
|
|
dac_reg("dac_send_buf"),
|
|
|
|
adc_reg("adc_finished", read_only=True),
|
|
adc_reg("adc_arm"),
|
|
adc_reg("adc_recv_buf", read_only=True),
|
|
adc_reg("adc_sel"),
|
|
|
|
MMIORegister("cl_in_loop", read_only=True),
|
|
MMIORegister("cl_cmd"),
|
|
MMIORegister("cl_word_in"),
|
|
MMIORegister("cl_start_cmd"),
|
|
MMIORegister("cl_finish_cmd", read_only=True),
|
|
]
|
|
csrh = CSRHandler(sys.argv[1], sys.argv[2], registers)
|
|
for r in registers:
|
|
csrh.update_reg(r)
|
|
MicropythonGenerator(csrh, sys.stdout).print_file()
|