mirror of
https://github.com/Silicon1602/srdl2sv.git
synced 2024-11-14 11:03:36 +00:00
Finish initial version of interrupt suport, closes #1
The software is now able to create most interrupt combinations of Section 9.9 of the SystemRDL 2.0 LRM. It supports stickybit/non-stickybit interrupts, it support posedge, negedge, bothedge, and level interrupts, and it is able to generate all surrounding logic. This commit also fixes a reset-bug that caused registers that were reset to 0 to be not reset (because 'if not reset_value' will return True if the 'reset_value' is 0).
This commit is contained in:
parent
c52e59abd0
commit
6359883c27
@ -228,8 +228,16 @@ class Component():
|
||||
|
||||
return ''.join(name)
|
||||
|
||||
def process_yaml(self, yaml_obj, values: dict = {}):
|
||||
def process_yaml(self,
|
||||
yaml_obj,
|
||||
values: dict = {},
|
||||
skip_signals: bool = False,
|
||||
skip_inputs: bool = False,
|
||||
skip_outputs: bool = False):
|
||||
try:
|
||||
if skip_signals:
|
||||
raise KeyError
|
||||
|
||||
for x in yaml_obj['signals']:
|
||||
self.signals[x['name'].format(**values)] =\
|
||||
(x['signal_type'].format(**values),
|
||||
@ -238,6 +246,9 @@ class Component():
|
||||
pass
|
||||
|
||||
try:
|
||||
if skip_inputs:
|
||||
raise KeyError
|
||||
|
||||
for x in yaml_obj['input_ports']:
|
||||
self.ports['input'][x['name'].format(**values)] =\
|
||||
(x['signal_type'].format(**values),
|
||||
@ -246,6 +257,9 @@ class Component():
|
||||
pass
|
||||
|
||||
try:
|
||||
if skip_outputs:
|
||||
raise KeyError
|
||||
|
||||
for x in yaml_obj['output_ports']:
|
||||
self.ports['output'][x['name'].format(**values)] =\
|
||||
(x['signal_type'].format(**values),
|
||||
|
@ -6,7 +6,7 @@ import yaml
|
||||
|
||||
from systemrdl.node import FieldNode, SignalNode
|
||||
from systemrdl.component import Reg, Regfile, Addrmap, Root
|
||||
from systemrdl.rdltypes import PrecedenceType, AccessType, OnReadType, OnWriteType
|
||||
from systemrdl.rdltypes import PrecedenceType, AccessType, OnReadType, OnWriteType, InterruptType
|
||||
|
||||
# Local modules
|
||||
from components.component import Component, TypeDef
|
||||
@ -46,8 +46,11 @@ class Field(Component):
|
||||
# seperately in case of alias registers
|
||||
if not self.config['external']:
|
||||
self.__add_always_ff()
|
||||
|
||||
# Only add normal hardware access if field is not an interrupt field
|
||||
if not self.__add_interrupt():
|
||||
self.__add_hw_access()
|
||||
self.__add_interrupt()
|
||||
|
||||
self.__add_combo()
|
||||
self.__add_swmod_swacc()
|
||||
self.__add_counter()
|
||||
@ -125,7 +128,7 @@ class Field(Component):
|
||||
)
|
||||
else:
|
||||
# If field spans multiple bytes, every byte shall have a seperate enable!
|
||||
for j, i in enumerate(range(self.lsbyte, self.msbyte+1)):
|
||||
for i in range(self.lsbyte, self.msbyte+1):
|
||||
msb_bus = 8*(i+1)-1 if i != self.msbyte else obj.msb
|
||||
lsb_bus = 8*i if i != self.lsbyte else obj.inst.lsb
|
||||
|
||||
@ -690,6 +693,81 @@ class Field(Component):
|
||||
if self.obj.get_property('intr'):
|
||||
self.intr = True
|
||||
|
||||
# Determine what causes the interrupt to get set, i.e.,
|
||||
# is it a trigger that is passed to the module through an
|
||||
# input or is it an internal signal
|
||||
if next_val := self.obj.get_property('next'):
|
||||
intr_trigger = self.get_signal_name(next_val)
|
||||
else:
|
||||
intr_trigger = \
|
||||
self.process_yaml(
|
||||
Field.templ_dict['interrupt_trigger_input'],
|
||||
{'path': self.path_underscored}
|
||||
)
|
||||
|
||||
if self.obj.get_property('stickybit'):
|
||||
self.access_rtl['hw_write'] = ([
|
||||
self.process_yaml(
|
||||
Field.templ_dict['sticky_intr'],
|
||||
{'path': self.path_underscored,
|
||||
'genvars': self.genvars_str,
|
||||
}
|
||||
)
|
||||
],
|
||||
False)
|
||||
|
||||
# Create logic that contains condition for trigger
|
||||
if self.obj.get_property('intr type') != InterruptType.level:
|
||||
if self.rst['name']:
|
||||
reset_intr_header = \
|
||||
self.process_yaml(
|
||||
Field.templ_dict['rst_intr_header'],
|
||||
{'interrupt_trigger_input': intr_trigger,
|
||||
'rst_name': self.rst['name'],
|
||||
'rst_negl': "!" if self.rst['active'] == "active_low" else "",
|
||||
'genvars': self.genvars_str,
|
||||
}
|
||||
)
|
||||
else:
|
||||
reset_intr_header = ""
|
||||
|
||||
self.rtl_footer.append(
|
||||
self.process_yaml(
|
||||
Field.templ_dict['always_ff_block_intr'],
|
||||
{'interrupt_trigger_input': intr_trigger,
|
||||
'always_ff_header': self.always_ff_header,
|
||||
'reset_intr_header': reset_intr_header,
|
||||
'genvars': self.genvars_str,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
# This should be implemented as a match case statement later,
|
||||
# but for now use the old if/elif/else construct to ensure
|
||||
# compatibility with Python versions before 2021
|
||||
self.rtl_footer.append(
|
||||
self.process_yaml(
|
||||
Field.templ_dict[str(self.obj.get_property('intr type'))],
|
||||
{'interrupt_trigger_input': intr_trigger,
|
||||
'path': self.path_underscored,
|
||||
'genvars': self.genvars_str,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
else:
|
||||
self.access_rtl['hw_write'] = ([
|
||||
self.process_yaml(
|
||||
Field.templ_dict['nonsticky_intr'],
|
||||
{'path': self.path_underscored,
|
||||
'assignment': intr_trigger,
|
||||
'genvars': self.genvars_str,
|
||||
}
|
||||
)
|
||||
],
|
||||
False)
|
||||
|
||||
# Generate masked & enabled version of interrupt to be
|
||||
# picked up by the register at the top level
|
||||
if mask := self.obj.get_property('mask'):
|
||||
@ -727,6 +805,8 @@ class Field(Component):
|
||||
self.itr_masked = False
|
||||
self.itr_haltmasked = False
|
||||
|
||||
return self.intr
|
||||
|
||||
def __add_hw_access(self):
|
||||
# Mutually exclusive. systemrdl-compiler performs check for this
|
||||
enable_mask_negl = ''
|
||||
@ -795,11 +875,15 @@ class Field(Component):
|
||||
|
||||
assignment = self.get_signal_name(self.obj.get_property('next'))
|
||||
|
||||
skip_inputs = True
|
||||
|
||||
if self.we_or_wel:
|
||||
self.logger.info("This field has a 'we' or 'wel' property and "
|
||||
"uses the 'next' property. Make sure this is "
|
||||
"is intentional.")
|
||||
else:
|
||||
skip_inputs = False
|
||||
|
||||
# No special property. Assign input to register
|
||||
assignment = \
|
||||
self.process_yaml(
|
||||
@ -819,7 +903,8 @@ class Field(Component):
|
||||
'enable_mask_end': enable_mask_end_rtl,
|
||||
'assignment': assignment,
|
||||
'idx': enable_mask_idx,
|
||||
'field_type': self.field_type}
|
||||
'field_type': self.field_type},
|
||||
skip_inputs = skip_inputs
|
||||
)
|
||||
)
|
||||
else:
|
||||
@ -952,10 +1037,13 @@ class Field(Component):
|
||||
break
|
||||
|
||||
# Check if there is a list that shall be unlooped
|
||||
try:
|
||||
if isinstance(self.access_rtl[i], tuple):
|
||||
access_rtl = [self.access_rtl[i]]
|
||||
else:
|
||||
access_rtl = self.access_rtl[i]
|
||||
except KeyError:
|
||||
continue
|
||||
|
||||
for unpacked_access_rtl in access_rtl:
|
||||
if len(unpacked_access_rtl[0]) == 0:
|
||||
@ -1113,10 +1201,17 @@ class Field(Component):
|
||||
|
||||
if self.rst['name']:
|
||||
self.resets.add(self.rst['name'])
|
||||
elif obj.get_property("reset") is not None:
|
||||
self.logger.warning("Field has a reset value, but no reset "\
|
||||
"signal was defined and connected to the "\
|
||||
"field. Note that explicit connecting this "\
|
||||
"is not required if a field_reset was defined.")
|
||||
|
||||
# Value of reset must always be determined on field level
|
||||
# Don't use 'not obj.get_property("reset"), since the value
|
||||
# could (and will often be) be '0'
|
||||
self.rst['value'] = \
|
||||
'\'x' if not obj.get_property("reset") else\
|
||||
'\'x' if obj.get_property("reset") == None else\
|
||||
obj.get_property('reset')
|
||||
|
||||
# Define dict that holds all RTL
|
||||
@ -1155,13 +1250,14 @@ class Field(Component):
|
||||
# Handle always_ff
|
||||
sense_list = 'sense_list_rst' if self.rst['async'] else 'sense_list_no_rst'
|
||||
|
||||
self.rtl_header.append(
|
||||
self.always_ff_header = \
|
||||
self.process_yaml(
|
||||
Field.templ_dict[sense_list],
|
||||
{'rst_edge': self.rst['edge'],
|
||||
'rst_name': self.rst['name']}
|
||||
)
|
||||
)
|
||||
|
||||
self.rtl_header.append(self.always_ff_header)
|
||||
|
||||
# Add actual reset line
|
||||
if self.rst['name']:
|
||||
@ -1179,9 +1275,6 @@ class Field(Component):
|
||||
|
||||
self.rtl_header.append("begin")
|
||||
|
||||
# Add name of actual field to Signal field
|
||||
# TODO
|
||||
|
||||
def sanity_checks(self):
|
||||
# If hw=rw/sw=[r]w and hw has no we/wel, sw will never be able to write
|
||||
if not self.we_or_wel and\
|
||||
@ -1195,8 +1288,6 @@ class Field(Component):
|
||||
"write every cycle.")
|
||||
|
||||
|
||||
# TODO: Counter & hw=r shouldn't work
|
||||
|
||||
# If hw=ro and the next property is set, throw a fatal
|
||||
if self.obj.get_property('hw') == AccessType.r\
|
||||
and self.obj.get_property('next'):
|
||||
|
@ -470,3 +470,79 @@ external_wr_assignments:
|
||||
external_wr_mask_segment:
|
||||
rtl: |-
|
||||
{{{width}{{b2r.byte_en[{idx}]}}}}
|
||||
interrupt_trigger_input:
|
||||
rtl: |-
|
||||
{path}_set_intr
|
||||
input_ports:
|
||||
- name: '{path}_set_intr'
|
||||
signal_type: 'logic'
|
||||
rst_intr_header:
|
||||
rtl: |-
|
||||
if ({rst_negl}{rst_name})
|
||||
<<INDENT>>
|
||||
{interrupt_trigger_input}_q{genvars} <= 1'b0;
|
||||
<<UNINDENT>>
|
||||
else
|
||||
signals:
|
||||
- name: '{interrupt_trigger_input}_q'
|
||||
signal_type: 'logic'
|
||||
always_ff_block_intr:
|
||||
rtl: |-
|
||||
|
||||
// Flops to generate appropriate interrupt signal
|
||||
{always_ff_header}
|
||||
{reset_intr_header}
|
||||
<<INDENT>>
|
||||
{interrupt_trigger_input}_q{genvars} <= {interrupt_trigger_input}{genvars};
|
||||
<<UNINDENT>>
|
||||
signals:
|
||||
- name: '{interrupt_trigger_input}_q'
|
||||
signal_type: 'logic'
|
||||
InterruptType.posedge:
|
||||
rtl: |-
|
||||
|
||||
// Define signal that causes the interrupt to be set (posedge-type interrupt)
|
||||
assign {path}_intr_latch{genvars} = !{interrupt_trigger_input}_q{genvars} && {interrupt_trigger_input}{genvars};
|
||||
signals:
|
||||
- name: '{path}_intr_latch'
|
||||
signal_type: 'logic'
|
||||
InterruptType.negedge:
|
||||
rtl: |-
|
||||
|
||||
// Define signal that causes the interrupt to be set (negedge-type interrupt)
|
||||
assign {path}_intr_latch{genvars} = {interrupt_trigger_input}_q{genvars} && !{interrupt_trigger_input}{genvars};
|
||||
signals:
|
||||
- name: '{path}_intr_latch'
|
||||
signal_type: 'logic'
|
||||
InterruptType.bothedge:
|
||||
rtl: |-
|
||||
|
||||
// Define signal that causes the interrupt to be set (bothedge-type interrupt)
|
||||
assign {path}_intr_latch{genvars} = ({interrupt_trigger_input}_q{genvars} && !{interrupt_trigger_input}{genvars}) || (!{interrupt_trigger_input}_q{genvars} && {interrupt_trigger_input}{genvars});
|
||||
signals:
|
||||
- name: '{path}_intr_latch'
|
||||
signal_type: 'logic'
|
||||
InterruptType.level:
|
||||
rtl: |-
|
||||
|
||||
// Define signal that causes the interrupt to be set (level-type interrupt)
|
||||
assign {path}_intr_latch{genvars} = {interrupt_trigger_input}{genvars};
|
||||
signals:
|
||||
- name: '{path}_intr_latch'
|
||||
signal_type: 'logic'
|
||||
sticky_intr:
|
||||
rtl: |-
|
||||
if ({path}_intr_latch{genvars})
|
||||
begin
|
||||
// Sticky interrupt. Keep value until software clears it
|
||||
{path}_q{genvars} <= 1'b1;
|
||||
end
|
||||
signals:
|
||||
- name: '{path}_intr_latch'
|
||||
signal_type: 'logic'
|
||||
nonsticky_intr:
|
||||
rtl: |-
|
||||
begin
|
||||
// Non-sticky interrupt. Only keep value high if source keeps up
|
||||
{path}_q{genvars} <= {assignment};
|
||||
end
|
||||
|
@ -138,7 +138,7 @@ interrupt_halt:
|
||||
rtl: |-
|
||||
|
||||
// Register has at least one interrupt field with halt property set
|
||||
assign {path}_halt{genvars} = {list};
|
||||
assign {path}_halt{genvars} = ({list});
|
||||
output_ports:
|
||||
- name: '{path}_halt'
|
||||
signal_type: 'logic'
|
||||
|
56
tests/systemrdl/interrupts.rdl
Normal file
56
tests/systemrdl/interrupts.rdl
Normal file
@ -0,0 +1,56 @@
|
||||
addrmap interrupts {
|
||||
signal { activelow; async; field_reset;} field_reset_n;
|
||||
|
||||
reg {
|
||||
field {sw=rw; hw=rw; intr; } intr1 [0:0] = 0;
|
||||
field {sw=rw; hw=rw; bothedge intr; } intr2 [1:1] = 0;
|
||||
field {sw=rw; hw=rw; negedge intr; } intr3 [2:2] = 0;
|
||||
field {sw=rw; hw=rw; posedge intr; } intr4 [3:3] = 0;
|
||||
field {sw=rw; hw=rw; } intr5 [4:4] = 0;
|
||||
field {sw=rw; hw=rw; nonsticky intr;} intr6 [5:5] = 0;
|
||||
} itrs_reg;
|
||||
|
||||
reg {
|
||||
field {sw=rw; hw=r;} intr1 [0:0];
|
||||
field {sw=rw; hw=r;} intr2 [1:1];
|
||||
} itrs_mask;
|
||||
|
||||
reg {
|
||||
field {sw=rw; hw=r;} intr5 [1:1];
|
||||
} itrs_enable;
|
||||
|
||||
reg {
|
||||
field {sw=rw; hw=r;} intr6 [1:1];
|
||||
} itrs_next_assign;
|
||||
|
||||
itrs_reg.intr1->mask = itrs_mask.intr1;
|
||||
itrs_reg.intr2->mask = itrs_mask.intr2;
|
||||
itrs_reg.intr5->enable = itrs_enable.intr5;
|
||||
itrs_reg.intr5->next = itrs_next_assign.intr6;
|
||||
|
||||
|
||||
// HALT REGISTERS
|
||||
reg {
|
||||
field {sw=rw; hw=rw; intr;} intr1 [0:0];
|
||||
field {sw=rw; hw=rw; intr;} intr2 [1:1];
|
||||
field {sw=rw; hw=rw; intr;} intr3 [2:2];
|
||||
field {sw=rw; hw=rw; } intr4 [3:3];
|
||||
field {sw=rw; hw=rw; intr;} intr5 [4:4];
|
||||
} itrs_halteable_reg;
|
||||
|
||||
reg {
|
||||
field {sw=rw; hw=r;} intr1 [0:0];
|
||||
field {sw=rw; hw=r;} intr2 [1:1];
|
||||
} itrs_halt;
|
||||
|
||||
itrs_halteable_reg.intr1->haltmask = itrs_halt.intr1;
|
||||
itrs_halteable_reg.intr2->haltmask = itrs_halt.intr2;
|
||||
|
||||
// USE INTERRUPT
|
||||
reg {
|
||||
field {sw=rw; hw=rw;} itrs_reg_next [0:0];
|
||||
field {sw=rw; hw=rw;} itrs_halteable_next [1:1];
|
||||
} itrs_next;
|
||||
|
||||
itrs_next.itrs_reg_next->next = itrs_reg->intr;
|
||||
};
|
Loading…
Reference in New Issue
Block a user