diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..e4c431a --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,11 @@ +## Description +Describe the changes made and why they were made. + +## Related Issue(s) +Link or list the issue(s) this PR addresses (e.g., #123). + +## Type +- [ ] Bug fix +- [ ] New feature +- [ ] Enhancement +- [ ] Documentation update diff --git a/FIFO_MANAGEMENT.txt b/FIFO_MANAGEMENT.txt new file mode 100644 index 0000000..eebdc47 --- /dev/null +++ b/FIFO_MANAGEMENT.txt @@ -0,0 +1,34 @@ + +/***FIFO MANAGEMENT**/ +// Internal FIFO memory, pointers, and counter +localparam ADDR_WIDTH = $clog2(FIFO_DEPTH); +reg [7:0] fifo_mem [0:FIFO_DEPTH-1]; +reg [ADDR_WIDTH-1:0] waddr_ptr; +reg [ADDR_WIDTH-1:0] raddr_ptr; +reg [ADDR_WIDTH:0] count; +wire fifo_full = (count == FIFO_DEPTH); +wire fifo_empty = (count == 0); + +// FIFO Push (write) and Pop (read) logic +always @(posedge clk or posedge rst) begin + if (rst) begin + waddr_ptr <= 0; + raddr_ptr <= 0; + count <= 0; + end + else begin + // Push data to FIFO (CPU write) + if (wvalid && awvalid && !fifo_full) begin + fifo_mem[waddr_ptr] <= wdata; // Write CPU data into FIFO + waddr_ptr <= waddr_ptr + 1; // Increment write pointer + count <= count + 1; // Increment FIFO count + end + + // Pop data from FIFO (CPU read) + if (rready && arvalid && !fifo_empty) begin + RBR <= fifo_mem[raddr_ptr]; // Load data to read register + raddr_ptr <= raddr_ptr + 1; // Increment read pointer + count <= count - 1; // Decrement FIFO count + end + end +end \ No newline at end of file diff --git a/docs/Gaming CPU Documentation & Style Guide.pdf b/docs/Gaming CPU Documentation & Style Guide.pdf new file mode 100644 index 0000000..ffa0c49 Binary files /dev/null and b/docs/Gaming CPU Documentation & Style Guide.pdf differ diff --git a/docs/ip-briefs/CPU_Core_Brief_(v1.0).md b/docs/ip-briefs/CPU_Core_Brief_(v1.0).md new file mode 100644 index 0000000..0924895 --- /dev/null +++ b/docs/ip-briefs/CPU_Core_Brief_(v1.0).md @@ -0,0 +1,85 @@ +**Author:** Sebastian Candelaria +**RTL:** *rtl/cpu/core/rv32_core.sv* + +--- +#### **Purpose & Role** +- The CPU Core is the main processing unit of the SoC, in charge of running all instructions from a given program and storing/loading memory data as needed + - 5-stage pipeline in RV32IMA (ISA-format) with M/S-mode (privilege levels) + - Built for a single-core system +--- +#### **Parameters** +- Parameters in Verilog/SystemVerilog are similar to constants and #define directives seen in C/C++ that are reused many times across a module to avoid magic numbers and promote reusability + +| Name | Default | Description | +| --------- | --------------- | ------------------------------------------------------------------------------------------------------ | +| HAS_M | 1 | Enable RV32M mul/div instructions | +| HAS_A | 1 | Enable LR/SC atomic instructions | + +--- +#### **Interfaces (Ports)** +- Any external input or output signal that will be used by the IF stage +- AXI, IRQ, and debug signal sizes must be defined +- Subject to change + +| Signal Name | Direction | Width | Description | +| ------------------------ | --------- | ----- | ---------------------------------------------------------------------- | +| **Global Signals** | | | | +| clk_i | In | 1 | Main clock input | +| rst_ni | In | 1 | Active-low reset | +| | | | | +| **PLIC/CLINT Interface** | | | | +| irq_ext_i | In | 1 | Interrupt request from external device(s) (controller, keyboard, etc.) | +| irq_timer_i | In | 1 | Interrupt request from timer | +| irq_soft_i | In | N/A | | +| | | | | +| **AXI Interface** | | | | +| i_axi_* | I/O | N/A | Instruction AXI: Communicates with instruction cache | +| d_axi_* | I/O | N/A | Data AXI: Communicates with data cache | +| | | | | +| **Debug Interface** | | | | +| dbg_* | I/O | N/A | JTAG Signals for checking CPU behavior (Test Clock, Test Data, etc.) | +| | | | | + +--- +#### **Reset/Init** +- When performing a reset, the signal rst_ni becomes LOW (0), the CPU enters its default program ready state + - The program counter (PC) is set to the BootROM's beginning instruction + - The control-status registers (CSRs) are set to initial values, such as # of cycles elapsed = 0 + - All data in the pipeline, including intermediate registers and the register file, is flushed (cleared) +--- +#### **Behavior & Timing** +- Instruction Execution Order: The pipeline executes the instructions in-order, or in the sequence given by the program + - Data forwarding, consisting of signals backtracking to Decode (ID) stage, is used to handle dependencies between instructions (EX: Instruction 2 needs data provided by Instruction 1) + - Hazard/flush control is needed for when hazards become issues, such as an instruction +- Control Components: The core needs to minimize cycle losses from control flow changes (i.e. jumping instructions) + - Branch Target Buffer (BTB, optional): Table of instruction addresses recently visited, indicating whether they are more likely to be visited again upon a branch (if condition is true, jump to instruction) instruction + - Trap Vector Exception: Upon encountering a trap/error (EX: Division by zero), the CPU should immediately execute a subroutine at a vector (function's address) given by a table +--- +#### **Programming Model** +- CLINT: *specs/registers/clint.yaml* +- PLIC: *specs/registers/plic.yaml* +--- +#### **Errors/IRQs** +- Trap Vector Handling +- Exception Causes +- MCAUSE/MEPC Behavior +--- +#### **Performance Targets** +- CPI: Average number of clock cycles taken to execute an instruction (>= 1) + - Goal: <= 2 cycles per instruction +- MIPS: # of millions of instructions per second + - Dependent on CPI and clock frequency + - Goal: >= 126.5 MIPS +--- +#### **Dependencies** +- AXI: *rtl/bus/axi/axi_crossbar.sv* + - AXI connection to fetch instructions and data from memory hierarchy (ideally cache, main memory upon miss) +- PLIC/CLINT: *rtl/irq/clint.sv*, *rtl/irq/plic.sv* + - PLIC/CLINT send interrupts, signals for CPU to temporarily switch to execution of a function's instructions in the program, based on controller inputs, timer values, etc. +- MMU: *rtl/cpu/mmu/sv32_mmu.sv* + - Controls where/how instructions and data are loaded and stored +- RV32 Package: *rtl/cpu/pkg/rv32_pkg.sv* +--- +#### **Verification Links** +- *sim/uvm/test_core.sv* + - SystemVerilog simulation environment to verify CPU core \ No newline at end of file diff --git a/docs/ip-briefs/Decode_Stage_Brief_(v1.0).md b/docs/ip-briefs/Decode_Stage_Brief_(v1.0).md new file mode 100644 index 0000000..1646ee7 --- /dev/null +++ b/docs/ip-briefs/Decode_Stage_Brief_(v1.0).md @@ -0,0 +1,67 @@ +**Author:** Sebastian Candelaria +**RTL:** *rtl/cpu/core/decode.sv* + +--- +#### **Purpose & Role** +- The Instruction Decode (ID) stage is the 2nd stage in a standard 5-stage RISC-V pipeline. At each clock cycle, the decode stage translates an instruction into data values and control signals to direct the rest of the pipeline + - For any instructions involving register-reading, the decode stage will retrieve the data from the register file (RF) + - Any instruction operation codes (opcodes) or encoded immediate/constant values (imm values) are translated into control signals and real, usable values + +--- +#### **Parameters** +- Parameters in Verilog/SystemVerilog are similar to constants and #define directives seen in C/C++ that are reused many times across a module to avoid magic numbers and promote reusability +- Note: Inherited from `rv32_pkg.sv` and `rv32_core` top-level. See CPU Core Brief for HAS_M/HAS_A configuration. + +| Name | Default | Description | +|-------------|---------|------------------------------------------------------| +| HAS_M | 1 | Enable RV32M instruction decoding | +| HAS_A | 1 | Enable RV32A instruction decoding | +| DATA_WIDTH | 32 | Operand width | + +--- +#### **Interfaces (Ports)** +- Any external input or output signal that will be used by the ID stage +- Subject to change + +| Signal Name | Direction | Width | Description | +| ------------------------ | --------- | ----- | ---------------------------------------------------------------------- | +| **Global Signals** | | | | +| clk_i | In | 1 | Main clock input | +| rst_ni | In | 1 | Active-low asynchronous reset | +| | | | | +| **Semi-Global Signals** | | | | +| inst_i | In | 32 | Fetched instruction | +| rf_a_i | In | 32 | Register A operand (data) from RF | +| rf_b_i | In | 32 | Register B operand (data) from RF | +| rf_a_o | Out | 32 | Register A operand to execute (EX) stage | +| rf_b_o | Out | 32 | Register B operand to execute (EX) stage | +| ctrl_*_o | Out | N/A | Control signals to execute | +| | | | | + +--- +#### **Reset/Init** +- When performing a reset, the signal rst_ni becomes LOW (0), the CPU enters its default state + - All registers are flushed + - Control signals enter default values +--- +#### **Behavior & Timing** +- One-Cycle Decode: Entire decode process should occur within one cycle + - Control Signals: Decode stage must produce control signals for following stages based on given instruction, likely through a control unit (CU) or FSM if needed +- Hazard/Flush: Upon encountering a pipeline hazard, such as branch calculation, decode should flush execute/memory stage + - Illegal instructions should raise a trap(error) cause, leading into trap subroutine execution + - Data Forwarding: Read output values from execute (EX) and memory (MM) stages and, if meant for register needed to read from, substitute values for next instruction's register data output. If data cannot be forwarded yet (EX: waiting on memory cache), stall until it can +--- +#### **Errors/IRQs** +- Illegal Instruction Trap +--- +#### **Performance Targets** +- 1-cycle execution +--- +#### **Dependencies** +- Execute (EX): *rtl/bus/core/execute.sv* +- Memory (MM): *rtl/bus/core/mem_stage.sv* +- RV32 Package: *rtl/cpu/pkg/rv32_pkg.sv* +--- +#### **Verification Links** +- *sim/uvm/test_decode.sv* + - SystemVerilog simulation environment to verify decode stage \ No newline at end of file diff --git a/docs/ip-briefs/Execute_Stage_Brief_(v1.0).md b/docs/ip-briefs/Execute_Stage_Brief_(v1.0).md new file mode 100644 index 0000000..126c149 --- /dev/null +++ b/docs/ip-briefs/Execute_Stage_Brief_(v1.0).md @@ -0,0 +1,71 @@ +**Author:** Sebastian Candelaria +**RTL:** *rtl/cpu/core/execute.sv* + +--- +#### **Purpose & Role** +- The Execute (EX) stage is the 3rd stage in a standard 5-stage RISC-V pipeline. At each clock cycle, the execute stage performs arithmetic and logic operations on operand values. + - Conditional statement evaluation (thus redirects) occurs here + - Contains Arithmetic & Logic Unit (ALU) for basic operations (add, sub, shift, etc.) + multiplier and divider + +--- +#### **Parameters** +- Parameters in Verilog/SystemVerilog are similar to constants and #define directives seen in C/C++ that are reused many times across a module to avoid magic numbers and promote reusability +- Note: Inherited from `rv32_pkg.sv` and `rv32_core` top-level. See CPU Core Brief for HAS_M/HAS_A configuration. + +| Name | Default | Description | +|-------------|---------|------------------------------------------------------| +| MUL_CYCLES | 3 | Multiplier latency in cycles | +| DIV_CYCLES | 5 | Divider latency in cycles | +| HAS_M | 1 | Enable mul/div hardware | + +--- +#### **Interfaces (Ports)** +- Any external input or output signal that will be used by the EX stage +- Subject to change + +| Signal Name | Direction | Width | Description | +| ------------------------ | --------- | ----- | ---------------------------------------------------------------------- | +| **Global Signals** | | | | +| clk_i | In | 1 | Main clock input | +| rst_ni | In | 1 | Active-low asynchronous reset | +| | | | | +| **Local Signals** | | | | +| ctrl_*_i | In | N/A | Control signals to execute | +| op_a_i | In | 32 | Register A operand (data) from RF | +| op_b_i | In | 32 | Register B operand (data) from RF | +| alu_res_o | Out | 32 | ALU result from processing operands | +| branch_taken_o | Out | 1 | Control signal for whether branch should be taken | +| branch_target_o | Out | N/A | Address for branch to redirect program counter (PC) to | +| | | | | + +--- +#### **Reset/Init** +- When performing a reset, the signal rst_ni becomes LOW (0), the CPU enters its default state + - All registers are flushed + - Control signals enter default values +--- +#### **Behavior & Timing** +- One-Cycle ALU: All ALU outputs should be available within one cycle of processing request +- Multi-Cycle Mul/Div: Multiplier and divider should take multiple cycles, requiring stalls until processing is finished + - Stalls should occur under busy/ready internal handshake signals (aka while working on operation, stall) +- Unique-Case FSM: A finite state machine (FSM), a sequential logic model for paths of outputs depending on inputs, should control output signals depending on specific operationctrl_*_os, such as initiating stalls upon multiply/divide and releasing at operation finish +- Redirect Signal: Upon determining branch output in one cycle, EX should send the result directly back to the fetch stage (IF) for the redirect to occur on the next cycle +- Memory Operations: All memory control signals and operations, unless needing execute, should bypass to MM stage on next cycle + - FENCE.I Side-Effects: Since FENCE.I replaces old instructions with new ones, this instruction should bypass to MM stage +--- +#### **Errors/IRQs** +- Division By Zero +- Overflow Handling +--- +#### **Performance Targets** +- CPU should stall for at most five cycles due to EX + - 1-cycle ALU, <= 5-cycle Mul/Div +--- +#### **Dependencies** +- Memory (MM): *rtl/bus/core/mem_stage.sv* +- Writeback (WB): *rtl/bus/core/writeback.sv* +- RV32 Package: *rtl/cpu/pkg/rv32_pkg.sv* +--- +#### **Verification Links** +- *sim/uvm/test_execute.sv* + - SystemVerilog simulation environment to verify execute stage \ No newline at end of file diff --git a/docs/ip-briefs/Fetch_Stage_Brief_(v1.0).md b/docs/ip-briefs/Fetch_Stage_Brief_(v1.0).md new file mode 100644 index 0000000..52c58c1 --- /dev/null +++ b/docs/ip-briefs/Fetch_Stage_Brief_(v1.0).md @@ -0,0 +1,70 @@ +**Author:** Sebastian Candelaria +**RTL:** *rtl/cpu/core/fetch.sv* + +--- +#### **Purpose & Role** +- The Instruction Fetch (IF) stage is the 1st stage in a standard 5-stage RISC-V pipeline. At each clock cycle, the fetch stage attempts to request an instruction from the cache via the AXI and sends that instruction down the pipeline. + - Program Counter (PC): Register holding memory address of next instruction to execute. Increments by 4 or changes to a new address given by a jump/branch instruction after each cycle. + - IF will stall if next PC address is not known yet or has not received an instruction yet. +--- +#### **Parameters** +- Parameters in Verilog/SystemVerilog are similar to constants and #define directives seen in C/C++ that are reused many times across a module to avoid magic numbers and promote reusability +- Note: Inherited from `rv32_pkg.sv` and `rv32_core` top-level. See CPU Core Brief for HAS_M/HAS_A configuration. + +| Name | Default | Description | +|-------------|----------------|-----------------------------------------------| +| RESET_ADDR | 32'h0000_0000 | PC value on reset (BootROM entry) | +| BTB_ENTRIES | 16 | Branch target buffer size (0 to disable) | +| ADDR_WIDTH | 32 | Address bus width | + +--- +#### **Interfaces (Ports)** +- Any external input or output signal that will be used by the IF stage +- AXI burst parameters must be defined later +- Subject to change + +| Signal Name | Direction | Width | Description | +| ------------------------ | --------- | ----- | ---------------------------------------------------------------------- | +| **Global Signals** | | | | +| clk_i | In | 1 | Main clock input | +| rst_ni | In | 1 | Active-low asynchronous reset | +| | | | | +| **Semi-Global Signals** | | | | +| pc_q | In | 32 | Program counter signal | +| ic_req_o | Out | N/A | Instruction cache request + valid | +| ic_rsp_i | In | N/A | Instruction cache response + valid | +| redir_i | In | 1 | Indicator to redirect PC to branch/jump address | +| inst_o | Out | 32 | Fetched instruction to decode (ID) stage | +| | | | | + +--- +#### **Reset/Init** +- When performing a reset, the signal rst_ni becomes LOW (0), the CPU enters its default state + - All registers are flushed +--- +#### **Behavior & Timing** +- Sequential Fetch: Since the pipeline executes the instructions in-order, fetch always grabs instructions in sequence from first to last address (except as directed by jump/branch instructions) + - Branch Target Buffer (BTB, optional) is used when the branch address is known but not the conditional outcome, predicting whether a branch should be taken (aka the branch address should be the next PC). + - Upon requesting an instruction from cache, it should be ready to send in the next clock cycle (therefore, the current instruction is sent out while the next instruction is requested) + - Upon miss/redirect, a one-cycle stall must happen to prevent an incorrect instruction from being sent + - Upon FENCE.I, an instruction that flushes the current instructions and inserts new ones, the instruction cache uses an invalidate hook to tell the fetch stage to wait until the new instructions are ready +--- +#### **Programming Model** +- N/A +--- +#### **Errors/IRQs** +- Instruction Access Fault +- Page Fault +--- +#### **Performance Targets** +- 1-cycle hit path: Expect instruction within one cycle always (moreso AXI and cache issue) +--- +#### **Dependencies** +- AXI: *rtl/bus/axi/axi_crossbar.sv* + - AXI connection to fetch instructions and data from memory hierarchy (ideally cache, main memory upon miss) +- Decode (ID): *rtl/bus/core/decode.sv* +- RV32 Package: *rtl/cpu/pkg/rv32_pkg.sv* +--- +#### **Verification Links** +- *sim/uvm/test_fetch.sv* + - SystemVerilog simulation environment to verify fetch stage \ No newline at end of file diff --git a/docs/ip-briefs/Instruction Cache (I$) Brief (v1.0).md b/docs/ip-briefs/Instruction Cache (I$) Brief (v1.0).md new file mode 100644 index 0000000..b674ec5 --- /dev/null +++ b/docs/ip-briefs/Instruction Cache (I$) Brief (v1.0).md @@ -0,0 +1,131 @@ +**Author:** Trevor Cannon +**RTL:** *rtl/mem/cache/icache.sv* + +--- +**Purpose & Role** +- Instruction Cache (I$) is a N-way set-associative cache that loads instructions from main memory through AXI port which are then sent to the Fetch (IF) stage of CPU pipeline + - N-way set associative: Cache is comprised of many "sets" where each set has N number of "ways" to store data. Each way in a set contains one "line" of data. + - Set-associative means that any incoming data can be stored at any way within the set, providing flexibility of storage + - Number of "ways" (N) can be decided on later, will likely be 2 or 4 way +--- +**Parameters** +- Parameters in Verilog/SystemVerilog are similar to constants and #define directives seen in C/C++ that are reused many times across a module to avoid magic numbers and promote reusability +- Parameters are subject to change but this is a general idea: + +| Name | Default | Description | +| --------- | --------------- | ------------------------------------------------------------------------------------------------------ | +| LINE_SIZE | project default | Size of cache line in bytes | +| WAYS | project default | Ways of associativity per set (number of lines per set) | +| IMEM_SIZE | project default | Total size of I$ | +| SETS | project default | Number of sets in cache, defined based formula: ($\frac{Total\,Cache\,Size}{Ways\,\times Line\,Size}$) | + +--- +**Interfaces (Ports)** +- Any input or output signal that will be used in the operation of the I$ +- AXI burst parameters must be defined later +- Subject to change + +| Signal Name | Direction | Width | Description | +| ----------------------- | --------- | ----- | ---------------------------------------------------------------------- | +| | | | | +| **Global Signals** | | | | +| clk_i | In | 1 | Main clock input | +| rst_ni | In | 1 | Active-low reset | +| | | | | +| **CPU Interface** | | | | +| cpu_req_valid_i | In | 1 | CPU requesting an instruction fetch | +| cpu_addr_i | In | 32 | CPU virtual address for requested instruction | +| icache_req_ready_o | Out | 1 | I$ ready to receive fetch requests | +| icache_resp_valid_o | Out | 1 | I$ returning a valid instruction | +| icache_resp_instr_o | Out | 32 | Instruction located at CPU's requested virtual address | +| cpu_resp_ready_i | In | 1 | CPU ready to accept instruction | +| icache_flush_i | In | 1 | Invalidate I$ on FENCE.I instruction | +| | | | | +| **AXI Interface** | | | | +| axi_mem_ar_o | Out | 32 | AXI Address Read (AR) - Physical address to be read from main memory | +| axi_ar_valid_o | Out | 1 | AXI Handshake - I$ sending valid address to main memory | +| axi_ar_ready_i | In | 1 | AXI Handshake - Main memory ready to accept address | +| axi_mem_r_i | In | 128 | AXI Read Data (R) - Returns data from requested address in main memory | +| axi_r_valid_i | In | 1 | AXI Handshake - Main memory data sent is valid | +| axi_r_ready_o | Out | 1 | AXI Handshake - I$ ready to accept data from main memory | +| | | | | +| **MMU/TLB Interface** | | | | +| icache_tlb_req_valid_o | Out | 1 | I$ is sending a valid virtual address to the TLB | +| icache_tlb_va_o | Out | 32 | Virtual address to be translated | +| tlb_req_ready_i | In | 1 | TLB ready for translation | +| tlb_resp_valid_i | In | 1 | TLB response is valid | +| tlb_resp_pa_i | In | 32 | TLB's translated physical address | +| icache_tlb_resp_ready_o | Out | 1 | I$ ready to accept TLB response | + +--- +**Reset/Init** +- When performing a reset, the signal rst_ni becomes LOW, and every cache lines valid bit is set LOW + - This signals an invalid line which will cause new data to be fetched from main memory on every CPU request + - This is done to put the cache in a known starting state +--- +**Behavior & Timing** +- Cache Hit: + - Defined as a requested instruction being found successfully in I$ and returned to CPU for execution + - Each address the CPU requests contains a Tag, Index, and Offset + - Index is used to map to the specific set in cache + - Tag is used to check if the data at that address is already present in cache + - Offset is the specific byte in the line corresponding to that address + - Since the CPU provides virtual addresses that must be converted to physical addresses, the MMU/TLB must be used to translate these addresses + - We will implement a Virtually-Indexed, Physically Tagged system (VIPT) where only the tag of the address will need to be translated in order to confirm a match + - This will greatly speedup operation as the cache does not need to wait on the TLB returning the full physical address and works in parallel with the TLB lookup + - VIPT works as follows: + - Once receiving a virtual address from the CPU, the cache will immediately send the address to the TLB to translate into a physical address + - At the same time this occurs, the Index of the virtual address is used to select the corresponding set and read all tags and data from all lines in that set + - When the TLB returns the physical address back to the cache, we can use the translated Tag to compare against all the tags from the set selected earlier + - If there is a match amongst the tags, it is considered a hit + - Data can then be sent immediately to the CPU since we have already read the entire line + offset earlier +- Cache Miss: + - A miss will occur if after checking the tag against all lines in that set we have no matches + - The CPU's IF stage must then be stalled until the correct line can be fetched from main memory (across AXI bus) using the physical address from TLB + - The fetched line is then stored according to our replacement policy and the pipeline is resumed +- Replacement Policy: + - When a cache miss occurs and the set is full, a line must be evicted from the cache + - Choosing which line to evict falls onto he replacement policy + - This can be defined later, common policies are Random or Least Recently Used (LRU) +--- +**Performance Targets** +- Hit Rate: + - Percentage of cache accesses that result in the correct data being found in cache + - 95%+ hit rate on successfully finding instructions in I$ + - Hit rate (and miss rate) can be improved upon by choosing well-suited replacement policy when needing to store new data in cache +- Hit Latency + - Time taken to return instruction to CPU on cache hit + - Aiming for 1-2 cycles +- Miss Rate + - Percentage of cache accesses that result in the data not being found, requiring main memory access + - Want <5% miss rate +- Miss Penalty: + - Additional time needed when cache miss occurs + - Miss penalty is heavily determined by AXI/main memory speeds + - Access time low as possible, but still could be tens to hundreds of clock cycles + - Minimizing miss latency is the goal +--- +**Dependencies** +- AXI: *rtl/bus/axi/axi_crossbar.sv* + - AXI connection for multiple master-slave agents + - I$ serves is master, main memory is slave + - The AXI data width and burst length will be defined the AXI crossbar itself + - Must match with project-wide specifications +- CPU (IF stage): *rtl/cpu/core/fetch.sv* + - I$ receives instruction requests from CPU fetch stage, returns instructions on hit, retrieves from main memory on miss +- MMU/TLB: *rtl/cpu/mmu/sv32_mmu.sv* + - MMU maps virtual addresses from the kernel to physical hardware addresses + - TLB (Translation Lookaside Buffer) stores recently used virtual-hardware address mappings to speed up memory accesses + - I$ and D$ must must make use of address translations from MMU/TLB as CPU will always give virtual addresses that must be mapped back into physical addresses +--- +**Verification Links** +- *sim/cocotb/test_icache.py* + - Cocotb simulation environment to verify instruction cache +- ~~*verification/cache/wb_wa_sequences.sv*~~ + - ~~Testing write-back and write-allocate features of D$~~ + - **I$ is read only, write policies not used** +- *verification/cache/self_mod_code_fencei.S* + - Testing the FENCE.I instruction where I$ is considered invalid due to self-modifying code + - FENCE.I is used when new code is written to memory that may conflict with instructions found in I$ or CPU fetch pipeline + - Must invalidate cache and force re-fetching of instructions to prevent use of stale or incorrect instructions + - Used by OS/kernel to flush I$ and prevent VIPT conflicts \ No newline at end of file diff --git a/docs/ip-briefs/Writeback_Stage_Brief_(v1.0).md b/docs/ip-briefs/Writeback_Stage_Brief_(v1.0).md new file mode 100644 index 0000000..557e391 --- /dev/null +++ b/docs/ip-briefs/Writeback_Stage_Brief_(v1.0).md @@ -0,0 +1,62 @@ +**Author:** Sebastian Candelaria +**RTL:** *rtl/cpu/core/writeback.sv* + +--- +#### **Purpose & Role** +- The Writeback (WB) stage is the 5th stage in a standard 5-stage RISC-V pipeline. At each clock cycle, if given data to send, WB sends the data directly data to the register file (RF) and adjusts the control-status registers (CSRs) as needed + - Any exceptions found must take priority and be committed (noted in CSRs, handled by flushing pipeline and resetting) + +--- +#### **Parameters** +- Parameters in Verilog/SystemVerilog are similar to constants and #define directives seen in C/C++ that are reused many times across a module to avoid magic numbers and promote reusability +- Note: Inherited from `rv32_pkg.sv` and `rv32_core` top-level. See CPU Core Brief for HAS_M/HAS_A configuration. + +| Name | Default | Description | +|---------------|---------|-------------------------------------------------- | +| DATA_WIDTH | 32 | Writeback data width | +| RF_ADDR_WIDTH | 5 | Register file address width (32 regs) | + +--- +#### **Interfaces (Ports)** +- Any external input or output signal that will be used by the MM stage (subject to change) + +| Signal Name | Direction | Width | Description | +| ------------------------ | --------- | ----- | ---------------------------------------------------------------------- | +| **Global Signals** | | | | +| clk_i | In | 1 | Main clock input | +| rst_ni | In | 1 | Active-low asynchronous reset | +| | | | | +| **Local Signals** | | | | +| rd_addr_i | In | 5 | Destination register address to send data to | +| rd_data_i | In | 32 | Data for destination register | +| rd_we_o | Out | 1 | Control signal for whether register file (RF) should be written | +| csr_* | I/O | N/A | Control-status registers read/write side-effects | +| | | | | + +--- +#### **Reset/Init** +- When performing a reset, the signal rst_ni becomes LOW (0), the CPU enters its default state + - All registers are flushed + - Control signals enter default values +--- +#### **Behavior & Timing** +- 1-Cycle Commit: All values should be written back by next cycle +- CSR/exceptions are routed to trap logic in core + - Stalls should occur under busy/ready internal handshake signals (aka while working on operation, stall) +- Unique-Case FSM: A finite state machine (FSM), a sequential logic model for paths of outputs depending on inputs, should control output +--- +#### **Errors/IRQs** +- Exception Commit Behavior +- Trap Delegation +--- +#### **Performance Targets** +- Each data or exception committed in 1 cycle +--- +#### **Dependencies** +- Memory (MM): *rtl/bus/core/mem_stage.sv* +- Execute (EX): *rtl/bus/core/execute.sv* +- RV32 Package: *rtl/cpu/pkg/rv32_pkg.sv* +--- +#### **Verification Links** +- *sim/uvm/test_writeback.sv* + - SystemVerilog simulation environment to verify writeback stage \ No newline at end of file diff --git a/docs/ip-briefs/axi_crossbar.md b/docs/ip-briefs/axi_crossbar.md new file mode 100644 index 0000000..559da11 --- /dev/null +++ b/docs/ip-briefs/axi_crossbar.md @@ -0,0 +1,65 @@ +### **axi_crossbar — Module Brief (v0.2)** +|**Owner:** Sebastian Candelaria and Bus System Team | **RTL:** rtl/bus/axi/axi_crossbar.sv | +| :-|-: | +--- +### **Purpose & Role** +Central AXI4 interconnect between managers and subordinates. Performs address decode, per-subordinate channel arbitration, and ID-based routing so multiple transactions can be outstanding concurrently. Common widths/IDs are defined in **rtl/bus/interconnect_pkg.sv.** + +--- + +### **System Context (Managers/Subordinates)** +- **Managers:** I$ port **axi_icache_port.sv**, D$ port **axi_dcache_port.sv**, DMA **axi_dma.sv**, and the MMU Page Table Walker **rtl/cpu/mmu/ptw.sv.** +- **Subordinates:** DDR via **rtl/mem/ddr/mig_axi_wrap.sv**, peripheral MMIO via **rtl/bus/axi/axi_to_axi_lite.sv** into **rtl/subsys/periph_axi_shell.sv.** +- **CDC:** When a clock crossing is required, channels use **axi_async_fifo.sv.** +*** +### **Interfaces (Ports)** +--- + +| Signal/Bundle | Dir | Width | Description | +|---|:---:|:---:|---| +| `s_axi_*[N_M]` | In | — | Subordinate-side inputs from managers (I\$, D\$, DMA, PTW). | +| `m_axi_*[N_S]` | Out | — | manager-side outputs to subordinates (DDR, AXI-Lite bridge, etc.). | +| `clk_i`, `rst_ni` | In | 1 | Fabric clock and active-low reset. | +--- + +### Parameters & Configuration +- Number of managers/subordinates, ID width, and data/address widths are set via **interconnect_pkg.sv.** +- Address windows correspond to the SoC memory map (DDR and MMIO ranges). +--- + +### Errors/IRQs + +- **IRQs:** None as the crossbar does not raise any interrupts. The interrupts would come from the peripherals. +- **Errors:** Conveyed only though AXI response codes and the crossbar should either pass through a subordinate's error or generate one itself for decode/feature violations. +--- + +### Performance Targets + +- **Clock:** **TBD** MHz (ASIC), **TBD** MHz (FPGA) +- **Throughput:** **1 beat/cycle** once granted (R & W) +- **Latency (xbar-only):** addr → grant ≤ 2 cycles; read addr → first data ≤ 3 cycles beyond subordinate; last-W → B ≤ 3 cycles +- **Arbitration:** **Round-robin;** starvation ≤ **N_M grants** +- **Bursts:** **INCR**, max len **TBD** +- **Outstanding:** per-manager **TBD R/TBD W**; backpressure only +- **CDC:** each crossing adds **+TBD cycles;** throughput unchanged +- **Reset:** READY may assert within ≤ **TBD cycles** after `rst_ni` deassert +--- + +### Behavior & Timing + +- Per-subordinate arbitration and decode; maintains AXI ready/valid ordering on all channels (AW/W/B/AR/R). +- Supports multiple outstanding transactions using `AXI ID` tagging; responses are routed back by ID. +- Designed for the system clock domain; CDC handled externally via **axi_async_fifo.sv** where required. +--- + +### Dependencies +- **Clocks/Reset:** clk_i, rst_ni (system domain; sync release). +- **Upstream managers (initiate AXI):** I$ port, D$ port, SMA engine, Page Table Walker. +- **Downstream subordinates (serve AXI):** DDR controller, AXI → AXI-Lite bridge into Peripheral Shell. +- **Configuration source:** address windows, ID/Data/Addr widths defined in the interconnect package. +- **CDC:** any clock crossings are handled *outside* the crossbar via AXI async FIFOs on the affected ports. +--- + +### Verification Links +- **AXI memory model testbench:** sim/common/tb_axi_mem.sv +- **System integration (exercises crossbar paths):** sim/cocotb/test_sd_spi.py, sim/cocotb/test_video_scanout.py, sim/cocotb/test_audio_i2s.py, sim/cocotb/test_mmu_sv32.py diff --git a/docs/ip-briefs/clint_module_brief_v0.2.md b/docs/ip-briefs/clint_module_brief_v0.2.md new file mode 100644 index 0000000..ed1fa22 --- /dev/null +++ b/docs/ip-briefs/clint_module_brief_v0.2.md @@ -0,0 +1,68 @@ +# CLINT - Module Brief (v0.3) + +**Owner:** Gavin Wiese +**RTL:** rtl/irq/clint.sv + +### Purpose & Role +The Core Local Interruptor (CLINT) is placed near the CPU core, as interrupts are sent directly to the core via memory. The CLINT provides two types of interrupts: **timer-based** (`timer_irq_o`) and **software-based** (`soft_irq_o`). It ensures that events from the timer or software are recognized promptly by the CPU. + +### Parameters + +- Timer Width: 64 bits (`mtime` and `mtimecmp` registers) +- Software interrupt width: 1 bit (`msip`) +- Software interrupt output width: 1 bit (`soft_irq_o`) +- Timer interrupt output width: 1 bit (`timer_irq_o`) + +### Interfaces (Ports) + +| **Signal** | **Dir** | **Width** | **Description** | +|----------------|---------|-----------|----------------------------------| +| mtime_o | out | 64 | Current timer value | +| timer_irq_o | out | 1 | Timer interrupt output | +| soft_irq_o | out | 1 | Software interrupt output | +| clk_i | in | 1 | System clock | +| rst_ni | in | 1 | Active-low reset | +| msip_we | in | 1 | Write enable for `msip` register | +| msip_wdata | in | 1 | Data to write to `msip` | +| mtimecmp_we | in | 1 | Write enable for `mtimecmp` register | +| mtimecmp_wdata | in | 64 | Data to write to `mtimecmp` | + +### Reset/Init + +An active-low reset (`rst_ni`) is used for the CLINT. When reset is asserted (`rst_ni = 0`), all internal registers—`mtime`, `mtimecmp`, and `msip`—are cleared to 0, and output signals (`mtime_o`, `timer_irq_o`, `soft_irq_o`) reflect these reset values. All values are reset synchronously with the system clock. Once reset is deasserted (`rst_ni = 1`), the `mtime` counter begins incrementing every clock cycle, and the interrupt outputs (`timer_irq_o` and `soft_irq_o`) update according to the current register states. + +### Behavior and Timing + +`mtime` increments by one on every rising edge of the system clock. When `mtime` becomes greater than or equal to `mtimecmp`, the timer interrupt output `timer_irq_o` is asserted and remains asserted until a new, greater `mtimecmp` value is written. The software interrupt output `soft_irq_o` directly reflects the state of the `msip` register. All operations are synchronous with the system clock, and outputs update on the cycle following their trigger conditions. + +### Programming Model + +The CLINT exposes three memory-mapped registers: + +- **`msip`** – Software interrupt pending. Writing a `1` to bit 0 asserts `soft_irq_o` until cleared. +- **`mtimecmp`** – Timer compare register. When the internal 64-bit counter `mtime` reaches or exceeds this value, `timer_irq_o` is asserted until a new, greater value is written. +- **`mtime`** – A 64-bit free-running counter that increments at the system clock rate. Software can read this register for timing purposes. + +All registers are accessible via write-enable and data inputs (`*_we`, `*_wdata`). + +### Errors/IRQs + +| **IRQ** | **Source** | **How it's triggered** | **How it's cleared** | +|----------------|------------|--------------------------------|------------------------------------------------| +| timer_irq_o | mtimecmp | Asserted when mtime >= mtimecmp | Cleared when a new, greater mtimecmp is written | +| soft_irq_o | msip | Reflects the value of the msip register | Cleared by writing 0 to msip | + +The CLINT does not generate additional error signals. + +### Performance Targets + +- `mtime` increments every clock cycle in sync with the system clock. +- Timer and software interrupts (`timer_irq_o` and `soft_irq_o`) are asserted within one clock cycle of their triggering condition. + +### Dependencies + +The CLINT depends on `clk_i` to increment `mtime` and `rst_ni` to initialize internal registers. Write-enable (`*_we`) and write-data (`*_wdata`) inputs are required to update the `msip` and `mtimecmp` registers. It must be connected to the CPU core so software can receive the interrupt outputs. + +### Verification Links + +Verification for the CLINT is planned through simulation testbenches to confirm correct behavior of the internal 64-bit timer (`mtime`), timer compare (`mtimecmp`), and software interrupt (`msip`) functionality. Testbenches will ensure that `timer_irq_o` and `soft_irq_o` assert under the correct conditions and that the module responds correctly to reset (`rst_ni`). diff --git a/docs/ip-briefs/plic_module_brief_v0.2.md b/docs/ip-briefs/plic_module_brief_v0.2.md new file mode 100644 index 0000000..24f5a83 --- /dev/null +++ b/docs/ip-briefs/plic_module_brief_v0.2.md @@ -0,0 +1,69 @@ +# PLIC - Module Brief (v0.3) + +**Owner:** Gavin Wiese +**RTL:** rtl/irq/plic.sv + +### Purpose & Role +The Platform-Level Interrupt Controller (PLIC) is placed near the CPU core. The PLIC manages and prioritizes external interrupt requests from up to 32 sources, forwarding only the highest-priority pending interrupt to the CPU. This allows the processor to efficiently handle asynchronous external events. + +### Parameters + +- Number of interrupt sources: 32 (`NSOURCES`) +- Priority field width per source: 3 bits (`PRIO_WIDTH`) +- Interrupt ID width for claim/complete operations: `$clog2(NSOURCES)` + +### Interfaces (Ports) + +| **Signal** | **Dir** | **Width** | **Description** | +|----------------------|---------|-----------|----------------------------------------------------| +| clk_i | in | 1 | System clock | +| rst_ni | in | 1 | Active-low asynchronous reset | +| src_i | in | 32 | External interrupt sources | +| priority_wdata | in | 96 | Data to write to all priority registers (32 × 3) | +| priority_we | in | 1 | Write enable for priority registers | +| enable_wdata | in | 32 | Data to write to enable register | +| enable_we | in | 1 | Write enable for enable register | +| claim_wdata | in | 5 | Claim complete input | +| claim_we | in | 1 | Write enable for claim completion | +| ext_irq_o | out | 1 | Interrupt output to the CPU core | +| claim_o | out | 5 | Current claimed interrupt ID | + +### Reset/Init + +An active-low asynchronous reset (`rst_ni`) is used for the PLIC. When reset is asserted (`rst_ni = 0`), all internal registers—including `priorities`, `enable`, `pending`, and `claim`—are cleared to 0, and the output signal `ext_irq_o` is deasserted. + +### Behavior and Timing + +The PLIC continuously monitors the 32 `src_i` interrupt lines. When one or more enabled interrupts are pending, the highest-priority source is selected, and `ext_irq_o` is asserted to signal the CPU core. Once the CPU completes the interrupt (signaled via `claim_we`), the pending bit for that interrupt is cleared and `ext_irq_o` deasserts. All operations are synchronous with the system clock, and `ext_irq_o` asserts one clock cycle after the conditions are met. + +### Programming Model + +The PLIC provides three sets of registers controlled via simple write-enable/data inputs: + +- **`priority`** – Stores the priority of each interrupt source. Higher values indicate higher priority. Updated via `priority_wdata` and `priority_we`. +- **`enable`** – Determines which interrupt sources are enabled. Updated via `enable_wdata` and `enable_we`. +- **`claim`** – Contains the currently claimed interrupt ID. Writing to this register with `claim_wdata` and `claim_we` signals completion, clearing the pending bit. + +All registers are accessible through the `_we` / `_wdata` inputs in this bus-free implementation. + +### Errors/IRQs + +| **IRQ** | **Source** | **Trigger** | **Clear** | +|------------|-----------|----------------------------------|-------------------------------------| +| ext_irq_o | src_i | One or more enabled interrupts pending | Cleared when CPU signals completion via claim input | + +The PLIC does not generate additional internal error signals; all interrupts come from external sources. + +### Performance Targets + +- `ext_irq_o` asserts within one clock cycle of a pending, enabled interrupt being detected. +- All internal registers (priority, enable, claim/complete, pending) update synchronously with the system clock. +- The PLIC can handle all 32 external sources without loss of pending interrupts. + +### Dependencies + +The PLIC depends on `clk_i` to update internal registers and monitor interrupt sources, and on `rst_ni` to initialize registers. External interrupt lines (`src_i`) provide input events, and the PLIC drives the single interrupt output (`ext_irq_o`) to the CPU core. Register updates are controlled via `_we` / `_wdata` inputs. + +### Verification Links + +Verification for the PLIC is planned through simulation testbenches to confirm correct behavior of priority handling, enable bits, and the claim/complete mechanism. Testbenches will ensure that `ext_irq_o` asserts for the highest-priority pending interrupt, that pending bits are cleared after a claim/complete operation, and that the module responds correctly to reset (`rst_ni`). diff --git a/rtl/bus/axi/axi_async_fifo.sv b/rtl/bus/axi/axi_async_fifo.sv index e69de29..8e372d4 100644 --- a/rtl/bus/axi/axi_async_fifo.sv +++ b/rtl/bus/axi/axi_async_fifo.sv @@ -0,0 +1,200 @@ +// +// Asynchronous FIFO (First In, First Out) RTL +// +// Uses Gray codes for synchronizing pointers across asynchronous clock domains +// to prevent instability issues when calculating states (full/empty). +// Use of N + 1 bit pointers for a 2^N deep FIFO to distinguish correctly +// between full and empty states. +// + +module axi_async_fifo #( + // Width of data stored in FIFO + parameter int unsigned DATA_WIDTH = 64, + + // Depth of FIFO needs to be a power of 2 (2, 4, 8, etc) + parameter int unsigned FIFO_DEPTH = 8 +)( + // Write side (Source/Slave) + input logic s_clk_i, + input logic s_rst_ni, // Active-low reset + input logic wr_en, // Write Enable + input logic [DATA_WIDTH-1:0] wr_data, // Write Data + output logic full, // FIFO full flag (registered) + + // Read side (Master/Sink) + input logic m_clk_i, + input logic m_rst_ni, // Active-low reset + input logic rd_en, // Read Enable + output logic [DATA_WIDTH-1:0] rd_data, // Read Data (registered) + output logic empty // FIFO Empty flag (registered) +); + // Local Parameters + localparam int PTR_WIDTH = $clog2(FIFO_DEPTH); + + // Pointer size (N + 1 bits) + localparam int PTR_SIZE = PTR_WIDTH + 1; + + // Memory address width (N bits) + localparam int ADDR_WIDTH = PTR_WIDTH; + + // Ensure that FIFO_DEPTH is a power of 2 (for simulation) + initial begin + if ((1 << PTR_WIDTH) != FIFO_DEPTH) begin + $error(1, "axi_async_fifo: FIFO_DEPTH (%0d) is not a power of 2", FIFO_DEPTH); + end + end + + // Memory array (Single port, written in s_clk_i domain, read in m_clk_i domain) + logic [DATA_WIDTH-1:0] memory [0:FIFO_DEPTH-1]; + + // Gray <-> Binary Conversion Functions + // These functions operate with the N + 1 bit pointers. + function automatic logic [PTR_SIZE-1:0] BinaryToGray(input logic [PTR_SIZE-1:0] binary); + BinaryToGray = (binary >> 1) ^ binary; + endfunction + + function automatic logic [PTR_SIZE-1:0] GrayToBinary(input logic [PTR_SIZE-1:0] gray); + logic [PTR_SIZE-1:0] binary; + automatic integer i; + begin + binary[PTR_WIDTH] = gray[PTR_WIDTH]; + for (i = PTR_WIDTH-1; i >= 0; i--) begin + binary[i] = binary[i + 1] ^ gray[i]; + end + GrayToBinary = binary; + end + endfunction + + // Write Domain Signals (s_clk_i) + // Write Pointer Registers (N + 1 bits) + logic [PTR_SIZE-1:0] writePtrBinary, writePtrBinaryNext; + logic [PTR_SIZE-1:0] writePtrGray, writePtrGrayNext; + + // Syncronized read pointer (gray) into write domain (2 flop synchronizer) + logic [PTR_SIZE-1:0] readPtrGraySync1W, readPtrGraySync2W; + + logic fullNext; // Combinational Full Status + + // Write pointer next-state logic (Binary and Gray) + assign writePtrBinaryNext = (wr_en && !full) + ? (writePtrBinary + 1'b1) + : writePtrBinary; + + assign writePtrGrayNext = BinaryToGray(writePtrBinaryNext); + + // Write pointer & memory update register block + always_ff @(posedge s_clk_i or negedge s_rst_ni) begin + if (!s_rst_ni) begin + writePtrBinary <= '0; + writePtrGray <= '0; + end else begin + writePtrBinary <= writePtrBinaryNext; + writePtrGray <= writePtrGrayNext; + + if (wr_en && !full) begin + memory[writePtrBinary[PTR_WIDTH-1:0]] <= wr_data; + end + end + end + + // Synchronize read pointer (Gray) into write domain (s_clk_i) + // readPtrGray is sourced from the m_clk_i domain (see the read domain below) + always_ff @(posedge s_clk_i or negedge s_rst_ni) begin + if (!s_rst_ni) begin + readPtrGraySync1W <= '0; + readPtrGraySync2W <= '0; + end else begin + readPtrGraySync1W <= readPtrGray; + readPtrGraySync2W <= readPtrGraySync1W; + end + end + + // Full flag logic (Combinational) + // Full Condition: next write gray pointer matches synchronized read gray pointer, + // except for MSB and MSB - 1, which are inverted. + always_comb begin + fullNext = + (writePtrGrayNext == { + ~readPtrGraySync2W[PTR_WIDTH], // MSB inverted + ~readPtrGraySync2W[PTR_WIDTH-1], // MSB - 1 inverted + readPtrGraySync2W[PTR_WIDTH-2:0] // Lower N - 1 bits matched + }); + end + + // Full flag register + always_ff @(posedge s_clk_i or negedge s_rst_ni) begin + if (!s_rst_ni) begin + full <= 1'b0; + end else begin + full <= fullNext; + end + end + + // Read Domain Signals (m_clk_i) + // Read pointer registers (N + 1 bits) + logic [PTR_SIZE-1:0] readPtrBinary, readPtrBinaryNext; + logic [PTR_SIZE-1:0] readPtrGray, readPtrGrayNext; + + // Syncronized read pointer (gray) into write domain (2-flop synchronizer) + logic [PTR_SIZE-1:0] writePtrGraySync1R, writePtrGraySync2R; + + logic emptyNext; // Combinational Empty Status + + // Read pointer next-state logic (binary and gray) + assign readPtrBinaryNext = (rd_en && !empty) + ? (readPtrBinary + 1'b1) + : readPtrBinary; + + assign readPtrGrayNext = BinaryToGray(readPtrBinaryNext); + + // Read pointer & data output register block + always_ff @(posedge m_clk_i or negedge m_rst_ni) begin + if (!m_rst_ni) begin + readPtrBinary <= '0; + readPtrGray <= '0; + rd_data <= '0; + end else begin + readPtrBinary <= readPtrBinaryNext; + readPtrGray <= readPtrGrayNext; + + // Read data from the location pointed to by the current binary pointer index + // Data is registered into rd_data on the clock edge. + if (rd_en && !empty) begin + rd_data <= memory[readPtrBinary[ADDR_WIDTH-1:0]]; + end + end + end + + // Synchronize write pointer (Gray) into read domain (m_clk_i) + // writePtrGray is sourced from the s_clk_i domain (see write domain above) + always_ff @(posedge m_clk_i or negedge m_rst_ni) + begin + if (!m_rst_ni) begin + writePtrGraySync1R <= '0; + writePtrGraySync2R <= '0; + end else begin + writePtrGraySync1R <= writePtrGray; + writePtrGraySync2R <= writePtrGraySync1R; + end + end + + // Empty flag logic: (Combinational) + // Empty condition: next read gray pointer equals synchronized write gray pointer. + always_comb begin + emptyNext = (readPtrGrayNext == writePtrGraySync2R); + end + + // Empty flag register + always_ff @(posedge m_clk_i or negedge m_rst_ni) begin + if (!m_rst_ni) begin + empty <= 1'b1; + end else begin + empty <= emptyNext; + end + end + +endmodule + + + + diff --git a/rtl/bus/axi/axi_crossbar.sv b/rtl/bus/axi/axi_crossbar.sv index e69de29..6f39805 100644 --- a/rtl/bus/axi/axi_crossbar.sv +++ b/rtl/bus/axi/axi_crossbar.sv @@ -0,0 +1,44 @@ +/* + axi_crossbar.sv + + Purpose & Role + Central AXI4 interconnect between managers and subordinates. + Performs address decode, per-subordinate channel arbitration, and ID-based routing so multiple transactions can be outstanding concurrently. + Common widths/IDs are defined in rtl/bus/interconnect_pkg.sv +*/ + +module axi_crossbar #( + // TODO: add parameters subject to change (managers/subordinates, widths) + parameter int unsigned N_M = 4, + parameter int unsigned N_S = 2, + parameter int unsigned ADDR_WIDTH = 32, + parameter int unsigned DATA_WIDTH = 64, + parameter int unsigned ID_WIDTH = 4 +)( + // TODO: add ports + input logic clk_i, + input logic rst_ni, + + // Manager-side AXI ports (I$, D$, DMA, PTW, etc) + // Amount of Manager-side AXI ports subject to change + output [ADDR_WIDTH-1:0] m_axi_I$, + output [ADDR_WIDTH-1:0] m_axi_D$, + output [ADDR_WIDTH-1:0] m_axi_DMA, + output [ADDR_WIDTH-1:0] m_axi_PTW, + // Subordinate-side AXI ports (DDR, AXI-Lite bridge, etc.) + // Amount and size of Subordinate-side AXi ports subject to change + input [ADDR_WIDTH-1:0] s_axi_DDR, + input [ADDR_WIDTH-1:0] s_axi_LiteB +); + + // TODO: address decode + // TODO: per-subordinate arbitration + // TODO: ID-based routing + // TODO: ready/valid handling + +endmodule + + + + + diff --git a/rtl/bus/axi/axi_dcache_port.sv b/rtl/bus/axi/axi_dcache_port.sv index e69de29..2859d17 100644 --- a/rtl/bus/axi/axi_dcache_port.sv +++ b/rtl/bus/axi/axi_dcache_port.sv @@ -0,0 +1,15 @@ +module axi_dcache_port #( + // TODO: Parameter setups + parameter +)( + // TODO: Port set up + ports + + // TODO: Input logic + input logic clk_i, + input logic rst_ni + +); + +endmodule + diff --git a/rtl/bus/axi/axi_dma.sv b/rtl/bus/axi/axi_dma.sv index e69de29..66a2e47 100644 --- a/rtl/bus/axi/axi_dma.sv +++ b/rtl/bus/axi/axi_dma.sv @@ -0,0 +1,14 @@ +module axi_dma #( + // TODO: Parameter setups + parameter +)( + // TODO: Port set up + ports + + // TODO: Input logic + input logic clk_i, + input logic rst_ni + +); + +endmodule diff --git a/rtl/bus/axi/axi_icache_port.sv b/rtl/bus/axi/axi_icache_port.sv index e69de29..1f92419 100644 --- a/rtl/bus/axi/axi_icache_port.sv +++ b/rtl/bus/axi/axi_icache_port.sv @@ -0,0 +1,14 @@ +module axi_icache_port #( + // TODO: Parameter setups + parameter +)( + // TODO: Port set up + ports + + // TODO: Input logic + input logic clk_i, + input logic rst_ni + +); + +endmodule diff --git a/rtl/bus/axi/axi_to_axi_lite.sv b/rtl/bus/axi/axi_to_axi_lite.sv index e69de29..4c647d8 100644 --- a/rtl/bus/axi/axi_to_axi_lite.sv +++ b/rtl/bus/axi/axi_to_axi_lite.sv @@ -0,0 +1,14 @@ +module axi_to_axi_lite #( + // TODO: Parameter setups + parameter +)( + // TODO: Port set up + ports + + // TODO: Input logic + input logic clk_i, + input logic rst_ni + +); + +endmodule diff --git a/rtl/cpu/mmu/sv32_mmu.sv b/rtl/cpu/mmu/sv32_mmu.sv index e69de29..caf696e 100644 --- a/rtl/cpu/mmu/sv32_mmu.sv +++ b/rtl/cpu/mmu/sv32_mmu.sv @@ -0,0 +1,140 @@ +// ----------------------------------------------------------------------------- +// sv32_mmu.sv +// Sv32 Memory Management Unit (MMU) +// One-pager implementation skeleton +// ----------------------------------------------------------------------------- +// +// Module performs Sv32 virtual to physical translation using an external +// PTW (Page Table Walker) and a TLB. This file is an early state template +// based on the one pager specification. +// +// ----------------------------------------------------------------------------- + +module sv32_mmu #( + parameter int TLB_ENTRIES = 16, + parameter int PAGE_SIZE = 4096, + parameter int PTW_TIMEOUT_CYCLES = 256, + parameter int ADDR_WIDTH = 32, + parameter int PADDR_WIDTH = 34 +)( + input logic clk_i, + input logic rst_ni, + + // Translation request from CPU + input logic [ADDR_WIDTH-1:0] va_i, + input logic valid_i, + output logic ready_o, + + // Translated physical address to CPU + output logic [PADDR_WIDTH-1:0] pa_o, + + // PTW interface (external module) + output logic ptw_req_valid_o, + output logic [ADDR_WIDTH-1:0] ptw_req_addr_o, + input logic ptw_rsp_valid_i, + input logic [63:0] ptw_rsp_data_i, + + // CSR / privilege inputs + input logic [31:0] satp_i, + input logic [1:0] priv_i +); + + // Internal Types & Signals + // ------------------------- + + typedef enum logic [1:0] { + IDLE, + TLB_LOOKUP, + PTW_WAIT, + OUTPUT_RESULT + } mmu_state_e; + + mmu_state_e state_d, state_q; + + logic tlb_hit; + logic [PADDR_WIDTH-1:0] tlb_pa; + + logic miss_detected; + + // TLB Instance (placeholder) + // -------------------------- + + // NOTE: + // Replace this with the actual TLB module + // from rtl/cpu/mmu/tlb.sv and hook up ports accordingly. + + // tlb #(.ENTRIES(TLB_ENTRIES)) u_tlb ( + // .clk_i(clk_i), + // .rst_ni(rst_ni), + // .lookup_va_i(va_i), + // .lookup_valid_i(valid_i), + // .lookup_ready_o(), + // .lookup_hit_o(tlb_hit), + // .lookup_pa_o(tlb_pa), + // .miss_o(miss_detected), + // .insert_valid_i(), + // .insert_vpn_i(), + // .insert_ppn_i(), + // .insert_perm_i(), + // .flush_i(1'b0) + // ); + + // PTW Interface Logic (template) + // ------------------------------ + + // For assignment submission: + // MMU will assert ptw_req_valid_o on a miss and wait for ptw_rsp_valid_i. + // + // Real logic will be added later by your PTW / MMU teammates. + + assign ptw_req_valid_o = (state_q == PTW_WAIT); + assign ptw_req_addr_o = va_i; // placeholder: real implementation extracts VPN + + // State Machine + // ------------- + + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + state_q <= IDLE; + end else begin + state_q <= state_d; + end + end + + always_comb begin + state_d = state_q; + ready_o = 1'b0; + pa_o = '0; + + case (state_q) + + IDLE: begin + if (valid_i) + state_d = TLB_LOOKUP; + end + + TLB_LOOKUP: begin + if (tlb_hit) begin + pa_o = tlb_pa; + ready_o = 1'b1; + state_d = OUTPUT_RESULT; + end else begin + state_d = PTW_WAIT; // TLB miss -> request PTW + end + end + + PTW_WAIT: begin + if (ptw_rsp_valid_i) begin + // Placeholder: real code inserts into TLB + checks permissions + ready_o = 1'b1; + state_d = OUTPUT_RESULT; + end + end + + OUTPUT_RESULT: begin + // End of translation, ready for next request + state_d = IDLE; + end + endcase + end +endmodule diff --git a/rtl/dma/dma_channel_arbiter.sv b/rtl/dma/dma_channel_arbiter.sv new file mode 100644 index 0000000..646826d --- /dev/null +++ b/rtl/dma/dma_channel_arbiter.sv @@ -0,0 +1,32 @@ +//====================================================================== +// DMA Channel Arbiter +// Author: Evan Eichholz +// Description: Fixed-priority channel arbitration +//====================================================================== + +module dma_channel_arbiter import dma_pkg::*; ( + input logic clk_i, + input logic rst_ni, + input logic [N_CHANNELS-1:0] req_i, + output logic [N_CHANNELS-1:0] grant_o +); + + logic [CHANNEL_W-1:0] current_channel; + + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + current_channel <= '0; + grant_o <= '0; + end else begin + // Fixed priority arbitration (Channel 0 highest priority) + grant_o <= '0; + for (int i = 0; i < N_CHANNELS; i++) begin + if (req_i[i]) begin + grant_o[i] <= 1'b1; + break; + end + end + end + end + +endmodule \ No newline at end of file diff --git a/rtl/dma/dma_controller.sv b/rtl/dma/dma_controller.sv new file mode 100644 index 0000000..39319e0 --- /dev/null +++ b/rtl/dma/dma_controller.sv @@ -0,0 +1,45 @@ +//====================================================================== +// DMA Controller - Top Level +// Author: Evan Eichholz +// Description: Multi-channel DMA with scatter-gather support +// References: specs/registers/dma.yaml, docs/dma_operation.md +//====================================================================== + +module dma_controller import dma_pkg::*; ( + // Clock and reset + input logic clk_i, + input logic rst_ni, + + // AXI4 Memory Interface (Master) - Write address channel + output logic [ADDR_W-1:0] m_axi_awaddr_o, + output logic [7:0] m_axi_awlen_o, + // ... (other AXI ports - truncated for brevity) + + // AXI4-Lite Control Interface (Slave) + input logic [ADDR_W-1:0] s_axi_awaddr_i, + // ... (other AXI-Lite ports) + + // Interrupt outputs + output logic [N_CHANNELS-1:0] irq_done_o, + output logic [N_CHANNELS-1:0] irq_error_o, + + // Peripheral request interface + input logic [N_CHANNELS-1:0] periph_req_i, + output logic [N_CHANNELS-1:0] periph_ack_o +); + + // Internal signals + dma_regs_t regs_q, regs_d; + channel_state_e [N_CHANNELS-1:0] channel_state; + logic [N_CHANNELS-1:0] channel_grant; + + // Module instances + dma_channel_arbiter u_channel_arbiter (.*); + dma_desc_fetch u_desc_fetch (.*); + dma_xfer_engine u_xfer_engine (.*); + dma_axi_mux u_axi_mux (.*); + dma_reg_if u_reg_if (.*); + dma_irq_ctrl u_irq_ctrl (.*); + dma_status u_status (.*); + +endmodule \ No newline at end of file diff --git a/rtl/dma/dma_pkg.sv b/rtl/dma/dma_pkg.sv new file mode 100644 index 0000000..720e3ca --- /dev/null +++ b/rtl/dma/dma_pkg.sv @@ -0,0 +1,48 @@ +//====================================================================== +// DMA Package - Types and Parameters +// Author: Evan Eichholz +// Description: Common types, parameters and definitions for DMA controller +// References: specs/registers/dma.yaml +//====================================================================== + +package dma_pkg; + + // Channel count and sizing + parameter int unsigned N_CHANNELS = 4; + parameter int unsigned DATA_W = 32; + parameter int unsigned ADDR_W = 32; + parameter int unsigned DESC_ADDR_W = 32; + parameter int unsigned CHANNEL_W = $clog2(N_CHANNELS); + + // DMA channel states + typedef enum logic [2:0] { + CH_IDLE, + CH_DESC_FETCH, + CH_XFER_READ, + CH_XFER_WRITE, + CH_COMPLETE, + CH_ERROR + } channel_state_e; + + // Descriptor structure (64-bit aligned for AXI efficiency) + typedef struct packed { + logic [DESC_ADDR_W-1:0] next_desc; // 32 bits + logic [ADDR_W-1:0] src_addr; // 32 bits + logic [ADDR_W-1:0] dst_addr; // 32 bits + logic [23:0] length; // 24 bits + logic [7:0] control; // 8 bits (last + config) + } dma_desc_t; + + // Control field bit assignments + parameter int CTRL_LAST_BIT = 7; + parameter int CTRL_CONFIG_MSB = 6; + parameter int CTRL_CONFIG_LSB = 0; + + // Register structure + typedef struct packed { + logic [31:0] ctrl; + logic [31:0] status; + logic [31:0] channel_enable; + } dma_regs_t; + +endpackage \ No newline at end of file diff --git a/rtl/irq/clint.sv b/rtl/irq/clint.sv index e69de29..4e55cf2 100644 --- a/rtl/irq/clint.sv +++ b/rtl/irq/clint.sv @@ -0,0 +1,25 @@ +module clint( + input logic clk_i, + input logic rst_ni, + output logic Timer_irq_o, + input logic [63:0] user_time, // + output logic [63:0] mtime_o, // + output logic msip +); + logic [63:0] mtimecmp; + + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + mtime_o <= 16'd0; + mtimecmp <= 16'd0; + Timer_irq_o <= 1'b0; + msip <= 1'b0; + end else begin + mtime_o <= mtime_o + 1; + mtimecmp <= user_time; + Timer_irq_o <= (mtime_o >= mtimecmp); + msip <= Timer_irq_o; + end + end +endmodule + diff --git a/rtl/irq/plic.sv b/rtl/irq/plic.sv index e69de29..e5be350 100644 --- a/rtl/irq/plic.sv +++ b/rtl/irq/plic.sv @@ -0,0 +1,105 @@ +module plic #( + parameter NSOURCES = 32, + parameter PRIO_WIDTH = 3 +)( + input logic clk_i, + input logic rst_ni, + input logic [NSOURCES-1:0] src_i, // External interrupt sources + + // Simple register interface (bus-free) + input logic [NSOURCES*PRIO_WIDTH-1:0] priority_wdata, + input logic priority_we, + input logic [NSOURCES-1:0] enable_wdata, + input logic enable_we, + input logic [$clog2(NSOURCES)-1:0] claim_wdata, + input logic claim_we, + + output logic ext_irq_o, + output logic [$clog2(NSOURCES)-1:0] claim_o +); + + // ------------------------- + // Registers / Internal state + // ------------------------- + logic [NSOURCES*PRIO_WIDTH-1:0] priorities; // Interrupt priorities + logic [NSOURCES-1:0] enable; // Enable bits for sources + logic [$clog2(NSOURCES)-1:0] claim; // Claim register + logic [NSOURCES-1:0] pending; // Pending interrupts + logic [$clog2(NSOURCES)-1:0] highestPriorIndex; + logic [PRIO_WIDTH-1:0] tempHighestValue; + logic activeClaim; + + assign claim_o = claim; + + // ------------------------- + // Active low reset + // ------------------------- + always_ff @(negedge rst_ni) begin + if (!rst_ni) begin + priorities <= 0; + enable <= 0; + claim <= 0; + pending <= 0; + highestPriorIndex <= 0; + tempHighestValue <= 0; + activeClaim <= 0; + end + end + + // ------------------------- + // Simple register writes + // ------------------------- + always_ff @(posedge clk_i) begin + if (priority_we) begin + priorities <= priority_wdata; + end + if (enable_we) begin + enable <= enable_wdata; + end + if (claim_we) begin + activeClaim <= 0; // Claim complete + end + end + + // ------------------------- + // Pending interrupt latching + // ------------------------- + always_ff @(posedge clk_i) begin + for (int i = 0; i < NSOURCES; i++) begin + pending[i] <= pending[i] | (src_i[i] & enable[i]); + end + end + + // ------------------------- + // Priority selector + // ------------------------- + always_ff @(posedge clk_i) begin + tempHighestValue <= 0; + highestPriorIndex <= 0; + for (int i = 0; i < NSOURCES; i++) begin + if (pending[i] && priorities[i*PRIO_WIDTH +: PRIO_WIDTH] > tempHighestValue) begin + tempHighestValue <= priorities[i*PRIO_WIDTH +: PRIO_WIDTH]; + highestPriorIndex <= i[$clog2(NSOURCES)-1:0]; + end + end + end + + // ------------------------- + // Claim logic + // ------------------------- + always_ff @(posedge clk_i) begin + if (!activeClaim && tempHighestValue != 0) begin + claim <= highestPriorIndex; + pending[highestPriorIndex] <= 0; + activeClaim <= 1; + end + end + + // ------------------------- + // IRQ output + // ------------------------- + always_ff @(posedge clk_i) begin + ext_irq_o <= activeClaim; + end + +endmodule diff --git a/rtl/mem/cache/cache_coherency_none.sv b/rtl/mem/cache/cache_coherency_none.sv index e69de29..3092871 100644 --- a/rtl/mem/cache/cache_coherency_none.sv +++ b/rtl/mem/cache/cache_coherency_none.sv @@ -0,0 +1,7 @@ +module cache_coherency_none #( + parameters +) ( + ports +); + +endmodule \ No newline at end of file diff --git a/rtl/mem/cache/cache_miss_unit.sv b/rtl/mem/cache/cache_miss_unit.sv index e69de29..d8ced03 100644 --- a/rtl/mem/cache/cache_miss_unit.sv +++ b/rtl/mem/cache/cache_miss_unit.sv @@ -0,0 +1,7 @@ +module cache_miss_unit #( + parameters +) ( + ports +); + +endmodule diff --git a/rtl/mem/cache/cache_tags.sv b/rtl/mem/cache/cache_tags.sv index e69de29..0519101 100644 --- a/rtl/mem/cache/cache_tags.sv +++ b/rtl/mem/cache/cache_tags.sv @@ -0,0 +1,7 @@ +module cache_tags #( + parameters +) ( + ports +); + +endmodule diff --git a/rtl/mem/cache/dcache.sv b/rtl/mem/cache/dcache.sv index e69de29..33d2b16 100644 --- a/rtl/mem/cache/dcache.sv +++ b/rtl/mem/cache/dcache.sv @@ -0,0 +1,11 @@ +module dcache #( + parameter int INSTR_WIDTH = 32, + parameter ADDR_WIDTH = 32, + parameter LINE_SIZE = 64, + parameter WAYS = 4, + parameter DMEM_SIZE = 16384 +) ( + ports +); + +endmodule diff --git a/rtl/mem/cache/icache.sv b/rtl/mem/cache/icache.sv index e69de29..92e6695 100644 --- a/rtl/mem/cache/icache.sv +++ b/rtl/mem/cache/icache.sv @@ -0,0 +1,164 @@ +module icache #( + parameter int INSTR_WIDTH = 32, + parameter int ADDR_WIDTH = 32, + parameter int LINE_SIZE = 64, + parameter int WAYS = 2, + parameter int IMEM_SIZE = 16384 +) ( + // Global / Control signals + input logic clk_i, + input logic rst_ni, + input logic icache_flush_i, // FENCE_I + + // CPU <-> I$ Interface + input logic cpu_req_valid_i, + input logic [ADDR_WIDTH-1 : 0] cpu_addr_i, + input logic cpu_resp_ready_i, + output logic icache_req_ready_o, + output logic icache_resp_valid_o, + output logic [INSTR_WIDTH-1 : 0] icache_resp_instr_o, + + // TLB -> I$ Interface + input logic [TAG-1 : 0] tlb_pa_tag_i, + input logic tlb_req_ready_i, + input logic tlb_resp_valid_i, + output logic icache_tlb_resp_ready_o, + + // L2$ <-> I$ Interface + input logic l2_ready_i, + input logic l2_valid_i, + input logic [ADDR_WIDTH-1 : 0] l2_addr_i, + input logic [LINE_SIZE*8-1:0] l2_data_i + + // AXI Interface not needed on I$, we only communicate with CPU and L2 Cache +); + //localparams for constants + localparam int SETS = IMEM_SIZE / (WAYS * LINE_SIZE); // 128 sets + localparam int OFFSET = $clog2(LINE_SIZE); // 6 bits + localparam int INDEX = $clog2(SETS); // 7 bits + localparam int TAG = ADDR_WIDTH - OFFSET - INDEX; // 19 bits + + // i-cache storage + logic [TAG-1:0] icache_tags [SETS][WAYS]; + logic [LINE_SIZE*8-1:0] icache_data [SETS][WAYS]; + logic valid_bits [SETS][WAYS]; + + // hold different address portions + logic [TAG-1 : 0] addr_tag_virtual; + logic [INDEX-1 : 0] addr_index; + logic [OFFSET-1 : 0] addr_offset; + + // cache hit + logic [$clog2(WAYS)-1 : 0] hit_way; + logic cache_tag_hit; + + // instruction output + logic [INSTR_WIDTH-1 : 0] icache_resp_instr_next; + logic icache_resp_valid_next; + + typedef enum logic [3:0] { + CACHE_IDLE, + CACHE_LOOKUP, + CACHE_HIT, + CACHE_MISS, + CACHE_FETCH, + CACHE_EVICT, + CACHE_OUTPUT + } cache_state_t; + + cache_state_t current_state, next_state; + + // tag lookup + always_comb begin + cache_tag_hit = 1'b0; + hit_way = '0; + if(current_state == CACHE_LOOKUP) begin + for(int way = 0; way < WAYS; way++) begin + if(tlb_pa_tag_i == icache_tags[addr_index][way] + && valid_bits[addr_index][way]) begin + hit_way = way; + cache_tag_hit = 1'b1; + break; + end + end + end + end + + //output data logic + always_comb begin + icache_resp_instr_next = '0; + icache_resp_valid_next = 1'b0; + if(current_state == CACHE_OUTPUT) begin + icache_resp_instr_next = icache_data[addr_index][hit_way][addr_offset*8+ADDR_WIDTH-1 : addr_offset*8]; + icache_resp_valid_next = 1'b1; + end + end + + always_ff @(posedge clk_i) begin + icache_resp_instr_o <= icache_resp_instr_next; + icache_resp_valid_next <= icache_resp_valid_next; + end + + // address slicing into offset and index + always_comb begin + addr_index = cpu_addr_i[OFFSET+INDEX-1 : OFFSET]; + addr_offset = cpu_addr_i[OFFSET-1 : 0]; + addr_tag_virtual = cpu_addr_i[ADDR_WIDTH-1 : OFFSET+INDEX]; + end + + // rst_ni invalidate all cache lines + always_ff @(posedge clk_i or negedge rst_ni) begin + if(!rst_ni) begin + current_state <= CACHE_IDLE; + icache_resp_instr_o <= '0; + icache_resp_valid_o <= 1'b0; + for(int set = 0; set < SETS; set++) begin + for(int way = 0; way < WAYS; way++) begin + valid_bits[set][way] <= 1'b0; + end + end + end + end + + // FENCE.I invalidate cache + always_ff @(posedge clk_i) begin + if(icache_flush_i) begin + current_state <= CACHE_IDLE; + for(int set = 0; set < SETS; set++) begin + for(int way = 0; way < WAYS; way++) begin + valid_bits[set][way] <= 1'b0; + end + end + end + end + + // combinational next state control + always_comb begin + case (current_state) + CACHE_IDLE: + if(cpu_req_valid_i && icache_req_ready_o) next_state = CACHE_LOOKUP; + CACHE_LOOKUP: + if(cache_tag_hit) next_state = CACHE_HIT; + else next_state = CACHE_MISS; + CACHE_HIT: + if(cpu_resp_ready_i && icache_resp_valid_o) next_state = CACHE_OUTPUT; + else next_state = CACHE_HIT; + CACHE_MISS: + if(tlb_req_ready_i && icache_tlb_resp_ready_o) next_state = CACHE_FETCH; + CACHE_FETCH: + CACHE_EVICT: + CACHE_OUTPUT: + if(!cpu_resp_ready_i) next_state = CACHE_OUTPUT; + else next_state = CACHE_IDLE; + default: next_state = CACHE_IDLE; + endcase + end + +/* +add next state combinational logic +add replacement/evict logic +add communiaction with L2 cache +refine state machine +*/ + +endmodule diff --git a/rtl/mem/cache/l2_cache.sv b/rtl/mem/cache/l2_cache.sv new file mode 100644 index 0000000..0a391eb --- /dev/null +++ b/rtl/mem/cache/l2_cache.sv @@ -0,0 +1,11 @@ +module l2_cache #( + parameter int INSTR_WIDTH = 32, + parameter int ADDR_WIDTH = 32, + parameter int LINE_SIZE = 64, + parameter int WAYS = 4, + parameter int L2_SIZE = 262144 +) ( + ports +); + +endmodule