blob: 96298c00d81df6ada812695754f4795356402a5e [file] [log] [blame] [view]
# What is ChAI?
ChAI is a reference implementation of an embedded system, build around the Kelvin CPU.
It is intended as a starting point for either building out a more complete system, or integrating
into an existing system
## Building ChAI RTL
The SystemVerilog for ChAI can be built with the following command:
```
bazel build //hdl/chisel/src/chai:chai_cc_library_emit_verilog
```
The generated file will be in `bazel-bin/hdl/chisel/src/chai/ChAI.sv`
## Running a simulating ChAI
You can run program on the ChAI verilator sim with:
```
bazel run //tests/verilator_sim:chai_sim -- \
--cycles 2000 \
/path/to/program.bin
```
## Components
- Kelvin CPU
- TileLink-UL crossbar
- UART (from lowRISC)
- SRAM (from Chisel, and using an adapter from lowRISC)
## Limitations
- Can only address memory at 32B / 256b offsets (existing peripherals may require modifications due to this)
- No interrupt support
## Adding a new peripheral
In ChAI.scala, add a new entry to the memoryRegions table for your peripheral:
```scala
val memoryRegions = Seq(
new kelvin.MemoryRegion(0, 4 * 1024 * 1024, MemoryType.DMEM), // SRAM
new kelvin.MemoryRegion(4 * 1024 * 1024, 4 * 1024 * 1024, MemoryType.Peripheral) // UART
new kelvin.MemoryRegion(8 * 1024 * 1024, 1 * 1024 * 1024, MemoryType.Peripheral) // My New Peripheral
)
```
A MemoryRegion consists of a starting address (in bytes), a size (in bytes), and the region type (IMEM, DMEM, Peripheral).
In ChAI.scala, connect your device's TileLink I/O to the crossbar:
```scala
val crossbar =
Module(new kelvin.TileLinkUL(tlul_p, kelvin_p.m, /* hosts= */ 1))
crossbar.io.hosts_a(0) <> kelvin_to_tlul.io.tl_o
crossbar.io.hosts_d(0) <> kelvin_to_tlul.io.tl_i
crossbar.io.devices_a(0) <> tlul_adapter_sram.io.tl_i
crossbar.io.devices_d(0) <> tlul_adapter_sram.io.tl_o
crossbar.io.devices_a(1) <> uart.io.tl_i
crossbar.io.devices_d(1) <> uart.io.tl_o
crossbar.io.devices_a(2) <> my_new_peripheral.io.tl_i
crossbar.io.devices_d(2) <> my_new_peripheral.io.tl_o
```
Here's a skeleton of a peripheral that has a single register that can be
read or written.
```scala
package chai
import chisel3._
import chisel3.util._
object DummyPeripheral {
def apply(tlul_p: kelvin.TLULParameters): DummyPeripheral = {
return Module(new DummyPeripheral(tlul_p))
}
}
class DummyPeripheral(tlul_p: kelvin.TLULParameters) extends Module {
val io = IO(new Bundle {
val tl_i = Input(new kelvin.TileLinkULIO_H2D(tlul_p))
val tl_o = Output(new kelvin.TileLinkULIO_D2H(tlul_p))
})
io.tl_o := 0.U.asTypeOf(new kelvin.TileLinkULIO_D2H(tlul_p))
val saved_value = RegInit(0.U(32.W))
val tl_i_reg = Reg(new kelvin.TileLinkULIO_H2D(tlul_p))
tl_i_reg := io.tl_i
io.tl_o.a_ready := true.B
when (tl_i_reg.a_valid) {
io.tl_o.d_valid := true.B
switch (tl_i_reg.a_opcode) {
is (4.U /* kelvin.TLULOpcodesA.Get.asUInt */) {
io.tl_o.d_opcode := kelvin.TLULOpcodesD.AccessAckData.asUInt
io.tl_o.d_param := 0.U
io.tl_o.d_size := tl_i_reg.a_size
io.tl_o.d_source := tl_i_reg.a_source
io.tl_o.d_sink := 1.U
io.tl_o.d_data := saved_value
io.tl_o.d_error := false.B
}
is (0.U /* PutFullData */) {
saved_value := tl_i_reg.a_data
io.tl_o.d_opcode := kelvin.TLULOpcodesD.AccessAck.asUInt
io.tl_o.d_param := 0.U
io.tl_o.d_size := tl_i_reg.a_size
io.tl_o.d_source := tl_i_reg.a_source
io.tl_o.d_sink := 1.U
io.tl_o.d_data := 0.U
io.tl_o.d_error := false.B
}
is (1.U /* PutPartialData */) {
/* no-op */
}
}
}
}
```