Calling a struct as a function

question

#1

I’m attempting to use Julia as a hardware description language (HDL).
The idea is somewhere between Verilog and Matlab, which Electrical Engineers are both readily familiar with.

Ideally, I would make the HDL as convenient to use as possible for the end-user, where Electrical Engineers are an idiosyncratic bunch when it comes to their programming languages.

Ideally, the syntax would be modeled very similarly to that of the existing verilog syntax.

As a first question, Is it possible to call a struct as a function?

I created a struct called BitNumber

struct BitNumber
    val::Int
    bit_end::UInt
    bit_start::UInt
    width::UInt
    BitNumber(v,e,s,w,b) = new( (a=63-e+s; v=v<<a ; v>>a) ,e,s,w, bits)
end

it stores a value, and contains some information about the bits at use.

if I were to create an instance of the BitNumber, I’d like to be able to extract specific bits from the number with a simple syntax.

i.e.

a = BitNumber(value = 15, bits = 4)
# a is 4 bits wide and has value 2

b = a(2,1)
# b is 2 bits wide and has value 3

I also have overloaded the bits function that does exactly this:

import Base.bits
function bits(number::BitNumber, bit_end, bit_start)
	n = number.val << (63 - bit_end + number.bit_start)
	n = n >> (63 - bit_end + bit_start + number.bit_start)
	return BitNumber(n, bit_end - bit_start)
end

a = BitNumber(value = 15, bits = 4)
# a is 4 bits wide and has value 2

b = bits(a, 2, 1)

Is there a means to either

  1. Call an instantiated BitNumber as a function, and define that function however I choose

  2. Contain a function inside the BitNumber struct that can access the BitNumber’s other info

i.e.

struct BitNumber
    val::Int
    bit_end::UInt
    bit_start::UInt
    width::UInt
    the_bits::Function
    BitNumber(v,e,s,w,b) = new( (a=63-e+s; v=v<<a ; v>>a) ,e,s,w, bits)
end

a = BitNumber(value = 15, bits = 4)

b = a.the_bits(1,2)

Am I just barking up the wrong tree?

Thank you!


#2

For a type in v0.5 you could define a function like this that allows the syntax b = a(2,1) (it’s probably the same for a struct in v0.6):

function (a::BitNumber)(width, val)
...
end

#3

Have you seen https://github.com/interplanetary-robot/Verilog.jl?

julia> struct Foo
       end

julia> f = Foo()
Foo()

julia> (x::Foo)() = println("hello")

julia> f()
hello

#4

for your first question then no, not at all it is possible to make any type callable.

for your second question then maybe ,
in order to have some kind of self referential behavior (e.g accessing the contents of the type implicitly from
a “member” function) you would need to make your struct into a mutable struct and construct it in 2 steps

mutable struct BitNumber
    val::Int
    bit_end::UInt
    bit_start::UInt
    width::UInt
    the_bits::Function
    BitNumber(v,e,s,w,bits_func) = begin
        ret = new( (a=63-e+s; v=v<<a ; v>>a) ,e,s,w)
        ret.the_bits = (bit_end,bit_start) -> bits_func(ret,bit_end,bit_start)
        ret
    end
end

But making it into a mutable struct is not what you want probably. since the contents can be then
modified arbitrarily, and can end up in a un-realistic state with no real-world meaning.

what you can do is maybe choose a more fluent function name and supply default values
for example:

function bitsof(number::BitNumber,bit_end = number.bit_end,bit_start = number.bit_start)
    bits(number,bit_end,bit_start)
end

bitsof(a)
bitsof(a,2,1)

and you can add a method that accepts keyword arguments,although slower can sometime enhance usability

function bitsof(number::BitNumber;bit_end = number.bit_end,bit_start = number.bit_start)
    bits(number,bit_end,bit_start)
end

bitsof(a, start_number = 1,end_number = 2)

#5

@ihnorton

Yes, I have seen Verilog.jl
It’s quite impressive.
What I’m trying to do is somewhat different.
Not sure how familiar you are with HDLs, but Berkeley just released an HDL written in Scala called Chisel.
The idea with Chisel is to use dynamism to aid in hardware description.
Verilog.jl is attempting to do a similar thing in Julia.

I’m attempting to create an HDL which is polymorphic.
Basically, matlab code that can turn into verilog.

as for my question, I’m working on it, and will return with a more thorough answer.


#6

Deprecated the bits function, and stuck with the given example


#Can index the bits in a BN using parentheses
function (number::BitNumber)(bit_e, bit_s)
    n = number.val << (63 - bit_e + number.bit_start)
    n = n >> (63 - bit_e + bit_s + number.bit_start)
    return BitNumber(n, abs(bit_e -bit_s), 0)
end
function (number::BitNumber)(index)
    n = number.val << (63 - index + number.bit_start)
    n = n >> 63
    return BitNumber(n, 1, 0)
end

And, is it possible to overload Base.colon?

I get a lot of errors when I attempt to do it.

The reason I want to is because

a = 0b111000

a[5:0]
# equals 111000
a[0:5]
# equals 000111

#7

Here are other object description DSLs you might want to look at:



#8

Thank you Sir.


#9

Do you mean something like this?

julia> type B end

julia> Base.getindex(::B, I::UnitRange{Int}) = "I start at $(start(I))"

julia> B()[1:2]
"I start at 1"

julia> B()[2:10]
"I start at 2"

But it is best not to define Base.getindex(::UInt, ::UnitRange{Int}). This is discouraged, see https://docs.julialang.org/en/stable/manual/style-guide/#Avoid-type-piracy-1 for the reasons.

Edit: With colons you can do it like that:

julia> Base.getindex(::B, ::Base.Colon) = "I am a colon"

julia> B()[:]
"I am a colon"

#10

Not exactly, no.

for a BitNumber (now wire in my source)

a = BitNumber(value = 56, bwidth = 6)
# a is equal to 111000

But, in order to support both big-endian syntax, and little-endian syntax the following must be permissible

For a Little-Endian system,
111000 = a[0:5]

for a Big-Endian system
111000 = a[5:0]

at present, it appears that UnitRange doesn’t support counting down?

For the end-user, a colon syntax of [a:b] must allow for both
a > b
and
b > a

Which doesn’t seem permitted.


#11

Try 5:-1:0 (which is a StepRange).


#12

Yes. But forcing the user to specify a negative count is pretty undesirable.

I’ll look into step range.

Thank you.


#13

UnitRanges with b < a already have a meaning in Julia, i.e. that the range is empty.
It can be used to denote an insertion point, for example, when returned from a search function.