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
|
||||
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