diff options
Diffstat (limited to 'pyscv')
-rw-r--r-- | pyscv/Frontend/__init__.py | 0 | ||||
-rw-r--r-- | pyscv/Frontend/lexer.py | 292 | ||||
-rw-r--r-- | pyscv/Frontend/logger.py | 10 | ||||
-rw-r--r-- | pyscv/Frontend/parser.py | 1 | ||||
-rw-r--r-- | pyscv/Frontend/reader.py | 51 | ||||
-rw-r--r-- | pyscv/InstructionSets/RV32C.py | 29 | ||||
-rw-r--r-- | pyscv/InstructionSets/RV32D.py | 0 | ||||
-rw-r--r-- | pyscv/InstructionSets/RV32F.py | 0 | ||||
-rw-r--r-- | pyscv/InstructionSets/RV32I.py | 466 | ||||
-rw-r--r-- | pyscv/InstructionSets/RV64I.py | 63 | ||||
-rw-r--r-- | pyscv/InstructionSets/__init__.py | 0 | ||||
-rw-r--r-- | pyscv/InstructionSets/instructions.py | 36 | ||||
-rw-r--r-- | pyscv/main.py | 8 | ||||
-rw-r--r-- | pyscv/memory.py | 112 | ||||
-rw-r--r-- | pyscv/registers/RV32F.py | 45 | ||||
-rw-r--r-- | pyscv/registers/RV32I.py | 117 | ||||
-rw-r--r-- | pyscv/registers/__init__.py | 0 |
17 files changed, 1230 insertions, 0 deletions
diff --git a/pyscv/Frontend/__init__.py b/pyscv/Frontend/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pyscv/Frontend/__init__.py diff --git a/pyscv/Frontend/lexer.py b/pyscv/Frontend/lexer.py new file mode 100644 index 0000000..f7ca18a --- /dev/null +++ b/pyscv/Frontend/lexer.py @@ -0,0 +1,292 @@ +# TODO Logging is interesting for debugging purposes, decide what to do with it +from .logger import newlogger +logger = newlogger(__name__) +#import logging +#logger.setLevel(logging.WARN) + + +###### + +from enum import Enum +from .reader import Reader + +binChars = set("01") +octChars = set("01234567") +decChars = set("0123456789") +hexChars = set("0123456789ABCDEFabcdef") + +class TokenType(Enum): + """ + These are the possible tokens that the lexer knows, they are converted to + their most accurate representation in python. + """ + identifier = 0 # Represented as strings + label = 1 # Represented as strings or integer if they are numeric + instruction = 2 # Represented as strings + directive = 3 # Represented as strings + integer = 4 # Represented as int + character = 5 # Represented as strings of length 1 + string = 6 # Represented as strings + float = 7 # Represented as ?? + # (we need correct conversions to the binary value) + # TODO + end = 8 + argsep = 9 + openparens = 10 + closeparens = 11 + +class Lexer: + def __init__(self, reader): + self.reader = reader + self.tokenstart = 0 + + def __iter__(self): + return self + + def __next__(self): + if self.reader.char is None: + self.reader.advance() + while True: + try: + # Instruction end + if self.reader.char == "\n" or self.reader.char == ";": + self.reader.advance() + return (TokenType.end, None) + + # Spaces + elif self.reader.char.isspace(): + self.reader.advance() + + # Argument separator + elif self.reader.char == ",": + self.reader.advance() + return (TokenType.argsep, None) + + # String + elif self.reader.char == '"': + return self.string() + + # Character + elif self.reader.char == "'": + return self.character() + + # Comment + elif self.reader.char == "#": + self.comment() + + # Parenthesis + # Load a register as an address + an offset + elif self.reader.char == '(': + self.reader.advance() + return (TokenType.openparens, None) + elif self.reader.char == ')': + self.reader.advance() + return (TokenType.closeparens, None) + + # Starts with digit: + # - Numbers (any kind) + # - Numeric Labels + # - Numeric Label references + # - Load register as address: ld a1, 4(a0) + # ^^^^^ + # offset + reg + elif self.reader.char.isdigit() or self.reader.char == "-": + return self.number() + + # Identifiers or labels + elif self.reader.char.isalpha() or self.reader.char == "_": + return self.identifier() + + # Directive + elif self.reader.char == "." and self.reader.peek().isalpha(): + return self.directive() + + elif self.reader.char == "": + break # FILE END + + else: + raise ValueError("Don't know how to lex") + + except Exception as e: + # Handle exceptions + # raise StopIteration + raise e + raise StopIteration + + def string (self): + logger.info("Found string") + self.reader.advance() # Ignore opening quotes + + string = "" + escaped = False # Set if previous character was a backslash + + while self.reader.char != '"' or escaped: + if self.reader.char == "": + # TODO: Check how to do this + raise ValueError("Error: string not closed, found EOF") + if self.reader.char == "\n": + # TODO: Consider the string as closed and continue but report + # the error? + raise ValueError("Error: string not closed, found newline") + + if escaped: + string += self.escaped_char(self.reader.char) + logger.debug("Escape sequence processed %s", + string[-1].__repr__()) + escaped = False + continue + if self.reader.char == "\\": + escaped = True + self.reader.advance() + continue + escaped = False + string += self.reader.char + self.reader.advance() + logger.info("Lexed string %s", string.__repr__()) + self.reader.advance() # Discard closing " + return (TokenType.string, string) + + def character (self): + logger.info("Found character") + self.reader.advance() # Ignore the opening quote + character = self.reader.char + if character == "": + # TODO: Check how to do this + raise ValueError("Error: found EOF") + if not character.isprintable() : + raise ValueError("Error: Non printable character") + + if character == "\\": + self.reader.advance() + character = self.escaped_char(self.reader.char) + logger.debug("Escape sequence processed: %s", character.__repr__()) + + # Make sure it's correctly closed + self.reader.advance() + if self.reader.char != "'": + raise ValueError("Parse error: expected `'`, found " + self.reader.char) + self.reader.advance() # Discard closing ' + + logger.info("Lexed char %s", character.__repr__()) + return (TokenType.character, character) + + def comment(self): + while self.reader != "\n": + self.reader.advance() + + def escaped_char(self, ch): + if ch == '"': + return '"' + elif ch == 'n': + return '\n' + elif ch == 't': + return '\t' + elif ch == '\\': + return '\\' + else: + # TODO: implement more escape sequences + return "" + + + def number(self): + """ + Process anything that starts with a number. Could be: + - An actual number in hex, octal, binary or decimal + - Numeric labels or numeric label references + - floating point (not implemented yet) + """ + numbstr = self.reader.char + self.reader.advance() + + logger.info("Found number: %s", numbstr) + + # Hex, Bin, Oct and the 0 + if numbstr == "0": + reprid = self.reader.peek() + if reprid == "x": + self.reader.advance() + return self.hex() + elif reprid == "b": + self.reader.advance() + return self.bin() + elif reprid.isdigit(): + return self.oct() + else: + return (TokenType.integer, 0) + + # TODO: floating point numbers + + # Decimal number + # consume a decimal number + while self.reader.char in decChars: + numbstr += self.reader.char + self.reader.advance() + + # Numeric label reference + if self.reader.char in set("bf"): + numbstr += self.reader.char + self.reader.advance() + return (TokenType.identifier, numbstr) + # TODO: Return identifier or label? + # It's a label-ref not a label! + + # Numeric label + if self.reader.char in ":": + self.reader.advance() + return (TokenType.label, numbstr) + + return (TokenType.integer, int(numbstr)) + + def hex(self): + # TODO: Handle possible errors + numbstr = "" + while self.reader.char in hexChars: + numbstr += self.reader.char + self.reader.advance() + return (TokenType.integer, int(numbstr, 16)) + + def oct(self): + # TODO: Handle possible errors + numbstr = "" + while self.reader.char in octChars: + numbstr += self.reader.char + self.reader.advance() + return (TokenType.integer, int(numbstr, 8)) + + def bin(self): + # TODO: Handle possible errors + numbstr = "" + while self.reader.char in binChars: + numbstr += self.reader.char + self.reader.advance() + return (TokenType.integer, int(numbstr, 2)) + + + def identifier(self): + logger.info("Found identifier") + s = "" + while self.reader.char.isalnum() or self.reader.char == "_": + s += self.reader.char + self.reader.advance() + + if self.reader.char == ":": + self.reader.advance() + return (TokenType.label, s) + else: + return (TokenType.identifier, s) + + def directive(self): + logger.info("Found directive") + s = "" + self.reader.advance() # Discard leading dot + while self.reader.char.isalnum() or self.reader.char == "_": + s += self.reader.char + self.reader.advance() + return (TokenType.directive, s) + +if __name__ == "__main__": + import sys + with Reader(sys.argv[1]) as src: + lexer = Lexer(src) + for token in lexer: + print(token) diff --git a/pyscv/Frontend/logger.py b/pyscv/Frontend/logger.py new file mode 100644 index 0000000..a0f76e1 --- /dev/null +++ b/pyscv/Frontend/logger.py @@ -0,0 +1,10 @@ +import logging +import sys + +logging.basicConfig() + + +def newlogger(name): + logger = logging.getLogger(name) + logger.setLevel(level=logging.DEBUG) + return logger diff --git a/pyscv/Frontend/parser.py b/pyscv/Frontend/parser.py new file mode 100644 index 0000000..b5acfbb --- /dev/null +++ b/pyscv/Frontend/parser.py @@ -0,0 +1 @@ +from InstructionSets import RV32I, RV32C, RV32F, RV32D diff --git a/pyscv/Frontend/reader.py b/pyscv/Frontend/reader.py new file mode 100644 index 0000000..a2a280b --- /dev/null +++ b/pyscv/Frontend/reader.py @@ -0,0 +1,51 @@ +# TODO Logging is interesting for debugging purposes, decide what to do with it +from .logger import newlogger +logger = newlogger(__name__) +#import logging +#logger.setLevel(logging.WARN) + +class Reader: + def __init__(self, filename): + self.filename = filename + self._file = None + self.lineno = 0 + self.charno = 0 + self.char = None + + def open(self): + self._file = open(self.filename, "r") + return + def close(self): + self._file.close() + return + + def __enter__(self): + self.open() + return self + + def __exit__(self, type, value, tb): + self.close() + + def advance(self): + self.charno += 1 + if self.char == "\n": + self.lineno += 1 + self.charno = 0 + self.char = self._file.read(1) + + logger.debug("Read: %s" % self.char.__repr__()) + return + + def peek(self): + pos = self._file.tell() + ch = self._file.read(1) + self._file.seek(pos) + return ch + + +if __name__ == "__main__": + with Reader(__file__ ) as src: + print(src.peek()) + while src.peek() != "": + src.advance() + print(src.char) diff --git a/pyscv/InstructionSets/RV32C.py b/pyscv/InstructionSets/RV32C.py new file mode 100644 index 0000000..e740ac4 --- /dev/null +++ b/pyscv/InstructionSets/RV32C.py @@ -0,0 +1,29 @@ +from instructions import Instruction, InstructionSet + +class Compressed(Instruction): + size = 2 + + +class CR(Compressed): + pass + +class CI(Compressed): + pass + +class CSS(Compressed): + pass + +class CIW(Compressed): + pass + +class CJ(Compressed): + pass + +class CB(Compressed): + pass + +class CL(Compressed): + pass + +class CS(Compressed): + pass diff --git a/pyscv/InstructionSets/RV32D.py b/pyscv/InstructionSets/RV32D.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pyscv/InstructionSets/RV32D.py diff --git a/pyscv/InstructionSets/RV32F.py b/pyscv/InstructionSets/RV32F.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pyscv/InstructionSets/RV32F.py diff --git a/pyscv/InstructionSets/RV32I.py b/pyscv/InstructionSets/RV32I.py new file mode 100644 index 0000000..3fa02ad --- /dev/null +++ b/pyscv/InstructionSets/RV32I.py @@ -0,0 +1,466 @@ +from .instructions import Instruction, InstructionSet +from ctypes import c_uint32 + +RV32I = InstructionSet() + +class R(Instruction): + funct3 = None + funct7 = None + opcode = None + def __init__(self, rd, rs1, rs2): + self.rd = rd + self.rs1 = rs1 + self.rs2 = rs2 + + def compile(self): + # TODO: ensure sizes and convert register names to number... + return c_uint32( + (self.funct7 << 25) +\ + (self.rs2 << 20) +\ + (self.rs1 << 15) +\ + (self.funct3 << 12) +\ + (self.rd << 7) +\ + self.opcode + ) + +class I(Instruction): + funct3 = None + opcode = None + + def __init__(self, rd, rs, imm): + self.rd = rd + self.rs = rs + self.imm = imm + + def compile(self): + return c_uint32( + (self.imm << 20) +\ + (self.rs << 15) +\ + (self.funct3 << 12) +\ + (self.rd << 7) +\ + self.opcode + ) + +class S(Instruction): + funct3 = None + opcode = None + + def __init__(self, rs1, rs2, imm): + self.rs1 = rs1 + self.rs2 = rs2 + self.imm = imm + + def compile(self): + imm_0_4 = self.imm & 0x1F + imm_5_11 = (self.imm & 0xFE0)>>5 + return c_uint32( + (imm_5_11 << 25) +\ + (self.rs1 << 20) +\ + (self.rs2 << 15) +\ + (self.funct3 << 12) +\ + (imm_0_4 << 7) +\ + self.opcode + ) + +class B(Instruction): + funct3 = None + opcode = None + + def __init__(self, rs1, rs2, imm): + self.rs1 = rs1 + self.rs2 = rs2 + self.imm = imm + + def compile(self): + # NOTE: The lowest bit of the imm is always 0 because instructions + # are at least 16 bits wide, so it's not used, that's why this + # instruction looks that weird + imm_12 = (self.imm & 0b1000000000000)>>12 + imm_11 = (self.imm & 0b0100000000000)>>11 + imm_5_10 = (self.imm & 0b0011111100000)>>5 + imm_1_4 = (self.imm & 0b0000000011110)>>1 + return c_uint32( + (imm_12 << 31) +\ + (imm_5_10 << 25) +\ + (self.rs2 << 20) +\ + (self.rs1 << 15) +\ + (self.funct3 << 12) +\ + (imm_1_4 << 8) +\ + (imm_11 << 7) +\ + self.opcode + ) + + def patch(imm): + self.imm = imm + + +class U(Instruction): + opcode = None + def __init__(self, rd, imm): + self.rd = rd + self.imm = imm + + def compile(self): + # NOTE: U Type is for AUIPC and LUI that only use the high part of the + # immediate + imm_12_32 = (self.imm & 0xFFFFF000)>>12 + return c_uint32( + (imm_12_32 << 12) +\ + (self.rd << 7) +\ + self.opcode + ) + +class J(Instruction): + opcode = None + + def __init__(self, rd, imm): + self.rd = rd + self.imm = imm + + def compile(self): + # NOTE: Jumps are also weird + imm_20 = (self.imm & 0x100000)>>20 + imm_12_19 = (self.imm & 0x0FF000)>>12 + imm_11 = (self.imm & 0x000800)>>11 + imm_1_10 = (self.imm & 0x0007FE)>>1 + return c_uint32( + (imm_20 << 31) +\ + (imm_1_10 << 21) +\ + (imm_11 << 20) +\ + (imm_12_19 << 12) +\ + (self.rd << 7) +\ + self.opcode + ) + + def patch(imm): + self.imm = imm + + + +@RV32I.instruction +class lui(U): + name = "lui" + opcode = 0b0110111 + +@RV32I.instruction +class auipc(U): + name = "auipc" + opcode = 0b0010111 + +@RV32I.instruction +class jal(J): + name = "jal" + opcode = 0b1101111 + + def execute(self, pc): + # TODO + # - Save current pc in rd + # - Make pc from `imm` + # - Return new pc + return pc + +@RV32I.instruction +class jalr(I): + name = "jalr" + opcode = 0b1100111 + funct3 = 0b000 + +@RV32I.instruction +class beq(B): + name = "beq" + opcode = 0b1100011 + funct3 = 0b000 + +@RV32I.instruction +class bne(B): + name = "bne" + opcode = 0b1100011 + funct3 = 0b001 + +@RV32I.instruction +class blt(B): + name = "blt" + opcode = 0b1100011 + funct3 = 0b100 + +@RV32I.instruction +class bge(B): + name = "bge" + opcode = 0b1100011 + funct3 = 0b101 + +@RV32I.instruction +class bltu(B): + name = "bltu" + opcode = 0b1100011 + funct3 = 0b110 + +@RV32I.instruction +class bgeu(B): + name = "bgeu" + opcode = 0b1100011 + funct3 = 0b111 + +@RV32I.instruction +class lb(I): + name = "lb" + opcode = 0b0000011 + funct3 = 0b000 + +@RV32I.instruction +class lh(I): + name = "lh" + opcode = 0b0000011 + funct3 = 0b001 + +@RV32I.instruction +class lw(I): + name = "lw" + opcode = 0b0000011 + funct3 = 0b010 + +@RV32I.instruction +class lbu(I): + name = "lbu" + opcode = 0b0000011 + funct3 = 0b100 + +@RV32I.instruction +class lhu(I): + name = "lhu" + opcode = 0b0000011 + funct3 = 0b101 + + +@RV32I.instruction +class sb(S): + name = "sb" + opcode = 0b0100011 + funct3 = 0b000 + +@RV32I.instruction +class sh(S): + name = "sh" + opcode = 0b0100011 + funct3 = 0b001 + +@RV32I.instruction +class sw(S): + name = "sw" + opcode = 0b0100011 + funct3 = 0b010 + +@RV32I.instruction +class addi(I): + name = "addi" + opcode = 0b0010011 + funct3 = 0b000 + + def execute(self, pc): + # TODO + return pc + self.size + +@RV32I.instruction +class slti(I): + name = "slti" + opcode = 0b0010011 + funct3 = 0b010 + +@RV32I.instruction +class sltiu(I): + name = "sltiu" + opcode = 0b0010011 + funct3 = 0b011 + +@RV32I.instruction +class xori(I): + name = "xori" + opcode = 0b0010011 + funct3 = 0b100 + +@RV32I.instruction +class ori(I): + name = "ori" + opcode = 0b0010011 + funct3 = 0b110 + +@RV32I.instruction +class andi(I): + name = "andi" + opcode = 0b0010011 + funct3 = 0b111 + + +class ShiftImm(I): + # NOTE: This is an special type used for shifting operations because they + # have 7 bits left after the maximum shift (5bits -> 32 rotations) + # they can apply. + # In RV64I they can indicate rotation with 1 bit more (64 rotations) so + # they use a funct6 instead. + funct7 = None + funct3 = None + opcode = None + + def __init__(self, rd, rs, imm): + self.rd = rd + self.rs = rs + self.imm = imm + + def compile(self): + return c_uint32( + (self.funct7 << 25) +\ + (self.imm << 20) +\ + (self.rs << 15) +\ + (self.funct3 << 12) +\ + (self.rd << 7) +\ + self.opcode + ) + +@RV32I.instruction +class slli(ShiftImm): + name = "slli" + opcode = 0b0010011 + funct3 = 0b001 + funct7 = 0b0000000 + +@RV32I.instruction +class srli(ShiftImm): + name = "srli" + opcode = 0b0010011 + funct3 = 0b101 + funct7 = 0b0000000 + +@RV32I.instruction +class srai(ShiftImm): + name = "srai" + opcode = 0b0010011 + funct3 = 0b101 + funct7 = 0b0100000 + + +@RV32I.instruction +class add(R): + name = "add" + opcode = 0b0110011 + funct3 = 0b000 + funct7 = 0b0000000 + + def execute(self, pc): + # TODO + return pc + self.size + +@RV32I.instruction +class sub(R): + name = "sub" + opcode = 0b0110011 + funct3 = 0b000 + funct7 = 0b0100000 + +@RV32I.instruction +class sll(R): + name = "sll" + opcode = 0b0110011 + funct3 = 0b001 + funct7 = 0b0000000 + +@RV32I.instruction +class slt(R): + name = "slt" + opcode = 0b0110011 + funct3 = 0b010 + funct7 = 0b0000000 + +@RV32I.instruction +class sltu(R): + name = "sltu" + opcode = 0b0110011 + funct3 = 0b011 + funct7 = 0b0000000 + +@RV32I.instruction +class xor(R): + name = "xor" + opcode = 0b0110011 + funct3 = 0b100 + funct7 = 0b0000000 + +@RV32I.instruction +class srl(R): + name = "srl" + opcode = 0b0110011 + funct3 = 0b101 + funct7 = 0b0000000 + +@RV32I.instruction +class sra(R): + name = "sra" + opcode = 0b0110011 + funct3 = 0b101 + funct7 = 0b0100000 + +@RV32I.instruction +class _or(R): + name = "or" + opcode = 0b0110011 + funct3 = 0b110 + funct7 = 0b0000000 + +@RV32I.instruction +class _and(R): + name = "and" + opcode = 0b0110011 + funct3 = 0b111 + funct7 = 0b0000000 + +@RV32I.instruction +class ecall(I): + name = "ecall" + opcode = 0b1110011 + funct3 = 0b000 + + def __init__(self): + # NOTE: ecall is a I-type instruction but doesn't get any arg and sets + # every field to 0 + self.rd = 0b00000 + self.rs = 0b00000 + self.imm = 0b000000000000 + + +@RV32I.instruction +class ebreak(I): + name = "ebreak" + opcode = 0b1110011 + funct3 = 0b000 + + def __init__(self): + # NOTE: ebreak is a I-type instruction but doesn't get any arg and pre- + # -sets every field to a fixed value + self.rd = 0b00000 + self.rs = 0b00000 + self.imm = 0b000000000001 + + + + + + + + + + + + + + + + +@RV32I.pseudoinstruction +class j(J): + name = "j" + def __new__(cls, imm): + return jal("x0", imm) + + +if __name__ == "__main__": + print(RV32I) + print(RV32I.instructions) diff --git a/pyscv/InstructionSets/RV64I.py b/pyscv/InstructionSets/RV64I.py new file mode 100644 index 0000000..72b8bcb --- /dev/null +++ b/pyscv/InstructionSets/RV64I.py @@ -0,0 +1,63 @@ +from .instructions import Instruction, InstructionSet +from ctypes import c_uint32 +from .RV32I import * + +RV64I = InstructionSet() + +class ShiftImm64(ShiftImm): + # NOTE: This is an special type used for shifting operations because they + # have 7 bits left after the maximum shift (5bits -> 32 rotations) + # they can apply. + # In RV64I they can indicate rotation with 1 bit more (64 rotations) so + # they use a funct6 instead. + funct6 = None + funct3 = None + opcode = None + + def __init__(self, rd, rs, imm): + self.rd = rd + self.rs = rs + self.imm = imm + + def compile(self): + return c_uint32( + (self.funct6 << 26) +\ + (self.imm << 20) +\ + (self.rs << 15) +\ + (self.funct3 << 12) +\ + (self.rd << 7) +\ + self.opcode + ) + +@RV64I.instruction +class slli(ShiftImm64): + name = "slli" + opcode = 0b0010011 + funct3 = 0b001 + funct6 = 0b000000 + +@RV64I.instruction +class srli(ShiftImm64): + name = "srli" + opcode = 0b0010011 + funct3 = 0b101 + funct6 = 0b000000 + +@RV64I.instruction +class srai(ShiftImm64): + name = "srai" + opcode = 0b0010011 + funct3 = 0b101 + funct6 = 0b010000 + +@RV64I.instruction +class sd(S): + name = "sd" + opcode = 0b0100011 + funct3 = 0b011 + +@RV64I.instruction +class ld(I): + name = "ld" + opcode = 0b0000011 + funct3 = 0b011 diff --git a/pyscv/InstructionSets/__init__.py b/pyscv/InstructionSets/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pyscv/InstructionSets/__init__.py diff --git a/pyscv/InstructionSets/instructions.py b/pyscv/InstructionSets/instructions.py new file mode 100644 index 0000000..00a3146 --- /dev/null +++ b/pyscv/InstructionSets/instructions.py @@ -0,0 +1,36 @@ +from ctypes import c_uint32 + + +class Instruction: + size = 4 # Instruction size in bytes + + def __init__(self): + pass + + def compile(self): + # return the binstream of the instruction in a c_uint32 + pass + + def execute(self, pc): + # executes the instruction and returns the next program counter + return + +class InstructionSet: + + def __init__(self, init=None): + self.instructions = dict() + + def instruction(self, ins): + if ins.name not in self.instructions: + self.instructions[ins.name] = ins + return ins + + # NOTE: We don't need to treat pseudoinstructions in an special way yet, + # but we separate the decorator for clarity + pseudoinstruction = instruction + + +if __name__ == "__main__": + # TODO This is the interface i'd love to have + addins = add("x5","x2","zero") + j("labelName") diff --git a/pyscv/main.py b/pyscv/main.py new file mode 100644 index 0000000..8cdc08f --- /dev/null +++ b/pyscv/main.py @@ -0,0 +1,8 @@ +# Use a generator for the execution flow +# -> PC has to be a global variable, updated by each instruction to the next val +# So user can set the PC by hand and call next(run) and make the code jump! + +pc = 0 +while True: + inst = fetch_instruction(pc) + pc = inst.execute() diff --git a/pyscv/memory.py b/pyscv/memory.py new file mode 100644 index 0000000..84bf0bd --- /dev/null +++ b/pyscv/memory.py @@ -0,0 +1,112 @@ +from ctypes import c_uint8, c_uint16, c_uint32 + +class Addressable: + def __init__(self, start=0, end=0): + self.start = start + self.end = end + def addr_to_pos(self, addr): + if not (self.start <= addr < self.end): + raise KeyError("Address out of space") + return addr - self.start + +def merge_bytes (byte_iter): + val = 0 + for i,v in enumerate(byte_ouput): + val += v.value << 8 * i + return val +def split_bytes (val, byte_count): + return tuple( c_uint8(val >> 8 * i) for i in range(byte_count) ) + +class Memory32 (Addressable): + """ + This is a raw 32-bit word memory addressable at a byte level. + Internally it is defined as c_uint8 list. + Special functions are needed to access halfs (c_uint16) and words + (c_uint32). + """ + + def __init__(self, start=0, end=0, bigEndian=False): + super().__init__(start, end) + self.bigEndian = bigEndian + self.lastChange = None + # Pre-allocate or allocate or allocate per write? + self.data = [None] * (end - start) + + def get_byte(self, addr): + return self.data[ self.addr_to_pos(addr) ] + def set_byte(self, addr, val): + if not isinstance(val, c_uint8): + if val > 0xFF: + raise ValueError("Value is larger than a byte") + self.data[ self.addr_to_pos(addr) ] = val if isinstance(val, c_uint8) else c_uint8(val) + self.lastChange = range(addr, addr+1) + + + def get_half(self,addr): + byte_output = (get_byte(addr), get_byte(addr+1)) + if self.bigEndian: + byte_output = reversed(byte_output) + return c_uint16( merge_bytes(byte_output) ) + + def set_half(self, addr, val): + if not isinstance(val, c_uint16): + if val > 0xFFFF: + raise ValueError("Value is larger than a half") + bytes = split_bytes(val, 2) + if self.bigEndian: + bytes = reversed(bytes) + for i,v in enumerate(bytes): + self.set_byte(addr+i, v) + self.lastChange = range(addr,addr+2) + + def get_word(self, pos): + byte_output = tuple(get_byte(addr+i) for i in range(4)) + if self.bigEndian: + byte_output = reversed(byte_output) + return c_uint32( merge_bytes(byte_output) ) + + def set_word(self, addr, val): + if not isinstance(val, c_uint32): + if val > 0xFFFFFFFF: + raise ValueError("Value is larger than a word") + bytes = split_bytes(val, 4) + if self.bigEndian: + bytes = reversed(bytes) + for i,v in enumerate(bytes): + self.set_byte(addr+i, v) + self.lastChange = range(addr,addr+4) + + def __str__(self): + out = " " + out += "-" * 50 + out += "\n" + for i,d in enumerate(self.data): + if d is not None: + addr = i+self.start + out += "->" if addr in self.lastChange else " " + out += f"| {addr:#20x} | {d.value:#5} | {d.value:#04x} | {d.value:#010b} |" + out += "\n" + out += " " + out += "-" * 50 + out += "\n" + return out + + +class Memory32RO (Memory32): + def set_byte(self, addr, val): + raise NotImplementedError("Trying to write in a read-only memory") + def set_half(self, addr, val): + raise NotImplementedError("Trying to write in a read-only memory") + def set_word(self, addr, val): + raise NotImplementedError("Trying to write in a read-only memory") + +class CodeMemory32 (Memory32RO): + def __init__(self, start=0, end=0): + super().__init__(writable=False,start=start,end=end) + + +if __name__ == "__main__": + m = Memory32(start=100,end=200) + print(m.start, m.end) + m.set_byte(100, 246) + print(m) diff --git a/pyscv/registers/RV32F.py b/pyscv/registers/RV32F.py new file mode 100644 index 0000000..8e3fd28 --- /dev/null +++ b/pyscv/registers/RV32F.py @@ -0,0 +1,45 @@ +from RV32I import RegistersRV32I + +class RegistersRV32F(RegistersRV32I): + + def __init__(self): + self.names = {} + self.data = [] + + self.setter = [] + self.getter = [] + + self.lastChange = None + + self.addRegister(("f0","ft0")) + self.addRegister(("f1","ft1")) + self.addRegister(("f2","ft2")) + self.addRegister(("f3","ft3")) + self.addRegister(("f4","ft4")) + self.addRegister(("f5","ft5")) + self.addRegister(("f6","ft6")) + self.addRegister(("f7","ft7")) + self.addRegister(("f8","fs0")) + self.addRegister(("f9","fs1")) + self.addRegister(("f10","a0")) + self.addRegister(("f11","a1")) + self.addRegister(("f12","a2")) + self.addRegister(("f13","a3")) + self.addRegister(("f14","a4")) + self.addRegister(("f15","a5")) + self.addRegister(("f16","a6")) + self.addRegister(("f17","a7")) + self.addRegister(("f18","fs2")) + self.addRegister(("f19","fs3")) + self.addRegister(("f20","fs4")) + self.addRegister(("f21","fs5")) + self.addRegister(("f22","fs6")) + self.addRegister(("f23","fs7")) + self.addRegister(("f24","fs8")) + self.addRegister(("f25","fs9")) + self.addRegister(("f26","fs10")) + self.addRegister(("f27","fs11")) + self.addRegister(("f28","ft8")) + self.addRegister(("f29","ft9")) + self.addRegister(("f30","ft10")) + self.addRegister(("f31","ft11")) diff --git a/pyscv/registers/RV32I.py b/pyscv/registers/RV32I.py new file mode 100644 index 0000000..a723064 --- /dev/null +++ b/pyscv/registers/RV32I.py @@ -0,0 +1,117 @@ +# Register block of RV32I +# Set and get using [] with the name of the register (lowercase) or the +# position +from ctypes import c_uint32 + +def defaultGetter(self, pos): + return self.data[pos] + +def defaultSetter(self, pos, val): + self.data[pos] = val + +def zeroSetter(self, pos, val): + # hardwired to zero + self.data[pos] = c_uint32(0) + + +class RegistersRV32I: + + def __init__(self): + self.names = {} + self.data = [] + + self.setter = [] + self.getter = [] + + self.lastChange = None + + self.addRegister(("x0","zero"), setter = zeroSetter) + self.addRegister(("x1","ra")) + self.addRegister(("x2","sp", "v0")) + self.addRegister(("x3","gp", "v1")) + self.addRegister(("x4","tp")) + self.addRegister(("x5","t0")) + self.addRegister(("x6","t1")) + self.addRegister(("x7","t2")) + self.addRegister(("x8","s0","fp")) + self.addRegister(("x9","s1")) + self.addRegister(("x10","a0")) + self.addRegister(("x11","a1")) + self.addRegister(("x12","a2")) + self.addRegister(("x13","a3")) + self.addRegister(("x14","a4")) + self.addRegister(("x15","a5")) + self.addRegister(("x16","a6")) + self.addRegister(("x17","a7")) + self.addRegister(("x18","s2")) + self.addRegister(("x19","s3")) + self.addRegister(("x20","s4")) + self.addRegister(("x21","s5")) + self.addRegister(("x22","s6")) + self.addRegister(("x23","s7")) + self.addRegister(("x24","s8")) + self.addRegister(("x25","s9")) + self.addRegister(("x26","s10")) + self.addRegister(("x27","s11")) + self.addRegister(("x28","t3")) + self.addRegister(("x29","t4")) + self.addRegister(("x30","t5")) + self.addRegister(("x31","t6")) + + + def addRegister(self, names, getter=defaultGetter, setter=defaultSetter): + for name in names: + if name in self.names: + raise KeyError("Register name already in use") + + currentpos = len(self.data) + for name in names: + self.names[name] = currentpos + self.data.append(c_uint32(0)) + + self.getter.append(getter) + self.setter.append(setter) + + def getPos(self, el): + if isinstance(el, str): + pos = self.names[el] + elif isinstance(el, int): + pos = el + else: + raise ValueError("Wrong type of register id. Must be str or int") + return pos + + def __getitem__(self, el): + pos = self.getPos(el) + return self.getter[pos](self, pos) + + def __setitem__(self, el, val): + pos = self.getPos(el) + + if val > 0xFFFFFFFF: + raise ValueError("Value is larger than size of the register") + val = c_uint32(val) + self.setter[pos](self, pos, val) + self.lastChange = pos + + + def __str__(self): + + out = " " + out += "-" * 67 + out += "\n" + for i,d in enumerate(self.data): + out += "->" if i == self.lastChange else " " + out += f" | {d.value:#13} | {d.value:#010x} | {d.value:#034b} |" + out += "\n" + out += " " + out += "-" * 67 + out += "\n" + return out + + +if __name__ == "__main__": + Regs = RegistersRV32I() + Regs[0] = 1 + Regs["x1"] = 10 + print(Regs) diff --git a/pyscv/registers/__init__.py b/pyscv/registers/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pyscv/registers/__init__.py |