Documentation ¶
Overview ¶
Package hwsim provides the necessary tools to build a virtual CPU using Go as a hardware description language and run it.
This includes a naive hardware simulator and an API to compose basic components (logic gates, muxers, etc.) into more complex ones.
The API is designed to mimmic a basic hardware description language and reduce typing overhead.
The sub-package hwlib provides a library of built-in logic gates as well as some more advanced components.
The simulation is built around wires that connect components together. While a Wire can recieve a signal by only one component, it can broadcast that signal to any number of components (fanout).
The simulation works by updating every half clock cycle the components that implement the PostUpdater interface (i.e. that have side effects or somehow "drive" the circuit, like outputs and clocked data flip-flops). The signals are then propagated through the simulation by "pulling" them up: calling Recv on a Wire triggers an update of the component feeding that Wire.
Time in the simulation is simply represented as a boolean value, false during the call to Circuit.Tick() and true during the call to Circuit.Tock(). Wires use this information to prevent recursion and provide loop detection.
As a result:
- Most components ignore the clk argument to Updater.Update(clk bool) and just forward it to the Send/Recv methods of their connected Wires.
- Most components like logic gates have no propagation delay.
- Other components like Data Flip-Flops (DFF) have a one clock cycle propagation delay.
- Direct wire loops are forbidden. Loops must go through a DFF or similar component.
The DFF provided in the hwlib package works like a gated D latch and can be used as a building block for all sequential components. Its output is considered stable only during calls to Circuit.Tick(), i.e. when the clk argument of Updater.Update(clk bool) is true.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( False = "false" // always false input True = "true" // alwyas true input Clk = "clk" // clock signal. False during Tick, true during Tock. )
Constant Wire names. These wires can only be connected to the input pins of a part.
Those are reserved names and should not be used as input or output names in custom chips.
Functions ¶
func IO ¶
IO is a wrapper around ParseIOSpec that panics if an error is returned.
Example ¶
package main import ( "fmt" "github.com/db47h/hwsim" ) func main() { fmt.Println(hwsim.IO("a,b")) fmt.Println(hwsim.IO("a[2],b")) fmt.Println(hwsim.IO("a[0..0],b[1..2]")) }
Output: [a b] [a[0] a[1] b] [a[0] b[1] b[2]]
func ParseIOSpec ¶
ParseIOSpec parses an input or output pin specification string and returns a slice of individual pin names suitable for use as the Input or Output field of a PartSpec.
The input format is:
InputDecl = PinDecl { "," PinDecl } . PinDecl = PinIdentifier | BusIdentifier . BusId = identifier "[" size | Range "]" . PinId = identifier . Range = integer ".." integer . identifier = letter { letter | digit } . size = { digit } . letter = "A" ... "Z" | "a" ... "z" | "_" . digit = "0" ... "9" .
Buses are declared by simply specifying their size. For example, the I/O specification string "a, b, bus[4]" will be expanded to:
[]string{"a", "b", "bus[0]", "bus[1]", "bus[2]", "bus[3]"}
Ranges can also be used to force a specific range of bus indices:
ParseIOSpec("p, g, c[1..4]")
will expand to:
[]string{"p", "g", "c[1]", "c[2]", "c[3]", "c[4]"}
Types ¶
type Bus ¶
type Bus []*Wire
A Bus is a set of Wires.
type Circuit ¶
type Circuit struct {
// contains filtered or unexported fields
}
Circuit is a runnable circuit simulation.
func NewCircuit ¶
NewCircuit builds a new circuit simulation based on the given parts.
func (*Circuit) ComponentCount ¶
ComponentCount returns the number of components in the circuit.
func (*Circuit) Tick ¶
func (c *Circuit) Tick()
Tick runs the simulation until the beginning of the next half clock cycle.
func (*Circuit) TickTock ¶
func (c *Circuit) TickTock()
TickTock runs the simulation for a whole clock cycle.
type Connection ¶
A Connection represents a connection between the pin PP of a part and the pins CP in its host chip.
func ParseConnections ¶
func ParseConnections(c string) (conns []Connection, err error)
ParseConnections parses a connection configuration like "partPinX=chipPinY, ..." into a []Connection{{PP: "partPinX", CP: []string{"chipPinX"}}, ...}.
Wire = Assignment { [ space ] "," [ space ] Assignment } . Assignment = Pin "=" Pin . Pin = identifier [ "[" Index | Range "]" ] . Index = integer . Range = integer ".." integer . identifier = letter { letter | digit } . integer = { digit } . letter = "A" ... "Z" | "a" ... "z" | "_" . digit = "0" ... "9" .
type MountFn ¶
A MountFn mounts a part into socket s. MountFn's should query the socket to get Wires connected to a part's pins and return closures around these Wires.
For example, a Not gate can be defined like this:
notSpec := &hwsim.PartSpec{ Name: "Not", In: hwsim.IO("in"), Out: hwsim.IO("out"), Mount: func (s *hwsim.Socket) hwsim.Updater { in, out := s.Wire("in"), s.Wire("out") return hwsim.UpdaterFn( func (clk bool) { out.Send(clk, !in.Recv(clk)) } ) }}
type NewPartFn ¶
A NewPartFn is a function that takes a connection configuration and returns a new Part. See ParseConnections for the syntax of the connection configuration string.
func Chip ¶
Chip composes existing parts into a new chip.
The pin names specified as inputs and outputs will be the inputs and outputs of the chip (the chip interface).
A XOR gate could be created like this:
xor, err := hwsim.Chip( "XOR", hwsim.IO("a, b"), hwsim.IO("out"), hwlib.Nand("a=a, b=b, out=nandAB"), hwlib.Nand("a=a, b=nandAB, out=w0"), hwlib.Nand("a=b, b=nandAB, out=w1"), hwlib.Nand("a=w0, b=w1, out=out"), )
The created chip can be composed with other parts to create other chips simply by calling the returned NewPartFn with a connection configuration:
xnor, err := Chip( "XNOR", hwsim.IO("a, b"), hwsim.IO("out"), // reuse the xor chip created above xor("a=a, b=b, out=xorAB"), hwlib.Not("in=xorAB, out=out"), )
type Part ¶
type Part struct { *PartSpec Conns []Connection }
A Part wraps a part specification together with its connections within a host chip.
type PartSpec ¶
type PartSpec struct { // Part name. Name string // Input pin names. Must be distinct pin names. // Use the IO() function to expand an input description like // "a, b, bus[2]" to []string{"a", "b", "bus[0]", "bus[1]"} // See IO() for more details. Inputs []string // Output pin name. Must be distinct pin names. // Use the IO() function to expand an output description string. Outputs []string // Pinout maps the input and output pin names (public interface) of a part // to internal (private) names. If nil, the In and Out values will be used // and mapped one to one. // In a MountFn, only internal pin names must be used when calling the Socket // methods. // Most custom part implementations should ignore this field and set it to // nil. Pinout map[string]string // Mount function (see MountFn). Mount MountFn }
A PartSpec represents a part specification (its blueprint).
Custom parts are implemented by creating a PartSpec:
notSpec := &hwsim.PartSpec{ Name: "Not", In: hwsim.IO("in"), Out: hwsim.IO("out"), Mount: func (s *hwsim.Socket) hwsim.Updater { in, out := s.Wire("in"), s.Wire("out") return hwsim.UpdaterFn( func (clk bool) { out.Send(clk, !in.Recv(clk)) } ) }}
Then get a NewPartFn for that PartSpec:
var notGate = notSpec.NewPart
or:
func Not(c string) Part { return notSpec.NewPart(c) }
Which can the be used as a NewPartFn when building other chips:
c, _ := Chip("dummy", In("a, b"), Out("notA, d"), notGate("in: a, out: notA"), // ... )
func MakePart ¶
MakePart wraps an Updater into a custom component. Input/output pins are identified by tags on fields of type *Wire.
The field tag must be `hw:"in"“ or `hw:"out"` to identify input and output pins. By default, the pin name is the field name in lowercase. A specific field name can be forced by adding it in the tag: `hw:"in,pin_name"`.
Buses must be arrays of *Wire (not slices).
Example ¶
MakePart example with a custom Mux4
package main import ( "fmt" hw "github.com/db47h/hwsim" ) // mux4 is a custom 4 bits mux. type mux4Impl struct { A [4]*hw.Wire `hw:"in"` // input bus "a" B [4]*hw.Wire `hw:"in"` // input bus "b" S *hw.Wire `hw:"in,sel"` // single pin, the second tag value forces the pin name to "sel" Out [4]*hw.Wire `hw:"out"` // output bus "out" } // Update implements Updater. func (m *mux4Impl) Update(clk bool) { if m.S.Recv(clk) { for i, b := range m.B { m.Out[i].Send(clk, b.Recv(clk)) } } else { for i, a := range m.A { m.Out[i].Send(clk, a.Recv(clk)) } } } // no need to import reflect, just cast a nil pointer to mux4 var m4Spec = hw.MakePart((*mux4Impl)(nil)) // m4Spec is the *PartSpec for our mux4. In order to use it like the built-ins // in hwlib, we need to get its NewPartFn method as a variable, or make it a function: func Mux4(c string) hw.Part { return m4Spec.NewPart(c) } // MakePart example with a custom Mux4 func main() { var a, b, out uint64 var sel bool c, err := hw.NewCircuit( // IOs to test the circuit hw.InputN(4, func() uint64 { return a })("out=in_a"), hw.InputN(4, func() uint64 { return b })("out=in_b"), hw.Input(func() bool { return sel })("out=in_sel"), // our custom Mux4 Mux4("a=in_a, b=in_b, sel=in_sel, out=mux_out"), // IOs continued... hw.OutputN(4, func(v uint64) { out = v })("in=mux_out"), ) if err != nil { panic(err) } a, b, sel = 1, 15, false c.TickTock() fmt.Printf("a=%d, b=%d, sel=%v => out=%d\n", a, b, sel, out) sel = true c.TickTock() fmt.Printf("a=%d, b=%d, sel=%v => out=%d\n", a, b, sel, out) }
Output: a=1, b=15, sel=false => out=1 a=1, b=15, sel=true => out=15
type PostUpdater ¶
type PostUpdater interface { Updater // Update updates outputs PostUpdate(clk bool) // Update inputs }
PostUpdater is implemented by Updaters that have side effects outside of a circuit or that somehow drive the circuit. All sequential components must implement PostUpdater.
type PostUpdaterFn ¶
type PostUpdaterFn func(clk bool)
A PostUpdaterFn is a single update function that implements PostUpdater.
func (PostUpdaterFn) PostUpdate ¶
func (f PostUpdaterFn) PostUpdate(clk bool)
PostUpdate implements PostUpdater.
type Socket ¶
type Socket struct {
// contains filtered or unexported fields
}
A Socket maps a part's internal pin names to Wires in a circuit. See PartSpec.Pinout.
type Updater ¶
type Updater interface { // Update is called every time an Updater's output pins must be updated. // The clk value is the current state of the clock signal (false during a // tick, true during a tock). Non-clocked components should ignore this // signal and just pass it along in the Recv and Send calls to their // connected wires. Update(clk bool) }
Updater is the interface for components in a circuit.
Clocked components must also implement PostUpdater.
type UpdaterFn ¶
type UpdaterFn func(clk bool)
A UpdaterFn is a single update function that implements Updater.
type Wire ¶
type Wire struct {
// contains filtered or unexported fields
}
A Wire connects pins together. A Wire may have only one source pin and multiple destination pins. i.e. Only one component can send a signal on a Wire.
Directories ¶
Path | Synopsis |
---|---|
Package hwlib provides a library of reusable parts for hwsim.
|
Package hwlib provides a library of reusable parts for hwsim. |
Package hwtest provides utility functions for testing circuits.
|
Package hwtest provides utility functions for testing circuits. |
internal
|
|
lex
Package lex provides a functional lexer.
|
Package lex provides a functional lexer. |