Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@ output/

# logs
*.log
*.json.log

# generated reports and traces
*.report.json
tmp-generated-*
**/collected_reports*/
**/sweep_reports*/
**/sweep_reports_micro*/
latest_collected_reports

# local build artifacts
/main

# Python caches
__pycache__/
*.pyc

.venv/
venv/
venv/
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ go run ./verify/cmd/verify-histogram # outputs histogram_verification_report
## Architecture (How it fits together)
- **Tile/Core** (`core/`): Instruction emulator + cycle-accurate send/recv paths on Akita ports; traces dataflow/memory events. Opcodes implemented in `core/emu.go`, state in `core/core.go`.
- **CGRA device** (`cgra/`, `config/`): Mesh wiring of tiles; configurable memory mode (`simple`, `shared`, `local`) via `config.DeviceBuilder`. Uses Akita direct connections and optional shared memory controllers.
- **Shared SRAM** (`config/`, `core/`): `memory_mode: "shared"` with `shared_memory_model: "banked"` models a shared SRAM scratchpad with per-bank conflict timing for compiler-style blocking `LOAD`/`STORE`; see `docs/shared-memory.md`.
- **Driver** (`api/driver.go`): Maps per-PE kernels, feeds inputs, collects outputs, and ticks the simulation engine. Supports preload/read of per-PE memory.
- **Verification fast path** (`verify/`): Static lint (STRUCT/TIMING) + functional simulator + report generator. Mirrors opcode semantics without timing/backpressure; CLIs in `verify/cmd/`.
- **Testbench** (`test/Zeonica_Testbench` submodule): Reference kernels (AXPY, histogram, etc.) consumed by verify and simulation tests.
Expand Down Expand Up @@ -81,4 +82,4 @@ flowchart LR
- If adding opcodes, update `core/emu.go` and `verify/funcsim.go`, plus unit tests in `verify/`.

## License
See `LICENSE` for details.
See `LICENSE` for details.
30 changes: 26 additions & 4 deletions api/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,28 @@ package api
import "github.com/sarchlab/akita/v4/sim"

type defaultPortFactory struct {
incomingBufCap int
outgoingBufCap int
}

func (f defaultPortFactory) make(c sim.Component, name string) sim.Port {
return sim.NewPort(c, 1, 1, name)
incoming := f.incomingBufCap
if incoming <= 0 {
incoming = 1
}
outgoing := f.outgoingBufCap
if outgoing <= 0 {
outgoing = 1
}
return sim.NewPort(c, incoming, outgoing, name)
}

// DriverBuilder creates a new instance of Driver.
type DriverBuilder struct {
engine sim.Engine
freq sim.Freq
engine sim.Engine
freq sim.Freq
portIncomingBufferCap int
portOutgoingBufferCap int
}

// WithEngine sets the engine.
Expand All @@ -27,10 +39,20 @@ func (b DriverBuilder) WithFreq(freq sim.Freq) DriverBuilder {
return b
}

// WithPortBufferDepth configures driver boundary-port incoming/outgoing capacity.
func (b DriverBuilder) WithPortBufferDepth(incoming, outgoing int) DriverBuilder {
b.portIncomingBufferCap = incoming
b.portOutgoingBufferCap = outgoing
return b
}

// Build create a driver.
func (b DriverBuilder) Build(name string) Driver {
d := &driverImpl{
portFactory: defaultPortFactory{},
portFactory: defaultPortFactory{
incomingBufCap: b.portIncomingBufferCap,
outgoingBufCap: b.portOutgoingBufferCap,
},
}

d.TickingComponent = sim.NewTickingComponent(name, b.engine, b.freq, d)
Expand Down
29 changes: 29 additions & 0 deletions api/builder_microarch_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package api

import (
"testing"

"github.com/sarchlab/akita/v4/sim"
)

func TestDriverBuilderWithPortBufferDepth(t *testing.T) {
engine := sim.NewSerialEngine()
driver := DriverBuilder{}.
WithEngine(engine).
WithFreq(1*sim.GHz).
WithPortBufferDepth(3, 5).
Build("Driver")

impl, ok := driver.(*driverImpl)
if !ok {
t.Fatalf("expected *driverImpl, got %T", driver)
}

factory, ok := impl.portFactory.(defaultPortFactory)
if !ok {
t.Fatalf("expected defaultPortFactory, got %T", impl.portFactory)
}
if factory.incomingBufCap != 3 || factory.outgoingBufCap != 5 {
t.Fatalf("unexpected driver port caps: in=%d out=%d", factory.incomingBufCap, factory.outgoingBufCap)
}
}
9 changes: 8 additions & 1 deletion api/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type Driver interface {

//
ReadMemory(x int, y int, addr uint32) uint32
ReadSharedMemory(x int, y int, addr uint32) uint32

// Run will run all the tasks that have been added to the driver.
Run()
Expand Down Expand Up @@ -80,6 +81,11 @@ func (d *driverImpl) ReadMemory(x int, y int, addr uint32) uint32 {
return tile.GetMemory(x, y, addr)
}

func (d *driverImpl) ReadSharedMemory(x int, y int, addr uint32) uint32 {
tile := d.device.GetTile(x, y)
return tile.ReadSharedMemory(x, y, addr)
}

// Tick runs the driver for one cycle.
func (d *driverImpl) Tick() (madeProgress bool) {
madeProgress = d.doFeedIn() || madeProgress
Expand Down Expand Up @@ -142,7 +148,8 @@ func (d *driverImpl) doOneFeedInTask(task *feedInTask) bool {
err := port.Send(msg)
//fmt.Println(msg)
if err != nil {
panic("CGRA cannot handle the data rate")
// Keep task pending when downstream is temporarily back-pressured.
continue
}

timeValue := float64(d.Engine.CurrentTime() * 1e9)
Expand Down
48 changes: 48 additions & 0 deletions api/feedin_backpressure_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package api

import (
"testing"

gomock "github.com/golang/mock/gomock"
"github.com/sarchlab/akita/v4/sim"
)

func TestDoOneFeedInTaskBackpressureDoesNotPanic(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

engine := sim.NewSerialEngine()
d := &driverImpl{}
d.TickingComponent = sim.NewTickingComponent("Driver", engine, 1*sim.GHz, d)

port := NewMockPort(ctrl)
port.EXPECT().CanSend().Return(true).Times(2)
port.EXPECT().Name().Return("mock-port").AnyTimes()
port.EXPECT().AsRemote().Return(sim.RemotePort("driver-local")).AnyTimes()
port.EXPECT().Send(gomock.Any()).Return(sim.NewSendError()).Times(1)
port.EXPECT().Send(gomock.Any()).Return(nil).Times(1)

task := &feedInTask{
data: []uint32{7},
localPorts: []sim.Port{port},
remotePorts: []sim.RemotePort{sim.RemotePort("device-remote")},
stride: 1,
color: 0,
rounds: 1,
portRounds: []int{0},
}

if progressed := d.doOneFeedInTask(task); progressed {
t.Fatal("expected no progress when Send returns backpressure error")
}
if task.portRounds[0] != 0 {
t.Fatalf("expected round to stay 0 after backpressure, got %d", task.portRounds[0])
}

if progressed := d.doOneFeedInTask(task); !progressed {
t.Fatal("expected progress once backpressure clears")
}
if task.portRounds[0] != 1 {
t.Fatalf("expected round to advance to 1, got %d", task.portRounds[0])
}
}
14 changes: 14 additions & 0 deletions api/mock_cgra_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cgra/cgra.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ type Tile interface {
GetMemory(x int, y int, addr uint32) uint32
WriteMemory(x int, y int, data uint32, baseAddr uint32)
WriteSharedMemory(x int, y int, data []byte, baseAddr uint32)
ReadSharedMemory(x int, y int, addr uint32) uint32
GetTileX() int
GetTileY() int
GetRetVal() uint32
Expand Down
27 changes: 26 additions & 1 deletion cgra/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,26 @@ func (d Data) First() uint32 {
return d.Data[0]
}

// LaneCount returns the number of lanes carried by this value.
func (d Data) LaneCount() int {
return len(d.Data)
}

// IsScalar reports whether the value is a single-lane scalar.
func (d Data) IsScalar() bool {
return len(d.Data) <= 1
}

// Clone returns a deep copy of the value container.
func (d Data) Clone() Data {
if len(d.Data) == 0 {
return Data{Pred: d.Pred}
}
cloned := make([]uint32, len(d.Data))
copy(cloned, d.Data)
return Data{Data: cloned, Pred: d.Pred}
}

// WithPred returns a copy with the given predicate flag.
func (d Data) WithPred(pred bool) Data {
d.Pred = pred
Expand All @@ -32,5 +52,10 @@ func (d Data) WithPred(pred bool) Data {

// FromSlice constructs a Data from a slice and optional predicate.
func FromSlice(vals []uint32, pred bool) Data {
return Data{Data: vals, Pred: pred}
if len(vals) == 0 {
return Data{Pred: pred}
}
cloned := make([]uint32, len(vals))
copy(cloned, vals)
return Data{Data: cloned, Pred: pred}
}
Loading
Loading