From 0d5b663f98132dcd34afad7293c22035c18f77a1 Mon Sep 17 00:00:00 2001 From: Alex Mykyta Date: Tue, 1 Jun 2021 21:51:24 -0700 Subject: [PATCH] basic framework --- MANIFEST.in | 2 + README.md | 7 + doc/logbooks/000-Main-Logbook | 29 +++ doc/logbooks/Alpha-Beta Versioning | 10 + doc/logbooks/Interrupts | 18 ++ doc/logbooks/Program Flow | 23 ++ doc/logbooks/Resets | 11 + doc/logbooks/Signal Dereferencer | 19 ++ doc/logbooks/Some Classes | 45 ++++ doc/logbooks/Validation Needed | 60 +++++ .../template-layers/1-port-declaration | 51 ++++ .../template-layers/1.1.hardware-interface | 77 ++++++ doc/logbooks/template-layers/2-CPUIF | 72 ++++++ doc/logbooks/template-layers/3-address-decode | 51 ++++ doc/logbooks/template-layers/4-fields | 35 +++ doc/logbooks/template-layers/5-readback-mux | 65 +++++ .../template-layers/6-output-port-mapping | 9 + hand-coded.sv | 223 ++++++++++++++++++ peakrdl/regblock/__about__.py | 1 + peakrdl/regblock/__init__.py | 3 + peakrdl/regblock/addr_decode.py | 123 ++++++++++ peakrdl/regblock/cpuif/__init__.py | 1 + peakrdl/regblock/cpuif/apb4/__init__.py | 33 +++ peakrdl/regblock/cpuif/apb4/apb4_tmpl.sv | 40 ++++ peakrdl/regblock/cpuif/base.py | 31 +++ peakrdl/regblock/cpuif/base_tmpl.sv | 6 + peakrdl/regblock/dereferencer.py | 77 ++++++ peakrdl/regblock/exporter.py | 106 +++++++++ peakrdl/regblock/field_logic/__init__.py | 92 ++++++++ peakrdl/regblock/hwif/__init__.py | 1 + peakrdl/regblock/hwif/base.py | 82 +++++++ peakrdl/regblock/hwif/struct.py | 182 ++++++++++++++ peakrdl/regblock/module_tmpl.sv | 90 +++++++ peakrdl/regblock/readback_mux.py | 29 +++ peakrdl/regblock/scan_design.py | 14 ++ peakrdl/regblock/signals.py | 90 +++++++ peakrdl/regblock/utils_tmpl.sv | 16 ++ setup.py | 48 ++++ tst.py | 21 ++ x.rdl | 27 +++ 40 files changed, 1920 insertions(+) create mode 100644 MANIFEST.in create mode 100644 doc/logbooks/000-Main-Logbook create mode 100644 doc/logbooks/Alpha-Beta Versioning create mode 100644 doc/logbooks/Interrupts create mode 100644 doc/logbooks/Program Flow create mode 100644 doc/logbooks/Resets create mode 100644 doc/logbooks/Signal Dereferencer create mode 100644 doc/logbooks/Some Classes create mode 100644 doc/logbooks/Validation Needed create mode 100644 doc/logbooks/template-layers/1-port-declaration create mode 100644 doc/logbooks/template-layers/1.1.hardware-interface create mode 100644 doc/logbooks/template-layers/2-CPUIF create mode 100644 doc/logbooks/template-layers/3-address-decode create mode 100644 doc/logbooks/template-layers/4-fields create mode 100644 doc/logbooks/template-layers/5-readback-mux create mode 100644 doc/logbooks/template-layers/6-output-port-mapping create mode 100644 hand-coded.sv create mode 100644 peakrdl/regblock/__about__.py create mode 100644 peakrdl/regblock/__init__.py create mode 100644 peakrdl/regblock/addr_decode.py create mode 100644 peakrdl/regblock/cpuif/__init__.py create mode 100644 peakrdl/regblock/cpuif/apb4/__init__.py create mode 100644 peakrdl/regblock/cpuif/apb4/apb4_tmpl.sv create mode 100644 peakrdl/regblock/cpuif/base.py create mode 100644 peakrdl/regblock/cpuif/base_tmpl.sv create mode 100644 peakrdl/regblock/dereferencer.py create mode 100644 peakrdl/regblock/exporter.py create mode 100644 peakrdl/regblock/field_logic/__init__.py create mode 100644 peakrdl/regblock/hwif/__init__.py create mode 100644 peakrdl/regblock/hwif/base.py create mode 100644 peakrdl/regblock/hwif/struct.py create mode 100644 peakrdl/regblock/module_tmpl.sv create mode 100644 peakrdl/regblock/readback_mux.py create mode 100644 peakrdl/regblock/scan_design.py create mode 100644 peakrdl/regblock/signals.py create mode 100644 peakrdl/regblock/utils_tmpl.sv create mode 100644 setup.py create mode 100755 tst.py create mode 100644 x.rdl diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..cf582ac --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +#recursive-include peakrdl/regblock/XXX * +recursive-exclude test * diff --git a/README.md b/README.md index c62dafa..fb060c2 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/doc/logbooks/000-Main-Logbook b/doc/logbooks/000-Main-Logbook new file mode 100644 index 0000000..369ef5e --- /dev/null +++ b/doc/logbooks/000-Main-Logbook @@ -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. diff --git a/doc/logbooks/Alpha-Beta Versioning b/doc/logbooks/Alpha-Beta Versioning new file mode 100644 index 0000000..c8a089d --- /dev/null +++ b/doc/logbooks/Alpha-Beta Versioning @@ -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... diff --git a/doc/logbooks/Interrupts b/doc/logbooks/Interrupts new file mode 100644 index 0000000..e21614e --- /dev/null +++ b/doc/logbooks/Interrupts @@ -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 \ No newline at end of file diff --git a/doc/logbooks/Program Flow b/doc/logbooks/Program Flow new file mode 100644 index 0000000..a07ff23 --- /dev/null +++ b/doc/logbooks/Program Flow @@ -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 diff --git a/doc/logbooks/Resets b/doc/logbooks/Resets new file mode 100644 index 0000000..2b7ad14 --- /dev/null +++ b/doc/logbooks/Resets @@ -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 diff --git a/doc/logbooks/Signal Dereferencer b/doc/logbooks/Signal Dereferencer new file mode 100644 index 0000000..0a40b59 --- /dev/null +++ b/doc/logbooks/Signal Dereferencer @@ -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 diff --git a/doc/logbooks/Some Classes b/doc/logbooks/Some Classes new file mode 100644 index 0000000..eadfad3 --- /dev/null +++ b/doc/logbooks/Some Classes @@ -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 diff --git a/doc/logbooks/Validation Needed b/doc/logbooks/Validation Needed new file mode 100644 index 0000000..b6edbc3 --- /dev/null +++ b/doc/logbooks/Validation Needed @@ -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 diff --git a/doc/logbooks/template-layers/1-port-declaration b/doc/logbooks/template-layers/1-port-declaration new file mode 100644 index 0000000..f4df391 --- /dev/null +++ b/doc/logbooks/template-layers/1-port-declaration @@ -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 diff --git a/doc/logbooks/template-layers/1.1.hardware-interface b/doc/logbooks/template-layers/1.1.hardware-interface new file mode 100644 index 0000000..b6a76d4 --- /dev/null +++ b/doc/logbooks/template-layers/1.1.hardware-interface @@ -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 diff --git a/doc/logbooks/template-layers/2-CPUIF b/doc/logbooks/template-layers/2-CPUIF new file mode 100644 index 0000000..d0a0c98 --- /dev/null +++ b/doc/logbooks/template-layers/2-CPUIF @@ -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) diff --git a/doc/logbooks/template-layers/3-address-decode b/doc/logbooks/template-layers/3-address-decode new file mode 100644 index 0000000..b835c1c --- /dev/null +++ b/doc/logbooks/template-layers/3-address-decode @@ -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 diff --git a/doc/logbooks/template-layers/4-fields b/doc/logbooks/template-layers/4-fields new file mode 100644 index 0000000..85993f5 --- /dev/null +++ b/doc/logbooks/template-layers/4-fields @@ -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 diff --git a/doc/logbooks/template-layers/5-readback-mux b/doc/logbooks/template-layers/5-readback-mux new file mode 100644 index 0000000..9284446 --- /dev/null +++ b/doc/logbooks/template-layers/5-readback-mux @@ -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 diff --git a/doc/logbooks/template-layers/6-output-port-mapping b/doc/logbooks/template-layers/6-output-port-mapping new file mode 100644 index 0000000..013330b --- /dev/null +++ b/doc/logbooks/template-layers/6-output-port-mapping @@ -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 diff --git a/hand-coded.sv b/hand-coded.sv new file mode 100644 index 0000000..3a6e9da --- /dev/null +++ b/hand-coded.sv @@ -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 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) diff --git a/peakrdl/regblock/cpuif/__init__.py b/peakrdl/regblock/cpuif/__init__.py new file mode 100644 index 0000000..08b2adc --- /dev/null +++ b/peakrdl/regblock/cpuif/__init__.py @@ -0,0 +1 @@ +from .base import CpuifBase diff --git a/peakrdl/regblock/cpuif/apb4/__init__.py b/peakrdl/regblock/cpuif/apb4/__init__.py new file mode 100644 index 0000000..dcd5cda --- /dev/null +++ b/peakrdl/regblock/cpuif/apb4/__init__.py @@ -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 diff --git a/peakrdl/regblock/cpuif/apb4/apb4_tmpl.sv b/peakrdl/regblock/cpuif/apb4/apb4_tmpl.sv new file mode 100644 index 0000000..edc4137 --- /dev/null +++ b/peakrdl/regblock/cpuif/apb4/apb4_tmpl.sv @@ -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 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) diff --git a/peakrdl/regblock/cpuif/base_tmpl.sv b/peakrdl/regblock/cpuif/base_tmpl.sv new file mode 100644 index 0000000..330d321 --- /dev/null +++ b/peakrdl/regblock/cpuif/base_tmpl.sv @@ -0,0 +1,6 @@ +begin + {%- filter indent %} + {%- block body %} + {%- endblock %} + {%- endfilter %} +end diff --git a/peakrdl/regblock/dereferencer.py b/peakrdl/regblock/dereferencer.py new file mode 100644 index 0000000..39c7c8a --- /dev/null +++ b/peakrdl/regblock/dereferencer.py @@ -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 diff --git a/peakrdl/regblock/exporter.py b/peakrdl/regblock/exporter.py new file mode 100644 index 0000000..3b23aa1 --- /dev/null +++ b/peakrdl/regblock/exporter.py @@ -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) diff --git a/peakrdl/regblock/field_logic/__init__.py b/peakrdl/regblock/field_logic/__init__.py new file mode 100644 index 0000000..719af96 --- /dev/null +++ b/peakrdl/regblock/field_logic/__init__.py @@ -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)};") diff --git a/peakrdl/regblock/hwif/__init__.py b/peakrdl/regblock/hwif/__init__.py new file mode 100644 index 0000000..ed04c84 --- /dev/null +++ b/peakrdl/regblock/hwif/__init__.py @@ -0,0 +1 @@ +from .base import HwifBase diff --git a/peakrdl/regblock/hwif/base.py b/peakrdl/regblock/hwif/base.py new file mode 100644 index 0000000..83f92fd --- /dev/null +++ b/peakrdl/regblock/hwif/base.py @@ -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() diff --git a/peakrdl/regblock/hwif/struct.py b/peakrdl/regblock/hwif/struct.py new file mode 100644 index 0000000..b778080 --- /dev/null +++ b/peakrdl/regblock/hwif/struct.py @@ -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 diff --git a/peakrdl/regblock/module_tmpl.sv b/peakrdl/regblock/module_tmpl.sv new file mode 100644 index 0000000..5b9cb32 --- /dev/null +++ b/peakrdl/regblock/module_tmpl.sv @@ -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 diff --git a/peakrdl/regblock/readback_mux.py b/peakrdl/regblock/readback_mux.py new file mode 100644 index 0000000..3734a5a --- /dev/null +++ b/peakrdl/regblock/readback_mux.py @@ -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 diff --git a/peakrdl/regblock/scan_design.py b/peakrdl/regblock/scan_design.py new file mode 100644 index 0000000..8afe5be --- /dev/null +++ b/peakrdl/regblock/scan_design.py @@ -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. +""" diff --git a/peakrdl/regblock/signals.py b/peakrdl/regblock/signals.py new file mode 100644 index 0000000..cb7a3b5 --- /dev/null +++ b/peakrdl/regblock/signals.py @@ -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 diff --git a/peakrdl/regblock/utils_tmpl.sv b/peakrdl/regblock/utils_tmpl.sv new file mode 100644 index 0000000..3f55930 --- /dev/null +++ b/peakrdl/regblock/utils_tmpl.sv @@ -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 %} diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ea3bd6c --- /dev/null +++ b/setup.py @@ -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", + }, +) diff --git a/tst.py b/tst.py new file mode 100755 index 0000000..245dfc1 --- /dev/null +++ b/tst.py @@ -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") diff --git a/x.rdl b/x.rdl new file mode 100644 index 0000000..82cb305 --- /dev/null +++ b/x.rdl @@ -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]; +};