Using AutoFPGA to connect simple registers to a debugging bus
My prior post regarding AutoFPGA discussed why I built AutoFPGA and what I intended to accomplish with it.
Today, let’s take a look at how to create some simple AutoFPGA component files that you can then use within a basic AutoFPGA design. Specifically, let’s look at how to connect wishbone components having only a single data register to an AutoFPGA project.
To do this, we’ll start out by taking a look at AutoFPGA’s configuration file format. You’ll need that to understand the basics of how to read the configuration files themselves. We’ll then discuss briefly the two Verilog files AutoFPGA generates, toplevel.v and main.v, and then how the various AutoFPGA tags get placed within that structure. This will then lead us to the point where we can create the AutoFPGA configuration files necessary to read and write single registers from a peripheral.
AutoFPGA’s Configuration File Format
Before we start discussing the details of how to write particular AutoFPGA cofiguration files, it only makes sense to pause to comment on the file structure in general. Indeed, AutoFPGA’s configuration file structure is actually fairly simple, with its focus primarily on staying out of the way.
Key value pairs
AutoFPGA
configuration files consist of a series key value pairs. Keys start at the
beginning of a line with an @
sign and end with an =
sign. Hence, to
define the key KEY
to have a value VALUE
, you’d write:
If the VALUE
fits within a single line, spaces will be trimmed from the ends
of it.
Multi-line values
Value’s can also take up multiple lines, as in,
Any white space found within multiple line VALUE
s will be left alone.
Comments
AutoFPGA
allows line based comments. Comment lines begin with either a #
and a
space, or two ##
s. Other comment characters, such as //
or /* ... */
will create comments within the result files that the VALUE
gets pasted into.
Unordered
Keys within an
AutoFPGA
configuration file are unordered, with the only exception being the
@PREFIX
key that separates components within a given configuration file
(more on this key in a moment).
What this means is that you can place keys for the top level module before
those for the
main module,
after the main module
keys, or interspersed between the two.
Substitution
VALUE
s from one KEY
may be substituted into VALUE
s from another, by
referencing the value by its keys name, as in @$(KEY)
.
For example, if you define a key @DEVID
,
You can later reference this key within another key’s value by using
@$(DEVID
to reference it. Hence,
will get expanded into
Expressions
KEY
s that start with @$
instead of @
define integer valued expressions.
AutoFPGA contains a simple expression
evaluator, allowing things like:
In this case, if you’ve defined BAUDRATE
as
or even
and CLKFREQHZ
as
then
AutoFPGA would calculate the
@BAUDCLOCKS
frequency to be 100 clocks. If @$(BAUDCLOCKS)
is used in
an string context, it will evaluate to the string "100"
. (The format
is adjustable via the @BAUDCLOCKS.FORMAT
tag, but we’ll get to that later.)
The @PREFIX key
All
AutoFPGA components begin with a
@PREFIX
key. This key defines both the beginning of the component’s keys
within a configuration file, as well as the end of any prior component that
may also be defined within the file. The @PREFIX
tag also creates a
sort of local variable namespace, since keys defined following a @PREFIX
key are quietly prefixed in the global key structure by the @PREFIX
.
While there are a couple of other details to AutoFPGA’s configuration files, those other details can wait until we need them later.
AutoFPGA’s RTL File Structure
Before you integrate your component into AutoFPGA, you’ll wnat to understand how AutoFPGA looks at RTL (Verilog) files.
In general, AutoFPGA insists that a design should have the structure shown in Fig 1. In this structure, the AutoFPGA generated files are marked with a red star.
In this structure, there is a top level file. Within this top level file are any references to vendor specific components. For example, MMCMs, PLLs, ISERDESE2 and OSERDESE2 components would be placed either in the top level itself, or in the vendor specific files beneath it. As an example, the Xilinx Memory Interface Generator (MIG) code is vendor specific, so it would belong at this top level. Another example might be the vendor specific components necessary for a HDMI receiver. The actual line of demarkation is made by examining what Verilator can simulate. Any components that Verilator can components can be placed in main.v and below, anything else goes into the top level or the vendor specific component set that it references.
Critical components of your design that don’t meet the criteria to be placed into main.v may need external (C++ software) co-simulators. We’ll come back to the concept of simulation later.
Also within the top level file is an instantiation of the main module. The main module consists of one (or more) bus masters controlling a set of peripherals. It can also contain items that are neither bus masters nor peripherals, but these items won’t get attached to any bus.
AutoFPGA will build both the top level module, as well as the main module for you–connecting components together as the need to be for your design.
However, AutoFPGA has no knowledge of the vendor specific or vendor independent component files. All it knows how to do is to copy and paste items into these two files to create your logic. You tell it how to interface with the other components, but that’s it.
Well, this isn’t quite true. AutoFPGA supports some Makefile based tags which can be used to create a make include file. You can use these tags to place a list of component items into a list of Verilog files included in your project. Some synthesizers, such as yosys need this information in order to build the project. Other synthesizers, isuch as Verilator, just need to be told where to look. We’ll come back to this topic later.
What I”m trying to say is that after AutoFPGA has created any top level file or main module, you’ll still need to synthesize your design from these module files. AutoFPGA doesn’t create make files, and it doesn’t build projects. These capabilities need to be part of the project that uses AutoFPGA.
Internal RTL File Structure
AutoFPGA is primarily a copy and paste utility, with some additional capabilities for composing and connecting bus structures and interrupts. What this means when it comes to the two RTL files that it generates is that AutoFPGA will primarily copy snippets of Verilog code, provided by your configuration file, into their proper places within these two files.
The majority of this work is done within main.v so let’s look at that first.
Fig 2 shows the structure of the main module.
The main module,
as shown in Fig 2, has several parts to it. It starts like all
AutoFPGA
generated file with a legal notice, copied from the file whose name is
associated with the @LEGAL
key. After that, there’s a section where
components can define ACCESS
keys. These are pre-processor macros, that
will be used to handle component dependencies if necessary (@DEPENDS
key).
Then comes a series of paste’d components from any @MAIN.PORTLIST
keys,
then any parameter definitions captured in @MAIN.PARAM
keys, and then any
declarations associated with @MAIN.IODECL
keys. The file continues,
defining any interrupt wires, wires defined within @MAIN.DEFNS
tags,
interrupt vectors, and bus interaction wire components.
The big point to know here is that these keys are pasted into this file in
the order shown. I’ve written the keys in all capitols in the positions
where their values will be pasted in within Fig 2, so you can see what goes
where.
The primary module for the component, where the actual bus interaction and
any I/O wire adjustment takes place, is placed within the value of the
@MAIN.INSERT
tag for the component. We’ll discuss many of the other details
as we go along.
The top-level module has a similar, although simpler, structure as shown in Fig 3.
Unlike the main module,
fewer KEY
s impact the
top-level.
There’s the list of I/O ports for the module, their declarations, and any
other definitions you might wish to have. After that, there’s the KEY
defining wires that need to be passed to the
main module,
@TOP.MAIN
, and any RTL code that needs to be
inserted into the
top-level.
module via the
@TOP.INSERT
key.
Not all components will need these top-level keys. If no top-level keys are given, then any keys describing ports for the main.v, file will automatically be assumed to be toplevel, ports as well, so they need not be defined twice if they are the same between both levels.
So, let’s take a look at what it takes to put a configuration file together that will specify how to impact the top level, or main module. We’ll start with some simple examples, and then move to some more complex examples.
Remember, as you go through these, that AutoFPGA is primarily a copy/paste utility, with some bus aggregation facilities added into it.
Reading a simple ad-hoc value
For our first example, let’s read a constant value from our bus. The value
itself is arbitrary for this example, so we’ll just set it to
32'h20170926
. You can see the full configuration file for this example
here.
The first step is to declare this item as a peripheral–as its own entity, and
to give it a name that will distinguish it from other peripherals within our
design. This is done via the @PREFIX
key. Let’s give this fixed data
component the name fixdata
, since that’s what we’ll be reading from it: fixed
data.
The @PREFIX
key is required for all components.
It defines the beginning of the component, and ends any component definitions
prior to it. (Yes, two components can be placed into a single configuration
file. A good example of this would be the flash
component
that has not only read-only flash memory, but also a set of program control
registers to control the erase circuitry, and any write-enable.)
We may also want an all-caps name for some of the contexts this peripheral
might be in. This isn’t required, like the @PREFIX
key is, but it makes
a nice illustration. For this, we’ll create the key DEVID
, and give it a
value of FIXEDATA
.
This is a classic example of a key that AutoFPGA knows nothing about. It will have only the meaning we give it below.
If you want this peripheral to have a place on the bus, then AutoFPGA needs to know how many addresses to assign to it. Let’s take just one bus address (four bytes for a 32-bit data bus–the default), since we’re only going to be returning a single, pre-determined, value.
Most of the example’s we’ll deal with today have only one address.
This particular peripheral is going to be a slave peripheral to the
main wishbone bus, wb
within our design.
Once a component is declared to be a bus slave, a series of wires will be defined for it within main.v as referenced in Fig 2 above. For our component, these wires are given by,
although I’ll tend to reference these as @$(PREFIX)_data
, @$(PREFIX)_ack
,
etc., since these descriptions are more generic and can be used regardless
of what the peripherals name, given by the @PREFIX
key, actually is. The
first three of these are the slave return wires required by the
wishbone bus. The third wire,
@$(PREFIX)_sel
will be true any time the address on the bus references this
component.
Because our value is already fixed, it won’t take any clocks to calculate
it–it’s already known. Therefore this slave is of type SINGLE
, since it
can return a result in a SINGLE
clock cycle. SINGLE
peripherals don’t
need to define their wishbone acknowledgement wires, @$(PREFIX)_ack
, or
their stall lines, @$(PREFIX)_stall
, since they never stall and the ack
is true the same clock the peripheral is accessed.
Other peripheral types are DOUBLE
, OTHER
, and MEMORY
. DOUBLE
peripherals require a single clock to calculate their value, yet never
stall. OTHER
and MEMORY
peripherals are more generic wishbone
peripherals–but we’ll discuss these more later.
It’s now time to return our value to the bus.
Normally a wishbone slave also
needs to assign values to a @$(PREFIX)_ack
and @$(PREFIX)_stall
line.
However, these are trivially defined for a SINGLE
peripheral type, so we
ignore them here. (The AutoFPGA generated
main.v
file ignores these lines for SINGLE
type peripherals as well.)
The last step in our peripheral definition is to give a name for this
peripheral to be used when accessing it via the debugging interface. This is
where we’ll use the @DEVID
key we defined earlier. The first of the lines
below just specifies that this peripheral has only one named register. The
second line says that, at an offset of zero words from the beginning of this
peripheral’s address is a register named R_@$(DEVID)
with a user name of
@$(DEVID)
. These will be turned into R_FIXEDATA
and FIXEDATA
respectively.
If you look in
regdefs.h,
or regdefs.cpp,
for R_FIXEDATA
, you’ll see the effects of these two lines.
While we haven’t discussed it, these two files,
regdefs.h,
and regdefs.cpp,
are part of the host-based software interface library specification shown in
Fig 4. They are marked with red stars, since they are created by
AutoFPGA, vice the design.h
file
created by the
RTL Makefile.
Once we include this fixdata.txt configuration file in our design, and build it, we can then run wbregs
to read from the peripheral–either in simulation or on our actual hardware.
Reading a Fixed Version number
What if we want our number to be defined by an include file?
Many of my designs contain an include file, builddate.v
, that is created
by a simple perl
script.
This include
file
defines a value DATESTAMP
containing the date when
mkdatev.pl
was last run. I use this as a version number within my design–primarily
to make certain I’m not accidentally running an older design when I think
I’ve built and loaded a newer design.
Creating a register containing this version
information
is almost identical to the last exercise. First, we declare our bus interface
as before, although this time with a different @PREFIX
and @DEVID
.
At this point, though, we want to instruct our
main module
to include the
Perl-script generated
build-date version
file.
This is done through the @MAIN.DEFNS
key, whose value then gets pasted at
the top of the
main module.
Remember, AutoFPGA is primarily a copy and paste utility. You can place any Verilog you want into your main module file at this same location using this tag, although not all things you could paste would make sense at the top of a module file and before the module declaration.
Our bus interaction code within the
main module,
given by the @MAIN.INSERT
key, is going to change only slightly. This
time, instead of returning 32'h20170926
, we’ll return our DATESTAMP
variable.
The rest is just like the last example.
You can see the full configuration file for reading this version information here.
One thing to note is that
AutoFPGA
only creates some files. It doesn’t create an entire project. Hence, the
creation of the builddate.v
file is done by the main
Makefile
calling the
mkdatev.pl
program–both of which are outside of the configuration files that
AutoFPGA
looks at and
creates.
Read access to an internal (changing) register
Let’s create another single register within our design using AutoFPGA. Suppose we wanted to know how many clock ticks had taken place since we first initialized our design, and whether the count had (eventually) rolled over. To get this value, we’d need to initialize a register and then count clocks with it.
The pwrcount.txt configuration file describes just how to build one of such a register. We’ll walk through this file below.
Since this is a single value, again, the bus access declaration is the same as
before but with a new name, pwrcount
.
Note that I didn’t use the DEVID
this time.
AutoFPGA
doesn’t know that key, and so it won’t miss it when it isn’t there. It’s just
an example of a key you can use if it helps you, or ignore if not.
At this point, I could just copy and paste a module reference into my main module,
assuming I had a module named powercounter
.
We’ll use that approach with a later example in this post for the
special purpose I/O peripheral,
spio.v,
Instead of doing things that way, though, let’s define a register,
r_pwrcount_data
. This register declaration will get placed at the top of the
main module,
before any logic, so that it can be referenced by any logic below. Before
doing so, though, we’ll replace pwrcount
within that name by our PREFIX
key, so as to continue the perception of variables with a local scope.
Now we can create the logic that defines this value.
Note that when we we are done, we set @$(PREFIX)_data
using an assign
statement rather than using r_@$(PREFIX)_data
. This is simply because
AutoFPGA
defines the bus variables for you as though you were going to create and
return them via a module–as wires, not registers.
Getting the address of the last bus error
In a similar fashion, we can place just about any logic we want within the
@MAIN.INSERT
tag. Remember,
AutoFPGA
is primarily a copy and paste utility. All you need to do is to tell it how
to wire your code
up
to the rest of the
main module.
This works based upon the fact that there are several wires defined for each
bus. These wires are all prefixed by the name of the bus and an underscore.
Hence, for the wb
bus, these wires will all have a wb_
prefix.
Since this is a
wishbone B4 pipelined bus, all of these
bus master wires will be defined as:
where AW
is the address width of the bus, and DW
is the data width.
AutoFPGA
needs to be able to support multiple busses, however, of multiple types.
For this reason, the @SLAVE.BUS
key is expanded into a series of keys:
This allows us to reference the bus clock using @$(SLAVE.BUS.CLOCK.WIRE)
,
or even the wb
prefix of the bus as @$(SLAVE.BUS.NAME)
. This means that
the address width of this slave is given by @$(SLAVE.BUS.AWID)
and the
bus data width is given by @$(SLAVE.BUS.WIDTH)
.
If you find these long key names tedious, you can rename them within your
component. For example, we could have created a key @CLK
and assigned
it to hold the value of @$(SLAVE.BUS.CLOCK.WIRE)
, another key @AW
and
assigned it to hold the value of @$(SLAVE.BUS.AWID)
, and other keys @BS
and @DW
as in,
Had we done so, our @MAIN.DEFNS
and @MAIN.INSERT
tag might have been
simplified to
What this particular configuration file does different from our previous
examples is to reference information about the bus using the keys defined
for any bus. This allows the bus error address register, defined above, to
only have as many bits defined as necessary
(one for each address line, or @$(AW)
). Not only will this register size
be adjusted if the width of the address bus is adjusted, but it will also
be adjusted if the width of the data bus is adjusted–as illustrated by the
@$(PREFIX)_data
’s dependence upon both @$(DW)
and @$(AW)
. Had we worked
a little harder, we might’ve also set up the trailing two zero bits to
expand as necessary with any changing data width.
Write support for the same value
Now that you know how to read a value from the bus, what about writing to registers on the bus?
The raw register configuration file shows how this might be done. This example peripheral does nothing but set a value you give to it upon any write, and then allow you to read that value back later.
In many ways, this peripheral is almost identical to the power counter component we discussed earlier. The difference is that this register can be set via a bus write. For this reason, the boiler plate setup is almost identical.
The difference is found within the @MAIN.INSERT
tag describing how this
peripheral deals with the bus. In this case, for any bus access, wb_stb
,
that is also a write, wb_we
, and that references this peripheral,
@$(PREFIX)_sel
, we update our register. (Remember how a generic wishbone
slave interacts with the bus?)
We could have made this module more generic by replacing the
wb
prefixes with @$(SLAVE.BUS.NAME)
. Had we done that, we would have then
been able to use this across busses with different names. For now, because
of how we’ve defined this, this component will always only ever read
it’s values from the bus with a wb_
prefix.
Reading values from external ports
Let’s look at one more example along this same theme. Suppose we wanted to add to our design a register allowing us to:
-
Read from the buttons in our design
-
Read from any switches on our board
-
Set any output LEDs
Unlike our previous examples, this example will require access to I/O ports within our design, so we’ll have to deal with this when we get there.
We’ll start with the keys we’ve already defined above.
Now let’s walk through the new keys.
The first of these new keys is an @ACCESS
key. It’s an optional key, as you
can tell from the fact none of our other components above had this key.
This @ACCESS
key, if present, will create a define
line at the beginning
of our
main.v
file.
The RTL Makefile then includes instructions to copy this line from the main.v file into a design.h file, allowing any associated C++ files to know whether or not this component is a part of our design.
The next new key is the @MAIN.PORTLIST
key. This key specifies values to be
placed at the top of
main.v
within the portlist for the
main module.
This key is unique in that the items within it are separated by commas, but that the last item has no comma following it. This allows AutoFPGA to put a comma between items, or not, as is necessary to compose multiple component I/Os into a design.
Immediately following the main module declaration is an opportunity to place parameters. Let’s identify that this particular design has five buttons, 8 LEDs, and 8 switches.
Following any @MAIN.PARAM
values, you can declare your I/O values.
As with so many other keys, these values will simply be pasted into the main.v file following any key-value substitutions.
In a like manner to the way we’ve defined variables before, we’ll define a five valued logic vector to hold the inputs from each of our button values.
This will get placed with all of the wire and register declarations at the top of the main.v file.
The next required piece is the @MAIN.INSERT
key, telling
AutoFPGA
how we are going to interact with the bus. In this case, we’ll compose
our five buttons into a quick vector, and then reference a special purpose
I/O module I call
spio.v.
This module handles bus writes and bus reads, and is a generic
wishbone
peripheral.
Since this is a generic peripheral, it needs to return
@$(PREFIX)_ack
and @$(PREFIX)_stall
values–even though we’ll ignore
them since this is peripheral has a SINGLE
bus interaction type.
The new key that we haven’t discussed yet is the @MAIN.ALT
key. This
key defines the logic that will be used in the case the @ACCESS
component
defining SPIO_ACCESS
is not defined. Items within this keyword
are used for defining values external ports, such as o_led
, should be
given should the primary logic for this component not be used.
This component contains one last tag, @INT.SPIO.WIRE
.
This tag identifies that spio_int
is a single wire, and that it may be
assigned to an interrupt vector as the interrupt generated by this peripheral.
Feel free to take a look at the AutoFPGA generated main.v file and see how we did!
More to come!
The goal of AutoFPGA was to be able to have a simple command line interface–simply run AutoFPGA with a list of peripherals, and get output files. You might wish to test this by removing any of the above files from the AutoFPGA command line to see how well we did, or even add some configuration files of your own.
If you choose to try this, you’ll notice that the AutoFPGA demo project contains more configuration files than the ones we’ve looked at today. These are the block RAM configuration file, the clock file that defines the clock and the reset wires, the debugging bus configuration file, and a global configuration file. Of these, only the block RAM configuration file may be easily removed. The debugging bus configuration file defines our bus master, and a bus doesn’t work very well without a master, so it would make more sense to replace that one than to remove it if you wish to change it. The clock file and global configuration file are not as easily replaced.
So, let’s come back and discuss the block RAM configuration file at a later time. Specifically, you’ll love how easy it is to change the size of the block RAM, and how well that size information is propagated throughout a AutoFPGA-based design.
We’ll also need to come back later to discuss how a wishbone bus master can be connected using AutoFPGA.
Until that time, our demonstration project plus this article should be enough to build and control most beginning FPGA tasks. Even better, and unlike most proprietary solutions, all of the code AutoFPGA builds is availble for your inspection and learning pleasure.
He answered and said, Whether he be a sinner or no, I know not: one thing I know, that, whereas I was blind, now I see