├── README.md └── src ├── __pycache__ ├── cpu.cpython-311.pyc ├── instr.cpython-311.pyc └── validator.cpython-311.pyc ├── cpu.py ├── instr.py ├── main.py ├── main2.py └── validator.py /README.md: -------------------------------------------------------------------------------- 1 | ### Comming soon 2 | 3 | - C++ Implementation with bindings in Python 4 | - Expanded Architecture: Unified Data and Instruction Memory. L1, L2, L3 caching. 5 | - Expanded ISA: Additional R & D type instructions. 6 | - Assembler + WLP4(C) Compiler 7 | 8 | # RISC-V Emulator 9 | 10 | This is a basic RISC-V emulator written in Python. It emulates a single cycle data path system. 11 | 12 | ## Instructions and Restrictions 13 | 14 | - **ADD** 15 | 16 | - **Instruction Format:** `ADD XA, XB, XC` 17 | - **Description:** Add the contents of `XB` and `XC` and stores the sum in `XA` 18 | 19 | - **SUB** 20 | 21 | - **Instruction Format:** `SUB XA, XB, XC` 22 | - **Description:** Subtract the contents of `XC` from `XB` and stores the difference in `XA` 23 | 24 | - **LDUR** 25 | 26 | - **Instruction Format:** `LDUR XA, [XB, IMM]` 27 | - **Description:** Loads the data from memory address `XB + IMM` into register `XA` 28 | - **Restrictions:** 29 | - Memory Access must be double-word aligned 30 | - Immediate value must be in the range [-256, 255] 31 | - **Example:** `LDUR X1, [X2, #24]` loads data from memory address `X2 + 24` into register `X1` 32 | 33 | - **STUR** 34 | 35 | - **Instruction Format:** `STUR XA, [XB, IMM]` 36 | - **Description:** Stores the data in `XA` into memory address `XB + IMM` 37 | - **Restrictions:** 38 | - Memory Access must be double-word aligned 39 | - Immediate value must be in the range [-256, 255] 40 | - **Example:** `STUR X1, [X2, #24]` stores the data in `X1` into memory address `X2 + 24` 41 | 42 | - **ADDI** 43 | 44 | - **Instruction Format:** `ADDI XA, XB, IMM` 45 | - **Description:** Add the contents of `XB` and `IMM` and stores the sum in `XA` 46 | - **Restrictions:** 47 | - Immediate value must be in the range [0, 4095] 48 | 49 | - **SUBI** 50 | 51 | - **Instruction Format:** `SUBI XA, XB, IMM` 52 | - **Description:** Subtract the value `IMM` from `XB` and stores the difference in `XA` 53 | - **Restrictions:** 54 | - Immediate value must be in the range [0, 4095] 55 | 56 | - **B** 57 | 58 | - **Instruction Format:** `B IMM` 59 | - **Description:** Unconditionally branches, incrementing the PC by an offset of `4 * IMM` 60 | - **Restrictions:** 61 | - Immediate value must be in the range [-33 554 432, 33 554 431] 62 | - **Example:** `B #28` sets the program counter to `PC + 4*28` 63 | 64 | - **CBZ** 65 | 66 | - **Instruction Format:** `CBZ XA, IMM` 67 | - **Description:** Incrementing PC by an offset of `4 * IMM` if the content of `XA` is `0`, else moves to next instruction 68 | - **Restrictions:** 69 | - Immediate value must be in the range [-262144, 262143] 70 | - **Example:** `CBZ X1, #8` sets the program counter to `PC + 4*8` if `X1` is `0`, else it moves the program counter to the next instruction 71 | 72 | - **CBNZ** 73 | - **Instruction Format:** `CBNZ XA, IMM` 74 | - **Description:** Incrementing PC by an offset of `4 * IMM` if the content of `XA` is not `0`, else moves to next instruction 75 | - **Restrictions:** 76 | - Immediate value must be in the range [-262144, 262143] 77 | - **Example:** `CBNZ X1, #8` sets the program counter to `PC + 4*8` if `X1` is not `0`, else it moves the program counter to the next instruction 78 | 79 | ## Basic Usage 80 | 81 | 1. Clone the repository: `git clone [URL]` 82 | 2. Navigate to the project directory: `cd src` 83 | 3. Run the emulator: `./main.py`. (Probably will need to do `chmod u+x main.py` first). This is a sample program. 84 | 4. To run your own program, import the CPU class and follow the below examples. 85 | 86 | Basic Usage Example: 87 | 88 | ```python 89 | from cpu import CPU 90 | 91 | code1 = [ 92 | # ... RISC-V Code here 93 | # (Array of strings, each string being 1 line of ARM code) 94 | ] 95 | 96 | # DMem Configuration (pre-initialization) 97 | dmem_config = ''' 98 | [16]=10 99 | [24]=20 100 | [32]=30 101 | ''' 102 | 103 | # Registers Configuration (pre-initialization) 104 | reg_config = ''' 105 | X10=16 106 | X1=4 107 | ''' 108 | 109 | # Initialize CPU object 110 | cpu1 = CPU('Array Sum', code1, reg_config=reg_config, mem_config=dmem_config, randomize=False) 111 | 112 | # Execute Program 113 | cpu1.run() 114 | 115 | # Step through program -> Use to debug 116 | # cpu1.step() 117 | 118 | # Set printing mode to decimal. (Hex by default) 119 | cpu1.set_print_mode(hex_mode=False) 120 | 121 | # Print CPU final state 122 | print(cpu1) 123 | 124 | # Assert Testing 125 | assert(cpu2.get_reg_value('X5') == 5) # X5 should have value 5 126 | assert(cpu2.get_double_word(56) == 15) # [56] should contain value 15 127 | 128 | ``` 129 | 130 | Basic Grading Example: 131 | 132 | ```python 133 | from cpu import CPU 134 | 135 | code1 = [ 136 | # ... RISC-V Code here 137 | ] 138 | 139 | code2 = [ 140 | # ... RISC-V Code here 141 | ] 142 | 143 | cpu1 = CPU('Solution', code1) 144 | cpu2 = CPU('Student Response', code2) 145 | 146 | cpu1.run() 147 | cpu2.run() 148 | 149 | # CPU state equality 150 | print(cpu1 == cpu2) 151 | 152 | ``` 153 | 154 | ## API Functions 155 | 156 | ### `__init__(self, id, code, reg_config='', mem_config='', randomize=True)` 157 | 158 | Instantiates a CPU object. By default, initializes the CPU to random values. This can be overridden to set specific values in registers and data memory. 159 | 160 | - `id`: Identifier for the CPU object (use for debugging). 161 | - `code`: Array of strings, each containing a line of ARM source code. 162 | - `reg_config`: String containing configuration for registers 163 | - `mem_config`: String containing configuration for data memory 164 | - `randomize`: Boolean flag used to enable/disable randomizing non-configured CPU values. 165 | 166 | Example1: 167 | 168 | ```python 169 | code = [ 170 | "ADDI X2,X12,#16", 171 | "STUR X2, [X1, #5]", 172 | ] 173 | 174 | cpu1 = CPU("first cpu", code) 175 | ``` 176 | 177 | Example2: 178 | 179 | ```python 180 | code = [ 181 | "ADDI X2,X12,#16", 182 | "STUR X2, [X1, #5]", 183 | ] 184 | 185 | # Dmem Values 186 | dmem_config = ''' 187 | [16]=1 188 | [24]=2 189 | ''' 190 | 191 | # Register Values 192 | reg_config = ''' 193 | X1=16 194 | X2=5 195 | ''' 196 | 197 | # __init__(self, code, reg_config = '', dmem_config = '', randomize = False) 198 | cpu3 = CPU('cpu3', code3, reg_config=reg_config, mem_config=dmem_config, randomize=False) 199 | ``` 200 | 201 | ### `run(self)` 202 | 203 | Executes the program in it's entirety. 204 | 205 | Example: 206 | 207 | ```python 208 | cpu1 = CPU("first cpu", code) 209 | 210 | cpu1.run() 211 | ``` 212 | 213 | ### `step(self)` 214 | 215 | Steps through every instruction of the program, printing the state every time. 216 | 217 | Example: 218 | 219 | ```python 220 | cpu1 = CPU("first cpu", code) 221 | 222 | cpu1.step() 223 | ``` 224 | 225 | ### `__eq__(cpu1, cpu2)` 226 | 227 | Returns `True` if two CPU objects are equal, else `False`. Two CPUs are equal if their registers and data memory both have the same values. 228 | 229 | - `cpu1`: CPU object instance 230 | - `cpu2`: CPU object instance 231 | 232 | Example: 233 | 234 | ```python 235 | cpu1 = CPU("first", code1) 236 | cpu1.run() 237 | 238 | cpu2 = CPU("second", code2) 239 | cpu2.run() 240 | 241 | print(cpu1 == cpu2) # Checking Equality Here 242 | ``` 243 | 244 | Related: 245 | 246 | - `__ne__(cpu1, cpu2)` 247 | - `reg_eq(cpu1, cpu2)` - Returns `True` if both CPUs have the same Register values, else `False` 248 | - `mem_eq(cpu1, cpu2)` - Returns `True` if both CPUs have the same Data Memory values, else `False` 249 | 250 | ### `__print__(self)` 251 | 252 | Prints the state of the CPU. 253 | 254 | Example: 255 | 256 | ```python 257 | cpu1 = CPU("first", code1) 258 | cpu1.run() 259 | 260 | print(cpu1) # Printing Here 261 | ``` 262 | 263 | Related: 264 | 265 | - `__str__(self)` - Returns a string containing the state of the CPU 266 | - `set_print_mode(self, hex_mode: True)` - Use `hex_mode` boolean flag to set printing to decimal/hexadecimal 267 | 268 | ### `write_state(self, file_name)` 269 | 270 | Writes the state of the CPU to a file. 271 | 272 | - `file_name`: Name of the file that the state of the CPU is written to. Creates a new file in `file_name` doesn't exist. 273 | 274 | Example: 275 | 276 | ```python 277 | cpu1 = CPU("first", code1) 278 | cpu1.run() 279 | 280 | cpu1.write_state("my_cpu_state.txt") # Writing Here 281 | ``` 282 | 283 | ### `get_reg_value(self, reg)` 284 | 285 | Returns the value in a register. 286 | 287 | - `reg`: String containing the name of the register whose value we wish to get. 288 | 289 | Example: 290 | 291 | ```python 292 | cpu1 = CPU("first", code1) 293 | cpu1.run() 294 | 295 | reg_val = cpu1.get_reg_value('X1') # Getting value in 'X1' here 296 | ``` 297 | 298 | ### `set_reg_value(self, reg, value)` 299 | 300 | Sets the value in a register. 301 | 302 | - `reg`: String containing the name of the register whose value we wish to get. 303 | - `value`: The new value we wish to place into the register 304 | 305 | Example: 306 | 307 | ```python 308 | cpu1 = CPU("first", code1) 309 | 310 | cpu1.set_reg_value('X1', 100) # Setting 'X1' to 100 here 311 | cpu1.set_reg_value('X2', 200) # Setting 'X2' to 200 here 312 | cpu1.set_reg_value('X3', 300) # Setting 'X3' to 300 here 313 | 314 | cpu1.run() 315 | ``` 316 | 317 | ### `get_double_word(self, mem_index)` 318 | 319 | Gets the double-word value at a memory index. Returns a 64-bit value from `mem[mem_index]` to `mem[mem_index + 7]` inclusive, where `mem[i]` is 8-bits of data. 320 | 321 | - `mem_index`: Index from which we want to get the value from 322 | 323 | Example: 324 | 325 | ```python 326 | cpu1 = CPU("first", code1) 327 | cpu1.run() 328 | 329 | # Getting double-word starting at index 8 here 330 | mem_val = cpu1.get_double_word(8) 331 | ``` 332 | 333 | ### `set_double_word(self, mem_index, value)` 334 | 335 | Sets the double word value starting at a memory index. 336 | 337 | - `mem_index`: Index from which we want to get the value from 338 | - `value`: The new value we want to place into the double word 339 | 340 | Example: 341 | 342 | ```python 343 | cpu1 = CPU("first", code1) 344 | 345 | cpu1.set_double_word(24, 150) # Setting mem[24] to 150 here 346 | cpu1.set_double_word(32, 250) # Setting mem[32] to 250 here 347 | cpu1.set_double_word(40, 350) # Setting mem[40] to 350 here 348 | 349 | cpu1.run() 350 | ``` 351 | 352 | Related: 353 | 354 | - `set_mem_val(self, mem_index, value)` - Sets 1 byte of memory data to a certain value 355 | 356 | ### `randomize_cpu(self)` 357 | 358 | Randomizes the values in the registers and data memory within a CPU. 359 | 360 | Example: 361 | 362 | ```python 363 | cpu1 = CPU("first", code1) 364 | 365 | cpu1.randomize_cpu() # Randomizing here 366 | 367 | cpu1.run() 368 | ``` 369 | 370 | Related: 371 | 372 | - `randomize_register(self, reg_index)` - Randomizes value within a particular register 373 | - `randomize_mem_byte(self, byte_index)` - Randomizes the value within a particular byte of memory 374 | - `randomize_registers(self)` - Randomizes values within the registers 375 | - `randomize_data_memory(self)` - Randomizes values within data memory 376 | -------------------------------------------------------------------------------- /src/__pycache__/cpu.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaikrishnaTadepalli/cs251_riskv_emu/ddfc77fd5b4163a875aceb416f94b0195bc73c43/src/__pycache__/cpu.cpython-311.pyc -------------------------------------------------------------------------------- /src/__pycache__/instr.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaikrishnaTadepalli/cs251_riskv_emu/ddfc77fd5b4163a875aceb416f94b0195bc73c43/src/__pycache__/instr.cpython-311.pyc -------------------------------------------------------------------------------- /src/__pycache__/validator.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaikrishnaTadepalli/cs251_riskv_emu/ddfc77fd5b4163a875aceb416f94b0195bc73c43/src/__pycache__/validator.cpython-311.pyc -------------------------------------------------------------------------------- /src/cpu.py: -------------------------------------------------------------------------------- 1 | import re 2 | import random 3 | 4 | # import instr as Instr 5 | from instr import Instr 6 | from validator import ValidateARM 7 | 8 | # number of bytes in data memory 9 | MEMSIZE = 256 10 | 11 | class CPU: 12 | def __init__(self, 13 | id: any, 14 | code: list[str], 15 | reg_config: str = "", 16 | mem_config: str = "", 17 | randomize: bool = True 18 | ) -> None: 19 | # Validate Arm Code 20 | ValidateARM.validate_code(code) 21 | 22 | self.id = id 23 | self.pc = 0 24 | 25 | self.code = code 26 | 27 | self.registers = [0] * 32 28 | self.data_mem = [0] * MEMSIZE 29 | self.print_mode_hex = True 30 | 31 | if randomize: self.randomize_cpu() 32 | if reg_config != "": self.config_reg(reg_config) 33 | if mem_config != "": self.config_mem(mem_config) 34 | 35 | def str_hex(self) -> str: 36 | def twos_complement_hex(number, num_bits): 37 | # Calculate the two's complement 38 | if number < 0: 39 | number = (1 << num_bits) + number 40 | 41 | # Convert to hexadecimal 42 | hex_str = hex(number & ((1 << num_bits) - 1)) 43 | 44 | # Pad with zeros if needed 45 | hex_str = hex_str[2:].zfill(num_bits // 4) 46 | hex_str = hex_str.upper() 47 | 48 | return ' '.join([hex_str[i:i+2] for i in range(0, len(hex_str), 2)]) 49 | 50 | def pad_hex(num, length): 51 | return twos_complement_hex(num, length * 4) 52 | 53 | def pad_left(data, length, char=' '): 54 | data_str = str(data) 55 | if len(data_str) >= length: return data_str[:length] 56 | else: return char * (length - len(data_str)) + data_str 57 | 58 | 59 | res = f"\nCPU ID: {self.id} {'-' * (65 - len(str(self.id)))}\n\n" 60 | 61 | res += f"PC = {self.pc}\n\n" 62 | 63 | res += f"Registers:\n" 64 | for i in range(0, 16): 65 | res += f"\t{pad_left(f'X{i}', 4)}: {pad_hex(self.registers[i], 16)}\t|| {pad_left(f'X{i + 16}', 3)}: {pad_hex(self.registers[i + 16], 16)}\n" 66 | 67 | res += "\nData Memory:\n" 68 | for i in range(0, MEMSIZE // 2, 8): 69 | c_w = MEMSIZE // 2 70 | c1, c2 = c_w * 0, c_w * 1 71 | 72 | d_hex = lambda x: pad_hex(self.data_mem[x], 2) 73 | 74 | res += f"\t{pad_left(c1 + i, 4)}: {d_hex(c1 + i + 0)} {d_hex(c1 + i + 1)} {d_hex(c1 + i + 2)} {d_hex(c1 + i + 3)} {d_hex(c1 + i + 4)} {d_hex(c1 + i + 5)} {d_hex(c1 + i + 6)} {d_hex(c1 + i + 7)}" 75 | res += f" || {pad_left(c2 + i, 4)}: {d_hex(c2 + i + 0)} {d_hex(c2 + i + 1)} {d_hex(c2 + i + 2)} {d_hex(c2 + i + 3)} {d_hex(c2 + i + 4)} {d_hex(c2 + i + 5)} {d_hex(c2 + i + 6)} {d_hex(c2 + i + 7)}" 76 | res += "\n" 77 | 78 | res += f"\n{'-' * 74}\n\n" 79 | 80 | return res 81 | 82 | def str_dec(self) -> str: 83 | def pad_left(data, length, char=' '): 84 | data_str = str(data) 85 | if len(data_str) >= length: return data_str[:length] 86 | else: return char * (length - len(data_str)) + data_str 87 | 88 | def dec_to_twos_comp(n): 89 | if n > 0x7FFFFFFFFFFFFFFF: 90 | return -1 * ((n ^ (0xFFFFFFFFFFFFFFFF)) +1) 91 | return n 92 | 93 | res = f"\nCPU ID: {self.id} {'-' * (65 - len(str(self.id)))}\n\n" 94 | 95 | res += f"PC = {self.pc}\n\n" 96 | 97 | res += f"Registers:\n" 98 | for i in range(0, 16): 99 | num1 = dec_to_twos_comp(self.registers[i]) 100 | num2 = dec_to_twos_comp(self.registers[i + 16]) 101 | res += f"\t{pad_left(f'X{i}', 4)}: {pad_left(num1, 23)}\t|| {pad_left(f'X{i + 16}', 3)}: {pad_left(num2, 23)}\n" 102 | 103 | res += "\nData Memory:\n" 104 | for i in range(0, MEMSIZE // 2, 8): 105 | c_w = MEMSIZE // 2 106 | c1, c2 = c_w * 0, c_w * 1 107 | 108 | num1, num2 = 0, 0 109 | for j in range(8): 110 | num1, num2 = num1 << 8, num2 << 8 111 | num1 += self.data_mem[c1 + i + j] 112 | num2 += self.data_mem[c2 + i + j] 113 | 114 | num1 = dec_to_twos_comp(num1) 115 | num2 = dec_to_twos_comp(num2) 116 | 117 | res += f"\t{pad_left(c1 + i, 4)}: {pad_left(num1, 23)}\t|| " 118 | res += f"{pad_left(c2 + i, 4)}: {pad_left(num2, 23)}\n" 119 | 120 | res += f"\n{'-' * 74}\n\n" 121 | 122 | return res 123 | 124 | def set_print_mode(self, hex_mode: bool = True) -> None: 125 | self.print_mode_hex = hex_mode 126 | 127 | def __str__(self) -> str: 128 | if self.print_mode_hex: 129 | return self.str_hex() 130 | else: 131 | return self.str_dec() 132 | 133 | def __print__(self) -> None: 134 | print(str(self)) 135 | 136 | def reg_eq(cpu1, cpu2) -> bool: 137 | for R1, R2 in zip(cpu1.registers, cpu2.registers): 138 | if R1 != R2: return False 139 | return True 140 | 141 | def mem_eq(cpu1, cpu2) -> bool: 142 | for M1, M2 in zip(cpu1.data_mem, cpu2.data_mem): 143 | if M1 != M2: return False 144 | return True 145 | 146 | def __eq__(cpu1, cpu2) -> bool: 147 | return CPU.reg_eq(cpu1, cpu2) and CPU.mem_eq(cpu1, cpu2) 148 | 149 | def __ne__(cpu1, cpu2) -> bool: 150 | return CPU.__eq__(cpu1, cpu2) == False 151 | 152 | def write_state(self, file_name: str) -> None: 153 | cpu_str = str(self) 154 | 155 | f = open(file_name) 156 | f.write(cpu_str) 157 | f.close() 158 | 159 | def config_reg(self, reg_config: str) -> None: 160 | reg_config_list = list(filter(lambda x: x != "", reg_config.splitlines())) 161 | 162 | # Regex for: 'X', some number, '=', some number 163 | inp_pattern = r'X\d+=-?\d+' 164 | for config in reg_config_list: 165 | formatted_config = config.replace(" ", "").upper() 166 | 167 | if bool(re.match(inp_pattern, formatted_config)) == False: 168 | error_msg = f''' 169 | Invalid reg_config rule. 170 | Recieved: {config}. 171 | Expected Pattern: 'XA=VAL'. 172 | Example: 'X1=4'. 173 | ''' 174 | raise ValueError(error_msg) 175 | 176 | reg, val = formatted_config.split('=') 177 | value = int(val) 178 | 179 | self.set_reg_value(reg, value) 180 | 181 | def config_mem(self, mem_config: str) -> None: 182 | mem_config_list = list(filter(lambda x: x != "", mem_config.splitlines())) 183 | 184 | # Regex for: '[', some number, ']', '=', some number 185 | inp_pattern = r'\[\d+\]=-?\d+' 186 | 187 | for config in mem_config_list: 188 | formatted_config = config.replace(" ", "").upper() 189 | 190 | if bool(re.match(inp_pattern, formatted_config)) == False: 191 | error_msg = f''' 192 | Invalid mem_config rule. 193 | Recieved rule: '{config}'. 194 | Expected Pattern: '[MEM]=VAL'. 195 | Example: '[8]=32'. 196 | ''' 197 | raise ValueError(error_msg) 198 | 199 | mem, val = formatted_config.split('=') 200 | 201 | mem = mem.replace('[', '') 202 | mem = mem.replace(']', '') 203 | 204 | mem_index = int(mem) 205 | value = int(val) 206 | 207 | self.set_double_word(mem_index, value) 208 | 209 | def get_cur_instr(self) -> str: 210 | i = (self.pc // 4) 211 | if i >= len(self.code): 212 | error_msg = f''' 213 | PC value out of bounds. 214 | Expected PC < {len(self.code) * 4}. Recieved PC = {self.pc}. 215 | (Expected i < {len(self.code)}. Recieved i = {i}.)' 216 | ''' 217 | raise IndexError(error_msg) 218 | return self.code[self.pc // 4].upper() 219 | 220 | def get_reg_index(reg: str) -> int: 221 | reg = reg.upper() 222 | 223 | error_msg = f''' 224 | Invalid register. Recieved '{reg}'. 225 | ''' 226 | 227 | if reg == 'XZR': 228 | return 31 229 | 230 | if len(reg) < 2: 231 | raise ValueError(error_msg) 232 | 233 | if reg[0] != 'X': 234 | raise ValueError(error_msg) 235 | 236 | try: 237 | reg_num = int(reg[1:]) 238 | except ValueError: 239 | raise ValueError(error_msg) 240 | 241 | if reg_num < 0 or reg_num > 31: 242 | raise ValueError(error_msg) 243 | 244 | return reg_num 245 | 246 | def get_immediate_value(imm: str) -> int: 247 | error_msg = f''' 248 | Invalid immediate value. Recieved '{imm}'. 249 | ''' 250 | 251 | if len(imm) <= 1 or imm[0] != '#': 252 | raise ValueError(error_msg) 253 | 254 | try: 255 | imm_num = int(imm[1:]) 256 | except ValueError: 257 | raise ValueError(error_msg) 258 | 259 | return imm_num 260 | 261 | def validate_double_word_index(self, mem_index: int) -> None: 262 | if (mem_index % 8) != 0: 263 | error_msg = f''' 264 | Invalid double word index. 265 | Recieved mem_index: [{mem_index}]. 266 | mem_index must be double-word aligned. 267 | ''' 268 | raise ValueError(error_msg) 269 | 270 | if mem_index + 7 >= len(self.data_mem): 271 | error_msg = f''' 272 | Invalid double word index. 273 | Index out of bounds. 274 | Recieved: {mem_index}. 275 | Maximum Accepted: {len(self.data_mem) - 8} 276 | ''' 277 | raise ValueError(error_msg) 278 | 279 | def get_reg_value(self, reg: str) -> int: 280 | reg_index = CPU.get_reg_index(reg) 281 | return self.registers[reg_index] 282 | 283 | def set_reg_value(self, reg: str, value: int) -> None: 284 | reg_index = CPU.get_reg_index(reg) 285 | self.set_register_val(reg_index, value) 286 | 287 | def get_double_word(self, mem_index: int) -> int: 288 | self.validate_double_word_index(mem_index) 289 | 290 | res = 0 291 | for i in range(8): 292 | res <<= 8 293 | res += self.data_mem[mem_index + i] 294 | return res 295 | 296 | def set_double_word(self, mem_index: int, value: int) -> None: 297 | self.validate_double_word_index(mem_index) 298 | for i in range(7, -1, -1): 299 | temp = value & 0xFF 300 | value >>= 8 301 | self.set_mem_val(mem_index + i, temp) 302 | 303 | def set_register_val(self, reg_index: int, value: int) -> None: 304 | if reg_index < 0 or reg_index >= len(self.registers): 305 | error_msg = f''' 306 | Invalid Register Index. Recieved {reg_index}. 307 | ''' 308 | raise IndexError(error_msg) 309 | 310 | self.registers[reg_index] = value 311 | 312 | def set_mem_val(self, mem_index: int, value: int) -> None: 313 | if mem_index < 0 or mem_index >= MEMSIZE: 314 | error_msg = f''' 315 | Invalid Memory Index. Recieved {mem_index}. 316 | ''' 317 | raise IndexError(error_msg) 318 | 319 | self.data_mem[mem_index] = value 320 | 321 | def randomize_register(self, reg_index: int) -> None: 322 | rand_val = random.randrange(2 ** 64) 323 | self.set_register_val(reg_index, rand_val) 324 | 325 | def randomize_mem_byte(self, byte_index: int) -> None: 326 | rand_val = random.randrange(2 ** 8) 327 | self.set_mem_val(byte_index, rand_val) 328 | 329 | def randomize_registers(self) -> None: 330 | for i in range(len(self.registers) - 1): 331 | self.randomize_register(i) 332 | 333 | def randomize_data_memory(self) -> None: 334 | for i in range(len(self.data_mem)): 335 | self.randomize_mem_byte(i) 336 | 337 | def randomize_cpu(self) -> None: 338 | self.randomize_registers() 339 | self.randomize_data_memory() 340 | 341 | def run_instr(self) -> None: 342 | # Get current Instruction 343 | instr_values = Instr.extract(self.get_cur_instr()) 344 | 345 | # Seperate Instruction and Instruction Args 346 | instr, args = instr_values.instr, instr_values.args 347 | 348 | def run_r_type() -> None: 349 | try: 350 | XA, XB, XC = args 351 | except: 352 | error_msg = f''' 353 | Invalid args. 354 | Recieved {args}. 355 | Expected type: ['XA', 'XB', 'XC']. 356 | ''' 357 | raise ValueError(error_msg) 358 | 359 | XA = CPU.get_reg_index(XA) 360 | XB = CPU.get_reg_index(XB) 361 | XC = CPU.get_reg_index(XC) 362 | 363 | if instr == Instr.ADD: 364 | # Compute value to be put in XA 365 | new_val = self.registers[XB] + self.registers[XC] 366 | elif instr == Instr.SUB: 367 | # Compute value to be put in XA 368 | new_val = self.registers[XB] - self.registers[XC] 369 | else: 370 | error_msg = f''' 371 | Unknown R-type instruction. Recieved '{instr}' 372 | ''' 373 | raise ValueError(error_msg) 374 | 375 | # Place new value in XA 376 | self.set_register_val(XA, new_val) 377 | 378 | # Increment PC to next instruction 379 | self.pc += 4 380 | 381 | def run_d_type() -> None: 382 | try: 383 | XA, XB, IMM = args 384 | except: 385 | error_msg = f''' 386 | Invalid args. 387 | Recieved {args}. 388 | Expected type: ['XA', 'XB', 'IMM']. 389 | ''' 390 | raise ValueError(error_msg) 391 | 392 | XA = CPU.get_reg_index(XA) 393 | XB = CPU.get_reg_index(XB) 394 | IMM = CPU.get_immediate_value(IMM) 395 | 396 | #Enforcing IMM bounds 397 | if not (-256 <= IMM <= 255): 398 | error_msg = f''' 399 | Immediete Value invalid. 400 | Recieved IMM = {IMM}. 401 | Expected -256 <= IMM <= 255. 402 | ''' 403 | raise ValueError(error_msg) 404 | 405 | mem_index = self.registers[XB] + IMM 406 | 407 | # Memory Access must be double word aligned 408 | if (mem_index % 8 != 0): 409 | error_msg = f''' 410 | Memory Access invalid during D-type instruction. 411 | Recieved access index = {mem_index}. 412 | Access Index must be double-word aligned. 413 | ''' 414 | raise ValueError(error_msg) 415 | 416 | if instr == Instr.LDUR: 417 | mem_value = self.get_double_word(mem_index) 418 | self.set_register_val(XA, mem_value) 419 | elif instr == Instr.STUR: 420 | reg_val = self.registers[XA] 421 | self.set_double_word(mem_index, reg_val) 422 | else: 423 | error_msg = f''' 424 | Unknown D-type instruction. Recieved '{instr}' 425 | ''' 426 | raise ValueError(error_msg) 427 | 428 | # Increment PC to next instruction 429 | self.pc += 4 430 | 431 | def run_i_type() -> None: 432 | try: 433 | XA, XB, IMM = args 434 | except: 435 | error_msg = f''' 436 | Invalid args. 437 | Recieved {args}. 438 | Expected type: ['XA', 'XB', 'IMM']. 439 | ''' 440 | raise ValueError(error_msg) 441 | 442 | XA = CPU.get_reg_index(XA) 443 | XB = CPU.get_reg_index(XB) 444 | IMM = CPU.get_immediate_value(IMM) 445 | 446 | # Enforcing IMM bounds 447 | if not (0 <= IMM <= 4095): 448 | error_msg = f''' 449 | Immediete Value invalid. 450 | Recieved IMM = {IMM}. 451 | Expected 0 <= IMM <= 4095. 452 | ''' 453 | raise ValueError(error_msg) 454 | 455 | if instr == Instr.ADDI: 456 | # Compute value to be put in XA 457 | new_val = self.registers[XB] + IMM 458 | elif instr == Instr.SUBI: 459 | # Compute value to be put in XA 460 | new_val = self.registers[XB] - IMM 461 | else: 462 | error_msg = f''' 463 | Unknown I-type instruction. Recieved '{instr}' 464 | ''' 465 | raise ValueError(error_msg) 466 | 467 | # Place new value in XA 468 | self.set_register_val(XA, new_val) 469 | 470 | # Increment PC to next instruction 471 | self.pc += 4 472 | 473 | def run_b_type() -> None: 474 | try: 475 | IMM = args[0] 476 | except: 477 | error_msg = f''' 478 | Invalid args. 479 | Recieved {args}. 480 | Expected type: ['IMM']. 481 | ''' 482 | raise ValueError(error_msg) 483 | 484 | IMM = CPU.get_immediate_value(IMM) 485 | 486 | # Enforcing IMM bounds 487 | if not (-33554432 <= IMM <= 33554431): 488 | error_msg = f''' 489 | Immediete Value invalid. 490 | Recieved IMM = {IMM}. 491 | Expected -33 554 432 <= IMM <= 33 554 431. 492 | ''' 493 | raise ValueError(error_msg) 494 | 495 | # Set new PC value 496 | self.pc += 4 * IMM 497 | 498 | def run_cb_type() -> None: 499 | try: 500 | XA, IMM = args 501 | except: 502 | error_msg = f''' 503 | Invalid args. 504 | Recieved {args}. 505 | Expected type: ['XA', 'IMM']. 506 | ''' 507 | raise ValueError(error_msg) 508 | 509 | XA = CPU.get_reg_index(XA) 510 | IMM = CPU.get_immediate_value(IMM) 511 | 512 | # Enforcing IMM bounds 513 | if not (-262144 <= IMM <= 262143): 514 | error_msg = f''' 515 | Immediete Value invalid. 516 | Recieved IMM = {IMM}. 517 | Expected -262 144 <= IMM <= 262 143. 518 | ''' 519 | raise ValueError(error_msg) 520 | 521 | step = 1 522 | if instr == Instr.CBZ: 523 | if self.registers[XA] == 0: step = IMM 524 | elif instr == Instr.CBNZ: 525 | if self.registers[XA] != 0: step = IMM 526 | else: 527 | error_msg = f''' 528 | Unknown CB-type instruction. Recieved '{instr}' 529 | ''' 530 | raise ValueError(error_msg) 531 | 532 | # Set new PC value 533 | self.pc += 4 * step 534 | 535 | if instr in Instr.R_TYPE: run_r_type() 536 | elif instr in Instr.D_TYPE: run_d_type() 537 | elif instr in Instr.I_TYPE: run_i_type() 538 | elif instr in Instr.B_TYPE: run_b_type() 539 | elif instr in Instr.CB_TYPE: run_cb_type() 540 | else: 541 | error_msg = f''' 542 | Invalid Instruction. 543 | Recieved '{self.get_cur_instr()}' where: 544 | - 'instr' is '{instr}' 545 | - 'args' is {args} 546 | ''' 547 | raise ValueError(error_msg) 548 | 549 | def run(self) -> None: 550 | while self.pc < len(self.code) * 4: 551 | self.run_instr() 552 | 553 | def step(self) -> None: 554 | print(f"Executing {'=' * 64}\n") 555 | print("Initial State: ") 556 | print(self) 557 | while self.pc < len(self.code) * 4: 558 | input() 559 | print("Instructions:") 560 | for i, instr in enumerate(self.code): 561 | line = f"\t{f'{i * 4: >3}'}: " + f"{instr: <25}" 562 | if i == (self.pc // 4): line += "\t<<< Just Executed" 563 | print(line) 564 | self.run_instr() 565 | print(self) 566 | print(f"Finished {'=' * 65}") 567 | -------------------------------------------------------------------------------- /src/instr.py: -------------------------------------------------------------------------------- 1 | 2 | class Instr_Info: 3 | def __init__(self, instr, args): 4 | self.instr = instr 5 | self.args = args 6 | 7 | class Instr: 8 | ADD = "ADD" 9 | SUB = "SUB" 10 | 11 | LDUR = "LDUR" 12 | STUR = "STUR" 13 | 14 | ADDI = "ADDI" 15 | SUBI = "SUBI" 16 | 17 | B = "B" 18 | 19 | CBZ = "CBZ" 20 | CBNZ = "CBNZ" 21 | 22 | INSTRS = [ 23 | ADD, SUB, LDUR, STUR, ADDI, SUBI, B, CBZ, CBNZ 24 | ] 25 | 26 | R_TYPE = [ ADD, SUB ] 27 | D_TYPE = [ LDUR, STUR ] 28 | I_TYPE = [ ADDI, SUBI ] 29 | B_TYPE = [ B ] 30 | CB_TYPE = [ CBZ, CBNZ ] 31 | 32 | def extract(instr_str): 33 | instr_str = instr_str.upper() 34 | 35 | instr = instr_str.split(' ')[0] 36 | args = instr_str[len(instr):] 37 | 38 | args = args.replace(',', ' ') 39 | 40 | args = args.replace('[', ' ') 41 | args = args.replace(']', ' ') 42 | 43 | args = args.split(' ') 44 | args = list(filter(lambda x: x != '', args)) 45 | 46 | return Instr_Info(instr, args) 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from cpu import CPU 4 | 5 | # Sum array problem: 6 | # - Array will be setup in dmem 7 | # - X1 points to base of array 8 | # - X2 has the length of the array 9 | # - X3 points to the last item in the array. Place the result after this 10 | 11 | code1 = [ 12 | 'ADD X4, XZR, XZR', # Use X4 as scratch register for sum - set it to 0 13 | 'LDUR X5, [X1, #0]', # Use X5 as scratch register to hold current array value 14 | 'ADD X4, X4, X5', # Add current array value to running sum 15 | 'ADDI X1, X1, #8', # Increment Array Pointer (use X1) 16 | 'SUBI X2, X2, #1', # Decrement counter (use X2) 17 | 'CBNZ X2, #-4', # If counter is not 0, go to next array item 18 | 'STUR X4, [X3, #8]', # Store result 1 place after the last item in the array 19 | ] 20 | 21 | # Memory Values (pre-initialization) 22 | dmem_config = ''' 23 | [16]=1 24 | [24]=2 25 | [32]=3 26 | [40]=4 27 | [48]=5 28 | ''' 29 | 30 | # Register Values (pre-initialization) 31 | reg_config = ''' 32 | X1=16 33 | X2=5 34 | X3=48 35 | ''' 36 | 37 | # Initialize CPU object. 38 | cpu1 = CPU('Array Sum Example', code1, reg_config, dmem_config) 39 | 40 | # CPU prints in hexadecimal by default. Set to decimal. 41 | cpu1.set_print_mode(hex_mode=False) 42 | 43 | # Run ARM code 44 | cpu1.run() 45 | 46 | # Steps through ARM code. Use for Debugging. 47 | # cpu1.step() 48 | 49 | # Print CPU state. 50 | print(cpu1) 51 | 52 | # Testing Expected Values 53 | assert(cpu1.get_reg_value('X1') == 56) # X1 should point to the address of the last item 54 | assert(cpu1.get_reg_value('X2') == 0) # X2 should be 0 55 | assert(cpu1.get_reg_value('X4') == 15) # X4 should contain sum: 55 56 | assert(cpu1.get_reg_value('X5') == 5) # X5 should have the value of the last array item 57 | assert(cpu1.get_double_word(56) == 15) # [96] should contian the sum: 55 -------------------------------------------------------------------------------- /src/main2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from cpu import CPU 4 | from validator import ValidateARM 5 | 6 | # Sum values from array 7 | code3 = [ 8 | "ADDI X0, X31, #5", 9 | "ADD X2, X31, XZR", 10 | "ADDI X8, X31, #0", 11 | "LDUR X1, [X8, #0]", 12 | "ADD X2, X2, X1", 13 | "SUBI X0, X0, #1", 14 | "ADDI X8, X8, #8", 15 | "CBNZ X0, #-4", 16 | ] 17 | 18 | ValidateARM.validate_code(code3) 19 | 20 | # Dmem Values 21 | dmem_config = ''' 22 | [16]=1 23 | [24]=2 24 | [32]=3 25 | [40]=4 26 | [48]=5 27 | ''' 28 | 29 | # Register Values 30 | reg_config = ''' 31 | X1=16 32 | X2=5 33 | ''' 34 | 35 | # __init__(self, code, reg_config = '', dmem_config = '', randomize = False) 36 | cpu3 = CPU('cpu3', code3, reg_config=reg_config, mem_config=dmem_config, randomize=False) 37 | 38 | print(cpu3) -------------------------------------------------------------------------------- /src/validator.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class ValidateARM(): 4 | def extract_immediate_value(instruction): 5 | match = re.search(r'#(-?\d+)', instruction) 6 | if match: 7 | return int(match.group(1)) 8 | return None 9 | 10 | def validate_immediate(immediate, lower_limit, upper_limit): 11 | return lower_limit <= immediate <= upper_limit 12 | 13 | def validate_instruction(instruction, patterns): 14 | for pattern, validator in patterns.items(): 15 | match = re.match(pattern, instruction) 16 | if match: 17 | return validator(match) 18 | raise SyntaxError(f"{instruction} does not follow the instruction format") 19 | 20 | def validate_range(match, lower_limit, upper_limit, instruction): 21 | immediate = int(match.group(1)) 22 | if not ValidateARM.validate_immediate(immediate, lower_limit, upper_limit): 23 | raise SyntaxError(f"Immediate value in {instruction} is out of range.") 24 | return True 25 | 26 | def validate_add_sub(match): 27 | return ValidateARM.validate_range(match, 0, 4095, match.group(0)) 28 | 29 | def validate_ldur_stur(match): 30 | return ValidateARM.validate_range(match, -256, 255, match.group(0)) 31 | 32 | def validate_cbnz_cbz(match): 33 | return ValidateARM.validate_range(match, -262144, 262143, match.group(0)) 34 | 35 | def validate_b(match): 36 | return ValidateARM.validate_range(match, -33554432, 33554431, match.group(0)) 37 | 38 | def validate_add(match): 39 | return True 40 | 41 | def validate_sub(match): 42 | return True 43 | 44 | def validate(instruction): 45 | instruction = instruction.upper() 46 | 47 | patterns = { 48 | r'^\s*ADDI\s+X([0-2]?[0-9]|30|31|ZR)\s*,*\s*X([0-2]?[0-9]|30|31|ZR)\s*,*\s*#(\d+)\s*$': ValidateARM.validate_add_sub, 49 | r'^\s*SUBI\s+X([0-2]?[0-9]|30|31|ZR)\s*,*\s*X([0-2]?[0-9]|30|31|ZR)\s*,*\s*#(\d+)\s*$': ValidateARM.validate_add_sub, 50 | r'^\s*LDUR\s+X([0-9]|1[0-9]|2[0-9]|30|31|ZR)\s*,*\s*\[\s*X([0-9]|1[0-9]|2[0-9]|30|31|ZR)\s*,*\s*#(-?\d+)\s*]\s*$': ValidateARM.validate_ldur_stur, 51 | r'^\s*STUR\s+X([0-9]|1[0-9]|2[0-9]|30|31|ZR)\s*,*\s*\[\s*X([0-9]|1[0-9]|2[0-9]|30|31|ZR)\s*,*\s*#(-?\d+)\s*]\s*$': ValidateARM.validate_ldur_stur, 52 | r'^\s*CBNZ\s+X([0-9]|1[0-9]|2[0-9]|30|31|ZR)\s*,*\s*#(-?\d+)\s*$': ValidateARM.validate_cbnz_cbz, 53 | r'^\s*CBZ\s+X([0-9]|1[0-9]|2[0-9]|30|31|ZR)\s*,*\s*#(-?\d+)\s*$': ValidateARM.validate_cbnz_cbz, 54 | r'^\s*B\s*\s*#(-?\d+)\s*$': ValidateARM.validate_b, 55 | r'^\s*ADD\s+X([0-2]?[0-9]|30|31|ZR)\s*,*\s*X([0-2]?[0-9]|30|31|ZR)\s*,*\s*X([0-2]?[0-9]|30|31|ZR)\s*$': ValidateARM.validate_add, 56 | r'^\s*SUB\s+X([0-2]?[0-9]|30|31|ZR)\s*,*\s*X([0-2]?[0-9]|30|31|ZR)\s*,*\s*X([0-2]?[0-9]|30|31|ZR)\s*$': ValidateARM.validate_sub, 57 | } 58 | 59 | for pattern, validator in patterns.items(): 60 | if re.match(pattern, instruction): 61 | return validator(re.match(pattern, instruction)) 62 | 63 | raise SyntaxError(f"{instruction} does not follow the instruction format") 64 | 65 | # Returns list of strings arm code from a file 66 | def read_file(filename: str): 67 | with open(filename, 'r') as file: 68 | lines = file.readlines() 69 | 70 | return [line.strip() for line in lines] 71 | 72 | # Takes a multiline string and returns a list of strings 73 | def split_string(arm_code: str): 74 | return list(filter(lambda x: x != "", arm_code.splitlines())) 75 | 76 | # Takes a list of strings arm code and validates it 77 | def validate_code(code): 78 | for line in code: 79 | try: 80 | ValidateARM.validate(line) 81 | except SyntaxError as e: 82 | print(e) 83 | raise SyntaxError() 84 | print('Arm Code Validated!') 85 | --------------------------------------------------------------------------------