basic framework
This commit is contained in:
2
MANIFEST.in
Normal file
2
MANIFEST.in
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#recursive-include peakrdl/regblock/XXX *
|
||||||
|
recursive-exclude test *
|
||||||
@@ -1,2 +1,9 @@
|
|||||||
|
[](https://pypi.org/project/peakrdl-regblock)
|
||||||
|
|
||||||
# PeakRDL-regblock
|
# PeakRDL-regblock
|
||||||
Generate SystemVerilog RTL that implements a register block from compiled SystemRDL input.
|
Generate SystemVerilog RTL that implements a register block from compiled SystemRDL input.
|
||||||
|
|
||||||
|
## Installing
|
||||||
|
Install from [PyPi](https://pypi.org/project/peakrdl-regblock) using pip:
|
||||||
|
|
||||||
|
python3 -m pip install peakrdl-regblock
|
||||||
|
|||||||
29
doc/logbooks/000-Main-Logbook
Normal file
29
doc/logbooks/000-Main-Logbook
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
================================================================================
|
||||||
|
Overarching philosophy
|
||||||
|
================================================================================
|
||||||
|
Encourage users to be able to tweak the design
|
||||||
|
Templating:
|
||||||
|
Design templates to be small bite-size snippets, each with clear intent
|
||||||
|
Templates shall be extendable
|
||||||
|
Templates shall be easy/intuitive
|
||||||
|
Output:
|
||||||
|
Output shall be beautiful. Consistent and clean formatting/whitespace builds trust
|
||||||
|
Output has comments. Users will be looking at it.
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
On templating...
|
||||||
|
================================================================================
|
||||||
|
Everything should be written out to a single file
|
||||||
|
The only exception is if a struct port interface is used, then the appropriate
|
||||||
|
struct package is also written out (or lumped into the same file?)
|
||||||
|
|
||||||
|
Each layer should actually be its own separate template.
|
||||||
|
|
||||||
|
Use helper functions to abstract away details about identifier implementation.
|
||||||
|
Similarly, some fields will end up with additional port-level signals inferred.
|
||||||
|
For example, if the user set the field's "anded=true" property
|
||||||
|
the template would do something like:
|
||||||
|
{{output_signal(field, "anded")}} = &{{field_value(field)}};
|
||||||
|
|
||||||
|
Basically, i'd define a ton of helper functions that return the signal identifier.
|
||||||
10
doc/logbooks/Alpha-Beta Versioning
Normal file
10
doc/logbooks/Alpha-Beta Versioning
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Holy smokes this is complicated
|
||||||
|
|
||||||
|
Keep this exporter in Alpha/Beta for a while
|
||||||
|
Add some text in the readme or somewhere:
|
||||||
|
- No guarantees of correctness! This is always true with open source software,
|
||||||
|
but even more here!
|
||||||
|
Be sure to do your own validation before using this in production.
|
||||||
|
- Alpha means the implementation may change drastically!
|
||||||
|
Unlike official sem-ver, I am not making any guarantees on compatibility
|
||||||
|
- I need your help! Validating, finding edge cases, etc...
|
||||||
18
doc/logbooks/Interrupts
Normal file
18
doc/logbooks/Interrupts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
Interrupts seem to be pretty well-described.
|
||||||
|
Basically...
|
||||||
|
|
||||||
|
- If a register contains one or more fields that use the intr property,
|
||||||
|
then it is implied to be an interrupt register
|
||||||
|
--> Add RegNode.has_intr and RegNode.has_halt properties?
|
||||||
|
- This register implies that there is an output irq signal that is propagated to the top, and it is the OR of all interrupt field bits
|
||||||
|
- BUT in the multilevel interrupt example, perhaps this output gets suppressed?
|
||||||
|
Suppress the output signal if Reg->intr gets referenced, since this means
|
||||||
|
the user is doing a multi-level interrupt.
|
||||||
|
This means that the register's interrupt signal is "consumed" by a second-level interrupt register
|
||||||
|
|
||||||
|
- WTF about the "halt" concept?
|
||||||
|
I assume this does NOT auto-imply an output?
|
||||||
|
Mayby only imply a default halt output if:
|
||||||
|
- an interrupt register has fields that use haltenable/haltmask
|
||||||
|
- AND the interrupt register's reg->halt has not been referenced
|
||||||
23
doc/logbooks/Program Flow
Normal file
23
doc/logbooks/Program Flow
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
1. Scan design. Collect information
|
||||||
|
- Check for unsupported constructs. Throw errors as appropriate
|
||||||
|
- Uniform regwidth, accesswidth, etc.
|
||||||
|
|
||||||
|
- Collect reset signals
|
||||||
|
cpuif_reset, field_reset
|
||||||
|
explicitly assigned to field->resetsignal
|
||||||
|
|
||||||
|
- Collect any other misc user signals that are referenced in the design
|
||||||
|
|
||||||
|
- Top-level interrupts
|
||||||
|
Collect X & Y:
|
||||||
|
X = set of all registers that have an interrupt field
|
||||||
|
Y = set of all interrupt registers that are referenced by a field
|
||||||
|
Top level interrupt registers are the set in X, but not in Y
|
||||||
|
(and probably other caveats. See notes)
|
||||||
|
|
||||||
|
2. Create intermediate template objects
|
||||||
|
|
||||||
|
3. Render top-level IO struct package (if applicable)
|
||||||
|
|
||||||
|
4. Render top-level module template
|
||||||
11
doc/logbooks/Resets
Normal file
11
doc/logbooks/Resets
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
================================================================================
|
||||||
|
Resets
|
||||||
|
================================================================================
|
||||||
|
use whatever is defined in RDL based on cpuif_reset and field_reset signals
|
||||||
|
Otherwise, provide configuration that defines what the default is:
|
||||||
|
a single reset that is active high/low, or sync/async
|
||||||
|
|
||||||
|
If cpuif_reset is specified, what do fields use?
|
||||||
|
I assume they still use the default reset separately?
|
||||||
|
YES. Agnisys appears to be wrong.
|
||||||
|
cpuif_reset has no influence on the fields' reset according to the spec
|
||||||
19
doc/logbooks/Signal Dereferencer
Normal file
19
doc/logbooks/Signal Dereferencer
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
I need some sort of signal "dereferencer" that can be easily used to translate references
|
||||||
|
to stuff via a normalized interface.
|
||||||
|
|
||||||
|
For example, if RDL defines:
|
||||||
|
my_field->next = my_other_field
|
||||||
|
Then in Python (or a template) I could do:
|
||||||
|
x = my_field.get_property("next")
|
||||||
|
y = dereferencer.get(x)
|
||||||
|
and trust that I'll get a value/identifier/whatever that accurately represents
|
||||||
|
the value being referenced
|
||||||
|
|
||||||
|
Values:
|
||||||
|
If X is a field reference:
|
||||||
|
... that implements storage, return its DFF value reference
|
||||||
|
... no storage, but has a hw input, grab from the hwif input
|
||||||
|
... no storage, and no hw input, return its constant reset value?
|
||||||
|
If X is a property reference... do whats right...
|
||||||
|
my_field->anded === (&path.to.my_field)
|
||||||
|
if X is a static value, return the literal
|
||||||
45
doc/logbooks/Some Classes
Normal file
45
doc/logbooks/Some Classes
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
|
||||||
|
================================================================================
|
||||||
|
Signal wrapper classes
|
||||||
|
================================================================================
|
||||||
|
Define a signal wrapper class that is easier to use in templates.
|
||||||
|
|
||||||
|
Provides the following properties:
|
||||||
|
.is_async
|
||||||
|
.is_activehigh
|
||||||
|
.identifier
|
||||||
|
Returns the Verilog identifier string for this signal
|
||||||
|
.activehigh_identifier
|
||||||
|
Normalizes identifier to active-high logic
|
||||||
|
same as .identifier, but prepends '~' if is_activehigh = False
|
||||||
|
.width
|
||||||
|
|
||||||
|
Default reset class instance:
|
||||||
|
Extends the base class
|
||||||
|
Hardcodes as follows:
|
||||||
|
.is_async = True
|
||||||
|
.is_activehigh = True
|
||||||
|
.identifier = "rst"
|
||||||
|
.width = 1
|
||||||
|
|
||||||
|
Wrapper classes
|
||||||
|
Wrap around a systemrdl.SignalNode
|
||||||
|
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
CPU Interface Class
|
||||||
|
================================================================================
|
||||||
|
Entry point class for a given CPU interface type (APB, AXI, etc..)
|
||||||
|
|
||||||
|
Does the following:
|
||||||
|
- Provide linkage to the logic implementation Jinja template
|
||||||
|
- Interface signal identifier properties
|
||||||
|
Aliases for signal identifiers to allow flat or sv-interface style
|
||||||
|
eg:
|
||||||
|
self.psel --> "s_apb_psel" or "s_apb.psel"
|
||||||
|
if sv interface, use the interface name class prpoerty
|
||||||
|
- Port declaration text property
|
||||||
|
declare as sv interface, or flat port list
|
||||||
|
If flattened, should use signal identifier properties
|
||||||
|
If sv interface, I should breakout the interface & modport name as
|
||||||
|
class properties for easy user-override
|
||||||
60
doc/logbooks/Validation Needed
Normal file
60
doc/logbooks/Validation Needed
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
|
||||||
|
================================================================================
|
||||||
|
Things that need validation by the compiler
|
||||||
|
================================================================================
|
||||||
|
Many of these are probably already covered, but being paranoid.
|
||||||
|
Make a list of things as I think of them.
|
||||||
|
|
||||||
|
Mark these as follows:
|
||||||
|
X = Yes, confirmed that the compiler covers this
|
||||||
|
! = No! Confirmed that the compiler does not check this
|
||||||
|
(blank) = TBD
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
X resetsignal width
|
||||||
|
reset signals shall have width of 1
|
||||||
|
|
||||||
|
X Field has no knowable value
|
||||||
|
- does not implement storage
|
||||||
|
- hw is not writable
|
||||||
|
- sw is readable
|
||||||
|
- No reset value specified
|
||||||
|
|
||||||
|
--> emit a warning?
|
||||||
|
|
||||||
|
! multiple field_reset in the same hierarchy
|
||||||
|
there can only be one signal declared with field_reset
|
||||||
|
in a given hierarchy
|
||||||
|
|
||||||
|
! multiple cpuif_reset in the same hierarchy
|
||||||
|
there can only be one signal declared with cpuif_reset
|
||||||
|
in a given hierarchy
|
||||||
|
|
||||||
|
! incrwidth/incrvalue & decrvalue/decrwidth
|
||||||
|
these pairs are mutually exclusive.
|
||||||
|
Make sure they are not both set after elaboration
|
||||||
|
Compiler checks for mutex within the same scope, but
|
||||||
|
i dont think I check for mutexes post-elaborate
|
||||||
|
|
||||||
|
... or, make these properties clear each-other on assignment
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Things that need validation by this exporter
|
||||||
|
================================================================================
|
||||||
|
List of stuff in case I forget.
|
||||||
|
|
||||||
|
! = No! exporter does not enforce this yet
|
||||||
|
x = Yes! I already implemented this.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
! "bridge" addrmap not supported
|
||||||
|
export shall refuse to process an addrmap marked as a "bridge"
|
||||||
|
Only need to check top-level. Compiler will enforce that child nodes arent bridges
|
||||||
|
|
||||||
|
cpuif_resets
|
||||||
|
! Warn/error on any signal with cpuif_reset set, that is not in the top-level
|
||||||
|
addrmap. At the very least, warn that it will be ignored
|
||||||
|
|
||||||
|
! multiple cpuif_reset
|
||||||
|
there can be only one cpuif reset
|
||||||
51
doc/logbooks/template-layers/1-port-declaration
Normal file
51
doc/logbooks/template-layers/1-port-declaration
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Port Declaration
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Generates the port declaration of the module:
|
||||||
|
- Parameters
|
||||||
|
- rd/wr error response/data behavior
|
||||||
|
Do missed accesses cause a SLVERR?
|
||||||
|
Do reads respond with a magic value?
|
||||||
|
- Pipeline enables
|
||||||
|
Enable reg stages in various places
|
||||||
|
|
||||||
|
- RDL-derived Parameters:
|
||||||
|
Someday in the future if i ever get around to this: https://github.com/SystemRDL/systemrdl-compiler/issues/58
|
||||||
|
|
||||||
|
- Clock/Reset
|
||||||
|
Single clk
|
||||||
|
One or more resets
|
||||||
|
|
||||||
|
- CPU Bus Interface
|
||||||
|
Given the bus interface object, emits the IO
|
||||||
|
This can be flattened ports, or a SV Interface
|
||||||
|
Regardless, it shall be malleable so that the user can use their favorite
|
||||||
|
declaration style
|
||||||
|
|
||||||
|
- Hardware interface
|
||||||
|
Two options:
|
||||||
|
- 2-port struct interface
|
||||||
|
Everything is rolled into two unpacked structs - inputs and outputs
|
||||||
|
- Flattened --> NOT DOING
|
||||||
|
Flatten/Unroll everything
|
||||||
|
No. not doing. I hate this and dont want to waste time implementing this.
|
||||||
|
This will NEVER be able to support parameterized regmaps, and just
|
||||||
|
creates a ton of corner cases i dont care to deal with.
|
||||||
|
|
||||||
|
Other IO Signals I need to be aware of:
|
||||||
|
any signals declared, and used in any references:
|
||||||
|
field.resetsignal
|
||||||
|
field.next
|
||||||
|
... etc ...
|
||||||
|
any signals declared and marked as cpuif_reset, or field_reset
|
||||||
|
These override the default rst
|
||||||
|
If both are defined, be sure to not emit the default
|
||||||
|
Pretty straightforward (see 17.1)
|
||||||
|
Also have some notes on this in my general Logbook
|
||||||
|
Will have to make a call on how these propagate if multiple defined
|
||||||
|
in different hierarchies
|
||||||
|
interrupt/halt outputs
|
||||||
|
See "Interrupts" logbook for explanation
|
||||||
|
addrmap.errextbus, regfile.errextbus, reg.errextbus
|
||||||
|
???
|
||||||
|
Apparently these are inputs
|
||||||
77
doc/logbooks/template-layers/1.1.hardware-interface
Normal file
77
doc/logbooks/template-layers/1.1.hardware-interface
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
================================================================================
|
||||||
|
Summary
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
RTL interface that provides access to per-field context signals
|
||||||
|
|
||||||
|
Regarding signals:
|
||||||
|
I think RDL-declared signals should actually be part of the hwif input
|
||||||
|
structure.
|
||||||
|
Exceptions:
|
||||||
|
- if the signal instance is at the top-level, it will get promoted to the
|
||||||
|
top level port list for convenience, and therefore omitted from the struct
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Naming Scheme
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
hwif_out
|
||||||
|
.my_regblock
|
||||||
|
.my_reg[X][Y]
|
||||||
|
.my_field
|
||||||
|
.value
|
||||||
|
.anded
|
||||||
|
|
||||||
|
hwif_in
|
||||||
|
.my_regblock
|
||||||
|
.my_reg[X][Y]
|
||||||
|
.my_field
|
||||||
|
.value
|
||||||
|
.we
|
||||||
|
.my_signal
|
||||||
|
.my_fieldreset_signal
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Flattened mode? --> NO
|
||||||
|
================================================================================
|
||||||
|
If user wants a flattened list of ports,
|
||||||
|
still use the same hwif_in/out struct internally.
|
||||||
|
Rather than declaring hwif_in and hwif_out in the port list, declare it internally
|
||||||
|
|
||||||
|
Add a mapping layer in the body of the module that performs a ton of assign statements
|
||||||
|
to map flat signals <-> struct
|
||||||
|
|
||||||
|
Alternatively, don't do this at all.
|
||||||
|
If I want to add a flattened mode, generate a wrapper module instead.
|
||||||
|
|
||||||
|
Marking this as YAGNI for now.
|
||||||
|
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
IO Signals
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
field value
|
||||||
|
If hw readable
|
||||||
|
bitwise reductions
|
||||||
|
if anded, ored, xored == True, output a signal
|
||||||
|
swmod/swacc
|
||||||
|
event strobes
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
field value
|
||||||
|
If hw writable
|
||||||
|
we/wel
|
||||||
|
if either is boolean, and true
|
||||||
|
not part of external hwif if reference
|
||||||
|
mutually exclusive
|
||||||
|
hwclr/hwset
|
||||||
|
if either is boolean, and true
|
||||||
|
not part of external hwif if reference
|
||||||
|
incr/decr
|
||||||
|
if counter=true, generate BOTH
|
||||||
|
incrvalue/decrvalue
|
||||||
|
if either incrwidth/decrwidth are set
|
||||||
|
signals!
|
||||||
|
any signal instances instantiated in the scope
|
||||||
72
doc/logbooks/template-layers/2-CPUIF
Normal file
72
doc/logbooks/template-layers/2-CPUIF
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
--------------------------------------------------------------------------------
|
||||||
|
CPU Bus interface layer
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Provides an abstraction layer between the outside SoC's bus interface, and the
|
||||||
|
internal register block's implementation.
|
||||||
|
Converts a user-selectable bus protocol to generic register file signals.
|
||||||
|
|
||||||
|
Upstream Signals:
|
||||||
|
Signal names are defined in the bus interface class and shall be malleable
|
||||||
|
to the user.
|
||||||
|
User can choose a flat signal interface, or a SV interface.
|
||||||
|
SV interface shall be easy to tweak since various orgs will use different
|
||||||
|
naming conventions in their library of interface definitions
|
||||||
|
|
||||||
|
Downstream Signals:
|
||||||
|
- cpuif_req
|
||||||
|
- Single-cycle pulse
|
||||||
|
- Qualifies the following child signals:
|
||||||
|
- cpuif_req_is_wr
|
||||||
|
1 denotes this is a write transfer
|
||||||
|
- cpuif_addr
|
||||||
|
Byte address
|
||||||
|
- cpuif_wr_data
|
||||||
|
- cpuif_wr_bitstrb
|
||||||
|
per-bit strobes
|
||||||
|
some protocols may opt to tie this to all 1's
|
||||||
|
- cpuif_rd_ack
|
||||||
|
- Single-cycle pulse
|
||||||
|
- Qualifies the following child signals:
|
||||||
|
- cpuif_rd_data
|
||||||
|
- cpuif_rd_err
|
||||||
|
|
||||||
|
- cpuif_wr_ack
|
||||||
|
- Single-cycle pulse
|
||||||
|
- Qualifies the following child signals:
|
||||||
|
- cpuif_wr_err
|
||||||
|
|
||||||
|
|
||||||
|
Misc thoughts
|
||||||
|
- Internal cpuif_* signals use a strobe-based protocol:
|
||||||
|
- Unknown, but fixed latency
|
||||||
|
- Makes for easy pipelining if needed
|
||||||
|
- Decided to keep cpuif_req signals common for read write:
|
||||||
|
This will allow address decode logic to be shared for read/write
|
||||||
|
Downside is split protocols like axi-lite can't have totally separate rd/wr
|
||||||
|
access lanes, but who cares?
|
||||||
|
- separate response strobes
|
||||||
|
Not necessary to use, but this lets me independently pipeline read/write paths.
|
||||||
|
read path will need more time if readback mux is large
|
||||||
|
- On multiple outstanding transactions
|
||||||
|
Currently, cpuif doesnt really support this. Goal was to make it easily pipelineable
|
||||||
|
without having to backfeed stall logic.
|
||||||
|
Could still be possible to do a "fly-by" pipeline with a more intelligent cpuif layer
|
||||||
|
Not worrying about this now.
|
||||||
|
|
||||||
|
|
||||||
|
Implementation:
|
||||||
|
Implement this mainly as a Jinja template.
|
||||||
|
Upstream bus intf signals are fetched via busif class properties. Ex:
|
||||||
|
{{busif.signal('pready')}} <= '1;
|
||||||
|
This allows the actual SV or flattened signal to be emitted
|
||||||
|
|
||||||
|
What protocols do I care about?
|
||||||
|
- AXI4 Lite
|
||||||
|
- Ignore AxPROT?
|
||||||
|
- APB3
|
||||||
|
- APB4
|
||||||
|
- Ignore pprot?
|
||||||
|
- AHB?
|
||||||
|
- Wishbone
|
||||||
|
- Generic
|
||||||
|
breakout the above signals as-is (reassign with a prefix or something)
|
||||||
51
doc/logbooks/template-layers/3-address-decode
Normal file
51
doc/logbooks/template-layers/3-address-decode
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Address Decode layer
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
A bunch of combinational address decodes that generate individual register
|
||||||
|
req strobes
|
||||||
|
|
||||||
|
Possible decode logic styles:
|
||||||
|
- Big case statement
|
||||||
|
+ Probably more sim-efficient
|
||||||
|
- Hard to do loop parameterization
|
||||||
|
- More annoying to do multiple regs per address
|
||||||
|
- Big always_comb + One if/else chain
|
||||||
|
+ Easy to nest loops & parameterize if needed
|
||||||
|
- sim has a lot to evaluate each time
|
||||||
|
- More annoying to do multiple regs per address
|
||||||
|
- implies precedence? Synth tools should be smart enough?
|
||||||
|
- Big always_comb + inline conditionals <---- DO THIS
|
||||||
|
+ Easy to nest loops & parameterize if needed
|
||||||
|
- sim has a lot to evaluate each time
|
||||||
|
+ Multiple regs per address possible
|
||||||
|
+ implies address decode parallelism.
|
||||||
|
?? Should I try using generate loops + assigns?
|
||||||
|
This would be more explicit parallelism, however some tools may
|
||||||
|
get upset at multiple assignments to a common struct
|
||||||
|
|
||||||
|
Implementation:
|
||||||
|
Jinja is inappropriate here
|
||||||
|
Very logic-heavy. Jinja may end up being annoying
|
||||||
|
Also, not much need for customization here
|
||||||
|
This may even make sense as a visitor that dumps lines
|
||||||
|
- visit each reg
|
||||||
|
- upon entering an array, create for loops
|
||||||
|
- upon exiting an array, emit 'end'
|
||||||
|
Make the strobe struct declared locally
|
||||||
|
No need for it to leave the block
|
||||||
|
Error handling
|
||||||
|
If no strobe generated, respond w error?
|
||||||
|
This is actually pretty expensive to do for writes.
|
||||||
|
Hold off on this for now.
|
||||||
|
Reads get this effectively for free in the readback mux.
|
||||||
|
Implement write response strobes back upstream to cpuif
|
||||||
|
Eventually allow for optional register stage for strobe struct
|
||||||
|
Will need to also pipeline the other cpuif signals
|
||||||
|
ok to discard the cpuif_addr. no longer needed
|
||||||
|
|
||||||
|
|
||||||
|
Downstream Signals:
|
||||||
|
- access strobes
|
||||||
|
Encase these into a struct datatype
|
||||||
|
- is_write + wr_data/wr_bitstrobe
|
||||||
35
doc/logbooks/template-layers/4-fields
Normal file
35
doc/logbooks/template-layers/4-fields
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Field storage / next value layer
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Where all the magic happens!!
|
||||||
|
|
||||||
|
Any field that implements storage is defined here.
|
||||||
|
Bigass struct that only contains storage elements
|
||||||
|
|
||||||
|
Each field consists of:
|
||||||
|
- an always_ff block
|
||||||
|
- series of if/else statements that assign the next value in the storage element
|
||||||
|
Think of this as a flat list of "next state" conditons, ranked by their precedence as follows:
|
||||||
|
- reset
|
||||||
|
- sw access (if sw precedence)
|
||||||
|
- onread/onwrite
|
||||||
|
- hw access
|
||||||
|
- Counter
|
||||||
|
- next
|
||||||
|
- etc
|
||||||
|
- sw access (if hw precedence)
|
||||||
|
- onread/onwrite
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
What about stuff like read-clear counters that cant lose a count?
|
||||||
|
In a traditional if/else chain, i need to be aware of the fact that its a counter
|
||||||
|
when handling the swaccess case
|
||||||
|
Is it possible to code this in a way where I can isolate the need to know every nuanced case here?
|
||||||
|
this may actually only apply to counters...
|
||||||
|
This is trivial in a 2-process implementation, but i'd rather avoid the overheads
|
||||||
|
|
||||||
|
|
||||||
|
Implementation
|
||||||
|
Makes sense to use a listener class
|
||||||
|
|
||||||
|
Be sure to skip alias registers
|
||||||
65
doc/logbooks/template-layers/5-readback-mux
Normal file
65
doc/logbooks/template-layers/5-readback-mux
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Readback mux layer
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Implementation:
|
||||||
|
- Big always_comb block
|
||||||
|
- Initialize default rd_data value
|
||||||
|
- Lotsa if statements that operate on reg strb to assign rd_data
|
||||||
|
- Merges all fields together into reg
|
||||||
|
- pulls value from storage element struct, or input struct
|
||||||
|
- Provision for optional flop stage?
|
||||||
|
|
||||||
|
Mux Strategy:
|
||||||
|
Flat case statement:
|
||||||
|
-- Cant parameterize
|
||||||
|
+ better performance?
|
||||||
|
|
||||||
|
Flatten array then mux:
|
||||||
|
- First, flatten ALL readback values into an array
|
||||||
|
Round up the size of the array to next ^2
|
||||||
|
needs to be fully addressable anyways!
|
||||||
|
This can be in a combinational block
|
||||||
|
Initialize the array to the default readback value
|
||||||
|
then, assign all register values. Use loops where necessary.
|
||||||
|
Append an extra 'is-valid' bit if I need to slverr on bad reads
|
||||||
|
- Next, use the read address as an index into this array
|
||||||
|
- If needed, I can do a staged decode!
|
||||||
|
Compute the most balanced fanin staging in Python. eg:
|
||||||
|
64 regs --mux--> 8x8 --mux--> 1
|
||||||
|
128 regs --mux--> 8x16 --mux--> 1
|
||||||
|
Favor smaller fanin first. Latter stage should have more fanin since routing congestion will be easier
|
||||||
|
256 regs --mux--> 16x16 --mux--> 1
|
||||||
|
- Potential sparseness of this makes me uncomfortable,
|
||||||
|
but its synthesis SEEMS like it would be really efficient!
|
||||||
|
- TODO: Rethink this
|
||||||
|
I feel like people will complain about this
|
||||||
|
It will likely also be pretty sim-inefficient?
|
||||||
|
Flat 1-hot array then OR reduce: <-- DO THIS
|
||||||
|
- Create a bus-wide flat array
|
||||||
|
eg: 32-bits x N readable registers
|
||||||
|
- Assign each element:
|
||||||
|
the readback value of each register
|
||||||
|
... masked by the register's access strobe
|
||||||
|
- I could also stuff an extra bit into the array that denotes the read is valid
|
||||||
|
A missed read will OR reduce down to a 0
|
||||||
|
- Finally, OR reduce all the elements in the array down to a flat 32-bit bus
|
||||||
|
- Retiming the large OR fanin can be done by chopping up the array into stages
|
||||||
|
for 2 stages, sqrt(N) gives each stage's fanin size. Round to favor
|
||||||
|
more fanin on 2nd stage
|
||||||
|
3 stages uses cube-root. etc...
|
||||||
|
- This has the benefit of re-using the address decode logic.
|
||||||
|
synth can choose to replicate logic if fanout is bad
|
||||||
|
|
||||||
|
|
||||||
|
WARNING:
|
||||||
|
Beware of read/write flop stage asymmetry & race conditions.
|
||||||
|
Eg. If a field is rclr, dont want to sample it after it gets read:
|
||||||
|
addr --> strb --> clear
|
||||||
|
addr --> loooong...retime --> sample rd value
|
||||||
|
Should guarantee that read-sampling happens at the same cycle as any read-modify
|
||||||
|
|
||||||
|
|
||||||
|
Forwards response strobe back up to cpu interface layer
|
||||||
|
|
||||||
|
Dont forget about alias registers here
|
||||||
9
doc/logbooks/template-layers/6-output-port-mapping
Normal file
9
doc/logbooks/template-layers/6-output-port-mapping
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Output Port mapping layer
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Assign to output struct port
|
||||||
|
|
||||||
|
Still TBD if this will actually be a distinct layer.
|
||||||
|
Cosmetically, this might be nicer to interleave with the field section above
|
||||||
|
Assign storage element & other derived values as requested by properties
|
||||||
223
hand-coded.sv
Normal file
223
hand-coded.sv
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
// Hand-coded demo. Not auto-generated
|
||||||
|
|
||||||
|
package top_pkg;
|
||||||
|
|
||||||
|
// top.whee[][].y
|
||||||
|
typedef struct {
|
||||||
|
logic value;
|
||||||
|
} top__wheexx__y__out_t;
|
||||||
|
|
||||||
|
// top.whee[][]
|
||||||
|
typedef struct {
|
||||||
|
top__wheexx__y__out_t y;
|
||||||
|
} top__wheexx__out_t;
|
||||||
|
|
||||||
|
// top.asdf[].aaa[].abc
|
||||||
|
typedef struct {
|
||||||
|
logic [14:0] value;
|
||||||
|
} top__asdfx__aaax__abc__out_t;
|
||||||
|
|
||||||
|
// top.asdf[].aaa[].def
|
||||||
|
typedef struct {
|
||||||
|
logic [3:0] value;
|
||||||
|
} top__asdfx__aaax__def__out_t;
|
||||||
|
|
||||||
|
// top.asdf[].aaa[]
|
||||||
|
typedef struct {
|
||||||
|
top__asdfx__aaax__abc__out_t abc;
|
||||||
|
top__asdfx__aaax__def__out_t def;
|
||||||
|
} top__asdfx__aaax__out_t;
|
||||||
|
|
||||||
|
// top.asdf[].bbb.abc
|
||||||
|
typedef struct {
|
||||||
|
logic value;
|
||||||
|
} top__asdfx__bbb__abc__out_t;
|
||||||
|
|
||||||
|
// top.asdf[].bbb.def
|
||||||
|
typedef struct {
|
||||||
|
logic value;
|
||||||
|
} top__asdfx__bbb__def__out_t;
|
||||||
|
|
||||||
|
// top.asdf[].bbb
|
||||||
|
typedef struct {
|
||||||
|
top__asdfx__bbb__abc__out_t abc;
|
||||||
|
top__asdfx__bbb__def__out_t def;
|
||||||
|
} top__asdfx__bbb__out_t;
|
||||||
|
|
||||||
|
// top.asdf[]
|
||||||
|
typedef struct {
|
||||||
|
top__asdfx__aaax__out_t aaa[4];
|
||||||
|
top__asdfx__bbb__out_t bbb;
|
||||||
|
} top__asdfx__out_t;
|
||||||
|
|
||||||
|
// top
|
||||||
|
typedef struct {
|
||||||
|
top__wheexx__out_t whee[2][8];
|
||||||
|
top__asdfx__out_t asdf[20];
|
||||||
|
} top__out_t;
|
||||||
|
|
||||||
|
endpackage
|
||||||
|
|
||||||
|
module top #(
|
||||||
|
// TODO: pipeline parameters
|
||||||
|
)(
|
||||||
|
input wire clk,
|
||||||
|
input wire rst,
|
||||||
|
|
||||||
|
|
||||||
|
apb4_intf.slave s_apb,
|
||||||
|
|
||||||
|
output top_pkg::top__out_t hwif_out
|
||||||
|
);
|
||||||
|
|
||||||
|
localparam ADDR_WIDTH = 32;
|
||||||
|
localparam DATA_WIDTH = 32;
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// CPU Bus interface logic
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
logic cpuif_req;
|
||||||
|
logic cpuif_req_is_wr;
|
||||||
|
logic [ADDR_WIDTH-1:0] cpuif_addr;
|
||||||
|
logic [DATA_WIDTH-1:0] cpuif_wr_data;
|
||||||
|
logic [DATA_WIDTH-1:0] cpuif_wr_bitstrb;
|
||||||
|
|
||||||
|
logic cpuif_rd_ack;
|
||||||
|
logic [DATA_WIDTH-1:0] cpuif_rd_data;
|
||||||
|
logic cpuif_rd_err;
|
||||||
|
|
||||||
|
logic cpuif_wr_ack;
|
||||||
|
logic cpuif_wr_err;
|
||||||
|
|
||||||
|
begin
|
||||||
|
// Request
|
||||||
|
logic is_active;
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
if(rst) begin
|
||||||
|
is_active <= '0;
|
||||||
|
cpuif_req <= '0;
|
||||||
|
cpuif_req_is_wr <= '0;
|
||||||
|
cpuif_addr <= '0;
|
||||||
|
cpuif_wr_data <= '0;
|
||||||
|
cpuif_wr_bitstrb <= '0;
|
||||||
|
end else begin
|
||||||
|
if(~is_active) begin
|
||||||
|
if(s_apb.psel) begin
|
||||||
|
is_active <= '1;
|
||||||
|
cpuif_req <= '1;
|
||||||
|
cpuif_req_is_wr <= s_apb.pwrite;
|
||||||
|
cpuif_addr <= s_apb.paddr[ADDR_WIDTH-1:0];
|
||||||
|
cpuif_wr_data <= s_apb.pwdata;
|
||||||
|
for(int i=0; i<DATA_WIDTH/8; i++) begin
|
||||||
|
cpuif_wr_bitstrb[i*8 +: 8] <= {8{s_apb.pstrb[i]}};
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end else begin
|
||||||
|
cpuif_req <= '0;
|
||||||
|
if(cpuif_rd_ack || cpuif_wr_ack) begin
|
||||||
|
is_active <= '0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// Response
|
||||||
|
assign s_apb.pready = cpuif_rd_ack | cpuif_wr_ack;
|
||||||
|
assign s_apb.prdata = cpuif_rd_data;
|
||||||
|
assign s_apb.pslverr = cpuif_rd_err | cpuif_wr_err;
|
||||||
|
end
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Address Decode
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
typedef struct {
|
||||||
|
logic whee[2][8];
|
||||||
|
struct {
|
||||||
|
logic aaa[4];
|
||||||
|
logic bbb;
|
||||||
|
} asdf[20];
|
||||||
|
} access_strb_t;
|
||||||
|
access_strb_t access_strb;
|
||||||
|
|
||||||
|
always_comb begin
|
||||||
|
for(int i0=0; i0<2; i0++) begin
|
||||||
|
for(int i1=0; i1<8; i1++) begin
|
||||||
|
access_strb.whee[i0][i1] = cpuif_req & (cpuif_addr == 'h0 + i0*'h20 + i1*'h4);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for(int i0=0; i0<20; i0++) begin
|
||||||
|
for(int i1=0; i1<4; i1++) begin
|
||||||
|
access_strb.asdf[i0].aaa[i1] = cpuif_req & (cpuif_addr == 'h40 + i0*'h14 + i1*'h4);
|
||||||
|
end
|
||||||
|
access_strb.asdf[i0].bbb = cpuif_req & (cpuif_addr == 'h50 + i0*'h14);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// Writes are always posted with no error response
|
||||||
|
assign cpuif_wr_ack = cpuif_req & cpuif_req_is_wr;
|
||||||
|
assign cpuif_wr_err = '0;
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Field logic
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
typedef struct {
|
||||||
|
struct {
|
||||||
|
logic y;
|
||||||
|
} whee[2][8];
|
||||||
|
struct {
|
||||||
|
struct {
|
||||||
|
logic [14:0] abc;
|
||||||
|
logic [3:0] def;
|
||||||
|
} aaa[4];
|
||||||
|
struct {
|
||||||
|
logic abc;
|
||||||
|
logic def;
|
||||||
|
} bbb;
|
||||||
|
} asdf[20];
|
||||||
|
} field_storage_t;
|
||||||
|
field_storage_t field_storage;
|
||||||
|
|
||||||
|
// TODO: Field next-state logic, and output port signal assignment (aka output mapping layer)
|
||||||
|
TODO:
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Readback mux
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
logic readback_err;
|
||||||
|
logic [DATA_WIDTH-1:0] readback_data;
|
||||||
|
|
||||||
|
always_comb begin
|
||||||
|
readback_err = '0;
|
||||||
|
readback_data = '0;
|
||||||
|
if(cpuif_req & ~cpuif_req_is_wr) begin
|
||||||
|
readback_err = '1;
|
||||||
|
for(int i0=0; i0<2; i0++) begin
|
||||||
|
for(int i1=0; i1<8; i1++) begin
|
||||||
|
if(cpuif_addr == 'h0 + i0*'h20 + i1*'h4) begin
|
||||||
|
readback_err = '0;
|
||||||
|
readback_data[0:0] = field_storage.whee[i0][i1].y;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for(int i0=0; i0<20; i0++) begin
|
||||||
|
for(int i1=0; i1<4; i1++) begin
|
||||||
|
if(cpuif_addr == 'h40 + i0*'h14 + i1*'h4) begin
|
||||||
|
readback_err = '0;
|
||||||
|
readback_data[16:2] = field_storage.asdf[i0].aaa[i1].abc;
|
||||||
|
readback_data[4:4] = field_storage.asdf[i0].aaa[i1].def;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if(cpuif_addr == 'h50 + i0*'h14) begin
|
||||||
|
readback_err = '0;
|
||||||
|
readback_data[0:0] = field_storage.asdf[i0].bbb.abc;
|
||||||
|
readback_data[1:1] = field_storage.asdf[i0].bbb.def;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assign cpuif_rd_ack = cpuif_req & ~cpuif_req_is_wr;
|
||||||
|
assign cpuif_rd_data = readback_data;
|
||||||
|
assign cpuif_rd_err = readback_err;
|
||||||
|
|
||||||
|
endmodule
|
||||||
1
peakrdl/regblock/__about__.py
Normal file
1
peakrdl/regblock/__about__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
__version__ = "0.1.0"
|
||||||
3
peakrdl/regblock/__init__.py
Normal file
3
peakrdl/regblock/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from .__about__ import __version__
|
||||||
|
|
||||||
|
from .exporter import RegblockExporter
|
||||||
123
peakrdl/regblock/addr_decode.py
Normal file
123
peakrdl/regblock/addr_decode.py
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import re
|
||||||
|
from typing import TYPE_CHECKING, List
|
||||||
|
|
||||||
|
from systemrdl.node import Node, AddressableNode, RegNode
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .exporter import RegblockExporter
|
||||||
|
|
||||||
|
class AddressDecode:
|
||||||
|
def __init__(self, exporter:'RegblockExporter', top_node:AddressableNode):
|
||||||
|
self.exporter = exporter
|
||||||
|
self.top_node = top_node
|
||||||
|
|
||||||
|
self._indent_level = 0
|
||||||
|
|
||||||
|
# List of address strides for each dimension
|
||||||
|
self._array_stride_stack = []
|
||||||
|
|
||||||
|
def get_strobe_struct(self) -> str:
|
||||||
|
lines = []
|
||||||
|
self._do_struct(lines, self.top_node, is_top=True)
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def get_implementation(self) -> str:
|
||||||
|
lines = []
|
||||||
|
self._do_address_decode_node(lines, self.top_node)
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Struct generation functions
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
@property
|
||||||
|
def _indent(self) -> str:
|
||||||
|
return " " * self._indent_level
|
||||||
|
|
||||||
|
def _get_node_array_suffix(self, node:AddressableNode) -> str:
|
||||||
|
if node.is_array:
|
||||||
|
return "".join([f'[{dim}]' for dim in node.array_dimensions])
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _do_struct(self, lines:List[str], node:AddressableNode, is_top:bool = False) -> None:
|
||||||
|
if is_top:
|
||||||
|
lines.append(f"{self._indent}typedef struct {{")
|
||||||
|
else:
|
||||||
|
lines.append(f"{self._indent}struct {{")
|
||||||
|
|
||||||
|
self._indent_level += 1
|
||||||
|
for child in node.children():
|
||||||
|
if isinstance(child, RegNode):
|
||||||
|
lines.append(f"{self._indent}logic {child.inst_name}{self._get_node_array_suffix(child)};")
|
||||||
|
elif isinstance(child, AddressableNode):
|
||||||
|
self._do_struct(lines, child)
|
||||||
|
self._indent_level -= 1
|
||||||
|
|
||||||
|
if is_top:
|
||||||
|
lines.append(f"{self._indent}}} access_strb_t;")
|
||||||
|
else:
|
||||||
|
lines.append(f"{self._indent}}} {node.inst_name}{self._get_node_array_suffix(node)};")
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Access strobe generation functions
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _push_array_dims(self, lines:List[str], node:AddressableNode):
|
||||||
|
if not node.is_array:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Collect strides for each array dimension
|
||||||
|
current_stride = node.array_stride
|
||||||
|
strides = []
|
||||||
|
for dim in reversed(node.array_dimensions):
|
||||||
|
strides.append(current_stride)
|
||||||
|
current_stride *= dim
|
||||||
|
strides.reverse()
|
||||||
|
|
||||||
|
for dim, stride in zip(node.array_dimensions, strides):
|
||||||
|
iterator = "i%d" % len(self._array_stride_stack)
|
||||||
|
self._array_stride_stack.append(stride)
|
||||||
|
lines.append(f"{self._indent}for(int {iterator}=0; {iterator}<{dim}; {iterator}++) begin")
|
||||||
|
self._indent_level += 1
|
||||||
|
|
||||||
|
def _pop_array_dims(self, lines:List[str], node:AddressableNode):
|
||||||
|
if not node.is_array:
|
||||||
|
return
|
||||||
|
|
||||||
|
for _ in node.array_dimensions:
|
||||||
|
self._array_stride_stack.pop()
|
||||||
|
self._indent_level -= 1
|
||||||
|
lines.append(f"{self._indent}end")
|
||||||
|
|
||||||
|
def _get_address_str(self, node:AddressableNode) -> str:
|
||||||
|
a = "'h%x" % (node.raw_absolute_address - self.top_node.raw_absolute_address)
|
||||||
|
for i, stride in enumerate(self._array_stride_stack):
|
||||||
|
a += f" + i{i}*'h{stride:x}"
|
||||||
|
return a
|
||||||
|
|
||||||
|
def _get_strobe_str(self, node:AddressableNode) -> str:
|
||||||
|
path = node.get_rel_path(self.top_node, array_suffix="[!]", empty_array_suffix="[!]")
|
||||||
|
|
||||||
|
class repl:
|
||||||
|
def __init__(self):
|
||||||
|
self.i = 0
|
||||||
|
def __call__(self, match):
|
||||||
|
s = f'i{self.i}'
|
||||||
|
self.i += 1
|
||||||
|
return s
|
||||||
|
|
||||||
|
path = re.sub(r'!', repl(), path)
|
||||||
|
strb = "access_strb." + path
|
||||||
|
return strb
|
||||||
|
|
||||||
|
def _do_address_decode_node(self, lines:List[str], node:AddressableNode) -> None:
|
||||||
|
for child in node.children():
|
||||||
|
if isinstance(child, RegNode):
|
||||||
|
self._push_array_dims(lines, child)
|
||||||
|
lines.append(f"{self._indent}{self._get_strobe_str(child)} = cpuif_req & (cpuif_addr == {self._get_address_str(child)});")
|
||||||
|
self._pop_array_dims(lines, child)
|
||||||
|
elif isinstance(child, AddressableNode):
|
||||||
|
self._push_array_dims(lines, child)
|
||||||
|
self._do_address_decode_node(lines, child)
|
||||||
|
self._pop_array_dims(lines, child)
|
||||||
1
peakrdl/regblock/cpuif/__init__.py
Normal file
1
peakrdl/regblock/cpuif/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .base import CpuifBase
|
||||||
33
peakrdl/regblock/cpuif/apb4/__init__.py
Normal file
33
peakrdl/regblock/cpuif/apb4/__init__.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
from ..base import CpuifBase
|
||||||
|
|
||||||
|
class APB4_Cpuif(CpuifBase):
|
||||||
|
template_path = "cpuif/apb4/apb4_tmpl.sv"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def port_declaration(self) -> str:
|
||||||
|
return "apb4_intf.slave s_apb"
|
||||||
|
|
||||||
|
def signal(self, name:str) -> str:
|
||||||
|
return "s_apb." + name
|
||||||
|
|
||||||
|
|
||||||
|
class APB4_Cpuif_flattened(APB4_Cpuif):
|
||||||
|
@property
|
||||||
|
def port_declaration(self) -> str:
|
||||||
|
# TODO: Reference data/addr width from verilog parameter perhaps?
|
||||||
|
lines = [
|
||||||
|
"input wire %s" % self.signal("psel"),
|
||||||
|
"input wire %s" % self.signal("penable"),
|
||||||
|
"input wire %s" % self.signal("pwrite"),
|
||||||
|
"input wire %s" % self.signal("pprot"),
|
||||||
|
"input wire [%d-1:0] %s" % (self.addr_width, self.signal("paddr")),
|
||||||
|
"input wire [%d-1:0] %s" % (self.data_width, self.signal("pwdata")),
|
||||||
|
"input wire [%d-1:0] %s" % (self.data_width / 8, self.signal("pstrb")),
|
||||||
|
"output logic %s" % self.signal("pready"),
|
||||||
|
"output logic [%d-1:0] %s" % (self.data_width, self.signal("prdata")),
|
||||||
|
"output logic %s" % self.signal("pslverr"),
|
||||||
|
]
|
||||||
|
return ",\n".join(lines)
|
||||||
|
|
||||||
|
def signal(self, name:str) -> str:
|
||||||
|
return "s_apb_" + name
|
||||||
40
peakrdl/regblock/cpuif/apb4/apb4_tmpl.sv
Normal file
40
peakrdl/regblock/cpuif/apb4/apb4_tmpl.sv
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{% extends "cpuif/base_tmpl.sv" %}
|
||||||
|
{%- import "utils_tmpl.sv" as utils with context %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
// Request
|
||||||
|
logic is_active;
|
||||||
|
{%- call utils.AlwaysFF(cpuif_reset) %}
|
||||||
|
if({{cpuif_reset.activehigh_identifier}}) begin
|
||||||
|
is_active <= '0;
|
||||||
|
cpuif_req <= '0;
|
||||||
|
cpuif_req_is_wr <= '0;
|
||||||
|
cpuif_addr <= '0;
|
||||||
|
cpuif_wr_data <= '0;
|
||||||
|
cpuif_wr_bitstrb <= '0;
|
||||||
|
end else begin
|
||||||
|
if(~is_active) begin
|
||||||
|
if({{cpuif.signal("psel")}}) begin
|
||||||
|
is_active <= '1;
|
||||||
|
cpuif_req <= '1;
|
||||||
|
cpuif_req_is_wr <= {{cpuif.signal("pwrite")}};
|
||||||
|
cpuif_addr <= {{cpuif.signal("paddr")}}[ADDR_WIDTH-1:0];
|
||||||
|
cpuif_wr_data <= {{cpuif.signal("pwdata")}};
|
||||||
|
for(int i=0; i<DATA_WIDTH/8; i++) begin
|
||||||
|
cpuif_wr_bitstrb[i*8 +: 8] <= {{"{8{"}}{{cpuif.signal("pstrb")}}[i]{{"}}"}};
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end else begin
|
||||||
|
cpuif_req <= '0;
|
||||||
|
if(cpuif_rd_ack || cpuif_wr_ack) begin
|
||||||
|
is_active <= '0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
{%- endcall %}
|
||||||
|
|
||||||
|
// Response
|
||||||
|
assign {{cpuif.signal("pready")}} = cpuif_rd_ack | cpuif_wr_ack;
|
||||||
|
assign {{cpuif.signal("prdata")}} = cpuif_rd_data;
|
||||||
|
assign {{cpuif.signal("pslverr")}} = cpuif_rd_err | cpuif_wr_err;
|
||||||
|
{%- endblock body%}
|
||||||
31
peakrdl/regblock/cpuif/base.py
Normal file
31
peakrdl/regblock/cpuif/base.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import jinja2
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..exporter import RegblockExporter
|
||||||
|
from ..signals import SignalBase
|
||||||
|
|
||||||
|
class CpuifBase:
|
||||||
|
template_path = "cpuif/base_tmpl.sv"
|
||||||
|
|
||||||
|
def __init__(self, exporter:'RegblockExporter', cpuif_reset:'SignalBase', data_width:int=32, addr_width:int=32):
|
||||||
|
self.exporter = exporter
|
||||||
|
self.cpuif_reset = cpuif_reset
|
||||||
|
self.data_width = data_width
|
||||||
|
self.addr_width = addr_width
|
||||||
|
|
||||||
|
@property
|
||||||
|
def port_declaration(self) -> str:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def get_implementation(self) -> str:
|
||||||
|
context = {
|
||||||
|
"cpuif": self,
|
||||||
|
"cpuif_reset": self.cpuif_reset,
|
||||||
|
"data_width": self.data_width,
|
||||||
|
"addr_width": self.addr_width,
|
||||||
|
}
|
||||||
|
|
||||||
|
template = self.exporter.jj_env.get_template(self.template_path)
|
||||||
|
return template.render(context)
|
||||||
6
peakrdl/regblock/cpuif/base_tmpl.sv
Normal file
6
peakrdl/regblock/cpuif/base_tmpl.sv
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
begin
|
||||||
|
{%- filter indent %}
|
||||||
|
{%- block body %}
|
||||||
|
{%- endblock %}
|
||||||
|
{%- endfilter %}
|
||||||
|
end
|
||||||
77
peakrdl/regblock/dereferencer.py
Normal file
77
peakrdl/regblock/dereferencer.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
from typing import TYPE_CHECKING, Union
|
||||||
|
from systemrdl.node import Node, FieldNode, SignalNode, RegNode
|
||||||
|
from systemrdl.rdltypes import PropertyReference
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .exporter import RegblockExporter
|
||||||
|
from .hwif.base import HwifBase
|
||||||
|
from .field_logic import FieldLogic
|
||||||
|
|
||||||
|
class Dereferencer:
|
||||||
|
"""
|
||||||
|
This class provides an interface to convert conceptual SystemRDL references
|
||||||
|
into Verilog identifiers
|
||||||
|
"""
|
||||||
|
def __init__(self, exporter:'RegblockExporter', hwif:'HwifBase', field_logic: "FieldLogic", top_node:Node):
|
||||||
|
self.exporter = exporter
|
||||||
|
self.hwif = hwif
|
||||||
|
self.field_logic = field_logic
|
||||||
|
self.top_node = top_node
|
||||||
|
|
||||||
|
def get_value(self, obj: Union[int, FieldNode, SignalNode, PropertyReference]) -> str:
|
||||||
|
"""
|
||||||
|
Returns the Verilog string that represents the value associated with the object.
|
||||||
|
|
||||||
|
If given a simple scalar value, then the corresponding Verilog literal is returned.
|
||||||
|
|
||||||
|
If obj references a structural systemrdl object, then the corresponding Verilog
|
||||||
|
expression is returned that represents its value.
|
||||||
|
"""
|
||||||
|
if isinstance(obj, int):
|
||||||
|
# Is a simple scalar value
|
||||||
|
return f"'h{obj:x}"
|
||||||
|
|
||||||
|
elif isinstance(obj, FieldNode):
|
||||||
|
if obj.implements_storage:
|
||||||
|
return self.field_logic.get_storage_identifier(obj)
|
||||||
|
|
||||||
|
if self.hwif.has_value_input(obj):
|
||||||
|
return self.hwif.get_input_identifier(obj)
|
||||||
|
|
||||||
|
# Field does not have a storage element, nor does it have a HW input
|
||||||
|
# must be a constant value as defined by its reset value
|
||||||
|
reset_value = obj.get_property('reset')
|
||||||
|
if reset_value is not None:
|
||||||
|
return f"'h{reset_value:x}"
|
||||||
|
else:
|
||||||
|
# No reset value defined!
|
||||||
|
# Fall back to a value of 0
|
||||||
|
return "'h0"
|
||||||
|
|
||||||
|
elif isinstance(obj, SignalNode):
|
||||||
|
# Signals are always inputs from the hwif
|
||||||
|
return self.hwif.get_input_identifier(obj)
|
||||||
|
|
||||||
|
elif isinstance(obj, PropertyReference):
|
||||||
|
# TODO: Table G1 describes other possible ref targets
|
||||||
|
|
||||||
|
# Value reduction properties
|
||||||
|
val = self.get_value(obj.node)
|
||||||
|
if obj.name == "anded":
|
||||||
|
return f"&({val})"
|
||||||
|
elif obj.name == "ored":
|
||||||
|
return f"|({val})"
|
||||||
|
elif obj.name == "xored":
|
||||||
|
return f"^({val})"
|
||||||
|
else:
|
||||||
|
raise RuntimeError
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise RuntimeError
|
||||||
|
|
||||||
|
def get_access_strobe(self, reg: RegNode) -> str:
|
||||||
|
"""
|
||||||
|
Returns the Verilog string that represents the register's access strobe
|
||||||
|
"""
|
||||||
|
# TODO: Implement me
|
||||||
|
raise NotImplementedError
|
||||||
106
peakrdl/regblock/exporter.py
Normal file
106
peakrdl/regblock/exporter.py
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import os
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import jinja2 as jj
|
||||||
|
from systemrdl.node import Node, RootNode
|
||||||
|
|
||||||
|
from .addr_decode import AddressDecode
|
||||||
|
from .field_logic import FieldLogic
|
||||||
|
from .dereferencer import Dereferencer
|
||||||
|
from .readback_mux import ReadbackMux
|
||||||
|
from .signals import InferredSignal
|
||||||
|
|
||||||
|
from .cpuif.apb4 import APB4_Cpuif
|
||||||
|
from .hwif.struct import StructHwif
|
||||||
|
|
||||||
|
class RegblockExporter:
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
user_template_dir = kwargs.pop("user_template_dir", None)
|
||||||
|
|
||||||
|
# Check for stray kwargs
|
||||||
|
if kwargs:
|
||||||
|
raise TypeError("got an unexpected keyword argument '%s'" % list(kwargs.keys())[0])
|
||||||
|
|
||||||
|
if user_template_dir:
|
||||||
|
loader = jj.ChoiceLoader([
|
||||||
|
jj.FileSystemLoader(user_template_dir),
|
||||||
|
jj.FileSystemLoader(os.path.dirname(__file__)),
|
||||||
|
jj.PrefixLoader({
|
||||||
|
'user': jj.FileSystemLoader(user_template_dir),
|
||||||
|
'base': jj.FileSystemLoader(os.path.dirname(__file__)),
|
||||||
|
}, delimiter=":")
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
loader = jj.ChoiceLoader([
|
||||||
|
jj.FileSystemLoader(os.path.dirname(__file__)),
|
||||||
|
jj.PrefixLoader({
|
||||||
|
'base': jj.FileSystemLoader(os.path.dirname(__file__)),
|
||||||
|
}, delimiter=":")
|
||||||
|
])
|
||||||
|
|
||||||
|
self.jj_env = jj.Environment(
|
||||||
|
loader=loader,
|
||||||
|
undefined=jj.StrictUndefined,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def export(self, node:Node, output_path:str, **kwargs):
|
||||||
|
# If it is the root node, skip to top addrmap
|
||||||
|
if isinstance(node, RootNode):
|
||||||
|
node = node.top
|
||||||
|
|
||||||
|
cpuif_cls = kwargs.pop("cpuif_cls", APB4_Cpuif)
|
||||||
|
hwif_cls = kwargs.pop("hwif_cls", StructHwif)
|
||||||
|
module_name = kwargs.pop("module_name", node.inst_name)
|
||||||
|
package_name = kwargs.pop("package_name", module_name + "_pkg")
|
||||||
|
|
||||||
|
# Check for stray kwargs
|
||||||
|
if kwargs:
|
||||||
|
raise TypeError("got an unexpected keyword argument '%s'" % list(kwargs.keys())[0])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Scan design...
|
||||||
|
|
||||||
|
# TODO: derive this from somewhere
|
||||||
|
cpuif_reset = InferredSignal("rst")
|
||||||
|
reset_signals = [cpuif_reset]
|
||||||
|
|
||||||
|
cpuif = cpuif_cls(
|
||||||
|
self,
|
||||||
|
cpuif_reset=cpuif_reset, # TODO:
|
||||||
|
data_width=32, # TODO:
|
||||||
|
addr_width=32 # TODO:
|
||||||
|
)
|
||||||
|
|
||||||
|
hwif = hwif_cls(
|
||||||
|
self,
|
||||||
|
top_node=node,
|
||||||
|
package_name=package_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
address_decode = AddressDecode(self, node)
|
||||||
|
field_logic = FieldLogic(self, node)
|
||||||
|
readback_mux = ReadbackMux(self, node)
|
||||||
|
dereferencer = Dereferencer(self, hwif, field_logic, node)
|
||||||
|
|
||||||
|
# Build Jinja template context
|
||||||
|
context = {
|
||||||
|
"module_name": module_name,
|
||||||
|
"data_width": 32, # TODO:
|
||||||
|
"addr_width": 32, # TODO:
|
||||||
|
"reset_signals": reset_signals,
|
||||||
|
"cpuif_reset": cpuif_reset,
|
||||||
|
"user_signals": [], # TODO:
|
||||||
|
"interrupts": [], # TODO:
|
||||||
|
"cpuif": cpuif,
|
||||||
|
"hwif": hwif,
|
||||||
|
"address_decode": address_decode,
|
||||||
|
"field_logic": field_logic,
|
||||||
|
"readback_mux": readback_mux,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Write out design
|
||||||
|
template = self.jj_env.get_template("module_tmpl.sv")
|
||||||
|
stream = template.stream(context)
|
||||||
|
stream.dump(output_path)
|
||||||
92
peakrdl/regblock/field_logic/__init__.py
Normal file
92
peakrdl/regblock/field_logic/__init__.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
from typing import TYPE_CHECKING, List
|
||||||
|
|
||||||
|
from systemrdl.node import Node, AddressableNode, RegNode, FieldNode
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..exporter import RegblockExporter
|
||||||
|
|
||||||
|
class FieldLogic:
|
||||||
|
def __init__(self, exporter:'RegblockExporter', top_node:Node):
|
||||||
|
self.exporter = exporter
|
||||||
|
self.top_node = top_node
|
||||||
|
|
||||||
|
self._indent_level = 0
|
||||||
|
|
||||||
|
def get_storage_struct(self) -> str:
|
||||||
|
lines = []
|
||||||
|
self._do_struct(lines, self.top_node, is_top=True)
|
||||||
|
|
||||||
|
# Only declare the storage struct if it exists
|
||||||
|
if lines:
|
||||||
|
lines.append(f"{self._indent}field_storage_t field_storage;")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def get_implementation(self) -> str:
|
||||||
|
return "TODO:"
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Field utility functions
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
def get_storage_identifier(self, obj: FieldNode):
|
||||||
|
assert obj.implements_storage
|
||||||
|
|
||||||
|
return "TODO: implement get_storage_identifier()"
|
||||||
|
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Struct generation functions
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
@property
|
||||||
|
def _indent(self) -> str:
|
||||||
|
return " " * self._indent_level
|
||||||
|
|
||||||
|
def _get_node_array_suffix(self, node:AddressableNode) -> str:
|
||||||
|
if node.is_array:
|
||||||
|
return "".join([f'[{dim}]' for dim in node.array_dimensions])
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _do_struct(self, lines:List[str], node:AddressableNode, is_top:bool = False) -> bool:
|
||||||
|
# Collect struct members first
|
||||||
|
contents = []
|
||||||
|
self._indent_level += 1
|
||||||
|
for child in node.children():
|
||||||
|
if isinstance(child, RegNode):
|
||||||
|
self._do_reg_struct(contents, child)
|
||||||
|
elif isinstance(child, AddressableNode):
|
||||||
|
self._do_struct(contents, child)
|
||||||
|
self._indent_level -= 1
|
||||||
|
|
||||||
|
# If struct is not empty, emit a struct!
|
||||||
|
if contents:
|
||||||
|
if is_top:
|
||||||
|
lines.append(f"{self._indent}typedef struct {{")
|
||||||
|
else:
|
||||||
|
lines.append(f"{self._indent}struct {{")
|
||||||
|
|
||||||
|
lines.extend(contents)
|
||||||
|
|
||||||
|
if is_top:
|
||||||
|
lines.append(f"{self._indent}}} field_storage_t;")
|
||||||
|
else:
|
||||||
|
lines.append(f"{self._indent}}} {node.inst_name}{self._get_node_array_suffix(node)};")
|
||||||
|
|
||||||
|
|
||||||
|
def _do_reg_struct(self, lines:List[str], node:RegNode) -> None:
|
||||||
|
|
||||||
|
fields = []
|
||||||
|
for field in node.fields():
|
||||||
|
if field.implements_storage:
|
||||||
|
fields.append(field)
|
||||||
|
|
||||||
|
if not fields:
|
||||||
|
return
|
||||||
|
|
||||||
|
lines.append(f"{self._indent}struct {{")
|
||||||
|
self._indent_level += 1
|
||||||
|
for field in fields:
|
||||||
|
if field.width == 1:
|
||||||
|
lines.append(f"{self._indent}logic {field.inst_name};")
|
||||||
|
else:
|
||||||
|
lines.append(f"{self._indent}logic [{field.width-1}:0] {field.inst_name};")
|
||||||
|
self._indent_level -= 1
|
||||||
|
lines.append(f"{self._indent}}} {node.inst_name}{self._get_node_array_suffix(node)};")
|
||||||
1
peakrdl/regblock/hwif/__init__.py
Normal file
1
peakrdl/regblock/hwif/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .base import HwifBase
|
||||||
82
peakrdl/regblock/hwif/base.py
Normal file
82
peakrdl/regblock/hwif/base.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
from typing import TYPE_CHECKING, Union
|
||||||
|
from systemrdl.node import Node, SignalNode, FieldNode
|
||||||
|
from systemrdl.rdltypes import AccessType
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..exporter import RegblockExporter
|
||||||
|
|
||||||
|
class HwifBase:
|
||||||
|
"""
|
||||||
|
Defines how the hardware input/output signals are generated:
|
||||||
|
- Field outputs
|
||||||
|
- Field inputs
|
||||||
|
- Signal inputs (except those that are promoted to the top)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, exporter:'RegblockExporter', top_node:'Node', package_name:str):
|
||||||
|
self.exporter = exporter
|
||||||
|
self.top_node = top_node
|
||||||
|
self.package_name = package_name
|
||||||
|
|
||||||
|
|
||||||
|
def get_package_declaration(self) -> str:
|
||||||
|
"""
|
||||||
|
If this hwif requires a package, generate the string
|
||||||
|
"""
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def port_declaration(self) -> str:
|
||||||
|
"""
|
||||||
|
Returns the declaration string for all I/O ports in the hwif group
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# hwif utility functions
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
def has_value_input(self, obj: Union[FieldNode, SignalNode]) -> bool:
|
||||||
|
"""
|
||||||
|
Returns True if the object infers an input wire in the hwif
|
||||||
|
"""
|
||||||
|
if isinstance(obj, FieldNode):
|
||||||
|
return obj.get_property("hw") in {AccessType.rw, AccessType.w}
|
||||||
|
elif isinstance(obj, SignalNode):
|
||||||
|
raise NotImplementedError # TODO:
|
||||||
|
else:
|
||||||
|
raise RuntimeError
|
||||||
|
|
||||||
|
|
||||||
|
def has_value_output(self, obj: FieldNode) -> bool:
|
||||||
|
"""
|
||||||
|
Returns True if the object infers an output wire in the hwif
|
||||||
|
"""
|
||||||
|
return obj.get_property("hw") in {AccessType.rw, AccessType.r}
|
||||||
|
|
||||||
|
|
||||||
|
def get_input_identifier(self, obj) -> str:
|
||||||
|
"""
|
||||||
|
Returns the identifier string that best represents the input object.
|
||||||
|
|
||||||
|
if obj is:
|
||||||
|
Field: the fields input value port
|
||||||
|
Signal: signal input value
|
||||||
|
TODO: finish this
|
||||||
|
|
||||||
|
raises an exception if obj is invalid
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
def get_output_identifier(self, obj) -> str:
|
||||||
|
"""
|
||||||
|
Returns the identifier string that best represents the output object.
|
||||||
|
|
||||||
|
if obj is:
|
||||||
|
Field: the fields output value port
|
||||||
|
TODO: finish this
|
||||||
|
|
||||||
|
raises an exception if obj is invalid
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
182
peakrdl/regblock/hwif/struct.py
Normal file
182
peakrdl/regblock/hwif/struct.py
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
from typing import Union, List, TYPE_CHECKING
|
||||||
|
|
||||||
|
from systemrdl.node import Node, AddressableNode, FieldNode, SignalNode
|
||||||
|
|
||||||
|
from .base import HwifBase
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..exporter import RegblockExporter
|
||||||
|
|
||||||
|
class StructHwif(HwifBase):
|
||||||
|
|
||||||
|
def __init__(self, exporter:'RegblockExporter', top_node:Node, package_name:str):
|
||||||
|
super().__init__(exporter, top_node, package_name)
|
||||||
|
|
||||||
|
self.has_input_struct = None
|
||||||
|
self.has_output_struct = None
|
||||||
|
self._indent_level = 0
|
||||||
|
|
||||||
|
|
||||||
|
def get_package_declaration(self) -> str:
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
lines.append(f"package {self.package_name};")
|
||||||
|
self._indent_level += 1
|
||||||
|
self.has_input_struct = self._do_struct_addressable(lines, self.top_node, is_input=True)
|
||||||
|
self.has_output_struct = self._do_struct_addressable(lines, self.top_node, is_input=False)
|
||||||
|
self._indent_level -= 1
|
||||||
|
lines.append("")
|
||||||
|
lines.append(f"endpackage")
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def port_declaration(self) -> str:
|
||||||
|
"""
|
||||||
|
Returns the declaration string for all I/O ports in the hwif group
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Assume get_package_declaration() is always called prior to this
|
||||||
|
assert self.has_input_struct is not None
|
||||||
|
assert self.has_output_struct is not None
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
if self.has_input_struct:
|
||||||
|
lines.append(f"input {self.package_name}::{self._get_struct_name(self.top_node, is_input=True)} hwif_in")
|
||||||
|
if self.has_output_struct:
|
||||||
|
lines.append(f"output {self.package_name}::{self._get_struct_name(self.top_node, is_input=False)} hwif_out")
|
||||||
|
|
||||||
|
return ",\n".join(lines)
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Struct generation functions
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
@property
|
||||||
|
def _indent(self) -> str:
|
||||||
|
return " " * self._indent_level
|
||||||
|
|
||||||
|
def _get_node_array_suffix(self, node:AddressableNode) -> str:
|
||||||
|
if node.is_array:
|
||||||
|
return "".join([f'[{dim}]' for dim in node.array_dimensions])
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _get_struct_name(self, node:Node, is_input:bool = True) -> str:
|
||||||
|
base = node.get_rel_path(
|
||||||
|
self.top_node.parent,
|
||||||
|
hier_separator="__",
|
||||||
|
array_suffix="x",
|
||||||
|
empty_array_suffix="x"
|
||||||
|
)
|
||||||
|
if is_input:
|
||||||
|
return f'{base}_in_t'
|
||||||
|
return f'{base}__out_t'
|
||||||
|
|
||||||
|
def _do_struct_addressable(self, lines:list, node:AddressableNode, is_input:bool = True) -> bool:
|
||||||
|
|
||||||
|
struct_children = []
|
||||||
|
|
||||||
|
# Generate structs for children first
|
||||||
|
for child in node.children():
|
||||||
|
if isinstance(child, AddressableNode):
|
||||||
|
if self._do_struct_addressable(lines, child, is_input):
|
||||||
|
struct_children.append(child)
|
||||||
|
elif isinstance(child, FieldNode):
|
||||||
|
if self._do_struct_field(lines, child, is_input):
|
||||||
|
struct_children.append(child)
|
||||||
|
elif is_input and isinstance(child, SignalNode):
|
||||||
|
# No child struct needed here
|
||||||
|
# TODO: Skip if this is a top-level child
|
||||||
|
struct_children.append(child)
|
||||||
|
|
||||||
|
# Generate this addressable node's struct
|
||||||
|
if struct_children:
|
||||||
|
lines.append("")
|
||||||
|
lines.append(f"{self._indent}// {node.get_rel_path(self.top_node.parent)}")
|
||||||
|
lines.append(f"{self._indent}typedef struct {{")
|
||||||
|
self._indent_level += 1
|
||||||
|
for child in struct_children:
|
||||||
|
if isinstance(child, AddressableNode):
|
||||||
|
lines.append(f"{self._indent}{self._get_struct_name(child, is_input)} {child.inst_name}{self._get_node_array_suffix(child)};")
|
||||||
|
elif isinstance(child, FieldNode):
|
||||||
|
lines.append(f"{self._indent}{self._get_struct_name(child, is_input)} {child.inst_name};")
|
||||||
|
elif isinstance(child, SignalNode):
|
||||||
|
if child.width == 1:
|
||||||
|
lines.append(f"{self._indent}logic {child.inst_name};")
|
||||||
|
else:
|
||||||
|
lines.append(f"{self._indent}logic [{child.msb}:{child.lsb}] {child.inst_name};")
|
||||||
|
|
||||||
|
self._indent_level -= 1
|
||||||
|
lines.append(f"{self._indent}}} {self._get_struct_name(node, is_input)};")
|
||||||
|
|
||||||
|
return bool(struct_children)
|
||||||
|
|
||||||
|
def _do_struct_field(self, lines:list, node:FieldNode, is_input:bool = True) -> bool:
|
||||||
|
contents = []
|
||||||
|
|
||||||
|
if is_input:
|
||||||
|
contents = self._get_struct_input_field_contents(node)
|
||||||
|
else:
|
||||||
|
contents = self._get_struct_output_field_contents(node)
|
||||||
|
|
||||||
|
if contents:
|
||||||
|
lines.append("")
|
||||||
|
lines.append(f"{self._indent}// {node.get_rel_path(self.top_node.parent)}")
|
||||||
|
lines.append(f"{self._indent}typedef struct {{")
|
||||||
|
self._indent_level += 1
|
||||||
|
for member in contents:
|
||||||
|
lines.append(self._indent + member)
|
||||||
|
self._indent_level -= 1
|
||||||
|
lines.append(f"{self._indent}}} {self._get_struct_name(node, is_input)};")
|
||||||
|
|
||||||
|
return bool(contents)
|
||||||
|
|
||||||
|
def _get_struct_input_field_contents(self, node:FieldNode) -> List[str]:
|
||||||
|
contents = []
|
||||||
|
|
||||||
|
# Provide input to field's value if it is writable by hw
|
||||||
|
if self.has_value_input(node):
|
||||||
|
if node.width == 1:
|
||||||
|
contents.append("logic value;")
|
||||||
|
else:
|
||||||
|
contents.append(f"logic [{node.width-1}:0] value;")
|
||||||
|
|
||||||
|
# TODO:
|
||||||
|
"""
|
||||||
|
we/wel
|
||||||
|
if either is boolean, and true
|
||||||
|
not part of external hwif if reference
|
||||||
|
mutually exclusive
|
||||||
|
hwclr/hwset
|
||||||
|
if either is boolean, and true
|
||||||
|
not part of external hwif if reference
|
||||||
|
incr/decr
|
||||||
|
if counter=true, generate BOTH
|
||||||
|
incrvalue/decrvalue
|
||||||
|
if either incrwidth/decrwidth are set
|
||||||
|
signals!
|
||||||
|
any signal instances instantiated in the scope
|
||||||
|
"""
|
||||||
|
|
||||||
|
return contents
|
||||||
|
|
||||||
|
def _get_struct_output_field_contents(self, node:FieldNode) -> List[str]:
|
||||||
|
contents = []
|
||||||
|
|
||||||
|
# Expose field's value if it is readable by hw
|
||||||
|
if self.has_value_output(node):
|
||||||
|
if node.width == 1:
|
||||||
|
contents.append("logic value;")
|
||||||
|
else:
|
||||||
|
contents.append(f"logic [{node.width-1}:0] value;")
|
||||||
|
|
||||||
|
# TODO:
|
||||||
|
"""
|
||||||
|
bitwise reductions
|
||||||
|
if anded, ored, xored == True, output a signal
|
||||||
|
swmod/swacc
|
||||||
|
event strobes
|
||||||
|
Are there was_written/was_read strobes too?
|
||||||
|
"""
|
||||||
|
|
||||||
|
return contents
|
||||||
90
peakrdl/regblock/module_tmpl.sv
Normal file
90
peakrdl/regblock/module_tmpl.sv
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
{%- import "utils_tmpl.sv" as utils with context -%}
|
||||||
|
|
||||||
|
{{hwif.get_package_declaration()}}
|
||||||
|
|
||||||
|
module {{module_name}} #(
|
||||||
|
// TODO: pipeline parameters
|
||||||
|
)(
|
||||||
|
input wire clk,
|
||||||
|
{%- for signal in reset_signals %}
|
||||||
|
{{signal.port_declaration}},
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{%- for signal in user_signals %}
|
||||||
|
{{signal.port_declaration}},
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{%- for interrupt in interrupts %}
|
||||||
|
{{interrupt.port_declaration}},
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{{cpuif.port_declaration|indent(8)}},
|
||||||
|
|
||||||
|
{{hwif.port_declaration|indent(8)}}
|
||||||
|
);
|
||||||
|
|
||||||
|
localparam ADDR_WIDTH = {{addr_width}};
|
||||||
|
localparam DATA_WIDTH = {{data_width}};
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// CPU Bus interface logic
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
logic cpuif_req;
|
||||||
|
logic cpuif_req_is_wr;
|
||||||
|
logic [ADDR_WIDTH-1:0] cpuif_addr;
|
||||||
|
logic [DATA_WIDTH-1:0] cpuif_wr_data;
|
||||||
|
logic [DATA_WIDTH-1:0] cpuif_wr_bitstrb;
|
||||||
|
|
||||||
|
logic cpuif_rd_ack;
|
||||||
|
logic [DATA_WIDTH-1:0] cpuif_rd_data;
|
||||||
|
logic cpuif_rd_err;
|
||||||
|
|
||||||
|
logic cpuif_wr_ack;
|
||||||
|
logic cpuif_wr_err;
|
||||||
|
|
||||||
|
{{cpuif.get_implementation()|indent}}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Address Decode
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
{{address_decode.get_strobe_struct()|indent}}
|
||||||
|
access_strb_t access_strb;
|
||||||
|
|
||||||
|
always_comb begin
|
||||||
|
{{address_decode.get_implementation()|indent(8)}}
|
||||||
|
end
|
||||||
|
|
||||||
|
// Writes are always posted with no error response
|
||||||
|
assign cpuif_wr_ack = cpuif_req & cpuif_req_is_wr;
|
||||||
|
assign cpuif_wr_err = '0;
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Field logic
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
{{field_logic.get_storage_struct()|indent}}
|
||||||
|
|
||||||
|
// TODO: Field next-state logic, and output port signal assignment (aka output mapping layer)
|
||||||
|
{{field_logic.get_implementation()|indent}}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Readback mux
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
logic readback_err;
|
||||||
|
logic readback_done;
|
||||||
|
logic [DATA_WIDTH-1:0] readback_data;
|
||||||
|
|
||||||
|
{{readback_mux.get_implementation()|indent}}
|
||||||
|
|
||||||
|
{%- call utils.AlwaysFF(cpuif_reset) %}
|
||||||
|
if({{cpuif_reset.activehigh_identifier}}) begin
|
||||||
|
cpuif_rd_ack <= '0;
|
||||||
|
cpuif_rd_data <= '0;
|
||||||
|
cpuif_rd_err <= '0;
|
||||||
|
end else begin
|
||||||
|
cpuif_rd_ack <= readback_done;
|
||||||
|
cpuif_rd_data <= readback_data;
|
||||||
|
cpuif_rd_err <= readback_err;
|
||||||
|
end
|
||||||
|
{%- endcall %}
|
||||||
|
|
||||||
|
endmodule
|
||||||
29
peakrdl/regblock/readback_mux.py
Normal file
29
peakrdl/regblock/readback_mux.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import re
|
||||||
|
from typing import TYPE_CHECKING, List
|
||||||
|
|
||||||
|
from systemrdl.node import Node, AddressableNode, RegNode
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .exporter import RegblockExporter
|
||||||
|
|
||||||
|
class ReadbackMux:
|
||||||
|
def __init__(self, exporter:'RegblockExporter', top_node:AddressableNode):
|
||||||
|
self.exporter = exporter
|
||||||
|
self.top_node = top_node
|
||||||
|
|
||||||
|
self._indent_level = 0
|
||||||
|
|
||||||
|
|
||||||
|
def get_implementation(self) -> str:
|
||||||
|
# TODO: Count the number of readable registers
|
||||||
|
# TODO: Emit the declaration for the readback array
|
||||||
|
# TODO: Always comb block to assign & mask all elements
|
||||||
|
# TODO: Separate always_comb block to OR reduce down
|
||||||
|
return "//TODO"
|
||||||
|
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
@property
|
||||||
|
def _indent(self) -> str:
|
||||||
|
return " " * self._indent_level
|
||||||
14
peakrdl/regblock/scan_design.py
Normal file
14
peakrdl/regblock/scan_design.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
"""
|
||||||
|
- Signal References
|
||||||
|
Collect any references to signals that lie outside of the hierarchy
|
||||||
|
These will be added as top-level signals
|
||||||
|
- top-level interrupts
|
||||||
|
|
||||||
|
|
||||||
|
Validate:
|
||||||
|
- Error if a property references a non-signal component, or property reference from
|
||||||
|
outside the export hierarchy
|
||||||
|
- No Mem components allowed
|
||||||
|
- Uniform regwidth, accesswidth, etc.
|
||||||
|
"""
|
||||||
90
peakrdl/regblock/signals.py
Normal file
90
peakrdl/regblock/signals.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from systemrdl import SignalNode
|
||||||
|
|
||||||
|
class SignalBase:
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_async(self) -> bool:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_activehigh(self) -> bool:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self) -> int:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def identifier(self) -> str:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def activehigh_identifier(self) -> str:
|
||||||
|
"""
|
||||||
|
Normalizes the identifier reference to be active-high logic
|
||||||
|
"""
|
||||||
|
if not self.is_activehigh:
|
||||||
|
return "~%s" % self.identifier
|
||||||
|
return self.identifier
|
||||||
|
|
||||||
|
@property
|
||||||
|
def port_declaration(self) -> str:
|
||||||
|
"""
|
||||||
|
Returns the port delcaration text for this signal.
|
||||||
|
In the context of this exporter, all signal objects happen to be inputs.
|
||||||
|
"""
|
||||||
|
if self.width > 1:
|
||||||
|
return "input wire [%d:0] %s" % (self.width - 1, self.identifier)
|
||||||
|
return "input wire %s" % self.identifier
|
||||||
|
|
||||||
|
|
||||||
|
class RDLSignal(SignalBase):
|
||||||
|
"""
|
||||||
|
Wrapper around a SystemRDL signal object
|
||||||
|
"""
|
||||||
|
def __init__(self, rdl_signal:'SignalNode'):
|
||||||
|
self.rdl_signal = rdl_signal
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_async(self) -> bool:
|
||||||
|
return self.rdl_signal.get_property("async")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_activehigh(self) -> bool:
|
||||||
|
return self.rdl_signal.get_property("activehigh")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self) -> int:
|
||||||
|
return self.rdl_signal.width
|
||||||
|
|
||||||
|
@property
|
||||||
|
def identifier(self) -> str:
|
||||||
|
# TODO: uniquify this somehow
|
||||||
|
# TODO: Deal with different hierarchies
|
||||||
|
return "TODO_%s" % self.rdl_signal.inst_name
|
||||||
|
|
||||||
|
|
||||||
|
class InferredSignal(SignalBase):
|
||||||
|
def __init__(self, identifier:str, width:int=1, is_async:bool=False):
|
||||||
|
self._identifier = identifier
|
||||||
|
self._width = width
|
||||||
|
self._is_async = is_async
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_async(self) -> bool:
|
||||||
|
return self._is_async
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_activehigh(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self) -> int:
|
||||||
|
return self._width
|
||||||
|
|
||||||
|
@property
|
||||||
|
def identifier(self) -> str:
|
||||||
|
return self._identifier
|
||||||
16
peakrdl/regblock/utils_tmpl.sv
Normal file
16
peakrdl/regblock/utils_tmpl.sv
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Creates an always_ff begin/end block with the appropriate edge sensitivity
|
||||||
|
* list depending on the resetsignal used
|
||||||
|
*/
|
||||||
|
{% macro AlwaysFF(resetsignal) %}
|
||||||
|
{%- if resetsignal.is_async and resetsignal.is_activehigh %}
|
||||||
|
always_ff @(posedge clk or posedge {{resetsignal.identifier}}) begin
|
||||||
|
{%- elif resetsignal.is_async and not resetsignal.is_activehigh %}
|
||||||
|
always_ff @(posedge clk or negedge {{resetsignal.identifier}}) begin
|
||||||
|
{%- else %}
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
{%- endif %}
|
||||||
|
{{- caller() }}
|
||||||
|
end
|
||||||
|
{%- endmacro %}
|
||||||
48
setup.py
Normal file
48
setup.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import os
|
||||||
|
import setuptools
|
||||||
|
|
||||||
|
with open("README.md", "r") as fh:
|
||||||
|
long_description = fh.read()
|
||||||
|
|
||||||
|
|
||||||
|
with open(os.path.join("peakrdl/regblock", "__about__.py")) as f:
|
||||||
|
v_dict = {}
|
||||||
|
exec(f.read(), v_dict)
|
||||||
|
version = v_dict['__version__']
|
||||||
|
|
||||||
|
setuptools.setup(
|
||||||
|
name="peakrdl-regblock",
|
||||||
|
version=version,
|
||||||
|
author="Alex Mykyta",
|
||||||
|
author_email="amykyta3@github.com",
|
||||||
|
description="Generate SystemVerilog RTL that implements a register block from compiled SystemRDL input",
|
||||||
|
long_description=long_description,
|
||||||
|
long_description_content_type="text/markdown",
|
||||||
|
url="https://github.com/SystemRDL/PeakRDL-regblock",
|
||||||
|
packages=['peakrdl.regblock'],
|
||||||
|
include_package_data=True,
|
||||||
|
install_requires=[
|
||||||
|
"systemrdl-compiler>=1.13.2",
|
||||||
|
"Jinja2>=2.11",
|
||||||
|
],
|
||||||
|
classifiers=(
|
||||||
|
#"Development Status :: 5 - Production/Stable",
|
||||||
|
"Development Status :: 3 - Alpha",
|
||||||
|
"Programming Language :: Python",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.5",
|
||||||
|
"Programming Language :: Python :: 3.6",
|
||||||
|
"Programming Language :: Python :: 3.7",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
"Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)",
|
||||||
|
),
|
||||||
|
project_urls={
|
||||||
|
"Source": "https://github.com/SystemRDL/PeakRDL-regblock",
|
||||||
|
"Tracker": "https://github.com/SystemRDL/PeakRDL-regblock/issues",
|
||||||
|
},
|
||||||
|
)
|
||||||
21
tst.py
Executable file
21
tst.py
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from systemrdl import RDLCompiler, RDLCompileError
|
||||||
|
from peakrdl.regblock import RegblockExporter
|
||||||
|
|
||||||
|
input_files = sys.argv[1:]
|
||||||
|
|
||||||
|
rdlc = RDLCompiler()
|
||||||
|
|
||||||
|
try:
|
||||||
|
for input_file in input_files:
|
||||||
|
rdlc.compile_file(input_file)
|
||||||
|
root = rdlc.elaborate()
|
||||||
|
except RDLCompileError:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
R = RegblockExporter()
|
||||||
|
|
||||||
|
R.export(root, "out.sv")
|
||||||
27
x.rdl
Normal file
27
x.rdl
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
|
||||||
|
addrmap top {
|
||||||
|
reg {
|
||||||
|
field {
|
||||||
|
hw=r; sw=rw;
|
||||||
|
} y = 0;
|
||||||
|
} whee[2][8];
|
||||||
|
|
||||||
|
regfile {
|
||||||
|
reg {
|
||||||
|
field {
|
||||||
|
hw=r; sw=rw;
|
||||||
|
} abc[16:2] = 0;
|
||||||
|
field {
|
||||||
|
hw=r; sw=rw;
|
||||||
|
} def[4] = 0;
|
||||||
|
} aaa[4];
|
||||||
|
reg {
|
||||||
|
field {
|
||||||
|
hw=r; sw=rw;
|
||||||
|
} abc = 0;
|
||||||
|
field {
|
||||||
|
hw=r; sw=rw;
|
||||||
|
} def = 0;
|
||||||
|
} bbb;
|
||||||
|
} asdf[20];
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user