├── riscy_boi ├── __init__.py ├── __main__.py ├── program_counter.py ├── data_memory.py ├── alu.py ├── top.py ├── register_file.py ├── cpu.py ├── instruction_decoder.py └── encoding.py ├── .gitignore ├── README.md ├── .pylintrc ├── pyproject.toml ├── tests ├── test_register_file.py ├── test_cpu.py ├── test_encoding.py ├── test_alu.py ├── test_program_counter.py └── test_instruction_decoder.py ├── conftest.py ├── riscyboi.xml └── poetry.lock /riscy_boi/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.vcd 2 | **/__pycache__/ 3 | build/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A simple RISC V CPU built with nMigen 2 | This is a toy project for learning digital logic design via designing a CPU 3 | implementing the RV32I ISA. 4 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [FORMAT] 2 | max-line-length=79 3 | indent-after-paren=8 4 | 5 | [BASIC] 6 | good-names=m,o,a,b,op,wp,i,pc,rd,rf 7 | 8 | [DESIGN] 9 | max-attributes=14 10 | max-args=6 11 | 12 | [MESSAGES CONTROL] 13 | disable=C0116,R0903,no-self-use,E1129,R0914 14 | -------------------------------------------------------------------------------- /riscy_boi/__main__.py: -------------------------------------------------------------------------------- 1 | """Entry point""" 2 | from nmigen_boards import blackice_ii 3 | 4 | from . import top 5 | 6 | 7 | def main(): 8 | plat = blackice_ii.BlackIceIIPlatform() 9 | top_inst = top.Top() 10 | plat.build(top_inst) 11 | 12 | 13 | if __name__ == "__main__": 14 | main() 15 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "riscy-boi" 3 | version = "0.1.0" 4 | description = "Toy nmigen rv32i cpu implementation" 5 | authors = ["Hannah McLaughlin "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.8" 9 | nmigen = {git="https://github.com/nmigen/nmigen"} 10 | nmigen_boards = {git="https://github.com/nmigen/nmigen-boards"} 11 | yowasp-yosys = "^0.9.post5191.dev103" 12 | yowasp-nextpnr-ice40 = "^0.0.post2867.dev68" 13 | yowasp-nextpnr-ice40-8k = "^0.0.post2867.dev68" 14 | 15 | [tool.poetry.dev-dependencies] 16 | pytest = "^6.2.1" 17 | pylint = "^2.6.0" 18 | 19 | [build-system] 20 | requires = ["poetry-core>=1.0.0"] 21 | build-backend = "poetry.core.masonry.api" 22 | -------------------------------------------------------------------------------- /tests/test_register_file.py: -------------------------------------------------------------------------------- 1 | """Register file tests""" 2 | import nmigen as nm 3 | 4 | from riscy_boi import register_file 5 | 6 | 7 | def test_write_to_read_register(sync_sim): 8 | m = nm.Module() 9 | rf = m.submodules.rf = register_file.RegisterFile() 10 | 11 | m.d.comb += [ 12 | rf.write_enable.eq(1), 13 | rf.write_select.eq(2), 14 | rf.read_select_1.eq(2), 15 | rf.write_data.eq(rf.read_data_1 + 1), 16 | ] 17 | 18 | def testbench(): 19 | expected_value = 10 20 | for _ in range(expected_value): 21 | yield 22 | 23 | assert (yield rf.read_data_1) == expected_value 24 | 25 | sync_sim(m, testbench) 26 | -------------------------------------------------------------------------------- /tests/test_cpu.py: -------------------------------------------------------------------------------- 1 | """CPU tests""" 2 | import nmigen as nm 3 | 4 | from riscy_boi import cpu, encoding 5 | 6 | 7 | def test_cpu(sync_sim): 8 | m = nm.Module() 9 | reg = 2 10 | cpu_inst = m.submodules.cpu = cpu.CPU(debug_reg=reg) 11 | 12 | link_reg = 5 13 | program = [encoding.IType.encode( 14 | 1, 15 | reg, 16 | encoding.IntRegImmFunct.ADDI, 17 | reg, 18 | encoding.Opcode.OP_IMM), 19 | # jump back to the previous instruction for infinite loop 20 | encoding.JType.encode(0x1ffffc, link_reg)] 21 | 22 | imem = nm.Memory(width=32, depth=1024, init=program) 23 | imem_rp = m.submodules.imem_rp = imem.read_port(domain="sync") 24 | m.d.comb += [ 25 | imem_rp.addr.eq(cpu_inst.imem_addr[2:]), 26 | cpu_inst.imem_data.eq(imem_rp.data), 27 | ] 28 | 29 | def testbench(): 30 | for _ in range(20): 31 | yield 32 | 33 | sync_sim(m, testbench) 34 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | """Test configuration""" 2 | import os 3 | import shutil 4 | 5 | import nmigen.sim 6 | import pytest 7 | 8 | 9 | VCD_TOP_DIR = os.path.join( 10 | os.path.dirname(os.path.realpath(__file__)), 11 | "tests", 12 | "vcd") 13 | 14 | 15 | def vcd_path(node): 16 | directory = os.path.join(VCD_TOP_DIR, node.fspath.basename.split(".")[0]) 17 | os.makedirs(directory, exist_ok=True) 18 | return os.path.join(directory, node.name + ".vcd") 19 | 20 | 21 | @pytest.fixture(scope="session", autouse=True) 22 | def clear_vcd_directory(): 23 | shutil.rmtree(VCD_TOP_DIR, ignore_errors=True) 24 | 25 | 26 | @pytest.fixture 27 | def comb_sim(request): 28 | 29 | def run(fragment, process): 30 | sim = nmigen.sim.Simulator(fragment) 31 | sim.add_process(process) 32 | with sim.write_vcd(vcd_path(request.node)): 33 | sim.run_until(100e-6) 34 | 35 | return run 36 | 37 | 38 | @pytest.fixture 39 | def sync_sim(request): 40 | 41 | def run(fragment, process): 42 | sim = nmigen.sim.Simulator(fragment) 43 | sim.add_sync_process(process) 44 | sim.add_clock(1 / 10e6) 45 | with sim.write_vcd(vcd_path(request.node)): 46 | sim.run() 47 | 48 | return run 49 | -------------------------------------------------------------------------------- /riscy_boi/program_counter.py: -------------------------------------------------------------------------------- 1 | """Program Counter""" 2 | import nmigen as nm 3 | 4 | INSTR_BYTES = 4 5 | 6 | 7 | class ProgramCounter(nm.Elaboratable): 8 | """ 9 | Program Counter 10 | 11 | * load (in): low to increment, high to load an address 12 | * input_address (in): the input used when loading an address 13 | 14 | * pc (out): the address of the instruction being executed this clock cycle 15 | * pc_next (out): the address of the instruction being executed next clock 16 | cycle 17 | * pc_inc (out): the address after that of the instruction being executed 18 | this clock cycle 19 | """ 20 | 21 | def __init__(self, width=32): 22 | self.load = nm.Signal() 23 | self.input_address = nm.Signal(width) 24 | self.pc = nm.Signal(width) 25 | self.pc_next = nm.Signal(width) 26 | self.pc_inc = nm.Signal(width) 27 | 28 | def elaborate(self, _): 29 | m = nm.Module() 30 | 31 | m.d.comb += self.pc_inc.eq(self.pc + INSTR_BYTES) 32 | m.d.sync += self.pc.eq(self.pc_next) 33 | 34 | with m.If(self.load): 35 | m.d.comb += self.pc_next.eq(self.input_address) 36 | with m.Else(): 37 | m.d.comb += self.pc_next.eq(self.pc_inc) 38 | 39 | return m 40 | -------------------------------------------------------------------------------- /riscy_boi/data_memory.py: -------------------------------------------------------------------------------- 1 | """Data memory interface""" 2 | import enum 3 | 4 | import nmigen as nm 5 | 6 | 7 | class AddressMode(enum.IntEnum): 8 | """Address mode for data memory""" 9 | BYTE = 0b00 10 | HALF = 0b01 11 | WORD = 0b10 12 | 13 | 14 | class DataMemory(nm.Elaboratable): 15 | """ 16 | Data memory interface 17 | 18 | * byte_address (in): the byte address to read from the data memory 19 | * address_mode (in): whether the output is byte, half-word or word 20 | * signed (in): whether to sign- or zero-extend output 21 | 22 | * load_value (out): the value read from the data memory, sliced according 23 | to address mode 24 | """ 25 | 26 | def __init__(self): 27 | # Inputs 28 | self.byte_address = nm.Signal(32) 29 | self.address_mode = nm.Signal(AddressMode) 30 | self.signed = nm.Signal() 31 | self.dmem_r_data = nm.Signal(32) 32 | 33 | # Outputs 34 | self.dmem_r_addr = nm.Signal(30) 35 | self.load_value = nm.Signal(32) 36 | 37 | def elaborate(self, _): 38 | m = nm.Module() 39 | m.d.comb += self.dmem_r_addr.eq(self.byte_address[2:]) 40 | 41 | with m.Switch(self.address_mode): 42 | with m.Case(AddressMode.BYTE): 43 | m.d.comb += self.load_value.eq(self.dmem_r_data.word_select( 44 | self.byte_address[0:2], 8)) 45 | with m.Case(AddressMode.HALF): 46 | m.d.comb += self.load_value.eq(self.dmem_r_data.word_select( 47 | self.byte_address[1], 16)) 48 | with m.Case(AddressMode.WORD): 49 | m.d.comb += self.load_value.eq(self.dmem_r_data) 50 | 51 | return m 52 | -------------------------------------------------------------------------------- /tests/test_encoding.py: -------------------------------------------------------------------------------- 1 | """Tests for encoding and decoding""" 2 | import nmigen as nm 3 | import pytest 4 | 5 | from riscy_boi import encoding 6 | 7 | 8 | @pytest.mark.parametrize( 9 | "offset", 10 | [ # note LSB is always zero 11 | 0b000001111000011110000, 12 | 0b100000000000000000000, 13 | 0b111110000111100001110, 14 | 0b001010101010101010100, 15 | 0b111111111111111111110, 16 | 0b110010100010001001100, 17 | 0b100110100010011001110, 18 | ]) 19 | def test_jtype_same_offset_out_as_in(comb_sim, offset): 20 | m = nm.Module() 21 | jal = nm.Const(encoding.JType.encode(offset, 5), shape=32) 22 | extended_offset = int(f"{offset:021b}"[0]*11 + f"{offset:021b}", 2) 23 | assert (extended_offset & 0x1ffffe) == offset 24 | 25 | def testbench(): 26 | assert (yield encoding.JType(jal).immediate()) == extended_offset 27 | 28 | comb_sim(m, testbench) 29 | 30 | 31 | @pytest.mark.parametrize( 32 | "imm", 33 | [ 34 | 0b11110000111, 35 | 0b01010101010, 36 | 0b11111111111, 37 | 0b00010010010, 38 | ]) 39 | def test_itype_same_immediate_out_as_in(comb_sim, imm): 40 | m = nm.Module() 41 | addi = nm.Const( 42 | encoding.IType.encode( 43 | imm, 44 | 1, 45 | encoding.IntRegImmFunct.ADDI, 46 | 2, 47 | encoding.Opcode.OP_IMM), 48 | shape=32) 49 | extended_imm = int(f"{imm:012b}"[0]*20 + f"{imm:012b}", 2) 50 | assert (extended_imm & 0x7ff) == imm 51 | 52 | def testbench(): 53 | assert (yield encoding.IType(addi).immediate()) == extended_imm 54 | 55 | comb_sim(m, testbench) 56 | -------------------------------------------------------------------------------- /riscy_boi/alu.py: -------------------------------------------------------------------------------- 1 | """Arithmetic Logic Unit""" 2 | import enum 3 | 4 | import nmigen as nm 5 | 6 | 7 | class ALUOp(enum.IntEnum): 8 | """Operations for the ALU""" 9 | ADD = 0b000 10 | SUB = 0b001 11 | AND = 0b010 12 | OR = 0b011 # noqa: E221 13 | XOR = 0b100 14 | SLL = 0b101 15 | SRL = 0b110 16 | SRA = 0b111 17 | 18 | 19 | class ALU(nm.Elaboratable): 20 | """ 21 | Arithmetic Logic Unit 22 | 23 | * op (in): the opcode 24 | * a (in): the first operand 25 | * b (in): the second operand 26 | 27 | * o (out): the output 28 | """ 29 | 30 | def __init__(self, width): 31 | """ 32 | Initialiser 33 | 34 | Args: 35 | width (int): data width 36 | """ 37 | self.op = nm.Signal(ALUOp) 38 | self.a = nm.Signal(width) 39 | self.b = nm.Signal(width) 40 | self.o = nm.Signal(width) 41 | 42 | def elaborate(self, _): 43 | m = nm.Module() 44 | shamt_width = 5 45 | 46 | with m.Switch(self.op): 47 | with m.Case(ALUOp.ADD): 48 | m.d.comb += self.o.eq(self.a + self.b) 49 | with m.Case(ALUOp.SUB): 50 | m.d.comb += self.o.eq(self.a - self.b) 51 | with m.Case(ALUOp.AND): 52 | m.d.comb += self.o.eq(self.a & self.b) 53 | with m.Case(ALUOp.OR): 54 | m.d.comb += self.o.eq(self.a | self.b) 55 | with m.Case(ALUOp.XOR): 56 | m.d.comb += self.o.eq(self.a ^ self.b) 57 | with m.Case(ALUOp.SLL): 58 | m.d.comb += self.o.eq(self.b << self.a[:shamt_width]) 59 | with m.Case(ALUOp.SRL): 60 | m.d.comb += self.o.eq(self.b >> self.a[:shamt_width]) 61 | with m.Case(ALUOp.SRA): 62 | m.d.comb += self.o.eq(self.b.as_signed() >> 63 | self.a[:shamt_width]) 64 | 65 | return m 66 | -------------------------------------------------------------------------------- /tests/test_alu.py: -------------------------------------------------------------------------------- 1 | """ALU tests""" 2 | import nmigen.sim 3 | import pytest 4 | 5 | from riscy_boi import alu 6 | 7 | 8 | @pytest.mark.parametrize( 9 | "op, a, b, o", [ 10 | (alu.ALUOp.ADD, 1, 1, 2), 11 | (alu.ALUOp.ADD, 1, 2, 3), 12 | (alu.ALUOp.ADD, 2, 1, 3), 13 | (alu.ALUOp.ADD, 258, 203, 461), 14 | (alu.ALUOp.ADD, 5, 0, 5), 15 | (alu.ALUOp.ADD, 0, 5, 5), 16 | (alu.ALUOp.ADD, 2**32 - 1, 1, 0), 17 | 18 | (alu.ALUOp.SUB, 1, 1, 0), 19 | (alu.ALUOp.SUB, 4942, 0, 4942), 20 | (alu.ALUOp.SUB, 1, 2, 2**32 - 1), 21 | 22 | (alu.ALUOp.AND, 0b1111, 0b1111, 0b1111), 23 | (alu.ALUOp.AND, 0b1111, 0b0000, 0b0000), 24 | (alu.ALUOp.AND, 0b1010, 0b1010, 0b1010), 25 | 26 | (alu.ALUOp.OR, 0b1010, 0b0101, 0b1111), 27 | (alu.ALUOp.OR, 0b1111, 0b0000, 0b1111), 28 | (alu.ALUOp.OR, 0b0000, 0b0000, 0b0000), 29 | (alu.ALUOp.OR, 0b1001, 0b1001, 0b1001), 30 | 31 | (alu.ALUOp.XOR, 0b1001, 0b1001, 0b0000), 32 | (alu.ALUOp.XOR, 0b1010, 0b0101, 0b1111), 33 | (alu.ALUOp.XOR, 0b0000, 0b0000, 0b0000), 34 | 35 | (alu.ALUOp.SLL, 1, 0b1111, 0b11110), 36 | (alu.ALUOp.SLL, 3, 0b010101, 0b010101000), 37 | (alu.ALUOp.SLL, 1, 2**32 - 1, 2**32 - 2), 38 | 39 | (alu.ALUOp.SRL, 1, 0b1111, 0b0111), 40 | (alu.ALUOp.SRL, 5, 0b1111, 0), 41 | 42 | (alu.ALUOp.SRA, 1, 0b1111, 0b0111), 43 | (alu.ALUOp.SRA, 44 | 1, 45 | 0b10001111000011110000111100001111, 46 | 0b11000111100001111000011110000111)]) 47 | def test_alu(comb_sim, op, a, b, o): 48 | alu_inst = alu.ALU(32) 49 | 50 | def testbench(): 51 | yield alu_inst.op.eq(op) 52 | yield alu_inst.a.eq(a) 53 | yield alu_inst.b.eq(b) 54 | yield nmigen.sim.Settle() 55 | assert (yield alu_inst.o) == o 56 | 57 | comb_sim(alu_inst, testbench) 58 | -------------------------------------------------------------------------------- /tests/test_program_counter.py: -------------------------------------------------------------------------------- 1 | """Program Counter tests""" 2 | from riscy_boi import program_counter 3 | 4 | 5 | def test_program_counter_increment(sync_sim): 6 | pc = program_counter.ProgramCounter() 7 | 8 | def testbench(): 9 | yield pc.load.eq(0) 10 | yield 11 | curr = (yield pc.pc) 12 | assert curr == program_counter.INSTR_BYTES 13 | assert (yield pc.pc_inc) == curr + program_counter.INSTR_BYTES 14 | assert (yield pc.pc_next) == curr + program_counter.INSTR_BYTES 15 | 16 | sync_sim(pc, testbench) 17 | 18 | 19 | def test_program_counter_set(sync_sim): 20 | pc = program_counter.ProgramCounter() 21 | 22 | def testbench(): 23 | address = 0xdeadbeef 24 | yield pc.input_address.eq(address) 25 | yield pc.load.eq(1) 26 | yield 27 | 28 | curr = (yield pc.pc) 29 | assert curr == program_counter.INSTR_BYTES 30 | assert (yield pc.pc_inc) == curr + program_counter.INSTR_BYTES 31 | assert (yield pc.pc_next) == address 32 | 33 | yield pc.load.eq(0) 34 | yield 35 | 36 | assert (yield pc.pc) == address 37 | assert (yield pc.pc_inc) == address + program_counter.INSTR_BYTES 38 | assert (yield pc.pc_next) == address + program_counter.INSTR_BYTES 39 | 40 | sync_sim(pc, testbench) 41 | 42 | 43 | def test_program_counter_sequence(sync_sim): 44 | pc = program_counter.ProgramCounter() 45 | 46 | def testbench(): 47 | address = 0xdeadbeef 48 | yield pc.input_address.eq(address) 49 | yield pc.load.eq(1) 50 | yield 51 | assert (yield pc.pc) == program_counter.INSTR_BYTES 52 | assert (yield pc.pc_inc) == program_counter.INSTR_BYTES * 2 53 | assert (yield pc.pc_next) == address 54 | 55 | yield pc.load.eq(0) 56 | yield 57 | assert (yield pc.pc) == address 58 | assert (yield pc.pc_inc) == address + program_counter.INSTR_BYTES 59 | assert (yield pc.pc_next) == address + program_counter.INSTR_BYTES 60 | 61 | sync_sim(pc, testbench) 62 | -------------------------------------------------------------------------------- /riscy_boi/top.py: -------------------------------------------------------------------------------- 1 | """Top level hardware""" 2 | import nmigen as nm 3 | 4 | from . import cpu 5 | from . import encoding 6 | 7 | 8 | class Top(nm.Elaboratable): 9 | """Top level""" 10 | 11 | def elaborate(self, platform): 12 | m = nm.Module() 13 | 14 | cd_sync = nm.ClockDomain("sync") 15 | m.domains += cd_sync 16 | 17 | clk100 = platform.request("clk100") 18 | cd_fast = nm.ClockDomain("fast") 19 | m.domains += cd_fast 20 | m.d.comb += cd_fast.clk.eq(clk100.i) 21 | 22 | m.submodules.pll = nm.Instance( 23 | "SB_PLL40_CORE", 24 | p_FEEDBACK_PATH="SIMPLE", 25 | p_DIVR=3, 26 | p_DIVF=40, 27 | p_DIVQ=6, 28 | p_FILTER_RANGE=2, 29 | i_RESETB=1, 30 | i_BYPASS=0, 31 | i_REFERENCECLK=clk100.i, 32 | o_PLLOUTCORE=cd_sync.clk) 33 | 34 | reg = 2 35 | cpu_inst = m.submodules.cpu = cpu.CPU(debug_reg=reg) 36 | link_reg = 5 37 | program = [encoding.IType.encode( 38 | 1, 39 | reg, 40 | encoding.IntRegImmFunct.ADDI, 41 | reg, 42 | encoding.Opcode.OP_IMM), 43 | # jump back to the previous instruction for infinite loop 44 | encoding.JType.encode(0x1ffffc, link_reg)] 45 | 46 | imem = nm.Memory(width=32, depth=256, init=program) 47 | imem_rp = m.submodules.imem_rp = imem.read_port() 48 | m.d.comb += [ 49 | imem_rp.addr.eq(cpu_inst.imem_addr), 50 | cpu_inst.imem_data.eq(imem_rp.data), 51 | ] 52 | 53 | dmem = nm.Memory(width=32, depth=256) 54 | dmem_rp = m.submodules.dmem_rp = dmem.read_port( 55 | transparent=False, 56 | domain="fast") 57 | dmem_wp = m.submodules.dmem_wp = dmem.write_port(domain="fast") 58 | m.d.comb += [ 59 | dmem_rp.addr.eq(cpu_inst.dmem_r_addr), 60 | cpu_inst.dmem_r_data.eq(dmem_rp.data), 61 | dmem_wp.addr.eq(cpu_inst.dmem_w_addr), 62 | dmem_wp.data.eq(cpu_inst.dmem_w_data), 63 | ] 64 | 65 | colours = ["b", "g", "o", "r"] 66 | leds = nm.Cat(platform.request(f"led_{c}") for c in colours) 67 | m.d.sync += leds.eq(cpu_inst.debug_out[13:17]) 68 | 69 | return m 70 | -------------------------------------------------------------------------------- /riscy_boi/register_file.py: -------------------------------------------------------------------------------- 1 | """Register file""" 2 | import nmigen as nm 3 | 4 | 5 | class RegisterFile(nm.Elaboratable): 6 | """ 7 | Register file 8 | 9 | * read_select_1 (in): select which register to read at read_data_1 10 | * read_select_2 (in): select which register to read at read_data_2 11 | * read_data_1 (out): value of register selected by read_select_1 12 | * read_data_2 (out): value of register selected by read_select_2 13 | 14 | * write_enable (in): assert to trigger write to register file 15 | * write_select (in): select which register to write to 16 | * write_data (in): data to write to register selected by write_select 17 | """ 18 | 19 | def __init__(self, num_registers=32, register_width=32, debug_reg=2): 20 | self.num_registers = num_registers 21 | self.register_width = register_width 22 | 23 | self.read_select_1 = nm.Signal(range(self.num_registers)) 24 | self.read_select_2 = nm.Signal(range(self.num_registers)) 25 | self.read_data_1 = nm.Signal(self.register_width) 26 | self.read_data_2 = nm.Signal(self.register_width) 27 | 28 | self.write_enable = nm.Signal() 29 | self.write_select = nm.Signal(self.register_width) 30 | self.write_data = nm.Signal(self.register_width) 31 | 32 | self.debug_out = nm.Signal(self.register_width) 33 | self.debug_reg = debug_reg 34 | 35 | def elaborate(self, _): 36 | m = nm.Module() 37 | registers = nm.Memory( 38 | width=self.register_width, 39 | depth=self.num_registers) 40 | 41 | rp1 = m.submodules.rp1 = registers.read_port(domain="comb") 42 | rp2 = m.submodules.rp2 = registers.read_port(domain="comb") 43 | debug_rp = m.submodules.debug_rp = registers.read_port(domain="comb") 44 | wp = m.submodules.wp = registers.write_port(domain="sync") 45 | 46 | # The first register, x0, has a special function: Reading it always 47 | # returns 0 and writes to it are ignored. 48 | # https://github.com/riscv/riscv-asm-manual/blob/master/riscv-asm.md 49 | m.d.comb += wp.en.eq( 50 | nm.Mux(self.write_select == 0, 0, self.write_enable)) 51 | 52 | m.d.comb += [ 53 | rp1.addr.eq(self.read_select_1), 54 | rp2.addr.eq(self.read_select_2), 55 | wp.addr.eq(self.write_select), 56 | debug_rp.addr.eq(self.debug_reg), 57 | self.read_data_1.eq(rp1.data), 58 | self.read_data_2.eq(rp2.data), 59 | wp.data.eq(self.write_data), 60 | self.debug_out.eq(debug_rp.data), 61 | ] 62 | 63 | return m 64 | -------------------------------------------------------------------------------- /riscy_boi/cpu.py: -------------------------------------------------------------------------------- 1 | """The CPU""" 2 | import nmigen as nm 3 | 4 | from . import alu 5 | from . import data_memory 6 | from . import instruction_decoder 7 | from . import program_counter 8 | from . import register_file 9 | 10 | 11 | class CPU(nm.Elaboratable): 12 | """rv32i CPU""" 13 | 14 | def __init__(self, debug_reg=2): 15 | self.imem_addr = nm.Signal(32) 16 | self.imem_data = nm.Signal(32) 17 | 18 | self.dmem_r_addr = nm.Signal(32) 19 | self.dmem_r_data = nm.Signal(32) 20 | self.dmem_w_addr = nm.Signal(32) 21 | self.dmem_w_data = nm.Signal(32) 22 | 23 | self.debug_reg = debug_reg 24 | self.debug_out = nm.Signal(32) 25 | 26 | def elaborate(self, _): 27 | m = nm.Module() 28 | 29 | alu_inst = m.submodules.alu = alu.ALU(32) 30 | dmem = m.submodules.dmem = data_memory.DataMemory() 31 | idec = m.submodules.idec = instruction_decoder.InstructionDecoder() 32 | pc = m.submodules.pc = program_counter.ProgramCounter() 33 | rf = m.submodules.rf = register_file.RegisterFile( 34 | debug_reg=self.debug_reg) 35 | 36 | m.d.comb += [ 37 | rf.read_select_1.eq(idec.rf_read_select_1), 38 | rf.read_select_2.eq(idec.rf_read_select_2), 39 | rf.write_enable.eq(idec.rf_write_enable), 40 | rf.write_select.eq(idec.rf_write_select), 41 | 42 | alu_inst.a.eq(idec.alu_imm), 43 | alu_inst.op.eq(idec.alu_op), 44 | 45 | self.dmem_r_addr.eq(dmem.dmem_r_addr), 46 | dmem.byte_address.eq(alu_inst.o), 47 | dmem.signed.eq(idec.dmem_signed), 48 | dmem.address_mode.eq(idec.dmem_address_mode), 49 | dmem.dmem_r_data.eq(self.dmem_r_data), 50 | 51 | pc.load.eq(idec.pc_load), 52 | pc.input_address.eq(alu_inst.o), 53 | 54 | self.imem_addr.eq(pc.pc_next), 55 | idec.instr.eq(self.imem_data), 56 | 57 | self.debug_out.eq(rf.debug_out), 58 | ] 59 | 60 | with m.Switch(idec.rd_mux_op): 61 | with m.Case(instruction_decoder.RdValue.PC_INC): 62 | m.d.comb += rf.write_data.eq(pc.pc_inc) 63 | with m.Case(instruction_decoder.RdValue.ALU_OUTPUT): 64 | m.d.comb += rf.write_data.eq(alu_inst.o) 65 | with m.Case(instruction_decoder.RdValue.LOAD): 66 | m.d.comb += rf.write_data.eq(dmem.load_value) 67 | 68 | with m.Switch(idec.alu_mux_op): 69 | with m.Case(instruction_decoder.ALUInput.READ_DATA_1): 70 | m.d.comb += alu_inst.b.eq(rf.read_data_1) 71 | with m.Case(instruction_decoder.ALUInput.PC): 72 | m.d.comb += alu_inst.b.eq(pc.pc) 73 | 74 | return m 75 | -------------------------------------------------------------------------------- /tests/test_instruction_decoder.py: -------------------------------------------------------------------------------- 1 | """Instruction decoder tests""" 2 | import nmigen.sim 3 | 4 | from riscy_boi import alu 5 | from riscy_boi import data_memory 6 | from riscy_boi import encoding 7 | from riscy_boi import instruction_decoder 8 | 9 | 10 | def test_decoding_addi(comb_sim): 11 | idec = instruction_decoder.InstructionDecoder() 12 | 13 | def testbench(): 14 | immediate = 0b100011110000 15 | opcode = encoding.Opcode.OP_IMM 16 | funct = encoding.IntRegImmFunct.ADDI 17 | rs1 = 1 18 | rd = 2 19 | instruction = encoding.IType.encode(immediate, rs1, funct, rd, opcode) 20 | 21 | yield idec.instr.eq(instruction) 22 | yield nmigen.sim.Settle() 23 | assert (yield idec.pc_load) == 0 24 | assert (yield idec.rf_read_select_1) == rs1 25 | assert (yield idec.alu_op) == alu.ALUOp.ADD 26 | assert (yield idec.alu_imm) == 0b11111111111111111111100011110000 27 | assert (yield idec.alu_mux_op) == ( 28 | instruction_decoder.ALUInput.READ_DATA_1) 29 | assert (yield idec.rd_mux_op) == instruction_decoder.RdValue.ALU_OUTPUT 30 | 31 | assert (yield idec.rf_write_enable) == 1 32 | assert (yield idec.rf_write_select) == rd 33 | 34 | comb_sim(idec, testbench) 35 | 36 | 37 | def test_decoding_jal(comb_sim): 38 | idec = instruction_decoder.InstructionDecoder() 39 | 40 | def testbench(): 41 | immediate = int("1" * 20 + "0", base=2) 42 | rd = 5 43 | instruction = encoding.JType.encode(immediate, rd) 44 | 45 | yield idec.instr.eq(instruction) 46 | yield nmigen.sim.Settle() 47 | assert (yield idec.pc_load) == 1 48 | assert (yield idec.alu_op) == alu.ALUOp.ADD 49 | assert (yield idec.alu_imm) == int("1" * 31 + "0", base=2) 50 | 51 | comb_sim(idec, testbench) 52 | 53 | 54 | def test_decoding_load_word(comb_sim): 55 | idec = instruction_decoder.InstructionDecoder() 56 | 57 | def testbench(): 58 | immediate = 0b100011110000 59 | opcode = encoding.Opcode.LOAD 60 | funct = encoding.LoadFunct.LW 61 | rs1 = 1 62 | rd = 2 63 | instruction = encoding.IType.encode(immediate, rs1, funct, rd, opcode) 64 | 65 | yield idec.instr.eq(instruction) 66 | yield nmigen.sim.Settle() 67 | assert (yield idec.pc_load) == 0 68 | assert (yield idec.rf_read_select_1) == rs1 69 | assert (yield idec.alu_op) == alu.ALUOp.ADD 70 | assert (yield idec.alu_imm) == 0b11111111111111111111100011110000 71 | assert (yield idec.alu_mux_op) == ( 72 | instruction_decoder.ALUInput.READ_DATA_1) 73 | assert (yield idec.rd_mux_op) == instruction_decoder.RdValue.LOAD 74 | 75 | assert (yield idec.rf_write_enable) == 1 76 | assert (yield idec.rf_write_select) == rd 77 | 78 | assert (yield idec.dmem_signed) == 1 79 | assert (yield idec.dmem_address_mode) == data_memory.AddressMode.WORD 80 | 81 | comb_sim(idec, testbench) 82 | -------------------------------------------------------------------------------- /riscyboi.xml: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /riscy_boi/instruction_decoder.py: -------------------------------------------------------------------------------- 1 | """Instruction decoder""" 2 | import enum 3 | 4 | 5 | import nmigen as nm 6 | 7 | from . import alu 8 | from . import data_memory 9 | from . import encoding 10 | 11 | 12 | class RdValue(enum.IntEnum): 13 | """MUX opcodes for the value put in the destination register""" 14 | ALU_OUTPUT = 0 15 | PC_INC = 1 16 | LOAD = 2 17 | 18 | 19 | class ALUInput(enum.IntEnum): 20 | """MUX opcodes for the value inputted to the ALU""" 21 | READ_DATA_1 = 0 22 | PC = 1 23 | 24 | 25 | class InstructionDecoder(nm.Elaboratable): 26 | """ 27 | Instruction decoder 28 | 29 | * instr (in): instruction to decode 30 | 31 | * pc_load (out): load signal to program counter 32 | 33 | * alu_op (out): ALU operation to perform 34 | * alu_imm (out): the value to input to the ALU, constructed from the 35 | immediate value in the instruction 36 | * alu_mux_op (out): multiplexor operator defining what value is the first 37 | input to the ALU 38 | 39 | * rf_write_enable (out): register file's write_enable input 40 | * rf_write_select (out): register files' write_select input 41 | * rf_read_select_1 (out): register file's read_select_1 input 42 | * rf_read_select_2 (out): register file's read_select_2 input 43 | * rd_mux_op (out): multiplexor operation defining what value is written to 44 | the destination register 45 | 46 | * dmem_address_mode (out): address mode for data memory reads 47 | * dmem_signed (out): whether input to data memory should be sign-extended 48 | """ 49 | 50 | def __init__(self, num_registers=32): 51 | self.instr_width = 32 52 | self.instr = nm.Signal(self.instr_width) 53 | 54 | self.pc_load = nm.Signal() 55 | self.alu_op = nm.Signal() 56 | self.alu_imm = nm.Signal(self.instr_width) 57 | self.rf_write_enable = nm.Signal() 58 | self.rf_write_select = nm.Signal(range(num_registers)) 59 | self.rf_read_select_1 = nm.Signal(range(num_registers)) 60 | self.rf_read_select_2 = nm.Signal(range(num_registers)) 61 | self.rd_mux_op = nm.Signal(RdValue) 62 | self.alu_mux_op = nm.Signal(ALUInput) 63 | self.dmem_address_mode = nm.Signal(data_memory.AddressMode) 64 | self.dmem_signed = nm.Signal() 65 | 66 | def elaborate(self, _): 67 | m = nm.Module() 68 | 69 | opcode = encoding.opcode(self.instr) 70 | m.d.comb += self.rf_write_select.eq(encoding.rd(self.instr)) 71 | m.d.comb += self.rf_read_select_1.eq(encoding.rs1(self.instr)) 72 | m.d.comb += self.rf_read_select_2.eq(encoding.rs2(self.instr)) 73 | 74 | with m.Switch(opcode): 75 | with m.Case(encoding.Opcode.OP_IMM): 76 | m.d.comb += self.rf_write_enable.eq(1) 77 | 78 | itype = encoding.IType(self.instr) 79 | with m.Switch(itype.funct()): 80 | m.d.comb += [ 81 | self.pc_load.eq(0), 82 | self.rd_mux_op.eq(RdValue.ALU_OUTPUT), 83 | self.alu_mux_op.eq(ALUInput.READ_DATA_1), 84 | ] 85 | 86 | with m.Case(encoding.IntRegImmFunct.ADDI): 87 | m.d.comb += [ 88 | self.alu_op.eq(alu.ALUOp.ADD), 89 | self.alu_imm.eq(itype.immediate()), 90 | ] 91 | 92 | with m.Case(encoding.IntRegImmFunct.XORI): 93 | m.d.comb += [ 94 | self.alu_op.eq(alu.ALUOp.XOR), 95 | self.alu_imm.eq(itype.immediate()), 96 | ] 97 | 98 | with m.Case(encoding.IntRegImmFunct.ORI): 99 | m.d.comb += [ 100 | self.alu_op.eq(alu.ALUOp.OR), 101 | self.alu_imm.eq(itype.immediate()), 102 | ] 103 | 104 | with m.Case(encoding.IntRegImmFunct.ORI): 105 | m.d.comb += [ 106 | self.alu_op.eq(alu.ALUOp.OR), 107 | self.alu_imm.eq(itype.immediate()), 108 | ] 109 | 110 | with m.Case(encoding.IntRegImmFunct.ANDI): 111 | m.d.comb += [ 112 | self.alu_op.eq(alu.ALUOp.AND), 113 | self.alu_imm.eq(itype.immediate()), 114 | ] 115 | 116 | with m.Case(encoding.IntRegImmFunct.SLLI): 117 | m.d.comb += [ 118 | self.alu_op.eq(alu.ALUOp.SLL), 119 | self.alu_imm.eq(itype.shift_amount()), 120 | ] 121 | 122 | with m.Case(encoding.IntRegImmFunct.SRLI_OR_SRAI): 123 | m.d.comb += self.alu_imm.eq(itype.shift_amount()) 124 | with m.Switch(itype.right_shift_type()): 125 | with m.Case(encoding.RightShiftType.SRLI): 126 | m.d.comb += self.alu_op.eq(alu.ALUOp.SRL) 127 | with m.Case(encoding.RightShiftType.SRAI): 128 | m.d.comb += self.alu_op.eq(alu.ALUOp.SRA) 129 | 130 | with m.Case(encoding.Opcode.JAL): 131 | m.d.comb += self.rf_write_enable.eq(1) 132 | 133 | jtype = encoding.JType(self.instr) 134 | m.d.comb += [ 135 | self.pc_load.eq(1), 136 | self.alu_op.eq(alu.ALUOp.ADD), 137 | self.alu_imm.eq(jtype.immediate()), 138 | self.rd_mux_op.eq(RdValue.PC_INC), 139 | self.alu_mux_op.eq(ALUInput.PC), 140 | ] 141 | 142 | with m.Case(encoding.Opcode.LOAD): 143 | m.d.comb += [ 144 | self.rf_write_enable.eq(1), 145 | self.pc_load.eq(0), 146 | self.alu_op.eq(alu.ALUOp.ADD), 147 | self.alu_imm.eq(itype.immediate()), 148 | self.rd_mux_op.eq(RdValue.LOAD), 149 | self.alu_mux_op.eq(ALUInput.READ_DATA_1), 150 | ] 151 | 152 | itype = encoding.IType(self.instr) 153 | funct = itype.funct() 154 | with m.If(funct == encoding.LoadFunct.LW): 155 | m.d.comb += self.dmem_address_mode.eq( 156 | data_memory.AddressMode.WORD) 157 | with m.Elif((funct == encoding.LoadFunct.LH) | 158 | (funct == encoding.LoadFunct.LHU)): 159 | m.d.comb += self.dmem_address_mode.eq( 160 | data_memory.AddressMode.HALF) 161 | with m.Elif((funct == encoding.LoadFunct.LB) | 162 | (funct == encoding.LoadFunct.LBU)): 163 | m.d.comb += self.dmem_address_mode.eq( 164 | data_memory.AddressMode.BYTE) 165 | 166 | m.d.comb += self.dmem_signed.eq(funct[2] == 0) 167 | 168 | return m 169 | -------------------------------------------------------------------------------- /riscy_boi/encoding.py: -------------------------------------------------------------------------------- 1 | """Instruction encoding and decoding""" 2 | import collections 3 | import enum 4 | import functools 5 | 6 | import nmigen as nm 7 | 8 | OPCODE_START = 0 9 | OPCODE_END = 7 10 | RD_START = 7 11 | RD_END = 12 12 | RS1_START = 15 13 | RS1_END = 20 14 | RS2_START = 20 15 | RS2_END = 25 16 | 17 | 18 | def sext(immediate, desired_length=32): 19 | """ 20 | Sign extension 21 | 22 | Args: 23 | immediate (nm.Value): the immediate to be sign extended 24 | desired_length (int): the desired length of the extended output 25 | 26 | Returns: 27 | nm.hdl.ast.Cat: the sign-extended immediate 28 | """ 29 | return nm.Cat( 30 | immediate, 31 | nm.Repl(immediate[-1], desired_length - len(immediate))) 32 | 33 | 34 | def opcode(instruction): 35 | """ 36 | Extract the opcode from an instruction. 37 | 38 | Note the opcode position is the same for all instructions. 39 | 40 | Args: 41 | instruction (nm.Signal): the instruction to extract the opcode from 42 | 43 | Returns: 44 | nm.hdl.ast.Slice: the opcode 45 | """ 46 | return instruction[OPCODE_START:OPCODE_END] 47 | 48 | 49 | def rd(instruction): 50 | """ 51 | Extract the destination register from an instruction. 52 | 53 | Note the destination register field is in the same position for all 54 | instructions that encode it, but not all instructions do (e.g. S-type 55 | instructions). 56 | 57 | Args: 58 | instruction (nm.Signal): the instruction to extract the destination 59 | register from 60 | 61 | Returns: 62 | nm.hdl.ast.Slice: the destination register 63 | """ 64 | return instruction[RD_START:RD_END] 65 | 66 | 67 | def rs1(instruction): 68 | """ 69 | Extract the source register 1 field from an instruction. 70 | 71 | Note this field is in the same position for all instructions that encode 72 | it, but not all instructions do (e.g. U-type instructions). 73 | 74 | Args: 75 | instruction (nm.Signal): the instruction to extract source register 1 76 | from 77 | 78 | Returns: 79 | nm.hdl.ast.Slice: the source register 1 field 80 | """ 81 | return instruction[RS1_START:RS1_END] 82 | 83 | 84 | def rs2(instruction): 85 | """ 86 | Extract the source register 2 field from an instruction. 87 | 88 | Note this field is in the same position for all instructions that encode 89 | it, but not all instructions do (e.g. I-type instructions). 90 | 91 | Args: 92 | instruction (nm.Signal): the instruction to extract source register 2 93 | from 94 | 95 | Returns: 96 | nm.hdl.ast.Slice: the source register 2 field 97 | """ 98 | return instruction[RS2_START:RS2_END] 99 | 100 | 101 | class Opcode(enum.IntEnum): 102 | """Instruction opcodes, see page 104 Risc V Spec v2.2""" 103 | OP_IMM = 0b0010011 # noqa: E221 104 | LUI = 0b0110111 # noqa: E221 105 | AUIPC = 0b0010111 # noqa: E221 106 | OP = 0b0110011 # noqa: E221 107 | JAL = 0b1101111 # noqa: E221 108 | JALR = 0b1100111 # noqa: E221 109 | BRANCH = 0b1100011 # noqa: E221 110 | LOAD = 0b0000011 # noqa: E221 111 | STORE = 0b0100011 # noqa: E221 112 | MISC_MEM = 0b0001111 # noqa: E221 113 | SYSTEM = 0b1110011 # noqa: E221 114 | 115 | 116 | class IntRegImmFunct(enum.IntEnum): 117 | """Funct field values for integer register-immediate instructions""" 118 | ADDI = 0b000 # noqa: E221 119 | SLTI = 0b010 # noqa: E221 120 | SLTIU = 0b011 # noqa: E221 121 | XORI = 0b100 # noqa: E221 122 | ORI = 0b110 # noqa: E221 123 | ANDI = 0b111 # noqa: E221 124 | SLLI = 0b001 # noqa: E221 125 | SRLI_OR_SRAI = 0b101 # noqa: E221 126 | 127 | 128 | class LoadFunct(enum.IntEnum): 129 | """Funct field values for load instructions""" 130 | LB = 0b000 # noqa: E221 131 | LH = 0b001 # noqa: E221 132 | LW = 0b010 # noqa: E221 133 | LBU = 0b100 134 | LHU = 0b101 135 | 136 | 137 | class RightShiftType(enum.IntEnum): 138 | """Shift type for distinguishing between SRLI and SRAI instructions""" 139 | SRLI = 0b0000000 140 | SRAI = 0b0100000 141 | 142 | 143 | class IType: 144 | """I-type instruction format""" 145 | IMM_START = 20 146 | IMM_END = 32 147 | FUNCT_START = 12 148 | FUNCT_END = 15 149 | 150 | def __init__(self, instruction): 151 | """ 152 | Initialiser 153 | 154 | Args: 155 | instruction (nm.Value): the instruction to decode 156 | """ 157 | self.instr = instruction 158 | 159 | @classmethod 160 | def encode(cls, imm_val, rs1_val, funct_val, rd_val, opcode_val): 161 | """ 162 | Assembler method to encode an instruction 163 | 164 | Args: 165 | imm_val (int): the immediate value 166 | rs1_val (int): the source register 1 value 167 | funct_val (IntRegImmFunct): the function field 168 | rd_val (int): the destination register value 169 | opcode_val (Opcode): the opcode 170 | 171 | Returns: 172 | int: the encoded instruction 173 | """ 174 | return ((imm_val << cls.IMM_START) | 175 | (rs1_val << RS1_START) | 176 | (funct_val << cls.FUNCT_START) | 177 | (rd_val << RD_START) | 178 | (opcode_val << OPCODE_START)) 179 | 180 | def immediate(self): 181 | """ 182 | Construct the sign-extended immediate from the instruction 183 | 184 | Returns: 185 | nm.hdl.ast.Cat: the decoded immediate 186 | """ 187 | return sext(self.instr[self.IMM_START:self.IMM_END]) 188 | 189 | def funct(self): 190 | return self.instr[self.FUNCT_START:self.FUNCT_END] 191 | 192 | def shift_amount(self): 193 | """For SLLI, SRLI and SRAI instructions, get the shift amount""" 194 | return self.instr[self.IMM_START:self.IMM_START + 5] 195 | 196 | def right_shift_type(self): 197 | """For SRLI and SRAI instructions, get the shift type""" 198 | return self.instr[self.IMM_START + 5:self.IMM_END] 199 | 200 | 201 | class JType: 202 | """J-type instruction format""" 203 | ImmediateField = collections.namedtuple( 204 | "ImmediateField", 205 | ["instr_start", "instr_end", "offset_start", "offset_end"], 206 | ) 207 | 208 | IMM_FIELDS = ( 209 | ImmediateField( 210 | instr_start=12, 211 | instr_end=20, 212 | offset_start=12, 213 | offset_end=20), 214 | ImmediateField( 215 | instr_start=20, 216 | instr_end=21, 217 | offset_start=11, 218 | offset_end=12), 219 | ImmediateField( 220 | instr_start=21, 221 | instr_end=31, 222 | offset_start=1, 223 | offset_end=11), 224 | ImmediateField( 225 | instr_start=31, 226 | instr_end=32, 227 | offset_start=20, 228 | offset_end=21)) 229 | 230 | OPCODE = Opcode.JAL # The only opcode in rv32i with J-type format 231 | 232 | def __init__(self, instruction): 233 | """ 234 | Initialiser 235 | 236 | Args: 237 | instruction (nm.Value): the instruction to decode 238 | """ 239 | self.instr = instruction 240 | 241 | @classmethod 242 | def encode(cls, offset, rd_val): 243 | """ 244 | Assembler method to encode an instruction 245 | 246 | Args: 247 | offset (int): the jump offset 248 | rd_val (int): the destination register 249 | 250 | Returns: 251 | int: the encoded instruction 252 | """ 253 | 254 | def shuffle_imm(result, field): 255 | width = field.offset_end - field.offset_start 256 | mask = int("1" * width, 2) << field.offset_start 257 | 258 | return result | ( 259 | (offset & mask) << 260 | (field.instr_start - field.offset_start)) 261 | 262 | sorted_imm_fields = sorted( 263 | cls.IMM_FIELDS, 264 | key=lambda field: field.instr_start) 265 | 266 | return (functools.reduce(shuffle_imm, sorted_imm_fields, 0) | 267 | (rd_val << RD_START) | 268 | (cls.OPCODE << OPCODE_START)) 269 | 270 | def immediate(self): 271 | """ 272 | Construct the sign-extended immediate from the instruction 273 | 274 | Returns: 275 | nm.hdl.ast.Cat: the decoded immediate 276 | """ 277 | sorted_imm_fields = sorted( 278 | self.IMM_FIELDS, 279 | key=lambda field: field.offset_start) 280 | 281 | to_unshuffle = [self.instr[field.instr_start:field.instr_end] 282 | for field in sorted_imm_fields] 283 | 284 | unshuffled = nm.Cat(0, *to_unshuffle) 285 | return sext(unshuffled) 286 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "appdirs" 3 | version = "1.4.4" 4 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 5 | category = "main" 6 | optional = false 7 | python-versions = "*" 8 | 9 | [[package]] 10 | name = "astroid" 11 | version = "2.4.2" 12 | description = "An abstract syntax tree for Python with inference support." 13 | category = "dev" 14 | optional = false 15 | python-versions = ">=3.5" 16 | 17 | [package.dependencies] 18 | lazy-object-proxy = ">=1.4.0,<1.5.0" 19 | six = ">=1.12,<2.0" 20 | wrapt = ">=1.11,<2.0" 21 | 22 | [[package]] 23 | name = "atomicwrites" 24 | version = "1.4.0" 25 | description = "Atomic file writes." 26 | category = "dev" 27 | optional = false 28 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 29 | 30 | [[package]] 31 | name = "attrs" 32 | version = "20.3.0" 33 | description = "Classes Without Boilerplate" 34 | category = "dev" 35 | optional = false 36 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 37 | 38 | [package.extras] 39 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] 40 | docs = ["furo", "sphinx", "zope.interface"] 41 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 42 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] 43 | 44 | [[package]] 45 | name = "colorama" 46 | version = "0.4.4" 47 | description = "Cross-platform colored terminal text." 48 | category = "dev" 49 | optional = false 50 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 51 | 52 | [[package]] 53 | name = "importlib-resources" 54 | version = "5.1.0" 55 | description = "Read resources from Python packages" 56 | category = "main" 57 | optional = false 58 | python-versions = ">=3.6" 59 | 60 | [package.extras] 61 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 62 | testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "pytest-black (>=0.3.7)", "pytest-mypy"] 63 | 64 | [[package]] 65 | name = "iniconfig" 66 | version = "1.1.1" 67 | description = "iniconfig: brain-dead simple config-ini parsing" 68 | category = "dev" 69 | optional = false 70 | python-versions = "*" 71 | 72 | [[package]] 73 | name = "isort" 74 | version = "5.7.0" 75 | description = "A Python utility / library to sort Python imports." 76 | category = "dev" 77 | optional = false 78 | python-versions = ">=3.6,<4.0" 79 | 80 | [package.extras] 81 | pipfile_deprecated_finder = ["pipreqs", "requirementslib"] 82 | requirements_deprecated_finder = ["pipreqs", "pip-api"] 83 | colors = ["colorama (>=0.4.3,<0.5.0)"] 84 | 85 | [[package]] 86 | name = "jinja2" 87 | version = "2.11.3" 88 | description = "A very fast and expressive template engine." 89 | category = "main" 90 | optional = false 91 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 92 | 93 | [package.dependencies] 94 | MarkupSafe = ">=0.23" 95 | 96 | [package.extras] 97 | i18n = ["Babel (>=0.8)"] 98 | 99 | [[package]] 100 | name = "lazy-object-proxy" 101 | version = "1.4.3" 102 | description = "A fast and thorough lazy object proxy." 103 | category = "dev" 104 | optional = false 105 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 106 | 107 | [[package]] 108 | name = "markupsafe" 109 | version = "1.1.1" 110 | description = "Safely add untrusted strings to HTML/XML markup." 111 | category = "main" 112 | optional = false 113 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" 114 | 115 | [[package]] 116 | name = "mccabe" 117 | version = "0.6.1" 118 | description = "McCabe checker, plugin for flake8" 119 | category = "dev" 120 | optional = false 121 | python-versions = "*" 122 | 123 | [[package]] 124 | name = "nmigen" 125 | version = "0.3.dev243+gf7c2b94" 126 | description = "Python toolbox for building complex digital hardware" 127 | category = "main" 128 | optional = false 129 | python-versions = "~=3.6" 130 | develop = false 131 | 132 | [package.dependencies] 133 | importlib-resources = {version = "*", markers = "python_version < \"3.9\""} 134 | Jinja2 = ">=2.11,<3.0" 135 | pyvcd = ">=0.2.2,<0.3.0" 136 | 137 | [package.extras] 138 | builtin-yosys = ["nmigen-yosys (>=0.9.post3527)"] 139 | remote-build = ["paramiko (>=2.7,<3.0)"] 140 | 141 | [package.source] 142 | type = "git" 143 | url = "https://github.com/nmigen/nmigen" 144 | reference = "master" 145 | resolved_reference = "f7c2b9419f9de450be76a0e9cf681931295df65f" 146 | 147 | [[package]] 148 | name = "nmigen-boards" 149 | version = "0.1.dev174+g6575dc4" 150 | description = "Board and connector definitions for nMigen" 151 | category = "main" 152 | optional = false 153 | python-versions = "*" 154 | develop = false 155 | 156 | [package.dependencies] 157 | nmigen = ">=0.2,<0.4" 158 | 159 | [package.source] 160 | type = "git" 161 | url = "https://github.com/nmigen/nmigen-boards" 162 | reference = "master" 163 | resolved_reference = "6575dc4ccfb9142d697bbfae3ebe06d8e07b04f5" 164 | 165 | [[package]] 166 | name = "packaging" 167 | version = "20.9" 168 | description = "Core utilities for Python packages" 169 | category = "dev" 170 | optional = false 171 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 172 | 173 | [package.dependencies] 174 | pyparsing = ">=2.0.2" 175 | 176 | [[package]] 177 | name = "pluggy" 178 | version = "0.13.1" 179 | description = "plugin and hook calling mechanisms for python" 180 | category = "dev" 181 | optional = false 182 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 183 | 184 | [package.extras] 185 | dev = ["pre-commit", "tox"] 186 | 187 | [[package]] 188 | name = "py" 189 | version = "1.10.0" 190 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 191 | category = "dev" 192 | optional = false 193 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 194 | 195 | [[package]] 196 | name = "pylint" 197 | version = "2.6.0" 198 | description = "python code static checker" 199 | category = "dev" 200 | optional = false 201 | python-versions = ">=3.5.*" 202 | 203 | [package.dependencies] 204 | astroid = ">=2.4.0,<=2.5" 205 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 206 | isort = ">=4.2.5,<6" 207 | mccabe = ">=0.6,<0.7" 208 | toml = ">=0.7.1" 209 | 210 | [[package]] 211 | name = "pyparsing" 212 | version = "2.4.7" 213 | description = "Python parsing module" 214 | category = "dev" 215 | optional = false 216 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 217 | 218 | [[package]] 219 | name = "pytest" 220 | version = "6.2.2" 221 | description = "pytest: simple powerful testing with Python" 222 | category = "dev" 223 | optional = false 224 | python-versions = ">=3.6" 225 | 226 | [package.dependencies] 227 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 228 | attrs = ">=19.2.0" 229 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 230 | iniconfig = "*" 231 | packaging = "*" 232 | pluggy = ">=0.12,<1.0.0a1" 233 | py = ">=1.8.2" 234 | toml = "*" 235 | 236 | [package.extras] 237 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 238 | 239 | [[package]] 240 | name = "pyvcd" 241 | version = "0.2.4" 242 | description = "Python VCD file support" 243 | category = "main" 244 | optional = false 245 | python-versions = ">=3.6" 246 | 247 | [[package]] 248 | name = "six" 249 | version = "1.15.0" 250 | description = "Python 2 and 3 compatibility utilities" 251 | category = "dev" 252 | optional = false 253 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 254 | 255 | [[package]] 256 | name = "toml" 257 | version = "0.10.2" 258 | description = "Python Library for Tom's Obvious, Minimal Language" 259 | category = "dev" 260 | optional = false 261 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 262 | 263 | [[package]] 264 | name = "wasmtime" 265 | version = "0.21.0" 266 | description = "A WebAssembly runtime powered by Wasmtime" 267 | category = "main" 268 | optional = false 269 | python-versions = ">=3.6" 270 | 271 | [package.extras] 272 | testing = ["coverage", "pytest", "pycparser", "pytest-flake8", "pytest-mypy"] 273 | 274 | [[package]] 275 | name = "wrapt" 276 | version = "1.12.1" 277 | description = "Module for decorators, wrappers and monkey patching." 278 | category = "dev" 279 | optional = false 280 | python-versions = "*" 281 | 282 | [[package]] 283 | name = "yowasp-nextpnr-ice40" 284 | version = "0.0.post2867.dev68" 285 | description = "nextpnr-ice40 FPGA place and route tool" 286 | category = "main" 287 | optional = false 288 | python-versions = "~=3.5" 289 | 290 | [package.dependencies] 291 | appdirs = ">=1.4,<2.0" 292 | importlib-resources = {version = "*", markers = "python_version < \"3.9\""} 293 | wasmtime = ">=0.20,<0.22" 294 | 295 | [[package]] 296 | name = "yowasp-nextpnr-ice40-8k" 297 | version = "0.0.post2867.dev68" 298 | description = "nextpnr-ice40 FPGA place and route tool" 299 | category = "main" 300 | optional = false 301 | python-versions = "~=3.5" 302 | 303 | [package.dependencies] 304 | yowasp-nextpnr-ice40 = "0.0.post2867.dev68" 305 | 306 | [[package]] 307 | name = "yowasp-yosys" 308 | version = "0.9.post5191.dev103" 309 | description = "Yosys Open SYnthesis Suite" 310 | category = "main" 311 | optional = false 312 | python-versions = "~=3.5" 313 | 314 | [package.dependencies] 315 | appdirs = ">=1.4,<2.0" 316 | importlib-resources = {version = "*", markers = "python_version < \"3.9\""} 317 | wasmtime = ">=0.20,<0.22" 318 | 319 | [metadata] 320 | lock-version = "1.1" 321 | python-versions = "^3.8" 322 | content-hash = "8cd7d41a7c21e566761db51f3590ca5f0580e55bb73a81b388a5eb69113e5fe3" 323 | 324 | [metadata.files] 325 | appdirs = [ 326 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 327 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 328 | ] 329 | astroid = [ 330 | {file = "astroid-2.4.2-py3-none-any.whl", hash = "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386"}, 331 | {file = "astroid-2.4.2.tar.gz", hash = "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703"}, 332 | ] 333 | atomicwrites = [ 334 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 335 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 336 | ] 337 | attrs = [ 338 | {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, 339 | {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, 340 | ] 341 | colorama = [ 342 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 343 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 344 | ] 345 | importlib-resources = [ 346 | {file = "importlib_resources-5.1.0-py3-none-any.whl", hash = "sha256:885b8eae589179f661c909d699a546cf10d83692553e34dca1bf5eb06f7f6217"}, 347 | {file = "importlib_resources-5.1.0.tar.gz", hash = "sha256:bfdad047bce441405a49cf8eb48ddce5e56c696e185f59147a8b79e75e9e6380"}, 348 | ] 349 | iniconfig = [ 350 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 351 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 352 | ] 353 | isort = [ 354 | {file = "isort-5.7.0-py3-none-any.whl", hash = "sha256:fff4f0c04e1825522ce6949973e83110a6e907750cd92d128b0d14aaaadbffdc"}, 355 | {file = "isort-5.7.0.tar.gz", hash = "sha256:c729845434366216d320e936b8ad6f9d681aab72dc7cbc2d51bedc3582f3ad1e"}, 356 | ] 357 | jinja2 = [ 358 | {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, 359 | {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, 360 | ] 361 | lazy-object-proxy = [ 362 | {file = "lazy-object-proxy-1.4.3.tar.gz", hash = "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"}, 363 | {file = "lazy_object_proxy-1.4.3-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442"}, 364 | {file = "lazy_object_proxy-1.4.3-cp27-cp27m-win32.whl", hash = "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4"}, 365 | {file = "lazy_object_proxy-1.4.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a"}, 366 | {file = "lazy_object_proxy-1.4.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d"}, 367 | {file = "lazy_object_proxy-1.4.3-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a"}, 368 | {file = "lazy_object_proxy-1.4.3-cp34-cp34m-win32.whl", hash = "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e"}, 369 | {file = "lazy_object_proxy-1.4.3-cp34-cp34m-win_amd64.whl", hash = "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357"}, 370 | {file = "lazy_object_proxy-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50"}, 371 | {file = "lazy_object_proxy-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db"}, 372 | {file = "lazy_object_proxy-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449"}, 373 | {file = "lazy_object_proxy-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156"}, 374 | {file = "lazy_object_proxy-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531"}, 375 | {file = "lazy_object_proxy-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb"}, 376 | {file = "lazy_object_proxy-1.4.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08"}, 377 | {file = "lazy_object_proxy-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383"}, 378 | {file = "lazy_object_proxy-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142"}, 379 | {file = "lazy_object_proxy-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea"}, 380 | {file = "lazy_object_proxy-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62"}, 381 | {file = "lazy_object_proxy-1.4.3-cp38-cp38-win32.whl", hash = "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd"}, 382 | {file = "lazy_object_proxy-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239"}, 383 | ] 384 | markupsafe = [ 385 | {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, 386 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, 387 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, 388 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, 389 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, 390 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, 391 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, 392 | {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, 393 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, 394 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, 395 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, 396 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, 397 | {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, 398 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, 399 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, 400 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, 401 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, 402 | {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, 403 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, 404 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, 405 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, 406 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, 407 | {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, 408 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, 409 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, 410 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, 411 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, 412 | {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, 413 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, 414 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, 415 | {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, 416 | {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, 417 | {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, 418 | ] 419 | mccabe = [ 420 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 421 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 422 | ] 423 | nmigen = [] 424 | nmigen-boards = [] 425 | packaging = [ 426 | {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, 427 | {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, 428 | ] 429 | pluggy = [ 430 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 431 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 432 | ] 433 | py = [ 434 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, 435 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, 436 | ] 437 | pylint = [ 438 | {file = "pylint-2.6.0-py3-none-any.whl", hash = "sha256:bfe68f020f8a0fece830a22dd4d5dddb4ecc6137db04face4c3420a46a52239f"}, 439 | {file = "pylint-2.6.0.tar.gz", hash = "sha256:bb4a908c9dadbc3aac18860550e870f58e1a02c9f2c204fdf5693d73be061210"}, 440 | ] 441 | pyparsing = [ 442 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 443 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 444 | ] 445 | pytest = [ 446 | {file = "pytest-6.2.2-py3-none-any.whl", hash = "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"}, 447 | {file = "pytest-6.2.2.tar.gz", hash = "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9"}, 448 | ] 449 | pyvcd = [ 450 | {file = "pyvcd-0.2.4-py2.py3-none-any.whl", hash = "sha256:c40b0e586a74cddaf82e6989f0168ae7f9b4f182a9a106a0da9df0d11a9c6b3b"}, 451 | {file = "pyvcd-0.2.4.tar.gz", hash = "sha256:071e51a8362908ad5a2a12f078185639b98b20b653a56f01679de169d0fa425d"}, 452 | ] 453 | six = [ 454 | {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, 455 | {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, 456 | ] 457 | toml = [ 458 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 459 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 460 | ] 461 | wasmtime = [ 462 | {file = "wasmtime-0.21.0-py3-none-any.whl", hash = "sha256:62eab89e7f60ab919eabf97526638be41ff158941a5a7dc2275079360cf04262"}, 463 | {file = "wasmtime-0.21.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:2bc335325b5f145c33045ba41e1372d5716e574cca60b07bf13600ad49617ee1"}, 464 | {file = "wasmtime-0.21.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:5fb7f6c1444cbeb14d28f29d18d50cb4220820b7d3a82b2a05c020b6e55b700e"}, 465 | {file = "wasmtime-0.21.0-py3-none-win_amd64.whl", hash = "sha256:d50a1486fc3542afdd1ccb109efc6d72fa2900834e535cbdfd6ebcf575e9a708"}, 466 | ] 467 | wrapt = [ 468 | {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"}, 469 | ] 470 | yowasp-nextpnr-ice40 = [ 471 | {file = "yowasp_nextpnr_ice40-0.0.post2867.dev68-py3-none-any.whl", hash = "sha256:80e140b00a9ba00a53a8cf31620937d18f2a0dcc8d8633ac4743d791728e6731"}, 472 | ] 473 | yowasp-nextpnr-ice40-8k = [ 474 | {file = "yowasp_nextpnr_ice40_8k-0.0.post2867.dev68-py3-none-any.whl", hash = "sha256:c05b14226ddc213e3cfae9e27b251aa97d7fe27648c759598d004262b623e716"}, 475 | ] 476 | yowasp-yosys = [ 477 | {file = "yowasp_yosys-0.9.post5191.dev103-py3-none-any.whl", hash = "sha256:51e438e1017e9d1a5ddce280b080f1da07164fb737b75a02604417101a4e1267"}, 478 | ] 479 | --------------------------------------------------------------------------------