basic framework

This commit is contained in:
Alex Mykyta
2021-06-01 21:51:24 -07:00
parent 292aec1c6e
commit 0d5b663f98
40 changed files with 1920 additions and 0 deletions

2
MANIFEST.in Normal file
View File

@@ -0,0 +1,2 @@
#recursive-include peakrdl/regblock/XXX *
recursive-exclude test *

View File

@@ -1,2 +1,9 @@
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/peakrdl-regblock.svg)](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

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

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

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

View 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

View 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

View 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

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

View 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

View 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

View 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

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

View File

@@ -0,0 +1 @@
__version__ = "0.1.0"

View File

@@ -0,0 +1,3 @@
from .__about__ import __version__
from .exporter import RegblockExporter

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

View File

@@ -0,0 +1 @@
from .base import CpuifBase

View 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

View 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%}

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

View File

@@ -0,0 +1,6 @@
begin
{%- filter indent %}
{%- block body %}
{%- endblock %}
{%- endfilter %}
end

View 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

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

View 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)};")

View File

@@ -0,0 +1 @@
from .base import HwifBase

View 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()

View 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

View 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

View 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

View 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.
"""

View 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

View 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
View 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
View 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
View 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];
};