diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 19404dd..53b4bd2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,19 +12,18 @@ jobs: uses: actions/checkout@v2 - name: Setup Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: - go-version: "stable" + go-version-file: go.mod - name: Build run: go build ./... - # - name: Lint - # uses: golangci/golangci-lint-action@v3 - # with: - # skip-pkg-cache: true - # version: "latest" - # args: --timeout=10m + - name: Lint + uses: golangci/golangci-lint-action@v6 + with: + version: v1.64.8 + args: --timeout=10m - name: Install Ginkgo run: go install github.com/onsi/ginkgo/v2/ginkgo diff --git a/.golangci.yml b/.golangci.yml index c38b09d..71dced4 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -18,24 +18,7 @@ run: # list of build tags, all linters use it. Default is empty list. build-tags: [] - # which dirs to skip: issues from them won't be reported; - # can use regexp here: generated.*, regexp is applied on full path; - # default value is empty list, but default dirs are skipped independently - # from this option's value (see skip-dirs-use-default). - skip-dirs: [] - - # default is true. Enables skipping of directories: - # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ - skip-dirs-use-default: true - - # which files to skip: they will be analyzed, but issues from them - # won't be reported. Default value is empty list, but there is - # no need to include all autogenerated files, we confidently recognize - # autogenerated files. If it's not please let us know. - skip-files: - - ".*\\.my\\.go$" - - go: "1.20" + go: "1.24" # by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules": # If invoked with -mod=readonly, the go command is disallowed from the implicit @@ -47,17 +30,6 @@ run: # the dependency descriptions in go.mod. # modules-download-mode: release -# output configuration options -output: - # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" - format: colored-line-number - - # print lines of code with issue, default is true - print-issued-lines: true - - # print linter name in the end of issue text, default is true - print-linter-name: true - # all available settings of specific linters linters-settings: errcheck: @@ -69,23 +41,17 @@ linters-settings: # default is false: such cases aren't reported by default. check-blank: false - # [deprecated] comma-separated list of pairs of the form pkg:regex - # the regex is used to ignore names within pkg. (default "fmt:.*"). - # see https://github.com/kisielk/errcheck#the-deprecated-method for details - ignore: fmt:.*,io/ioutil:^Read.* - - # path to a file containing a list of functions to exclude from checking - # see https://github.com/kisielk/errcheck#excluding-functions for details - # exclude: /path/to/file.txt + # list of functions to exclude from checking + exclude-functions: + - fmt.Fprint + - fmt.Fprintf + - fmt.Fprintln funlen: lines: 60 statements: 40 govet: - # report about shadowed variables - check-shadowing: true - # settings per analyzer settings: printf: # analyzer name, run `go tool vet help` to see all analyzers @@ -103,10 +69,6 @@ linters-settings: - shadow disable-all: false - golint: - # minimal confidence for issues, default is 0.8 - min-confidence: 0.8 - gofmt: # simplify code: gofmt with `-s` option, true by default simplify: true @@ -124,10 +86,6 @@ linters-settings: # minimal code complexity to report, 30 by default (but we recommend 10-20) min-complexity: 10 - maligned: - # print struct with more effective memory layout or not, false by default - suggest-new: true - dupl: # tokens count to trigger issue, 150 by default threshold: 100 @@ -138,15 +96,6 @@ linters-settings: # minimal occurrences count to trigger, 3 by default min-occurrences: 3 - depguard: - list-type: blacklist - include-go-root: false - packages: - - github.com/sirupsen/logrus - packages-with-error-messages: - # specify an error message to output when a blacklisted package is used - github.com/sirupsen/logrus: "logging is allowed only by logutils.Log" - misspell: # Correct spellings using locale preferences for US or UK. # Default is to use a neutral variety of English. @@ -162,13 +111,6 @@ linters-settings: # tab width in spaces. Default to 1. tab-width: 1 - unused: - # treat code as a program (not a library) and report unused exported identifiers; default is false. - # XXX: if you enable this setting, unused will report a lot of false-positives in text editors: - # if it's called for subdir of a project it can't find funcs usages. All text editor integrations - # with golangci-lint call it on a directory with the changed file. - check-exported: false - unparam: # Inspect exported functions, default is false. Set to true if no external program/library imports your code. # XXX: if you enable this setting, unparam will report a lot of false-positives in text editors: @@ -231,20 +173,6 @@ linters-settings: revive: confidence: 0.8 # Do not complain if there is no package comment in main packages. - wsl: - # If true append is only allowed to be cuddled if appending value is - # matching variables, fields or types on line above. Default is true. - strict-append: true - # Allow calls and assignments to be cuddled as long as the lines have any - # matching variables, fields or types. Default is true. - allow-assign-and-call: true - # Allow multiline assignments to be cuddled. Default is true. - allow-multiline-assign: true - # Allow case blocks to end with a whitespace. - allow-case-traling-whitespace: true - # Allow declarations (var) to be cuddled. - allow-cuddle-declarations: false - linters: disable-all: true enable: @@ -258,7 +186,7 @@ linters: - misspell - nakedret - prealloc - - exportloopref + - copyloopvar - unconvert - whitespace - gosimple @@ -268,6 +196,11 @@ linters: fast: false issues: + exclude-dirs: [] + exclude-dirs-use-default: true + exclude-files: + - ".*\\.my\\.go$" + # List of regexps of issue texts to exclude, empty list by default. # But independently from this option we use default exclude patterns, # it can be disabled by `exclude-use-default: false`. To list all @@ -324,8 +257,3 @@ issues: # Show only new issues created after git revision `REV` new-from-rev: "" - -service: - golangci-lint-version: 1.20.x # use the fixed version to not introduce new linters unexpectedly - prepare: - - echo "here I can run custom commands, but no preparation needed for this repo" diff --git a/api/driver.go b/api/driver.go index 42cc9ce..41fa379 100644 --- a/api/driver.go +++ b/api/driver.go @@ -62,7 +62,7 @@ type driverImpl struct { collectTasks [4][]*collectTask //Four Directions } -// struct for manually mapping different kernel to different PE +// PerPEKernels maps each PE coordinate to a kernel/program payload. type PerPEKernels map[[2]int]string func (d *driverImpl) PreloadMemory(x int, y int, data uint32, baseAddr uint32) { @@ -115,23 +115,26 @@ func (d *driverImpl) removeFinishedFeedInTasks() { func (d *driverImpl) doOneFeedInTask(task *feedInTask) bool { madeProgress := false - canSendAll := true - for _, port := range task.localPorts { + for i, port := range task.localPorts { + if task.portRounds[i] >= task.rounds { + continue + } if !port.CanSend() { - canSendAll = false - break + continue } - } - if !canSendAll { - return false - } + dataIndex := task.portRounds[i]*task.stride + i + if dataIndex < 0 || dataIndex >= len(task.data) { + panic(fmt.Sprintf( + "feed task index out of range: idx=%d len=%d stride=%d port=%d round=%d", + dataIndex, len(task.data), task.stride, i, task.portRounds[i], + )) + } - for i, port := range task.localPorts { msg := cgra.MoveMsgBuilder{}. WithSrc(port.AsRemote()). WithDst(task.remotePorts[i]). - WithData(cgra.NewScalar(task.data[task.round*task.stride+i])). + WithData(cgra.NewScalar(task.data[dataIndex])). WithColor(task.color). WithSendTime(d.Engine.CurrentTime()). // Set the current engine time here Build() @@ -145,15 +148,15 @@ func (d *driverImpl) doOneFeedInTask(task *feedInTask) bool { core.Trace("DataFlow", "Behavior", "FeedIn", slog.Float64("Time", float64(d.Engine.CurrentTime()*1e9)), - "Data", task.data[task.round*task.stride+i], + "Data", task.data[dataIndex], "Color", task.color, "From", port.Name(), "To", task.remotePorts[i], ) + task.portRounds[i]++ madeProgress = true } - task.round++ return madeProgress } @@ -171,43 +174,61 @@ func (d *driverImpl) doCollect() bool { } func (d *driverImpl) doOneCollectTask(task *collectTask) bool { - if !d.allDataReady(task) { - return false - } + madeProgress := false + for i, port := range task.ports { + if task.portRounds[i] >= task.rounds { + continue + } + item := port.PeekIncoming() + if item == nil { + continue + } - //fmt.Printf("\033[31mCollect Task: %v\033[0m\n", task) + msg, ok := item.(*cgra.MoveMsg) + if !ok { + continue + } + if msg.Color != task.color { + continue + } - for i, port := range task.ports { - msg := port.RetrieveIncoming().(*cgra.MoveMsg) - task.data[task.round*task.stride+i] = msg.Data.First() - // in red + port.RetrieveIncoming() + dataIndex := task.portRounds[i]*task.stride + i + if dataIndex < 0 || dataIndex >= len(task.data) { + panic(fmt.Sprintf( + "collect task index out of range: idx=%d len=%d stride=%d port=%d round=%d", + dataIndex, len(task.data), task.stride, i, task.portRounds[i], + )) + } + task.data[dataIndex] = msg.Data.First() core.Trace("DataFlow", "Behavior", "Collect", slog.Float64("Time", float64(d.Engine.CurrentTime()*1e9)), - "Data", task.data[task.round*task.stride+i], + "Data", msg.Data.First(), "Pred", msg.Data.Pred, "Color", task.color, "From", task.ports[i].Name(), "To", "None", ) - } - task.round++ + task.portRounds[i]++ + madeProgress = true + } - return true + return madeProgress } -func (*driverImpl) allDataReady(task *collectTask) bool { - for _, port := range task.ports { - item := port.PeekIncoming() - if item == nil { - return false - } - } +// func (*driverImpl) allDataReady(task *collectTask) bool { +// for _, port := range task.ports { +// item := port.PeekIncoming() +// if item == nil { +// return false +// } +// } - return true -} +// return true +// } func (d *driverImpl) removeFinishedCollectTasks() { for i := 0; i < 4; i++ { @@ -298,11 +319,51 @@ type feedInTask struct { stride int color int - round int + rounds int + + // Port-wise progress allows opportunistic feed-in and avoids global barriers. + portRounds []int +} + +func validateTaskLayout(dataLen, stride, portCount int, taskName string) int { + if stride <= 0 { + panic(fmt.Sprintf("%s: stride must be > 0", taskName)) + } + if portCount <= 0 { + panic(fmt.Sprintf("%s: no ports to process", taskName)) + } + if stride < portCount { + panic(fmt.Sprintf( + "%s: stride (%d) must be >= number of ports (%d)", + taskName, stride, portCount, + )) + } + if dataLen%stride != 0 { + panic(fmt.Sprintf( + "%s: data length (%d) must be a multiple of stride (%d)", + taskName, dataLen, stride, + )) + } + rounds := dataLen / stride + if rounds > 0 { + maxIndex := (rounds-1)*stride + (portCount - 1) + if maxIndex >= dataLen { + panic(fmt.Sprintf( + "%s: invalid layout (max index %d out of data length %d)", + taskName, maxIndex, dataLen, + )) + } + } + return rounds } func (t *feedInTask) isFinished() bool { - return t.round >= len(t.data)/t.stride + for _, r := range t.portRounds { + if r < t.rounds { + return false + } + } + return true } func (d *driverImpl) FeedIn( @@ -318,6 +379,8 @@ func (d *driverImpl) FeedIn( stride: stride, color: d.getColorIndex(color), } + task.rounds = validateTaskLayout(len(task.data), task.stride, len(task.localPorts), "FeedIn") + task.portRounds = make([]int, len(task.localPorts)) sideIndex := int(side) d.feedInTasks[sideIndex] = append(d.feedInTasks[sideIndex], task) @@ -360,11 +423,19 @@ type collectTask struct { ports []sim.Port stride int color int - round int + rounds int + + // Port-wise progress allows opportunistic collect and avoids global barriers. + portRounds []int } func (t *collectTask) isFinished() bool { - return t.round >= len(t.data)/t.stride + for _, r := range t.portRounds { + if r < t.rounds { + return false + } + } + return true } func (d *driverImpl) Collect( @@ -380,6 +451,8 @@ func (d *driverImpl) Collect( stride: stride, color: d.getColorIndex(color), } + task.rounds = validateTaskLayout(len(task.data), task.stride, len(task.ports), "Collect") + task.portRounds = make([]int, len(task.ports)) sideIndex := int(side) //fmt.Println(color) diff --git a/api/driver_internal_test.go b/api/driver_internal_test.go index 64fefbb..dd0ef45 100644 --- a/api/driver_internal_test.go +++ b/api/driver_internal_test.go @@ -114,6 +114,8 @@ var _ = Describe("Driver", func() { Expect(driver.feedInTasks[sideIndex][0].remotePorts). To(HaveLen(3)) Expect(driver.feedInTasks[sideIndex][0].stride).To(Equal(3)) + Expect(driver.feedInTasks[sideIndex][0].rounds).To(Equal(4)) + Expect(driver.feedInTasks[sideIndex][0].portRounds).To(Equal([]int{0, 0, 0})) }) It("should handle Collect API", func() { @@ -126,6 +128,26 @@ var _ = Describe("Driver", func() { Expect(driver.collectTasks[sideIndex][0].data).To(Equal(data)) Expect(driver.collectTasks[sideIndex][0].ports). To(HaveLen(3)) + Expect(driver.collectTasks[sideIndex][0].rounds).To(Equal(2)) + Expect(driver.collectTasks[sideIndex][0].portRounds).To(Equal([]int{0, 0, 0})) + }) + + It("should reject invalid FeedIn layout", func() { + Expect(func() { + driver.FeedIn([]uint32{1, 2, 3, 4, 5}, cgra.North, [2]int{0, 3}, 3, "R") + }).To(Panic()) + Expect(func() { + driver.FeedIn([]uint32{1, 2, 3, 4}, cgra.North, [2]int{0, 3}, 2, "R") + }).To(Panic()) + }) + + It("should reject invalid Collect layout", func() { + Expect(func() { + driver.Collect(make([]uint32, 5), cgra.North, [2]int{0, 3}, 3, "R") + }).To(Panic()) + Expect(func() { + driver.Collect(make([]uint32, 4), cgra.North, [2]int{0, 3}, 2, "R") + }).To(Panic()) }) It("should do feed in", func() { @@ -165,7 +187,8 @@ var _ = Describe("Driver", func() { remotePorts: remotePorts, stride: 3, color: 0, // R - round: 0, + rounds: 2, + portRounds: []int{0, 0, 0}, }, } @@ -188,6 +211,58 @@ var _ = Describe("Driver", func() { Expect(driver.feedInTasks[sideIndex]).To(BeEmpty()) }) + It("should do feed in opportunistically", func() { + localPort1 := portFactory.ports["Driver.DeviceNorth[0]"] + localPort2 := portFactory.ports["Driver.DeviceNorth[1]"] + localPort3 := portFactory.ports["Driver.DeviceNorth[2]"] + + localPort1.EXPECT().CanSend().Return(true).AnyTimes() + localPort2.EXPECT().CanSend().Return(false).Times(1) + localPort2.EXPECT().CanSend().Return(true).AnyTimes() + localPort3.EXPECT().CanSend().Return(true).AnyTimes() + + sent1 := make([]uint32, 0, 2) + sent2 := make([]uint32, 0, 2) + sent3 := make([]uint32, 0, 2) + localPort1.EXPECT().Send(gomock.Any()).DoAndReturn(func(msg *cgra.MoveMsg) error { + sent1 = append(sent1, msg.Data.First()) + return nil + }).Times(2) + localPort2.EXPECT().Send(gomock.Any()).DoAndReturn(func(msg *cgra.MoveMsg) error { + sent2 = append(sent2, msg.Data.First()) + return nil + }).Times(2) + localPort3.EXPECT().Send(gomock.Any()).DoAndReturn(func(msg *cgra.MoveMsg) error { + sent3 = append(sent3, msg.Data.First()) + return nil + }).Times(2) + + sideIndex := int(cgra.North) + driver.feedInTasks[sideIndex] = []*feedInTask{ + { + data: []uint32{1, 2, 3, 4, 5, 6}, + localPorts: []sim.Port{localPort1, localPort2, localPort3}, + remotePorts: []sim.RemotePort{"remotePort1", "remotePort2", "remotePort3"}, + stride: 3, + color: 0, + rounds: 2, + portRounds: []int{0, 0, 0}, + }, + } + + driver.Tick() + Expect(driver.feedInTasks[sideIndex][0].portRounds).To(Equal([]int{1, 0, 1})) + + driver.Tick() + Expect(driver.feedInTasks[sideIndex][0].portRounds).To(Equal([]int{2, 1, 2})) + + driver.Tick() + Expect(driver.feedInTasks[sideIndex]).To(BeEmpty()) + Expect(sent1).To(Equal([]uint32{1, 4})) + Expect(sent2).To(Equal([]uint32{2, 5})) + Expect(sent3).To(Equal([]uint32{3, 6})) + }) + It("should do collect", func() { localPort1 := portFactory.ports["Driver.DeviceNorth[0]"] localPort2 := portFactory.ports["Driver.DeviceNorth[1]"] @@ -203,22 +278,20 @@ var _ = Describe("Driver", func() { ports: ports, stride: 3, color: 0, // R - round: 0, + rounds: 2, + portRounds: []int{ + 0, 0, 0, + }, }, } - // Mock PeekIncoming and RetrieveIncoming for first round - // Note: allDataReady checks PeekIncoming multiple times (once per port), - // then doOneCollectTask calls RetrieveIncoming for each port + // Mock PeekIncoming and RetrieveIncoming for first round. msg1 := cgra.MoveMsgBuilder{}.WithData(cgra.NewScalar(1)).Build() msg2 := cgra.MoveMsgBuilder{}.WithData(cgra.NewScalar(2)).Build() msg3 := cgra.MoveMsgBuilder{}.WithData(cgra.NewScalar(3)).Build() - // allDataReady will call PeekIncoming for all ports (at least once each) - // Since allDataReady may be called multiple times, we use AnyTimes - localPort1.EXPECT().PeekIncoming().Return(msg1).AnyTimes() - localPort2.EXPECT().PeekIncoming().Return(msg2).AnyTimes() - localPort3.EXPECT().PeekIncoming().Return(msg3).AnyTimes() - // Then doOneCollectTask will call RetrieveIncoming for each port + localPort1.EXPECT().PeekIncoming().Return(msg1).Times(1) + localPort2.EXPECT().PeekIncoming().Return(msg2).Times(1) + localPort3.EXPECT().PeekIncoming().Return(msg3).Times(1) localPort1.EXPECT().RetrieveIncoming().Return(msg1).Times(1) localPort2.EXPECT().RetrieveIncoming().Return(msg2).Times(1) localPort3.EXPECT().RetrieveIncoming().Return(msg3).Times(1) @@ -229,9 +302,9 @@ var _ = Describe("Driver", func() { msg4 := cgra.MoveMsgBuilder{}.WithData(cgra.NewScalar(4)).Build() msg5 := cgra.MoveMsgBuilder{}.WithData(cgra.NewScalar(5)).Build() msg6 := cgra.MoveMsgBuilder{}.WithData(cgra.NewScalar(6)).Build() - localPort1.EXPECT().PeekIncoming().Return(msg4).AnyTimes() - localPort2.EXPECT().PeekIncoming().Return(msg5).AnyTimes() - localPort3.EXPECT().PeekIncoming().Return(msg6).AnyTimes() + localPort1.EXPECT().PeekIncoming().Return(msg4).Times(1) + localPort2.EXPECT().PeekIncoming().Return(msg5).Times(1) + localPort3.EXPECT().PeekIncoming().Return(msg6).Times(1) localPort1.EXPECT().RetrieveIncoming().Return(msg4).Times(1) localPort2.EXPECT().RetrieveIncoming().Return(msg5).Times(1) localPort3.EXPECT().RetrieveIncoming().Return(msg6).Times(1) @@ -241,6 +314,62 @@ var _ = Describe("Driver", func() { Expect(driver.collectTasks[sideIndex]).To(BeEmpty()) Expect(data).To(Equal([]uint32{1, 2, 3, 4, 5, 6})) }) + + It("should do collect opportunistically", func() { + localPort1 := portFactory.ports["Driver.DeviceNorth[0]"] + localPort2 := portFactory.ports["Driver.DeviceNorth[1]"] + localPort3 := portFactory.ports["Driver.DeviceNorth[2]"] + + data := make([]uint32, 6) + sideIndex := int(cgra.North) + driver.collectTasks[sideIndex] = []*collectTask{ + { + data: data, + ports: []sim.Port{localPort1, localPort2, localPort3}, + stride: 3, + color: 0, + rounds: 2, + portRounds: []int{0, 0, 0}, + }, + } + + msg1 := cgra.MoveMsgBuilder{}.WithData(cgra.NewScalar(1)).WithColor(0).Build() + msg2 := cgra.MoveMsgBuilder{}.WithData(cgra.NewScalar(2)).WithColor(0).Build() + msg3 := cgra.MoveMsgBuilder{}.WithData(cgra.NewScalar(3)).WithColor(0).Build() + msg4 := cgra.MoveMsgBuilder{}.WithData(cgra.NewScalar(4)).WithColor(0).Build() + msg5 := cgra.MoveMsgBuilder{}.WithData(cgra.NewScalar(5)).WithColor(0).Build() + msg6 := cgra.MoveMsgBuilder{}.WithData(cgra.NewScalar(6)).WithColor(0).Build() + + gomock.InOrder( + localPort1.EXPECT().PeekIncoming().Return(msg1), + localPort1.EXPECT().RetrieveIncoming().Return(msg1), + localPort1.EXPECT().PeekIncoming().Return(msg4), + localPort1.EXPECT().RetrieveIncoming().Return(msg4), + ) + gomock.InOrder( + localPort2.EXPECT().PeekIncoming().Return(nil), + localPort2.EXPECT().PeekIncoming().Return(msg2), + localPort2.EXPECT().RetrieveIncoming().Return(msg2), + localPort2.EXPECT().PeekIncoming().Return(msg5), + localPort2.EXPECT().RetrieveIncoming().Return(msg5), + ) + gomock.InOrder( + localPort3.EXPECT().PeekIncoming().Return(msg3), + localPort3.EXPECT().RetrieveIncoming().Return(msg3), + localPort3.EXPECT().PeekIncoming().Return(msg6), + localPort3.EXPECT().RetrieveIncoming().Return(msg6), + ) + + driver.Tick() + Expect(driver.collectTasks[sideIndex][0].portRounds).To(Equal([]int{1, 0, 1})) + + driver.Tick() + Expect(driver.collectTasks[sideIndex][0].portRounds).To(Equal([]int{2, 1, 2})) + + driver.Tick() + Expect(driver.collectTasks[sideIndex]).To(BeEmpty()) + Expect(data).To(Equal([]uint32{1, 2, 3, 4, 5, 6})) + }) }) func expectPortsToSend( @@ -263,6 +392,7 @@ func expectPortsToSend( } } +//nolint:unused func expectPortsToRecv( ports []*MockPort, data []uint32, diff --git a/cgra/cgra.go b/cgra/cgra.go index 7a5e8c3..c55d359 100644 --- a/cgra/cgra.go +++ b/cgra/cgra.go @@ -9,21 +9,35 @@ import ( type Side int const ( + // North is the top cardinal side. North Side = iota + // East is the right cardinal side. East + // South is the bottom cardinal side. South + // West is the left cardinal side. West + // NorthEast is the upper-right diagonal side. NorthEast + // NorthWest is the upper-left diagonal side. NorthWest + // SouthEast is the lower-right diagonal side. SouthEast + // SouthWest is the lower-left diagonal side. SouthWest + // Router is the logical router port side. Router + // Dummy1 is an auxiliary placeholder side. Dummy1 + // Dummy2 is an auxiliary placeholder side. Dummy2 + // Dummy3 is an auxiliary placeholder side. Dummy3 ) // Name returns the name of the side. +// +//nolint:gocyclo func (s Side) Name() string { switch s { case North: diff --git a/cgra/data.go b/cgra/data.go index 3787231..6c1a25a 100644 --- a/cgra/data.go +++ b/cgra/data.go @@ -1,5 +1,6 @@ package cgra +// Data is the value container transferred between CGRA components. type Data struct { Data []uint32 Pred bool @@ -10,7 +11,7 @@ func NewScalar(v uint32) Data { return Data{Data: []uint32{v}, Pred: true} } -// NewScalar creates a Data that wraps a single uint32 value with Pred=true by default. +// NewScalarWithPred creates a scalar Data value with an explicit predicate. func NewScalarWithPred(v uint32, pred bool) Data { return Data{Data: []uint32{v}, Pred: pred} } diff --git a/cgra/msg.go b/cgra/msg.go index 5239ac0..b52e3b7 100644 --- a/cgra/msg.go +++ b/cgra/msg.go @@ -61,7 +61,7 @@ func (m MoveMsgBuilder) WithData(data Data) MoveMsgBuilder { return m } -// WithData sets the color of the msg +// WithColor sets the color of the msg. func (m MoveMsgBuilder) WithColor(color int) MoveMsgBuilder { m.color = color return m diff --git a/config/config.go b/config/config.go index 3afe3ab..ea02b64 100644 --- a/config/config.go +++ b/config/config.go @@ -90,6 +90,7 @@ func (d DeviceBuilder) Build(name string) cgra.Device { return dev } +//nolint:funlen func (d DeviceBuilder) createSharedMemory(dev *device) { if d.memoryMode == "shared" { // Create shared memory controller @@ -230,7 +231,6 @@ func (d DeviceBuilder) connectTilePorts(srcTile *tile, srcSide cgra.Side, dstTile *tile, dstSide cgra.Side) { - srcPort := srcTile.GetPort(srcSide) dstPort := dstTile.GetPort(dstSide) diff --git a/core/builder.go b/core/builder.go index ef533a5..8025a48 100644 --- a/core/builder.go +++ b/core/builder.go @@ -25,17 +25,21 @@ func (b Builder) WithFreq(freq sim.Freq) Builder { return b } +// WithExitAddr sets the shared exit flag address. func (b Builder) WithExitAddr(exitAddr *bool) Builder { b.exitAddr = exitAddr return b } +// WithRetValAddr sets the shared return value address. func (b Builder) WithRetValAddr(retValAddr *uint32) Builder { b.retValAddr = retValAddr return b } // Build creates a core. +// +//nolint:funlen func (b Builder) Build(name string) *Core { c := &Core{} diff --git a/core/core.go b/core/core.go index f3a6b2c..8b32db5 100644 --- a/core/core.go +++ b/core/core.go @@ -1,3 +1,4 @@ +//nolint:funlen,lll,whitespace package core import ( @@ -14,6 +15,7 @@ type portPair struct { remote sim.RemotePort } +// Core models one CGRA compute core with local state and ports. type Core struct { *sim.TickingComponent @@ -23,32 +25,35 @@ type Core struct { emu instEmulator } +// GetRetVal returns the shared return value. func (c *Core) GetRetVal() uint32 { return *c.state.retVal } +// GetTileX returns the x coordinate of the core. func (c *Core) GetTileX() int { return int(c.state.TileX) } +// GetTileY returns the y coordinate of the core. func (c *Core) GetTileY() int { return int(c.state.TileY) } +// GetTickingComponent returns the ticking component wrapper. func (c *Core) GetTickingComponent() sim.Component { return c.TickingComponent } -// get memory +// GetMemory returns the memory value at addr for this core. func (c *Core) GetMemory(x int, y int, addr uint32) uint32 { if x == int(c.state.TileX) && y == int(c.state.TileY) { return c.state.Memory[addr] - } else { - panic("Invalid Tile") } + panic("Invalid Tile") } -// write memory +// WriteMemory writes one memory word at baseAddr for this core. func (c *Core) WriteMemory(x int, y int, data uint32, baseAddr uint32) { //fmt.Printf("Core [%d][%d] receive WriteMemory(x=%d, y=%d)\n", c.state.TileX, c.state.TileY, x, y) if x == int(c.state.TileX) && y == int(c.state.TileY) { @@ -67,6 +72,7 @@ func (c *Core) WriteMemory(x int, y int, data uint32, baseAddr uint32) { } } +// SetRemotePort connects a side to a remote port endpoint. func (c *Core) SetRemotePort(side cgra.Side, remote sim.RemotePort) { c.ports[side].remote = remote } @@ -85,11 +91,11 @@ func (c *Core) MapProgram(program interface{}, x int, y int) { // Tick runs the program for one cycle. func (c *Core) Tick() (madeProgress bool) { - madeProgress = c.doSend() || madeProgress + madeProgress = c.doRecv() || madeProgress // madeProgress = c.AlwaysPart() || madeProgress // madeProgress = c.emu.runRoutingRules(&c.state) || madeProgress madeProgress = c.runProgram() || madeProgress - madeProgress = c.doRecv() || madeProgress + madeProgress = c.doSend() || madeProgress return madeProgress } @@ -137,16 +143,15 @@ func (c *Core) doSend() bool { // handle the memory request if c.state.SendBufHeadBusy[c.emu.getColorIndex("R")][cgra.Router] { // only one port, must be Router-red - if c.state.IsToWriteMemory { msg := mem.WriteReqBuilder{}. WithAddress(uint64(c.state.AddrBuf)). WithData(makeBytesFromUint32(c.state.SendBufHead[c.emu.getColorIndex("R")][cgra.Router].First())). - WithSrc(c.ports[cgra.Side(cgra.Router)].local.AsRemote()). - WithDst(c.ports[cgra.Side(cgra.Router)].remote). + WithSrc(c.ports[cgra.Router].local.AsRemote()). + WithDst(c.ports[cgra.Router].remote). Build() - err := c.ports[cgra.Side(cgra.Router)].local.Send(msg) + err := c.ports[cgra.Router].local.Send(msg) if err != nil { return madeProgress } @@ -164,12 +169,12 @@ func (c *Core) doSend() bool { } else { msg := mem.ReadReqBuilder{}. WithAddress(uint64(c.state.AddrBuf)). - WithSrc(c.ports[cgra.Side(cgra.Router)].local.AsRemote()). - WithDst(c.ports[cgra.Side(cgra.Router)].remote). + WithSrc(c.ports[cgra.Router].local.AsRemote()). + WithDst(c.ports[cgra.Router].remote). WithByteSize(4). Build() - err := c.ports[cgra.Side(cgra.Router)].local.Send(msg) + err := c.ports[cgra.Router].local.Send(msg) if err != nil { return madeProgress } @@ -236,47 +241,46 @@ func (c *Core) doRecv() bool { } } - item := c.ports[cgra.Side(cgra.Router)].local.PeekIncoming() + item := c.ports[cgra.Router].local.PeekIncoming() if item == nil { return madeProgress - } else { - if c.state.RecvBufHeadReady[c.emu.getColorIndex("R")][cgra.Router] { - return madeProgress - } + } + if c.state.RecvBufHeadReady[c.emu.getColorIndex("R")][cgra.Router] { + return madeProgress + } - // if msg is DataReadyRsp, then the data is ready - if msg, ok := item.(*mem.DataReadyRsp); ok { - c.state.RecvBufHeadReady[c.emu.getColorIndex("R")][cgra.Router] = true - c.state.RecvBufHead[c.emu.getColorIndex("R")][cgra.Router] = cgra.NewScalar(convert4BytesToUint32(msg.Data)) + // if msg is DataReadyRsp, then the data is ready + if msg, ok := item.(*mem.DataReadyRsp); ok { + c.state.RecvBufHeadReady[c.emu.getColorIndex("R")][cgra.Router] = true + c.state.RecvBufHead[c.emu.getColorIndex("R")][cgra.Router] = cgra.NewScalar(convert4BytesToUint32(msg.Data)) - Trace("Memory", - "Behavior", "Recv", - "Time", float64(c.Engine.CurrentTime()*1e9), - "Data", msg.Data, - "Src", msg.Src, - "Dst", msg.Dst, - "Pred", c.state.RecvBufHead[c.emu.getColorIndex("R")][cgra.Router].Pred, - "Color", "R", - ) + Trace("Memory", + "Behavior", "Recv", + "Time", float64(c.Engine.CurrentTime()*1e9), + "Data", msg.Data, + "Src", msg.Src, + "Dst", msg.Dst, + "Pred", c.state.RecvBufHead[c.emu.getColorIndex("R")][cgra.Router].Pred, + "Color", "R", + ) - c.ports[cgra.Side(cgra.Router)].local.RetrieveIncoming() - madeProgress = true - } else if msg, ok := item.(*mem.WriteDoneRsp); ok { - c.state.RecvBufHeadReady[c.emu.getColorIndex("R")][cgra.Router] = true - c.state.RecvBufHead[c.emu.getColorIndex("R")][cgra.Router] = cgra.NewScalar(0) + c.ports[cgra.Router].local.RetrieveIncoming() + madeProgress = true + } else if msg, ok := item.(*mem.WriteDoneRsp); ok { + c.state.RecvBufHeadReady[c.emu.getColorIndex("R")][cgra.Router] = true + c.state.RecvBufHead[c.emu.getColorIndex("R")][cgra.Router] = cgra.NewScalar(0) - Trace("Memory", - "Behavior", "Recv", - "Time", float64(c.Engine.CurrentTime()*1e9), - "Src", msg.Src, - "Dst", msg.Dst, - "Pred", c.state.RecvBufHead[c.emu.getColorIndex("R")][cgra.Router].Pred, - "Color", "R", - ) + Trace("Memory", + "Behavior", "Recv", + "Time", float64(c.Engine.CurrentTime()*1e9), + "Src", msg.Src, + "Dst", msg.Dst, + "Pred", c.state.RecvBufHead[c.emu.getColorIndex("R")][cgra.Router].Pred, + "Color", "R", + ) - c.ports[cgra.Side(cgra.Router)].local.RetrieveIncoming() - madeProgress = true - } + c.ports[cgra.Router].local.RetrieveIncoming() + madeProgress = true } return madeProgress diff --git a/core/data.go b/core/data.go index 60e3294..3388df7 100644 --- a/core/data.go +++ b/core/data.go @@ -1,5 +1,6 @@ package core +// Data is the value container transferred between core components. type Data struct { Data []uint32 Pred bool @@ -10,7 +11,7 @@ func NewScalar(v uint32) Data { return Data{Data: []uint32{v}, Pred: true} } -// NewScalar creates a Data that wraps a single uint32 value with Pred=true by default. +// NewScalarWithPred creates a scalar Data value with an explicit predicate. func NewScalarWithPred(v uint32, pred bool) Data { return Data{Data: []uint32{v}, Pred: pred} } diff --git a/core/emu.go b/core/emu.go index d1cc453..2eb4dfb 100644 --- a/core/emu.go +++ b/core/emu.go @@ -1,3 +1,4 @@ +//nolint:funlen,gocyclo,lll,unused,gosimple package core import ( @@ -10,10 +11,13 @@ import ( "github.com/sarchlab/zeonica/cgra" ) +// OpMode controls how instruction groups are scheduled. type OpMode int const ( + // SyncOp executes one instruction group synchronously. SyncOp OpMode = iota + // AsyncOp executes operations asynchronously with reservation tracking. AsyncOp ) @@ -23,19 +27,21 @@ type routingRule struct { color string } +// Trigger describes routing trigger metadata. type Trigger struct { src [12]bool color int branch string } +// ReservationState tracks pending operations and operand usage. type ReservationState struct { ReservationMap map[int]bool // to show whether each operation of a instruction group is finished. OpToExec int RefCountRuntime map[string]int // to record the left times to be used of each source opearand. deep copied from RefCount } -// return bool, True means the operand is still in use, False means the operand is not in use anymore +// DecrementRefCount decrements runtime operand use count and reports if still in use. func (r *ReservationState) DecrementRefCount(opr Operand, state *coreState) bool { key := opr.Impl + opr.Color if _, ok := r.RefCountRuntime[key]; ok { @@ -47,28 +53,25 @@ func (r *ReservationState) DecrementRefCount(opr Operand, state *coreState) bool return false } return true - } else { - // something wrong, raise error - panic("invalid operand in DecrementRefCount") } + // something wrong, raise error + panic("invalid operand in DecrementRefCount") } +// SetRefCount initializes runtime operand reference counters for an instruction group. func (r *ReservationState) SetRefCount(ig InstructionGroup, state *coreState) { for _, op := range ig.Operations { for _, opr := range op.SrcOperands.Operands { if state.Directions[opr.Impl] { key := opr.Impl + opr.Color - if _, ok := r.RefCountRuntime[key]; ok { - r.RefCountRuntime[key]++ - } else { - r.RefCountRuntime[key] = 1 - } + r.RefCountRuntime[key]++ } } // only port in the src is needed to be considered } } +// SetReservationMap marks operations in the instruction group as pending. func (r *ReservationState) SetReservationMap(ig InstructionGroup, state *coreState) { for i := 0; i < len(ig.Operations); i++ { r.ReservationMap[i] = true @@ -131,9 +134,9 @@ func (i instEmulator) RunInstructionGroup(cinst InstructionGroup, state *coreSta } prevPC := state.PCInBlock prevCount := state.CurrReservationState.OpToExec - progress_sync := false + progressSync := false if state.Mode == SyncOp { - progress_sync = i.RunInstructionGroupWithSyncOps(cinst, state, time) + progressSync = i.RunInstructionGroupWithSyncOps(cinst, state, time) } else if state.Mode == AsyncOp { i.RunInstructionGroupWithAsyncOps(cinst, state, time) } else { @@ -146,7 +149,7 @@ func (i instEmulator) RunInstructionGroup(cinst InstructionGroup, state *coreSta if state.Mode == AsyncOp { if state.CurrReservationState.OpToExec == 0 { // this instruction group is finished if state.NextPCInBlock == -1 { // nobody elect PC other than +4 - state.PCInBlock += 1 + state.PCInBlock++ } else { // there is a jump state.PCInBlock = state.NextPCInBlock // not set block, in case of index out of range @@ -163,7 +166,7 @@ func (i instEmulator) RunInstructionGroup(cinst InstructionGroup, state *coreSta state.NextPCInBlock = -1 } // else, this group is not finished, PC stays the same } else if state.Mode == SyncOp { - if progress_sync { + if progressSync { if state.NextPCInBlock == -1 { print("PC+4 for PC=", state.PCInBlock, " X:", state.TileX, " Y:", state.TileY, "\n") print("Instruction at PC=", state.PCInBlock, " is ", state.SelectedBlock.InstructionGroups[state.PCInBlock].Operations[0].OpCode, "\n") @@ -192,7 +195,7 @@ func (i instEmulator) RunInstructionGroup(cinst InstructionGroup, state *coreSta return false } } else if state.Mode == SyncOp { - return progress_sync + return progressSync } else { panic("invalid mode") } @@ -339,7 +342,7 @@ func (i instEmulator) RunOperation(inst Operation, state *coreState, time float6 "LLS": i.runLLS, "SHL": i.runLLS, // SHL is an alias for LLS "LRS": i.runLRS, - "MUL": i.runMul, // MULI + "MUL": i.runMul, // MULI "MUL_ADD": i.runMulAdd, // dst = src0 * src1 + src2 (systolic MAC) "DIV": i.runDiv, "OR": i.runOR, @@ -441,9 +444,7 @@ func (i instEmulator) readOperand(operand Operand, state *coreState) (value cgra //fmt.Println("[ReadOperand] read", value, "from port", operand.Impl, ":", value, "@(", state.TileX, ",", state.TileY, ")") } else { // try to convert into int - if strings.HasPrefix(operand.Impl, "#") { - operand.Impl = strings.TrimPrefix(operand.Impl, "#") - } + operand.Impl = strings.TrimPrefix(operand.Impl, "#") num, err := strconv.Atoi(operand.Impl) if err == nil { value = cgra.NewScalar(uint32(num)) diff --git a/core/emu_unit_test.go b/core/emu_unit_test.go index d46791a..5bf1b06 100644 --- a/core/emu_unit_test.go +++ b/core/emu_unit_test.go @@ -41,7 +41,7 @@ var _ = Describe("InstEmulator", func() { } }) - var mul_const_inst = InstructionGroup{ + var mulConstInst = InstructionGroup{ Operations: []Operation{ { OpCode: "MUL", @@ -66,10 +66,10 @@ var _ = Describe("InstEmulator", func() { s.Registers[0] = cgra.NewScalar(5) s.SelectedBlock = &EntryBlock{ InstructionGroups: []InstructionGroup{ - mul_const_inst, + mulConstInst, }, } - ie.RunInstructionGroup(mul_const_inst, &s, 0) + ie.RunInstructionGroup(mulConstInst, &s, 0) Expect(s.Registers[1]).To(Equal(cgra.NewScalar(15))) Expect(s.PCInBlock).To(Equal(int32(0))) // in the test, there is no SelectedBlock, so emu will not increase the PCInBlock diff --git a/core/program.go b/core/program.go index 58641e6..4d1db00 100644 --- a/core/program.go +++ b/core/program.go @@ -1,3 +1,4 @@ +//nolint:funlen,gocyclo,lll package core import ( @@ -10,7 +11,7 @@ import ( "gopkg.in/yaml.v3" ) -// CoreProgram represents a program for a specific core +// YAMLCoreProgram represents one core program section in YAML. type YAMLCoreProgram struct { Row int `yaml:"row"` Column int `yaml:"column"` @@ -25,12 +26,13 @@ type YAMLEntry struct { InstructionGroups []YAMLInstructionGroup `yaml:"instructions"` } -// Instruction represents a single instruction in the YAML +// YAMLInstructionGroup represents one grouped instruction bundle in YAML. type YAMLInstructionGroup struct { Operations []YAMLOperation `yaml:"operations"` IndexPerII int `yaml:"index_per_ii"` } +// YAMLOperation represents one operation in a YAML instruction group. type YAMLOperation struct { OpCode string `yaml:"opcode"` SrcOperands []YAMLOperand `yaml:"src_operands"` @@ -59,17 +61,20 @@ type YAMLRoot struct { ArrayConfig ArrayConfig `yaml:"array_config"` } +// Program is the internal executable representation for one core. type Program struct { EntryBlocks []EntryBlock CompiledII int } +// EntryBlock is one entry block in a core program. type EntryBlock struct { EntryCond OperandList // not used InstructionGroups []InstructionGroup Label map[string]int } +// InstructionGroup is one schedulable instruction group. type InstructionGroup struct { Operations []Operation RefCount map[string]int @@ -91,6 +96,7 @@ func (ig *InstructionGroup) String() string { } } +// Operation is one executable operation in an instruction group. type Operation struct { // The raw text of the instruction. OpCode string @@ -100,10 +106,12 @@ type Operation struct { InvalidIterations int // Invalid iterations from YAML file } +// OperandList wraps source or destination operands for an operation. type OperandList struct { Operands []Operand } +// Operand describes a single operand with implementation and color metadata. type Operand struct { Flag bool Color string @@ -665,6 +673,7 @@ func LoadProgramFileFromASM(programFilePath string) map[string]Program { } } +// PrintProgram prints a human-readable view of one program. func PrintProgram(program Program) { for _, entryBlock := range program.EntryBlocks { fmt.Println(entryBlock.Label) diff --git a/core/program_asm_test.go b/core/program_asm_test.go index 2d521d1..3e2f99a 100644 --- a/core/program_asm_test.go +++ b/core/program_asm_test.go @@ -1,3 +1,4 @@ +//nolint:funlen package core import ( diff --git a/core/util.go b/core/util.go index 8b3659f..1e3644c 100644 --- a/core/util.go +++ b/core/util.go @@ -9,14 +9,20 @@ import ( ) const ( - PrintToggle = false - LevelTrace slog.Level = slog.LevelInfo + 1 + // PrintToggle enables verbose state table printing in debugging. + PrintToggle = false + // LevelTrace is a custom trace level above info. + LevelTrace slog.Level = slog.LevelInfo + 1 ) +// Trace writes a trace-level structured log record. func Trace(msg string, args ...any) { slog.Log(context.Background(), LevelTrace, msg, args...) } +// PrintState prints a formatted snapshot of core runtime state. +// +//nolint:gocyclo,funlen func PrintState(state *coreState) { if !PrintToggle { return @@ -153,6 +159,7 @@ func PrintState(state *coreState) { fmt.Println("================================================") } +// LogState writes a structured debug checkpoint for the core state. func LogState(state *coreState) { slog.Debug("StateCheckpoint", "X", state.TileX, "Y", state.TileY, diff --git a/report/report.go b/report/report.go index 4151f9a..5b142b0 100644 --- a/report/report.go +++ b/report/report.go @@ -1,3 +1,4 @@ +// Package report generates and prints execution summaries from trace logs. package report import ( @@ -11,6 +12,7 @@ import ( "sort" ) +// GenerateOptions controls report generation behavior from a trace log. type GenerateOptions struct { TestName string LogPath string @@ -21,6 +23,7 @@ type GenerateOptions struct { MismatchCount *int } +// Report is the aggregate execution summary derived from a trace log. type Report struct { TestName string `json:"testName,omitempty"` LogPath string `json:"logPath"` @@ -40,11 +43,13 @@ type Report struct { TopHotTiles []TopHotTile `json:"topHotTiles"` } +// GridInfo describes the grid size used by the workload. type GridInfo struct { Width int `json:"width"` Height int `json:"height"` } +// TileStats stores per-tile metrics in the generated report. type TileStats struct { X int `json:"x"` Y int `json:"y"` @@ -58,6 +63,7 @@ type TileStats struct { TotalEvents int64 `json:"totalEvents"` } +// TopHotTile is a ranked hot tile summary entry. type TopHotTile struct { X int `json:"x"` Y int `json:"y"` @@ -96,6 +102,9 @@ type tileAccumulator struct { var tileEndpointPattern = regexp.MustCompile(`Device\.Tile\[(\d+)\]\[(\d+)\]\.Core\.`) +// GenerateFromLog builds a report by parsing a JSON trace log. +// +//nolint:gocyclo,funlen func GenerateFromLog(opts GenerateOptions) (Report, error) { if opts.LogPath == "" { return Report{}, fmt.Errorf("log path is required") @@ -110,7 +119,7 @@ func GenerateFromLog(opts GenerateOptions) (Report, error) { if err != nil { return Report{}, fmt.Errorf("open log file: %w", err) } - defer file.Close() + defer func() { _ = file.Close() }() tileData := make(map[tileCoord]*tileAccumulator) globalCycleSet := make(map[int64]struct{}) @@ -259,6 +268,7 @@ func GenerateFromLog(opts GenerateOptions) (Report, error) { return report, nil } +// SaveJSON writes a report as pretty-printed JSON. func SaveJSON(report Report, path string) error { content, err := json.MarshalIndent(report, "", " ") if err != nil { @@ -272,10 +282,12 @@ func SaveJSON(report Report, path string) error { return nil } +// PrintSummary prints a compact report summary to stdout. func PrintSummary(report Report) { PrintSummaryToWriter(report, os.Stdout) } +// PrintSummaryToWriter prints a compact report summary to the writer. func PrintSummaryToWriter(report Report, w io.Writer) { fmt.Fprintln(w, "========================") fmt.Fprintln(w, "Zeonica Report Summary") diff --git a/runtimecfg/report.go b/runtimecfg/report.go new file mode 100644 index 0000000..e32d46d --- /dev/null +++ b/runtimecfg/report.go @@ -0,0 +1,67 @@ +package runtimecfg + +import ( + "fmt" + + "github.com/sarchlab/zeonica/report" +) + +const defaultTopN = 5 + +// BuildReportOptions builds report options from resolved runtime configuration. +func (r *Runtime) BuildReportOptions(topN int, passed *bool, mismatchCount *int) (report.GenerateOptions, error) { + if !r.Config.LoggingEnabled { + return report.GenerateOptions{}, fmt.Errorf("logging is disabled, cannot build report options from trace log") + } + if r.Config.LogPath == "" { + return report.GenerateOptions{}, fmt.Errorf("log path is empty, cannot build report options") + } + if topN <= 0 { + topN = defaultTopN + } + + return report.GenerateOptions{ + TestName: r.Config.TestName, + LogPath: r.Config.LogPath, + GridWidth: r.Config.Columns, + GridHeight: r.Config.Rows, + TopN: topN, + Passed: passed, + MismatchCount: mismatchCount, + }, nil +} + +// DefaultReportPath returns the default output path for report JSON. +func (r *Runtime) DefaultReportPath() string { + return fmt.Sprintf("%s.report.json", r.Config.TestName) +} + +// GenerateAndSaveReport generates report data and persists it as JSON. +func (r *Runtime) GenerateAndSaveReport(topN int, passed *bool, mismatchCount *int) (report.Report, string, error) { + opts, err := r.BuildReportOptions(topN, passed, mismatchCount) + if err != nil { + return report.Report{}, "", err + } + + result, err := report.GenerateFromLog(opts) + if err != nil { + return report.Report{}, "", fmt.Errorf("generate report from log: %w", err) + } + + reportPath := r.DefaultReportPath() + if err := report.SaveJSON(result, reportPath); err != nil { + return report.Report{}, "", fmt.Errorf("save report json: %w", err) + } + + return result, reportPath, nil +} + +// GenerateSaveAndPrintReport generates, saves, and prints summary in one call. +func (r *Runtime) GenerateSaveAndPrintReport(topN int, passed *bool, mismatchCount *int) (string, error) { + result, reportPath, err := r.GenerateAndSaveReport(topN, passed, mismatchCount) + if err != nil { + return "", err + } + report.PrintSummary(result) + return reportPath, nil +} diff --git a/runtimecfg/runtime.go b/runtimecfg/runtime.go new file mode 100644 index 0000000..1e744d6 --- /dev/null +++ b/runtimecfg/runtime.go @@ -0,0 +1,256 @@ +package runtimecfg + +import ( + "fmt" + "log/slog" + "os" + "regexp" + "strconv" + "strings" + + "github.com/sarchlab/akita/v4/sim" + "github.com/sarchlab/zeonica/api" + "github.com/sarchlab/zeonica/cgra" + "github.com/sarchlab/zeonica/config" +) + +const ( + defaultRows = 4 + defaultColumns = 4 + defaultExecutionModel = "serial" + defaultDriverName = "Driver" + defaultDeviceName = "Device" + defaultLogTemplate = ".json.log" +) + +var freqPattern = regexp.MustCompile(`^([0-9]+)\s*(ghz|mhz|khz|hz)$`) + +// ResolvedConfig is the executable runtime configuration after defaults/resolution. +type ResolvedConfig struct { + TestName string + Rows int + Columns int + ExecutionModel string + DriverName string + DriverFreq sim.Freq + DeviceName string + DeviceFreq sim.Freq + BindToArchitecture bool + LoggingEnabled bool + LogPath string +} + +// BuildOverrides allows optional size override when not binding to architecture. +type BuildOverrides struct { + Width int + Height int +} + +// Runtime holds initialized simulator objects and resolved configuration. +type Runtime struct { + Spec ArchSpec + SpecPath string + Config ResolvedConfig + Engine sim.Engine + Driver api.Driver + Device cgra.Device +} + +// LoadRuntime loads arch spec, resolves config, and builds runtime objects. +func LoadRuntime(specPath, testName string) (*Runtime, error) { + spec, err := Load(specPath) + if err != nil { + return nil, err + } + + cfg, err := Resolve(spec, testName) + if err != nil { + return nil, err + } + + rt, err := BuildRuntime(cfg, nil) + if err != nil { + return nil, err + } + rt.Spec = spec + rt.SpecPath = specPath + return rt, nil +} + +// Resolve resolves defaults and validates runtime values from ArchSpec. +func Resolve(spec ArchSpec, testName string) (ResolvedConfig, error) { + resolved := ResolvedConfig{ + TestName: normalizeTestName(testName), + Rows: defaultOrPositive(spec.CGRADefaults.Rows, defaultRows), + Columns: defaultOrPositive(spec.CGRADefaults.Columns, defaultColumns), + ExecutionModel: defaultOrString(spec.Simulator.ExecutionModel, defaultExecutionModel), + DriverName: defaultOrString(spec.Simulator.Driver.Name, defaultDriverName), + DeviceName: defaultOrString(spec.Simulator.Device.Name, defaultDeviceName), + BindToArchitecture: defaultOrBool(spec.Simulator.Device.BindToArchitecture, true), + LoggingEnabled: defaultOrBool(spec.Simulator.Logging.Enabled, true), + } + + driverFreq, err := parseFrequency(spec.Simulator.Driver.Frequency, 1*sim.GHz) + if err != nil { + return ResolvedConfig{}, fmt.Errorf("resolve driver frequency: %w", err) + } + resolved.DriverFreq = driverFreq + + deviceFreq, err := parseFrequency(spec.Simulator.Device.Frequency, 1*sim.GHz) + if err != nil { + return ResolvedConfig{}, fmt.Errorf("resolve device frequency: %w", err) + } + resolved.DeviceFreq = deviceFreq + + logTemplate := defaultOrString(spec.Simulator.Logging.File, defaultLogTemplate) + resolved.LogPath = resolveLogPath(logTemplate, resolved.TestName) + + return resolved, nil +} + +// BuildRuntime builds engine, driver, and device from a resolved config. +func BuildRuntime(cfg ResolvedConfig, overrides *BuildOverrides) (*Runtime, error) { + executionModel := strings.ToLower(strings.TrimSpace(cfg.ExecutionModel)) + var engine sim.Engine + switch executionModel { + case "", "serial": + engine = sim.NewSerialEngine() + default: + return nil, fmt.Errorf("unsupported execution_model %q (currently only serial is supported)", cfg.ExecutionModel) + } + + width := cfg.Columns + height := cfg.Rows + if !cfg.BindToArchitecture && overrides != nil { + if overrides.Width > 0 { + width = overrides.Width + } + if overrides.Height > 0 { + height = overrides.Height + } + } + + driver := api.DriverBuilder{}. + WithEngine(engine). + WithFreq(cfg.DriverFreq). + Build(cfg.DriverName) + + device := config.DeviceBuilder{}. + WithEngine(engine). + WithFreq(cfg.DeviceFreq). + WithWidth(width). + WithHeight(height). + Build(cfg.DeviceName) + + driver.RegisterDevice(device) + + return &Runtime{ + Config: cfg, + Engine: engine, + Driver: driver, + Device: device, + }, nil +} + +// InitTraceLogger initializes the default slog JSON trace logger. +func (r *Runtime) InitTraceLogger(level slog.Leveler) (*os.File, error) { + if !r.Config.LoggingEnabled { + return nil, nil + } + + file, err := os.Create(r.Config.LogPath) + if err != nil { + return nil, fmt.Errorf("create trace log file: %w", err) + } + + handler := slog.NewJSONHandler(file, &slog.HandlerOptions{ + Level: level, + }) + slog.SetDefault(slog.New(handler)) + return file, nil +} + +// CloseTraceLog flushes and closes the trace log file. +func CloseTraceLog(file *os.File) error { + if file == nil { + return nil + } + if err := file.Sync(); err != nil { + return fmt.Errorf("sync trace log: %w", err) + } + if err := file.Close(); err != nil { + return fmt.Errorf("close trace log: %w", err) + } + return nil +} + +func defaultOrPositive(value, fallback int) int { + if value > 0 { + return value + } + return fallback +} + +func defaultOrString(value, fallback string) string { + trimmed := strings.TrimSpace(value) + if trimmed == "" { + return fallback + } + return trimmed +} + +func defaultOrBool(value *bool, fallback bool) bool { + if value == nil { + return fallback + } + return *value +} + +func normalizeTestName(testName string) string { + trimmed := strings.TrimSpace(testName) + if trimmed == "" { + return "test" + } + return trimmed +} + +func resolveLogPath(template, testName string) string { + resolved := strings.ReplaceAll(template, "", testName) + if strings.TrimSpace(resolved) == "" { + return strings.ReplaceAll(defaultLogTemplate, "", testName) + } + return resolved +} + +func parseFrequency(input string, fallback sim.Freq) (sim.Freq, error) { + text := strings.ToLower(strings.TrimSpace(input)) + if text == "" { + return fallback, nil + } + + matches := freqPattern.FindStringSubmatch(text) + if len(matches) != 3 { + return 0, fmt.Errorf("invalid frequency format %q, expected like 1GHz/500MHz", input) + } + + value, err := strconv.ParseInt(matches[1], 10, 64) + if err != nil { + return 0, fmt.Errorf("parse frequency value: %w", err) + } + if value <= 0 { + return 0, fmt.Errorf("frequency must be positive") + } + + switch matches[2] { + case "ghz": + return sim.Freq(value) * sim.GHz, nil + case "mhz": + return sim.Freq(value) * sim.MHz, nil + case "khz": + return sim.Freq(value) * sim.KHz, nil + case "hz": + return sim.Freq(value), nil + default: + return 0, fmt.Errorf("unsupported frequency unit %q", matches[2]) + } +} diff --git a/runtimecfg/spec.go b/runtimecfg/spec.go new file mode 100644 index 0000000..1325203 --- /dev/null +++ b/runtimecfg/spec.go @@ -0,0 +1,75 @@ +// Package runtimecfg loads and resolves simulator runtime settings from arch spec. +package runtimecfg + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v3" +) + +// ArchSpec captures the architecture and simulator settings from arch_spec.yaml. +// It intentionally keeps an inline map at each level to allow forward-compatible +// extension without changing callers. +type ArchSpec struct { + CGRADefaults CGRADefaults `yaml:"cgra_defaults"` + Simulator Simulator `yaml:"simulator"` + Extra map[string]any `yaml:",inline"` +} + +// CGRADefaults contains default CGRA shape settings from arch spec. +type CGRADefaults struct { + Rows int `yaml:"rows"` + Columns int `yaml:"columns"` + Extra map[string]any `yaml:",inline"` +} + +// Simulator contains simulator runtime settings from arch spec. +type Simulator struct { + ExecutionModel string `yaml:"execution_model"` + Logging SimulatorLogging `yaml:"logging"` + Driver NamedComponent `yaml:"driver"` + Device DeviceComponent `yaml:"device"` + Extra map[string]any `yaml:",inline"` +} + +// SimulatorLogging configures trace logging behavior. +type SimulatorLogging struct { + Enabled *bool `yaml:"enabled"` + File string `yaml:"file"` + Extra map[string]any `yaml:",inline"` +} + +// NamedComponent contains shared component naming/frequency fields. +type NamedComponent struct { + Name string `yaml:"name"` + Frequency string `yaml:"frequency"` + Extra map[string]any `yaml:",inline"` +} + +// DeviceComponent defines simulator device-specific settings. +type DeviceComponent struct { + Name string `yaml:"name"` + Frequency string `yaml:"frequency"` + BindToArchitecture *bool `yaml:"bind_to_architecture"` + Extra map[string]any `yaml:",inline"` +} + +// Load reads and parses an architecture spec YAML file. +func Load(path string) (ArchSpec, error) { + if path == "" { + return ArchSpec{}, fmt.Errorf("arch spec path is required") + } + + data, err := os.ReadFile(path) + if err != nil { + return ArchSpec{}, fmt.Errorf("read arch spec: %w", err) + } + + var spec ArchSpec + if err := yaml.Unmarshal(data, &spec); err != nil { + return ArchSpec{}, fmt.Errorf("parse arch spec: %w", err) + } + + return spec, nil +} diff --git a/test/add/arith_test.go b/test/add/arith_test.go index 51b9a3a..00c08e4 100644 --- a/test/add/arith_test.go +++ b/test/add/arith_test.go @@ -1,3 +1,4 @@ +//nolint:funlen package main import ( diff --git a/test/arch_spec/arch_spec.yaml b/test/arch_spec/arch_spec.yaml new file mode 100644 index 0000000..acb94d3 --- /dev/null +++ b/test/arch_spec/arch_spec.yaml @@ -0,0 +1,60 @@ +architecture: + name: "NeuraMultiCgra" + version: "1.0" + +multi_cgra_defaults: + base_topology: "mesh" + rows: 1 + columns: 1 + +cgra_defaults: + rows: 4 + columns: 4 + ctrl_mem_items: 20 + base_topology: "mesh" + +tile_defaults: + num_registers: 32 + fu_types: ["add", "mul", "div", "fadd", "fmul", "fdiv", "logic", "cmp", "sel", "type_conv", "vfmul", "fadd_fadd", "fmul_fadd", "grant", "loop_control", "phi", "constant", "mem", "return", "mem_indexed", "alloca", "shift"] + +link_defaults: + latency: 1 + bandwidth: 32 + +link_overrides: + +# Removes tiles that are actually used by the FIR kernel in the default mapping. +# This forces the mapper to find alternative placements. +tile_overrides: + # Removes tile(1,1) which is used in the default mapping. + - cgra_x: 0 + cgra_y: 0 + tile_x: 1 + tile_y: 1 + existence: false + + # Removes tile(0,1) which is used in the default mapping. + - cgra_x: 0 + cgra_y: 0 + tile_x: 0 + tile_y: 1 + existence: false + +extensions: + crossbar: false + +simulator: + execution_model: "serial" + + logging: + enabled: true + file: ".json.log" + + driver: + name: "Driver" + frequency: "1GHz" + + device: + name: "Device" + frequency: "1GHz" + bind_to_architecture: true diff --git a/test/cf/cf_test.go b/test/cf/cf_test.go index c07639c..c4a9ee2 100644 --- a/test/cf/cf_test.go +++ b/test/cf/cf_test.go @@ -1,3 +1,4 @@ +//nolint:funlen package main import ( @@ -19,12 +20,9 @@ func TestCmpExOperation(t *testing.T) { length := 5 // Create test data - src := make([]uint32, length) + src := []uint32{1, 2, 9, 9, 0, 0, 3, 5, 6, 7} dst := make([]uint32, length) - // Generate random test data - src = []uint32{1, 2, 9, 9, 0, 0, 3, 5, 6, 7} - // Create simulation engine engine := sim.NewSerialEngine() diff --git a/test/mem/mem_test.go b/test/mem/mem_test.go index 5c53158..27f7595 100644 --- a/test/mem/mem_test.go +++ b/test/mem/mem_test.go @@ -1,3 +1,4 @@ +//nolint:funlen,whitespace package main import ( @@ -19,12 +20,9 @@ func TestLoadStoreOperation(t *testing.T) { length := 10 // Create test data - src := make([]uint32, length) + src := []uint32{1, 2, 9, 9, 0, 0, 3, 5, 6, 7} dst := make([]uint32, length) - // Generate random test data - src = []uint32{1, 2, 9, 9, 0, 0, 3, 5, 6, 7} - // Create simulation engine engine := sim.NewSerialEngine() @@ -197,8 +195,7 @@ func TestGoAround(t *testing.T) { width := 2 height := 2 - src := make([]uint32, 5) - src = []uint32{114, 514, 19, 19, 810} + src := []uint32{114, 514, 19, 19, 810} dst := make([]uint32, 5) srcI := make([]int32, 5) dstI := make([]int32, 5) @@ -269,8 +266,7 @@ func TestSharedMemory(t *testing.T) { width := 2 height := 2 - src := make([]uint32, 5) - src = []uint32{114, 514, 19, 19, 810} + src := []uint32{114, 514, 19, 19, 810} dst1 := make([]uint32, 5) dst2 := make([]uint32, 5) dst3 := make([]uint32, 5) diff --git a/test/misc/spread_test.go b/test/misc/spread_test.go index fd9e1f3..e5cf94e 100644 --- a/test/misc/spread_test.go +++ b/test/misc/spread_test.go @@ -1,3 +1,4 @@ +//nolint:funlen package main import ( @@ -19,15 +20,12 @@ func TestSpreadOperation(t *testing.T) { length := 10 // Create test data - src := make([]uint32, length) + src := []uint32{1, 2, 9, 9, 0, 0, 3, 5, 6, 7} dst1 := make([]uint32, length) dst2 := make([]uint32, length) dst3 := make([]uint32, length) dst4 := make([]uint32, length) - // Generate random test data - src = []uint32{1, 2, 9, 9, 0, 0, 3, 5, 6, 7} - // Create simulation engine engine := sim.NewSerialEngine() diff --git a/test/pred/cf_test.go b/test/pred/cf_test.go index fa390c6..1cffe00 100644 --- a/test/pred/cf_test.go +++ b/test/pred/cf_test.go @@ -1,3 +1,4 @@ +//nolint:funlen package main import ( @@ -15,9 +16,7 @@ import ( ) func TestPhiGpredOperation(t *testing.T) { - // log to file - f, err := os.Create("phi_gpred.json.log") if err != nil { panic(err) diff --git a/test/testbench/axpy/main.go b/test/testbench/axpy/main.go index 09632e6..11c1f53 100644 --- a/test/testbench/axpy/main.go +++ b/test/testbench/axpy/main.go @@ -2,13 +2,14 @@ package main import ( "fmt" - "log/slog" "os" + "path/filepath" + "runtime" + "strings" "github.com/sarchlab/akita/v4/sim" - "github.com/sarchlab/zeonica/api" - "github.com/sarchlab/zeonica/config" "github.com/sarchlab/zeonica/core" + "github.com/sarchlab/zeonica/runtimecfg" ) func computeAxpy(a uint32, x []uint32, y []uint32) []uint32 { @@ -23,25 +24,15 @@ func computeAxpy(a uint32, x []uint32, y []uint32) []uint32 { return out } -func Axpy() { - width := 4 - height := 4 - - engine := sim.NewSerialEngine() - - driver := api.DriverBuilder{}. - WithEngine(engine). - WithFreq(1 * sim.GHz). - Build("Driver") - - device := config.DeviceBuilder{}. - WithEngine(engine). - WithFreq(1 * sim.GHz). - WithWidth(width). - WithHeight(height). - Build("Device") - - driver.RegisterDevice(device) +// Axpy runs the AXPY testbench on the configured runtime. +// +//nolint:gocyclo,funlen +func Axpy(rt *runtimecfg.Runtime) int { + width := rt.Config.Columns + height := rt.Config.Rows + driver := rt.Driver + device := rt.Device + engine := rt.Engine programPath := os.Getenv("ZEONICA_PROGRAM_YAML") if programPath == "" { @@ -76,10 +67,10 @@ func Axpy() { } expected := computeAxpy(A, xData, yData) - // Mapping uses tile (2,1) for the multiplicand load and store. - // To match y = a*x + y, preload x at (2,1) and y at (2,0). - xTile := [2]int{2, 1} - yTile := [2]int{2, 0} + // Mapping uses tile (3,2) for x load/MUL and tile (2,3) for y load. + // Store happens at tile (3,3). + xTile := [2]int{3, 2} + yTile := [2]int{2, 3} for addr, val := range xData { driver.PreloadMemory(xTile[0], xTile[1], val, uint32(addr)) @@ -100,7 +91,7 @@ func Axpy() { fmt.Println("========================") fmt.Println("output memory:") - outputTile := [2]int{2, 1} + outputTile := [2]int{3, 3} outputData := make([]uint32, N) for addr := 0; addr < N; addr++ { val := driver.ReadMemory(outputTile[0], outputTile[1], uint32(addr)) @@ -121,19 +112,78 @@ func Axpy() { } else { fmt.Printf("❌ output mismatches axpy: %d\n", mismatch) } + return mismatch +} + +func resolveArchSpecPath() (string, error) { + fromEnv := strings.TrimSpace(os.Getenv("ZEONICA_ARCH_SPEC")) + if fromEnv != "" { + if _, err := os.Stat(fromEnv); err == nil { + return fromEnv, nil + } + return "", fmt.Errorf("ZEONICA_ARCH_SPEC points to a missing file: %s", fromEnv) + } + + candidates := []string{ + "test/arch_spec/arch_spec.yaml", + "../../arch_spec/arch_spec.yaml", + } + + if _, thisFile, _, ok := runtime.Caller(0); ok { + candidates = append(candidates, + filepath.Clean(filepath.Join(filepath.Dir(thisFile), "..", "..", "arch_spec", "arch_spec.yaml")), + ) + } + + seen := make(map[string]struct{}, len(candidates)) + normalized := make([]string, 0, len(candidates)) + for _, candidate := range candidates { + clean := filepath.Clean(candidate) + if _, exists := seen[clean]; exists { + continue + } + seen[clean] = struct{}{} + normalized = append(normalized, clean) + if _, err := os.Stat(clean); err == nil { + return clean, nil + } + } + + return "", fmt.Errorf("cannot locate arch spec, tried: %s", strings.Join(normalized, ", ")) } func main() { - f, err := os.Create("axpy.json.log") + const testName = "axpy" + + archSpecPath, err := resolveArchSpecPath() + if err != nil { + panic(err) + } + + rt, err := runtimecfg.LoadRuntime(archSpecPath, testName) + if err != nil { + panic(err) + } + + traceLog, err := rt.InitTraceLogger(core.LevelTrace) if err != nil { panic(err) } - defer f.Close() - handler := slog.NewJSONHandler(f, &slog.HandlerOptions{ - Level: core.LevelTrace, - }) + mismatch := Axpy(rt) + + if err := runtimecfg.CloseTraceLog(traceLog); err != nil { + panic(err) + } - slog.SetDefault(slog.New(handler)) - Axpy() + passed := mismatch == 0 + if rt.Config.LoggingEnabled { + reportPath, err := rt.GenerateSaveAndPrintReport(5, &passed, &mismatch) + if err != nil { + panic(err) + } + fmt.Printf("report saved: %s\n", reportPath) + } else { + fmt.Println("logging disabled in arch spec, skipped report generation") + } } diff --git a/test/testbench/axpy/tmp-generated-dfg.dot b/test/testbench/axpy/tmp-generated-dfg.dot index 1ae8a3b..329cf15 100644 --- a/test/testbench/axpy/tmp-generated-dfg.dot +++ b/test/testbench/axpy/tmp-generated-dfg.dot @@ -1,78 +1,46 @@ digraph DFG { rankdir=LR; node [shape=box, style=filled]; - n0 [label="GRANT_ONCE\nID=0\n(2,0) t=3", fillcolor=white]; - n1 [label="CONSTANT\nID=1\n(0,0) t=0", fillcolor=lightblue]; - n2 [label="GRANT_ONCE\nID=2\n(2,0) t=2", fillcolor=white]; - n5 [label="DATA_MOV\nID=5\n(1,0) t=4", fillcolor=lightgreen]; - n8 [label="ICMP_SGT\nID=8\n(0,0) t=1", fillcolor=white]; - n11 [label="NOT\nID=11\n(0,0) t=5", fillcolor=white]; - n12 [label="GRANT_ONCE\nID=12\n(0,0) t=2", fillcolor=white]; - n14 [label="DATA_MOV\nID=14\n(1,1) t=5", fillcolor=lightgreen]; - n16 [label="DATA_MOV\nID=16\n(1,0) t=4", fillcolor=lightgreen]; - n17 [label="GRANT_ONCE\nID=17\n(0,1) t=6", fillcolor=white]; - n18 [label="NOT\nID=18\n(1,1) t=6", fillcolor=white]; - n19 [label="GRANT_PREDICATE\nID=19\n(1,0) t=3", fillcolor=white]; - n20 [label="GRANT_PREDICATE\nID=20\n(1,0) t=5", fillcolor=white]; - n25 [label="GRANT_PREDICATE\nID=25\n(0,1) t=7", fillcolor=white]; - n26 [label="PHI_START\nID=26\n(1,0) t=4", fillcolor=white]; - n27 [label="ZEXT\nID=27\n(1,0) t=6", fillcolor=white]; - n33 [label="ADD\nID=33\n(1,1) t=5", fillcolor=white]; - n34 [label="GEP\nID=34\n(2,0) t=6", fillcolor=white]; - n35 [label="GEP\nID=35\n(2,0) t=5", fillcolor=white]; - n38 [label="DATA_MOV\nID=38\n(2,1) t=8", fillcolor=lightgreen]; - n41 [label="ICMP_EQ\nID=41\n(1,1) t=7", fillcolor=white]; - n42 [label="LOAD\nID=42\n(2,0) t=7", fillcolor=white]; - n43 [label="LOAD\nID=43\n(2,1) t=6", fillcolor=white]; - n49 [label="GRANT_PREDICATE\nID=49\n(1,1) t=10", fillcolor=white]; - n50 [label="NOT\nID=50\n(1,1) t=8", fillcolor=white]; - n51 [label="MUL\nID=51\n(2,1) t=7", fillcolor=white]; - n55 [label="PHI\nID=55\n(0,1) t=11", fillcolor=white]; - n56 [label="GRANT_PREDICATE\nID=56\n(1,1) t=9", fillcolor=white]; - n57 [label="ADD\nID=57\n(2,1) t=8", fillcolor=white]; - n61 [label="RETURN_VOID\nID=61\n(0,2) t=12", fillcolor=white]; - n62 [label="STORE\nID=62\n(2,1) t=9", fillcolor=white]; - n140001 [label="DATA_MOV\nID=140001\n(0,1) t=3", fillcolor=lightgreen]; - n160000 [label="DATA_MOV\nID=160000\n(0,0) t=3", fillcolor=lightgreen]; - n300000 [label="DATA_MOV\nID=300000\n(1,0) t=5", fillcolor=lightgreen]; - n0 -> n5; - n5 -> n20; - n34 -> n38; - n38 -> n62; - n12 -> n160000; - n160000 -> n16; - n16 -> n20; - n12 -> n140001; - n140001 -> n14; - n14 -> n18; - n26 -> n300000; - n57 -> n62; - n300000 -> n34; - n56 -> n26; - n26 -> n33; - n55 -> n61; - n20 -> n27; - n50 -> n56; - n17 -> n25; - n25 -> n55; - n12 -> n19; - n41 -> n49; - n11 -> n17; - n19 -> n26; - n49 -> n55; - n8 -> n12; - n34 -> n42; - n18 -> n25; - n8 -> n11; - n2 -> n19; - n33 -> n56; - n1 -> n8; - n26 -> n35; - n27 -> n41; - n33 -> n41; - n35 -> n43; - n41 -> n50; - n42 -> n57; - n43 -> n51; - n51 -> n57; + n0 [label="GRANT_ONCE\nID=0\n(3,2) t=0", fillcolor=white]; + n4 [label="PHI_START\nID=4\n(2,2) t=1", fillcolor=white]; + n6 [label="DATA_MOV\nID=6\n(2,3) t=2", fillcolor=lightgreen]; + n8 [label="ADD\nID=8\n(2,2) t=2", fillcolor=white]; + n9 [label="GEP\nID=9\n(2,3) t=3", fillcolor=white]; + n10 [label="GEP\nID=10\n(3,2) t=2", fillcolor=white]; + n13 [label="DATA_MOV\nID=13\n(3,3) t=5", fillcolor=lightgreen]; + n16 [label="ICMP_EQ\nID=16\n(2,2) t=3", fillcolor=white]; + n17 [label="LOAD\nID=17\n(2,3) t=4", fillcolor=white]; + n18 [label="LOAD\nID=18\n(3,2) t=3", fillcolor=white]; + n19 [label="DATA_MOV\nID=19\n(2,1) t=4", fillcolor=lightgreen]; + n24 [label="GRANT_PREDICATE\nID=24\n(2,1) t=5", fillcolor=white]; + n25 [label="NOT\nID=25\n(2,2) t=4", fillcolor=white]; + n26 [label="MUL\nID=26\n(3,2) t=4", fillcolor=white]; + n30 [label="RETURN_VOID\nID=30\n(2,1) t=6", fillcolor=white]; + n31 [label="GRANT_PREDICATE\nID=31\n(2,2) t=5", fillcolor=white]; + n32 [label="ADD\nID=32\n(3,3) t=5", fillcolor=white]; + n35 [label="STORE\nID=35\n(3,3) t=6", fillcolor=white]; + n200000 [label="DATA_MOV\nID=200000\n(2,2) t=4", fillcolor=lightgreen]; + n4 -> n6; + n6 -> n9; + n9 -> n13; + n13 -> n35; + n16 -> n19; + n19 -> n24; + n16 -> n200000; + n31 -> n4; + n26 -> n32; + n0 -> n4; + n32 -> n35; + n4 -> n8; + n4 -> n10; + n200000 -> n24; + n8 -> n31; + n8 -> n16; + n9 -> n17; + n24 -> n30; + n10 -> n18; + n25 -> n31; + n16 -> n25; + n17 -> n32; + n18 -> n26; } diff --git a/test/testbench/axpy/tmp-generated-dfg.yaml b/test/testbench/axpy/tmp-generated-dfg.yaml index 7e30008..9c4beea 100644 --- a/test/testbench/axpy/tmp-generated-dfg.yaml +++ b/test/testbench/axpy/tmp-generated-dfg.yaml @@ -1,252 +1,143 @@ nodes: - id: 0 opcode: "GRANT_ONCE" - tile_x: 2 - tile_y: 0 - time_step: 3 - - id: 1 - opcode: "CONSTANT" - tile_x: 0 - tile_y: 0 + tile_x: 3 + tile_y: 2 time_step: 0 - - id: 2 - opcode: "GRANT_ONCE" + - id: 4 + opcode: "PHI_START" tile_x: 2 - tile_y: 0 - time_step: 2 - - id: 5 - opcode: "DATA_MOV" - tile_x: 1 - tile_y: 0 - time_step: 4 - - id: 8 - opcode: "ICMP_SGT" - tile_x: 0 - tile_y: 0 + tile_y: 2 time_step: 1 - - id: 11 - opcode: "NOT" - tile_x: 0 - tile_y: 0 - time_step: 5 - - id: 12 - opcode: "GRANT_ONCE" - tile_x: 0 - tile_y: 0 - time_step: 2 - - id: 14 - opcode: "DATA_MOV" - tile_x: 1 - tile_y: 1 - time_step: 5 - - id: 16 + - id: 6 opcode: "DATA_MOV" - tile_x: 1 - tile_y: 0 - time_step: 4 - - id: 17 - opcode: "GRANT_ONCE" - tile_x: 0 - tile_y: 1 - time_step: 6 - - id: 18 - opcode: "NOT" - tile_x: 1 - tile_y: 1 - time_step: 6 - - id: 19 - opcode: "GRANT_PREDICATE" - tile_x: 1 - tile_y: 0 - time_step: 3 - - id: 20 - opcode: "GRANT_PREDICATE" - tile_x: 1 - tile_y: 0 - time_step: 5 - - id: 25 - opcode: "GRANT_PREDICATE" - tile_x: 0 - tile_y: 1 - time_step: 7 - - id: 26 - opcode: "PHI_START" - tile_x: 1 - tile_y: 0 - time_step: 4 - - id: 27 - opcode: "ZEXT" - tile_x: 1 - tile_y: 0 - time_step: 6 - - id: 33 + tile_x: 2 + tile_y: 3 + time_step: 2 + - id: 8 opcode: "ADD" - tile_x: 1 - tile_y: 1 - time_step: 5 - - id: 34 - opcode: "GEP" tile_x: 2 - tile_y: 0 - time_step: 6 - - id: 35 + tile_y: 2 + time_step: 2 + - id: 9 opcode: "GEP" tile_x: 2 - tile_y: 0 - time_step: 5 - - id: 38 + tile_y: 3 + time_step: 3 + - id: 10 + opcode: "GEP" + tile_x: 3 + tile_y: 2 + time_step: 2 + - id: 13 opcode: "DATA_MOV" - tile_x: 2 - tile_y: 1 - time_step: 8 - - id: 41 + tile_x: 3 + tile_y: 3 + time_step: 5 + - id: 16 opcode: "ICMP_EQ" - tile_x: 1 - tile_y: 1 - time_step: 7 - - id: 42 + tile_x: 2 + tile_y: 2 + time_step: 3 + - id: 17 opcode: "LOAD" tile_x: 2 - tile_y: 0 - time_step: 7 - - id: 43 + tile_y: 3 + time_step: 4 + - id: 18 opcode: "LOAD" + tile_x: 3 + tile_y: 2 + time_step: 3 + - id: 19 + opcode: "DATA_MOV" tile_x: 2 tile_y: 1 - time_step: 6 - - id: 49 + time_step: 4 + - id: 24 opcode: "GRANT_PREDICATE" - tile_x: 1 + tile_x: 2 tile_y: 1 - time_step: 10 - - id: 50 + time_step: 5 + - id: 25 opcode: "NOT" - tile_x: 1 - tile_y: 1 - time_step: 8 - - id: 51 + tile_x: 2 + tile_y: 2 + time_step: 4 + - id: 26 opcode: "MUL" + tile_x: 3 + tile_y: 2 + time_step: 4 + - id: 30 + opcode: "RETURN_VOID" tile_x: 2 tile_y: 1 - time_step: 7 - - id: 55 - opcode: "PHI" - tile_x: 0 - tile_y: 1 - time_step: 11 - - id: 56 + time_step: 6 + - id: 31 opcode: "GRANT_PREDICATE" - tile_x: 1 - tile_y: 1 - time_step: 9 - - id: 57 - opcode: "ADD" tile_x: 2 - tile_y: 1 - time_step: 8 - - id: 61 - opcode: "RETURN_VOID" - tile_x: 0 tile_y: 2 - time_step: 12 - - id: 62 + time_step: 5 + - id: 32 + opcode: "ADD" + tile_x: 3 + tile_y: 3 + time_step: 5 + - id: 35 opcode: "STORE" - tile_x: 2 - tile_y: 1 - time_step: 9 - - id: 140001 - opcode: "DATA_MOV" - tile_x: 0 - tile_y: 1 - time_step: 3 - - id: 160000 - opcode: "DATA_MOV" - tile_x: 0 - tile_y: 0 - time_step: 3 - - id: 300000 + tile_x: 3 + tile_y: 3 + time_step: 6 + - id: 200000 opcode: "DATA_MOV" - tile_x: 1 - tile_y: 0 - time_step: 5 + tile_x: 2 + tile_y: 2 + time_step: 4 edges: - - from: 0 - to: 5 - - from: 5 - to: 20 - - from: 34 - to: 38 - - from: 38 - to: 62 - - from: 12 - to: 160000 - - from: 160000 - to: 16 + - from: 4 + to: 6 + - from: 6 + to: 9 + - from: 9 + to: 13 + - from: 13 + to: 35 - from: 16 - to: 20 - - from: 12 - to: 140001 - - from: 140001 - to: 14 - - from: 14 - to: 18 - - from: 26 - to: 300000 - - from: 57 - to: 62 - - from: 300000 - to: 34 - - from: 56 - to: 26 - - from: 26 - to: 33 - - from: 55 - to: 61 - - from: 20 - to: 27 - - from: 50 - to: 56 - - from: 17 - to: 25 - - from: 25 - to: 55 - - from: 12 to: 19 - - from: 41 - to: 49 - - from: 11 - to: 17 - from: 19 - to: 26 - - from: 49 - to: 55 - - from: 8 - to: 12 - - from: 34 - to: 42 - - from: 18 - to: 25 - - from: 8 - to: 11 - - from: 2 - to: 19 - - from: 33 - to: 56 - - from: 1 - to: 8 + to: 24 + - from: 16 + to: 200000 + - from: 31 + to: 4 - from: 26 + to: 32 + - from: 0 + to: 4 + - from: 32 to: 35 - - from: 27 - to: 41 - - from: 33 - to: 41 - - from: 35 - to: 43 - - from: 41 - to: 50 - - from: 42 - to: 57 - - from: 43 - to: 51 - - from: 51 - to: 57 + - from: 4 + to: 8 + - from: 4 + to: 10 + - from: 200000 + to: 24 + - from: 8 + to: 31 + - from: 8 + to: 16 + - from: 9 + to: 17 + - from: 24 + to: 30 + - from: 10 + to: 18 + - from: 25 + to: 31 + - from: 16 + to: 25 + - from: 17 + to: 32 + - from: 18 + to: 26 diff --git a/test/testbench/axpy/tmp-generated-instructions.asm b/test/testbench/axpy/tmp-generated-instructions.asm index aa07a1a..979f887 100644 --- a/test/testbench/axpy/tmp-generated-instructions.asm +++ b/test/testbench/axpy/tmp-generated-instructions.asm @@ -1,108 +1,67 @@ -# Compiled II: 6 +# Compiled II: 5 -PE(0,0): +PE(2,1): { - CONSTANT, [arg0] -> [$0] (t=0, inv_iters=0) + GRANT_PREDICATE, [$0], [NORTH, RED] -> [$0] (t=5, inv_iters=1) } (idx_per_ii=0) { - ICMP_SGT, [$0], [#0] -> [$0], [$1] (t=1, inv_iters=0) + RETURN_VOID, [$0] (t=6, inv_iters=1) } (idx_per_ii=1) { - GRANT_ONCE, [$0] -> [$0], [EAST, RED], [NORTH, RED] (t=2, inv_iters=0) -} (idx_per_ii=2) -{ - DATA_MOV, [$0] -> [EAST, RED] (t=3, inv_iters=0) -} (idx_per_ii=3) -{ - NOT, [$1] -> [NORTH, RED] (t=5, inv_iters=0) -} (idx_per_ii=5) - -PE(1,0): -{ - ZEXT, [$0] -> [NORTH, RED] (t=6, inv_iters=1) -} (idx_per_ii=0) -{ - GRANT_PREDICATE, [EAST, RED], [WEST, RED] -> [$0] (t=3, inv_iters=0) -} (idx_per_ii=3) -{ - PHI_START, [$0], [NORTH, RED] -> [EAST, RED], [$2], [NORTH, RED] (t=4, inv_iters=0) - DATA_MOV, [EAST, RED] -> [$0] (t=4, inv_iters=0) - DATA_MOV, [WEST, RED] -> [$1] (t=4, inv_iters=0) + DATA_MOV, [NORTH, RED] -> [$0] (t=4, inv_iters=0) } (idx_per_ii=4) -{ - GRANT_PREDICATE, [$0], [$1] -> [$0] (t=5, inv_iters=0) - DATA_MOV, [$2] -> [EAST, RED] (t=5, inv_iters=0) -} (idx_per_ii=5) -PE(2,0): +PE(2,2): { - GEP, [WEST, RED] -> [$0], [NORTH, RED] (t=6, inv_iters=1) + GRANT_PREDICATE, [$1], [$0] -> [$0] (t=5, inv_iters=1) } (idx_per_ii=0) { - LOAD, [$0] -> [NORTH, RED] (t=7, inv_iters=1) + PHI_START, [EAST, RED], [$0] -> [EAST, RED], [NORTH, RED], [$0] (t=1, inv_iters=0) } (idx_per_ii=1) { - GRANT_ONCE, [#0] -> [WEST, RED] (t=2, inv_iters=0) + ADD, [$0], [#1] -> [$0], [$1] (t=2, inv_iters=0) } (idx_per_ii=2) { - GRANT_ONCE, [arg0] -> [WEST, RED] (t=3, inv_iters=0) + ICMP_EQ, [$0], [#16] -> [$0], [SOUTH, RED], [$2] (t=3, inv_iters=0) } (idx_per_ii=3) { - GEP, [WEST, RED] -> [NORTH, RED] (t=5, inv_iters=0) -} (idx_per_ii=5) + NOT, [$0] -> [$0] (t=4, inv_iters=0) + DATA_MOV, [$2] -> [SOUTH, RED] (t=4, inv_iters=0) +} (idx_per_ii=4) -PE(0,1): +PE(3,2): { - GRANT_ONCE, [SOUTH, RED] -> [$0] (t=6, inv_iters=1) + GRANT_ONCE, [#0] -> [WEST, RED] (t=0, inv_iters=0) } (idx_per_ii=0) { - GRANT_PREDICATE, [$0], [EAST, RED] -> [$0] (t=7, inv_iters=1) -} (idx_per_ii=1) + GEP, [WEST, RED] -> [$0] (t=2, inv_iters=0) +} (idx_per_ii=2) { - DATA_MOV, [SOUTH, RED] -> [EAST, RED] (t=3, inv_iters=0) + LOAD, [$0] -> [$0] (t=3, inv_iters=0) } (idx_per_ii=3) { - PHI, [$0], [EAST, RED] -> [NORTH, RED] (t=11, inv_iters=1) -} (idx_per_ii=5) + MUL, [$0], [#3] -> [NORTH, RED] (t=4, inv_iters=0) +} (idx_per_ii=4) -PE(1,1): -{ - NOT, [$1] -> [WEST, RED] (t=6, inv_iters=1) -} (idx_per_ii=0) -{ - ICMP_EQ, [$0], [SOUTH, RED] -> [$0], [$1], [$2] (t=7, inv_iters=1) -} (idx_per_ii=1) +PE(2,3): { - NOT, [$0] -> [$0] (t=8, inv_iters=1) + DATA_MOV, [SOUTH, RED] -> [$0] (t=2, inv_iters=0) } (idx_per_ii=2) { - GRANT_PREDICATE, [$3], [$0] -> [SOUTH, RED] (t=9, inv_iters=1) + GEP, [$0] -> [$0], [EAST, RED] (t=3, inv_iters=0) } (idx_per_ii=3) { - DATA_MOV, [WEST, RED] -> [$1] (t=4, inv_iters=0) - GRANT_PREDICATE, [$1], [$2] -> [WEST, RED] (t=10, inv_iters=1) + LOAD, [$0] -> [EAST, RED] (t=4, inv_iters=0) } (idx_per_ii=4) -{ - ADD, [SOUTH, RED], [#1] -> [$0], [$3] (t=5, inv_iters=0) -} (idx_per_ii=5) -PE(2,1): +PE(3,3): { - LOAD, [SOUTH, RED] -> [$0] (t=6, inv_iters=1) + ADD, [SOUTH, RED], [WEST, RED] -> [$0] (t=5, inv_iters=1) } (idx_per_ii=0) { - MUL, [$0], [arg1] -> [$0] (t=7, inv_iters=1) - DATA_MOV, [SOUTH, RED] -> [$1] (t=7, inv_iters=1) + STORE, [$0], [$1] (t=6, inv_iters=1) } (idx_per_ii=1) { - ADD, [$0], [SOUTH, RED] -> [$0] (t=8, inv_iters=1) -} (idx_per_ii=2) -{ - STORE, [$0], [$1] (t=9, inv_iters=1) -} (idx_per_ii=3) - -PE(0,2): -{ - RETURN_VOID, [SOUTH, RED] (t=12, inv_iters=2) -} (idx_per_ii=0) + DATA_MOV, [WEST, RED] -> [$1] (t=4, inv_iters=0) +} (idx_per_ii=4) diff --git a/test/testbench/axpy/tmp-generated-instructions.yaml b/test/testbench/axpy/tmp-generated-instructions.yaml index 4675ffa..7d192ee 100644 --- a/test/testbench/axpy/tmp-generated-instructions.yaml +++ b/test/testbench/axpy/tmp-generated-instructions.yaml @@ -1,483 +1,283 @@ array_config: columns: 4 rows: 4 - compiled_ii: 6 + compiled_ii: 5 cores: - - column: 0 - row: 0 - core_id: "0" + - column: 2 + row: 1 + core_id: "6" entries: - entry_id: "entry0" instructions: - index_per_ii: 0 operations: - - opcode: "CONSTANT" - id: 1 - time_step: 0 - invalid_iterations: 0 - src_operands: - - operand: "#16" - color: "RED" - dst_operands: - - operand: "$0" - color: "RED" - - index_per_ii: 1 - operations: - - opcode: "ICMP_SGT" - id: 8 - time_step: 1 - invalid_iterations: 0 + - opcode: "GRANT_PREDICATE" + id: 24 + time_step: 5 + invalid_iterations: 1 src_operands: - operand: "$0" color: "RED" - - operand: "#0" + - operand: "NORTH" color: "RED" dst_operands: - operand: "$0" color: "RED" - - operand: "$1" - color: "RED" - - index_per_ii: 2 + - index_per_ii: 1 operations: - - opcode: "GRANT_ONCE" - id: 12 - time_step: 2 - invalid_iterations: 0 + - opcode: "RETURN_VOID" + id: 30 + time_step: 6 + invalid_iterations: 1 src_operands: - operand: "$0" color: "RED" - dst_operands: - - operand: "$0" - color: "RED" - - operand: "EAST" - color: "RED" - - operand: "NORTH" - color: "RED" - - index_per_ii: 3 + - index_per_ii: 4 operations: - opcode: "DATA_MOV" - id: 160000 - time_step: 3 - invalid_iterations: 0 - src_operands: - - operand: "$0" - color: "RED" - dst_operands: - - operand: "EAST" - color: "RED" - - index_per_ii: 5 - operations: - - opcode: "NOT" - id: 11 - time_step: 5 + id: 19 + time_step: 4 invalid_iterations: 0 src_operands: - - operand: "$1" + - operand: "NORTH" color: "RED" dst_operands: - - operand: "NORTH" + - operand: "$0" color: "RED" - - column: 1 - row: 0 - core_id: "1" + - column: 2 + row: 2 + core_id: "10" entries: - entry_id: "entry0" instructions: - index_per_ii: 0 operations: - - opcode: "ZEXT" - id: 27 - time_step: 6 + - opcode: "GRANT_PREDICATE" + id: 31 + time_step: 5 invalid_iterations: 1 src_operands: + - operand: "$1" + color: "RED" - operand: "$0" color: "RED" dst_operands: - - operand: "NORTH" + - operand: "$0" color: "RED" - - index_per_ii: 3 + - index_per_ii: 1 operations: - - opcode: "GRANT_PREDICATE" - id: 19 - time_step: 3 + - opcode: "PHI_START" + id: 4 + time_step: 1 invalid_iterations: 0 src_operands: - operand: "EAST" color: "RED" - - operand: "WEST" + - operand: "$0" color: "RED" dst_operands: + - operand: "EAST" + color: "RED" + - operand: "NORTH" + color: "RED" - operand: "$0" color: "RED" - - index_per_ii: 4 + - index_per_ii: 2 operations: - - opcode: "PHI_START" - id: 26 - time_step: 4 + - opcode: "ADD" + id: 8 + time_step: 2 invalid_iterations: 0 src_operands: - operand: "$0" color: "RED" - - operand: "NORTH" + - operand: "#1" color: "RED" dst_operands: - - operand: "EAST" - color: "RED" - - operand: "$2" + - operand: "$0" color: "RED" - - operand: "NORTH" + - operand: "$1" color: "RED" - - opcode: "DATA_MOV" - id: 5 - time_step: 4 + - index_per_ii: 3 + operations: + - opcode: "ICMP_EQ" + id: 16 + time_step: 3 invalid_iterations: 0 src_operands: - - operand: "EAST" + - operand: "$0" + color: "RED" + - operand: "#16" color: "RED" dst_operands: - operand: "$0" color: "RED" - - opcode: "DATA_MOV" - id: 16 - time_step: 4 - invalid_iterations: 0 - src_operands: - - operand: "WEST" + - operand: "SOUTH" color: "RED" - dst_operands: - - operand: "$1" + - operand: "$2" color: "RED" - - index_per_ii: 5 + - index_per_ii: 4 operations: - - opcode: "GRANT_PREDICATE" - id: 20 - time_step: 5 + - opcode: "NOT" + id: 25 + time_step: 4 invalid_iterations: 0 src_operands: - operand: "$0" color: "RED" - - operand: "$1" - color: "RED" dst_operands: - operand: "$0" color: "RED" - opcode: "DATA_MOV" - id: 300000 - time_step: 5 + id: 200000 + time_step: 4 invalid_iterations: 0 src_operands: - operand: "$2" color: "RED" dst_operands: - - operand: "EAST" + - operand: "SOUTH" color: "RED" - - column: 2 - row: 0 - core_id: "2" + - column: 3 + row: 2 + core_id: "11" entries: - entry_id: "entry0" instructions: - index_per_ii: 0 - operations: - - opcode: "GEP" - id: 34 - time_step: 6 - invalid_iterations: 1 - src_operands: - - operand: "WEST" - color: "RED" - dst_operands: - - operand: "$0" - color: "RED" - - operand: "NORTH" - color: "RED" - - index_per_ii: 1 - operations: - - opcode: "LOAD" - id: 42 - time_step: 7 - invalid_iterations: 1 - src_operands: - - operand: "$0" - color: "RED" - dst_operands: - - operand: "NORTH" - color: "RED" - - index_per_ii: 2 - operations: - - opcode: "GRANT_ONCE" - id: 2 - time_step: 2 - invalid_iterations: 0 - src_operands: - - operand: "#0" - color: "RED" - dst_operands: - - operand: "WEST" - color: "RED" - - index_per_ii: 3 operations: - opcode: "GRANT_ONCE" id: 0 - time_step: 3 + time_step: 0 invalid_iterations: 0 src_operands: - - operand: "#16" + - operand: "#0" color: "RED" dst_operands: - operand: "WEST" color: "RED" - - index_per_ii: 5 + - index_per_ii: 2 operations: - opcode: "GEP" - id: 35 - time_step: 5 + id: 10 + time_step: 2 invalid_iterations: 0 src_operands: - operand: "WEST" color: "RED" - dst_operands: - - operand: "NORTH" - color: "RED" - - column: 0 - row: 1 - core_id: "4" - entries: - - entry_id: "entry0" - instructions: - - index_per_ii: 0 - operations: - - opcode: "GRANT_ONCE" - id: 17 - time_step: 6 - invalid_iterations: 1 - src_operands: - - operand: "SOUTH" - color: "RED" - dst_operands: - - operand: "$0" - color: "RED" - - index_per_ii: 1 - operations: - - opcode: "GRANT_PREDICATE" - id: 25 - time_step: 7 - invalid_iterations: 1 - src_operands: - - operand: "$0" - color: "RED" - - operand: "EAST" - color: "RED" dst_operands: - operand: "$0" color: "RED" - index_per_ii: 3 operations: - - opcode: "DATA_MOV" - id: 140001 + - opcode: "LOAD" + id: 18 time_step: 3 invalid_iterations: 0 src_operands: - - operand: "SOUTH" + - operand: "$0" color: "RED" dst_operands: - - operand: "EAST" + - operand: "$0" color: "RED" - - index_per_ii: 5 + - index_per_ii: 4 operations: - - opcode: "PHI" - id: 55 - time_step: 11 - invalid_iterations: 1 + - opcode: "MUL" + id: 26 + time_step: 4 + invalid_iterations: 0 src_operands: - operand: "$0" color: "RED" - - operand: "EAST" + - operand: "#3" color: "RED" dst_operands: - operand: "NORTH" color: "RED" - - column: 1 - row: 1 - core_id: "5" + - column: 2 + row: 3 + core_id: "14" entries: - entry_id: "entry0" instructions: - - index_per_ii: 0 - operations: - - opcode: "NOT" - id: 18 - time_step: 6 - invalid_iterations: 1 - src_operands: - - operand: "$1" - color: "RED" - dst_operands: - - operand: "WEST" - color: "RED" - - index_per_ii: 1 + - index_per_ii: 2 operations: - - opcode: "ICMP_EQ" - id: 41 - time_step: 7 - invalid_iterations: 1 + - opcode: "DATA_MOV" + id: 6 + time_step: 2 + invalid_iterations: 0 src_operands: - - operand: "$0" - color: "RED" - operand: "SOUTH" color: "RED" dst_operands: - operand: "$0" color: "RED" - - operand: "$1" - color: "RED" - - operand: "$2" - color: "RED" - - index_per_ii: 2 + - index_per_ii: 3 operations: - - opcode: "NOT" - id: 50 - time_step: 8 - invalid_iterations: 1 + - opcode: "GEP" + id: 9 + time_step: 3 + invalid_iterations: 0 src_operands: - operand: "$0" color: "RED" dst_operands: - operand: "$0" color: "RED" - - index_per_ii: 3 - operations: - - opcode: "GRANT_PREDICATE" - id: 56 - time_step: 9 - invalid_iterations: 1 - src_operands: - - operand: "$3" - color: "RED" - - operand: "$0" - color: "RED" - dst_operands: - - operand: "SOUTH" + - operand: "EAST" color: "RED" - index_per_ii: 4 operations: - - opcode: "DATA_MOV" - id: 14 + - opcode: "LOAD" + id: 17 time_step: 4 invalid_iterations: 0 src_operands: - - operand: "WEST" - color: "RED" - dst_operands: - - operand: "$1" - color: "RED" - - opcode: "GRANT_PREDICATE" - id: 49 - time_step: 10 - invalid_iterations: 1 - src_operands: - - operand: "$1" - color: "RED" - - operand: "$2" - color: "RED" - dst_operands: - - operand: "WEST" - color: "RED" - - index_per_ii: 5 - operations: - - opcode: "ADD" - id: 33 - time_step: 5 - invalid_iterations: 0 - src_operands: - - operand: "SOUTH" - color: "RED" - - operand: "#1" - color: "RED" - dst_operands: - operand: "$0" color: "RED" - - operand: "$3" + dst_operands: + - operand: "EAST" color: "RED" - - column: 2 - row: 1 - core_id: "6" + - column: 3 + row: 3 + core_id: "15" entries: - entry_id: "entry0" instructions: - index_per_ii: 0 operations: - - opcode: "LOAD" - id: 43 - time_step: 6 + - opcode: "ADD" + id: 32 + time_step: 5 invalid_iterations: 1 src_operands: - operand: "SOUTH" color: "RED" + - operand: "WEST" + color: "RED" dst_operands: - operand: "$0" color: "RED" - index_per_ii: 1 operations: - - opcode: "MUL" - id: 51 - time_step: 7 + - opcode: "STORE" + id: 35 + time_step: 6 invalid_iterations: 1 src_operands: - operand: "$0" color: "RED" - - operand: "#3" - color: "RED" - dst_operands: - - operand: "$0" - color: "RED" - - opcode: "DATA_MOV" - id: 38 - time_step: 7 - invalid_iterations: 1 - src_operands: - - operand: "SOUTH" - color: "RED" - dst_operands: - operand: "$1" color: "RED" - - index_per_ii: 2 + - index_per_ii: 4 operations: - - opcode: "ADD" - id: 57 - time_step: 8 - invalid_iterations: 1 + - opcode: "DATA_MOV" + id: 13 + time_step: 4 + invalid_iterations: 0 src_operands: - - operand: "$0" - color: "RED" - - operand: "SOUTH" + - operand: "WEST" color: "RED" dst_operands: - - operand: "$0" - color: "RED" - - index_per_ii: 3 - operations: - - opcode: "STORE" - id: 62 - time_step: 9 - invalid_iterations: 1 - src_operands: - - operand: "$0" - color: "RED" - operand: "$1" color: "RED" - - column: 0 - row: 2 - core_id: "8" - entries: - - entry_id: "entry0" - instructions: - - index_per_ii: 0 - operations: - - opcode: "RETURN_VOID" - id: 61 - time_step: 12 - invalid_iterations: 2 - src_operands: - - operand: "SOUTH" - color: "RED" diff --git a/test/testbench/branch_for/main.go b/test/testbench/branch_for/main.go index 7f683c3..dfc7055 100644 --- a/test/testbench/branch_for/main.go +++ b/test/testbench/branch_for/main.go @@ -2,35 +2,24 @@ package main import ( "fmt" - "log/slog" "math" "os" + "path/filepath" + "runtime" + "strings" "github.com/sarchlab/akita/v4/sim" - "github.com/sarchlab/zeonica/api" - "github.com/sarchlab/zeonica/config" "github.com/sarchlab/zeonica/core" + "github.com/sarchlab/zeonica/runtimecfg" ) -func BranchFor() { - width := 4 - height := 4 - - engine := sim.NewSerialEngine() - - driver := api.DriverBuilder{}. - WithEngine(engine). - WithFreq(1 * sim.GHz). - Build("Driver") - - device := config.DeviceBuilder{}. - WithEngine(engine). - WithFreq(1 * sim.GHz). - WithWidth(width). - WithHeight(height). - Build("Device") - - driver.RegisterDevice(device) +// BranchFor runs the branch_for testbench on the configured runtime. +func BranchFor(rt *runtimecfg.Runtime) int { + width := rt.Config.Columns + height := rt.Config.Rows + driver := rt.Driver + device := rt.Device + engine := rt.Engine programPath := os.Getenv("ZEONICA_PROGRAM_YAML") if programPath == "" { @@ -69,11 +58,14 @@ func BranchFor() { fmt.Printf("retVal(bits=0x%08x) -> %f\n", retBits, retVal) expected := cpuBranchFor() + mismatch := 0 if retVal == expected { fmt.Printf("✅ branch_for test passed: retVal=%f expected=%f\n", retVal, expected) } else { fmt.Printf("❌ branch_for test failed: retVal=%f expected=%f\n", retVal, expected) + mismatch = 1 } + return mismatch } func cpuBranchFor() float32 { @@ -92,17 +84,75 @@ func cpuBranchFor() float32 { } } +func resolveArchSpecPath() (string, error) { + fromEnv := strings.TrimSpace(os.Getenv("ZEONICA_ARCH_SPEC")) + if fromEnv != "" { + if _, err := os.Stat(fromEnv); err == nil { + return fromEnv, nil + } + return "", fmt.Errorf("ZEONICA_ARCH_SPEC points to a missing file: %s", fromEnv) + } + + candidates := []string{ + "test/arch_spec/arch_spec.yaml", + "../../arch_spec/arch_spec.yaml", + } + + if _, thisFile, _, ok := runtime.Caller(0); ok { + candidates = append(candidates, + filepath.Clean(filepath.Join(filepath.Dir(thisFile), "..", "..", "arch_spec", "arch_spec.yaml")), + ) + } + + seen := make(map[string]struct{}, len(candidates)) + normalized := make([]string, 0, len(candidates)) + for _, candidate := range candidates { + clean := filepath.Clean(candidate) + if _, exists := seen[clean]; exists { + continue + } + seen[clean] = struct{}{} + normalized = append(normalized, clean) + if _, err := os.Stat(clean); err == nil { + return clean, nil + } + } + + return "", fmt.Errorf("cannot locate arch spec, tried: %s", strings.Join(normalized, ", ")) +} + func main() { - f, err := os.Create("branch_for.json.log") + const testName = "branch_for" + + archSpecPath, err := resolveArchSpecPath() + if err != nil { + panic(err) + } + + rt, err := runtimecfg.LoadRuntime(archSpecPath, testName) + if err != nil { + panic(err) + } + + traceLog, err := rt.InitTraceLogger(core.LevelTrace) if err != nil { panic(err) } - defer f.Close() - handler := slog.NewJSONHandler(f, &slog.HandlerOptions{ - Level: core.LevelTrace, - }) - slog.SetDefault(slog.New(handler)) + mismatch := BranchFor(rt) + + if err := runtimecfg.CloseTraceLog(traceLog); err != nil { + panic(err) + } - BranchFor() + passed := mismatch == 0 + if rt.Config.LoggingEnabled { + reportPath, err := rt.GenerateSaveAndPrintReport(5, &passed, &mismatch) + if err != nil { + panic(err) + } + fmt.Printf("report saved: %s\n", reportPath) + } else { + fmt.Println("logging disabled in arch spec, skipped report generation") + } } diff --git a/test/testbench/fir/fir_test.go b/test/testbench/fir/fir_test.go index c4ab36f..010e7c4 100644 --- a/test/testbench/fir/fir_test.go +++ b/test/testbench/fir/fir_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/sarchlab/zeonica/core" + "github.com/sarchlab/zeonica/runtimecfg" ) func TestFir(t *testing.T) { @@ -22,8 +23,17 @@ func TestFir(t *testing.T) { }) slog.SetDefault(slog.New(handler)) - retVal, expected := Fir() - if retVal != expected { - t.Fatalf("fir mismatch: got=%d expected=%d", retVal, expected) + archSpecPath, err := resolveArchSpecPath() + if err != nil { + t.Fatalf("resolve arch spec: %v", err) + } + rt, err := runtimecfg.LoadRuntime(archSpecPath, "fir") + if err != nil { + t.Fatalf("load runtime: %v", err) + } + + mismatch := Fir(rt) + if mismatch != 0 { + t.Fatalf("fir mismatch count: got=%d expected=0", mismatch) } } diff --git a/test/testbench/fir/main.go b/test/testbench/fir/main.go index a8fc098..93197bd 100644 --- a/test/testbench/fir/main.go +++ b/test/testbench/fir/main.go @@ -2,35 +2,25 @@ package main import ( "fmt" - "log/slog" "os" + "path/filepath" + "runtime" + "strings" "github.com/sarchlab/akita/v4/sim" - "github.com/sarchlab/zeonica/api" - "github.com/sarchlab/zeonica/config" "github.com/sarchlab/zeonica/core" - "github.com/sarchlab/zeonica/report" + "github.com/sarchlab/zeonica/runtimecfg" ) -func Fir() (int32, int32) { - width := 4 - height := 4 - - engine := sim.NewSerialEngine() - - driver := api.DriverBuilder{}. - WithEngine(engine). - WithFreq(1 * sim.GHz). - Build("Driver") - - device := config.DeviceBuilder{}. - WithEngine(engine). - WithFreq(1 * sim.GHz). - WithWidth(width). - WithHeight(height). - Build("Device") - - driver.RegisterDevice(device) +// Fir runs the FIR testbench on the configured runtime. +// +//nolint:gocyclo,funlen +func Fir(rt *runtimecfg.Runtime) int { + width := rt.Config.Columns + height := rt.Config.Rows + driver := rt.Driver + device := rt.Device + engine := rt.Engine programPath := os.Getenv("ZEONICA_PROGRAM_YAML") if programPath == "" { @@ -104,57 +94,85 @@ func Fir() (int32, int32) { retVal := int32(retBits) fmt.Printf("retVal(bits=0x%08x) -> %d\n", retBits, retVal) + mismatchCount := 0 if retVal == expected { fmt.Println("✅ Fir tests passed!") } else { fmt.Printf("❌ Fir tests failed! expected=%d\n", expected) + mismatchCount = 1 } - return retVal, expected + return mismatchCount } -func main() { - f, err := os.Create("fir.json.log") - if err != nil { - panic(err) +func resolveArchSpecPath() (string, error) { + fromEnv := strings.TrimSpace(os.Getenv("ZEONICA_ARCH_SPEC")) + if fromEnv != "" { + if _, err := os.Stat(fromEnv); err == nil { + return fromEnv, nil + } + return "", fmt.Errorf("ZEONICA_ARCH_SPEC points to a missing file: %s", fromEnv) } - handler := slog.NewJSONHandler(f, &slog.HandlerOptions{ - Level: core.LevelTrace, - }) + candidates := []string{ + "test/arch_spec/arch_spec.yaml", + "../../arch_spec/arch_spec.yaml", + } - slog.SetDefault(slog.New(handler)) - retVal, expected := Fir() + if _, thisFile, _, ok := runtime.Caller(0); ok { + candidates = append(candidates, + filepath.Clean(filepath.Join(filepath.Dir(thisFile), "..", "..", "arch_spec", "arch_spec.yaml")), + ) + } - if err := f.Sync(); err != nil { - panic(err) + seen := make(map[string]struct{}, len(candidates)) + normalized := make([]string, 0, len(candidates)) + for _, candidate := range candidates { + clean := filepath.Clean(candidate) + if _, exists := seen[clean]; exists { + continue + } + seen[clean] = struct{}{} + normalized = append(normalized, clean) + if _, err := os.Stat(clean); err == nil { + return clean, nil + } } - if err := f.Close(); err != nil { + + return "", fmt.Errorf("cannot locate arch spec, tried: %s", strings.Join(normalized, ", ")) +} + +func main() { + const testName = "fir" + + archSpecPath, err := resolveArchSpecPath() + if err != nil { panic(err) } - passed := retVal == expected - mismatchCount := 0 - if !passed { - mismatchCount = 1 + rt, err := runtimecfg.LoadRuntime(archSpecPath, testName) + if err != nil { + panic(err) } - r, err := report.GenerateFromLog(report.GenerateOptions{ - TestName: "fir", - LogPath: "fir.json.log", - GridWidth: 4, - GridHeight: 4, - TopN: 5, - Passed: &passed, - MismatchCount: &mismatchCount, - }) + traceLog, err := rt.InitTraceLogger(core.LevelTrace) if err != nil { panic(err) } - if err := report.SaveJSON(r, "fir.report.json"); err != nil { + mismatchCount := Fir(rt) + + if err := runtimecfg.CloseTraceLog(traceLog); err != nil { panic(err) } - report.PrintSummary(r) - fmt.Println("report saved: fir.report.json") + passed := mismatchCount == 0 + if rt.Config.LoggingEnabled { + reportPath, err := rt.GenerateSaveAndPrintReport(5, &passed, &mismatchCount) + if err != nil { + panic(err) + } + fmt.Printf("report saved: %s\n", reportPath) + } else { + fmt.Println("logging disabled in arch spec, skipped report generation") + } } diff --git a/test/testbench/gemm/main.go b/test/testbench/gemm/main.go index 55de76f..ecf0289 100644 --- a/test/testbench/gemm/main.go +++ b/test/testbench/gemm/main.go @@ -11,6 +11,7 @@ import ( "github.com/sarchlab/zeonica/core" ) +// Gemm runs the legacy GEMM testbench. func Gemm() { width := 4 height := 4 @@ -31,7 +32,7 @@ func Gemm() { driver.RegisterDevice(device) - program := core.LoadProgramFileFromYAML("test/testbench/gemm/gemm_int.yaml") + program := core.LoadProgramFileFromYAML("tmp-generated-instructions.yaml") if len(program) == 0 { panic("Failed to load program") } @@ -58,7 +59,7 @@ func Gemm() { driver.PreloadMemory(3, 3, 3, 0) driver.PreloadMemory(3, 3, 1, 1) driver.PreloadMemory(2, 1, 2, 0) - driver.PreloadMemory(2, 1, 4, 1) // addr has ERRORS !!!!!! + driver.PreloadMemory(2, 1, 4, 1) driver.Run() diff --git a/test/testbench/histogram/main.go b/test/testbench/histogram/main.go index 0510397..a237d87 100644 --- a/test/testbench/histogram/main.go +++ b/test/testbench/histogram/main.go @@ -2,34 +2,37 @@ package main import ( "fmt" - "log/slog" + "math/rand" "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "time" "github.com/sarchlab/akita/v4/sim" - "github.com/sarchlab/zeonica/api" - "github.com/sarchlab/zeonica/config" "github.com/sarchlab/zeonica/core" + "github.com/sarchlab/zeonica/runtimecfg" ) -func Histogram() { - width := 4 - height := 4 - - engine := sim.NewSerialEngine() - - driver := api.DriverBuilder{}. - WithEngine(engine). - WithFreq(1 * sim.GHz). - Build("Driver") - - device := config.DeviceBuilder{}. - WithEngine(engine). - WithFreq(1 * sim.GHz). - WithWidth(width). - WithHeight(height). - Build("Device") +// Histogram runs the histogram testbench on the configured runtime. +// +//nolint:gocyclo,funlen +func Histogram(rt *runtimecfg.Runtime) int { + width := rt.Config.Columns + height := rt.Config.Rows + seed := time.Now().UnixNano() + if seedStr := os.Getenv("ZEONICA_RAND_SEED"); seedStr != "" { + if parsed, err := strconv.ParseInt(seedStr, 10, 64); err == nil { + seed = parsed + } + } + rng := rand.New(rand.NewSource(seed)) + fmt.Printf("Using random seed: %d\n", seed) - driver.RegisterDevice(device) + driver := rt.Driver + device := rt.Device + engine := rt.Engine programPath := os.Getenv("ZEONICA_PROGRAM_YAML") if programPath == "" { @@ -52,10 +55,10 @@ func Histogram() { } } - // preload input data from benchmark (DATA_LEN=20) - inputData := []uint32{ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - 11, 12, 13, 14, 14, 14, 14, 14, 14, 15, + // preload randomized input data (DATA_LEN=20, value range [1, 19]) + inputData := make([]uint32, 20) + for i := range inputData { + inputData[i] = uint32(rng.Intn(19) + 1) } expected := computeHistogram(inputData, 5) @@ -107,6 +110,7 @@ func Histogram() { } else { fmt.Printf("❌ output mismatches histogram: %d\n", histMismatch) } + return histMismatch } func computeHistogram(input []uint32, bins int) []uint32 { @@ -130,17 +134,75 @@ func computeHistogram(input []uint32, bins int) []uint32 { return result } +func resolveArchSpecPath() (string, error) { + fromEnv := strings.TrimSpace(os.Getenv("ZEONICA_ARCH_SPEC")) + if fromEnv != "" { + if _, err := os.Stat(fromEnv); err == nil { + return fromEnv, nil + } + return "", fmt.Errorf("ZEONICA_ARCH_SPEC points to a missing file: %s", fromEnv) + } + + candidates := []string{ + "test/arch_spec/arch_spec.yaml", + "../../arch_spec/arch_spec.yaml", + } + + if _, thisFile, _, ok := runtime.Caller(0); ok { + candidates = append(candidates, + filepath.Clean(filepath.Join(filepath.Dir(thisFile), "..", "..", "arch_spec", "arch_spec.yaml")), + ) + } + + seen := make(map[string]struct{}, len(candidates)) + normalized := make([]string, 0, len(candidates)) + for _, candidate := range candidates { + clean := filepath.Clean(candidate) + if _, exists := seen[clean]; exists { + continue + } + seen[clean] = struct{}{} + normalized = append(normalized, clean) + if _, err := os.Stat(clean); err == nil { + return clean, nil + } + } + + return "", fmt.Errorf("cannot locate arch spec, tried: %s", strings.Join(normalized, ", ")) +} + func main() { - f, err := os.Create("histogram.json.log") + const testName = "histogram" + + archSpecPath, err := resolveArchSpecPath() if err != nil { panic(err) } - defer f.Close() - handler := slog.NewJSONHandler(f, &slog.HandlerOptions{ - Level: core.LevelTrace, - }) + rt, err := runtimecfg.LoadRuntime(archSpecPath, testName) + if err != nil { + panic(err) + } - slog.SetDefault(slog.New(handler)) - Histogram() + traceLog, err := rt.InitTraceLogger(core.LevelTrace) + if err != nil { + panic(err) + } + + mismatch := Histogram(rt) + + if err := runtimecfg.CloseTraceLog(traceLog); err != nil { + panic(err) + } + + passed := mismatch == 0 + if rt.Config.LoggingEnabled { + reportPath, err := rt.GenerateSaveAndPrintReport(5, &passed, &mismatch) + if err != nil { + panic(err) + } + fmt.Printf("report saved: %s\n", reportPath) + } else { + fmt.Println("logging disabled in arch spec, skipped report generation") + } } diff --git a/tool/viz/README.md b/tool/viz/README.md new file mode 100644 index 0000000..6f6c54c --- /dev/null +++ b/tool/viz/README.md @@ -0,0 +1,25 @@ +# CGRA Log Viewer + +This viewer visualizes JSONL traces like `gemm.json.log` with a cycle slider and playback. + +## Run + +From repository root: + +```bash +python3 -m http.server 8000 +``` + +Open: + +```text +http://localhost:8000/viz/ +``` + +It will try to load `../gemm.json.log` automatically. You can also load any other trace from the file picker. + +## Supported events + +- `DataFlow` (`FeedIn`, `Send`, `Recv`, `Collect`) +- `Inst` (`DATA_MOV`, `MUL_ADD`, `STORE`) +- `Memory` (`StoreDirect`) diff --git a/tool/viz/app.js b/tool/viz/app.js new file mode 100644 index 0000000..721c034 --- /dev/null +++ b/tool/viz/app.js @@ -0,0 +1,582 @@ +const state = { + events: [], + byTime: new Map(), + minTime: 0, + maxTime: 0, + currentTime: 0, + maxX: 0, + maxY: 0, + timer: null, + speedMs: 1100, + showDataFlow: true, + showInst: true, + showMemory: true, + showLabels: true, +}; + +const layout = { + width: 940, + height: 620, + originX: 170, + originY: 90, + tileSize: 100, + gap: 24, + driverOffset: 52, +}; + +const colors = { + Send: "#006d77", + Recv: "#118ab2", + FeedIn: "#ef476f", + Collect: "#8338ec", + Inst: "#f77f00", + Memory: "#d62828", +}; + +const svg = d3.select("#canvas"); +let staticLayer; +let dynamicLayer; + +const controls = { + playBtn: document.getElementById("playBtn"), + stepBackBtn: document.getElementById("stepBackBtn"), + stepFwdBtn: document.getElementById("stepFwdBtn"), + speedSelect: document.getElementById("speedSelect"), + timeSlider: document.getElementById("timeSlider"), + timeLabel: document.getElementById("timeLabel"), + showDataFlow: document.getElementById("showDataFlow"), + showInst: document.getElementById("showInst"), + showMemory: document.getElementById("showMemory"), + showLabels: document.getElementById("showLabels"), + fileInput: document.getElementById("fileInput"), + statsLine: document.getElementById("statsLine"), + eventDump: document.getElementById("eventDump"), +}; + +function tileKey(x, y) { + return `${x},${y}`; +} + +function tileRect(x, y) { + const step = layout.tileSize + layout.gap; + const px = layout.originX + x * step; + const py = layout.originY + (state.maxY - y) * step; + return { x: px, y: py, w: layout.tileSize, h: layout.tileSize }; +} + +function parseEndpoint(name) { + if (!name || name === "None") { + return null; + } + const tileMatch = /^Device\.Tile\[(\d+)\]\[(\d+)\]\.Core\.(North|South|East|West)$/.exec(name); + if (tileMatch) { + return { + kind: "tilePort", + y: Number(tileMatch[1]), + x: Number(tileMatch[2]), + port: tileMatch[3], + raw: name, + }; + } + const driverMatch = /^Driver\.Device(North|South|East|West)\[(\d+)\]$/.exec(name); + if (driverMatch) { + return { + kind: "driver", + side: driverMatch[1], + idx: Number(driverMatch[2]), + raw: name, + }; + } + return { kind: "unknown", raw: name }; +} + +function endpointPoint(ep) { + if (!ep) { + return null; + } + if (ep.kind === "tilePort") { + const r = tileRect(ep.x, ep.y); + if (ep.port === "North") return { x: r.x + r.w / 2, y: r.y, tile: tileKey(ep.x, ep.y) }; + if (ep.port === "South") return { x: r.x + r.w / 2, y: r.y + r.h, tile: tileKey(ep.x, ep.y) }; + if (ep.port === "West") return { x: r.x, y: r.y + r.h / 2, tile: tileKey(ep.x, ep.y) }; + if (ep.port === "East") return { x: r.x + r.w, y: r.y + r.h / 2, tile: tileKey(ep.x, ep.y) }; + } + if (ep.kind === "driver") { + const side = ep.side; + const idx = ep.idx; + if (side === "North" && idx <= state.maxX) { + const r = tileRect(idx, state.maxY); + return { x: r.x + r.w / 2, y: r.y - layout.driverOffset }; + } + if (side === "South" && idx <= state.maxX) { + const r = tileRect(idx, 0); + return { x: r.x + r.w / 2, y: r.y + r.h + layout.driverOffset }; + } + if (side === "West" && idx <= state.maxY) { + const r = tileRect(0, idx); + return { x: r.x - layout.driverOffset, y: r.y + r.h / 2 }; + } + if (side === "East" && idx <= state.maxY) { + const r = tileRect(state.maxX, idx); + return { x: r.x + r.w + layout.driverOffset, y: r.y + r.h / 2 }; + } + } + return null; +} + +function inferBounds(events) { + let maxX = 0; + let maxY = 0; + for (const e of events) { + if (Number.isInteger(e.X)) maxX = Math.max(maxX, e.X); + if (Number.isInteger(e.Y)) maxY = Math.max(maxY, e.Y); + for (const f of ["Src", "Dst", "From", "To"]) { + if (!e[f]) continue; + const ep = parseEndpoint(e[f]); + if (ep && ep.kind === "tilePort") { + maxX = Math.max(maxX, ep.x); + maxY = Math.max(maxY, ep.y); + } + } + } + return { maxX, maxY }; +} + +function parseJsonLines(text) { + const lines = text.split(/\r?\n/).map((s) => s.trim()).filter(Boolean); + const rows = []; + for (const line of lines) { + try { + const obj = JSON.parse(line); + if (obj && Number.isInteger(obj.Time)) { + rows.push(obj); + } + } catch (_) { + // Ignore malformed lines. + } + } + return rows; +} + +function indexByTime(events) { + const byTime = new Map(); + let minTime = Number.POSITIVE_INFINITY; + let maxTime = Number.NEGATIVE_INFINITY; + for (const e of events) { + const t = e.Time; + if (!byTime.has(t)) byTime.set(t, []); + byTime.get(t).push(e); + minTime = Math.min(minTime, t); + maxTime = Math.max(maxTime, t); + } + if (!Number.isFinite(minTime) || !Number.isFinite(maxTime)) { + minTime = 0; + maxTime = 0; + } + return { byTime, minTime, maxTime }; +} + +function summarizeEvent(e) { + if (e.msg === "DataFlow") { + if (e.Behavior === "FeedIn") { + return `DataFlow FeedIn data=${e.Data} ${e.From} -> ${e.To}`; + } + if (e.Behavior === "Collect") { + return `DataFlow Collect data=${e.Data} from ${e.From}`; + } + return `DataFlow ${e.Behavior} data=${e.Data} ${e.Src} -> ${e.Dst}`; + } + if (e.msg === "Inst") { + return `Inst ${e.OpCode} tile=(${e.X},${e.Y}) id=${e.ID} pred=${e.Pred}`; + } + if (e.msg === "Memory") { + return `Memory ${e.Behavior} tile=(${e.X},${e.Y}) value=${e.Value} addr=${e.Addr}`; + } + return JSON.stringify(e); +} + +function drawStaticScene() { + svg.selectAll("*").remove(); + staticLayer = svg.append("g"); + dynamicLayer = svg.append("g"); + + const bg = staticLayer.append("rect"); + bg + .attr("x", 14) + .attr("y", 14) + .attr("width", layout.width - 28) + .attr("height", layout.height - 28) + .attr("fill", "#fff8e8") + .attr("stroke", "#ccbfa4") + .attr("rx", 18); + + const tiles = []; + for (let y = 0; y <= state.maxY; y += 1) { + for (let x = 0; x <= state.maxX; x += 1) { + tiles.push({ x, y }); + } + } + + const tileGroup = staticLayer.append("g").attr("class", "tile-group"); + tileGroup + .selectAll("rect") + .data(tiles) + .join("rect") + .attr("class", (d) => `tile tile-${d.x}-${d.y}`) + .attr("x", (d) => tileRect(d.x, d.y).x) + .attr("y", (d) => tileRect(d.x, d.y).y) + .attr("width", layout.tileSize) + .attr("height", layout.tileSize) + .attr("rx", 10); + + tileGroup + .selectAll("text") + .data(tiles) + .join("text") + .attr("class", "tile-label") + .attr("x", (d) => tileRect(d.x, d.y).x + 7) + .attr("y", (d) => tileRect(d.x, d.y).y + 18) + .text((d) => `(${d.x},${d.y})`); + + const driverNodes = []; + for (let i = 0; i <= state.maxX; i += 1) { + driverNodes.push({ side: "North", idx: i }); + driverNodes.push({ side: "South", idx: i }); + } + for (let i = 0; i <= state.maxY; i += 1) { + driverNodes.push({ side: "West", idx: i }); + driverNodes.push({ side: "East", idx: i }); + } + + const drivers = staticLayer.append("g").attr("class", "drivers"); + drivers + .selectAll("circle") + .data(driverNodes) + .join("circle") + .attr("cx", (d) => endpointPoint({ kind: "driver", side: d.side, idx: d.idx }).x) + .attr("cy", (d) => endpointPoint({ kind: "driver", side: d.side, idx: d.idx }).y) + .attr("r", 10) + .attr("fill", "#f2d6b3") + .attr("stroke", "#8b7c63") + .attr("stroke-width", 1.2); + + drivers + .selectAll("text") + .data(driverNodes) + .join("text") + .attr("class", "driver-label") + .attr("x", (d) => endpointPoint({ kind: "driver", side: d.side, idx: d.idx }).x + 12) + .attr("y", (d) => endpointPoint({ kind: "driver", side: d.side, idx: d.idx }).y + 4) + .text((d) => `${d.side[0]}${d.idx}`); + + const legend = staticLayer.append("g").attr("transform", "translate(28, 34)"); + const legendItems = [ + ["Send", colors.Send], + ["Recv", colors.Recv], + ["FeedIn", colors.FeedIn], + ["Collect", colors.Collect], + ["Inst", colors.Inst], + ["Memory", colors.Memory], + ]; + legend + .selectAll("circle") + .data(legendItems) + .join("circle") + .attr("cx", (_d, i) => i * 112) + .attr("cy", 0) + .attr("r", 5) + .attr("fill", (d) => d[1]); + legend + .selectAll("text") + .data(legendItems) + .join("text") + .attr("class", "legend-text") + .attr("x", (_d, i) => i * 112 + 9) + .attr("y", 4) + .text((d) => d[0]); +} + +function drawLink(type, srcPoint, dstPoint) { + const path = d3.path(); + path.moveTo(srcPoint.x, srcPoint.y); + const dx = dstPoint.x - srcPoint.x; + const dy = dstPoint.y - srcPoint.y; + const curve = Math.abs(dx) > Math.abs(dy) ? 0.25 : -0.25; + path.bezierCurveTo( + srcPoint.x + dx * 0.35, + srcPoint.y + dy * curve, + srcPoint.x + dx * 0.65, + dstPoint.y - dy * curve, + dstPoint.x, + dstPoint.y, + ); + + dynamicLayer + .append("path") + .attr("class", "event-link") + .attr("d", path.toString()) + .attr("stroke", colors[type] || "#555") + .attr("stroke-opacity", 0.78); + + const pulse = dynamicLayer + .append("circle") + .attr("class", "pulse") + .attr("cx", srcPoint.x) + .attr("cy", srcPoint.y) + .attr("fill", colors[type] || "#444") + .attr("opacity", 0.95); + + pulse + .transition() + .duration(Math.max(220, state.speedMs - 160)) + .ease(d3.easeCubicInOut) + .attr("cx", dstPoint.x) + .attr("cy", dstPoint.y) + .attr("opacity", 0.2) + .remove(); +} + +function applyTileActivity(activeTiles) { + staticLayer.selectAll(".tile").classed("active", false); + for (const key of activeTiles) { + const [x, y] = key.split(",").map(Number); + staticLayer.select(`.tile-${x}-${y}`).classed("active", true); + } + staticLayer.selectAll(".tile-label").style("display", state.showLabels ? null : "none"); +} + +function drawTileBadges(timeEvents) { + const instCounts = new Map(); + const memCounts = new Map(); + + for (const e of timeEvents) { + if (e.msg === "Inst" && state.showInst) { + const k = tileKey(e.X, e.Y); + instCounts.set(k, (instCounts.get(k) || 0) + 1); + } + if (e.msg === "Memory" && state.showMemory) { + const k = tileKey(e.X, e.Y); + memCounts.set(k, (memCounts.get(k) || 0) + 1); + } + } + + for (const [k, count] of instCounts.entries()) { + const [x, y] = k.split(",").map(Number); + const r = tileRect(x, y); + dynamicLayer + .append("circle") + .attr("class", "inst-badge") + .attr("cx", r.x + 16) + .attr("cy", r.y + 16) + .attr("r", 10) + .attr("fill", colors.Inst) + .attr("opacity", 0.9); + dynamicLayer + .append("text") + .attr("x", r.x + 12) + .attr("y", r.y + 20) + .attr("fill", "#fff") + .attr("font-size", 11) + .text(`${count}`); + } + + for (const [k, count] of memCounts.entries()) { + const [x, y] = k.split(",").map(Number); + const r = tileRect(x, y); + dynamicLayer + .append("rect") + .attr("class", "memory-badge") + .attr("x", r.x + r.w - 23) + .attr("y", r.y + r.h - 23) + .attr("width", 16) + .attr("height", 16) + .attr("rx", 3) + .attr("fill", colors.Memory) + .attr("opacity", 0.9); + dynamicLayer + .append("text") + .attr("x", r.x + r.w - 20) + .attr("y", r.y + r.h - 11) + .attr("fill", "#fff") + .attr("font-size", 11) + .text(`${count}`); + } +} + +function renderCycleDetails(events, t) { + const counts = { + DataFlow: 0, + Inst: 0, + Memory: 0, + }; + for (const e of events) { + if (e.msg === "DataFlow") counts.DataFlow += 1; + if (e.msg === "Inst") counts.Inst += 1; + if (e.msg === "Memory") counts.Memory += 1; + } + controls.statsLine.textContent = + `Cycle ${t} | DataFlow=${counts.DataFlow} | Inst=${counts.Inst} | Memory=${counts.Memory}`; + controls.eventDump.textContent = events.map(summarizeEvent).join("\n"); +} + +function renderTime(t) { + state.currentTime = t; + controls.timeLabel.textContent = `T=${t}`; + controls.timeSlider.value = String(t); + dynamicLayer.selectAll("*").remove(); + + const events = state.byTime.get(t) || []; + const activeTiles = new Set(); + + for (const e of events) { + if (e.msg === "DataFlow" && state.showDataFlow) { + let src = null; + let dst = null; + let type = e.Behavior; + if (e.Behavior === "FeedIn") { + src = parseEndpoint(e.From); + dst = parseEndpoint(e.To); + } else if (e.Behavior === "Collect") { + src = parseEndpoint(e.From); + } else { + src = parseEndpoint(e.Src); + dst = parseEndpoint(e.Dst); + } + const srcPoint = endpointPoint(src); + const dstPoint = endpointPoint(dst); + if (srcPoint && dstPoint) { + drawLink(type, srcPoint, dstPoint); + } else if (srcPoint) { + dynamicLayer + .append("circle") + .attr("class", "pulse") + .attr("cx", srcPoint.x) + .attr("cy", srcPoint.y) + .attr("fill", colors[type] || "#333") + .attr("opacity", 0.9) + .transition() + .duration(Math.max(220, state.speedMs - 200)) + .attr("r", 9) + .attr("opacity", 0.15) + .remove(); + } + if (srcPoint && srcPoint.tile) activeTiles.add(srcPoint.tile); + if (dstPoint && dstPoint.tile) activeTiles.add(dstPoint.tile); + } + if (e.msg === "Inst" && state.showInst) { + activeTiles.add(tileKey(e.X, e.Y)); + } + if (e.msg === "Memory" && state.showMemory) { + activeTiles.add(tileKey(e.X, e.Y)); + } + } + + applyTileActivity(activeTiles); + drawTileBadges(events); + renderCycleDetails(events, t); +} + +function stopPlayback() { + if (state.timer) { + clearInterval(state.timer); + state.timer = null; + } + controls.playBtn.textContent = "Play"; +} + +function playOrPause() { + if (state.timer) { + stopPlayback(); + return; + } + controls.playBtn.textContent = "Pause"; + state.timer = setInterval(() => { + if (state.currentTime >= state.maxTime) { + stopPlayback(); + return; + } + renderTime(state.currentTime + 1); + }, state.speedMs); +} + +function initControls() { + controls.playBtn.addEventListener("click", playOrPause); + controls.stepBackBtn.addEventListener("click", () => { + stopPlayback(); + renderTime(Math.max(state.minTime, state.currentTime - 1)); + }); + controls.stepFwdBtn.addEventListener("click", () => { + stopPlayback(); + renderTime(Math.min(state.maxTime, state.currentTime + 1)); + }); + controls.timeSlider.addEventListener("input", (e) => { + stopPlayback(); + renderTime(Number(e.target.value)); + }); + controls.speedSelect.addEventListener("change", (e) => { + state.speedMs = Number(e.target.value); + if (state.timer) { + stopPlayback(); + playOrPause(); + } + }); + controls.showDataFlow.addEventListener("change", (e) => { + state.showDataFlow = Boolean(e.target.checked); + renderTime(state.currentTime); + }); + controls.showInst.addEventListener("change", (e) => { + state.showInst = Boolean(e.target.checked); + renderTime(state.currentTime); + }); + controls.showMemory.addEventListener("change", (e) => { + state.showMemory = Boolean(e.target.checked); + renderTime(state.currentTime); + }); + controls.showLabels.addEventListener("change", (e) => { + state.showLabels = Boolean(e.target.checked); + renderTime(state.currentTime); + }); + controls.fileInput.addEventListener("change", async (e) => { + const file = e.target.files?.[0]; + if (!file) return; + const text = await file.text(); + loadTrace(text); + }); +} + +function loadTrace(text) { + stopPlayback(); + const events = parseJsonLines(text); + state.events = events; + const bounds = inferBounds(events); + state.maxX = bounds.maxX; + state.maxY = bounds.maxY; + const index = indexByTime(events); + state.byTime = index.byTime; + state.minTime = index.minTime; + state.maxTime = index.maxTime; + + controls.timeSlider.min = String(state.minTime); + controls.timeSlider.max = String(state.maxTime); + controls.timeSlider.value = String(state.minTime); + + drawStaticScene(); + renderTime(state.minTime); +} + +async function boot() { + initControls(); + + // Default behavior: load ../gemm.json.log when served from repo root. + try { + const resp = await fetch("../gemm.json.log"); + if (!resp.ok) throw new Error(`HTTP ${resp.status}`); + const text = await resp.text(); + loadTrace(text); + } catch (_) { + controls.statsLine.textContent = "Default log not loaded. Use the file picker."; + controls.eventDump.textContent = ""; + } +} + +boot(); diff --git a/tool/viz/index.html b/tool/viz/index.html new file mode 100644 index 0000000..cd8528b --- /dev/null +++ b/tool/viz/index.html @@ -0,0 +1,60 @@ + + + + + + CGRA GEMM Log Viewer + + + + +
+
+
+

CGRA GEMM Log Viewer

+

Timeline visualization for JSONL execution traces

+
+
+ + +
+
+ +
+
+ + + + + + T=0 +
+
+ +
+
+ + + + +
+
+ +
+ +
+ +
+

Cycle Details

+

+

+      
+
+ + + + diff --git a/tool/viz/styles.css b/tool/viz/styles.css new file mode 100644 index 0000000..0f2adf1 --- /dev/null +++ b/tool/viz/styles.css @@ -0,0 +1,187 @@ +:root { + --bg: #f2efe7; + --panel: #fffdf7; + --panel-2: #f8f3e6; + --text: #1a1f24; + --muted: #6c7680; + --line: #cbbfa1; + --tile: #fbf4df; + --tile-active: #ffebad; + --send: #006d77; + --recv: #118ab2; + --feed: #ef476f; + --collect: #8338ec; + --inst: #f77f00; + --memory: #d62828; + --shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 8px 20px rgba(0, 0, 0, 0.06); +} + +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; + color: var(--text); + background: + radial-gradient(circle at 90% 5%, #ffd89e44, transparent 35%), + radial-gradient(circle at 10% 90%, #8ecae644, transparent 38%), + var(--bg); + font-family: "Avenir Next", "Segoe UI", "Helvetica Neue", sans-serif; +} + +.layout { + max-width: 1200px; + margin: 1rem auto; + padding: 0 1rem 1.5rem; + display: grid; + gap: 0.8rem; + grid-template-columns: 1fr; +} + +.panel { + background: var(--panel); + border: 1px solid var(--line); + border-radius: 14px; + padding: 0.9rem 1rem; + box-shadow: var(--shadow); +} + +.topbar { + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; +} + +h1 { + margin: 0; + font-size: 1.3rem; +} + +h2 { + margin: 0 0 0.5rem; + font-size: 1.1rem; +} + +.subtitle { + margin: 0.2rem 0 0; + color: var(--muted); + font-size: 0.92rem; +} + +.file-load { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.92rem; +} + +.controls .row { + display: flex; + gap: 0.6rem; + align-items: center; + flex-wrap: wrap; + margin-bottom: 0.5rem; +} + +button, +select, +input[type="file"] { + border-radius: 8px; + border: 1px solid #bfb8a6; + background: var(--panel-2); + color: var(--text); + font-size: 0.9rem; + padding: 0.35rem 0.55rem; +} + +button { + cursor: pointer; +} + +#timeSlider { + width: 100%; +} + +.filters { + margin-bottom: 0; +} + +.filters label { + font-size: 0.9rem; +} + +.viz { + padding: 0.2rem; +} + +#canvas { + width: 100%; + min-height: 480px; + display: block; +} + +#statsLine { + margin: 0; + color: var(--muted); + font-size: 0.9rem; +} + +#eventDump { + max-height: 260px; + overflow: auto; + background: #fbf7ea; + border: 1px solid var(--line); + border-radius: 10px; + padding: 0.7rem; + margin-top: 0.6rem; + font-size: 0.82rem; + line-height: 1.45; +} + +.tile { + fill: var(--tile); + stroke: #9c9070; + stroke-width: 1.4px; +} + +.tile.active { + fill: var(--tile-active); +} + +.tile-label { + fill: #444; + font-size: 12px; + pointer-events: none; +} + +.legend-text, +.driver-label { + fill: #4d4d4d; + font-size: 11px; +} + +.event-link { + fill: none; + stroke-width: 2.4px; + stroke-linecap: round; +} + +.pulse { + r: 5; +} + +.inst-badge, +.memory-badge { + stroke: #222; + stroke-width: 0.5px; +} + +@media (max-width: 860px) { + #canvas { + min-height: 360px; + } +} diff --git a/verify/funcsim.go b/verify/funcsim.go index 30f42ff..da5fe0b 100644 --- a/verify/funcsim.go +++ b/verify/funcsim.go @@ -1,3 +1,4 @@ +//nolint:funlen package verify import ( @@ -12,6 +13,8 @@ import ( // Run executes the functional simulator for up to maxSteps iterations. // Returns an error if execution fails. +// +//nolint:gocyclo func (fs *FunctionalSimulator) Run(maxSteps int) error { if fs.programs == nil || fs.arch == nil { return fmt.Errorf("FunctionalSimulator not properly initialized") @@ -142,6 +145,8 @@ func (fs *FunctionalSimulator) isOperandReady(x, y int, operand *core.Operand) b } // executeOp executes a single operation +// +//nolint:gocyclo func (fs *FunctionalSimulator) executeOp(x, y int, op *core.Operation) { switch strings.ToUpper(op.OpCode) { case "MOV": @@ -597,6 +602,8 @@ func (fs *FunctionalSimulator) runNot(x, y int, op *core.Operation) { // runIcmp implements integer comparison operations // Semantics: ICMP_EQ, ICMP_SGT, ICMP_SLT, ICMP_SGE, ICMP_SLE, ICMP_SNE +// +//nolint:gocyclo func (fs *FunctionalSimulator) runIcmp(x, y int, op *core.Operation) { if len(op.SrcOperands.Operands) < 2 || len(op.DstOperands.Operands) == 0 { return @@ -885,13 +892,13 @@ func (fs *FunctionalSimulator) writeToNeighbor(x, y int, portDir string, color s func neighborFromWrite(x, y int, portDir string) (int, int, bool) { switch strings.ToUpper(portDir) { case "NORTH": - return x, y-1, true + return x, y - 1, true case "SOUTH": - return x, y+1, true + return x, y + 1, true case "EAST": - return x+1, y, true + return x + 1, y, true case "WEST": - return x-1, y, true + return x - 1, y, true default: return x, y, false } diff --git a/verify/lint.go b/verify/lint.go index 0a4d2f7..09cc504 100644 --- a/verify/lint.go +++ b/verify/lint.go @@ -1,3 +1,4 @@ +//nolint:funlen package verify import ( @@ -12,6 +13,8 @@ import ( // For kernels with modulo scheduling (ii > 0), it uses a D∈{0,1} iteration // distance model to reduce false positives on loop-carried dependencies. // Returns a list of issues found, or empty list if no issues. +// +//nolint:gocyclo func RunLint(programs map[string]core.Program, arch *ArchInfo) []Issue { var issues []Issue @@ -121,6 +124,8 @@ func RunLint(programs map[string]core.Program, arch *ArchInfo) []Issue { // - If consumer reads NORTH, producer is at (x, y-1) and writes SOUTH // - If consumer reads SOUTH, producer is at (x, y+1) and writes NORTH // - Similarly for EAST/WEST +// +//nolint:gocyclo func checkTimingConstraints(programs map[string]core.Program, arch *ArchInfo, ii int) []Issue { var issues []Issue diff --git a/verify/report.go b/verify/report.go index 52ee057..b6ffd4b 100644 --- a/verify/report.go +++ b/verify/report.go @@ -49,7 +49,9 @@ func GenerateReport(programs map[string]core.Program, arch *ArchInfo, maxSimStep return report } -// WriteReport writes a formatted report to a writer +// WriteReport writes a formatted report to a writer. +// +//nolint:gocyclo,funlen func (r *VerificationReport) WriteReport(w io.Writer) { separator := strings.Repeat("=", 60) dash := strings.Repeat("-", 60) @@ -90,17 +92,17 @@ func (r *VerificationReport) WriteReport(w io.Writer) { i+1, issue.PEX, issue.PEY, issue.Time, issue.OpID) fmt.Fprintf(w, " Message: %s\n", issue.Message) if issue.Details != nil { - if producer_t, ok := issue.Details["producer_t"]; ok { - fmt.Fprintf(w, " Producer writes at t=%v\n", producer_t) + if producerT, ok := issue.Details["producer_t"]; ok { + fmt.Fprintf(w, " Producer writes at t=%v\n", producerT) } - if consumer_t, ok := issue.Details["consumer_t"]; ok { - fmt.Fprintf(w, " Consumer reads at t=%v\n", consumer_t) + if consumerT, ok := issue.Details["consumer_t"]; ok { + fmt.Fprintf(w, " Consumer reads at t=%v\n", consumerT) } - if required_lat, ok := issue.Details["required_latency"]; ok { - fmt.Fprintf(w, " Required latency: %v cycles\n", required_lat) + if requiredLat, ok := issue.Details["required_latency"]; ok { + fmt.Fprintf(w, " Required latency: %v cycles\n", requiredLat) } - if actual_lat, ok := issue.Details["actual_latency"]; ok { - fmt.Fprintf(w, " Actual latency: %v cycles\n", actual_lat) + if actualLat, ok := issue.Details["actual_latency"]; ok { + fmt.Fprintf(w, " Actual latency: %v cycles\n", actualLat) } } fmt.Fprintln(w) @@ -158,7 +160,7 @@ func (r *VerificationReport) SaveReportToFile(filename string) error { if err != nil { return fmt.Errorf("failed to create report file: %w", err) } - defer file.Close() + defer func() { _ = file.Close() }() r.WriteReport(file) return nil diff --git a/verify/verify.go b/verify/verify.go index 3e36f2b..d42ea0e 100644 --- a/verify/verify.go +++ b/verify/verify.go @@ -133,7 +133,9 @@ import ( type IssueType string const ( + // IssueStruct indicates a mapping/structure lint issue. IssueStruct IssueType = "STRUCT" // Mapping/structure error (illegal PE, port conflict) + // IssueTiming indicates a dependency/timing lint issue. IssueTiming IssueType = "TIMING" // Dependency/timing error (insufficient latency) ) @@ -206,10 +208,10 @@ type FunctionalSimulator struct { arch *ArchInfo peStates [][]*PEState - currentT int + currentT int TraceOpPre func(x, y, t int, op *core.Operation) TraceOpPost func(x, y, t int, op *core.Operation) - TraceStore func(x, y, t int, addr uint32, value core.Data, op *core.Operation) + TraceStore func(x, y, t int, addr uint32, value core.Data, op *core.Operation) } // NewFunctionalSimulator creates a new functional simulator @@ -327,6 +329,8 @@ func parseCoordinate(coordStr string) (int, int, error) { // getNeighbor returns the neighbor PE coordinates given a direction // Returns (nx, ny, valid) where valid is false if neighbor is out of bounds +// +//nolint:unused func (arch *ArchInfo) getNeighbor(x, y int, dir string) (int, int, bool) { dirUpper := strings.ToUpper(dir) switch dirUpper { diff --git a/verify/verify_test.go b/verify/verify_test.go index 563da9e..76d446c 100644 --- a/verify/verify_test.go +++ b/verify/verify_test.go @@ -1,3 +1,4 @@ +//nolint:funlen package verify import (