Merge branch 'feature/hal-os'

master
kolaente 4 years ago
commit 81e8dd1cd9
Signed by: kolaente
GPG Key ID: F40E70337AB24C9B
  1. 22
      cluster.yml
  2. 54
      cmd/cluster.go
  3. 25
      cmd/root.go
  4. 3
      go.mod
  5. 3
      go.sum
  6. 59
      hal/cluster.go
  7. 149
      hal/cluster_test.go
  8. 12
      hal/command_line_io.go
  9. 14
      hal/instructions.go
  10. 22
      hal/module.go
  11. 9
      hal/module_test.go
  12. 21
      hal/multi_threaded_io.go
  13. 23
      parser/from_config.go
  14. 22
      parser/program_parser.go
  15. 0
      programs/example.hal
  16. 0
      programs/newton1.hal
  17. 0
      programs/newton2.hal
  18. 2
      programs/noop.hal
  19. 15
      programs/prak3.hal

@ -0,0 +1,22 @@
nodes:
- name: node1
program: ./programs/prak3.hal
- name: node2
program: ./programs/prak3.hal
- name: node3
program: ./programs/prak3.hal
- name: node4
program: ./programs/prak3.hal
connections:
- source_node: node1
source_port: 3
dest_node: node2
dest_port: 2
- source_node: node2
source_port: 3
dest_node: node3
dest_port: 2
- source_node: node3
source_port: 3
dest_node: node4
dest_port: 2

@ -0,0 +1,54 @@
package cmd
import (
"context"
"git.jfdev.de/JonasFranzDEV/hal/hal"
"git.jfdev.de/JonasFranzDEV/hal/parser"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
"io/ioutil"
"os"
)
var clusterCmd = &cobra.Command{
Args: cobra.ExactArgs(1),
Use: "cluster [config file]",
RunE: runClusterCommand,
}
func init() {
rootCommand.AddCommand(clusterCmd)
}
func runClusterCommand(cmd *cobra.Command, args []string) error {
configFile := args[0]
// Parse config
if stats, err := os.Stat(configFile); err != nil || stats.IsDir() {
return err
}
file, err := ioutil.ReadFile(configFile)
if err != nil {
return err
}
config := &hal.ClusterConfig{}
if err := yaml.Unmarshal(file, config); err != nil {
return err
}
// Parse modules
modules, err := parser.GetModulesFromConfig(config, debug)
if err != nil {
return err
}
// Create the cluster
cluster, err := config.BuildCluster(modules)
if err != nil {
return err
}
// Run
return cluster.Run(context.Background())
}

@ -1,7 +1,6 @@
package cmd
import (
"bufio"
"fmt"
"os"
"time"
@ -23,10 +22,10 @@ var rootCommand = &cobra.Command{
Args: cobra.ExactArgs(1),
Use: "hal",
RunE: runRootCommand,
PreRun: func(cmd *cobra.Command, args []string) {
PersistentPreRun: func(cmd *cobra.Command, args []string) {
startTime = time.Now()
},
PostRun: func(cmd *cobra.Command, args []string) {
PersistentPostRun: func(cmd *cobra.Command, args []string) {
totalTime := time.Since(startTime)
fmt.Printf("Total execution time: %v\n", totalTime)
},
@ -37,28 +36,12 @@ var debug bool
var startTime time.Time
func init() {
rootCommand.Flags().BoolVarP(&debug, "debug", "d", false, "Enable debug mode")
rootCommand.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "Enable debug mode")
}
func runRootCommand(cmd *cobra.Command, args []string) error {
filename := args[0]
if stats, err := os.Stat(filename); err != nil || stats.IsDir() {
return err
}
file, err := os.Open(filename)
if err != nil {
return err
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
var input []string
for scanner.Scan() {
input = append(input, scanner.Text())
}
if err := file.Close(); err != nil {
return err
}
program, err := parser.ParseProgram(input)
program, err := parser.ParseFile(filename)
if err != nil {
return err
}

@ -6,5 +6,6 @@ require (
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.5.1
gopkg.in/yaml.v2 v2.3.0 // indirect
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
gopkg.in/yaml.v2 v2.3.0
)

@ -122,7 +122,10 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

@ -0,0 +1,59 @@
package hal
import (
"context"
"golang.org/x/sync/errgroup"
)
type Cluster struct {
modules []*Module
}
func (c *Cluster) Run(ctx context.Context) error {
errs, _ := errgroup.WithContext(ctx)
for _, module := range c.modules {
errs.Go(module.Run)
}
return errs.Wait()
}
func (config *ClusterConfig) BuildCluster(modules map[string]*Module) (*Cluster, error) {
// We assume all modules in the connection config exist
for _, c := range config.Connections {
conn := NewMultiThreadedIO()
modules[c.SourceNode].IO[c.SourcePort] = conn
modules[c.DestNode].IO[c.DestPort] = conn
}
// for host, client := range mapping {
// modules[host.Module] = struct{}{}
// modules[client.Module] = struct{}{}
// host.Module.IO[host.IOPort] = NewMultiThreadedIO(&client)
// }
cluster := &Cluster{modules: make([]*Module, len(modules), len(modules))}
var index int64
for _, module := range modules {
cluster.modules[index] = module
index++
}
return cluster, nil
}
type ClusterConnection struct {
SourcePort int64 `yaml:"source_port"`
SourceNode string `yaml:"source_node"`
DestPort int64 `yaml:"dest_port"`
DestNode string `yaml:"dest_node"`
}
type ClusterNode struct {
Name string `yaml:"name"`
// Path to the hal program
Program string `yaml:"program"`
}
type ClusterConfig struct {
Nodes []*ClusterNode `yaml:"nodes"`
Connections []*ClusterConnection `yaml:"connections"`
}

@ -0,0 +1,149 @@
package hal
import (
"github.com/stretchr/testify/assert"
"testing"
)
// func TestCluster_Run(t *testing.T) {
// var mapping ModuleMapping
//
// firstModule := &Module{
// programStorage: map[int64]*ProgrammedInstruction{
// 1: {
// Instruction: InstructionStart,
// },
// },
// register: []float64{0},
// IO: map[int64]IO{},
// }
// secondModule := &Module{
// programStorage: map[int64]*ProgrammedInstruction{
// 1: {
// Instruction: InstructionStop,
// },
// },
// register: []float64{0},
// IO: map[int64]IO{},
// }
//
// mapping = map[MultiThreadedIOPort]MultiThreadedIOPort{
// {
// Module: firstModule,
// IOPort: 0,
// }: {
// Module: secondModule,
// IOPort: 0,
// },
// }
// assert.NoError(t, mapping.BuildCluster().Run(context.Background()))
// }
func TestModuleMapping_BuildCluster(t *testing.T) {
config := &ClusterConfig{
Nodes: []*ClusterNode{
{
Name: "node1",
},
{
Name: "node2",
},
{
Name: "node3",
},
},
Connections: []*ClusterConnection{
{
SourceNode: "node1",
SourcePort: 1,
DestNode: "node2",
DestPort: 1,
},
{
SourceNode: "node2",
SourcePort: 1,
DestNode: "node1",
DestPort: 1,
},
{
SourceNode: "node1",
SourcePort: 2,
DestNode: "node2",
DestPort: 2,
},
{
SourceNode: "node2",
SourcePort: 2,
DestNode: "node1",
DestPort: 2,
},
{
SourceNode: "node3",
SourcePort: 2,
DestNode: "node2",
DestPort: 3,
},
{
SourceNode: "node2",
SourcePort: 3,
DestNode: "node3",
DestPort: 2,
},
{
SourceNode: "node3",
SourcePort: 3,
DestNode: "node1",
DestPort: 3,
},
{
SourceNode: "node1",
SourcePort: 3,
DestNode: "node3",
DestPort: 3,
},
},
}
modules := make(map[string]*Module)
modules = map[string]*Module{
"node1": {
programStorage: map[int64]*ProgrammedInstruction{
1: {Instruction: InstructionStart},
2: {Instruction: InstructionStop},
},
register: []float64{0},
IO: map[int64]IO{},
},
"node2": {
programStorage: map[int64]*ProgrammedInstruction{
1: {Instruction: InstructionStart},
2: {Instruction: InstructionStop},
},
register: []float64{0},
IO: map[int64]IO{},
},
"node3": {
programStorage: map[int64]*ProgrammedInstruction{
1: {Instruction: InstructionStart},
2: {Instruction: InstructionStop},
},
register: []float64{0},
IO: map[int64]IO{},
},
}
cluster, err := config.BuildCluster(modules)
assert.NoError(t, err)
// First connection config
assert.NotNil(t, cluster.modules[0].IO[1])
assert.NotNil(t, cluster.modules[1].IO[1])
// Second
assert.NotNil(t, cluster.modules[0].IO[2])
assert.NotNil(t, cluster.modules[1].IO[2])
// Third
assert.NotNil(t, cluster.modules[1].IO[3])
assert.NotNil(t, cluster.modules[2].IO[2])
// Fourth
assert.NotNil(t, cluster.modules[0].IO[3])
assert.NotNil(t, cluster.modules[2].IO[3])
}

@ -4,23 +4,25 @@ import (
"fmt"
)
func NewCommandLineIO(index int) *CommandLineIO {
func NewCommandLineIO(index int64, moduleName string) *CommandLineIO {
return &CommandLineIO{
index: index,
index: index,
moduleName: moduleName,
}
}
type CommandLineIO struct {
index int
index int64
moduleName string
}
func (io *CommandLineIO) Read() (result float64, err error) {
fmt.Printf("Input[%d]: ", io.index)
fmt.Printf("Input[%s][%d]: ", io.moduleName, io.index)
_, err = fmt.Scanf("%f", &result)
return
}
func (io *CommandLineIO) Write(output float64) error {
fmt.Printf("Output[%d]: %f\n", io.index, output)
fmt.Printf("Output[%s][%d]: %f\n", io.moduleName, io.index, output)
return nil
}

@ -78,20 +78,20 @@ var InstructionStop = &Instruction{
var InstructionOut = &Instruction{
Name: "OUT",
ExecuteWithOperand: func(module *Module, operand float64) error {
index := int(operand)
if len(module.IO) <= index {
return fmt.Errorf("index %d exceeds IO size of %d", index, len(module.IO))
index := int64(operand)
if _, ok := module.IO[index]; !ok {
return fmt.Errorf("IO[%d] is not registered", index)
}
return module.IO[int64(operand)].Write(module.accumulator)
return module.IO[index].Write(module.accumulator)
},
}
var InstructionIn = &Instruction{
Name: "IN",
ExecuteWithOperand: func(module *Module, operand float64) (err error) {
index := int(operand)
if len(module.IO) <= index {
return fmt.Errorf("index %d exceeds IO size of %d", index, len(module.IO))
index := int64(operand)
if _, ok := module.IO[index]; !ok {
return fmt.Errorf("IO[%d] is not registered", index)
}
module.accumulator, err = module.IO[index].Read()
return

@ -15,10 +15,11 @@ type Module struct {
accumulator float64
programStorage Program
register []float64
IO []IO
IO map[int64]IO
isStopped bool
debugEnabled bool
name string // Used to print the module name when debugging
}
func (h *Module) programCounter() int64 {
@ -40,7 +41,8 @@ func (h *Module) debug(format string, args ...interface{}) {
if !h.debugEnabled {
return
}
fmt.Printf("[DEBUG] "+format+"\n", args...)
fmt.Printf("[DEBUG] ["+h.name+"] "+format+"\n", args...)
}
func (h *Module) Step() error {
@ -71,18 +73,26 @@ func (h *Module) Run() error {
return nil
}
func NewHALModule(program Program, registerSize uint64, ioSize uint64, debug bool) (*Module, error) {
func NewHALModule(program Program, registerSize uint64, ioSize int64, debug bool) (*Module, error) {
return NewNamedHALModule(program, registerSize, ioSize, debug, "")
}
func NewNamedHALModule(program Program, registerSize uint64, ioSize int64, debug bool, name string) (*Module, error) {
if registerSize <= 10 {
return nil, fmt.Errorf("register size must be greater then 10 [ registerSize = %d ]", registerSize)
}
ios := make([]IO, ioSize)
for index, _ := range ios {
ios[index] = NewCommandLineIO(index)
ios := make(map[int64]IO)
if ioSize < 0 {
return nil, fmt.Errorf("io size must positive")
}
for i := int64(0); i <= ioSize; i++ {
ios[i] = NewCommandLineIO(i, name)
}
return &Module{
programStorage: program,
register: make([]float64, registerSize),
IO: ios,
debugEnabled: debug,
name: name,
}, nil
}

@ -16,13 +16,10 @@ func TestModule_Run(t *testing.T) {
},
}
module := &Module{
programStorage: program,
register: make([]float64, 256),
IO: make([]float64, 2),
}
module, err := NewHALModule(program, 256, 2, true)
assert.NoError(t, err)
err := module.Run()
err = module.Run()
assert.NoError(t, err)
assert.True(t, module.isStopped)
assert.Equal(t, float64(11), module.register[0])

@ -0,0 +1,21 @@
package hal
type MultiThreadedIO struct {
channel chan float64
}
func NewMultiThreadedIO() *MultiThreadedIO {
return &MultiThreadedIO{
channel: make(chan float64),
}
}
func (io *MultiThreadedIO) Read() (float64, error) {
result := <-io.channel
return result, nil
}
func (io *MultiThreadedIO) Write(content float64) error {
io.channel <- content
return nil
}

@ -0,0 +1,23 @@
package parser
import (
"git.jfdev.de/JonasFranzDEV/hal/hal"
)
func GetModulesFromConfig(config *hal.ClusterConfig, debug bool) (modules map[string]*hal.Module, err error) {
// all modules with their name as key
modules = make(map[string]*hal.Module)
for _, node := range config.Nodes {
program, err := ParseFile(node.Program)
if err != nil {
return nil, err
}
// By default, all IO is the standard IO, we'll override it with channels where needed.
modules[node.Name], err = hal.NewNamedHALModule(program, 256, 4, debug, node.Name)
if err != nil {
return nil, err
}
}
return
}

@ -1,13 +1,35 @@
package parser
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"git.jfdev.de/JonasFranzDEV/hal/hal"
)
func ParseFile(filepath string) (hal.Program, error) {
if stats, err := os.Stat(filepath); err != nil || stats.IsDir() {
return nil, err
}
file, err := os.Open(filepath)
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
var input []string
for scanner.Scan() {
input = append(input, scanner.Text())
}
if err := file.Close(); err != nil {
return nil, err
}
return ParseProgram(input)
}
// ParseProgram parses an program by the following specification: LINE_NUMBER INSTRUCTION OPERAND(optional)
func ParseProgram(input []string) (hal.Program, error) {
program := make(hal.Program)

@ -0,0 +1,2 @@
01 START
02 STOP

@ -0,0 +1,15 @@
01 START
02 IN 2
03 STORE 1
04 JUMPNEG 30
10 MULNUM 2
11 ADDNUM 1
12 OUT 1
30 OUT 3
31 LOAD 1
32 JUMPPOS 02
99 STOP
Loading…
Cancel
Save