# OPCODES
# instruction: 00000---
# addressing mode: -----000
from collections import namedtuple
Adm = namedtuple('Adm', ['IMP', 'IMM', 'ABS', 'REL', 'IDX'])
Opc = namedtuple('Opc', [
'LDA', 'STA', 'PHA', 'PLA', 'ASL', 'ASR', 'TXA', 'TAX',
'INX', 'DEX', 'ADD', 'SUB', 'AND', 'OR', 'XOR', 'CMP',
'RTS', 'JNZ', 'JZ', 'JSR', 'JMP'
])
opc = Opc(*range(21))
adm = Adm(*range(5))
import pandas as pd
import numpy as np
df = pd.DataFrame(columns=[*opc._asdict()])
for n,i in adm._asdict().items():
df.loc[n] = (np.left_shift(pd.Series(opc._asdict()),3) + i).apply(hex)
allowed = [0x1, 0x2, 0x4, 0xa, 0xc, 0x10, 0x18, 0x21, 0x29, 0x30, 0x38, 0x40, 0x48, 0x51, 0x59, 0x61, 0x69, 0x71, 0x79, 0x80, 0x8b, 0x93, 0x9a, 0xa2]
df.style.applymap(lambda v: 'background-color: #f55' if not int(v, 16) in allowed else 'background-color: #5f5')
LDA | STA | PHA | PLA | ASL | ASR | TXA | TAX | INX | DEX | ADD | SUB | AND | OR | XOR | CMP | RTS | JNZ | JZ | JSR | JMP | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
IMP | 0x0 | 0x8 | 0x10 | 0x18 | 0x20 | 0x28 | 0x30 | 0x38 | 0x40 | 0x48 | 0x50 | 0x58 | 0x60 | 0x68 | 0x70 | 0x78 | 0x80 | 0x88 | 0x90 | 0x98 | 0xa0 |
IMM | 0x1 | 0x9 | 0x11 | 0x19 | 0x21 | 0x29 | 0x31 | 0x39 | 0x41 | 0x49 | 0x51 | 0x59 | 0x61 | 0x69 | 0x71 | 0x79 | 0x81 | 0x89 | 0x91 | 0x99 | 0xa1 |
ABS | 0x2 | 0xa | 0x12 | 0x1a | 0x22 | 0x2a | 0x32 | 0x3a | 0x42 | 0x4a | 0x52 | 0x5a | 0x62 | 0x6a | 0x72 | 0x7a | 0x82 | 0x8a | 0x92 | 0x9a | 0xa2 |
REL | 0x3 | 0xb | 0x13 | 0x1b | 0x23 | 0x2b | 0x33 | 0x3b | 0x43 | 0x4b | 0x53 | 0x5b | 0x63 | 0x6b | 0x73 | 0x7b | 0x83 | 0x8b | 0x93 | 0x9b | 0xa3 |
IDX | 0x4 | 0xc | 0x14 | 0x1c | 0x24 | 0x2c | 0x34 | 0x3c | 0x44 | 0x4c | 0x54 | 0x5c | 0x64 | 0x6c | 0x74 | 0x7c | 0x84 | 0x8c | 0x94 | 0x9c | 0xa4 |
# ROM source
src = """
; testing some basic instructions
lda #$0
tax
_1:
sta range,x
inx
txa
cmp #$10
jnz _1
at $100
range db $0
"""
# Assembler
from pyparsing import *
class Parser():
pos = 0
labels = {}
binary = []
def setOrig(self, tokens):
self.pos = int(tokens.adr, 16)
def setLabel(self, tokens):
self.labels[tokens.lbl[0]] = self.pos-1
def setRef(self, tokens):
self.labels[tokens.lbl] = self.pos
if tokens.size == 'db':
self.binary.append(int(tokens.val, 16))
self.pos += 1
else:
self.binary.append(int(tokens.val, 16) & 0xff)
self.binary.append(int(tokens.val, 16) >> 8)
self.pos += 2
def setOp(self, tokens):
c = getattr(opc, tokens.op) << 3
if tokens.op == 'LDA':
if tokens.am == '#$':
c = c | adm.IMM
self.pos += 2
self.binary.append(c)
self.binary.append(int(tokens.val, 16))
elif tokens.am == '$':
c |= adm.IDX if tokens.idx else adm.ABS
self.pos += 3
self.binary.append(c)
l = int(tokens.val, 16) & 0xff
h = int(tokens.val, 16) >> 8
self.binary.append(h)
self.binary.append(l)
elif tokens.lbl:
c |= adm.IDX if tokens.idx else adm.ABS
self.pos += 3
self.binary.append(c)
self.binary.append(tokens.lbl)
elif tokens.op == 'STA':
c |= adm.IDX if tokens.idx else adm.ABS
if tokens.am == '$':
self.pos += 3
self.binary.append(c)
l = int(tokens.val, 16) & 0xff
h = int(tokens.val, 16) >> 8
self.binary.append(h)
self.binary.append(l)
elif tokens.lbl:
self.pos += 3
self.binary.append(c)
self.binary.append(tokens.lbl[0])
elif tokens.op in ['TAX', 'TXA', 'PHA', 'PLA', 'RTS', 'ASL', 'ASR', 'INX', 'DEX']:
c = c | adm.IMP
self.pos += 1
self.binary.append(c)
elif tokens.op in ['ADD', 'SUB', 'AND', 'OR', 'XOR', 'CMP']:
c = c | adm.IMM
self.pos += 2
self.binary.append(c)
self.binary.append(int(tokens.val, 16))
elif tokens.op in ['JNZ', 'JZ']:
c = c | adm.REL
self.pos += 2
self.binary.append(c)
self.binary.append(tokens.lbl[0])
elif tokens.op in ['JSR', 'JMP']:
c = c | adm.ABS
self.pos += 3
self.binary.append(c)
self.binary.append(tokens.lbl[0])
def parse(self, src):
op = oneOf(' '.join(opc._asdict()), caseless=True)
org = (Word('atAT') + '$' + Word(hexnums)('adr')).setParseAction(self.setOrig)
val = Word('#$')('am') + Word(hexnums)('val')
lbl = (~op + Word(alphanums + '_'))('lbl')
prm = (val|lbl) + ~Literal(':') + Optional(Word(', x'))('idx')
label = (lbl + ':').setParseAction(self.setLabel)
comment = ';' + restOfLine
instruction = (op('op') + Optional(prm('prm'))).setParseAction(self.setOp)
ref = (Word(alphanums + '_')('lbl') + oneOf('db dw')('size') + '$' + Word(hexnums)('val')).setParseAction(self.setRef)
asm = OneOrMore(org | label | instruction | comment | ref)
asm.parseString(src, parseAll=True)
for i,b in enumerate(self.binary):
if not isinstance(b, int):
if b in self.labels.keys():
if self.binary[i-1] in [0x8b, 0x93]: #todo extract addressing mode
self.binary[i] = self.labels[b] - i + 1
else:
self.binary[i] = self.labels[b] & 0xff
self.binary.insert(i+1, self.labels[b] >> 8)
else:
print('unresolved', b)
return tuple(self.binary)
rom = Parser().parse(src)
print('Compiled ROM: ', end='')
for b in rom:
if b < 0: b = 0x100 + b
print(f'{b:02x}', end=' ')
Compiled ROM: 01 00 38 0c 00 01 40 30 79 10 8b f8 00
# RAM & CPU
from myhdl import *
# 2k RAM + ROM
@block
def mem(clk, adr, we, di, do):
ram = [Signal(intbv(0)[8:]) for i in range(0x2000)] # 8k
@always(clk.posedge)
def logic():
if we:
ram[adr.val].next = di
else:
if adr < len(rom):
do.next = rom[adr.val]
else:
do.next = ram[adr.val]
return logic
@block
def processor(clk, rst, di, do, adr, we):
"""
IR = instruction register
IM = immediate value
RX = X register
RW = W register used for status flags
SR = status register
AM = addressing mode
SP = stack pointer
"""
(F1, F2, D, E, M1, M2) = range(0,6)
#s = enum('F1', 'F2', 'D', 'E', 'M1', 'M2')
pc = Signal(modbv(0)[11:])
cyc = Signal(modbv(0)[3:])
ir, im, ra, rx, rw, sr, am = (Signal(modbv(0)[8:]) for i in range(7))
sp = Signal(modbv(0xff)[8:])
@always(clk.posedge)
def logic():
if rst:
# rst.next = 0
pc.next = 0
adr.next = 0
elif cyc == F1:
adr.next = pc + 1
pc.next = pc + 1
cyc.next = F2
elif cyc == F2:
adr.next = pc + 1
ir.next = do
cyc.next = D
elif cyc == D:
im.next = do
am.next = ir & 7
ir.next = (ir >> 3) & 0x1f
if (ir >> 3) == opc.RTS: # rts
adr.next = sp + 1
sp.next = sp + 1
cyc.next = E
elif cyc == E:
if ir == opc.LDA: # lda
if am == adm.IMM:
ra.next = im
pc.next = pc + 1
elif am == adm.ABS:
adr.next = (do << 8) | im
pc.next = pc + 2
elif am == adm.IDX:
adr.next = (do << 8) | im + rx
pc.next = pc + 2
elif ir == opc.STA: # sta
if am == adm.ABS:
adr.next = (do << 8) | im
we.next = 1
di.next = ra
pc.next = pc + 2
elif am == adm.IDX:
adr.next = (do << 8) | im + rx
we.next = 1
di.next = ra
pc.next = pc + 2
elif ir == opc.TAX: # tax
rx.next = ra
rw.next = 1
elif ir == opc.TXA: # txa
ra.next = rx
elif ir == opc.ADD: # add im
ra.next = ra + im
pc.next = pc + 1
elif ir == opc.SUB: # sub im
ra.next = ra - im
pc.next = pc + 1
elif ir == opc.AND: # and im
ra.next = ra & im
pc.next = pc + 1
elif ir == opc.OR: # or im
ra.next = ra | im
pc.next = pc + 1
elif ir == opc.XOR: # xor im
ra.next = ra ^ im
pc.next = pc + 1
elif ir == opc.ASL: # asl im
ra.next = ra << im
elif ir == opc.ASR: # asr im
ra.next = ra >> im
elif ir == opc.JNZ: # jnz rel
if sr[6] == 0:
pc.next = pc + im.signed()
else:
pc.next = pc + 1
elif ir == opc.JZ: # jz rel
if sr[6] != 0:
pc.next = pc + im.signed()
else:
pc.next = pc + 1
elif ir == opc.INX: # inx
rx.next = rx + 1
rw.next = 1
elif ir == opc.DEX: # dex
rx.next = rx - 1
rw.next = 1
elif ir == opc.PHA: # pha
adr.next = sp
sp.next = sp - 1
di.next = ra
we.next = 1
elif ir == opc.PLA: # pla
sp.next = sp + 1
adr.next = sp + 1
elif ir == opc.CMP: # cmp im
rw.next = 2
sr.next = concat((ra-im)>=0x80, (ra-im)==0, sr[6:0])
pc.next = pc + 1
elif ir == opc.JSR: # jsr abs
adr.next = sp
sp.next = sp - 1
di.next = (pc + 2) >> 8
we.next = 1
elif ir == opc.RTS: # rts
adr.next = sp + 1
sp.next = sp + 1
elif ir == opc.JMP: # jmp abs
pc.next = (do << 8) | im
cyc.next = M1
elif cyc == M1:
if (ir == opc.PLA) or (ir == opc.LDA and am == adm.ABS or am == adm.IDX):
ra.next = do
elif ir == opc.JSR:
adr.next = sp
sp.next = sp - 1
di.next = (pc + 2) & 0xff
we.next = 1
pc.next = (do << 8) | im
elif ir == opc.RTS:
pc.next = do
else:
we.next = 0
adr.next = pc
cyc.next = M2
elif cyc == M2:
if ir == 0x11:
ra.next = do
sr.next = concat(do>=0x80, do==0, sr[6:0])
elif rw == 0:
sr.next = concat(ra>=0x80, ra==0, sr[6:0])
elif rw == 1:
sr.next = concat(rx>=0x80, rx==0, sr[6:0])
if ir == 0x17:
pc.next = (do << 8) | (pc & 0xff)
adr.next = (do << 8) | (pc & 0xff)
else:
adr.next = pc
we.next = 0
rw.next = 0
cyc.next = F1
return logic
@block
def sim():
clk = Signal(bool(0))
rst = Signal(bool(1))
we = Signal(bool(0))
adr = Signal(modbv(0)[16:])
di = Signal(modbv(0)[8:])
do = Signal(modbv(0)[8:])
mi = mem(clk, adr, we, di, do)
cpu = processor(clk, rst, di, do, adr, we)
mi.convert(hdl='Verilog')
cpu.convert(hdl='Verilog')
@always(delay(2))
def stimulus():
clk.next = not clk
if rst: rst.next = 0
return stimulus, mi, cpu
tb = sim()
tb.config_sim(
trace=True,
tracebackup=False,
filename='dump'
)
tb.run_sim(1800)
<class 'myhdl._SuspendSimulation'>: Simulated 1800 timesteps
tb.quit_sim()
from IPython.display import HTML
from Verilog_VCD import Verilog_VCD as vcd
dump = vcd.parse_vcd('./dump.vcd')
# make some signals more readable
resolvers = {
'ir': lambda v: opc._fields[int(v,2)] if len(opc)>=int(v,2) else str(hex(int(v,2))),
'cyc': lambda v: (['F1','F2','D','E','M1','M2'][int(v,2)],['#faa','#faa','#aaf','#faf','#0f0','#0f0'][int(v,2)])
}
selection = ['ra', 'rx', 'pc', 'ir', 'di', 'adr', 'clk', 'cyc', 'rst']
waves = ''
names = ''
ticks = ''
start = min([d[1]['tv'][-1][0] for d in dump.items()])
stop = max([d[1]['tv'][-1][0] for d in dump.items()])
for i in range(start,stop): ticks += f'<span class="tick">{i}</span>'
for n,d in dump.items():
name = d['nets'][0]['name']
size = d['nets'][0]['size']
if name in selection:
wave = ''
pv = d['tv'][0][1]
pc = 'white'
for i in range(start, stop):
if len(d['tv']):
t,v = d['tv'][0]
else: t = -1
if int(size) == 1:
cls = ''
if i == t:
del d['tv'][0]
cls = 'bc' if pv != v else ''
pv = v
else:
v = pv
if int(v) == 1:
wave += f'<span class="b1 {cls}"></span>'
else:
wave += f'<span class="b0 {cls}"></span>'
else:
c = pc
if i == t:
del d['tv'][0]
if name in resolvers:
v = resolvers[name](v)
if isinstance(v, tuple): v,c = v
pc = c
else:
v = str(hex(int(v, 2)))
wave += f'<span class="value" style="background-color:{c}">{v}</span>'
else:
wave += f'<span class="fill" style="background-color:{c}"></span>'
names += f'<span class="name">{name}</span>'
waves += wave + '<br>'
css = """<style>
.waves{width:max-content;width:-moz-max-content;margin-left:40px;overflow:hidden;background-image:
url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAUCAYAAAD/Rn+7AAAAQUlEQVRIS+3SwQkAMAzDQGWm7j9Cd+oQ+pSg/A3h7AEOcPn0pgdlMwlKQBJM0ArYfBtM0ArYfBtM0ArYfBtcL/gAGlIUFdA8RdcAAAAASUVORK5CYII=')}
.ticks{width:max-content;width:-moz-max-content;margin-left:40px;font-size:10px}
.tick{display:inline-block;width:40px;overflow:hidden}
.names{width:40px;position:absolute;z-index:10;margin-top:2px;margin-left:-6px;text-align:right}
span.name{display:block;background-color:white;font-size:10px;height:23px}
span.fill,span.value{display:inline-block;width:40px;height:20px;margin-top:2px;font-size:10px;
border-top:1px solid #000;border-bottom:1px solid #000;position:relative;box-sizing:border-box;padding-left:5px;
line-height:20px;vertical-align:middle}
span.value:before{content:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAUCAYAAAC07qxWAAAAXElEQVQ4T62TUQoAIAhD9f6HNhYpZWEK+utjm5lMRCIiFBWjFhDCwMoghJ+qy9SsS+AFqxoamlGHPuwj0FR36KVYAifsY/mMljUDzoHaMqafJwX27rr3P35PAVsYVTpKDYc2sIoAAAAASUVORK5CYII=');position:absolute;
font-size:16px;left:-5px;line-height:15px;top:-1px;}
.b0{border-bottom:1px solid #000;width:40px;height:16px;display:inline-block;margin-top:4px;box-sizing:border-box}
.b1{border-top:1px solid #000;width:40px;display:inline-block;height:16px;margin-top:4px;box-sizing:border-box}
.bc{border-left:1px solid #000}
</style>
"""
HTML(
css
+ '<div class="ticks">' + ticks + '</div>'
+ '<div class="names">' + names + '</div>'
+ '<div class="waves">' + waves + '</div>'
)