Skip to content
9 changes: 9 additions & 0 deletions Contributing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Contributing

Contributions are welcome! You can help by:
- Adding new RISC-V instructions or extensions
- Improving the pipeline or memory modules
- Adding more RISCoF test cases
- Fixing bugs in existing modules

Please fork the repository, create a feature branch, and submit a pull request.
Binary file added NucleusRV_B.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added NucleusRV_w.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 45 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
# NucleusRV
<picture>
<source media="(prefers-color-scheme: dark)" srcset="nrv_B.png">
<source media="(prefers-color-scheme: light)" srcset="NucleusRV_w.png">
<img alt="NucleusRV Logo" src="NucleusRV_w.png" width="600">
</picture>

[![Join the chat at https://gitter.im/merledu/nucleusrv](https://badges.gitter.im/merledu/nucleusrv.svg)](https://gitter.im/merledu/nucleusrv?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

A chisel based riscv 5-stage pipelined cpu design, implementing 32-bit version of the ISA (incomplete).

## Supported Extensions

| Extension | Description |
|-----------|-------------|
| I | Base Integer Instructions |
| M | Integer Multiplication & Division |
| F | Single-Precision Floating Point |
| C | Compressed Instructions |
| A | Atomic Instructions |



## Dependencies

Expand All @@ -28,16 +43,43 @@ python3 gen_verilog.py <imem> <dmem>
### Running RISC-V assembly

```sh
python3 simulate.py --sbt_args "--imem <imem>" nucleusrv.components.NRVDriver Top
python3 simulate.py --sbt_args "--imem <PATH_TO_YOUR_IMEM_FILE>" nucleusrv.components.NRVDriver Top

```

### Running RISC-V Architectural Tests
* Make sure to have the RISC-V GNU Toolchain and Verilator in your `PATH`.
* Create a python virtual environment and follow the `README.md` in `riscof/riscv-arch-test/`.
* Create a python virtual environment.
```sh
python3 -m venv .venv
source .venv/bin/activate
```
* Install Python Dependencies for RISCoF
```sh
pip install --upgrade pip
pip install git+https://github.com/riscv/riscof.git
```
* Run `run_riscv_arch_tests.py` in root directory.
```sh
python3 run_riscv_arch_tests.py
```
* RISCoF Architecture Tests:
```sh
cd riscof/riscv-arch-test/riscv-ctg
pip install -e .
```
```sh
cd ../riscv-isac
pip install -e .
cd ../../..
```
* Verify Installation
```sh
riscof --help
spike --help
```
You should see RISCoF and Spike help options displayed.


### Building C Programs
* In `tools/tests` directory, create a folder and write c program in the `main.c` file
Expand Down
Binary file added nrv_B.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 49 additions & 0 deletions src/main/scala/components/AtomicAlu.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package nucleusrv.components
import chisel3._
import chisel3.util._

class AMOALU extends Module {
val io = IO(new Bundle {
val memData = Input(UInt(32.W)) // Data loaded from memory (old value at rs1 address)
val src2 = Input(UInt(32.W)) // Value from rs2 register
val amoOp = Input(UInt(4.W)) // Operation code from decoder
val result = Output(UInt(32.W)) // Computed result to write back
})
io.result := 0.U

// AMOoperation encodin
val AMO_ADD = 1.U
val AMO_SWAP = 2.U
val AMO_XOR = 3.U
val AMO_AND = 4.U
val AMO_OR = 5.U
val AMO_MIN = 6.U
val AMO_MAX = 7.U
val AMO_MINU = 8.U
val AMO_MAXU = 9.U

// Signed for comparisons
val s_memData = io.memData.asSInt
val s_src2 = io.src2.asSInt

switch(io.amoOp) {
is(AMO_ADD) {
io.result := io.memData + io.src2 }
is(AMO_SWAP) {
io.result := io.src2 } // Swap old with new
is(AMO_XOR) {
io.result := io.memData ^ io.src2 }
is(AMO_AND) {
io.result := io.memData & io.src2 }
is(AMO_OR) {
io.result := io.memData | io.src2 }
is(AMO_MIN) {
io.result := Mux(s_memData < s_src2, io.memData, io.src2) }
is(AMO_MAX) {
io.result := Mux(s_memData > s_src2, io.memData, io.src2) }
is(AMO_MINU) {
io.result := Mux(io.memData < io.src2, io.memData, io.src2) }
is(AMO_MAXU) {
io.result := Mux(io.memData > io.src2, io.memData, io.src2) }
}
}
67 changes: 67 additions & 0 deletions src/main/scala/components/AtomicDecoder.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package nucleusrv.components
import chisel3._
import chisel3.util._


class AtomicDecodeOut extends Bundle {
val isLR = Bool()
val isSC = Bool()
val isAMO = Bool()
val amoOp = UInt(4.W) // Encoded value for AMO operation type
}

class AtomicDecoder extends Module {
val io = IO(new Bundle {
val instr = Input(UInt(32.W))
val out = Output(new AtomicDecodeOut)
})

io.out.isLR := false.B
io.out.isSC := false.B
io.out.isAMO := false.B
io.out.amoOp := 0.U

val opcode = io.instr(6,0)
val funct5 = io.instr(31,27)
val funct3 = io.instr(14,12)

// opcode for atomic instructions
val OPCODE_ATOMIC = "b0101111".U

when(opcode === OPCODE_ATOMIC && funct3 === "b010".U) {
switch(funct5) {
is("b00010".U) {
io.out.isLR := true.B } // LR.W
is("b00011".U) {
io.out.isSC := true.B } // SC.W

is("b00000".U) {
io.out.isAMO := true.B;
io.out.amoOp := 1.U } // AMOADD
is("b00001".U) {
io.out.isAMO := true.B;
io.out.amoOp := 2.U } // AMOSWAP
is("b00100".U) {
io.out.isAMO := true.B;
io.out.amoOp := 3.U } // AMOXOR
is("b01100".U) {
io.out.isAMO := true.B;
io.out.amoOp := 4.U } // AMOAND
is("b01000".U) {
io.out.isAMO := true.B;
io.out.amoOp := 5.U } // AMOOR
is("b10000".U) {
io.out.isAMO := true.B;
io.out.amoOp := 6.U } // AMOMIN
is("b10100".U) {
io.out.isAMO := true.B;
io.out.amoOp := 7.U } // AMOMAX
is("b11000".U) {
io.out.isAMO := true.B;
io.out.amoOp := 8.U } // AMOMINU
is("b11100".U) {
io.out.isAMO := true.B;
io.out.amoOp := 9.U } // AMOMAXU
}
}
}
40 changes: 40 additions & 0 deletions src/main/scala/components/Bus.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package nucleusrv.components

import chisel3._
import chisel3.util._

class BusIO(addrWidth: Int, dataWidth: Int) extends Bundle {
val addr = Input(UInt(addrWidth.W))
val wdata = Input(UInt(dataWidth.W))
val rdata = Output(UInt(dataWidth.W))
val wen = Input(Bool())
val ren = Input(Bool())
}

class Bus(addrWidth: Int = 32, dataWidth: Int = 32) extends Module {
val io = IO(new Bundle {
val cpu = Flipped(new BusIO(addrWidth, dataWidth))
val imem = new BusIO(addrWidth, dataWidth)
val dmem = new BusIO(addrWidth, dataWidth)
})

// Default signals
io.imem := 0.U.asTypeOf(io.imem)
io.dmem := 0.U.asTypeOf(io.dmem)
io.cpu.rdata := 0.U

// Address decode: <0x1000_0000 -> imem, else -> dmem
when(io.cpu.addr < "h10000000".U) {
io.imem.addr := io.cpu.addr
io.imem.wdata := io.cpu.wdata
io.imem.wen := io.cpu.wen
io.imem.ren := io.cpu.ren
io.cpu.rdata := io.imem.rdata
}.otherwise {
io.dmem.addr := io.cpu.addr
io.dmem.wdata := io.cpu.wdata
io.dmem.wen := io.cpu.wen
io.dmem.ren := io.cpu.ren
io.cpu.rdata := io.dmem.rdata
}
}
60 changes: 53 additions & 7 deletions src/main/scala/components/Core.scala
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ class Core(implicit val config:Configs) extends Module{
val id_reg_fcsr_o_data = if (F) Some(RegInit(0.U(32.W))) else None
val id_reg_is_f = if (F) Some(RegInit(0.B)) else None

// Atomic signals ID-EX
val id_reg_isAMO = RegInit(false.B)
val id_reg_isLR = RegInit(false.B)
val id_reg_isSC = RegInit(false.B)
val id_reg_amoOp = RegInit(0.U(4.W))


// EX-MEM Registers
val ex_reg_branch = RegInit(0.U(32.W))
val ex_reg_zero = RegInit(0.U(32.W))
Expand All @@ -76,7 +83,10 @@ class Core(implicit val config:Configs) extends Module{
val ex_reg_f_read = if (F) Some(Reg(Vec(3, Bool()))) else None
val ex_reg_f_except = if (F) Some(RegInit(VecInit(Vector.fill(5)(0.B)))) else None
val ex_reg_is_f = if (F) Some(RegInit(0.B)) else None

// Atomic signals EX-MEM
val ex_reg_isAMO = RegInit(false.B)
val ex_reg_amoOp = RegInit(0.U(4.W))

// MEM-WB Registers
val mem_reg_rd = RegInit(0.U(32.W))
val mem_reg_ins = RegInit(0.U(32.W))
Expand Down Expand Up @@ -180,7 +190,7 @@ class Core(implicit val config:Configs) extends Module{
when(ID.ifid_flush) {
if_reg_ins := 0.U
}


/****************
* Decode Stage *
Expand Down Expand Up @@ -230,6 +240,11 @@ class Core(implicit val config:Configs) extends Module{
ID.f_read_reg.get(2)(i) := mem_reg_f_read.get(i)
}
}
// ID-EX A
id_reg_isAMO := ID.isAMO
id_reg_isLR := ID.isLR
id_reg_isSC := ID.isSC
id_reg_amoOp := ID.amoOp

/*****************
* Execute Stage *
Expand Down Expand Up @@ -283,6 +298,18 @@ class Core(implicit val config:Configs) extends Module{
ex_reg_is_f.get := EX.is_f_o.get
ID.f_except.get(0) <> EX.exceptions.get
}
// forward atomic control from ID->EX to EX->MEM
ex_reg_isAMO := id_reg_isAMO
ex_reg_amoOp := id_reg_amoOp

// AMO ALU instance (use in MEM stage)
val amoALU = Module(new AMOALU)

// ----- AMO wiring in Memory stage -----
// Connect AMOALU inputs to data read from memory.. rs2 (ex_reg_wd)
amoALU.io.memData := MEM.io.readData
amoALU.io.src2 := ex_reg_wd
amoALU.io.amoOp := ex_reg_amoOp

/****************
* Memory Stage *
Expand All @@ -302,7 +329,12 @@ class Core(implicit val config:Configs) extends Module{
//
// } otherwise{
mem_reg_rd := MEM.io.readData
mem_reg_result := ex_reg_result
// mem_reg_result := ex_reg_result

// If this is an AMO, WB should receive the original memory value (MEM.io.readData).
// For other cases keep previous behavior.
mem_reg_result := Mux(ex_reg_isAMO, MEM.io.readData, ex_reg_result)

// mem_reg_ctl_memToReg := ex_reg_ctl_memToReg
mem_reg_ctl_regWrite <> ex_reg_ctl_regWrite
mem_reg_ins := ex_reg_ins
Expand All @@ -313,14 +345,28 @@ class Core(implicit val config:Configs) extends Module{
ex_reg_result := EX.ALUresult
// }
mem_reg_wra := ex_reg_wra
mem_reg_ctl_memToReg := ex_reg_ctl_memToReg
// mem_reg_ctl_memToReg := ex_reg_ctl_memToReg

// Force the memToReg selector to choose memory result for AMO
// (assuming memToReg==1 means load -> uses MEM.io.readData in WB)
mem_reg_ctl_memToReg := Mux(ex_reg_isAMO, 1.U, ex_reg_ctl_memToReg)

mem_reg_is_csr := ex_reg_is_csr
mem_reg_csr_data := ex_reg_csr_data
EX.ex_mem_regWrite <> ex_reg_ctl_regWrite
MEM.io.aluResultIn := ex_reg_result
MEM.io.writeData := ex_reg_wd
MEM.io.readEnable := ex_reg_ctl_memRead
MEM.io.writeEnable := ex_reg_ctl_memWrite
// MEM.io.writeData := ex_reg_wd
// If ex_reg_isAMO: writeData should be amoALU result; otherwise normal ex_reg_wd
MEM.io.writeData := Mux(ex_reg_isAMO, amoALU.io.result, ex_reg_wd)

// MEM.io.readEnable := ex_reg_ctl_memRead
// For readEnable we keep original control (AMO still needs a read)
MEM.io.readEnable := ex_reg_ctl_memRead || ex_reg_isAMO

//MEM.io.writeEnable := ex_reg_ctl_memWrite
// Ensure we assert writeEnable for AMO (RMW needs write back)
MEM.io.writeEnable := ex_reg_ctl_memWrite || ex_reg_isAMO

MEM.io.f3 := ex_reg_ins(14,12)
EX.mem_result := ex_reg_result
ID.csr_Mem := ex_reg_is_csr
Expand Down
16 changes: 16 additions & 0 deletions src/main/scala/components/InstructionDecode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ class InstructionDecode(
// RVFI pins
val raddr = if (TRACE) Some(Output(Vec(3, UInt(5.W)))) else None
val rd_wdata = if (TRACE) Some(Output(UInt(32.W))) else None

// Atomic Outputpins
val isAMO = Bool()
val isLR = Bool()
val isSC = Bool()
val amoOp = UInt(4.W)
})

val is_f = if (F) Some(WireInit(0.B)) else None
Expand All @@ -98,6 +104,16 @@ class InstructionDecode(
).map(io.id_instruction(6, 0) === _.U).reduce(_ || _)
io.is_f.get := is_f.get
}
// Atomic Decoder
val atomicDecoder = Module(new AtomicDecoder)
atomicDecoder.io.instr := io.id_instruction

io.isAMO := atomicDecoder.io.out.isAMO
io.isLR := atomicDecoder.io.out.isLR
io.isSC := atomicDecoder.io.out.isSC
io.amoOp := atomicDecoder.io.out.amoOp



// CSR
val csr = if (Zicsr) Some(Module(new CSR())) else None
Expand Down
Loading