#!/usr/bin/env python

import os
import sys

HERE = os.path.dirname(__file__)
sys.path.insert(0, os.path.join(HERE, '..', 'instructions'))

import instructions

"""
Given the instruction class, opcode, and format select bit, output the correct
decode bits in a format suitable for insertion as a ROM.

ROM outputs:
  [29]    spsr  Set PSR.
  [28]    priv  Privileged instruction.
  [27]    cache Cache operation.
  [26]    upc   Update carry flag.
  [25]    rfe   Is return-from-exception.
  [24]    swi   Is software interrupt.
  [23]    wcr   Write control register.
  [22]    valid Valid instruction
  [21:20] imsel Immediate select:
                2'b00: imm13
                2'b01: imm24
                2'b10: hi16
                2'b11: lo16
  [19:16] bcc   ALU branch condition code.
  [15:14] maw   Memory access width.
  [13:13] icall Is call instruction.
  [12:12] rdlr  RD is link register.
  [11:11] mstr  Memory store.
  [10:10] mldr  Memory load.
  [9:9]   op2rb ALU operand 2 is RB.
  [8:8]   op1rb ALU operand 1 is RB.
  [7:7]   op1ra ALU operand 1 is RA.
  [6:6]   upcc  Update ALU condition codes.
  [5:5]   wrrd  Write to destination register (RD).
  [4:0]   aluop ALU opcode.
"""

IMM13 = 0
IMM24 = 1
HI16  = 2
LO16  = 3

itab = instructions.instructions

rom_entries = {}

class RomEntry(object):
    def __init__(self, idef, bits):
        self._idef = idef
        self._bits = bits

    @property
    def idef(self):
        return self._idef

    @property
    def bits(self):
        return self._bits

field_shifts = {
    'spsr':  29,
    'priv':  28,
    'cache': 27,
    'upc':   26,
    'rfe':   25,
    'swi':   24,
    'wcr':   23,
    'valid': 22,
    'imsel': 20,
    'bcc':   16,
    'maw':   14,
    'icall': 13,
    'rdlr':  12,
    'mstr':  11,
    'mldr':  10,
    'op2rb': 9,
    'op1rb': 8,
    'op1ra': 7,
    'upcc':  6,
    'wrrd':  5,
    'aluop': 0
}

def is_arith_instruction(instr):
    return instr['class'] == 0

def is_branch_instruction(instr):
    return instr['class'] == 1

def is_mem_instruction(instr):
    return instr['class'] == 2

def is_misc_instruction(instr):
    return instr['class'] == 3

def add_arith_instruction(opcode, idef):
    flags_updated = idef.get('flags_updated', '')
    alu_opcode = idef['opcode']
    if 'alu_opcode' in idef:
        alu_opcode = instructions.alu_opcodes.get(idef['alu_opcode'],
                                                  alu_opcode)
    rom_entries[(opcode, 'register')] = RomEntry(idef, {
        'valid': 1,
        'op2rb': 1,
        'op1ra': 1,
        'wrrd':  opcode != 'cmp',
        'aluop': alu_opcode,
        'upcc':  'Z' in flags_updated or 'O' in flags_updated,
        'upc':   'C' in flags_updated
    })
    rom_entries[(opcode, 'immediate')] = RomEntry(idef, {
        'valid': 1,
        'imsel': IMM13,
        'op1ra': 1,
        'wrrd':  opcode != 'cmp',
        'aluop': alu_opcode,
        'upcc':  'Z' in flags_updated or 'O' in flags_updated,
        'upc':   'C' in flags_updated
    })

def alu_opcode_val(name):
    return instructions.alu_opcodes[name]

def add_branch_instruction(opcode, idef):
    cc = idef.get('branch_cc', '7')
    if opcode not in ['swi', 'rfe']:
        rom_entries[(opcode, 'register')] = RomEntry(idef, {
            'valid': 1,
            'bcc':   int(cc),
            'icall': opcode == 'call',
            'rdlr':  opcode == 'call',
            'op1rb': 1,
            'wrrd':  opcode == 'call',
            'aluop': alu_opcode_val('copya')
        })
    # Return to immediate isn't a real instruction, trap it.
    if opcode != 'ret':
        alu_opcodes = {
            'swi': 0x11,
            'rfe': 0x12,
        }
        rom_entries[(opcode, 'immediate')] = RomEntry(idef, {
            'valid': 1,
            'imsel': IMM24,
            'bcc':   int(cc),
            'icall': opcode == 'call',
            'rdlr':  opcode == 'call',
            'wrrd':  opcode == 'call',
            'aluop': alu_opcodes.get(opcode, alu_opcode_val('add')),
            'swi':   opcode == 'swi',
            'rfe':   opcode == 'rfe'
        })

def add_mem_instruction(opcode, idef):
    def access_width(opcode):
        return int({
            'ldr32': 2,
            'str32': 2,
            'ldr16': 1,
            'str16': 1,
            'ldr8':  0,
            'str8':  0.
        }[opcode])

    if opcode in ['gcr', 'scr']:
        rom_entries[(opcode, 'immediate')] = RomEntry(idef, {
            'valid': 1,
            'wcr':   opcode == 'scr',
            'op1ra': opcode == 'scr', # cr[N] := ra
            'wrrd':  opcode == 'gcr', # rd := cr[N]
            'aluop': alu_opcode_val('copya') if opcode == 'scr' else alu_opcode_val('gcr'),
        })
    elif not 'cache' in opcode:
        rom_entries[(opcode, 'register')] = RomEntry(idef, {
            'valid': 1,
            'maw':   access_width(opcode),
            'mstr':  'str' in opcode,
            'mldr':  'ldr' in opcode,
            'op1ra': 1,
            'aluop': alu_opcode_val('add'),
        })
        # PC-relative load/store.
        rom_entries[(opcode, 'immediate')] = RomEntry(idef, {
            'valid': 1,
            'maw':   access_width(opcode),
            'mstr':  'str' in opcode,
            'mldr':  'ldr' in opcode,
            'aluop': alu_opcode_val('add'),
        })
    else:
        alu_opcode = idef['opcode']
        if 'alu_opcode' in idef:
            alu_opcode = instructions.alu_opcodes.get(idef['alu_opcode'],
                                                    alu_opcode)
        rom_entries[(opcode, 'immediate')] = RomEntry(idef, {
            'valid': 1,
            'aluop': alu_opcode,
            'imsel': 0,
            'op1ra': True,
            'cache': 1,
        })

def add_misc_instruction(opcode, idef):
    if opcode in ['movhi', 'orlo']:
        """
        movhi and orlo are special in that they have 16-bit immediates that
        also cover bit 25 so we need to insert this one twice in case the high
        bit of the immediate is set.
        """
        rom_entries[(opcode, 'immediate')] = RomEntry(idef, {
            'valid': 1,
            'imsel': HI16 if opcode == 'movhi' else LO16,
            'op1rb': opcode == 'orlo',
            'wrrd':  1,
            'aluop': idef['opcode'],
        })
        rom_entries[(opcode, 'register')] = RomEntry(idef, {
            'valid': 1,
            'imsel': HI16 if opcode == 'movhi' else LO16,
            'op1rb': opcode == 'orlo',
            'wrrd':  1,
            'aluop': idef['opcode'],
        })
    elif opcode in ['spsr', 'gpsr']:
        rom_entries[(opcode, 'immediate')] = RomEntry(idef, {
            'valid': 1,
            'spsr':  1 if opcode == 'spsr' else 0,
            'op1ra': opcode == 'spsr', # psr := ra
            'wrrd':  opcode == 'gpsr',  # rd := psr
            'aluop': alu_opcode_val(idef['alu_opcode'])
        })
    elif opcode in ['bkp', 'nop']:
        rom_entries[(opcode, 'immediate')] = RomEntry(idef, {
            'valid': 1,
        })
    elif opcode == 'cpuid':
        rom_entries[(opcode, 'immediate')] = RomEntry(idef, {
            'valid': 1,
            'wrrd':  1,
            'aluop': alu_opcode_val('cpuid'),
        })

for opcode, idef in itab.items():
    if is_arith_instruction(idef):
        add_arith_instruction(opcode, idef)
    if is_branch_instruction(idef):
        add_branch_instruction(opcode, idef)
    if is_mem_instruction(idef):
        add_mem_instruction(opcode, idef)
    if is_misc_instruction(idef):
        add_misc_instruction(opcode, idef)

def set_privileged():
    for entry, romentry in rom_entries.items():
        idef = romentry.idef
        romentry.bits['priv'] = idef.get('privileged', 0)

set_privileged()

def dump_rom(rom):
    print('@00000000')
    for v in rom:
        print('{0:08x}'.format(v))

def build_rom():
    # All instructions are initially invalid
    rom = [0 for i in range(1 << 7)]

    for entry, romentry in rom_entries.items():
        cls = itab[entry[0]]['class']
        opc = itab[entry[0]]['opcode']
        address = (cls << 5) | (opc << 1) | (entry[1] == 'register')

        data = 0
        for k, v in romentry.bits.items():
            data |= v << field_shifts[k]
        rom[address] = data

    return rom

dump_rom(build_rom())
